OSDN Git Service

Merge "Restart loader in onResume" into oc-dev
authorLei Yu <jackqdyulei@google.com>
Fri, 2 Jun 2017 19:21:27 +0000 (19:21 +0000)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Fri, 2 Jun 2017 19:21:29 +0000 (19:21 +0000)
42 files changed:
res/layout/preference_progress_category.xml
res/values/strings.xml
res/xml/enterprise_privacy_settings.xml
res/xml/wifi_network_details_fragment.xml
src/com/android/settings/Utils.java
src/com/android/settings/accounts/AutoSyncWorkDataPreferenceController.java
src/com/android/settings/core/DynamicAvailabilityPreferenceController.java
src/com/android/settings/core/PreferenceAvailabilityObserver.java [new file with mode: 0644]
src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java
src/com/android/settings/enterprise/AlwaysOnVpnCurrentUserPreferenceController.java
src/com/android/settings/enterprise/AlwaysOnVpnManagedProfilePreferenceController.java
src/com/android/settings/enterprise/CaCertsPreferenceController.java
src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceController.java
src/com/android/settings/enterprise/EnterprisePrivacyPreferenceController.java
src/com/android/settings/enterprise/EnterprisePrivacySettings.java
src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceController.java
src/com/android/settings/enterprise/ExposureChangesCategoryPreferenceController.java [new file with mode: 0644]
src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerBase.java
src/com/android/settings/enterprise/GlobalHttpProxyPreferenceController.java
src/com/android/settings/enterprise/ImePreferenceController.java
src/com/android/settings/notification/AppNotificationSettings.java
src/com/android/settings/notification/ChannelNotificationSettings.java
src/com/android/settings/notification/NotificationBackend.java
src/com/android/settings/notification/NotificationSettingsBase.java
src/com/android/settings/notification/VolumeSeekBarPreference.java
src/com/android/settings/wifi/WifiDetailPreference.java
src/com/android/settings/wifi/details/WifiDetailPreferenceController.java
tests/robotests/src/com/android/settings/accounts/AutoSyncWorkDataPreferenceControllerTest.java
tests/robotests/src/com/android/settings/core/DynamicAvailabilityPreferenceControllerTest.java
tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java
tests/robotests/src/com/android/settings/enterprise/AlwaysOnVpnCurrentUserPreferenceControllerTest.java
tests/robotests/src/com/android/settings/enterprise/AlwaysOnVpnManagedProfilePreferenceControllerTest.java
tests/robotests/src/com/android/settings/enterprise/CaCertsPreferenceControllerTest.java
tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyPreferenceControllerTest.java
tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceControllerTest.java
tests/robotests/src/com/android/settings/enterprise/ExposureChangesCategoryPreferenceControllerTest.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerTestBase.java
tests/robotests/src/com/android/settings/enterprise/GlobalHttpProxyPreferenceControllerTest.java
tests/robotests/src/com/android/settings/enterprise/ImePreferenceControllerTest.java
tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java

index d858697..2ac3dc1 100644 (file)
@@ -20,9 +20,9 @@
               android:layout_height="wrap_content"
               android:gravity="center_vertical"
               android:orientation="horizontal"
-              android:layout_marginBottom="16dp"
               android:paddingStart="?android:attr/listPreferredItemPaddingStart"
               android:paddingTop="16dp"
+              android:paddingBottom="16dp"
               android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
 
     <LinearLayout
@@ -56,6 +56,7 @@
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
         android:layout_marginStart="16dip"
+        android:minWidth="32dp"
         android:text="@string/progress_scanning"/>
 
 </LinearLayout>
index 2f7aa64..aad73e8 100644 (file)
     <!-- Title for the installed app info storage page. The total storage space taken up by this app. [CHAR LIMIT=40]-->
     <string name="app_info_storage_title">Space used</string>
 
+    <!-- The divider symbol between different parts of the notification header including spaces. not translatable [CHAR LIMIT=3] -->
+    <string name="notification_header_divider_symbol_with_spaces" translatable="false">" • "</string>
+
+
 </resources>
index 45c784e..2d07fa2 100644 (file)
@@ -45,7 +45,8 @@
                     android:selectable="false"/>
     </PreferenceCategory>
 
-    <PreferenceCategory android:title="@string/enterprise_privacy_exposure_changes_category">
+    <PreferenceCategory android:title="@string/enterprise_privacy_exposure_changes_category"
+                        android:key="exposure_changes_category">
         <Preference android:fragment="com.android.settings.enterprise.ApplicationListFragment$EnterpriseInstalledPackages"
                     android:key="number_enterprise_installed_packages"
                     android:title="@string/enterprise_privacy_enterprise_installed_packages"/>
index fafbbb0..f9926ca 100644 (file)
 
     <!-- IPv6 Details -->
     <PreferenceCategory
-            android:key="ipv6_details_category"
+            android:key="ipv6_category"
             android:title="@string/wifi_details_ipv6_address_header"
-            android:selectable="false"/>
+            android:selectable="false">
+        <Preference
+                android:key="ipv6_addresses"
+                android:selectable="false"/>
+    </PreferenceCategory>
 
 </PreferenceScreen>
index 9655fd2..8a77fea 100644 (file)
@@ -603,7 +603,8 @@ public final class Utils extends com.android.settingslib.Utils {
     }
 
     /**
-     * Returns the managed profile of the current user or null if none found.
+     * Returns the managed profile of the current user or {@code null} if none is found or a profile
+     * exists but it is disabled.
      */
     public static UserHandle getManagedProfile(UserManager userManager) {
         List<UserHandle> userProfiles = userManager.getUserProfiles();
@@ -622,6 +623,29 @@ public final class Utils extends com.android.settingslib.Utils {
     }
 
     /**
+     * Returns the managed profile of the current user or {@code null} if none is found. Unlike
+     * {@link #getManagedProfile} this method returns enabled and disabled managed profiles.
+     */
+    public static UserHandle getManagedProfileWithDisabled(UserManager userManager) {
+        // TODO: Call getManagedProfileId from here once Robolectric supports
+        // API level 24 and UserManager.getProfileIdsWithDisabled can be Mocked (to avoid having
+        // yet another implementation that loops over user profiles in this method). In the meantime
+        // we need to use UserManager.getProfiles that is available on API 23 (the one currently
+        // used for Settings Robolectric tests).
+        final int myUserId = UserHandle.myUserId();
+        List<UserInfo> profiles = userManager.getProfiles(myUserId);
+        final int count = profiles.size();
+        for (int i = 0; i < count; i++) {
+            final UserInfo profile = profiles.get(i);
+            if (profile.isManagedProfile()
+                    && profile.getUserHandle().getIdentifier() != myUserId) {
+                return profile.getUserHandle();
+            }
+        }
+        return null;
+    }
+
+    /**
      * Retrieves the id for the given user's managed profile.
      *
      * @return the managed profile id or UserHandle.USER_NULL if there is none.
index 1d08968..acf43aa 100644 (file)
@@ -27,7 +27,7 @@ public class AutoSyncWorkDataPreferenceController extends AutoSyncPersonalDataPr
 
     public AutoSyncWorkDataPreferenceController(Context context, Fragment parent) {
         super(context, parent);
-        mUserHandle = Utils.getManagedProfile(mUserManager);
+        mUserHandle = Utils.getManagedProfileWithDisabled(mUserManager);
     }
 
     @Override
index 9323aa3..8d98b50 100644 (file)
@@ -29,6 +29,7 @@ public abstract class DynamicAvailabilityPreferenceController extends Preference
 
     private Preference mPreference;
     private PreferenceScreen mScreen;
+    private PreferenceAvailabilityObserver mAvailabilityObserver = null;
 
     public DynamicAvailabilityPreferenceController(Context context, Lifecycle lifecycle) {
         super(context);
@@ -37,6 +38,14 @@ public abstract class DynamicAvailabilityPreferenceController extends Preference
         }
     }
 
+    public void setAvailabilityObserver(PreferenceAvailabilityObserver observer) {
+        mAvailabilityObserver = observer;
+    }
+
+    public PreferenceAvailabilityObserver getAvailabilityObserver() {
+        return mAvailabilityObserver;
+    }
+
     @Override
     public void displayPreference(PreferenceScreen screen) {
         mScreen = screen;
@@ -56,4 +65,10 @@ public abstract class DynamicAvailabilityPreferenceController extends Preference
             mScreen.addPreference(mPreference);
         }
     }
+
+    protected void notifyOnAvailabilityUpdate(boolean available) {
+        if (mAvailabilityObserver != null) {
+            mAvailabilityObserver.onPreferenceAvailabilityUpdated(getPreferenceKey(), available);
+        }
+    }
 }
diff --git a/src/com/android/settings/core/PreferenceAvailabilityObserver.java b/src/com/android/settings/core/PreferenceAvailabilityObserver.java
new file mode 100644 (file)
index 0000000..46ff3ba
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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.core;
+
+/**
+ * @deprecated This interface allows a {@link android.support.v7.preference.PreferenceGroup}'s
+ * controller to observe the availability of the {@link android.support.v7.preference.Preference}s
+ * inside it, hiding the group when all preferences become unavailable. In the future,
+ * {@link android.support.v7.preference.PreferenceGroup} will have native support for that
+ * functionality, removing the need for this interface.
+ */
+public interface PreferenceAvailabilityObserver {
+
+    /**
+     * Notifies the observer that the availability of the preference identified by {@code key} has
+     * been updated.
+     */
+    void onPreferenceAvailabilityUpdated(String key, boolean available);
+}
index f0aca01..556baec 100644 (file)
@@ -50,15 +50,15 @@ public abstract class AdminGrantedPermissionsPreferenceControllerBase
                 true /* async */,
                 (num) -> {
                     if (num == 0) {
-                        preference.setVisible(false);
                         mHasApps = false;
                     } else {
-                        preference.setVisible(true);
                         preference.setSummary(mContext.getResources().getQuantityString(
                                 R.plurals.enterprise_privacy_number_packages_lower_bound,
                                 num, num));
                         mHasApps = true;
                     }
+                    preference.setVisible(mHasApps);
+                    notifyOnAvailabilityUpdate(mHasApps);
                 });
     }
 
@@ -80,6 +80,7 @@ public abstract class AdminGrantedPermissionsPreferenceControllerBase
         mFeatureProvider.calculateNumberOfAppsWithAdminGrantedPermissions(mPermissions,
                 false /* async */, (num) -> haveAppsWithAdminGrantedPermissions[0] = num > 0);
         mHasApps = haveAppsWithAdminGrantedPermissions[0];
+        notifyOnAvailabilityUpdate(mHasApps);
         return mHasApps;
     }
 
index e98ece8..32f2bbe 100644 (file)
@@ -42,7 +42,9 @@ public class AlwaysOnVpnCurrentUserPreferenceController
 
     @Override
     public boolean isAvailable() {
-        return mFeatureProvider.isAlwaysOnVpnSetInCurrentUser();
+        final boolean available = mFeatureProvider.isAlwaysOnVpnSetInCurrentUser();
+        notifyOnAvailabilityUpdate(available);
+        return available;
     }
 
     @Override
index 4796b75..8486368 100644 (file)
@@ -33,7 +33,9 @@ public class AlwaysOnVpnManagedProfilePreferenceController
 
     @Override
     public boolean isAvailable() {
-        return mFeatureProvider.isAlwaysOnVpnSetInManagedProfile();
+        final boolean available = mFeatureProvider.isAlwaysOnVpnSetInManagedProfile();
+        notifyOnAvailabilityUpdate(available);
+        return available;
     }
 
     @Override
