Merge "Modifying SuggestionParser to support dismiss logic of smart suggestions."

This commit is contained in:
Soroosh Mariooryad
2017-02-07 02:12:55 +00:00
committed by Android (Google) Code Review
6 changed files with 263 additions and 18 deletions

View File

@@ -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<SuggestionCategory> mSuggestionList;
private final ArrayMap<Pair<String, String>, 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<SuggestionCategory>) 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<SuggestionCategory>();
mSharedPrefs = sharedPrefs;
mSmartDismissControl = DEFAULT_SMART_DISMISS_CONTROL;
Log.wtf(TAG, "Only use this constructor for testing");
}
public List<Tile> getSuggestions() {
return getSuggestions(false);
}
public List<Tile> getSuggestions(boolean isSmartSuggestionEnabled) {
List<Tile> 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<Tile> suggestions, int countBefore) {
public void filterSuggestions(
List<Tile> 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<Tile> suggestions) {
@VisibleForTesting
void readSuggestions(
SuggestionCategory category, List<Tile> 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;

View File

@@ -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)

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 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.
-->
<optional-steps>
<step category="com.android.settings.suggested.category.LOCK_SCREEN" />
<step category="com.android.settings.suggested.category.EMAIL" />
<step category="com.android.settings.suggested.category.PARTNER_ACCOUNT"
multiple="true" />
<step category="com.android.settings.suggested.category.HOTWORD" />
<step category="com.android.settings.suggested.category.DEFAULT"
multiple="true" />
<step category="com.android.settings.suggested.category.SETTINGS_ONLY"
multiple="true" />
</optional-steps>

View File

@@ -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;
}
}

View File

@@ -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<Tile> mSuggestionsBeforeDismiss;
private List<Tile> mSuggestionsAfterDismiss;
private SharedPreferences mPrefs;
private Tile mSuggestion;
private List<ResolveInfo> 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<Tile>();
mSuggestionsAfterDismiss = new ArrayList<Tile>();
mSuggestionParser.readSuggestions(
mSuggestioCategory, mSuggestionsBeforeDismiss, isSmartSuggestionEnabled);
if (mSuggestionParser.dismissSuggestion(
mSuggestionsBeforeDismiss.get(0), isSmartSuggestionEnabled)) {
mInfo.remove(0);
}
mSuggestionParser.readSuggestions(
mSuggestioCategory, mSuggestionsAfterDismiss, isSmartSuggestionEnabled);
}
}

View File

@@ -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();