Remove old suggestion code.

Change-Id: I2ab9b32ea7622228b0d61b3a16f0695ab04597d7
Fixes: 110361022
Test: robotests
This commit is contained in:
Fan Zhang
2018-06-18 11:30:00 -07:00
parent 11675ada26
commit 6f6e9563b0
7 changed files with 1 additions and 997 deletions

View File

@@ -34,7 +34,6 @@ import android.provider.Settings.Global;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.widget.RemoteViews;
import java.util.ArrayList;
import java.util.Collections;
@@ -96,11 +95,7 @@ public class TileUtils {
/**
* The key used to get the category from metadata of activities of action
* {@link #EXTRA_SETTINGS_ACTION}
* The value must be one of:
* <li>com.android.settings.category.wireless</li>
* <li>com.android.settings.category.device</li>
* <li>com.android.settings.category.personal</li>
* <li>com.android.settings.category.system</li>
* The value must be from {@link CategoryKey}.
*/
private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
@@ -171,17 +166,6 @@ public class TileUtils {
public static final String META_DATA_PREFERENCE_SUMMARY_URI =
"com.android.settings.summary_uri";
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml to specify the
* custom view which should be displayed for the preference. The custom view will be inflated
* as a remote view.
*
* This also can be used with {@link #META_DATA_PREFERENCE_SUMMARY_URI}, by setting the id
* of the summary TextView to '@android:id/summary'.
*/
public static final String META_DATA_PREFERENCE_CUSTOM_VIEW =
"com.android.settings.custom_view";
public static final String SETTING_PKG = "com.android.settings";
/**
@@ -442,11 +426,6 @@ public class TileUtils {
keyHint = metaData.getString(META_DATA_PREFERENCE_KEYHINT);
}
}
if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) {
int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW);
tile.remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
updateSummaryAndTitle(context, providerMap, tile);
}
}
} catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);

View File

@@ -1,25 +0,0 @@
/*
* 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.suggestions;
public class SuggestionCategory {
public String category;
public String pkg;
public boolean multiple;
public boolean exclusive;
public long exclusiveExpireDaysInMillis;
}

View File

@@ -1,85 +0,0 @@
/*
* 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.suggestions;
import android.content.Intent;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.settingslib.drawer.Tile;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SuggestionList {
// Category -> list of suggestion map
private final Map<SuggestionCategory, List<Tile>> mSuggestions;
// A flatten list of all suggestions.
private List<Tile> mSuggestionList;
public SuggestionList() {
mSuggestions = new ArrayMap<>();
}
public void addSuggestions(SuggestionCategory category, List<Tile> suggestions) {
mSuggestions.put(category, suggestions);
}
public List<Tile> getSuggestions() {
if (mSuggestionList != null) {
return mSuggestionList;
}
mSuggestionList = new ArrayList<>();
for (List<Tile> suggestions : mSuggestions.values()) {
mSuggestionList.addAll(suggestions);
}
dedupeSuggestions(mSuggestionList);
return mSuggestionList;
}
public boolean isExclusiveSuggestionCategory() {
if (mSuggestions.size() != 1) {
// If there is no category, or more than 1 category, it's not exclusive by definition.
return false;
}
for (SuggestionCategory category : mSuggestions.keySet()) {
if (category.exclusive) {
return true;
}
}
return false;
}
/**
* Filter suggestions list so they are all unique.
*/
private void dedupeSuggestions(List<Tile> suggestions) {
final Set<String> intents = new ArraySet<>();
for (int i = suggestions.size() - 1; i >= 0; i--) {
final Tile suggestion = suggestions.get(i);
final String intentUri = suggestion.intent.toUri(Intent.URI_INTENT_SCHEME);
if (intents.contains(intentUri)) {
suggestions.remove(i);
} else {
intents.add(intentUri);
}
}
}
}

View File

@@ -1,498 +0,0 @@
/*
* 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.suggestions;
import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.util.Xml;
import android.view.InflateException;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.drawer.TileUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class SuggestionParser {
private static final String TAG = "SuggestionParser";
// If defined, only returns this suggestion if the feature is supported.
public static final String META_DATA_REQUIRE_FEATURE = "com.android.settings.require_feature";
// If defined, only display this optional step if an account of that type exists.
private static final String META_DATA_REQUIRE_ACCOUNT = "com.android.settings.require_account";
// If defined and not true, do not should optional step.
private static final String META_DATA_IS_SUPPORTED = "com.android.settings.is_supported";
// If defined, only display this optional step if the current user is of that type.
private static final String META_DATA_REQUIRE_USER_TYPE =
"com.android.settings.require_user_type";
// If defined, only display this optional step if a connection is available.
private static final String META_DATA_IS_CONNECTION_REQUIRED =
"com.android.settings.require_connection";
// The valid values that setup wizard recognizes for differentiating user types.
private static final String META_DATA_PRIMARY_USER_TYPE_VALUE = "primary";
private static final String META_DATA_ADMIN_USER_TYPE_VALUE = "admin";
private static final String META_DATA_GUEST_USER_TYPE_VALUE = "guest";
private static final String META_DATA_RESTRICTED_USER_TYPE_VALUE = "restricted";
/**
* Allows suggestions to appear after a certain number of days, and to re-appear if dismissed.
* For instance:
* 0,10
* Will appear immediately, but if the user removes it, it will come back after 10 days.
*
* Another example:
* 10,30
* Will only show up after 10 days, and then again after 30.
*/
public static final String META_DATA_DISMISS_CONTROL = "com.android.settings.dismiss";
// Shared prefs keys for storing dismissed state.
// Index into current dismissed state.
public static final String SETUP_TIME = "_setup_time";
private static final String IS_DISMISSED = "_is_dismissed";
// Default dismiss control for smart suggestions.
private static final String DEFAULT_SMART_DISMISS_CONTROL = "0";
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 mDefaultDismissControl;
public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml,
String defaultDismissControl) {
this(
context,
sharedPrefs,
(List<SuggestionCategory>) new SuggestionOrderInflater(context).parse(orderXml),
defaultDismissControl);
}
public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml) {
this(context, sharedPrefs, orderXml, DEFAULT_SMART_DISMISS_CONTROL);
}
@VisibleForTesting
public SuggestionParser(
Context context,
SharedPreferences sharedPrefs,
List<SuggestionCategory> suggestionList,
String defaultDismissControl) {
mContext = context;
mSuggestionList = suggestionList;
mSharedPrefs = sharedPrefs;
mDefaultDismissControl = defaultDismissControl;
}
public SuggestionList getSuggestions(boolean isSmartSuggestionEnabled) {
final SuggestionList suggestionList = new SuggestionList();
final int N = mSuggestionList.size();
for (int i = 0; i < N; i++) {
final SuggestionCategory category = mSuggestionList.get(i);
if (category.exclusive && !isExclusiveCategoryExpired(category)) {
// If suggestions from an exclusive category are present, parsing is stopped
// and only suggestions from that category are displayed. Note that subsequent
// exclusive categories are also ignored.
final List<Tile> exclusiveSuggestions = new ArrayList<>();
// Read suggestion and force isSmartSuggestion to be false so the rule defined
// from each suggestion itself is used.
readSuggestions(category, exclusiveSuggestions, false /* isSmartSuggestion */);
if (!exclusiveSuggestions.isEmpty()) {
final SuggestionList exclusiveList = new SuggestionList();
exclusiveList.addSuggestions(category, exclusiveSuggestions);
return exclusiveList;
}
} else {
// Either the category is not exclusive, or the exclusiveness expired so we should
// treat it as a normal category.
final List<Tile> suggestions = new ArrayList<>();
readSuggestions(category, suggestions, isSmartSuggestionEnabled);
suggestionList.addSuggestions(category, suggestions);
}
}
return suggestionList;
}
/**
* Dismisses a suggestion, returns true if the suggestion has no more dismisses left and should
* be disabled.
*/
public boolean dismissSuggestion(Tile suggestion) {
final String keyBase = suggestion.intent.getComponent().flattenToShortString();
mSharedPrefs.edit()
.putBoolean(keyBase + IS_DISMISSED, true)
.commit();
return true;
}
@VisibleForTesting
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), isSmartSuggestionEnabled)) {
suggestions.remove(i--);
}
}
}
@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);
if (category.pkg != null) {
intent.setPackage(category.pkg);
}
TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,
mAddCache, null, suggestions, true, false, false, true /* shouldUpdateTiles */);
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.
Tile item = suggestions.remove(suggestions.size() - 1);
while (suggestions.size() > countBefore) {
Tile last = suggestions.remove(suggestions.size() - 1);
if (last.priority > item.priority) {
item = last;
}
}
// If category is marked as done, do not add any item.
if (!isCategoryDone(category.category)) {
suggestions.add(item);
}
}
}
private boolean isAvailable(Tile suggestion) {
final String featuresRequired = suggestion.metaData.getString(META_DATA_REQUIRE_FEATURE);
if (featuresRequired != null) {
for (String feature : featuresRequired.split(",")) {
if (TextUtils.isEmpty(feature)) {
Log.w(TAG, "Found empty substring when parsing required features: "
+ featuresRequired);
} else if (!mContext.getPackageManager().hasSystemFeature(feature)) {
Log.i(TAG, suggestion.title + " requires unavailable feature " + feature);
return false;
}
}
}
return true;
}
@RequiresPermission(Manifest.permission.MANAGE_USERS)
private boolean satisifesRequiredUserType(Tile suggestion) {
final String requiredUser = suggestion.metaData.getString(META_DATA_REQUIRE_USER_TYPE);
if (requiredUser != null) {
final UserManager userManager = mContext.getSystemService(UserManager.class);
UserInfo userInfo = userManager.getUserInfo(UserHandle.myUserId());
for (String userType : requiredUser.split("\\|")) {
final boolean primaryUserCondtionMet = userInfo.isPrimary()
&& META_DATA_PRIMARY_USER_TYPE_VALUE.equals(userType);
final boolean adminUserConditionMet = userInfo.isAdmin()
&& META_DATA_ADMIN_USER_TYPE_VALUE.equals(userType);
final boolean guestUserCondtionMet = userInfo.isGuest()
&& META_DATA_GUEST_USER_TYPE_VALUE.equals(userType);
final boolean restrictedUserCondtionMet = userInfo.isRestricted()
&& META_DATA_RESTRICTED_USER_TYPE_VALUE.equals(userType);
if (primaryUserCondtionMet || adminUserConditionMet || guestUserCondtionMet
|| restrictedUserCondtionMet) {
return true;
}
}
Log.i(TAG, suggestion.title + " requires user type " + requiredUser);
return false;
}
return true;
}
public boolean satisfiesRequiredAccount(Tile suggestion) {
final String requiredAccountType = suggestion.metaData.getString(META_DATA_REQUIRE_ACCOUNT);
if (requiredAccountType == null) {
return true;
}
AccountManager accountManager = mContext.getSystemService(AccountManager.class);
Account[] accounts = accountManager.getAccountsByType(requiredAccountType);
boolean satisfiesRequiredAccount = accounts.length > 0;
if (!satisfiesRequiredAccount) {
Log.i(TAG, suggestion.title + " requires unavailable account type "
+ requiredAccountType);
}
return satisfiesRequiredAccount;
}
public boolean isSupported(Tile suggestion) {
final int isSupportedResource = suggestion.metaData.getInt(META_DATA_IS_SUPPORTED);
try {
if (suggestion.intent == null) {
return false;
}
final Resources res = mContext.getPackageManager().getResourcesForActivity(
suggestion.intent.getComponent());
boolean isSupported =
isSupportedResource != 0 ? res.getBoolean(isSupportedResource) : true;
if (!isSupported) {
Log.i(TAG, suggestion.title + " requires unsupported resource "
+ isSupportedResource);
}
return isSupported;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent());
return false;
} catch (Resources.NotFoundException e) {
Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent(), e);
return false;
}
}
private boolean satisfiesConnectivity(Tile suggestion) {
final boolean isConnectionRequired =
suggestion.metaData.getBoolean(META_DATA_IS_CONNECTION_REQUIRED);
if (!isConnectionRequired) {
return true;
}
ConnectivityManager cm =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
boolean satisfiesConnectivity = netInfo != null && netInfo.isConnectedOrConnecting();
if (!satisfiesConnectivity) {
Log.i(TAG, suggestion.title + " is missing required connection.");
}
return satisfiesConnectivity;
}
public boolean isCategoryDone(String category) {
String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
}
public void markCategoryDone(String category) {
String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
Settings.Secure.putInt(mContext.getContentResolver(), name, 1);
}
/**
* Whether or not the category's exclusiveness has expired.
*/
private boolean isExclusiveCategoryExpired(SuggestionCategory category) {
final String keySetupTime = category.category + SETUP_TIME;
final long currentTime = System.currentTimeMillis();
if (!mSharedPrefs.contains(keySetupTime)) {
mSharedPrefs.edit()
.putLong(keySetupTime, currentTime)
.commit();
}
if (category.exclusiveExpireDaysInMillis < 0) {
// negative means never expires
return false;
}
final long setupTime = mSharedPrefs.getLong(keySetupTime, 0);
final long elapsedTime = currentTime - setupTime;
Log.d(TAG, "Day " + elapsedTime / DateUtils.DAY_IN_MILLIS + " for " + category.category);
return elapsedTime > category.exclusiveExpireDaysInMillis;
}
@VisibleForTesting
boolean isDismissed(Tile suggestion, boolean isSmartSuggestionEnabled) {
String dismissControl = getDismissControl(suggestion, isSmartSuggestionEnabled);
String keyBase = suggestion.intent.getComponent().flattenToShortString();
if (!mSharedPrefs.contains(keyBase + SETUP_TIME)) {
mSharedPrefs.edit()
.putLong(keyBase + SETUP_TIME, System.currentTimeMillis())
.commit();
}
// Check if it's already manually dismissed
final boolean isDismissed = mSharedPrefs.getBoolean(keyBase + IS_DISMISSED, false);
if (isDismissed) {
return true;
}
if (dismissControl == null) {
return false;
}
// Parse when suggestion should first appear. return true to artificially hide suggestion
// before then.
int firstAppearDay = parseDismissString(dismissControl);
long firstAppearDayInMs = getEndTime(mSharedPrefs.getLong(keyBase + SETUP_TIME, 0),
firstAppearDay);
if (System.currentTimeMillis() >= firstAppearDayInMs) {
// Dismiss timeout has passed, undismiss it.
mSharedPrefs.edit()
.putBoolean(keyBase + IS_DISMISSED, false)
.commit();
return false;
}
return true;
}
private long getEndTime(long startTime, int daysDelay) {
long days = daysDelay * DateUtils.DAY_IN_MILLIS;
return startTime + days;
}
/**
* Parse the first int from a string formatted as "0,1,2..."
* The value means suggestion should first appear on Day X.
*/
private int parseDismissString(String dismissControl) {
final String[] dismissStrs = dismissControl.split(",");
return Integer.parseInt(dismissStrs[0]);
}
private String getDismissControl(Tile suggestion, boolean isSmartSuggestionEnabled) {
if (isSmartSuggestionEnabled) {
return mDefaultDismissControl;
} else {
return suggestion.metaData.getString(META_DATA_DISMISS_CONTROL);
}
}
private static class SuggestionOrderInflater {
private static final String TAG_LIST = "optional-steps";
private static final String TAG_ITEM = "step";
private static final String ATTR_CATEGORY = "category";
private static final String ATTR_PACKAGE = "package";
private static final String ATTR_MULTIPLE = "multiple";
private static final String ATTR_EXCLUSIVE = "exclusive";
private static final String ATTR_EXCLUSIVE_EXPIRE_DAYS = "exclusiveExpireDays";
private final Context mContext;
public SuggestionOrderInflater(Context context) {
mContext = context;
}
public Object parse(int resource) {
XmlPullParser parser = mContext.getResources().getXml(resource);
final AttributeSet attrs = Xml.asAttributeSet(parser);
try {
// Look for the root node.
int type;
do {
type = parser.next();
} while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
// Temp is the root that was found in the xml
Object xmlRoot = onCreateItem(parser.getName(), attrs);
// Inflate all children under temp
rParse(parser, xmlRoot, attrs);
return xmlRoot;
} catch (XmlPullParserException | IOException e) {
Log.w(TAG, "Problem parser resource " + resource, e);
return null;
}
}
/**
* Recursive method used to descend down the xml hierarchy and instantiate
* items, instantiate their children.
*/
private void rParse(XmlPullParser parser, Object parent, final AttributeSet attrs)
throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
Object item = onCreateItem(name, attrs);
onAddChildItem(parent, item);
rParse(parser, item, attrs);
}
}
protected void onAddChildItem(Object parent, Object child) {
if (parent instanceof List<?> && child instanceof SuggestionCategory) {
((List<SuggestionCategory>) parent).add((SuggestionCategory) child);
} else {
throw new IllegalArgumentException("Parent was not a list");
}
}
protected Object onCreateItem(String name, AttributeSet attrs) {
if (name.equals(TAG_LIST)) {
return new ArrayList<SuggestionCategory>();
} else if (name.equals(TAG_ITEM)) {
SuggestionCategory category = new SuggestionCategory();
category.category = attrs.getAttributeValue(null, ATTR_CATEGORY);
category.pkg = attrs.getAttributeValue(null, ATTR_PACKAGE);
String multiple = attrs.getAttributeValue(null, ATTR_MULTIPLE);
category.multiple = !TextUtils.isEmpty(multiple) && Boolean.parseBoolean(multiple);
String exclusive = attrs.getAttributeValue(null, ATTR_EXCLUSIVE);
category.exclusive =
!TextUtils.isEmpty(exclusive) && Boolean.parseBoolean(exclusive);
String expireDaysAttr = attrs.getAttributeValue(null,
ATTR_EXCLUSIVE_EXPIRE_DAYS);
long expireDays = !TextUtils.isEmpty(expireDaysAttr)
? Integer.parseInt(expireDaysAttr)
: -1;
category.exclusiveExpireDaysInMillis = DateUtils.DAY_IN_MILLIS * expireDays;
return category;
} else {
throw new IllegalArgumentException("Unknown item " + name);
}
}
}
}

