Merge "Dynamically update injected security preferences."
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index 46e7fe5..b092857 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -62,10 +62,13 @@
 import com.android.settings.search.Index;
 import com.android.settings.search.Indexable;
 import com.android.settings.search.SearchIndexableRaw;
+import com.android.settings.security.SecurityFeatureProvider;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedPreference;
 import com.android.settingslib.RestrictedSwitchPreference;
 import com.android.settingslib.drawer.CategoryKey;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -130,6 +133,7 @@
 
     private DashboardFeatureProvider mDashboardFeatureProvider;
     private DevicePolicyManager mDPM;
+    private SecurityFeatureProvider mSecurityFeatureProvider;
     private SubscriptionManager mSubscriptionManager;
     private UserManager mUm;
 
@@ -183,6 +187,8 @@
         mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
                 .getDashboardFeatureProvider(activity);
 
+        mSecurityFeatureProvider = FeatureFactory.getFactory(activity).getSecurityFeatureProvider();
+
         if (savedInstanceState != null
                 && savedInstanceState.containsKey(TRUST_AGENT_CLICK_INTENT)) {
             mTrustAgentClickIntent = savedInstanceState.getParcelable(TRUST_AGENT_CLICK_INTENT);
@@ -416,6 +422,11 @@
             }
         }
 