index d020676..fc89dc8 100644 (file)
@@ -44,8 +44,11 @@ public class CaCertsPreferenceController extends DynamicAvailabilityPreferenceCo
 
     @Override
     public boolean isAvailable() {
-        return mFeatureProvider.getNumberOfOwnerInstalledCaCertsForCurrentUserAndManagedProfile()
-                > 0;
+        final boolean available =
+                mFeatureProvider.getNumberOfOwnerInstalledCaCertsForCurrentUserAndManagedProfile()
+                        > 0;
+        notifyOnAvailabilityUpdate(available);
+        return available;
     }
 
     @Override
index acbcc2e..9876f71 100644 (file)
@@ -42,14 +42,18 @@ public class EnterpriseInstalledPackagesPreferenceController
     public void updateState(Preference preference) {
         mFeatureProvider.calculateNumberOfPolicyInstalledApps(true /* async */,
                 (num) -> {
+                    final boolean available;
                     if (num == 0) {
-                        preference.setVisible(false);
+                        available = false;
                     } else {
-                        preference.setVisible(true);
+                        available = true;
                         preference.setSummary(mContext.getResources().getQuantityString(
                                 R.plurals.enterprise_privacy_number_packages_lower_bound, num,
                                 num));
+
                     }
+                    preference.setVisible(available);
+                    notifyOnAvailabilityUpdate(available);
                 });
     }
 
@@ -68,7 +72,9 @@ public class EnterpriseInstalledPackagesPreferenceController
         final Boolean[] haveEnterpriseInstalledPackages = { null };
         mFeatureProvider.calculateNumberOfPolicyInstalledApps(false /* async */,
                 (num) -> haveEnterpriseInstalledPackages[0] = num > 0);
-        return haveEnterpriseInstalledPackages[0];
+        final boolean available = haveEnterpriseInstalledPackages[0];
+        notifyOnAvailabilityUpdate(available);
+        return available;
     }
 
     @Override
index 69e0416..372982f 100644 (file)
@@ -45,7 +45,9 @@ public class EnterprisePrivacyPreferenceController extends DynamicAvailabilityPr
 
     @Override
     public boolean isAvailable() {
-        return mFeatureProvider.hasDeviceOwner();
+        final boolean available = mFeatureProvider.hasDeviceOwner();
+        notifyOnAvailabilityUpdate(available);
+        return available;
     }
 
     @Override
index a80dec0..f7327c8 100644 (file)
@@ -61,23 +61,32 @@ public class EnterprisePrivacySettings extends DashboardFragment {
         controllers.add(new NetworkLogsPreferenceController(context));
         controllers.add(new BugReportsPreferenceController(context));
         controllers.add(new SecurityLogsPreferenceController(context));
-        controllers.add(new EnterpriseInstalledPackagesPreferenceController(context, lifecycle,
-                async));
-        controllers.add(new AdminGrantedLocationPermissionsPreferenceController(context, lifecycle,
-                async));
-        controllers.add(new AdminGrantedMicrophonePermissionPreferenceController(context, lifecycle,
-                async));
-        controllers.add(new AdminGrantedCameraPermissionPreferenceController(context, lifecycle,
-                async));
-        controllers.add(new EnterpriseSetDefaultAppsPreferenceController(context, lifecycle));
-        controllers.add(new AlwaysOnVpnCurrentUserPreferenceController(context, lifecycle));
-        controllers.add(new AlwaysOnVpnManagedProfilePreferenceController(context, lifecycle));
-        controllers.add(new GlobalHttpProxyPreferenceController(context, lifecycle));
-        controllers.add(new CaCertsPreferenceController(context, lifecycle));
+        final List exposureChangesCategoryControllers = new ArrayList<PreferenceController>();
+        exposureChangesCategoryControllers.add(new EnterpriseInstalledPackagesPreferenceController(
+                context, lifecycle, async));
+        exposureChangesCategoryControllers.add(
+                new AdminGrantedLocationPermissionsPreferenceController(context, lifecycle, async));
+        exposureChangesCategoryControllers.add(
+                new AdminGrantedMicrophonePermissionPreferenceController(context, lifecycle,
+                        async));
+        exposureChangesCategoryControllers.add(new AdminGrantedCameraPermissionPreferenceController(
+                context, lifecycle, async));
+        exposureChangesCategoryControllers.add(new EnterpriseSetDefaultAppsPreferenceController(
+                context, lifecycle));
+        exposureChangesCategoryControllers.add(new AlwaysOnVpnCurrentUserPreferenceController(
+                context, lifecycle));
+        exposureChangesCategoryControllers.add(new AlwaysOnVpnManagedProfilePreferenceController(
+                context, lifecycle));
+        exposureChangesCategoryControllers.add(new ImePreferenceController(context, lifecycle));
+        exposureChangesCategoryControllers.add(new GlobalHttpProxyPreferenceController(context,
+                lifecycle));
+        exposureChangesCategoryControllers.add(new CaCertsPreferenceController(context, lifecycle));
+        controllers.addAll(exposureChangesCategoryControllers);
+        controllers.add(new ExposureChangesCategoryPreferenceController(context, lifecycle,
+                exposureChangesCategoryControllers, async));
         controllers.add(new FailedPasswordWipeCurrentUserPreferenceController(context, lifecycle));
         controllers.add(new FailedPasswordWipeManagedProfilePreferenceController(context,
                 lifecycle));
-        controllers.add(new ImePreferenceController(context, lifecycle));
         return controllers;
     }
 
index 2f43a61..35f6e41 100644 (file)
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (C) 2017 The Android Open Source Project
  *
@@ -48,7 +49,9 @@ public class EnterpriseSetDefaultAppsPreferenceController
 
     @Override
     public boolean isAvailable() {
-        return getNumberOfEnterpriseSetDefaultApps() > 0;
+        final boolean available = getNumberOfEnterpriseSetDefaultApps() > 0;
+        notifyOnAvailabilityUpdate(available);
+        return available;
     }
 
     @Override
diff --git a/src/com/android/settings/enterprise/ExposureChangesCategoryPreferenceController.java b/src/com/android/settings/enterprise/ExposureChangesCategoryPreferenceController.java
new file mode 100644 (file)
index 0000000..4c89659
--- /dev/null
@@ -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.enterprise;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.core.DynamicAvailabilityPreferenceController;
+import com.android.settings.core.PreferenceAvailabilityObserver;
+import com.android.settings.core.lifecycle.Lifecycle;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A controller that hides a {@link android.support.v7.preference.PreferenceGroup} when none of the
+ * {@link Preference}s inside it are visible.
+ *
+ * TODO(b/62051162): Use {@link android.support.v7.preference.PreferenceGroup}'s native ability to
+ * hide itself when all {@link Preference}s inside it are invisible when that functionality becomes
+ * available. This custom controller will still be needed to remove the
+ * {@link android.support.v7.preference.PreferenceGroup} from the search index as required (by
+ * having {@link #isAvailable()} return {@code false} if the method returns {@code false} for all
+ * {@link Preference}s in the {@link android.support.v7.preference.PreferenceGroup}).
+ */
+public class ExposureChangesCategoryPreferenceController
+        extends DynamicAvailabilityPreferenceController implements PreferenceAvailabilityObserver {
+
+    private static final String KEY_EXPOSURE_CHANGES_CATEGORY = "exposure_changes_category";
+    private final Set<String> mAvailablePrefs = new HashSet<String>();
+    private Preference mPreference = null;
+    private boolean mControllingUi;
+
+    /**
+     * When {@code controllingUi} is {@code true}, some of the preferences may have their visibility
+     * determined asynchronously. In this case, {@link #isAvailable()} must always return {@code
+     * true} and the group should be hidden using {@link Preference#setVisible()} if all preferences
+     * report that they are invisible.
+     * When {@code controllingUi} is {@code false}, we are running on the search indexer thread and
+     * visibility must be determined synchronously. {@link #isAvailable()} can rely on all
+     * preferences having their visibility determined already and should return whether the group is
+     * visible or not.
+     */
+    public ExposureChangesCategoryPreferenceController(Context context, Lifecycle lifecycle,
+            List<DynamicAvailabilityPreferenceController> controllers, boolean controllingUi) {
+        super(context, lifecycle);
+        mControllingUi = controllingUi;
+        for (final DynamicAvailabilityPreferenceController controller : controllers) {
+            controller.setAvailabilityObserver(this);
+        }
+    }
+
+    @Override
+    public void onPreferenceAvailabilityUpdated(String key, boolean available) {
+        if (available) {
+            mAvailablePrefs.add(key);
+        } else {
+            mAvailablePrefs.remove(key);
+        }
+        available = haveAnyVisiblePreferences();
+        if (mControllingUi) {
+            notifyOnAvailabilityUpdate(available);
+        }
+        if (mPreference != null) {
+            mPreference.setVisible(available);
+        }
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        mPreference = preference;
+        mPreference.setVisible(haveAnyVisiblePreferences());
+    }
+
+    @Override
+    public boolean isAvailable() {
+        if (mControllingUi) {
+            // When running on the main UI thread, some preferences determine their visibility
+            // asynchronously. Always return true here and determine the pref group's actual
+            // visibility as the other preferences report their visibility asynchronously via
+            // onPreferenceAvailabilityUpdated().
+            return true;
+        }
+        final boolean available = haveAnyVisiblePreferences();
+        notifyOnAvailabilityUpdate(available);
+        return available;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_EXPOSURE_CHANGES_CATEGORY;
+    }
+
+    private boolean haveAnyVisiblePreferences() {
+        return mAvailablePrefs.size() > 0;
+    }
+}
index 2f35da6..7485fe2 100644 (file)
@@ -46,6 +46,8 @@ public abstract class FailedPasswordWipePreferenceControllerBase
 
     @Override
     public boolean isAvailable() {
-        return getMaximumFailedPasswordsBeforeWipe() > 0;
+        final boolean available = getMaximumFailedPasswordsBeforeWipe() > 0;
+        notifyOnAvailabilityUpdate(available);
+        return available;
     }
 }
index 6ee7fc4..76c49da 100644 (file)
@@ -32,7 +32,9 @@ public class GlobalHttpProxyPreferenceController extends DynamicAvailabilityPref
 
     @Override
     public boolean isAvailable() {
-        return mFeatureProvider.isGlobalHttpProxySet();
+        final boolean available = mFeatureProvider.isGlobalHttpProxySet();
+        notifyOnAvailabilityUpdate(available);
+        return available;
     }
 
     @Override
index b090bed..b13aec7 100644 (file)
@@ -43,7 +43,9 @@ public class ImePreferenceController extends DynamicAvailabilityPreferenceContro
 
     @Override
     public boolean isAvailable() {
-        return mFeatureProvider.getImeLabelIfOwnerSet() != null;
+        final boolean available = mFeatureProvider.getImeLabelIfOwnerSet() != null;
+        notifyOnAvailabilityUpdate(available);
+        return available;
     }
 
     @Override
index efcb5a1..6c4f27c 100644 (file)
@@ -95,7 +95,6 @@ public class AppNotificationSettings extends NotificationSettingsBase {
         getPreferenceScreen().setOrderingAsAdded(true);
         setupBlock();
         addHeaderPref();
-        addAppLinkPref();
 
         mShowLegacyChannelConfig = mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid);
         if (mShowLegacyChannelConfig) {
@@ -120,6 +119,7 @@ public class AppNotificationSettings extends NotificationSettingsBase {
                         return;
                     }
                     populateChannelList();
+                    addAppLinkPref();
                 }
             }.execute();
         }
