From 3bf4b9365e1cc97be1087523e312c0a28100a372 Mon Sep 17 00:00:00 2001 From: yuemingw Date: Thu, 1 Feb 2018 13:57:52 +0000 Subject: [PATCH] Block location accuracy when DISALLOW_CONFIG_LOCATION is set. Bug: 72633181 Test: make ROBOTEST_FILTER=InjectedSettingTest -j40 RunSettingsRoboTests Test: make ROBOTEST_FILTER=LocationServicePreferenceControllerTest -j40 RunSettingsRoboTests Change-Id: I91e4dbff8bcce637424646f5435e72f9bb707631 --- .../settings/location/InjectedSetting.java | 111 +++++++++++----- .../LocationServicePreferenceController.java | 9 +- .../settings/location/SettingsInjector.java | 40 ++++-- .../widget/RestrictedAppPreference.java | 120 ++++++++++++++++++ .../location/InjectedSettingTest.java | 58 +++++++++ ...cationServicePreferenceControllerTest.java | 51 +++++++- 6 files changed, 347 insertions(+), 42 deletions(-) create mode 100644 src/com/android/settings/widget/RestrictedAppPreference.java create mode 100644 tests/robotests/src/com/android/settings/location/InjectedSettingTest.java diff --git a/src/com/android/settings/location/InjectedSetting.java b/src/com/android/settings/location/InjectedSetting.java index e5f1e68a561..7eae872e20e 100644 --- a/src/com/android/settings/location/InjectedSetting.java +++ b/src/com/android/settings/location/InjectedSetting.java @@ -22,7 +22,8 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.Immutable; -import com.android.internal.util.Preconditions; + +import java.util.Objects; /** * Specifies a setting that is being injected into Settings > Location > Location services. @@ -65,32 +66,19 @@ class InjectedSetting { */ public final String settingsActivity; - private InjectedSetting(String packageName, String className, - String title, int iconId, UserHandle userHandle, String settingsActivity) { - this.packageName = Preconditions.checkNotNull(packageName, "packageName"); - this.className = Preconditions.checkNotNull(className, "className"); - this.title = Preconditions.checkNotNull(title, "title"); - this.iconId = iconId; - this.mUserHandle = userHandle; - this.settingsActivity = Preconditions.checkNotNull(settingsActivity); - } - /** - * Returns a new instance, or null. + * The user restriction associated with this setting. */ - public static InjectedSetting newInstance(String packageName, String className, - String title, int iconId, UserHandle userHandle, String settingsActivity) { - if (packageName == null || className == null || - TextUtils.isEmpty(title) || TextUtils.isEmpty(settingsActivity)) { - if (Log.isLoggable(SettingsInjector.TAG, Log.WARN)) { - Log.w(SettingsInjector.TAG, "Illegal setting specification: package=" - + packageName + ", class=" + className - + ", title=" + title + ", settingsActivity=" + settingsActivity); - } - return null; - } - return new InjectedSetting(packageName, className, title, iconId, userHandle, - settingsActivity); + public final String userRestriction; + + private InjectedSetting(Builder builder) { + this.packageName = builder.mPackageName; + this.className = builder.mClassName; + this.title = builder.mTitle; + this.iconId = builder.mIconId; + this.mUserHandle = builder.mUserHandle; + this.settingsActivity = builder.mSettingsActivity; + this.userRestriction = builder.mUserRestriction; } @Override @@ -102,6 +90,7 @@ class InjectedSetting { ", iconId=" + iconId + ", userId=" + mUserHandle.getIdentifier() + ", settingsActivity='" + settingsActivity + '\'' + + ", userRestriction='" + userRestriction + '}'; } @@ -121,10 +110,13 @@ class InjectedSetting { InjectedSetting that = (InjectedSetting) o; - return packageName.equals(that.packageName) && className.equals(that.className) - && title.equals(that.title) && iconId == that.iconId - && mUserHandle.equals(that.mUserHandle) - && settingsActivity.equals(that.settingsActivity); + return Objects.equals(packageName, that.packageName) + && Objects.equals(className, that.className) + && Objects.equals(title, that.title) + && Objects.equals(iconId, that.iconId) + && Objects.equals(mUserHandle, that.mUserHandle) + && Objects.equals(settingsActivity, that.settingsActivity) + && Objects.equals(userRestriction, that.userRestriction); } @Override @@ -133,8 +125,67 @@ class InjectedSetting { result = 31 * result + className.hashCode(); result = 31 * result + title.hashCode(); result = 31 * result + iconId; - result = 31 * result + mUserHandle.hashCode(); + result = 31 * result + (mUserHandle == null ? 0 : mUserHandle.hashCode()); result = 31 * result + settingsActivity.hashCode(); + result = 31 * result + (userRestriction == null ? 0 : userRestriction.hashCode()); return result; } + + public static class Builder { + private String mPackageName; + private String mClassName; + private String mTitle; + private int mIconId; + private UserHandle mUserHandle; + private String mSettingsActivity; + private String mUserRestriction; + + public Builder setPackageName(String packageName) { + mPackageName = packageName; + return this; + } + + public Builder setClassName(String className) { + mClassName = className; + return this; + } + + public Builder setTitle(String title) { + mTitle = title; + return this; + } + + public Builder setIconId(int iconId) { + mIconId = iconId; + return this; + } + + public Builder setUserHandle(UserHandle userHandle) { + mUserHandle = userHandle; + return this; + } + + public Builder setSettingsActivity(String settingsActivity) { + mSettingsActivity = settingsActivity; + return this; + } + + public Builder setUserRestriction(String userRestriction) { + mUserRestriction = userRestriction; + return this; + } + + public InjectedSetting build() { + if (mPackageName == null || mClassName == null || TextUtils.isEmpty(mTitle) + || TextUtils.isEmpty(mSettingsActivity)) { + if (Log.isLoggable(SettingsInjector.TAG, Log.WARN)) { + Log.w(SettingsInjector.TAG, "Illegal setting specification: package=" + + mPackageName + ", class=" + mClassName + + ", title=" + mTitle + ", settingsActivity=" + mSettingsActivity); + } + return null; + } + return new InjectedSetting(this); + } + } } diff --git a/src/com/android/settings/location/LocationServicePreferenceController.java b/src/com/android/settings/location/LocationServicePreferenceController.java index 0a6a5c14006..a1d20690dcf 100644 --- a/src/com/android/settings/location/LocationServicePreferenceController.java +++ b/src/com/android/settings/location/LocationServicePreferenceController.java @@ -25,6 +25,7 @@ import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceScreen; import android.util.Log; +import com.android.settings.widget.RestrictedAppPreference; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; @@ -88,7 +89,13 @@ public class LocationServicePreferenceController extends LocationBasePreferenceC @Override public void updateState(Preference preference) { mCategoryLocationServices.removeAll(); - LocationSettings.addPreferencesSorted(getLocationServices(), mCategoryLocationServices); + final List prefs = getLocationServices(); + for (Preference pref : prefs) { + if (pref instanceof RestrictedAppPreference) { + ((RestrictedAppPreference) pref).checkRestrictionAndSetDisabled(); + } + } + LocationSettings.addPreferencesSorted(prefs, mCategoryLocationServices); } @Override diff --git a/src/com/android/settings/location/SettingsInjector.java b/src/com/android/settings/location/SettingsInjector.java index dfa51433820..2c6a4f38c8c 100644 --- a/src/com/android/settings/location/SettingsInjector.java +++ b/src/com/android/settings/location/SettingsInjector.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; @@ -37,11 +38,14 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.support.v7.preference.Preference; +import android.text.TextUtils; import android.util.AttributeSet; +import android.util.IconDrawableFactory; import android.util.Log; import android.util.Xml; import com.android.settings.widget.AppPreference; +import com.android.settings.widget.RestrictedAppPreference; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -215,12 +219,21 @@ class SettingsInjector { sa.getResourceId(android.R.styleable.SettingInjectorService_icon, 0); final String settingsActivity = sa.getString(android.R.styleable.SettingInjectorService_settingsActivity); + final String userRestriction = sa.getString( + android.R.styleable.SettingInjectorService_userRestriction); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "parsed title: " + title + ", iconId: " + iconId + ", settingsActivity: " + settingsActivity); } - return InjectedSetting.newInstance(packageName, className, - title, iconId, userHandle, settingsActivity); + return new InjectedSetting.Builder() + .setPackageName(packageName) + .setClassName(className) + .setTitle(title) + .setIconId(iconId) + .setUserHandle(userHandle) + .setSettingsActivity(settingsActivity) + .setUserRestriction(userRestriction) + .build(); } finally { sa.recycle(); } @@ -290,15 +303,26 @@ class SettingsInjector { */ private Preference addServiceSetting(Context prefContext, List prefs, InjectedSetting info) { - PackageManager pm = mContext.getPackageManager(); - Drawable appIcon = pm.getDrawable(info.packageName, info.iconId, null); - Drawable icon = pm.getUserBadgedIcon(appIcon, info.mUserHandle); - Preference pref = new AppPreference(prefContext); + final PackageManager pm = mContext.getPackageManager(); + Drawable appIcon = null; + try { + final PackageItemInfo itemInfo = new PackageItemInfo(); + itemInfo.icon = info.iconId; + itemInfo.packageName = info.packageName; + final ApplicationInfo appInfo = pm.getApplicationInfo(info.packageName, + PackageManager.GET_META_DATA); + appIcon = IconDrawableFactory.newInstance(mContext) + .getBadgedIcon(itemInfo, appInfo, info.mUserHandle.getIdentifier()); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Can't get ApplicationInfo for " + info.packageName, e); + } + Preference pref = TextUtils.isEmpty(info.userRestriction) + ? new AppPreference(prefContext) + : new RestrictedAppPreference(prefContext, info.userRestriction); pref.setTitle(info.title); pref.setSummary(null); - pref.setIcon(icon); + pref.setIcon(appIcon); pref.setOnPreferenceClickListener(new ServiceSettingClickedListener(info)); - prefs.add(pref); return pref; } diff --git a/src/com/android/settings/widget/RestrictedAppPreference.java b/src/com/android/settings/widget/RestrictedAppPreference.java new file mode 100644 index 00000000000..af6d8d1e0d4 --- /dev/null +++ b/src/com/android/settings/widget/RestrictedAppPreference.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2018 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.widget; + +import android.content.Context; +import android.os.UserHandle; +import android.support.v7.preference.PreferenceManager; +import android.support.v7.preference.PreferenceViewHolder; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; + +import com.android.settings.R; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedPreferenceHelper; + +/** + * {@link AppPreference} that implements user restriction utilities using + * {@link com.android.settingslib.RestrictedPreferenceHelper}. + * Used to show policy transparency on {@link AppPreference}. + */ +public class RestrictedAppPreference extends AppPreference { + private RestrictedPreferenceHelper mHelper; + private String userRestriction; + + public RestrictedAppPreference(Context context) { + super(context); + initialize(null, null); + } + + public RestrictedAppPreference(Context context, String userRestriction) { + super(context); + initialize(null, userRestriction); + } + + public RestrictedAppPreference(Context context, AttributeSet attrs, String userRestriction) { + super(context, attrs); + initialize(attrs, userRestriction); + } + + private void initialize(AttributeSet attrs, String userRestriction) { + setWidgetLayoutResource(R.layout.restricted_icon); + mHelper = new RestrictedPreferenceHelper(getContext(), this, attrs); + this.userRestriction = userRestriction; + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + mHelper.onBindViewHolder(holder); + final View restrictedIcon = holder.findViewById(R.id.restricted_icon); + if (restrictedIcon != null) { + restrictedIcon.setVisibility(isDisabledByAdmin() ? View.VISIBLE : View.GONE); + } + } + + @Override + public void performClick() { + if (!mHelper.performClick()) { + super.performClick(); + } + } + + @Override + public void setEnabled(boolean enabled) { + if (isDisabledByAdmin() && enabled) { + return; + } + super.setEnabled(enabled); + } + + public void setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin admin) { + if (mHelper.setDisabledByAdmin(admin)) { + notifyChanged(); + } + } + + public boolean isDisabledByAdmin() { + return mHelper.isDisabledByAdmin(); + } + + public void useAdminDisabledSummary(boolean useSummary) { + mHelper.useAdminDisabledSummary(useSummary); + } + + @Override + protected void onAttachedToHierarchy(PreferenceManager preferenceManager) { + mHelper.onAttachedToHierarchy(); + super.onAttachedToHierarchy(preferenceManager); + } + + public void checkRestrictionAndSetDisabled() { + if (TextUtils.isEmpty(userRestriction)) { + return; + } + mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId()); + } + + public void checkRestrictionAndSetDisabled(String userRestriction) { + mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId()); + } + + public void checkRestrictionAndSetDisabled(String userRestriction, int userId) { + mHelper.checkRestrictionAndSetDisabled(userRestriction, userId); + } +} diff --git a/tests/robotests/src/com/android/settings/location/InjectedSettingTest.java b/tests/robotests/src/com/android/settings/location/InjectedSettingTest.java new file mode 100644 index 00000000000..fb999582e49 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/InjectedSettingTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 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.location; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(SettingsRobolectricTestRunner.class) +public final class InjectedSettingTest { + + private static final String TEST_STRING = "test"; + + @Test + public void buildWithoutPackageName_ShouldReturnNull() { + assertThat(((new InjectedSetting.Builder()) + .setClassName(TEST_STRING) + .setTitle(TEST_STRING) + .setSettingsActivity(TEST_STRING).build())).isNull(); + } + + private InjectedSetting getTestSetting() { + return new InjectedSetting.Builder() + .setPackageName(TEST_STRING) + .setClassName(TEST_STRING) + .setTitle(TEST_STRING) + .setSettingsActivity(TEST_STRING).build(); + } + + @Test + public void testEquals() { + InjectedSetting setting1 = getTestSetting(); + InjectedSetting setting2 = getTestSetting(); + assertThat(setting1).isEqualTo(setting2); + } + + @Test + public void testHashCode() { + InjectedSetting setting = getTestSetting(); + assertThat(setting.hashCode()).isEqualTo(1225314048); + } +} diff --git a/tests/robotests/src/com/android/settings/location/LocationServicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationServicePreferenceControllerTest.java index 195e1b47c6c..099ef7df296 100644 --- a/tests/robotests/src/com/android/settings/location/LocationServicePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationServicePreferenceControllerTest.java @@ -24,16 +24,25 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.admin.DevicePolicyManager; import android.arch.lifecycle.LifecycleOwner; +import android.content.ComponentName; import android.content.Context; +import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceScreen; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowUserManager; +import com.android.settings.widget.RestrictedAppPreference; import com.android.settingslib.core.lifecycle.Lifecycle; +import java.util.ArrayList; +import java.util.List; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,11 +50,13 @@ import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; - -import java.util.ArrayList; -import java.util.List; +import org.robolectric.annotation.Config; @RunWith(SettingsRobolectricTestRunner.class) +@Config( + shadows = { + ShadowUserManager.class + }) public class LocationServicePreferenceControllerTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -56,6 +67,8 @@ public class LocationServicePreferenceControllerTest { private PreferenceScreen mScreen; @Mock private SettingsInjector mSettingsInjector; + @Mock + private DevicePolicyManager mDevicePolicyManager; private Context mContext; private LocationServicePreferenceController mController; @@ -73,6 +86,9 @@ public class LocationServicePreferenceControllerTest { final String key = mController.getPreferenceKey(); when(mScreen.findPreference(key)).thenReturn(mCategory); when(mCategory.getKey()).thenReturn(key); + when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)) + .thenReturn(mDevicePolicyManager); + } @Test @@ -132,4 +148,33 @@ public class LocationServicePreferenceControllerTest { verify(mSettingsInjector).reloadStatusMessages(); } + + @Test + public void withUserRestriction_shouldDisableLocationAccuracy() { + final List preferences = new ArrayList<>(); + final RestrictedAppPreference pref = new RestrictedAppPreference(mContext, + UserManager.DISALLOW_CONFIG_LOCATION); + pref.setTitle("Location Accuracy"); + preferences.add(pref); + doReturn(preferences).when(mSettingsInjector) + .getInjectedSettings(any(Context.class), anyInt()); + + int userId = UserHandle.myUserId(); + List enforcingUsers = new ArrayList<>(); + enforcingUsers.add(new UserManager.EnforcingUser(userId, + UserManager.RESTRICTION_SOURCE_DEVICE_OWNER)); + ComponentName componentName = new ComponentName("test", "test"); + // Ensure that RestrictedLockUtils.checkIfRestrictionEnforced doesn't return null. + ShadowUserManager.getShadow().setUserRestrictionSources( + UserManager.DISALLOW_CONFIG_LOCATION, + UserHandle.of(userId), + enforcingUsers); + when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(componentName); + + mController.displayPreference(mScreen); + mController.updateState(mCategory); + + assertThat(pref.isEnabled()).isFalse(); + assertThat(pref.isDisabledByAdmin()).isTrue(); + } }