Merge "Add handling for account tiles for specific account type."
diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java
index 8cbdd60..02cc436 100644
--- a/src/com/android/settings/notification/ChannelNotificationSettings.java
+++ b/src/com/android/settings/notification/ChannelNotificationSettings.java
@@ -156,7 +156,7 @@
             @Override
             public boolean onPreferenceChange(Preference preference, Object newValue) {
                 final boolean vibrate = (Boolean) newValue;
-                mChannel.setVibration(vibrate);
+                mChannel.enableVibration(vibrate);
                 mChannel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
                 mBackend.updateChannel(mPkg, mUid, mChannel);
                 return true;
diff --git a/tests/robotests/src/com/android/settings/SettingsDialogFragmentTest.java b/tests/robotests/src/com/android/settings/SettingsDialogFragmentTest.java
index 86b613c..9bf168d 100644
--- a/tests/robotests/src/com/android/settings/SettingsDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/SettingsDialogFragmentTest.java
@@ -17,6 +17,7 @@
 
 import android.app.Dialog;
 import android.app.Fragment;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/tests/robotests/src/com/android/settings/core/codeinspection/ClassScanner.java b/tests/robotests/src/com/android/settings/core/codeinspection/ClassScanner.java
new file mode 100644
index 0000000..09af870
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/core/codeinspection/ClassScanner.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.core.codeinspection;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Scans and builds all classes in current classloader.
+ */
+public class ClassScanner {
+
+    private static final String CLASS_SUFFIX = ".class";
+
+    public List<Class<?>> getClassesForPackage(String packageName)
+            throws ClassNotFoundException {
+        final List<Class<?>> classes = new ArrayList<>();
+
+        try {
+            final Enumeration<URL> resources = Thread.currentThread().getContextClassLoader()
+                    .getResources(packageName.replace('.', '/'));
+            if (!resources.hasMoreElements()) {
+                return classes;
+            }
+            URL url = resources.nextElement();
+            while (url != null) {
+                final URLConnection connection = url.openConnection();
+
+                if (connection instanceof JarURLConnection) {
+                    loadClassFromJar((JarURLConnection) connection, packageName,
+                            classes);
+                } else {
+                    loadClassFromDirectory(new File(URLDecoder.decode(url.getPath(), "UTF-8")),
+                            packageName, classes);
+                }
+                if (resources.hasMoreElements()) {
+                    url = resources.nextElement();
+                } else {
+                    break;
+                }
+            }
+        } catch (final IOException e) {
+            throw new ClassNotFoundException("Error when parsing " + packageName, e);
+        }
+        return classes;
+    }
+
+    private void loadClassFromDirectory(File directory, String packageName, List<Class<?>> classes)
+            throws ClassNotFoundException {
+        if (directory.exists() && directory.isDirectory()) {
+            final String[] files = directory.list();
+
+            for (final String file : files) {
+                if (file.endsWith(CLASS_SUFFIX)) {
+                    try {
+                        classes.add(Class.forName(
+                                packageName + '.' + file.substring(0, file.length() - 6),
+                                false /* init */,
+                                Thread.currentThread().getContextClassLoader()));
+                    } catch (NoClassDefFoundError e) {
+                        // do nothing. this class hasn't been found by the
+                        // loader, and we don't care.
+                    }
+                } else {
+                    final File tmpDirectory = new File(directory, file);
+                    if (tmpDirectory.isDirectory()) {
+                        loadClassFromDirectory(tmpDirectory, packageName + "." + file, classes);
+                    }
+                }
+            }
+        }
+    }
+
+    private void loadClassFromJar(JarURLConnection connection, String packageName,
+            List<Class<?>> classes) throws ClassNotFoundException, IOException {
+        final JarFile jarFile = connection.getJarFile();
+        final Enumeration<JarEntry> entries = jarFile.entries();
+        String name;
+        if (!entries.hasMoreElements()) {
+            return;
+        }
+        JarEntry jarEntry = entries.nextElement();
+        while (jarEntry != null) {
+            name = jarEntry.getName();
+
+            if (name.contains(CLASS_SUFFIX)) {
+                name = name.substring(0, name.length() - CLASS_SUFFIX.length()).replace('/', '.');
+
+                if (name.startsWith(packageName)) {
+                    try {
+                        classes.add(Class.forName(name,
+                                false /* init */,
+                                Thread.currentThread().getContextClassLoader()));
+                    } catch (NoClassDefFoundError e) {
+                        // do nothing. this class hasn't been found by the
+                        // loader, and we don't care.
+                    }
+                }
+            }
+            if (entries.hasMoreElements()) {
+                jarEntry = entries.nextElement();
+            } else {
+                break;
+            }
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/core/codeinspection/CodeInspectionTest.java b/tests/robotests/src/com/android/settings/core/codeinspection/CodeInspectionTest.java
new file mode 100644
index 0000000..88d8171
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/core/codeinspection/CodeInspectionTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.core.codeinspection;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.core.instrumentation.InstrumentableFragmentCodeInspector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+/**
+ * Test suite that scans all class in app package, and perform different types of code inspection
+ * for conformance.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class CodeInspectionTest {
+
+    private List<Class<?>> mClasses;
+
+    @Before
+    public void setUp() throws Exception {
+        mClasses = new ClassScanner().getClassesForPackage(CodeInspector.PACKAGE_NAME);
+    }
+
+    @Test
+    public void runCodeInspections() {
+        new InstrumentableFragmentCodeInspector(mClasses).run();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/core/codeinspection/CodeInspector.java b/tests/robotests/src/com/android/settings/core/codeinspection/CodeInspector.java
new file mode 100644
index 0000000..80a2c11
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/core/codeinspection/CodeInspector.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.core.codeinspection;
+
+import java.util.List;
+
+/**
+ * Inspector takes a list of class objects and perform static code analysis in its {@link #run()}
+ * method.
+ */
+public abstract class CodeInspector {
+
+    public static final String PACKAGE_NAME = "com.android.settings";
+
+    protected final List<Class<?>> mClasses;
+
+    public CodeInspector(List<Class<?>> classes) {
+        mClasses = classes;
+    }
+
+    /**
+     * Code inspection runner method.
+     */
+    public abstract void run();
+}
diff --git a/tests/robotests/src/com/android/settings/core/instrumentation/InstrumentableFragmentCodeInspector.java b/tests/robotests/src/com/android/settings/core/instrumentation/InstrumentableFragmentCodeInspector.java
new file mode 100644
index 0000000..5e8cfdc
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/core/instrumentation/InstrumentableFragmentCodeInspector.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.core.instrumentation;
+
+import android.app.Fragment;
+import android.util.ArraySet;
+
+import com.android.settings.ChooseLockPassword;
+import com.android.settings.ChooseLockPattern;
+import com.android.settings.CredentialCheckResultTracker;
+import com.android.settings.CustomDialogPreference;
+import com.android.settings.CustomEditTextPreference;
+import com.android.settings.CustomListPreference;
+import com.android.settings.RestrictedListPreference;
+import com.android.settings.applications.AppOpsCategory;
+import com.android.settings.core.codeinspection.CodeInspector;
+import com.android.settings.core.lifecycle.ObservableDialogFragment;
+import com.android.settings.deletionhelper.ActivationWarningFragment;
+import com.android.settings.inputmethod.UserDictionaryLocalePicker;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+/**
+ * {@link CodeInspector} that verifies all fragments implements Instrumentable.
+ */
+public class InstrumentableFragmentCodeInspector extends CodeInspector {
+
+    private static final String TEST_CLASS_SUFFIX = "Test";
+
+    private static final List<String> whitelist;
+
+    static {
+        whitelist = new ArrayList<>();
+        whitelist.add(
+                CustomEditTextPreference.CustomPreferenceDialogFragment.class.getName());
+        whitelist.add(
+                CustomListPreference.CustomListPreferenceDialogFragment.class.getName());
+        whitelist.add(
+                RestrictedListPreference.RestrictedListPreferenceDialogFragment.class.getName());
+        whitelist.add(ChooseLockPassword.SaveAndFinishWorker.class.getName());
+        whitelist.add(ChooseLockPattern.SaveAndFinishWorker.class.getName());
+        whitelist.add(ActivationWarningFragment.class.getName());
+        whitelist.add(ObservableDialogFragment.class.getName());
+        whitelist.add(CustomDialogPreference.CustomPreferenceDialogFragment.class.getName());
+        whitelist.add(AppOpsCategory.class.getName());
+        whitelist.add(UserDictionaryLocalePicker.class.getName());
+        whitelist.add(CredentialCheckResultTracker.class.getName());
+    }
+
+    public InstrumentableFragmentCodeInspector(List<Class<?>> classes) {
+        super(classes);
+    }
+
+    @Override
+    public void run() {
+        final Set<String> broken = new ArraySet<>();
+
+        for (Class clazz : mClasses) {
+            // Skip abstract classes.
+            if (Modifier.isAbstract(clazz.getModifiers())) {
+                continue;
+            }
+            final String packageName = clazz.getPackage().getName();
+            // Skip classes that are not in Settings.
+            if (!packageName.contains(PACKAGE_NAME + ".")) {
+                continue;
+            }
+            final String className = clazz.getName();
+            // Skip classes from tests.
+            if (className.endsWith(TEST_CLASS_SUFFIX)) {
+                continue;
+            }
+            // If it's a fragment, it must also be instrumentable.
+            if (Fragment.class.isAssignableFrom(clazz)
+                    && !Instrumentable.class.isAssignableFrom(clazz)
+                    && !whitelist.contains(className)) {
+                broken.add(className);
+            }
+        }
+        final StringBuilder sb = new StringBuilder(
+                "All fragment should implement Instrumentable, but the following are not:\n");
+        for (String c : broken) {
+            sb.append(c).append("\n");
+        }
+        assertWithMessage(sb.toString())
+                .that(broken.isEmpty())
+                .isTrue();
+    }
+}