View File

@@ -1,31 +0,0 @@
<?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.DEFERRED_SETUP"
exclusive="true" />
<step category="com.android.settings.suggested.category.LOCK_SCREEN" />
<step category="com.android.settings.suggested.category.TRUST_AGENT" />
<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.GESTURE" />
<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

@@ -28,7 +28,6 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.RuntimeEnvironment.application;
import static org.robolectric.shadow.api.Shadow.extract;
import android.app.ActivityManager;
import android.content.ContentResolver;
@@ -52,9 +51,6 @@ import android.util.ArrayMap;
import android.util.Pair;
import android.widget.RemoteViews;
import com.android.settingslib.R;
import com.android.settingslib.suggestions.SuggestionParser;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -67,7 +63,6 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -163,35 +158,6 @@ public class TileUtilsTest {
assertThat(outTiles.isEmpty()).isTrue();
}
@Test
public void getTilesForIntent_shouldSkipFilteredApps() {
Intent intent = new Intent();
Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
List<Tile> outTiles = new ArrayList<>();
List<ResolveInfo> info = new ArrayList<>();
ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
URI_GET_SUMMARY);
addMetadataToInfo(resolveInfo, "com.android.settings.require_account", "com.google");
addMetadataToInfo(resolveInfo, "com.android.settings.require_connection", "true");
info.add(resolveInfo);
when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
.thenReturn(info);
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
SuggestionParser parser = new SuggestionParser(
mContext,
null,
Collections.emptyList(),
"0,10");
parser.filterSuggestions(outTiles, 0, false);
assertThat(outTiles.size()).isEqualTo(0);
}
@Test
public void getCategories_shouldHandleExtraIntentAction() {
final String testCategory = "category1";
@@ -392,108 +358,6 @@ public class TileUtilsTest {
assertThat(outTiles.size()).isEqualTo(1);
}
@Test
public void getTilesForIntent_shouldShowRemoteViewIfSpecified() {
Intent intent = new Intent();
Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
List<Tile> outTiles = new ArrayList<>();
List<ResolveInfo> info = new ArrayList<>();
ResolveInfo resolveInfo = newInfo(true, null /* category */);
resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view",
R.layout.user_preference);
info.add(resolveInfo);
when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
.thenReturn(info);
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
Tile tile = outTiles.get(0);
assertThat(tile.remoteViews).isNotNull();
assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
}
@Test
public void getTilesForIntent_summaryUriSpecified_shouldOverrideRemoteViewSummary()
throws RemoteException {
Intent intent = new Intent();
Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
List<Tile> outTiles = new ArrayList<>();
List<ResolveInfo> info = new ArrayList<>();
ResolveInfo resolveInfo = newInfo(true, null /* category */, null,
null, URI_GET_SUMMARY);
resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view",
R.layout.user_preference);
info.add(resolveInfo);
when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
.thenReturn(info);
// Mock the content provider interaction.
Bundle bundle = new Bundle();
bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY, "new summary text");
when(mIContentProvider.call(anyString(),
eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_SUMMARY))), eq(URI_GET_SUMMARY),
any())).thenReturn(bundle);
when(mContentResolver.acquireUnstableProvider(anyString()))
.thenReturn(mIContentProvider);
when(mContentResolver.acquireUnstableProvider(any(Uri.class)))
.thenReturn(mIContentProvider);
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
Tile tile = outTiles.get(0);
assertThat(tile.remoteViews).isNotNull();
assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
// Make sure the summary TextView got a new text string.
TileUtilsShadowRemoteViews shadowRemoteViews = extract(tile.remoteViews);
assertThat(shadowRemoteViews.overrideViewId).isEqualTo(android.R.id.summary);
assertThat(shadowRemoteViews.overrideText).isEqualTo("new summary text");
}
@Test
public void getTilesForIntent_providerUnavailable_shouldNotOverrideRemoteViewSummary()
throws RemoteException {
Intent intent = new Intent();
Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
List<Tile> outTiles = new ArrayList<>();
List<ResolveInfo> info = new ArrayList<>();
ResolveInfo resolveInfo = newInfo(true, null /* category */, null,
null, URI_GET_SUMMARY);
resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view",
R.layout.user_preference);
info.add(resolveInfo);
when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
.thenReturn(info);
// Mock the content provider interaction.
Bundle bundle = new Bundle();
bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY, "new summary text");
when(mIContentProvider.call(anyString(),
eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_SUMMARY))), eq(URI_GET_SUMMARY),
any())).thenReturn(bundle);
TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
null /* defaultCategory */, outTiles, false /* usePriority */,
false /* checkCategory */, true /* forceTintExternalIcon */);
assertThat(outTiles.size()).isEqualTo(1);
Tile tile = outTiles.get(0);
assertThat(tile.remoteViews).isNotNull();
assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
// Make sure the summary TextView didn't get any text view updates.
TileUtilsShadowRemoteViews shadowRemoteViews = extract(tile.remoteViews);
assertThat(shadowRemoteViews.overrideViewId).isNull();
assertThat(shadowRemoteViews.overrideText).isNull();
}
public static ResolveInfo newInfo(boolean systemApp, String category) {
return newInfo(systemApp, category, null);
}

