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();
+ }
+}