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