Sync to latest SettingsIntelligence source code

Test: builds & robotests
Change-Id: Ie83909a9747afd07c541c6236224f079da8a2dc9
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_JAVA_LIBRARIES := \
     junit \
-    platform-robolectric-prebuilt \
+    platform-robolectric-3.4.2-prebuilt \
     sdk_vcurrent
 
 LOCAL_INSTRUMENTATION_FOR := SettingsIntelligence
@@ -37,4 +37,4 @@
 
 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 @@
 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 @@
     @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 @@
                         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
similarity index 67%
rename from tests/robotests/src/com/android/settings/intelligence/SuggestionServiceTest.java
rename to 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;
+public class TestConfig {
 
-@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
-public class SuggestionServiceTest {
+    public static final String MANIFEST_PATH = "--default";
+    public static final int SDK_VERSION = 26;
 
-    @Test
-    public void testRun() {
-
-    }
 }
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 @@
         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/com/android/settings/intelligence/SuggestionServiceTest.java b/tests/robotests/src/android/service/settings/suggestions/SuggestionService.java
similarity index 61%
copy from tests/robotests/src/com/android/settings/intelligence/SuggestionServiceTest.java
copy to tests/robotests/src/android/service/settings/suggestions/SuggestionService.java
index 747c128..e74f1fa 100644
--- a/tests/robotests/src/com/android/settings/intelligence/SuggestionServiceTest.java
+++ b/tests/robotests/src/android/service/settings/suggestions/SuggestionService.java
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.settings.intelligence;
+package android.service.settings.suggestions;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
+import android.app.Service;
 
-@RunWith(SettingsIntelligenceRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
-public class SuggestionServiceTest {
+import java.util.List;
 
-    @Test
-    public void testRun() {
+/**
+ * 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 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 @@
 
     @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);
     }