summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFan Zhang <zhfan@google.com>2017-09-27 10:23:23 -0700
committerFan Zhang <zhfan@google.com>2017-09-27 12:08:56 -0700
commit04c0816541080d46f70803b58ce8666098f7089a (patch)
tree5aaf3351e7587f8ee5b54e953abeae51cf314ad1
parent3f7535a8c62e98f1e21e1029235967b2deda241e (diff)
downloadandroid_packages_apps_SettingsIntelligence-04c0816541080d46f70803b58ce8666098f7089a.tar.gz
android_packages_apps_SettingsIntelligence-04c0816541080d46f70803b58ce8666098f7089a.tar.bz2
android_packages_apps_SettingsIntelligence-04c0816541080d46f70803b58ce8666098f7089a.zip
Sync to latest SettingsIntelligence source code
Test: builds & robotests Change-Id: Ie83909a9747afd07c541c6236224f079da8a2dc9
-rw-r--r--AndroidManifest.xml2
-rw-r--r--res/mipmap-anydpi/ic_launcher.xml (renamed from res/drawable/ic_launcher_settings_intelligence.xml)0
-rw-r--r--src/com/android/settings/intelligence/suggestions/SuggestionDismissHandler.java58
-rw-r--r--src/com/android/settings/intelligence/suggestions/SuggestionParser.java203
-rw-r--r--src/com/android/settings/intelligence/suggestions/SuggestionService.java25
-rw-r--r--src/com/android/settings/intelligence/suggestions/eligibility/AccountEligibilityChecker.java48
-rw-r--r--src/com/android/settings/intelligence/suggestions/eligibility/ConnectivityEligibilityChecker.java51
-rw-r--r--src/com/android/settings/intelligence/suggestions/eligibility/DismissedChecker.java111
-rw-r--r--src/com/android/settings/intelligence/suggestions/eligibility/FeatureEligibilityChecker.java51
-rw-r--r--src/com/android/settings/intelligence/suggestions/eligibility/ProviderEligibilityChecker.java70
-rw-r--r--src/com/android/settings/intelligence/suggestions/model/CandidateSuggestion.java212
-rw-r--r--src/com/android/settings/intelligence/suggestions/model/SuggestionCategory.java72
-rw-r--r--src/com/android/settings/intelligence/suggestions/model/SuggestionCategoryRegistry.java72
-rw-r--r--src/com/android/settings/intelligence/suggestions/model/SuggestionListBuilder.java69
-rw-r--r--src/com/android/settings/intelligence/suggestions/ranking/EventStore.java2
-rw-r--r--tests/robotests/Android.mk4
-rw-r--r--tests/robotests/runners/android_mk/com/android/settings/intelligence/SettingsIntelligenceRobolectricTestRunner.java27
-rw-r--r--tests/robotests/runners/gradle/com/android/settings/intelligence/SettingsIntelligenceRobolectricTestRunner.java36
-rw-r--r--tests/robotests/runners/gradle/com/android/settings/intelligence/TestConfig.java (renamed from tests/robotests/src/com/android/settings/intelligence/SuggestionServiceTest.java)14
-rw-r--r--tests/robotests/src/android/service/settings/suggestions/Suggestion.java7
-rw-r--r--tests/robotests/src/android/service/settings/suggestions/SuggestionService.java31
-rw-r--r--tests/robotests/src/com/android/settings/intelligence/suggestions/SuggestionParserTest.java120
-rw-r--r--tests/robotests/src/com/android/settings/intelligence/suggestions/SuggestionServiceTest.java67
-rw-r--r--tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/AccountEligibilityCheckerTest.java96
-rw-r--r--tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/ConnectivityEligibilityCheckerTest.java106
-rw-r--r--tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/DismissedCheckerTest.java98
-rw-r--r--tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/FeatureEligibilityCheckerTest.java93
-rw-r--r--tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/ProviderEligibilityCheckerTest.java71
-rw-r--r--tests/robotests/src/com/android/settings/intelligence/suggestions/model/CandidateSuggestionTest.java125
-rw-r--r--tests/robotests/src/com/android/settings/intelligence/suggestions/model/SuggestionCategoryRegistryTest.java59
-rw-r--r--tests/robotests/src/com/android/settings/intelligence/suggestions/model/SuggestionListBuilderTest.java72
-rw-r--r--tests/robotests/src/com/android/settings/intelligence/suggestions/ranking/SuggestionRankerTest.java31
32 files changed, 2035 insertions, 68 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1791d3f..76a9c68 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -23,7 +23,7 @@
<application
android:label="@string/app_name_settings_intelligence"
- android:icon="@drawable/ic_launcher_settings_intelligence">
+ android:icon="@mipmap/ic_launcher">
<service
android:name=".suggestions.SuggestionService"
android:exported="true"
diff --git a/res/drawable/ic_launcher_settings_intelligence.xml b/res/mipmap-anydpi/ic_launcher.xml
index 07e226d..07e226d 100644
--- a/res/drawable/ic_launcher_settings_intelligence.xml
+++ b/res/mipmap-anydpi/ic_launcher.xml
diff --git a/src/com/android/settings/intelligence/suggestions/SuggestionDismissHandler.java b/src/com/android/settings/intelligence/suggestions/SuggestionDismissHandler.java
new file mode 100644
index 0000000..400227b
--- /dev/null
+++ b/src/com/android/settings/intelligence/suggestions/SuggestionDismissHandler.java
@@ -0,0 +1,58 @@
+/*
+ * 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.settings.intelligence.suggestions;
+
+import android.content.Context;
+
+public class SuggestionDismissHandler {
+
+ private static final String IS_DISMISSED = "_is_dismissed";
+
+ private static SuggestionDismissHandler sDismissHandler;
+
+ private SuggestionDismissHandler() {}
+
+ public static SuggestionDismissHandler getInstance() {
+ if (sDismissHandler == null) {
+ sDismissHandler = new SuggestionDismissHandler();
+ }
+ return sDismissHandler;
+ }
+
+ public void markSuggestionDismissed(Context context, String id) {
+ SuggestionService.getSharedPrefs(context)
+ .edit()
+ .putBoolean(getDismissKey(id), true)
+ .apply();
+ }
+
+ public void markSuggestionNotDismissed(Context context, String id) {
+ SuggestionService.getSharedPrefs(context)
+ .edit()
+ .putBoolean(getDismissKey(id), false)
+ .apply();
+ }
+
+ public boolean isSuggestionDismissed(Context context, String id) {
+ return SuggestionService.getSharedPrefs(context)
+ .getBoolean(getDismissKey(id), false);
+ }
+
+ private static String getDismissKey(String id) {
+ return id + IS_DISMISSED;
+ }
+}
diff --git a/src/com/android/settings/intelligence/suggestions/SuggestionParser.java b/src/com/android/settings/intelligence/suggestions/SuggestionParser.java
new file mode 100644
index 0000000..c5af82e
--- /dev/null
+++ b/src/com/android/settings/intelligence/suggestions/SuggestionParser.java
@@ -0,0 +1,203 @@
+/*
+ * 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.settings.intelligence.suggestions;
+
+import static com.android.settings.intelligence.suggestions.model.SuggestionCategoryRegistry
+ .CATEGORIES;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.service.settings.suggestions.Suggestion;
+import android.support.annotation.VisibleForTesting;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.settings.intelligence.suggestions.model.CandidateSuggestion;
+import com.android.settings.intelligence.suggestions.model.SuggestionCategory;
+import com.android.settings.intelligence.suggestions.model.SuggestionListBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Main parser to get suggestions from all providers.
+ * <p/>
+ * Copied from framework/packages/SettingsLib/src/.../SuggestionParser
+ */
+class SuggestionParser {
+
+ private static final String TAG = "SuggestionParser";
+ private static final String SETUP_TIME = "_setup_time";
+
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final SharedPreferences mSharedPrefs;
+ private final Map<String, Suggestion> mAddCache;
+
+
+ public SuggestionParser(Context context) {
+ mContext = context.getApplicationContext();
+ mPackageManager = context.getPackageManager();
+ mAddCache = new ArrayMap<>();
+ mSharedPrefs = SuggestionService.getSharedPrefs(mContext);
+ }
+
+ public List<Suggestion> getSuggestions() {
+ final SuggestionListBuilder suggestionBuilder = new SuggestionListBuilder();
+
+ for (SuggestionCategory category : CATEGORIES) {
+ if (category.isExclusive() && !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.
+
+ // Read suggestion and force ignoreSuggestionDismissRule to be false so the rule
+ // defined from each suggestion itself is used.
+ final List<Suggestion> exclusiveSuggestions =
+ readSuggestions(category, false /* ignoreDismissRule */);
+ if (!exclusiveSuggestions.isEmpty()) {
+ suggestionBuilder.addSuggestions(category, exclusiveSuggestions);
+ return suggestionBuilder.build();
+ }
+ } else {
+ // Either the category is not exclusive, or the exclusiveness expired so we should
+ // treat it as a normal category.
+ final List<Suggestion> suggestions =
+ readSuggestions(category, true /* ignoreDismissRule */);
+ suggestionBuilder.addSuggestions(category, suggestions);
+ }
+ }
+ return suggestionBuilder.build();
+ }
+
+ @VisibleForTesting
+ List<Suggestion> readSuggestions(SuggestionCategory category, boolean ignoreDismissRule) {
+ final List<Suggestion> suggestions = new ArrayList<>();
+ final Intent probe = new Intent(Intent.ACTION_MAIN);
+ probe.addCategory(category.getCategory());
+ List<ResolveInfo> results = mPackageManager
+ .queryIntentActivities(probe, PackageManager.GET_META_DATA);
+ for (ResolveInfo resolved : results) {
+ final CandidateSuggestion candidate = new CandidateSuggestion(mContext, resolved,
+ ignoreDismissRule);
+ if (!candidate.isEligible()) {
+ continue;
+ }
+
+ final String id = candidate.getId();
+ Suggestion suggestion = mAddCache.get(id);
+ if (suggestion == null) {
+ suggestion = candidate.toSuggestion();
+ mAddCache.put(id, suggestion);
+ }
+ if (!suggestions.contains(suggestion)) {
+ suggestions.add(suggestion);
+ }
+ }
+ return suggestions;
+ }
+
+ /**
+ * Whether or not the category's exclusiveness has expired.
+ */
+ private boolean isExclusiveCategoryExpired(SuggestionCategory category) {
+ final String keySetupTime = category.getCategory() + SETUP_TIME;
+ final long currentTime = System.currentTimeMillis();
+ if (!mSharedPrefs.contains(keySetupTime)) {
+ mSharedPrefs.edit()
+ .putLong(keySetupTime, currentTime)
+ .commit();
+ }
+ if (category.getExclusiveExpireDaysInMillis() < 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.getCategory());
+ return elapsedTime > category.getExclusiveExpireDaysInMillis();
+ }
+
+//
+// /**
+// * Gets text associated with the input key from the content provider.
+// * @param context context
+// * @param uriString URI for the content provider
+// * @param providerMap Maps URI authorities to providers
+// * @param key Key mapping to the text in bundle returned by the content provider
+// * @return Text associated with the key, if returned by the content provider
+// */
+// public static String getTextFromUri(Context context, String uriString,
+// Map<String, IContentProvider> providerMap, String key) {
+// Bundle bundle = getBundleFromUri(context, uriString, providerMap);
+// return (bundle != null) ? bundle.getString(key) : null;
+// }
+//
+// private static Bundle getBundleFromUri(Context context, String uriString,
+// Map<String, IContentProvider> providerMap) {
+// if (TextUtils.isEmpty(uriString)) {
+// return null;
+// }
+// Uri uri = Uri.parse(uriString);
+// String method = getMethodFromUri(uri);
+// if (TextUtils.isEmpty(method)) {
+// return null;
+// }
+// IContentProvider provider = getProviderFromUri(context, uri, providerMap);
+// if (provider == null) {
+// return null;
+// }
+// try {
+// return provider.call(context.getPackageName(), method, uriString, null);
+// } catch (RemoteException e) {
+// return null;
+// }
+// }
+//
+// private static IContentProvider getProviderFromUri(Context context, Uri uri,
+// Map<String, IContentProvider> providerMap) {
+// if (uri == null) {
+// return null;
+// }
+// String authority = uri.getAuthority();
+// if (TextUtils.isEmpty(authority)) {
+// return null;
+// }
+// if (!providerMap.containsKey(authority)) {
+// providerMap.put(authority, context.getContentResolver().acquireUnstableProvider(uri));
+// }
+// return providerMap.get(authority);
+// }
+//
+// /** Returns the first path segment of the uri if it exists as the method, otherwise null. */
+// static String getMethodFromUri(Uri uri) {
+// if (uri == null) {
+// return null;
+// }
+// List<String> pathSegments = uri.getPathSegments();
+// if ((pathSegments == null) || pathSegments.isEmpty()) {
+// return null;
+// }
+// return pathSegments.get(0);
+// }
+}
diff --git a/src/com/android/settings/intelligence/suggestions/SuggestionService.java b/src/com/android/settings/intelligence/suggestions/SuggestionService.java
index 6014ee8..b8ccef0 100644
--- a/src/com/android/settings/intelligence/suggestions/SuggestionService.java
+++ b/src/com/android/settings/intelligence/suggestions/SuggestionService.java
@@ -16,26 +16,39 @@
package com.android.settings.intelligence.suggestions;
+import android.content.Context;
+import android.content.SharedPreferences;
import android.service.settings.suggestions.Suggestion;
import android.util.Log;
-import java.util.ArrayList;
+import com.android.settings.intelligence.suggestions.ranking.SuggestionRanker;
+
import java.util.List;
-public class SuggestionService extends android.service.settings.suggestions.SuggestionService {
+public class SuggestionService extends android.service.settings.suggestions.SuggestionService {
private static final String TAG = "SuggestionService";
+ private static final String SHARED_PREF_FILENAME = "suggestions";
+
@Override
public List<Suggestion> onGetSuggestions() {
- final List<Suggestion> data = new ArrayList<>();
- data.add(new Suggestion.Builder("test").build());
- return data;
+ final SuggestionParser parser = new SuggestionParser(this);
+ final List<Suggestion> list = parser.getSuggestions();
+ SuggestionRanker.getInstance(this).rankSuggestions(list);
+ return list;
}
@Override
public void onSuggestionDismissed(Suggestion suggestion) {
- Log.d(TAG, "dismissing suggestion " + suggestion.getTitle());
+ final String id = suggestion.getId();
+ Log.d(TAG, "dismissing suggestion " + id);
+ SuggestionDismissHandler.getInstance()
+ .markSuggestionDismissed(this /* context */, id);
}
+ public static SharedPreferences getSharedPrefs(Context context) {
+ return context.getApplicationContext()
+ .getSharedPreferences(SHARED_PREF_FILENAME, Context.MODE_PRIVATE);
+ }
}
diff --git a/src/com/android/settings/intelligence/suggestions/eligibility/AccountEligibilityChecker.java b/src/com/android/settings/intelligence/suggestions/eligibility/AccountEligibilityChecker.java
new file mode 100644
index 0000000..447dc77
--- /dev/null
+++ b/src/com/android/settings/intelligence/suggestions/eligibility/AccountEligibilityChecker.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.settings.intelligence.suggestions.eligibility;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+public class AccountEligibilityChecker {
+ /**
+ * If defined, only display this optional step if an account of that type exists.
+ */
+ @VisibleForTesting
+ static final String META_DATA_REQUIRE_ACCOUNT = "com.android.settings.require_account";
+ private static final String TAG = "AccountEligibility";
+
+ public static boolean isEligible(Context context, String id, ResolveInfo info) {
+ final String requiredAccountType = info.activityInfo.metaData.
+ getString(META_DATA_REQUIRE_ACCOUNT);
+ if (requiredAccountType == null) {
+ return true;
+ }
+ AccountManager accountManager = AccountManager.get(context);
+ Account[] accounts = accountManager.getAccountsByType(requiredAccountType);
+ boolean satisfiesRequiredAccount = accounts.length > 0;
+ if (!satisfiesRequiredAccount) {
+ Log.i(TAG, id + " requires unavailable account type " + requiredAccountType);
+ }
+ return satisfiesRequiredAccount;
+ }
+}
diff --git a/src/com/android/settings/intelligence/suggestions/eligibility/ConnectivityEligibilityChecker.java b/src/com/android/settings/intelligence/suggestions/eligibility/ConnectivityEligibilityChecker.java
new file mode 100644
index 0000000..9318c98
--- /dev/null
+++ b/src/com/android/settings/intelligence/suggestions/eligibility/ConnectivityEligibilityChecker.java
@@ -0,0 +1,51 @@
+/*
+ * 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.settings.intelligence.suggestions.eligibility;
+
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+public class ConnectivityEligibilityChecker {
+
+ /**
+ * If defined, only display this optional step if a connection is available.
+ */
+ @VisibleForTesting
+ static final String META_DATA_IS_CONNECTION_REQUIRED =
+ "com.android.settings.require_connection";
+ private static final String TAG = "ConnectivityEligibility";
+
+ public static boolean isEligible(Context context, String id, ResolveInfo info) {
+ final boolean isConnectionRequired =
+ info.activityInfo.metaData.getBoolean(META_DATA_IS_CONNECTION_REQUIRED);
+ if (!isConnectionRequired) {
+ return true;
+ }
+ final ConnectivityManager cm =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo netInfo = cm.getActiveNetworkInfo();
+ boolean satisfiesConnectivity = netInfo != null && netInfo.isConnectedOrConnecting();
+ if (!satisfiesConnectivity) {
+ Log.i(TAG, id + " is missing required connection.");
+ }
+ return satisfiesConnectivity;
+ }
+}
diff --git a/src/com/android/settings/intelligence/suggestions/eligibility/DismissedChecker.java b/src/com/android/settings/intelligence/suggestions/eligibility/DismissedChecker.java
new file mode 100644
index 0000000..a28ae3d
--- /dev/null
+++ b/src/com/android/settings/intelligence/suggestions/eligibility/DismissedChecker.java
@@ -0,0 +1,111 @@
+/*
+ * 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.settings.intelligence.suggestions.eligibility;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.ResolveInfo;
+import android.support.annotation.VisibleForTesting;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+import com.android.settings.intelligence.suggestions.SuggestionDismissHandler;
+import com.android.settings.intelligence.suggestions.SuggestionService;
+
+public class DismissedChecker {
+
+ /**
+ * Allows suggestions to appear after a certain number of days, and to re-appear if dismissed.
+ * For instance:
+ * 0,10
+ * Will appear immediately, the 10 is ignored.
+ *
+ * 10
+ * Will appear after 10 days
+ */
+ @VisibleForTesting
+ static final String META_DATA_DISMISS_CONTROL = "com.android.settings.dismiss";
+
+ // Shared prefs keys for storing dismissed state.
+ // Index into current dismissed state.
+ private static final String SETUP_TIME = "_setup_time";
+ // Default dismiss rule for suggestions.
+
+ private static final int DEFAULT_FIRST_APPEAR_DAY = 0;
+
+ private static final String TAG = "DismissedChecker";
+
+ public static boolean isEligible(Context context, String id, ResolveInfo info,
+ boolean ignoreAppearRule) {
+ final SharedPreferences prefs = SuggestionService.getSharedPrefs(context);
+ final SuggestionDismissHandler dismissHandler = SuggestionDismissHandler.getInstance();
+ final String keySetupTime = id + SETUP_TIME;
+ if (!prefs.contains(keySetupTime)) {
+ prefs.edit()
+ .putLong(keySetupTime, System.currentTimeMillis())
+ .apply();
+ }
+
+ // Check if it's already manually dismissed
+ final boolean isDismissed = dismissHandler.isSuggestionDismissed(context, id);
+ if (isDismissed) {
+ return false;
+ }
+
+ // Parse when suggestion should first appear. return true to artificially hide suggestion
+ // before then.
+ int firstAppearDay = ignoreAppearRule
+ ? DEFAULT_FIRST_APPEAR_DAY
+ : parseAppearDay(info);
+ long firstAppearDayInMs = getEndTime(prefs.getLong(keySetupTime, 0), firstAppearDay);
+ if (System.currentTimeMillis() >= firstAppearDayInMs) {
+ // Dismiss timeout has passed, undismiss it.
+ dismissHandler.markSuggestionNotDismissed(context, id);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Parse the first int from a string formatted as "0,1,2..."
+ * The value means suggestion should first appear on Day X.
+ */
+ private static int parseAppearDay(ResolveInfo info) {
+ if (!info.activityInfo.metaData.containsKey(META_DATA_DISMISS_CONTROL)) {
+ return 0;
+ }
+
+ final Object firstAppearRule = info.activityInfo.metaData
+ .get(META_DATA_DISMISS_CONTROL);
+ if (firstAppearRule instanceof Integer) {
+ return (int) firstAppearRule;
+ } else {
+ try {
+ final String[] days = ((String) firstAppearRule).split(",");
+ return Integer.parseInt(days[0]);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to parse appear/dismiss rule, fall back to 0");
+ return 0;
+ }
+ }
+ }
+
+ private static long getEndTime(long startTime, int daysDelay) {
+ long days = daysDelay * DateUtils.DAY_IN_MILLIS;
+ return startTime + days;
+ }
+}
diff --git a/src/com/android/settings/intelligence/suggestions/eligibility/FeatureEligibilityChecker.java b/src/com/android/settings/intelligence/suggestions/eligibility/FeatureEligibilityChecker.java
new file mode 100644
index 0000000..1308515
--- /dev/null
+++ b/src/com/android/settings/intelligence/suggestions/eligibility/FeatureEligibilityChecker.java
@@ -0,0 +1,51 @@
+/*
+ * 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.settings.intelligence.suggestions.eligibility;
+
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.Log;
+
+public class FeatureEligibilityChecker {
+
+ private static final String TAG = "FeatureEligibility";
+
+ /**
+ * If defined, only returns this suggestion if the feature is supported.
+ */
+ @VisibleForTesting
+ static final String META_DATA_REQUIRE_FEATURE = "com.android.settings.require_feature";
+
+ public static boolean isEligible(Context context, String id, ResolveInfo info) {
+ final String featuresRequired = info.activityInfo.metaData
+ .getString(META_DATA_REQUIRE_FEATURE);
+ if (featuresRequired != null) {
+ for (String feature : featuresRequired.split(",")) {
+ if (TextUtils.isEmpty(feature)) {
+ Log.i(TAG, "Found empty substring when parsing required features: "
+ + featuresRequired);
+ } else if (!context.getPackageManager().hasSystemFeature(feature)) {
+ Log.i(TAG, id + " requires unavailable feature " + feature);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/settings/intelligence/suggestions/eligibility/ProviderEligibilityChecker.java b/src/com/android/settings/intelligence/suggestions/eligibility/ProviderEligibilityChecker.java
new file mode 100644
index 0000000..99efa4f
--- /dev/null
+++ b/src/com/android/settings/intelligence/suggestions/eligibility/ProviderEligibilityChecker.java
@@ -0,0 +1,70 @@
+/*
+ * 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.settings.intelligence.suggestions.eligibility;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.util.Log;
+
+public class ProviderEligibilityChecker {
+ /**
+ * If defined and not true, do not should optional step.
+ */
+ private static final String META_DATA_IS_SUPPORTED = "com.android.settings.is_supported";
+ private static final String TAG = "ProviderEligibility";
+
+ public static boolean isEligible(Context context, String id, ResolveInfo info) {
+ return isSystemApp(id, info)
+ && isEnabledInMetadata(context, id, info);
+ }
+
+ private static boolean isSystemApp(String id, ResolveInfo info) {
+ final boolean isSystemApp = info != null
+ && info.activityInfo != null
+ && info.activityInfo.applicationInfo != null
+ && (info.activityInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_SYSTEM) != 0;
+ if (!isSystemApp) {
+ Log.i(TAG, id + " is not system app, not eligible for suggestion");
+ }
+ return isSystemApp;
+ }
+
+ private static boolean isEnabledInMetadata(Context context, String id, ResolveInfo info) {
+ final int isSupportedResource =
+ info.activityInfo.metaData.getInt(META_DATA_IS_SUPPORTED);
+ try {
+ final Resources res = context.getPackageManager()
+ .getResourcesForApplication(info.activityInfo.applicationInfo);
+ boolean isSupported =
+ isSupportedResource != 0 ? res.getBoolean(isSupportedResource) : true;
+ if (!isSupported) {
+ Log.i(TAG, id + " requires unsupported resource " + isSupportedResource);
+ }
+ return isSupported;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Cannot find resources for " + id, e);
+ return false;
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "Cannot find resources for " + id, e);
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/settings/intelligence/suggestions/model/CandidateSuggestion.java b/src/com/android/settings/intelligence/suggestions/model/CandidateSuggestion.java
new file mode 100644
index 0000000..09fd02d
--- /dev/null
+++ b/src/com/android/settings/intelligence/suggestions/model/CandidateSuggestion.java
@@ -0,0 +1,212 @@
+/*
+ * 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.settings.intelligence.suggestions.model;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import com.android.settings.intelligence.suggestions.eligibility.AccountEligibilityChecker;
+import com.android.settings.intelligence.suggestions.eligibility.ConnectivityEligibilityChecker;
+import com.android.settings.intelligence.suggestions.eligibility.DismissedChecker;
+import com.android.settings.intelligence.suggestions.eligibility.FeatureEligibilityChecker;
+import com.android.settings.intelligence.suggestions.eligibility.ProviderEligibilityChecker;
+
+/**
+ * A wrapper to {@link android.content.pm.ResolveInfo} that matches Suggestion signature.
+ * <p/>
+ * This class contains necessary metadata to eventually be
+ * processed into a {@link android.service.settings.suggestions.Suggestion}.
+ */
+public class CandidateSuggestion {
+
+ public static final String META_DATA_PREFERENCE_ICON_TINTABLE =
+ "com.android.settings.icon_tintable";
+
+ public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
+
+ /**
+ * Name of the meta-data item that should be set in the AndroidManifest.xml
+ * to specify the summary text that should be displayed for the preference.
+ */
+ public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
+
+ /**
+ * Name of the meta-data item that should be set in the AndroidManifest.xml
+ * to specify the content provider providing the summary text that should be displayed for the
+ * preference.
+ *
+ * Summary provided by the content provider overrides any static summary.
+ */
+ public static final String META_DATA_PREFERENCE_SUMMARY_URI =
+ "com.android.settings.summary_uri";
+
+ public static final String META_DATA_PREFERENCE_CUSTOM_VIEW =
+ "com.android.settings.custom_view";
+
+ /**
+ * Name of the meta-data item that should be set in the AndroidManifest.xml
+ * to specify the icon that should be displayed for the preference.
+ */
+ public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
+
+ private static final String TAG = "CandidateSuggestion";
+
+ private final String mId;
+ private final Context mContext;
+ private final ResolveInfo mResolveInfo;
+ private final Intent mIntent;
+ private final boolean mIsEligible;
+ private final boolean mIgnoreAppearRule;
+
+ public CandidateSuggestion(Context context, ResolveInfo resolveInfo,
+ boolean ignoreAppearRule) {
+ mContext = context;
+ mIgnoreAppearRule = ignoreAppearRule;
+ mResolveInfo = resolveInfo;
+ mIntent = new Intent()
+ .setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
+ mId = generateId();
+ mIsEligible = initIsEligible();
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Whether or not this candidate is eligible for display.
+ * <p/>
+ * Note: eligible doesn't mean it will be displayed.
+ */
+ public boolean isEligible() {
+ return mIsEligible;
+ }
+
+ public Suggestion toSuggestion() {
+ if (!mIsEligible) {
+ return null;
+ }
+ final Suggestion.Builder builder = new Suggestion.Builder(mId);
+ updateBuilder(builder);
+ return builder.build();
+ }
+
+ /**
+ * Checks device condition against suggestion requirement. Returns true if the suggestion is
+ * eligible.
+ * <p/>
+ * Note: eligible doesn't mean it will be displayed.
+ */
+ private boolean initIsEligible() {
+ if (!ProviderEligibilityChecker.isEligible(mContext, mId, mResolveInfo)) {
+ return false;
+ }
+ if (!ConnectivityEligibilityChecker.isEligible(mContext, mId, mResolveInfo)) {
+ return false;
+ }
+ if (!FeatureEligibilityChecker.isEligible(mContext, mId, mResolveInfo)) {
+ return false;
+ }
+ if (!AccountEligibilityChecker.isEligible(mContext, mId, mResolveInfo)) {
+ return false;
+ }
+ if (!DismissedChecker.isEligible(mContext, mId, mResolveInfo, mIgnoreAppearRule)) {
+ return false;
+ }
+ return true;
+ }
+
+ private void updateBuilder(Suggestion.Builder builder) {
+ final PackageManager pm = mContext.getPackageManager();
+ final ApplicationInfo applicationInfo = mResolveInfo.activityInfo.applicationInfo;
+
+ int icon = 0;
+ boolean iconTintable = false;
+ String title = null;
+ String summary = null;
+ RemoteViews remoteViews = null;
+
+ // Get the activity's meta-data
+ try {
+ final Resources res = pm.getResourcesForApplication(applicationInfo.packageName);
+ final Bundle metaData = mResolveInfo.activityInfo.metaData;
+
+ if (res != null && metaData != null) {
+ if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
+ icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
+ }
+ if (metaData.containsKey(META_DATA_PREFERENCE_ICON_TINTABLE)) {
+ iconTintable = metaData.getBoolean(META_DATA_PREFERENCE_ICON_TINTABLE);
+ }
+ if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+ if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
+ title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
+ } else {
+ title = metaData.getString(META_DATA_PREFERENCE_TITLE);
+ }
+ }
+ if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
+ if (metaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
+ summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
+ } else {
+ summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
+ }
+ }
+ if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) {
+ int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW);
+ remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
+ Log.d(TAG, "Couldn't find info", e);
+ }
+
+ // Set the preference title to the activity's label if no
+ // meta-data is found
+ if (TextUtils.isEmpty(title)) {
+ title = mResolveInfo.activityInfo.loadLabel(pm).toString();
+ }
+
+ if (icon == 0) {
+ icon = mResolveInfo.activityInfo.icon;
+ }
+ // TODO: Need to use ContentProvider to read dynamic title/summary etc.
+ final PendingIntent pendingIntent = PendingIntent
+ .getActivity(mContext, 0 /* requestCode */, mIntent, 0 /* flags */);
+ builder.setTitle(title)
+ .setSummary(summary)
+ .setPendingIntent(pendingIntent);
+ // TODO: Need to extend Suggestion and set the following.
+ // set icon
+ // set icon tintable
+ // set remote view
+ }
+
+ private String generateId() {
+ return mIntent.getComponent().flattenToString();
+ }
+}
diff --git a/src/com/android/settings/intelligence/suggestions/model/SuggestionCategory.java b/src/com/android/settings/intelligence/suggestions/model/SuggestionCategory.java
new file mode 100644
index 0000000..2bf8f81
--- /dev/null
+++ b/src/com/android/settings/intelligence/suggestions/model/SuggestionCategory.java
@@ -0,0 +1,72 @@
+/*
+ * 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.settings.intelligence.suggestions.model;
+
+/**
+ * A category for {@link android.service.settings.suggestions.Suggestion}.
+ * <p/>
+ * Each suggestion can sepcify it belongs to 1 or more categories. This metadata will be used to
+ * help determine when to show a suggestion to user.
+ */
+public class SuggestionCategory {
+ private final String mCategory;
+ private final boolean mExclusive;
+ private final long mExclusiveExpireDaysInMillis;
+
+ private SuggestionCategory(Builder builder) {
+ this.mCategory = builder.mCategory;
+ this.mExclusive = builder.mExclusive;
+ this.mExclusiveExpireDaysInMillis = builder.mExclusiveExpireDaysInMillis;
+ }
+
+ public String getCategory() {
+ return mCategory;
+ }
+
+ public boolean isExclusive() {
+ return mExclusive;
+ }
+
+ public long getExclusiveExpireDaysInMillis() {
+ return mExclusiveExpireDaysInMillis;
+ }
+
+ public static class Builder {
+ private String mCategory;
+ private boolean mExclusive;
+ private long mExclusiveExpireDaysInMillis;
+
+ public Builder setCategory(String category) {
+ mCategory = category;
+ return this;
+ }
+
+ public Builder setExclusive(boolean exclusive) {
+ mExclusive = exclusive;
+ return this;
+ }
+
+ public Builder setExclusiveExpireDaysInMillis(long exclusiveExpireDaysInMillis) {
+ mExclusiveExpireDaysInMillis = exclusiveExpireDaysInMillis;
+ return this;
+ }
+
+ public SuggestionCategory build() {
+ return new SuggestionCategory(this);
+ }
+ }
+}
diff --git a/src/com/android/settings/intelligence/suggestions/model/SuggestionCategoryRegistry.java b/src/com/android/settings/intelligence/suggestions/model/SuggestionCategoryRegistry.java
new file mode 100644
index 0000000..7917a3e
--- /dev/null
+++ b/src/com/android/settings/intelligence/suggestions/model/SuggestionCategoryRegistry.java
@@ -0,0 +1,72 @@
+/*
+ * 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.settings.intelligence.suggestions.model;
+
+import android.support.annotation.VisibleForTesting;
+import android.text.format.DateUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SuggestionCategoryRegistry {
+
+ @VisibleForTesting
+ static final String CATEGORY_KEY_DEFERRED_SETUP =
+ "com.android.settings.suggested.category.DEFERRED_SETUP";
+ @VisibleForTesting
+ static final String CATEGORY_KEY_FIRST_IMPRESSION =
+ "com.android.settings.suggested.category.FIRST_IMPRESSION";
+
+ public static final List<SuggestionCategory> CATEGORIES;
+
+ private static final long NEVER_EXPIRE = -1L;
+
+ // This is equivalent to packages/apps/settings/res/values/suggestion_ordering.xml
+ static {
+ CATEGORIES = new ArrayList<>();
+ CATEGORIES.add(buildCategory(CATEGORY_KEY_DEFERRED_SETUP,
+ true /* exclusive */, 14 * DateUtils.DAY_IN_MILLIS));
+ CATEGORIES.add(buildCategory(CATEGORY_KEY_FIRST_IMPRESSION,
+ true /* exclusive */, 14 * DateUtils.DAY_IN_MILLIS));
+ CATEGORIES.add(buildCategory("com.android.settings.suggested.category.LOCK_SCREEN",
+ false /* exclusive */, NEVER_EXPIRE));
+ CATEGORIES.add(buildCategory("com.android.settings.suggested.category.TRUST_AGENT",
+ false /* exclusive */, NEVER_EXPIRE));
+ CATEGORIES.add(buildCategory("com.android.settings.suggested.category.EMAIL",
+ false /* exclusive */, NEVER_EXPIRE));
+ CATEGORIES.add(buildCategory("com.android.settings.suggested.category.PARTNER_ACCOUNT",
+ false /* exclusive */, NEVER_EXPIRE));
+ CATEGORIES.add(buildCategory("com.android.settings.suggested.category.GESTURE",
+ false /* exclusive */, NEVER_EXPIRE));
+ CATEGORIES.add(buildCategory("com.android.settings.suggested.category.HOTWORD",
+ false /* exclusive */, NEVER_EXPIRE));
+ CATEGORIES.add(buildCategory("com.android.settings.suggested.category.DEFAULT",
+ false /* exclusive */, NEVER_EXPIRE));
+ CATEGORIES.add(buildCategory("com.android.settings.suggested.category.SETTINGS_ONLY",
+ false /* exclusive */, NEVER_EXPIRE));
+ }
+
+ private static SuggestionCategory buildCategory(String categoryName, boolean exclusive,
+ long expireDaysInMillis) {
+ return new SuggestionCategory.Builder()
+ .setCategory(categoryName)
+ .setExclusive(exclusive)
+ .setExclusiveExpireDaysInMillis(expireDaysInMillis)
+ .build();
+ }
+
+}
diff --git a/src/com/android/settings/intelligence/suggestions/model/SuggestionListBuilder.java b/src/com/android/settings/intelligence/suggestions/model/SuggestionListBuilder.java
new file mode 100644
index 0000000..d40373b
--- /dev/null
+++ b/src/com/android/settings/intelligence/suggestions/model/SuggestionListBuilder.java
@@ -0,0 +1,69 @@
+/*
+ * 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.settings.intelligence.suggestions.model;
+
+import android.service.settings.suggestions.Suggestion;
+import android.util.ArraySet;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class SuggestionListBuilder {
+
+ private Map<SuggestionCategory, List<Suggestion>> mSuggestions;
+ private boolean isBuilt;
+
+ public SuggestionListBuilder() {
+ mSuggestions = new HashMap<>();
+ }
+
+ public void addSuggestions(SuggestionCategory category, List<Suggestion> suggestions) {
+ if (isBuilt) {
+ throw new IllegalStateException("Already built suggestion list, cannot add new ones");
+ }
+ mSuggestions.put(category, suggestions);
+ }
+
+ public List<Suggestion> build() {
+ isBuilt = true;
+ return dedupeSuggestions();
+ }
+
+ /**
+ * Filter suggestions list so they are all unique.
+ */
+ private List<Suggestion> dedupeSuggestions() {
+ final Set<String> ids = new ArraySet<>();
+ final List<Suggestion> suggestions = new ArrayList<>();
+ for (List<Suggestion> suggestionsInCategory : mSuggestions.values()) {
+ suggestions.addAll(suggestionsInCategory);
+ }
+ for (int i = suggestions.size() - 1; i >= 0; i--) {
+ final Suggestion suggestion = suggestions.get(i);
+ final String id = suggestion.getId();
+ if (ids.contains(id)) {
+ suggestions.remove(i);
+ } else {
+ ids.add(id);
+ }
+ }
+ return suggestions;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/settings/intelligence/suggestions/ranking/EventStore.java b/src/com/android/settings/intelligence/suggestions/ranking/EventStore.java
index 4772fae..f79c513 100644
--- a/src/com/android/settings/intelligence/suggestions/ranking/EventStore.java
+++ b/src/com/android/settings/intelligence/suggestions/ranking/EventStore.java
@@ -84,7 +84,7 @@ public class EventStore {
}
private void writePref(String prefKey, long value) {
- mSharedPrefs.edit().putLong(prefKey, value).commit();
+ mSharedPrefs.edit().putLong(prefKey, value).apply();
}
private long readPref(String prefKey, Long defaultValue) {
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
index cb106ca..a27930e 100644
--- a/tests/robotests/Android.mk
+++ b/tests/robotests/Android.mk
@@ -13,7 +13,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
LOCAL_JAVA_LIBRARIES := \
junit \
- platform-robolectric-prebuilt \
+ platform-robolectric-3.4.2-prebuilt \
sdk_vcurrent
LOCAL_INSTRUMENTATION_FOR := SettingsIntelligence
@@ -37,4 +37,4 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
LOCAL_TEST_PACKAGE := SettingsIntelligence
-include prebuilts/misc/common/robolectric/run_robotests.mk
+include prebuilts/misc/common/robolectric/3.4.2/run_robotests.mk
diff --git a/tests/robotests/runners/android_mk/com/android/settings/intelligence/SettingsIntelligenceRobolectricTestRunner.java b/tests/robotests/runners/android_mk/com/android/settings/intelligence/SettingsIntelligenceRobolectricTestRunner.java
index e7f1d68..57ba8b8 100644
--- a/tests/robotests/runners/android_mk/com/android/settings/intelligence/SettingsIntelligenceRobolectricTestRunner.java
+++ b/tests/robotests/runners/android_mk/com/android/settings/intelligence/SettingsIntelligenceRobolectricTestRunner.java
@@ -16,13 +16,11 @@
package com.android.settings.intelligence;
-import java.util.List;
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;
/**
* Custom test runner for dealing with resources from multiple sources. This is needed because the
@@ -32,7 +30,8 @@ import org.robolectric.res.ResourcePath;
public class SettingsIntelligenceRobolectricTestRunner extends RobolectricTestRunner {
/** We don't actually want to change this behavior, so we just call super. */
- public SettingsIntelligenceRobolectricTestRunner(Class<?> testClass) throws InitializationError {
+ public SettingsIntelligenceRobolectricTestRunner(Class<?> testClass)
+ throws InitializationError {
super(testClass);
}
@@ -44,7 +43,7 @@ public class SettingsIntelligenceRobolectricTestRunner extends RobolectricTestRu
@Override
protected AndroidManifest getAppManifest(Config config) {
// Using the manifest file's relative path, we can figure out the application directory.
- final String appRoot = "vendor/unbundled_google/packages/Turbo";
+ final String appRoot = "packages/apps/SettingsIntelligence";
final String manifestPath = appRoot + "/AndroidManifest.xml";
final String resDir = appRoot + "tests/robotests/res";
final String assetsDir = appRoot + config.assetDir();
@@ -56,28 +55,10 @@ public class SettingsIntelligenceRobolectricTestRunner extends RobolectricTestRu
Fs.fileFromPath(manifestPath),
Fs.fileFromPath(resDir),
Fs.fileFromPath(assetsDir)) {
- @Override
- public List<ResourcePath> getIncludedResourcePaths() {
- List<ResourcePath> paths = super.getIncludedResourcePaths();
- paths.add(
- new ResourcePath(
- getPackageName(),
- Fs.fileFromPath(
- "./vendor/unbundled_google/packages/"
- + "Turbo/tests/robotests/res"),
- null));
- paths.add(
- new ResourcePath(
- getPackageName(),
- Fs.fileFromPath(
- "./vendor/unbundled_google/packages/Turbo/res"),
- null));
- return paths;
- }
};
// Set the package name to the renamed one
- manifest.setPackageName("com.google.android.apps.turbo");
+ manifest.setPackageName("com.android.settings.intelligence");
return manifest;
}
}
diff --git a/tests/robotests/runners/gradle/com/android/settings/intelligence/SettingsIntelligenceRobolectricTestRunner.java b/tests/robotests/runners/gradle/com/android/settings/intelligence/SettingsIntelligenceRobolectricTestRunner.java
new file mode 100644
index 0000000..69ebcf3
--- /dev/null
+++ b/tests/robotests/runners/gradle/com/android/settings/intelligence/SettingsIntelligenceRobolectricTestRunner.java
@@ -0,0 +1,36 @@
+/*
+ * 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.settings.intelligence;
+
+import org.junit.runners.model.InitializationError;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+public class SettingsIntelligenceRobolectricTestRunner extends RobolectricTestRunner {
+
+ /**
+ * Creates a runner to run {@code testClass}. Looks in your working directory for your
+ * AndroidManifest.xml file
+ * and res directory by default. Use the {@link Config} annotation to configure.
+ *
+ * @param testClass the test class to be run
+ * @throws InitializationError if junit says so
+ */
+ public SettingsIntelligenceRobolectricTestRunner(Class<?> testClass)
+ throws InitializationError {
+ super(testClass);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/intelligence/SuggestionServiceTest.java b/tests/robotests/runners/gradle/com/android/settings/intelligence/TestConfig.java
index 747c128..6317252 100644
--- a/tests/robotests/src/com/android/settings/intelligence/SuggestionServiceTest.java
+++ b/tests/robotests/runners/gradle/com/android/settings/intelligence/TestConfig.java
@@ -13,19 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.settings.intelligence;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
-
-@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
-public class SuggestionServiceTest {
+public class TestConfig {
- @Test
- public void testRun() {
+ public static final String MANIFEST_PATH = "--default";
+ public static final int SDK_VERSION = 26;
- }
}
diff --git a/tests/robotests/src/android/service/settings/suggestions/Suggestion.java b/tests/robotests/src/android/service/settings/suggestions/Suggestion.java
index 2bb6192..f670f89 100644
--- a/tests/robotests/src/android/service/settings/suggestions/Suggestion.java
+++ b/tests/robotests/src/android/service/settings/suggestions/Suggestion.java
@@ -61,13 +61,6 @@ public class Suggestion {
mId = builder.mId;
}
- private Suggestion(Parcel in) {
- mId = in.readString();
- mTitle = in.readCharSequence();
- mSummary = in.readCharSequence();
- mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
- }
-
/**
* Builder class for {@link Suggestion}.
*/
diff --git a/tests/robotests/src/android/service/settings/suggestions/SuggestionService.java b/tests/robotests/src/android/service/settings/suggestions/SuggestionService.java
new file mode 100644
index 0000000..e74f1fa
--- /dev/null
+++ b/tests/robotests/src/android/service/settings/suggestions/SuggestionService.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.service.settings.suggestions;
+
+import android.app.Service;
+
+import java.util.List;
+
+/**
+ * Dupe to android.service.settings.suggestions.SuggestionService to get around robolectric problem.
+ */
+public abstract class SuggestionService extends Service {
+
+ public abstract List<Suggestion> onGetSuggestions();
+
+ public abstract void onSuggestionDismissed(Suggestion suggestion);
+}
diff --git a/tests/robotests/src/com/android/settings/intelligence/suggestions/SuggestionParserTest.java b/tests/robotests/src/com/android/settings/intelligence/suggestions/SuggestionParserTest.java
new file mode 100644
index 0000000..479d5c8
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/intelligence/suggestions/SuggestionParserTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.settings.intelligence.suggestions;
+
+import static com.android.settings.intelligence.suggestions.model.CandidateSuggestion
+ .META_DATA_PREFERENCE_ICON;
+import static com.android.settings.intelligence.suggestions.model.CandidateSuggestion
+ .META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settings.intelligence.suggestions.model.CandidateSuggestion
+ .META_DATA_PREFERENCE_SUMMARY_URI;
+import static com.android.settings.intelligence.suggestions.model.CandidateSuggestion
+ .META_DATA_PREFERENCE_TITLE;
+import static com.android.settings.intelligence.suggestions.model.CandidateSuggestionTest.newInfo;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
+
+import com.android.settings.intelligence.SettingsIntelligenceRobolectricTestRunner;
+import com.android.settings.intelligence.TestConfig;
+import com.android.settings.intelligence.suggestions.model.SuggestionCategoryRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowPackageManager;
+
+import java.util.List;
+
+@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SuggestionParserTest {
+
+ private Context mContext;
+ private ShadowPackageManager mPackageManager;
+ private SuggestionParser mSuggestionParser;
+ private ResolveInfo mInfo1;
+ private ResolveInfo mInfo1Dupe;
+ private ResolveInfo mInfo2;
+ private ResolveInfo mInfo3;
+ private ResolveInfo mInfo4;
+
+ private Intent exclusiveIntent1;
+ private Intent exclusiveIntent2;
+ private Intent regularIntent;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+ mSuggestionParser = new SuggestionParser(mContext);
+
+ mInfo1 = newInfo(mContext, "Class1", true /* systemApp */,
+ null /* summaryUri */, "title4", 0 /* titleResId */);
+ mInfo1Dupe = newInfo(mContext, "Class1", true /* systemApp */,
+ null /* summaryUri */, "title4", 0 /* titleResId */);
+ mInfo2 = newInfo(mContext, "Class2", true /* systemApp */,
+ null /* summaryUri */, "title4", 0 /* titleResId */);
+ mInfo3 = newInfo(mContext, "Class3", true /* systemApp */,
+ null /* summaryUri */, "title4", 0 /* titleResId */);
+ mInfo4 = newInfo(mContext, "Class4", true /* systemApp */,
+ null /* summaryUri */, "title4", 0 /* titleResId */);
+ mInfo4.activityInfo.applicationInfo.packageName = "ineligible";
+
+ exclusiveIntent1 = new Intent(Intent.ACTION_MAIN).addCategory(
+ SuggestionCategoryRegistry.CATEGORIES.get(0).getCategory());
+ exclusiveIntent2 = new Intent(Intent.ACTION_MAIN).addCategory(
+ SuggestionCategoryRegistry.CATEGORIES.get(1).getCategory());
+ regularIntent = new Intent(Intent.ACTION_MAIN).addCategory(
+ SuggestionCategoryRegistry.CATEGORIES.get(2).getCategory());
+ }
+
+ @Test
+ public void testGetSuggestions_exclusive() {
+ mPackageManager.addResolveInfoForIntent(exclusiveIntent1, mInfo1);
+ mPackageManager.addResolveInfoForIntent(exclusiveIntent1, mInfo1Dupe);
+ mPackageManager.addResolveInfoForIntent(exclusiveIntent2, mInfo2);
+ mPackageManager.addResolveInfoForIntent(regularIntent, mInfo3);
+ final List<Suggestion> suggestions = mSuggestionParser.getSuggestions();
+
+ // info1
+ assertThat(suggestions).hasSize(1);
+ }
+
+ @Test
+ public void testGetSuggestion_onlyRegularCategoryAndNoDupe() {
+ mPackageManager.addResolveInfoForIntent(regularIntent, mInfo1);
+ mPackageManager.addResolveInfoForIntent(regularIntent, mInfo1Dupe);
+ mPackageManager.addResolveInfoForIntent(regularIntent, mInfo2);
+ mPackageManager.addResolveInfoForIntent(regularIntent, mInfo3);
+ mPackageManager.addResolveInfoForIntent(regularIntent, mInfo4);
+
+ final List<Suggestion> suggestions = mSuggestionParser.getSuggestions();
+
+ // info1, info2, info3 (info4 is skip because its package name is ineligible)
+ assertThat(suggestions).hasSize(3);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/intelligence/suggestions/SuggestionServiceTest.java b/tests/robotests/src/com/android/settings/intelligence/suggestions/SuggestionServiceTest.java
new file mode 100644
index 0000000..02bd455
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/intelligence/suggestions/SuggestionServiceTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.settings.intelligence.suggestions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.service.settings.suggestions.Suggestion;
+
+import com.android.settings.intelligence.SettingsIntelligenceRobolectricTestRunner;
+import com.android.settings.intelligence.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.android.controller.ServiceController;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SuggestionServiceTest {
+
+ private SuggestionService mService;
+ private ServiceController<SuggestionService> mServiceController;
+
+ @Before
+ public void setUp() {
+ mServiceController = Robolectric.buildService(SuggestionService.class);
+ mService = mServiceController.create().get();
+ }
+
+ @Test
+ public void getSuggestion_shouldReturnNonNull() {
+ assertThat(mService.onGetSuggestions()).isNotNull();
+ }
+
+ @Test
+ public void dismissSuggestion_shouldDismiss() {
+ final String id = "id1";
+ final Suggestion suggestion = new Suggestion.Builder(id).build();
+
+ // Not dismissed
+ assertThat(SuggestionDismissHandler.getInstance().isSuggestionDismissed(mService, id))
+ .isFalse();
+
+ // Dismiss
+ mService.onSuggestionDismissed(suggestion);
+
+ // Dismissed
+ assertThat(SuggestionDismissHandler.getInstance().isSuggestionDismissed(mService, id))
+ .isTrue();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/AccountEligibilityCheckerTest.java b/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/AccountEligibilityCheckerTest.java
new file mode 100644
index 0000000..033dc86
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/AccountEligibilityCheckerTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.settings.intelligence.suggestions.eligibility;
+
+import static com.android.settings.intelligence.suggestions.eligibility.AccountEligibilityChecker
+ .META_DATA_REQUIRE_ACCOUNT;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+
+import com.android.settings.intelligence.SettingsIntelligenceRobolectricTestRunner;
+import com.android.settings.intelligence.TestConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowAccountManager;
+
+@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AccountEligibilityCheckerTest {
+
+ private static final String ID = "test";
+ private Context mContext;
+ private ResolveInfo mInfo;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mInfo = new ResolveInfo();
+ mInfo.activityInfo = new ActivityInfo();
+ mInfo.activityInfo.metaData = new Bundle();
+ mInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ mInfo.activityInfo.applicationInfo.packageName =
+ RuntimeEnvironment.application.getPackageName();
+ }
+
+ @After
+ public void tearDown() {
+ final ShadowAccountManager shadowAccountManager = Shadows.shadowOf(
+ AccountManager.get(mContext));
+ shadowAccountManager.removeAllAccounts();
+ }
+
+ @Test
+ public void isEligible_noAccountRequirement_shouldReturnTrue() {
+ assertThat(AccountEligibilityChecker.isEligible(mContext, ID, mInfo))
+ .isTrue();
+ }
+
+ @Test
+ public void isEligible_failRequirement_shouldReturnFalse() {
+ // Require android.com account but AccountManager doesn't have it.
+ mInfo.activityInfo.metaData.putString(META_DATA_REQUIRE_ACCOUNT, "android.com");
+
+ assertThat(AccountEligibilityChecker.isEligible(mContext, ID, mInfo))
+ .isFalse();
+ }
+
+ @Test
+ public void isEligible_passRequirement_shouldReturnTrue() {
+ mInfo.activityInfo.metaData.putString(META_DATA_REQUIRE_ACCOUNT, "android.com");
+ final Account account = new Account("TEST", "android.com");
+
+ final ShadowAccountManager shadowAccountManager = Shadows.shadowOf(
+ AccountManager.get(mContext));
+ shadowAccountManager.addAccount(account);
+
+ assertThat(AccountEligibilityChecker.isEligible(mContext, ID, mInfo))
+ .isTrue();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/ConnectivityEligibilityCheckerTest.java b/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/ConnectivityEligibilityCheckerTest.java
new file mode 100644
index 0000000..b6de5b9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/ConnectivityEligibilityCheckerTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.settings.intelligence.suggestions.eligibility;
+
+import static com.android.settings.intelligence.suggestions.eligibility
+ .ConnectivityEligibilityChecker.META_DATA_IS_CONNECTION_REQUIRED;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+
+import com.android.settings.intelligence.SettingsIntelligenceRobolectricTestRunner;
+import com.android.settings.intelligence.TestConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowConnectivityManager;
+
+@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ConnectivityEligibilityCheckerTest {
+
+ private static final String ID = "test";
+ private Context mContext;
+ private ResolveInfo mInfo;
+ private ShadowConnectivityManager mConnectivityManager;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mInfo = new ResolveInfo();
+ mInfo.activityInfo = new ActivityInfo();
+ mInfo.activityInfo.metaData = new Bundle();
+ mInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ mInfo.activityInfo.applicationInfo.packageName =
+ RuntimeEnvironment.application.getPackageName();
+
+ mConnectivityManager = Shadows.shadowOf(
+ (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE));
+ }
+
+ @After
+ public void tearDown() {
+ mConnectivityManager.setActiveNetworkInfo(null);
+ }
+
+ @Test
+ public void isEligible_noRequirement_shouldReturnTrue() {
+ assertThat(ConnectivityEligibilityChecker.isEligible(mContext, ID, mInfo))
+ .isTrue();
+
+ mInfo.activityInfo.metaData.putBoolean(META_DATA_IS_CONNECTION_REQUIRED, false);
+ assertThat(ConnectivityEligibilityChecker.isEligible(mContext, ID, mInfo))
+ .isTrue();
+ }
+
+ @Test
+ public void isEligible_hasConnection_shouldReturnTrue() {
+ mInfo.activityInfo.metaData.putBoolean(META_DATA_IS_CONNECTION_REQUIRED, true);
+
+ final NetworkInfo networkInfo = mock(NetworkInfo.class);
+ when(networkInfo.isConnectedOrConnecting())
+ .thenReturn(true);
+
+ mConnectivityManager.setActiveNetworkInfo(networkInfo);
+
+ assertThat(ConnectivityEligibilityChecker.isEligible(mContext, ID, mInfo))
+ .isTrue();
+ }
+
+ @Test
+ public void isEligible_noConnection_shouldReturnFalse() {
+ mInfo.activityInfo.metaData.putBoolean(META_DATA_IS_CONNECTION_REQUIRED, true);
+
+ mConnectivityManager.setActiveNetworkInfo(null);
+
+ assertThat(ConnectivityEligibilityChecker.isEligible(mContext, ID, mInfo))
+ .isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/DismissedCheckerTest.java b/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/DismissedCheckerTest.java
new file mode 100644
index 0000000..7464d65
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/DismissedCheckerTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.settings.intelligence.suggestions.eligibility;
+
+import static com.android.settings.intelligence.suggestions.eligibility.DismissedChecker
+ .META_DATA_DISMISS_CONTROL;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+
+import com.android.settings.intelligence.SettingsIntelligenceRobolectricTestRunner;
+import com.android.settings.intelligence.TestConfig;
+import com.android.settings.intelligence.suggestions.SuggestionDismissHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+
+@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DismissedCheckerTest {
+ private static final String ID = "test";
+ private Context mContext;
+ private ResolveInfo mInfo;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mInfo = new ResolveInfo();
+ mInfo.activityInfo = new ActivityInfo();
+ mInfo.activityInfo.metaData = new Bundle();
+ mInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ mInfo.activityInfo.applicationInfo.packageName =
+ RuntimeEnvironment.application.getPackageName();
+ }
+
+ @Test
+ public void isEligible_newSuggestion_noRule_shouldReturnTrue() {
+ assertThat(DismissedChecker.isEligible(mContext, ID, mInfo, true /* ignoreAppearRule */))
+ .isTrue();
+ }
+
+ @Test
+ public void isEligible_newSuggestion_hasFutureRule_shouldReturnFalse() {
+ mInfo.activityInfo.metaData.putString(META_DATA_DISMISS_CONTROL, "10");
+
+ assertThat(DismissedChecker.isEligible(mContext, ID, mInfo, false /* ignoreAppearRule */))
+ .isFalse();
+ }
+
+ @Test
+ public void isEligible_newSuggestion_ignoreFutureRule_shouldReturnFalse() {
+ mInfo.activityInfo.metaData.putString(META_DATA_DISMISS_CONTROL, "10");
+
+ assertThat(DismissedChecker.isEligible(mContext, ID, mInfo, true /* ignoreAppearRule */))
+ .isTrue();
+ }
+
+ @Test
+ public void isEligible_newSuggestion_hasPastRule_shouldReturnTrue() {
+ mInfo.activityInfo.metaData.putString(META_DATA_DISMISS_CONTROL, "-10");
+
+ assertThat(DismissedChecker.isEligible(mContext, ID, mInfo, false /* ignoreAppearRule */))
+ .isTrue();
+ assertThat(DismissedChecker.isEligible(mContext, ID, mInfo, true /* ignoreAppearRule */))
+ .isTrue();
+ }
+
+ @Test
+ public void isEligible_dismissedSuggestion_shouldReturnFalse() {
+ SuggestionDismissHandler.getInstance().markSuggestionDismissed(mContext, ID);
+
+ assertThat(DismissedChecker.isEligible(mContext, ID, mInfo, true /* ignoreAppearRule */))
+ .isFalse();
+ }
+
+} \ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/FeatureEligibilityCheckerTest.java b/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/FeatureEligibilityCheckerTest.java
new file mode 100644
index 0000000..a2f2c23
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/FeatureEligibilityCheckerTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.settings.intelligence.suggestions.eligibility;
+
+
+import static com.android.settings.intelligence.suggestions.eligibility.FeatureEligibilityChecker
+ .META_DATA_REQUIRE_FEATURE;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+
+import com.android.settings.intelligence.SettingsIntelligenceRobolectricTestRunner;
+import com.android.settings.intelligence.TestConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowPackageManager;
+
+@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class FeatureEligibilityCheckerTest {
+
+ private static final String ID = "test";
+ private Context mContext;
+ private ResolveInfo mInfo;
+ private ShadowPackageManager mPackageManager;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mInfo = new ResolveInfo();
+ mInfo.activityInfo = new ActivityInfo();
+ mInfo.activityInfo.metaData = new Bundle();
+ mInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ mInfo.activityInfo.applicationInfo.packageName =
+ RuntimeEnvironment.application.getPackageName();
+ mPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+ }
+
+ @After
+ public void tearDown() {
+ mPackageManager.clearSystemAvailableFeatures();
+ }
+
+ @Test
+ public void isEligible_noRequirement_shouldReturnTrue() {
+ assertThat(FeatureEligibilityChecker.isEligible(mContext, ID, mInfo))
+ .isTrue();
+ }
+
+ @Test
+ public void isEligible_failRequirement_shouldReturnFalse() {
+ mInfo.activityInfo.metaData.putString(META_DATA_REQUIRE_FEATURE, "test_feature");
+
+ assertThat(FeatureEligibilityChecker.isEligible(mContext, ID, mInfo))
+ .isFalse();
+ }
+
+ @Test
+ public void isEligible_passRequirement_shouldReturnTrue() {
+ final FeatureInfo featureInfo = new FeatureInfo();
+ featureInfo.name = "fingerprint";
+ mInfo.activityInfo.metaData.putString(META_DATA_REQUIRE_FEATURE, featureInfo.name);
+ mPackageManager.addSystemAvailableFeature(featureInfo);
+
+ assertThat(FeatureEligibilityChecker.isEligible(mContext, ID, mInfo))
+ .isFalse();
+ }
+} \ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/ProviderEligibilityCheckerTest.java b/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/ProviderEligibilityCheckerTest.java
new file mode 100644
index 0000000..16ddf8f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/intelligence/suggestions/eligibility/ProviderEligibilityCheckerTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.settings.intelligence.suggestions.eligibility;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+
+import com.android.settings.intelligence.SettingsIntelligenceRobolectricTestRunner;
+import com.android.settings.intelligence.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ProviderEligibilityCheckerTest {
+
+ private static final String ID = "test";
+ private Context mContext;
+ private ResolveInfo mInfo;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mInfo = new ResolveInfo();
+ mInfo.activityInfo = new ActivityInfo();
+ mInfo.activityInfo.metaData = new Bundle();
+ mInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ mInfo.activityInfo.applicationInfo.packageName =
+ RuntimeEnvironment.application.getPackageName();
+ }
+
+ @Test
+ public void isEligible_systemFlagSet_shouldReturnTrue() {
+ mInfo.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+
+ assertThat(ProviderEligibilityChecker.isEligible(mContext, ID, mInfo))
+ .isTrue();
+ }
+
+ @Test
+ public void isEligible_systemFlagNotSet_shouldReturnFalse() {
+ mInfo.activityInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
+
+ assertThat(ProviderEligibilityChecker.isEligible(mContext, ID, mInfo))
+ .isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/intelligence/suggestions/model/CandidateSuggestionTest.java b/tests/robotests/src/com/android/settings/intelligence/suggestions/model/CandidateSuggestionTest.java
new file mode 100644
index 0000000..240f1e0
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/intelligence/suggestions/model/CandidateSuggestionTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.settings.intelligence.suggestions.model;
+
+import static com.android.settings.intelligence.suggestions.model.CandidateSuggestion
+ .META_DATA_PREFERENCE_ICON;
+import static com.android.settings.intelligence.suggestions.model.CandidateSuggestion
+ .META_DATA_PREFERENCE_SUMMARY;
+import static com.android.settings.intelligence.suggestions.model.CandidateSuggestion
+ .META_DATA_PREFERENCE_SUMMARY_URI;
+import static com.android.settings.intelligence.suggestions.model.CandidateSuggestion
+ .META_DATA_PREFERENCE_TITLE;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
+
+import com.android.settings.intelligence.SettingsIntelligenceRobolectricTestRunner;
+import com.android.settings.intelligence.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class CandidateSuggestionTest {
+
+ private static final String PACKAGE_NAME = "pkg";
+ private static final String CLASS_NAME = "class";
+
+ private Context mContext;
+ private ResolveInfo mInfo;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mInfo = new ResolveInfo();
+ mInfo.activityInfo = new ActivityInfo();
+ mInfo.activityInfo.metaData = new Bundle();
+ mInfo.activityInfo.packageName = PACKAGE_NAME;
+ mInfo.activityInfo.name = CLASS_NAME;
+
+ mInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ mInfo.activityInfo.applicationInfo.packageName =
+ RuntimeEnvironment.application.getPackageName();
+ }
+
+ @Test
+ public void getId_shouldUseComponentName() {
+ final CandidateSuggestion candidate =
+ new CandidateSuggestion(mContext, mInfo, true /* ignoreAppearRule */);
+
+ assertThat(candidate.getId())
+ .contains(PACKAGE_NAME + "/" + CLASS_NAME);
+ }
+
+ @Test
+ public void parseMetadata_eligibleSuggestion() {
+ final ResolveInfo info = newInfo(mContext, "class", true /* systemApp */,
+ null /*summaryUri */, "title", 0 /* titleResId */);
+ Suggestion suggestion = new CandidateSuggestion(
+ mContext, info, false /* ignoreAppearRule*/)
+ .toSuggestion();
+ assertThat(suggestion.getId()).isEqualTo(mContext.getPackageName()+"/class");
+ assertThat(suggestion.getTitle()).isEqualTo("title");
+ assertThat(suggestion.getSummary()).isEqualTo("static-summary");
+ }
+
+ @Test
+ public void parseMetadata_ineligibleSuggestion() {
+ final ResolveInfo info = newInfo(mContext, "class", false /* systemApp */,
+ null /*summaryUri */, "title", 0 /* titleResId */);
+ final CandidateSuggestion candidate = new CandidateSuggestion(
+ mContext, info, false /* ignoreAppearRule*/);
+
+ assertThat(candidate.isEligible()).isFalse();
+ assertThat(candidate.toSuggestion()).isNull();
+ }
+
+ public static ResolveInfo newInfo(Context context, String className, boolean systemApp,
+ String summaryUri, String title, int titleResId) {
+ final ResolveInfo info = new ResolveInfo();
+ info.activityInfo = new ActivityInfo();
+ info.activityInfo.applicationInfo = new ApplicationInfo();
+ if (systemApp) {
+ info.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ }
+ info.activityInfo.packageName = context.getPackageName();
+ info.activityInfo.applicationInfo.packageName = info.activityInfo.packageName;
+ info.activityInfo.name = className;
+ info.activityInfo.metaData = new Bundle();
+ info.activityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON, 314159);
+ info.activityInfo.metaData.putString(META_DATA_PREFERENCE_SUMMARY, "static-summary");
+ if (summaryUri != null) {
+ info.activityInfo.metaData.putString(META_DATA_PREFERENCE_SUMMARY_URI, summaryUri);
+ }
+ if (titleResId != 0) {
+ info.activityInfo.metaData.putInt(META_DATA_PREFERENCE_TITLE, titleResId);
+ } else if (title != null) {
+ info.activityInfo.metaData.putString(META_DATA_PREFERENCE_TITLE, title);
+ }
+ return info;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/intelligence/suggestions/model/SuggestionCategoryRegistryTest.java b/tests/robotests/src/com/android/settings/intelligence/suggestions/model/SuggestionCategoryRegistryTest.java
new file mode 100644
index 0000000..71041f8
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/intelligence/suggestions/model/SuggestionCategoryRegistryTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.settings.intelligence.suggestions.model;
+
+import static com.android.settings.intelligence.suggestions.model.SuggestionCategoryRegistry
+ .CATEGORIES;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settings.intelligence.SettingsIntelligenceRobolectricTestRunner;
+import com.android.settings.intelligence.TestConfig;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SuggestionCategoryRegistryTest {
+
+ @Test
+ public void getCategories_shouldHave10Categories() {
+ assertThat(CATEGORIES)
+ .hasSize(10);
+ }
+
+ @Test
+ public void verifyExclusiveCategories() {
+ final List<String> exclusiveCategories = new ArrayList<>();
+ exclusiveCategories.add(SuggestionCategoryRegistry.CATEGORY_KEY_DEFERRED_SETUP);
+ exclusiveCategories.add(SuggestionCategoryRegistry.CATEGORY_KEY_FIRST_IMPRESSION);
+
+ int exclusiveCount = 0;
+ for (SuggestionCategory category : CATEGORIES) {
+ if (category.isExclusive()) {
+ exclusiveCount++;
+ assertThat(exclusiveCategories).contains(category.getCategory());
+ }
+ }
+ assertThat(exclusiveCount).isEqualTo(exclusiveCategories.size());
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/intelligence/suggestions/model/SuggestionListBuilderTest.java b/tests/robotests/src/com/android/settings/intelligence/suggestions/model/SuggestionListBuilderTest.java
new file mode 100644
index 0000000..cef6338
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/intelligence/suggestions/model/SuggestionListBuilderTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.settings.intelligence.suggestions.model;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.service.settings.suggestions.Suggestion;
+
+import com.android.settings.intelligence.SettingsIntelligenceRobolectricTestRunner;
+import com.android.settings.intelligence.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+
+@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SuggestionListBuilderTest {
+
+ private SuggestionListBuilder mBuilder;
+ private SuggestionCategory mCategory1;
+ private SuggestionCategory mCategory2;
+ private Suggestion mSuggestion1;
+ private Suggestion mSuggestion2;
+
+ @Before
+ public void setUp() {
+ mSuggestion1 = new Suggestion.Builder("id1")
+ .setTitle("title1")
+ .setSummary("summary1")
+ .build();
+ mCategory1 = SuggestionCategoryRegistry.CATEGORIES.get(3);
+ mCategory2 = SuggestionCategoryRegistry.CATEGORIES.get(4);
+ mSuggestion2 = new Suggestion.Builder("id2")
+ .setTitle("title2")
+ .setSummary("summary2")
+ .build();
+ mBuilder = new SuggestionListBuilder();
+ }
+
+ @Test
+ public void dedupe_shouldSkipSameSuggestion() {
+ mBuilder.addSuggestions(mCategory1, Arrays.asList(mSuggestion1));
+ mBuilder.addSuggestions(mCategory2, Arrays.asList(mSuggestion1));
+
+ assertThat(mBuilder.build()).hasSize(1);
+ }
+
+ @Test
+ public void dedupe_shouldContainDifferentSuggestion() {
+ mBuilder.addSuggestions(mCategory1, Arrays.asList(mSuggestion1, mSuggestion2));
+
+ assertThat(mBuilder.build()).hasSize(2);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/intelligence/suggestions/ranking/SuggestionRankerTest.java b/tests/robotests/src/com/android/settings/intelligence/suggestions/ranking/SuggestionRankerTest.java
index 7866ea3..839b7fd 100644
--- a/tests/robotests/src/com/android/settings/intelligence/suggestions/ranking/SuggestionRankerTest.java
+++ b/tests/robotests/src/com/android/settings/intelligence/suggestions/ranking/SuggestionRankerTest.java
@@ -39,16 +39,15 @@ public class SuggestionRankerTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
mFeatures = new HashMap<>();
- mFeatures.put("pkg1", new HashMap<>());
- mFeatures.put("pkg2", new HashMap<>());
- mFeatures.put("pkg3", new HashMap<>());
- mSuggestions = new ArrayList<Suggestion>() {
- {
- add(new Suggestion.Builder("pkg1").build());
- add(new Suggestion.Builder("pkg2").build());
- add(new Suggestion.Builder("pkg3").build());
- }
- };
+ mFeatures.put("pkg1", new HashMap<String, Double>());
+ mFeatures.put("pkg2", new HashMap<String, Double>());
+ mFeatures.put("pkg3", new HashMap<String, Double>());
+ mSuggestions = new ArrayList<>();
+
+ mSuggestions.add(new Suggestion.Builder("pkg1").build());
+ mSuggestions.add(new Suggestion.Builder("pkg2").build());
+ mSuggestions.add(new Suggestion.Builder("pkg3").build());
+
mSuggestionFeaturizer = mock(SuggestionFeaturizer.class);
mSuggestionRanker = new SuggestionRanker(mSuggestionFeaturizer);
when(mSuggestionFeaturizer.featurize(mSuggestions)).thenReturn(mFeatures);
@@ -60,13 +59,11 @@ public class SuggestionRankerTest {
@Test
public void testRank() {
- List<Suggestion> expectedOrderdList = new ArrayList<Suggestion>() {
- {
- add(mSuggestions.get(0)); // relevance = 0.9
- add(mSuggestions.get(2)); // relevance = 0.5
- add(mSuggestions.get(1)); // relevance = 0.1
- }
- };
+ final List<Suggestion> expectedOrderdList = new ArrayList<>();
+ expectedOrderdList.add(mSuggestions.get(0)); // relevance = 0.9
+ expectedOrderdList.add(mSuggestions.get(2)); // relevance = 0.5
+ expectedOrderdList.add(mSuggestions.get(1)); // relevance = 0.1
+
mSuggestionRanker.rankSuggestions(mSuggestions);
assertThat(mSuggestions).isEqualTo(expectedOrderdList);
}