index 8c43a3f..3ae0bfa 100644 (file)
@@ -22,15 +22,17 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 
 import android.app.Activity;
 import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
 import android.content.Intent;
-import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.UserHandle;
+import android.os.AsyncTask;
 import android.provider.Settings;
 import android.support.v7.preference.Preference;
 import android.text.TextUtils;
+import android.text.BidiFormatter;
+import android.text.SpannableStringBuilder;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -38,7 +40,6 @@ import android.view.View;
 import android.widget.Switch;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.widget.LockPatternUtils;
 import com.android.settings.AppHeader;
 import com.android.settings.R;
 import com.android.settings.RingtonePreference;
@@ -64,6 +65,8 @@ public class ChannelNotificationSettings extends NotificationSettingsBase {
     private RestrictedSwitchPreference mVibrate;
     private NotificationSoundPreference mRingtone;
     private FooterPreference mFooter;
+    private NotificationChannelGroup mChannelGroup;
+    private AppHeaderController mHeaderPref;
 
     @Override
     public int getMetricsCategory() {
@@ -93,6 +96,27 @@ public class ChannelNotificationSettings extends NotificationSettingsBase {
             mShowLegacyChannelConfig = true;
         } else {
             populateUpgradedChannelPrefs();
+
+            if (mChannel.getGroup() != null) {
+                // Go look up group name
+                new AsyncTask<Void, Void, Void>() {
+                    @Override
+                    protected Void doInBackground(Void... unused) {
+                        if (mChannel.getGroup() != null) {
+                            mChannelGroup = mBackend.getGroup(mChannel.getGroup(), mPkg, mUid);
+                        }
+                        return null;
+                    }
+
+                    @Override
+                    protected void onPostExecute(Void unused) {
+                        if (getHost() == null || mChannelGroup == null) {
+                            return;
+                        }
+                        setChannelGroupLabel(mChannelGroup.getName());
+                    }
+                }.execute();
+            }
         }
 
         updateDependents(mChannel.getImportance() == IMPORTANCE_NONE);
@@ -114,9 +138,10 @@ public class ChannelNotificationSettings extends NotificationSettingsBase {
         rows.put(mAppRow.pkg, mAppRow);
         collectConfigActivities(rows);
         final Activity activity = getActivity();
-        final Preference pref = FeatureFactory.getFactory(activity)
+        mHeaderPref = FeatureFactory.getFactory(activity)
                 .getApplicationFeatureProvider(activity)
-                .newAppHeaderController(this /* fragment */, null /* appHeader */)
+                .newAppHeaderController(this /* fragment */, null /* appHeader */);
+        final Preference pref = mHeaderPref
                 .setIcon(mAppRow.icon)
                 .setLabel(mChannel.getName())
                 .setSummary(mAppRow.label)
@@ -128,6 +153,20 @@ public class ChannelNotificationSettings extends NotificationSettingsBase {
         getPreferenceScreen().addPreference(pref);
     }
 
+    private void setChannelGroupLabel(CharSequence groupName) {
+        final SpannableStringBuilder summary = new SpannableStringBuilder();
+        BidiFormatter bidi = BidiFormatter.getInstance();
+        summary.append(bidi.unicodeWrap(mAppRow.label.toString()));
+        if (groupName != null) {
+            summary.append(bidi.unicodeWrap(mContext.getText(
+                    R.string.notification_header_divider_symbol_with_spaces)));
+            summary.append(bidi.unicodeWrap(groupName.toString()));
+        }
+        final Activity activity = getActivity();
+        mHeaderPref.setSummary(summary.toString());
+        mHeaderPref.done(activity, getPrefContext());
+    }
+
     private void addFooterPref() {
         if (!TextUtils.isEmpty(mChannel.getDescription())) {
             FooterPreference descPref = new FooterPreference(getPrefContext());
@@ -175,6 +214,7 @@ public class ChannelNotificationSettings extends NotificationSettingsBase {
     private void setupVibrate() {
         mVibrate = (RestrictedSwitchPreference) findPreference(KEY_VIBRATE);
         mVibrate.setDisabledByAdmin(mSuspendedAppsAdmin);
+        mVibrate.setEnabled(!(mAppRow.lockedImportance || mVibrate.isDisabledByAdmin()));
         mVibrate.setChecked(mChannel.shouldVibrate());
         mVibrate.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
             @Override
@@ -191,6 +231,7 @@ public class ChannelNotificationSettings extends NotificationSettingsBase {
     private void setupRingtone() {
         mRingtone = (NotificationSoundPreference) findPreference(KEY_RINGTONE);
         mRingtone.setRingtone(mChannel.getSound());
+        mRingtone.setEnabled(!(mAppRow.lockedImportance));
         mRingtone.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
             @Override
             public boolean onPreferenceChange(Preference preference, Object newValue) {
@@ -246,12 +287,15 @@ public class ChannelNotificationSettings extends NotificationSettingsBase {
         channelArgs.putBoolean(AppHeader.EXTRA_HIDE_INFO_BUTTON, true);
         channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mPkg);
         channelArgs.putString(Settings.EXTRA_CHANNEL_ID, mChannel.getId());
-        Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(),
-                ChannelImportanceSettings.class.getName(),
-                channelArgs, null, R.string.notification_importance_title, null,
-                false, getMetricsCategory());
-        mImportance.setIntent(channelIntent);
-        mImportance.setEnabled(mSuspendedAppsAdmin == null);
+        mImportance.setEnabled(mSuspendedAppsAdmin == null && !mAppRow.lockedImportance);
+        // Set up intent to show importance selection only if this setting is enabled.
+        if (mImportance.isEnabled()) {
+            Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(),
+                    ChannelImportanceSettings.class.getName(),
+                    channelArgs, null, R.string.notification_importance_title, null,
+                    false, getMetricsCategory());
+            mImportance.setIntent(channelIntent);
+        }
         mImportance.setSummary(getImportanceSummary(mChannel.getImportance()));
     }
 
@@ -345,7 +389,7 @@ public class ChannelNotificationSettings extends NotificationSettingsBase {
         if (mAppLink != null) {
             setVisible(mAppLink, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
         }
-        if (mFooter !=null) {
+        if (mFooter != null) {
             setVisible(mFooter, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
         }
     }
index 84d7e31..96737db 100644 (file)
@@ -122,6 +122,19 @@ public class NotificationBackend {
         }
     }
 
+
+    public NotificationChannelGroup getGroup(String groupId, String pkg, int uid) {
+        if (groupId == null) {
+            return null;
+        }
+        try {
+            return sINM.getNotificationChannelGroupForPackage(groupId, pkg, uid);
+        } catch (Exception e) {
+            Log.w(TAG, "Error calling NoMan", e);
+            return null;
+        }
+    }
+
     public ParceledListSlice<NotificationChannelGroup> getChannelGroups(String pkg, int uid) {
         try {
             return sINM.getNotificationChannelGroupsForPackage(pkg, uid, false);
index bfa9355..717cf08 100644 (file)
@@ -263,7 +263,7 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen
     }
 
     protected void addAppLinkPref() {
-        if (mAppRow.settingsIntent != null) {
+        if (mAppRow.settingsIntent != null && mAppLink == null) {
             mAppLink = new Preference(getPrefContext());
             mAppLink.setKey(KEY_APP_LINK);
             mAppLink.setOrder(500);
@@ -293,6 +293,8 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen
     private void setupImportanceToggle() {
         mImportanceToggle = (RestrictedSwitchPreference) findPreference(KEY_ALLOW_SOUND);
         mImportanceToggle.setDisabledByAdmin(mSuspendedAppsAdmin);
+        mImportanceToggle.setEnabled(!(mAppRow.lockedImportance
+                || mImportanceToggle.isDisabledByAdmin()));
         mImportanceToggle.setChecked(mChannel.getImportance() >= IMPORTANCE_DEFAULT
                 || mChannel.getImportance() == IMPORTANCE_UNSPECIFIED);
         mImportanceToggle.setOnPreferenceChangeListener(
@@ -313,6 +315,7 @@ abstract public class NotificationSettingsBase extends SettingsPreferenceFragmen
     protected void setupPriorityPref(boolean priority) {
         mPriority = (RestrictedSwitchPreference) findPreference(KEY_BYPASS_DND);
         mPriority.setDisabledByAdmin(mSuspendedAppsAdmin);
+        mPriority.setEnabled(!(mAppRow.lockedImportance || mPriority.isDisabledByAdmin()));
         mPriority.setChecked(priority);
         mPriority.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
             @Override
index 8a51961..b4fe6d8 100644 (file)
@@ -91,9 +91,10 @@ public class VolumeSeekBarPreference extends SeekBarPreference {
         mStopped = true;
         if (mVolumizer != null) {
             mVolumizer.stop();
+            mVolumizer = null;
         }
     }
-    
+
     @Override
     public void onBindViewHolder(PreferenceViewHolder view) {
         super.onBindViewHolder(view);
index 6d34ad1..b62df56 100644 (file)
@@ -19,6 +19,7 @@ package com.android.settings.wifi;
 import android.content.Context;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceViewHolder;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.widget.TextView;
 
@@ -37,6 +38,7 @@ public class WifiDetailPreference extends Preference {
     }
 
     public void setDetailText(String text) {
+        if (TextUtils.equals(mDetailText, text)) return;
         mDetailText = text;
         notifyChanged();
     }
index b3f83b3..c0dd0a8 100644 (file)
@@ -27,6 +27,7 @@ import android.graphics.drawable.Drawable;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.IpPrefix;
+import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkBadging;
@@ -67,6 +68,7 @@ import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.List;
 import java.util.StringJoiner;
+import java.util.stream.Collectors;
 
 /**
  * Controller for logic pertaining to displaying Wifi information for the
@@ -100,7 +102,9 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
     @VisibleForTesting
     static final String KEY_DNS_PREF = "dns";
     @VisibleForTesting
-    static final String KEY_IPV6_ADDRESS_CATEGORY = "ipv6_details_category";
+    static final String KEY_IPV6_CATEGORY = "ipv6_category";
+    @VisibleForTesting
+    static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
 
     private AccessPoint mAccessPoint;
     private final ConnectivityManagerWrapper mConnectivityManagerWrapper;
@@ -133,8 +137,9 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
     private WifiDetailPreference mGatewayPref;
     private WifiDetailPreference mSubnetPref;
     private WifiDetailPreference mDnsPref;
+    private PreferenceCategory mIpv6Category;
+    private Preference mIpv6AddressPref;
 
-    private PreferenceCategory mIpv6AddressCategory;
     private final IntentFilter mFilter;
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -241,8 +246,8 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
         mSubnetPref = (WifiDetailPreference) screen.findPreference(KEY_SUBNET_MASK_PREF);
         mDnsPref = (WifiDetailPreference) screen.findPreference(KEY_DNS_PREF);
 
-        mIpv6AddressCategory =
-                (PreferenceCategory) screen.findPreference(KEY_IPV6_ADDRESS_CATEGORY);
+        mIpv6Category = (PreferenceCategory) screen.findPreference(KEY_IPV6_CATEGORY);
+        mIpv6AddressPref = (Preference) screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
 
         mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */));
         mForgetButton = (Button) mButtonsPref.findViewById(R.id.forget_button);
@@ -315,8 +320,6 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
         mFrequencyPref.setDetailText(band);
 
         updateIpLayerInfo();
-        mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE
-                || mSignInButton.getVisibility() == View.VISIBLE);
     }
 
     private void exitActivity() {
@@ -348,74 +351,69 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
         mSignalStrengthPref.setDetailText(mSignalStr[summarySignalLevel]);
     }
 
+    private void updatePreference(WifiDetailPreference pref, String detailText) {
+        if (!TextUtils.isEmpty(detailText)) {
+            pref.setDetailText(detailText);
+            pref.setVisible(true);
+        } else {
+            pref.setVisible(false);
+        }
+    }
+
     private void updateIpLayerInfo() {
         mSignInButton.setVisibility(canSignIntoNetwork() ? View.VISIBLE : View.INVISIBLE);
-
-        // Reset all fields
-        mIpv6AddressCategory.removeAll();
-        mIpv6AddressCategory.setVisible(false);
-        mIpAddressPref.setVisible(false);
-        mSubnetPref.setVisible(false);
-        mGatewayPref.setVisible(false);
-        mDnsPref.setVisible(false);
+        mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE
+                || mSignInButton.getVisibility() == View.VISIBLE);
 
         if (mNetwork == null || mLinkProperties == null) {
+            mIpAddressPref.setVisible(false);
+            mSubnetPref.setVisible(false);
+            mGatewayPref.setVisible(false);
+            mDnsPref.setVisible(false);
+            mIpv6Category.setVisible(false);
             return;
         }
-        List<InetAddress> addresses = mLinkProperties.getAddresses();
-
-        // Set IPv4 and IPv6 addresses
-        for (int i = 0; i < addresses.size(); i++) {
-            InetAddress addr = addresses.get(i);
-            if (addr instanceof Inet4Address) {
-                mIpAddressPref.setDetailText(addr.getHostAddress());
-                mIpAddressPref.setVisible(true);
-            } else if (addr instanceof Inet6Address) {
-                String ip = addr.getHostAddress();
-                Preference pref = new Preference(mPrefContext);
-                pref.setKey(ip);
-                pref.setTitle(ip);
-                pref.setSelectable(false);
-                mIpv6AddressCategory.addPreference(pref);
-                mIpv6AddressCategory.setVisible(true);
+
+        // Find IPv4 and IPv6 addresses.
+        String ipv4Address = null;
+        String subnet = null;
+        StringJoiner ipv6Addresses = new StringJoiner("\n");
+
+        for (LinkAddress addr : mLinkProperties.getLinkAddresses()) {
+            if (addr.getAddress() instanceof Inet4Address) {
+                ipv4Address = addr.getAddress().getHostAddress();
+                subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
+            } else if (addr.getAddress() instanceof Inet6Address) {
+                ipv6Addresses.add(addr.getAddress().getHostAddress());
             }
         }
 
-        // Set up IPv4 gateway and subnet mask
+        // Find IPv4 default gateway.
         String gateway = null;
-        String subnet = null;
         for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
-            if (routeInfo.hasGateway() && routeInfo.getGateway() instanceof Inet4Address) {
+            if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
                 gateway = routeInfo.getGateway().getHostAddress();
+                break;
             }
-            IpPrefix ipPrefix = routeInfo.getDestination();
-            if (ipPrefix != null && ipPrefix.getAddress() instanceof Inet4Address
-                    && ipPrefix.getPrefixLength() > 0) {
-                subnet = ipv4PrefixLengthToSubnetMask(ipPrefix.getPrefixLength());
-            }
-        }
-
-        if (!TextUtils.isEmpty(subnet)) {
-            mSubnetPref.setDetailText(subnet);
-            mSubnetPref.setVisible(true);
         }
 
-        if (!TextUtils.isEmpty(gateway)) {
-            mGatewayPref.setDetailText(gateway);
-            mGatewayPref.setVisible(true);
-        }
-
-        // Set IPv4 DNS addresses
-        StringJoiner stringJoiner = new StringJoiner(",");
-        for (InetAddress dnsServer : mLinkProperties.getDnsServers()) {
-            if (dnsServer instanceof Inet4Address) {
-                stringJoiner.add(dnsServer.getHostAddress());
-            }
-        }
-        String dnsText = stringJoiner.toString();
-        if (!dnsText.isEmpty()) {
-            mDnsPref.setDetailText(dnsText);
-            mDnsPref.setVisible(true);
+        // Find IPv4 DNS addresses.
+        String dnsServers = mLinkProperties.getDnsServers().stream()
+                .filter(Inet4Address.class::isInstance)
+                .map(InetAddress::getHostAddress)
+                .collect(Collectors.joining(","));
+
+        // Update UI.
+        updatePreference(mIpAddressPref, ipv4Address);
+        updatePreference(mSubnetPref, subnet);
+        updatePreference(mGatewayPref, gateway);
+        updatePreference(mDnsPref, dnsServers);
+
+        if (ipv6Addresses.length() > 0) {
+            mIpv6AddressPref.setSummary(ipv6Addresses.toString());
+            mIpv6Category.setVisible(true);
+        } else {
+            mIpv6Category.setVisible(false);
         }
     }
 
index d93d3a7..cd66d5e 100644 (file)
@@ -21,6 +21,7 @@ import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Answers.RETURNS_DEEP_STUBS;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.when;
 
 import android.app.Fragment;
@@ -46,6 +47,8 @@ import java.util.List;
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class AutoSyncWorkDataPreferenceControllerTest {
 
+    private static int MANAGED_PROFILE_ID = 10;
+
     @Mock(answer = RETURNS_DEEP_STUBS)
     private UserManager mUserManager;
     @Mock(answer = RETURNS_DEEP_STUBS)
@@ -80,29 +83,30 @@ public class AutoSyncWorkDataPreferenceControllerTest {
 
     @Test
     public void checkIsAvailable_singleUserProfile_shouldNotDisplay() {
-        final List<UserInfo> infos = new ArrayList<>();
-        infos.add(new UserInfo(1, "user 1", 0));
         when(mUserManager.isManagedProfile()).thenReturn(false);
         when(mUserManager.isLinkedUser()).thenReturn(false);
-        when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(UserHandle.USER_SYSTEM, "user 1", 0 /* flags */));
+        when(mUserManager.getProfiles(eq(UserHandle.USER_SYSTEM))).thenReturn(infos);
 
         assertThat(mController.isAvailable()).isFalse();
     }
 
     @Test
     public void multipleProfile_shouldInitWithWorkProfileUserHandle() {
-        final int id1 = 1;
-        final int id2 = 2;
-        final UserInfo managedUser = new UserInfo(id2, "user 2", FLAG_MANAGED_PROFILE);
-        final List<UserHandle> infos = new ArrayList<>();
-        infos.add(new UserHandle(id1));
-        infos.add(new UserHandle(id2));
-        when(mUserManager.getUserProfiles()).thenReturn(infos);
-        when(mUserManager.getUserHandle()).thenReturn(id1);
-        when(mUserManager.getUserInfo(id2)).thenReturn(managedUser);
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.isLinkedUser()).thenReturn(false);
+
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(UserHandle.USER_SYSTEM, "user 1", 0 /* flags */));
+        infos.add(new UserInfo(
+                MANAGED_PROFILE_ID, "work profile", UserInfo.FLAG_MANAGED_PROFILE));
+        when(mUserManager.getProfiles(eq(UserHandle.USER_SYSTEM))).thenReturn(infos);
 
         mController = new AutoSyncWorkDataPreferenceController(mContext, mFragment);
 
-        assertThat(mController.mUserHandle.getIdentifier()).isEqualTo(id2);
+        assertThat(mController.mUserHandle.getIdentifier()).isEqualTo(MANAGED_PROFILE_ID);
+        assertThat(mController.isAvailable()).isTrue();
     }
 }
index 38a8356..399c753 100644 (file)
@@ -22,6 +22,7 @@ import android.support.v7.preference.PreferenceScreen;
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
 import com.android.settings.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -50,6 +51,7 @@ public final class DynamicAvailabilityPreferenceControllerTest {
     private @Mock Preference mPreference;
     private @Mock PreferenceScreen mScreen;
     private @Mock Lifecycle mLifecycle;
+    private @Mock PreferenceAvailabilityObserver mObserver;
 
     private boolean mIsAvailable;
     private Preference mUpdatedPreference = null;
@@ -115,6 +117,21 @@ public final class DynamicAvailabilityPreferenceControllerTest {
         assertThat(mUpdatedPreference).isEqualTo(mPreference);
     }
 
+    @Test
+    public void testNotifyOnAvailabilityUpdate() {
+        final DynamicAvailabilityPreferenceController controller
+                = new DynamicAvailabilityPreferenceControllerTestable(mLifecycle);
+        controller.setAvailabilityObserver(mObserver);
+        assertThat(controller.getAvailabilityObserver()).isEqualTo(mObserver);
+
+        mIsAvailable = false;
+        controller.isAvailable();
+        verify(mObserver).onPreferenceAvailabilityUpdated(PREFERENCE_KEY, false);
+
+        mIsAvailable = true;
+        controller.isAvailable();
+        verify(mObserver).onPreferenceAvailabilityUpdated(PREFERENCE_KEY, true);
+    }
 
     private class DynamicAvailabilityPreferenceControllerTestable
             extends DynamicAvailabilityPreferenceController {
@@ -124,6 +141,7 @@ public final class DynamicAvailabilityPreferenceControllerTest {
 
         @Override
         public boolean isAvailable() {
+            notifyOnAvailabilityUpdate(mIsAvailable);
             return mIsAvailable;
         }
 
index 96ce081..c1a3143 100644 (file)
@@ -21,6 +21,7 @@ import android.support.v7.preference.Preference;
 
 import com.android.settings.R;
 import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.core.PreferenceAvailabilityObserver;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -32,9 +33,12 @@ import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -48,6 +52,7 @@ public abstract class AdminGrantedPermissionsPreferenceControllerTestBase {
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     protected Context mContext;
     private FakeFeatureFactory mFeatureFactory;
+    @Mock private PreferenceAvailabilityObserver mObserver;
 
     protected AdminGrantedPermissionsPreferenceControllerBase mController;
 
@@ -64,6 +69,12 @@ public abstract class AdminGrantedPermissionsPreferenceControllerTestBase {
         FakeFeatureFactory.setupForTest(mContext);
         mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
         mController = createController(true /* async */);
+        mController.setAvailabilityObserver(mObserver);
+    }
+
+    @Test
+    public void testGetAvailabilityObserver() {
+        assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
     }
 
     private void setNumberOfPackagesWithAdminGrantedPermissions(int number, boolean async) {
@@ -85,6 +96,7 @@ public abstract class AdminGrantedPermissionsPreferenceControllerTestBase {
         setNumberOfPackagesWithAdminGrantedPermissions(0, true /* async */);
         mController.updateState(preference);
         assertThat(preference.isVisible()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(mKey, false);
 
         setNumberOfPackagesWithAdminGrantedPermissions(20, true /* async */);
         when(mContext.getResources().getQuantityString(
@@ -93,27 +105,33 @@ public abstract class AdminGrantedPermissionsPreferenceControllerTestBase {
         mController.updateState(preference);
         assertThat(preference.getSummary()).isEqualTo("minimum 20 apps");
         assertThat(preference.isVisible()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(mKey, true);
     }
 
     @Test
     public void testIsAvailableSync() {
         final AdminGrantedPermissionsPreferenceControllerBase controller
                 = createController(false /* async */);
+        controller.setAvailabilityObserver(mObserver);
 
         setNumberOfPackagesWithAdminGrantedPermissions(0, false /* async */);
         assertThat(controller.isAvailable()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(mKey, false);
 
         setNumberOfPackagesWithAdminGrantedPermissions(20, false /* async */);
         assertThat(controller.isAvailable()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(mKey, true);
     }
 
     @Test
     public void testIsAvailableAsync() {
         setNumberOfPackagesWithAdminGrantedPermissions(0, true /* async */);
         assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver, never()).onPreferenceAvailabilityUpdated(eq(mKey), anyBoolean());
 
         setNumberOfPackagesWithAdminGrantedPermissions(20, true /* async */);
         assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver, never()).onPreferenceAvailabilityUpdated(eq(mKey), anyBoolean());
     }
 
     @Test
index 9d1bd58..51c8a7b 100644 (file)
@@ -22,6 +22,7 @@ import android.support.v7.preference.Preference;
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -33,6 +34,7 @@ import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -42,12 +44,14 @@ import static org.mockito.Mockito.when;
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public final class AlwaysOnVpnCurrentUserPreferenceControllerTest {
 
-    private final String VPN_SET_DEVICE = "VPN set";
-    private final String VPN_SET_PERSONAL = "VPN set in personal profile";
+    private static final String VPN_SET_DEVICE = "VPN set";
+    private static final String VPN_SET_PERSONAL = "VPN set in personal profile";
+    private static final String KEY_ALWAYS_ON_VPN_PRIMARY_USER = "always_on_vpn_primary_user";
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
     private FakeFeatureFactory mFeatureFactory;
+    @Mock private PreferenceAvailabilityObserver mObserver;
 
     private AlwaysOnVpnCurrentUserPreferenceController mController;
 
@@ -62,6 +66,12 @@ public final class AlwaysOnVpnCurrentUserPreferenceControllerTest {
                 .thenReturn(VPN_SET_DEVICE);
         when(mContext.getString(R.string.enterprise_privacy_always_on_vpn_personal))
                 .thenReturn(VPN_SET_PERSONAL);
+        mController.setAvailabilityObserver(mObserver);
+    }
+
+    @Test
+    public void testGetAvailabilityObserver() {
+        assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
     }
 
     @Test
@@ -85,10 +95,12 @@ public final class AlwaysOnVpnCurrentUserPreferenceControllerTest {
         when(mFeatureFactory.enterprisePrivacyFeatureProvider.isAlwaysOnVpnSetInCurrentUser())
                 .thenReturn(false);
         assertThat(mController.isAvailable()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_ALWAYS_ON_VPN_PRIMARY_USER, false);
 
         when(mFeatureFactory.enterprisePrivacyFeatureProvider.isAlwaysOnVpnSetInCurrentUser())
                 .thenReturn(true);
         assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_ALWAYS_ON_VPN_PRIMARY_USER, true);
     }
 
     @Test
@@ -99,6 +111,6 @@ public final class AlwaysOnVpnCurrentUserPreferenceControllerTest {
 
     @Test
     public void testGetPreferenceKey() {
-        assertThat(mController.getPreferenceKey()).isEqualTo("always_on_vpn_primary_user");
+        assertThat(mController.getPreferenceKey()).isEqualTo(KEY_ALWAYS_ON_VPN_PRIMARY_USER);
     }
 }
index 8ac10d1..4562711 100644 (file)
@@ -21,6 +21,7 @@ import android.support.v7.preference.Preference;
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -32,6 +33,7 @@ import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -41,9 +43,12 @@ import static org.mockito.Mockito.when;
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public final class AlwaysOnVpnManagedProfilePreferenceControllerTest {
 
+    private static final String KEY_ALWAYS_ON_VPN_MANAGED_PROFILE = "always_on_vpn_managed_profile";
+
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
     private FakeFeatureFactory mFeatureFactory;
+    @Mock private PreferenceAvailabilityObserver mObserver;
 
     private AlwaysOnVpnManagedProfilePreferenceController mController;
 
@@ -54,6 +59,12 @@ public final class AlwaysOnVpnManagedProfilePreferenceControllerTest {
         mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
         mController = new AlwaysOnVpnManagedProfilePreferenceController(mContext,
                 null /* lifecycle */);
+        mController.setAvailabilityObserver(mObserver);
+    }
+
+    @Test
+    public void testGetAvailabilityObserver() {
+        assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
     }
 
     @Test
@@ -61,10 +72,12 @@ public final class AlwaysOnVpnManagedProfilePreferenceControllerTest {
         when(mFeatureFactory.enterprisePrivacyFeatureProvider.isAlwaysOnVpnSetInManagedProfile())
                 .thenReturn(false);
         assertThat(mController.isAvailable()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_ALWAYS_ON_VPN_MANAGED_PROFILE, false);
 
         when(mFeatureFactory.enterprisePrivacyFeatureProvider.isAlwaysOnVpnSetInManagedProfile())
                 .thenReturn(true);
         assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_ALWAYS_ON_VPN_MANAGED_PROFILE, true);
     }
 
     @Test
@@ -75,6 +88,6 @@ public final class AlwaysOnVpnManagedProfilePreferenceControllerTest {
 
     @Test
     public void testGetPreferenceKey() {
-        assertThat(mController.getPreferenceKey()).isEqualTo("always_on_vpn_managed_profile");
+        assertThat(mController.getPreferenceKey()).isEqualTo(KEY_ALWAYS_ON_VPN_MANAGED_PROFILE);
     }
 }
index fef2e0f..2c0e320 100644 (file)
@@ -23,6 +23,7 @@ import android.support.v7.preference.Preference;
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -34,6 +35,7 @@ import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -43,9 +45,12 @@ import static org.mockito.Mockito.when;
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public final class CaCertsPreferenceControllerTest {
 
+    private static final String KEY_CA_CERTS = "ca_certs";
+
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
     private FakeFeatureFactory mFeatureFactory;
+    @Mock private PreferenceAvailabilityObserver mObserver;
 
     private CaCertsPreferenceController mController;
 
@@ -55,6 +60,12 @@ public final class CaCertsPreferenceControllerTest {
         FakeFeatureFactory.setupForTest(mContext);
         mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
         mController = new CaCertsPreferenceController(mContext, null /* lifecycle */);
+        mController.setAvailabilityObserver(mObserver);
+    }
+
+    @Test
+    public void testGetAvailabilityObserver() {
+        assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
     }
 
     @Test
@@ -74,10 +85,12 @@ public final class CaCertsPreferenceControllerTest {
         when(mFeatureFactory.enterprisePrivacyFeatureProvider
                 .getNumberOfOwnerInstalledCaCertsForCurrentUserAndManagedProfile()).thenReturn(0);
         assertThat(mController.isAvailable()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_CA_CERTS, false);
 
         when(mFeatureFactory.enterprisePrivacyFeatureProvider
                 .getNumberOfOwnerInstalledCaCertsForCurrentUserAndManagedProfile()).thenReturn(10);
         assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_CA_CERTS, true);
     }
 
     @Test
@@ -88,6 +101,6 @@ public final class CaCertsPreferenceControllerTest {
 
     @Test
     public void testGetPreferenceKey() {
-        assertThat(mController.getPreferenceKey()).isEqualTo("ca_certs");
+        assertThat(mController.getPreferenceKey()).isEqualTo(KEY_CA_CERTS);
     }
 }
index 4255d96..cf54bb0 100644 (file)
@@ -24,6 +24,7 @@ import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.core.PreferenceAvailabilityObserver;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -37,9 +38,12 @@ import org.mockito.stubbing.Answer;
 import org.robolectric.annotation.Config;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyObject;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -49,9 +53,13 @@ import static org.mockito.Mockito.when;
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public final class EnterpriseInstalledPackagesPreferenceControllerTest {
 
+    private static final String KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES
+            = "number_enterprise_installed_packages";
+
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
     private FakeFeatureFactory mFeatureFactory;
+    @Mock private PreferenceAvailabilityObserver mObserver;
 
     private EnterpriseInstalledPackagesPreferenceController mController;
 
@@ -62,6 +70,12 @@ public final class EnterpriseInstalledPackagesPreferenceControllerTest {
         mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
         mController = new EnterpriseInstalledPackagesPreferenceController(mContext,
                 null /* lifecycle */, true /* async */);
+        mController.setAvailabilityObserver(mObserver);
+    }
+
+    @Test
+    public void testGetAvailabilityObserver() {
+        assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
     }
 
     private void setNumberOfEnterpriseInstalledPackages(int number, boolean async) {
@@ -82,6 +96,8 @@ public final class EnterpriseInstalledPackagesPreferenceControllerTest {
         setNumberOfEnterpriseInstalledPackages(0, true /* async */);
         mController.updateState(preference);
         assertThat(preference.isVisible()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES,
+                false);
 
         setNumberOfEnterpriseInstalledPackages(20, true /* async */);
         when(mContext.getResources().getQuantityString(
@@ -90,6 +106,8 @@ public final class EnterpriseInstalledPackagesPreferenceControllerTest {
         mController.updateState(preference);
         assertThat(preference.getSummary()).isEqualTo("minimum 20 apps");
         assertThat(preference.isVisible()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES,
+                true);
     }
 
     @Test
@@ -97,21 +115,30 @@ public final class EnterpriseInstalledPackagesPreferenceControllerTest {
         final EnterpriseInstalledPackagesPreferenceController controller
                 = new EnterpriseInstalledPackagesPreferenceController(mContext,
                         null /* lifecycle */, false /* async */);
+        controller.setAvailabilityObserver(mObserver);
 
         setNumberOfEnterpriseInstalledPackages(0, false /* async */);
         assertThat(controller.isAvailable()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(
+                KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES, false);
 
         setNumberOfEnterpriseInstalledPackages(20, false /* async */);
         assertThat(controller.isAvailable()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(
+                KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES, true);
     }
 
     @Test
     public void testIsAvailableAsync() {
         setNumberOfEnterpriseInstalledPackages(0, true /* async */);
         assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+                eq(KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES), anyBoolean());
 
         setNumberOfEnterpriseInstalledPackages(20, true /* async */);
         assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+                eq(KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES), anyBoolean());
     }
 
     @Test
@@ -123,6 +150,6 @@ public final class EnterpriseInstalledPackagesPreferenceControllerTest {
     @Test
     public void testGetPreferenceKey() {
         assertThat(mController.getPreferenceKey())
-                .isEqualTo("number_enterprise_installed_packages");
+                .isEqualTo(KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES);
     }
 }
index fe48347..101a45a 100644 (file)
@@ -23,6 +23,7 @@ import android.support.v7.preference.Preference;
 import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -34,6 +35,7 @@ import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -43,13 +45,15 @@ import static org.mockito.Mockito.when;
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public final class EnterprisePrivacyPreferenceControllerTest {
 
-    private final String MANAGED_GENERIC = "managed by organization";
-    private final String MANAGED_WITH_NAME = "managed by Foo, Inc.";
-    private final String MANAGING_ORGANIZATION = "Foo, Inc.";
+    private static final String MANAGED_GENERIC = "managed by organization";
+    private static final String MANAGED_WITH_NAME = "managed by Foo, Inc.";
+    private static final String MANAGING_ORGANIZATION = "Foo, Inc.";
+    private static final String KEY_ENTERPRISE_PRIVACY = "enterprise_privacy";
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
     private FakeFeatureFactory mFeatureFactory;
+    @Mock private PreferenceAvailabilityObserver mObserver;
 
     private EnterprisePrivacyPreferenceController mController;
 
@@ -59,6 +63,12 @@ public final class EnterprisePrivacyPreferenceControllerTest {
         FakeFeatureFactory.setupForTest(mContext);
         mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
         mController = new EnterprisePrivacyPreferenceController(mContext, null /* lifecycle */);
+        mController.setAvailabilityObserver(mObserver);
+    }
+
+    @Test
+    public void testGetAvailabilityObserver() {
+        assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
     }
 
     @Test
@@ -85,10 +95,11 @@ public final class EnterprisePrivacyPreferenceControllerTest {
     public void testIsAvailable() {
         when(mFeatureFactory.enterprisePrivacyFeatureProvider.hasDeviceOwner()).thenReturn(false);
         assertThat(mController.isAvailable()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_ENTERPRISE_PRIVACY, false);
 
         when(mFeatureFactory.enterprisePrivacyFeatureProvider.hasDeviceOwner()).thenReturn(true);
         assertThat(mController.isAvailable()).isTrue();
-
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_ENTERPRISE_PRIVACY, true);
     }
 
     @Test
@@ -99,6 +110,6 @@ public final class EnterprisePrivacyPreferenceControllerTest {
 
     @Test
     public void testGetPreferenceKey() {
-        assertThat(mController.getPreferenceKey()).isEqualTo("enterprise_privacy");
+        assertThat(mController.getPreferenceKey()).isEqualTo(KEY_ENTERPRISE_PRIVACY);
     }
 }
index d41be75..16fa5ba 100644 (file)
 
 package com.android.settings.enterprise;
 
+import android.app.Application;
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.core.DynamicAvailabilityPreferenceController;
 import com.android.settings.core.PreferenceController;
 import com.android.settings.testutils.FakeFeatureFactory;
 
@@ -31,10 +35,14 @@ import org.junit.runner.RunWith;
 import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowApplication;
+import org.xmlpull.v1.XmlPullParser;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.when;
@@ -46,6 +54,9 @@ import static org.mockito.Mockito.when;
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public final class EnterprisePrivacySettingsTest {
 
+    private final static String RESOURCES_NAMESPACE = "http://schemas.android.com/apk/res/android";
+    private final static String ATTR_KEY = "key";
+
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
     private FakeFeatureFactory mFeatureFactory;
@@ -101,23 +112,24 @@ public final class EnterprisePrivacySettingsTest {
     }
 
     @Test
-    public void getPreferenceControllers() {
+    public void getPreferenceControllers() throws Exception {
         final List<PreferenceController> controllers = mSettings.getPreferenceControllers(
                 ShadowApplication.getInstance().getApplicationContext());
         verifyPreferenceControllers(controllers);
     }
 
     @Test
-    public void getSearchIndexProviderPreferenceControllers() {
+    public void getSearchIndexProviderPreferenceControllers() throws Exception {
         final List<PreferenceController> controllers
                 = EnterprisePrivacySettings.SEARCH_INDEX_DATA_PROVIDER.getPreferenceControllers(
                         ShadowApplication.getInstance().getApplicationContext());
         verifyPreferenceControllers(controllers);
     }
 
-    private void verifyPreferenceControllers(List<PreferenceController> controllers) {
+    private void verifyPreferenceControllers(List<PreferenceController> controllers)
+            throws Exception {
         assertThat(controllers).isNotNull();
-        assertThat(controllers.size()).isEqualTo(15);
+        assertThat(controllers.size()).isEqualTo(16);
         int position = 0;
         assertThat(controllers.get(position++)).isInstanceOf(NetworkLogsPreferenceController.class);
         assertThat(controllers.get(position++)).isInstanceOf(BugReportsPreferenceController.class);
@@ -137,14 +149,71 @@ public final class EnterprisePrivacySettingsTest {
                 AlwaysOnVpnCurrentUserPreferenceController.class);
         assertThat(controllers.get(position++)).isInstanceOf(
                 AlwaysOnVpnManagedProfilePreferenceController.class);
+        assertThat(controllers.get(position++)).isInstanceOf(ImePreferenceController.class);
         assertThat(controllers.get(position++)).isInstanceOf(
                 GlobalHttpProxyPreferenceController.class);
         assertThat(controllers.get(position++)).isInstanceOf(
                 CaCertsPreferenceController.class);
+        final PreferenceController exposureChangesCategoryController = controllers.get(position);
+        final int exposureChangesCategoryControllerIndex = position;
+        assertThat(controllers.get(position++)).isInstanceOf(
+                ExposureChangesCategoryPreferenceController.class);
         assertThat(controllers.get(position++)).isInstanceOf(
                 FailedPasswordWipeCurrentUserPreferenceController.class);
         assertThat(controllers.get(position++)).isInstanceOf(
                 FailedPasswordWipeManagedProfilePreferenceController.class);
-        assertThat(controllers.get(position++)).isInstanceOf(ImePreferenceController.class);
+
+        // The "Changes made by your organization's admin" category is hidden when all Preferences
+        // inside it become unavailable. To do this correctly, the category's controller must:
+        // a) Observe the availability of all Preferences in the category and
+        // b) Be listed after those Preferences' controllers, so that availability is updated in
+        //    the correct order
+
+        // Find all Preferences in the category.
+        final XmlResourceParser parser = RuntimeEnvironment.application.getResources().getXml(
+                R.xml.enterprise_privacy_settings);
+        boolean done = false;
+        int type;
+        final Set<String> expectedObserved = new HashSet<>();
+        while (!done && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (type != XmlPullParser.START_TAG || !"exposure_changes_category".equals(
+                    parser.getAttributeValue(RESOURCES_NAMESPACE, ATTR_KEY))) {
+                continue;
+            }
+            int depth = 1;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (type == XmlPullParser.START_TAG) {
+                    final String key = parser.getAttributeValue(RESOURCES_NAMESPACE, ATTR_KEY);
+                    if (key != null) {
+                        expectedObserved.add(key);
+                    }
+                    depth++;
+                } else if (type == XmlPullParser.END_TAG) {
+                    depth--;
+                    if (depth == 0) {
+                        done = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        // Find all Preferences the category's controller is observing.
+        final Set<String> actualObserved = new HashSet<>();
+        int maxObservedIndex = -1;
+        for (int i = 0; i < controllers.size(); i++) {
+            final PreferenceController controller = controllers.get(i);
+            if (controller instanceof DynamicAvailabilityPreferenceController &&
+                    ((DynamicAvailabilityPreferenceController) controller).getAvailabilityObserver()
+                            == exposureChangesCategoryController) {
+                actualObserved.add(controller.getPreferenceKey());
+                maxObservedIndex = i;
+            }
+        }
+
+        // Verify that the category's controller is observing the Preferences inside it.
+        assertThat(actualObserved).isEqualTo(expectedObserved);
+        // Verify that the category's controller is listed after the Preferences' controllers.
+        assertThat(maxObservedIndex).isLessThan(exposureChangesCategoryControllerIndex);
     }
 }
index 34d9b24..5eb59e7 100644 (file)
@@ -29,6 +29,7 @@ import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.applications.EnterpriseDefaultApps;
 import com.android.settings.applications.UserAppInfo;
+import com.android.settings.core.PreferenceAvailabilityObserver;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -48,6 +49,7 @@ import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -57,11 +59,14 @@ import static org.mockito.Mockito.when;
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public final class EnterpriseSetDefaultAppsPreferenceControllerTest {
 
+    private static final String KEY_DEFAULT_APPS = "number_enterprise_set_default_apps";
+
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private UserManager mUm;
     private FakeFeatureFactory mFeatureFactory;
+    @Mock private PreferenceAvailabilityObserver mObserver;
 
     private EnterpriseSetDefaultAppsPreferenceController mController;
 
@@ -72,6 +77,12 @@ public final class EnterpriseSetDefaultAppsPreferenceControllerTest {
         mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
         mController = new EnterpriseSetDefaultAppsPreferenceController(mContext,
                 null /* lifecycle */);
+        mController.setAvailabilityObserver(mObserver);
+    }
+
+    @Test
+    public void testGetAvailabilityObserver() {
+        assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
     }
 
     private void setEnterpriseSetDefaultApps(Intent[] intents, int number) {
@@ -118,10 +129,12 @@ public final class EnterpriseSetDefaultAppsPreferenceControllerTest {
         when(mFeatureFactory.applicationFeatureProvider.findPersistentPreferredActivities(anyInt(),
                 anyObject())).thenReturn(new ArrayList<UserAppInfo>());
         assertThat(mController.isAvailable()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_DEFAULT_APPS, false);
 
         setEnterpriseSetDefaultApps(EnterpriseDefaultApps.BROWSER.getIntents(), 1);
         configureUsers(1);
         assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_DEFAULT_APPS, true);
     }
 
     @Test
@@ -132,8 +145,7 @@ public final class EnterpriseSetDefaultAppsPreferenceControllerTest {
 
     @Test
     public void testGetPreferenceKey() {
-        assertThat(mController.getPreferenceKey())
-                .isEqualTo("number_enterprise_set_default_apps");
+        assertThat(mController.getPreferenceKey()).isEqualTo(KEY_DEFAULT_APPS);
     }
 
     private static class MatchesIntents extends ArgumentMatcher<Intent[]> {
diff --git a/tests/robotests/src/com/android/settings/enterprise/ExposureChangesCategoryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/ExposureChangesCategoryPreferenceControllerTest.java
new file mode 100644 (file)
index 0000000..1c92ea5
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * 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.enterprise;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.core.DynamicAvailabilityPreferenceController;
+import com.android.settings.core.PreferenceAvailabilityObserver;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link ExposureChangesCategoryPreferenceController}.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public final class ExposureChangesCategoryPreferenceControllerTest {
+
+    private static final String KEY_1 = "key_1";
+    private static final String KEY_2 = "key_2";
+    private static final String KEY_EXPOSURE_CHANGES_CATEGORY = "exposure_changes_category";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    private List<DynamicAvailabilityPreferenceController> mControllers;
+    private ExposureChangesCategoryPreferenceController mController;
+    @Mock private PreferenceAvailabilityObserver mObserver;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mControllers = Arrays.asList(mock(DynamicAvailabilityPreferenceController.class),
+                mock(DynamicAvailabilityPreferenceController.class));
+        mController = new ExposureChangesCategoryPreferenceController(mContext,
+                null /* lifecycle */, mControllers, true /* controllingUi */);
+        mController.setAvailabilityObserver(mObserver);
+    }
+
+    @Test
+    public void testInitialization() {
+        verify(mControllers.get(0)).setAvailabilityObserver(mController);
+        verify(mControllers.get(1)).setAvailabilityObserver(mController);
+    }
+
+    @Test
+    public void testGetAvailabilityObserver() {
+        assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
+    }
+
+    @Test
+    public void testOnPreferenceAvailabilityUpdated() {
+        final Preference preference = new Preference(mContext, null, 0, 0);
+        preference.setVisible(true);
+
+        mController.updateState(preference);
+        assertThat(preference.isVisible()).isFalse();
+
+        mController.onPreferenceAvailabilityUpdated(KEY_1, true);
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, true);
+        assertThat(preference.isVisible()).isTrue();
+        reset(mObserver);
+
+        mController.onPreferenceAvailabilityUpdated(KEY_2, true);
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, true);
+        assertThat(preference.isVisible()).isTrue();
+        reset(mObserver);
+
+        mController.onPreferenceAvailabilityUpdated(KEY_1, false);
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, true);
+        assertThat(preference.isVisible()).isTrue();
+        reset(mObserver);
+
+        mController.onPreferenceAvailabilityUpdated(KEY_2, false);
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, false);
+        assertThat(preference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void testUpdateState() {
+        final Preference preference = new Preference(mContext, null, 0, 0);
+        preference.setVisible(false);
+
+        mController.onPreferenceAvailabilityUpdated(KEY_1, true);
+        mController.updateState(preference);
+        assertThat(preference.isVisible()).isTrue();
+    }
+
+    @Test
+    public void testIsAvailableForUi() {
+        assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+                eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+
+        mController.onPreferenceAvailabilityUpdated(KEY_1, true);
+        reset(mObserver);
+        assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+                eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+
+        mController.onPreferenceAvailabilityUpdated(KEY_1, false);
+        reset(mObserver);
+        assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+                eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+    }
+
+    @Test
+    public void testIsAvailableForSearch() {
+        final ExposureChangesCategoryPreferenceController controller
+                = new ExposureChangesCategoryPreferenceController(mContext, null /* lifecycle */,
+                        mControllers, false /* controllingUi */);
+        controller.setAvailabilityObserver(mObserver);
+        verify(mControllers.get(0)).setAvailabilityObserver(controller);
+        verify(mControllers.get(1)).setAvailabilityObserver(controller);
+
+        assertThat(controller.isAvailable()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, false);
+        reset(mObserver);
+
+        controller.onPreferenceAvailabilityUpdated(KEY_1, true);
+        verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+                eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+        assertThat(controller.isAvailable()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, true);
+        reset(mObserver);
+
+        controller.onPreferenceAvailabilityUpdated(KEY_2, true);
+        verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+                eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+        assertThat(controller.isAvailable()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, true);
+        reset(mObserver);
+
+        controller.onPreferenceAvailabilityUpdated(KEY_1, false);
+        verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+                eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+        assertThat(controller.isAvailable()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, true);
+        reset(mObserver);
+
+        controller.onPreferenceAvailabilityUpdated(KEY_2, false);
+        verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+                eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+        assertThat(controller.isAvailable()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, false);
+    }
+
+    @Test
+    public void testHandlePreferenceTreeClick() {
+        assertThat(mController.handlePreferenceTreeClick(new Preference(mContext, null, 0, 0)))
+                .isFalse();
+    }
+
+    @Test
+    public void testGetPreferenceKey() {
+        assertThat(mController.getPreferenceKey()).isEqualTo(KEY_EXPOSURE_CHANGES_CATEGORY);
+    }
+}
index c14b71e..cbc220f 100644 (file)
@@ -21,6 +21,7 @@ import android.content.res.Resources;
 import android.support.v7.preference.Preference;
 
 import com.android.settings.R;
+import com.android.settings.core.PreferenceAvailabilityObserver;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -30,6 +31,7 @@ import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -42,6 +44,7 @@ public abstract class FailedPasswordWipePreferenceControllerTestBase {
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     protected Context mContext;
     protected FakeFeatureFactory mFeatureFactory;
+    @Mock private PreferenceAvailabilityObserver mObserver;
 
     protected FailedPasswordWipePreferenceControllerBase mController;
 
@@ -56,6 +59,12 @@ public abstract class FailedPasswordWipePreferenceControllerTestBase {
         mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
     }
 
+    @Test
+    public void testGetAvailabilityObserver() {
+        mController.setAvailabilityObserver(mObserver);
+        assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
+    }
+
     public abstract void setMaximumFailedPasswordsBeforeWipe(int maximum);
 
     @Test
@@ -72,11 +81,15 @@ public abstract class FailedPasswordWipePreferenceControllerTestBase {
 
     @Test
     public void testIsAvailable() {
+        mController.setAvailabilityObserver(mObserver);
+
         setMaximumFailedPasswordsBeforeWipe(0);
         assertThat(mController.isAvailable()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(mKey, false);
 
         setMaximumFailedPasswordsBeforeWipe(10);
         assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(mKey, true);
     }
 
     @Test
index a0bc9ee..016d970 100644 (file)
@@ -21,6 +21,7 @@ import android.support.v7.preference.Preference;
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -32,6 +33,7 @@ import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -40,9 +42,13 @@ import static org.mockito.Mockito.when;
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public final class GlobalHttpProxyPreferenceControllerTest {
+
+    private static final String KEY_GLOBAL_HTTP_PROXY = "global_http_proxy";
+
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
     private FakeFeatureFactory mFeatureFactory;
+    @Mock private PreferenceAvailabilityObserver mObserver;
 
     private GlobalHttpProxyPreferenceController mController;
 
@@ -52,6 +58,12 @@ public final class GlobalHttpProxyPreferenceControllerTest {
         FakeFeatureFactory.setupForTest(mContext);
         mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
         mController = new GlobalHttpProxyPreferenceController(mContext, null /* lifecycle */);
+        mController.setAvailabilityObserver(mObserver);
+    }
+
+    @Test
+    public void testGetAvailabilityObserver() {
+        assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
     }
 
     @Test
@@ -59,10 +71,12 @@ public final class GlobalHttpProxyPreferenceControllerTest {
         when(mFeatureFactory.enterprisePrivacyFeatureProvider.isGlobalHttpProxySet())
                 .thenReturn(false);
         assertThat(mController.isAvailable()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_GLOBAL_HTTP_PROXY, false);
 
         when(mFeatureFactory.enterprisePrivacyFeatureProvider.isGlobalHttpProxySet())
                 .thenReturn(true);
         assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_GLOBAL_HTTP_PROXY, true);
     }
 
     @Test
@@ -73,6 +87,6 @@ public final class GlobalHttpProxyPreferenceControllerTest {
 
     @Test
     public void testGetPreferenceKey() {
-        assertThat(mController.getPreferenceKey()).isEqualTo("global_http_proxy");
+        assertThat(mController.getPreferenceKey()).isEqualTo(KEY_GLOBAL_HTTP_PROXY);
     }
 }
index 05d0535..3304b44 100644 (file)
@@ -23,6 +23,7 @@ import android.support.v7.preference.Preference;
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
 import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
@@ -34,6 +35,7 @@ import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -43,12 +45,14 @@ import static org.mockito.Mockito.when;
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public final class ImePreferenceControllerTest {
 
-    private final String DEFAULT_IME_LABEL = "Test IME";
-    private final String DEFAULT_IME_TEXT = "Set to Test IME";
+    private static final String DEFAULT_IME_LABEL = "Test IME";
+    private static final String DEFAULT_IME_TEXT = "Set to Test IME";
+    private static final String KEY_INPUT_METHOD = "input_method";
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
     private FakeFeatureFactory mFeatureFactory;
+    @Mock private PreferenceAvailabilityObserver mObserver;
 
     private ImePreferenceController mController;
 
@@ -60,6 +64,12 @@ public final class ImePreferenceControllerTest {
         mController = new ImePreferenceController(mContext, null /* lifecycle */);
         when(mContext.getResources().getString(R.string.enterprise_privacy_input_method_name,
                 DEFAULT_IME_LABEL)).thenReturn(DEFAULT_IME_TEXT);
+        mController.setAvailabilityObserver(mObserver);
+    }
+
+    @Test
+    public void testGetAvailabilityObserver() {
+        assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
     }
 
     @Test
@@ -77,10 +87,12 @@ public final class ImePreferenceControllerTest {
         when(mFeatureFactory.enterprisePrivacyFeatureProvider.getImeLabelIfOwnerSet())
             .thenReturn(null);
         assertThat(mController.isAvailable()).isFalse();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_INPUT_METHOD, false);
 
         when(mFeatureFactory.enterprisePrivacyFeatureProvider.getImeLabelIfOwnerSet())
             .thenReturn(DEFAULT_IME_LABEL);
         assertThat(mController.isAvailable()).isTrue();
+        verify(mObserver).onPreferenceAvailabilityUpdated(KEY_INPUT_METHOD, true);
     }
 
     @Test
@@ -91,6 +103,6 @@ public final class ImePreferenceControllerTest {
 
     @Test
     public void testGetPreferenceKey() {
-        assertThat(mController.getPreferenceKey()).isEqualTo("input_method");
+        assertThat(mController.getPreferenceKey()).isEqualTo(KEY_INPUT_METHOD);
     }
 }
index 0298195..41825a5 100644 (file)
@@ -75,9 +75,13 @@ import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
+import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -89,9 +93,6 @@ public class WifiDetailPreferenceControllerTest {
     private static final String MAC_ADDRESS = WifiInfo.DEFAULT_MAC_ADDRESS;
     private static final String SECURITY = "None";
 
-    private InetAddress mIpv4Address;
-    private Inet6Address mIpv6Address;
-
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private PreferenceScreen mockScreen;
 
@@ -120,35 +121,76 @@ public class WifiDetailPreferenceControllerTest {
     @Mock private WifiDetailPreference mockSubnetPref;
     @Mock private WifiDetailPreference mockDnsPref;
     @Mock private Button mockForgetButton;
-    @Mock private PreferenceCategory mockIpv6AddressCategory;
+    @Mock private PreferenceCategory mockIpv6Category;
+    @Mock private WifiDetailPreference mockIpv6AddressesPref;
 
     @Captor private ArgumentCaptor<NetworkCallback> mCallbackCaptor;
     @Captor private ArgumentCaptor<View.OnClickListener> mForgetClickListener;
+    @Captor private ArgumentCaptor<Preference> mIpv6AddressCaptor;
 
     private Context mContext = RuntimeEnvironment.application;
     private Lifecycle mLifecycle;
     private LinkProperties mLinkProperties;
     private WifiDetailPreferenceController mController;
 
+    // This class exists so that these values can be made static final. They can't be static final
+    // members of the test class, because any attempt to call IpPrefix or RouteInfo constructors
+    // during static initialization of the test class results in NoSuchMethorError being thrown
+    // when the test is run.
+    private static class Constants {
+        static final int IPV4_PREFIXLEN = 25;
+        static final LinkAddress IPV4_ADDR;
+        static final Inet4Address IPV4_GATEWAY;
+        static final RouteInfo IPV4_DEFAULT;
+        static final RouteInfo IPV4_SUBNET;
+        static final LinkAddress IPV6_LINKLOCAL;
+        static final LinkAddress IPV6_GLOBAL1;
+        static final LinkAddress IPV6_GLOBAL2;
+        static final InetAddress IPV4_DNS1;
+        static final InetAddress IPV4_DNS2;
+        static final InetAddress IPV6_DNS;
+
+        private static LinkAddress ipv6LinkAddress(String addr) throws UnknownHostException {
+            return new LinkAddress(InetAddress.getByName(addr), 64);
+        }
+
+        private static LinkAddress ipv4LinkAddress(String addr, int prefixlen)
+                throws UnknownHostException {
+            return new LinkAddress(InetAddress.getByName(addr), prefixlen);
+        }
+
+        static {
+            try {
+                // We create our test constants in these roundabout ways because the robolectric
+                // shadows don't contain NetworkUtils.parseNumericAddress and other utility methods,
+                // so the easy ways to do things fail with NoSuchMethodError.
+                IPV4_ADDR = ipv4LinkAddress("192.0.2.2", IPV4_PREFIXLEN);
+                IPV4_GATEWAY = (Inet4Address) InetAddress.getByName("192.0.2.127");
+
+                final Inet4Address any4 = (Inet4Address) InetAddress.getByName("0.0.0.0");
+                IpPrefix subnet = new IpPrefix(IPV4_ADDR.getAddress(), IPV4_PREFIXLEN);
+                IPV4_SUBNET = new RouteInfo(subnet, any4);
+                IPV4_DEFAULT = new RouteInfo(new IpPrefix(any4, 0), IPV4_GATEWAY);
+
+                IPV6_LINKLOCAL = ipv6LinkAddress("fe80::211:25ff:fef8:7cb2%1");
+                IPV6_GLOBAL1 = ipv6LinkAddress("2001:db8:1::211:25ff:fef8:7cb2");
+                IPV6_GLOBAL2 = ipv6LinkAddress("2001:db8:1::3dfe:8902:f98f:739d");
+
+                IPV4_DNS1 = InetAddress.getByName("8.8.8.8");
+                IPV4_DNS2 = InetAddress.getByName("8.8.4.4");
+                IPV6_DNS = InetAddress.getByName("2001:4860:4860::64");
+            } catch (UnknownHostException e) {
+                throw new RuntimeException("Invalid hardcoded IP addresss: " + e);
+            }
+        }
+    }
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
         mLifecycle = new Lifecycle();
 
-        try {
-            mIpv4Address = InetAddress.getByAddress(
-                    new byte[] { (byte) 255, (byte) 255, (byte) 255, (byte) 255 });
-            mIpv6Address = Inet6Address.getByAddress(
-                    "123", /* host */
-                    new byte[] {
-                            (byte) 0xFE, (byte) 0x80, 0, 0, 0, 0, 0, 0, 0x02, 0x11, 0x25,
-                            (byte) 0xFF, (byte) 0xFE, (byte) 0xF8, (byte) 0x7C, (byte) 0xB2},
-                    1  /*scope id */);
-        } catch (UnknownHostException e) {
-            throw new RuntimeException(e);
-        }
-
         when(mockAccessPoint.getConfig()).thenReturn(mockWifiConfig);
         when(mockAccessPoint.getLevel()).thenReturn(LEVEL);
         when(mockAccessPoint.getSecurityString(false)).thenReturn(SECURITY);
@@ -217,8 +259,10 @@ public class WifiDetailPreferenceControllerTest {
                 .thenReturn(mockSubnetPref);
         when(mockScreen.findPreference(WifiDetailPreferenceController.KEY_DNS_PREF))
                 .thenReturn(mockDnsPref);
-        when(mockScreen.findPreference(WifiDetailPreferenceController.KEY_IPV6_ADDRESS_CATEGORY))
-                .thenReturn(mockIpv6AddressCategory);
+        when(mockScreen.findPreference(WifiDetailPreferenceController.KEY_IPV6_CATEGORY))
+                .thenReturn(mockIpv6Category);
+        when(mockScreen.findPreference(WifiDetailPreferenceController.KEY_IPV6_ADDRESSES_PREF))
+                .thenReturn(mockIpv6AddressesPref);
     }
 
     @Test
@@ -330,26 +374,23 @@ public class WifiDetailPreferenceControllerTest {
 
     @Test
     public void ipAddressPref_shouldHaveDetailTextSet() {
-        LinkAddress ipv4Address = new LinkAddress(mIpv4Address, 32);
-
-        mLinkProperties.addLinkAddress(ipv4Address);
+        mLinkProperties.addLinkAddress(Constants.IPV4_ADDR);
 
         mController.displayPreference(mockScreen);
 
-        verify(mockIpAddressPref).setDetailText(mIpv4Address.getHostAddress());
+        verify(mockIpAddressPref).setDetailText(Constants.IPV4_ADDR.getAddress().getHostAddress());
     }
 
     @Test
     public void gatewayAndSubnet_shouldHaveDetailTextSet() {
-        int prefixLength = 24;
-        IpPrefix subnet = new IpPrefix(mIpv4Address, prefixLength);
-        InetAddress gateway = mIpv4Address;
-        mLinkProperties.addRoute(new RouteInfo(subnet, gateway));
+        mLinkProperties.addLinkAddress(Constants.IPV4_ADDR);
+        mLinkProperties.addRoute(Constants.IPV4_DEFAULT);
+        mLinkProperties.addRoute(Constants.IPV4_SUBNET);
 
         mController.displayPreference(mockScreen);
 
-        verify(mockSubnetPref).setDetailText("255.255.255.0");
-        verify(mockGatewayPref).setDetailText(mIpv4Address.getHostAddress());
+        verify(mockSubnetPref).setDetailText("255.255.255.128");
+        verify(mockGatewayPref).setDetailText("192.0.2.127");
     }
 
     @Test
@@ -376,23 +417,96 @@ public class WifiDetailPreferenceControllerTest {
     @Test
     public void noLinkProperties_allIpDetailsHidden() {
         when(mockConnectivityManager.getLinkProperties(mockNetwork)).thenReturn(null);
-        reset(mockIpv6AddressCategory, mockIpAddressPref, mockSubnetPref, mockGatewayPref,
+        reset(mockIpv6Category, mockIpAddressPref, mockSubnetPref, mockGatewayPref,
                 mockDnsPref);
 
         mController.displayPreference(mockScreen);
 
-        verify(mockIpv6AddressCategory).setVisible(false);
+        verify(mockIpv6Category).setVisible(false);
         verify(mockIpAddressPref).setVisible(false);
         verify(mockSubnetPref).setVisible(false);
         verify(mockGatewayPref).setVisible(false);
         verify(mockDnsPref).setVisible(false);
-        verify(mockIpv6AddressCategory, never()).setVisible(true);
+        verify(mockIpv6Category, never()).setVisible(true);
         verify(mockIpAddressPref, never()).setVisible(true);
         verify(mockSubnetPref, never()).setVisible(true);
         verify(mockGatewayPref, never()).setVisible(true);
         verify(mockDnsPref, never()).setVisible(true);
     }
 
+    // Convenience method to convert a LinkAddress to a string without a prefix length.
+    private String asString(LinkAddress l) {
+        return l.getAddress().getHostAddress();
+    }
+
+    // Pretend that the NetworkCallback was triggered with a new copy of lp. We need to create a
+    // new copy because the code only updates if !mLinkProperties.equals(lp).
+    private void updateLinkProperties(LinkProperties lp) {
+        mCallbackCaptor.getValue().onLinkPropertiesChanged(mockNetwork, new LinkProperties(lp));
+    }
+
+    private void verifyDisplayedIpv6Addresses(InOrder inOrder, LinkAddress... addresses) {
+        String text = Arrays.stream(addresses)
+                .map(address -> asString(address))
+                .collect(Collectors.joining("\n"));
+        inOrder.verify(mockIpv6AddressesPref).setSummary(text);
+    }
+
+    @Test
+    public void onLinkPropertiesChanged_updatesFields() {
+        mController.displayPreference(mockScreen);
+        mController.onResume();
+
+        InOrder inOrder = inOrder(mockIpAddressPref, mockGatewayPref, mockSubnetPref,
+                mockDnsPref, mockIpv6Category, mockIpv6AddressesPref);
+
+        LinkProperties lp = new LinkProperties();
+
+        lp.addLinkAddress(Constants.IPV6_LINKLOCAL);
+        updateLinkProperties(lp);
+        verifyDisplayedIpv6Addresses(inOrder, Constants.IPV6_LINKLOCAL);
+        inOrder.verify(mockIpv6Category).setVisible(true);
+
+        lp.addRoute(Constants.IPV4_DEFAULT);
+        updateLinkProperties(lp);
+        inOrder.verify(mockGatewayPref).setDetailText(Constants.IPV4_GATEWAY.getHostAddress());
+        inOrder.verify(mockGatewayPref).setVisible(true);
+
+        lp.addLinkAddress(Constants.IPV4_ADDR);
+        lp.addRoute(Constants.IPV4_SUBNET);
+        updateLinkProperties(lp);
+        inOrder.verify(mockIpAddressPref).setDetailText(asString(Constants.IPV4_ADDR));
+        inOrder.verify(mockIpAddressPref).setVisible(true);
+        inOrder.verify(mockSubnetPref).setDetailText("255.255.255.128");
+        inOrder.verify(mockSubnetPref).setVisible(true);
+
+        lp.addLinkAddress(Constants.IPV6_GLOBAL1);
+        lp.addLinkAddress(Constants.IPV6_GLOBAL2);
+        updateLinkProperties(lp);
+        verifyDisplayedIpv6Addresses(inOrder,
+                Constants.IPV6_LINKLOCAL,
+                Constants.IPV6_GLOBAL1,
+                Constants.IPV6_GLOBAL2);
+
+        lp.removeLinkAddress(Constants.IPV6_GLOBAL1);
+        updateLinkProperties(lp);
+        verifyDisplayedIpv6Addresses(inOrder,
+                Constants.IPV6_LINKLOCAL,
+                Constants.IPV6_GLOBAL2);
+
+        lp.addDnsServer(Constants.IPV6_DNS);
+        updateLinkProperties(lp);
+        inOrder.verify(mockDnsPref, never()).setVisible(true);
+
+        lp.addDnsServer(Constants.IPV4_DNS1);
+        lp.addDnsServer(Constants.IPV4_DNS2);
+        updateLinkProperties(lp);
+        inOrder.verify(mockDnsPref).setDetailText(
+                Constants.IPV4_DNS1.getHostAddress() + "," +
+                Constants.IPV4_DNS2.getHostAddress());
+        inOrder.verify(mockDnsPref).setVisible(true);
+    }
+
     @Test
     public void canForgetNetwork_noNetwork() {
         when(mockAccessPoint.getConfig()).thenReturn(null);
@@ -496,28 +610,29 @@ public class WifiDetailPreferenceControllerTest {
 
     @Test
     public void ipv6AddressPref_shouldHaveHostAddressTextSet() {
-        LinkAddress ipv6Address = new LinkAddress(mIpv6Address, 128);
-
-        mLinkProperties.addLinkAddress(ipv6Address);
+        mLinkProperties.addLinkAddress(Constants.IPV6_LINKLOCAL);
+        mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL1);
+        mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL2);
 
         mController.displayPreference(mockScreen);
 
-        ArgumentCaptor<Preference> preferenceCaptor = ArgumentCaptor.forClass(Preference.class);
-        verify(mockIpv6AddressCategory).addPreference(preferenceCaptor.capture());
-        assertThat(preferenceCaptor.getValue().getTitle()).isEqualTo(mIpv6Address.getHostAddress());
+        List <Preference> addrs = mIpv6AddressCaptor.getAllValues();
+
+        String expectedAddresses = String.join("\n",
+                asString(Constants.IPV6_LINKLOCAL),
+                asString(Constants.IPV6_GLOBAL1),
+                asString(Constants.IPV6_GLOBAL2));
+
+        verify(mockIpv6AddressesPref).setSummary(expectedAddresses);
     }
 
     @Test
     public void ipv6AddressPref_shouldNotBeSelectable() {
-        LinkAddress ipv6Address = new LinkAddress(mIpv6Address, 128);
-
-        mLinkProperties.addLinkAddress(ipv6Address);
+        mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL2);
 
         mController.displayPreference(mockScreen);
 
-        ArgumentCaptor<Preference> preferenceCaptor = ArgumentCaptor.forClass(Preference.class);
-        verify(mockIpv6AddressCategory).addPreference(preferenceCaptor.capture());
-        assertThat(preferenceCaptor.getValue().isSelectable()).isFalse();
+        assertThat(mockIpv6AddressesPref.isSelectable()).isFalse();
     }
 
     @Test