View File

@@ -1,200 +0,0 @@
/*
* 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.suggestions;
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.RuntimeEnvironment.application;
import static org.robolectric.shadow.api.Shadow.extract;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.preference.PreferenceManager;
import com.android.settingslib.SettingsLibRobolectricTestRunner;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.drawer.TileUtilsTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.shadows.ShadowPackageManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@RunWith(SettingsLibRobolectricTestRunner.class)
public class SuggestionParserTest {
private ShadowPackageManager mPackageManager;
private SuggestionParser mSuggestionParser;
private SuggestionCategory mMultipleCategory;
private SuggestionCategory mExclusiveCategory;
private SuggestionCategory mExpiredExclusiveCategory;
private List<Tile> mSuggestionsBeforeDismiss;
private List<Tile> mSuggestionsAfterDismiss;
private SharedPreferences mPrefs;
private Tile mSuggestion;
@Before
public void setUp() {
mPackageManager = extract(application.getPackageManager());
mPrefs = PreferenceManager.getDefaultSharedPreferences(application);
mSuggestion = new Tile();
mSuggestion.intent = new Intent("action");
mSuggestion.intent.setComponent(new ComponentName("pkg", "cls"));
mSuggestion.metaData = new Bundle();
mMultipleCategory = new SuggestionCategory();
mMultipleCategory.category = "category1";
mMultipleCategory.multiple = true;
mExclusiveCategory = new SuggestionCategory();
mExclusiveCategory.category = "category2";
mExclusiveCategory.exclusive = true;
mExpiredExclusiveCategory = new SuggestionCategory();
mExpiredExclusiveCategory.category = "category3";
mExpiredExclusiveCategory.exclusive = true;
mExpiredExclusiveCategory.exclusiveExpireDaysInMillis = 0;
mSuggestionParser = new SuggestionParser(application, mPrefs,
Arrays.asList(mMultipleCategory, mExclusiveCategory, mExpiredExclusiveCategory),
"0");
ResolveInfo info1 = TileUtilsTest.newInfo(true, null);
info1.activityInfo.packageName = "pkg";
ResolveInfo infoDupe1 = TileUtilsTest.newInfo(true, null);
infoDupe1.activityInfo.packageName = "pkg";
ResolveInfo info2 = TileUtilsTest.newInfo(true, null);
info2.activityInfo.packageName = "pkg2";
ResolveInfo info3 = TileUtilsTest.newInfo(true, null);
info3.activityInfo.packageName = "pkg3";
ResolveInfo info4 = TileUtilsTest.newInfo(true, null);
info4.activityInfo.packageName = "pkg4";
Intent intent1 = new Intent(Intent.ACTION_MAIN).addCategory("category1");
Intent intent2 = new Intent(Intent.ACTION_MAIN).addCategory("category2");
Intent intent3 = new Intent(Intent.ACTION_MAIN).addCategory("category3");
mPackageManager.addResolveInfoForIntent(intent1, info1);
mPackageManager.addResolveInfoForIntent(intent1, info2);
mPackageManager.addResolveInfoForIntent(intent1, infoDupe1);
mPackageManager.addResolveInfoForIntent(intent2, info3);
mPackageManager.addResolveInfoForIntent(intent3, info4);
}
@Test
public void dismissSuggestion_shouldDismiss() {
assertThat(mSuggestionParser.dismissSuggestion(mSuggestion)).isTrue();
}
@Test
public void testGetSuggestions_withoutSmartSuggestions_shouldDismiss() {
readAndDismissSuggestion(false);
mSuggestionParser.readSuggestions(mMultipleCategory, mSuggestionsAfterDismiss, false);
assertThat(mSuggestionsBeforeDismiss).hasSize(2);
assertThat(mSuggestionsAfterDismiss).hasSize(1);
assertThat(mSuggestionsBeforeDismiss.get(1)).isEqualTo(mSuggestionsAfterDismiss.get(0));
}
@Test
public void testGetSuggestions_withSmartSuggestions_shouldDismiss() {
readAndDismissSuggestion(true);
assertThat(mSuggestionsBeforeDismiss).hasSize(2);
assertThat(mSuggestionsAfterDismiss).hasSize(1);
}
@Test
public void testGetSuggestion_exclusiveNotAvailable_onlyRegularCategoryAndNoDupe() {
mPackageManager.removeResolveInfosForIntent(
new Intent(Intent.ACTION_MAIN).addCategory("category2"),
"pkg3");
mPackageManager.removeResolveInfosForIntent(
new Intent(Intent.ACTION_MAIN).addCategory("category3"),
"pkg4");
// If exclusive item is not available, the other categories should be shown
final SuggestionList sl =
mSuggestionParser.getSuggestions(false /* isSmartSuggestionEnabled */);
final List<Tile> suggestions = sl.getSuggestions();
assertThat(suggestions).hasSize(2);
assertThat(suggestions.get(0).intent.getComponent().getPackageName()).isEqualTo("pkg");
assertThat(suggestions.get(1).intent.getComponent().getPackageName()).isEqualTo("pkg2");
}
@Test
public void testGetSuggestion_exclusiveExpiredAvailable_shouldLoadWithRegularCategory() {
// First remove permanent exclusive
mPackageManager.removeResolveInfosForIntent(
new Intent(Intent.ACTION_MAIN).addCategory("category2"),
"pkg3");
// Set the other exclusive to be expired.
mPrefs.edit()
.putLong(mExpiredExclusiveCategory.category + "_setup_time",
System.currentTimeMillis() - 1000)
.commit();
// If exclusive is expired, they should be shown together with the other categories
final SuggestionList sl =
mSuggestionParser.getSuggestions(true /* isSmartSuggestionEnabled */);
final List<Tile> suggestions = sl.getSuggestions();
assertThat(suggestions).hasSize(3);
}
@Test
public void testGetSuggestions_exclusive() {
final SuggestionList sl =
mSuggestionParser.getSuggestions(false /* isSmartSuggestionEnabled */);
final List<Tile> suggestions = sl.getSuggestions();
assertThat(suggestions).hasSize(1);
}
@Test
public void isSuggestionDismissed_dismissedSuggestion_shouldReturnTrue() {
final Tile suggestion = new Tile();
suggestion.metaData = new Bundle();
suggestion.metaData.putString(SuggestionParser.META_DATA_DISMISS_CONTROL, "1,2,3");
suggestion.intent = new Intent().setComponent(new ComponentName("pkg", "cls"));
// Dismiss suggestion when smart suggestion is not enabled.
mSuggestionParser.dismissSuggestion(suggestion);
assertThat(mSuggestionParser.isDismissed(suggestion, true /* isSmartSuggestionEnabled */))
.isTrue();
}
private void readAndDismissSuggestion(boolean isSmartSuggestionEnabled) {
mSuggestionsBeforeDismiss = new ArrayList<>();
mSuggestionsAfterDismiss = new ArrayList<>();
mSuggestionParser.readSuggestions(
mMultipleCategory, mSuggestionsBeforeDismiss, isSmartSuggestionEnabled);
final Tile suggestion = mSuggestionsBeforeDismiss.get(0);
if (mSuggestionParser.dismissSuggestion(suggestion)) {
mPackageManager.removeResolveInfosForIntent(
new Intent(Intent.ACTION_MAIN).addCategory(mMultipleCategory.category),
suggestion.intent.getComponent().getPackageName());
}
mSuggestionParser.readSuggestions(
mMultipleCategory, mSuggestionsAfterDismiss, isSmartSuggestionEnabled);
}
}