+        // Update preference data with tile data. Security feature provider only updates the data
+        // if it actually needs to be changed.
+        mSecurityFeatureProvider.updatePreferences(getActivity(), root,
+                mDashboardFeatureProvider.getTilesForCategory(CategoryKey.CATEGORY_SECURITY));
+
         for (int i = 0; i < SWITCH_PREFERENCE_KEYS.length; i++) {
             final Preference pref = findPreference(SWITCH_PREFERENCE_KEYS[i]);
             if (pref != null) pref.setOnPreferenceChangeListener(this);
diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java
index 0b8ee8e..8b6c3b1 100644
--- a/src/com/android/settings/overlay/FeatureFactory.java
+++ b/src/com/android/settings/overlay/FeatureFactory.java
@@ -27,6 +27,7 @@
 import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProvider;
+import com.android.settings.security.SecurityFeatureProvider;
 import com.android.settings.search2.SearchFeatureProvider;
 
 /**
@@ -85,6 +86,8 @@
 
     public abstract SurveyFeatureProvider getSurveyFeatureProvider(Context context);
 
+    public abstract SecurityFeatureProvider getSecurityFeatureProvider();
+
     public static final class FactoryNotFoundException extends RuntimeException {
         public FactoryNotFoundException(Throwable throwable) {
             super("Unable to create factory. Did you misconfigure Proguard?", throwable);
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java
index c2d5d79..eb5d065 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.java
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java
@@ -33,6 +33,8 @@
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProviderImpl;
+import com.android.settings.security.SecurityFeatureProvider;
+import com.android.settings.security.SecurityFeatureProviderImpl;
 import com.android.settings.search2.SearchFeatureProvider;
 import com.android.settings.search2.SearchFeatureProviderImpl;
 
@@ -48,6 +50,7 @@
     private LocaleFeatureProvider mLocaleFeatureProvider;
     private EnterprisePrivacyFeatureProvider mEnterprisePrivacyFeatureProvider;
     private SearchFeatureProvider mSearchFeatureProvider;
+    private SecurityFeatureProvider mSecurityFeatureProvider;
 
     @Override
     public SupportFeatureProvider getSupportFeatureProvider(Context context) {
@@ -115,4 +118,12 @@
     public SurveyFeatureProvider getSurveyFeatureProvider(Context context) {
         return null;
     }
+
+    @Override
+    public SecurityFeatureProvider getSecurityFeatureProvider() {
+        if (mSecurityFeatureProvider == null) {
+            mSecurityFeatureProvider = new SecurityFeatureProviderImpl();
+        }
+        return mSecurityFeatureProvider;
+    }
 }
diff --git a/src/com/android/settings/security/SecurityFeatureProvider.java b/src/com/android/settings/security/SecurityFeatureProvider.java
new file mode 100644
index 0000000..5cf6fc9
--- /dev/null
+++ b/src/com/android/settings/security/SecurityFeatureProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import android.content.Context;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settingslib.drawer.DashboardCategory;
+
+
+/** FeatureProvider for security. */
+public interface SecurityFeatureProvider {
+
+    /** Update preferences with data from associated tiles. */
+    void updatePreferences(Context context, PreferenceScreen preferenceScreen,
+            DashboardCategory dashboardCategory);
+}
diff --git a/src/com/android/settings/security/SecurityFeatureProviderImpl.java b/src/com/android/settings/security/SecurityFeatureProviderImpl.java
new file mode 100644
index 0000000..91659fd
--- /dev/null
+++ b/src/com/android/settings/security/SecurityFeatureProviderImpl.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IContentProvider;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import com.android.settingslib.drawer.DashboardCategory;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.drawer.TileUtils;
+
+import java.util.Map;
+
+/** Implementation for {@code SecurityFeatureProvider}. */
+public class SecurityFeatureProviderImpl implements SecurityFeatureProvider {
+
+    /** Update preferences with data from associated tiles. */
+    public void updatePreferences(Context context, PreferenceScreen preferenceScreen,
+            DashboardCategory dashboardCategory) {
+        if (preferenceScreen == null) {
+            return;
+        }
+        int tilesCount = (dashboardCategory != null) ? dashboardCategory.getTilesCount() : 0;
+        if (tilesCount == 0) {
+            return;
+        }
+        Map<String, IContentProvider> providerMap = new ArrayMap<>();
+        for (int i = 0; i < tilesCount; i++) {
+            Tile tile = dashboardCategory.getTile(i);
+            // If the tile does not have a key or appropriate meta data, skip it.
+            if (TextUtils.isEmpty(tile.key) || (tile.metaData == null)) {
+                continue;
+            }
+            Preference matchingPref = preferenceScreen.findPreference(tile.key);
+            // If the tile does not have a matching preference, skip it.
+            if (matchingPref == null) {
+                continue;
+            }
+            // Check if the tile has content providers for dynamically updatable content.
+            String iconUri = tile.metaData.getString(TileUtils.META_DATA_PREFERENCE_ICON_URI, null);
+            String summaryUri =
+                    tile.metaData.getString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, null);
+            if (!TextUtils.isEmpty(iconUri)) {
+                int icon = TileUtils.getIconFromUri(context, iconUri, providerMap);
+                boolean updateIcon = true;
+                String packageName = null;
+                // Dynamic icon has to come from the same package that the preference launches.
+                if (tile.intent != null) {
+                        Intent intent = tile.intent;
+                        if (!TextUtils.isEmpty(intent.getPackage())) {
+                            packageName = intent.getPackage();
+                        } else if (intent.getComponent() != null) {
+                            packageName = intent.getComponent().getPackageName();
+                        }
+                }
+                if (TextUtils.isEmpty(packageName)) {
+                    updateIcon = false;
+                } else {
+                    if (tile.icon == null) {
+                        // If the tile does not have an icon already, only update if the suggested
+                        // icon is non-zero.
+                        updateIcon = (icon != 0);
+                    } else {
+                        // If the existing icon has the same resource package and resource id, the
+                        // icon does not need to be updated.
+                        updateIcon = !(packageName.equals(tile.icon.getResPackage())
+                                && (icon == tile.icon.getResId()));
+                    }
+                }
+                if (updateIcon) {
+                    try {
+                        matchingPref.setIcon(context.getPackageManager()
+                                .getResourcesForApplication(packageName)
+                                        .getDrawable(icon, context.getTheme()));
+                    } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
+                        // Intentionally ignored. If icon resources cannot be found, do not update.
+                    }
+                }
+            }
+            if (!TextUtils.isEmpty(summaryUri)) {
+                String summary = TileUtils.getTextFromUri(context, summaryUri, providerMap,
+                        TileUtils.META_DATA_PREFERENCE_SUMMARY);
+                // Only update the summary if it has actually changed.
+                if (summary == null) {
+                    if (matchingPref.getSummary() != null) {
+                        matchingPref.setSummary(summary);
+                    }
+                } else if (!summary.equals(matchingPref.getSummary())) {
+                    matchingPref.setSummary(summary);
+                }
+            }
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/security/SecurityFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/security/SecurityFeatureProviderImplTest.java
new file mode 100644
index 0000000..7df9af7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/security/SecurityFeatureProviderImplTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
+import android.os.Bundle;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.drawer.TileUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SecurityFeatureProviderImplTest {
+
+    private static final String MOCK_KEY = "key";
+    private static final String MOCK_SUMMARY = "summary";
+    private static final String URI_GET_SUMMARY = "content://package/text/summary";
+    private static final String URI_GET_ICON = "content://package/icon/my_icon";
+
+    private static final Drawable MOCK_DRAWABLE = new PaintDrawable(Color.BLUE);
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private Resources mResources;
+
+    private SecurityFeatureProviderImpl mImpl;
+
+    @Implements(com.android.settingslib.drawer.TileUtils.class)
+    public static class TileUtilsMock {
+        @Implementation
+        public static int getIconFromUri(Context context, String uriString,
+                Map<String, IContentProvider> providerMap) {
+            return 161803;
+        }
+
+        @Implementation
+        public static String getTextFromUri(Context context, String uriString,
+                Map<String, IContentProvider> providerMap, String key) {
+            return MOCK_SUMMARY;
+        }
+    }
+
+    @Before
+    public void setUp() throws PackageManager.NameNotFoundException {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        mImpl = new SecurityFeatureProviderImpl();
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.getResourcesForApplication(anyString())).thenReturn(mResources);
+        when(mResources.getDrawable(anyInt(), any())).thenReturn(MOCK_DRAWABLE);
+    }
+
+    @Test
+    public void updateTilesData_shouldNotProcessEmptyScreenOrTiles() {
+        mImpl.updatePreferences(mContext, null, null);
+        mImpl.updatePreferences(mContext, new PreferenceScreen(mContext, null), null);
+        verifyNoMoreInteractions(mPackageManager);
+    }
+
+    @Test
+    public void updateTilesData_shouldNotProcessNonMatchingPreference() {
+        DashboardCategory dashboardCategory = new DashboardCategory();
+        dashboardCategory.addTile(new Tile());
+        mImpl.updatePreferences(mContext, getPreferenceScreen(), dashboardCategory);
+        verifyNoMoreInteractions(mPackageManager);
+    }
+
+    @Test
+    public void updateTilesData_shouldNotProcessMatchingPreferenceWithNoData() {
+        mImpl.updatePreferences(mContext, getPreferenceScreen(), getDashboardCategory());
+        verifyNoMoreInteractions(mPackageManager);
+    }
+
+    @Test
+    @Config(shadows = {
+            TileUtilsMock.class,
+    })
+    public void updateTilesData_shouldUpdateMatchingPreference() {
+        Bundle bundle = new Bundle();
+        bundle.putString(TileUtils.META_DATA_PREFERENCE_ICON_URI, URI_GET_ICON);
+        bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, URI_GET_SUMMARY);
+
+        PreferenceScreen screen = getPreferenceScreen();
+        DashboardCategory dashboardCategory = getDashboardCategory();
+        dashboardCategory.getTile(0).intent = new Intent().setPackage("package");
+        dashboardCategory.getTile(0).metaData = bundle;
+
+        mImpl.updatePreferences(mContext, getPreferenceScreen(), dashboardCategory);
+        assertThat(screen.findPreference(MOCK_KEY).getIcon()).isEqualTo(MOCK_DRAWABLE);
+        assertThat(screen.findPreference(MOCK_KEY).getSummary()).isEqualTo(MOCK_SUMMARY);
+    }
+
+    @Test
+    @Config(shadows = {
+            TileUtilsMock.class,
+    })
+    public void updateTilesData_shouldNotUpdateAlreadyUpdatedPreference() {
+        Bundle bundle = new Bundle();
+        bundle.putString(TileUtils.META_DATA_PREFERENCE_ICON_URI, URI_GET_ICON);
+        bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, URI_GET_SUMMARY);
+
+        PreferenceScreen screen = getPreferenceScreen();
+        screen.findPreference(MOCK_KEY).setIcon(MOCK_DRAWABLE);
+        screen.findPreference(MOCK_KEY).setSummary(MOCK_SUMMARY);
+
+        DashboardCategory dashboardCategory = getDashboardCategory();
+        dashboardCategory.getTile(0).intent = new Intent().setPackage("package");
+        dashboardCategory.getTile(0).metaData = bundle;
+
+        mImpl.updatePreferences(mContext, screen, dashboardCategory);
+        verify(screen.findPreference(MOCK_KEY), times(0)).setIcon(any());
+        verify(screen.findPreference(MOCK_KEY), times(0)).setSummary(anyString());
+        assertThat(screen.findPreference(MOCK_KEY).getIcon()).isEqualTo(MOCK_DRAWABLE);
+        assertThat(screen.findPreference(MOCK_KEY).getSummary()).isEqualTo(MOCK_SUMMARY);
+    }
+
+    private PreferenceScreen getPreferenceScreen() {
+        PreferenceScreen screen = new PreferenceScreen(mContext, null);
+        Preference preference = spy(new Preference(mContext));
+        preference.setKey(MOCK_KEY);
+        screen.addPreference(preference);
+        return screen;
+    }
+
+    private static DashboardCategory getDashboardCategory() {
+        DashboardCategory dashboardCategory = new DashboardCategory();
+        Tile tile = new Tile();
+        tile.key = MOCK_KEY;
+        dashboardCategory.addTile(tile);
+        return dashboardCategory;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
index 1625f35..bc0894c 100644
--- a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -25,6 +25,7 @@
 import com.android.settings.localepicker.LocaleFeatureProvider;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.overlay.SupportFeatureProvider;
+import com.android.settings.security.SecurityFeatureProvider;
 import com.android.settings.search2.SearchFeatureProvider;
 import com.android.settings.overlay.SurveyFeatureProvider;
 
@@ -47,6 +48,7 @@
     public final EnterprisePrivacyFeatureProvider enterprisePrivacyFeatureProvider;
     public final SearchFeatureProvider searchFeatureProvider;
     public final SurveyFeatureProvider surveyFeatureProvider;
+    public final SecurityFeatureProvider securityFeatureProvider;
 
     /**
      * Call this in {@code @Before} method of the test class to use fake factory.
@@ -78,6 +80,7 @@
         enterprisePrivacyFeatureProvider = mock(EnterprisePrivacyFeatureProvider.class);
         searchFeatureProvider = mock(SearchFeatureProvider.class);
         surveyFeatureProvider = mock(SurveyFeatureProvider.class);
+        securityFeatureProvider = mock(SecurityFeatureProvider.class);
     }
 
     @Override
@@ -124,4 +127,9 @@
     public SurveyFeatureProvider getSurveyFeatureProvider(Context context) {
         return surveyFeatureProvider;
     }
+
+    @Override
+    public SecurityFeatureProvider getSecurityFeatureProvider() {
+        return securityFeatureProvider;
+    }
 }