diff options
author | Zoltan Szatmary-Ban <szatmz@google.com> | 2014-10-15 11:35:55 +0100 |
---|---|---|
committer | Zoltan Szatmary-Ban <szatmz@google.com> | 2014-12-05 19:40:59 +0000 |
commit | 86c877e9b66165bb46432de6172d1fdc6b2808fe (patch) | |
tree | 924f56613ad4e3a0134883368cc89cf25b2fb447 /src | |
parent | 3f5fd1febae34976bdc3f03b44eaec99bbf9703b (diff) | |
download | packages_apps_Settings-86c877e9b66165bb46432de6172d1fdc6b2808fe.zip packages_apps_Settings-86c877e9b66165bb46432de6172d1fdc6b2808fe.tar.gz packages_apps_Settings-86c877e9b66165bb46432de6172d1fdc6b2808fe.tar.bz2 |
Make Location Settings multiprofile aware
Injected location services and location access status are shown
for managed profiles on Settings > Location
Bug: 18602878
Change-Id: Ic6232f3dc03d9675b90fbfd0163fe5bae4bd13c6
Diffstat (limited to 'src')
5 files changed, 192 insertions, 68 deletions
diff --git a/src/com/android/settings/location/DimmableIconPreference.java b/src/com/android/settings/location/DimmableIconPreference.java index acde1c1..f785992 100644 --- a/src/com/android/settings/location/DimmableIconPreference.java +++ b/src/com/android/settings/location/DimmableIconPreference.java @@ -16,10 +16,14 @@ package com.android.settings.location; +import android.annotation.Nullable; import android.content.Context; import android.graphics.drawable.Drawable; import android.preference.Preference; +import android.text.TextUtils; import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; /** * A preference item that can dim the icon when it's disabled, either directly or because its parent @@ -29,16 +33,23 @@ public class DimmableIconPreference extends Preference { private static final int ICON_ALPHA_ENABLED = 255; private static final int ICON_ALPHA_DISABLED = 102; - public DimmableIconPreference(Context context, AttributeSet attrs, int defStyle) { + private final CharSequence mContentDescription; + + public DimmableIconPreference(Context context, AttributeSet attrs, int defStyle, + @Nullable CharSequence contentDescription) { super(context, attrs, defStyle); + mContentDescription = contentDescription; } - public DimmableIconPreference(Context context, AttributeSet attrs) { + public DimmableIconPreference(Context context, AttributeSet attrs, + @Nullable CharSequence contentDescription) { super(context, attrs); + mContentDescription = contentDescription; } - public DimmableIconPreference(Context context) { + public DimmableIconPreference(Context context, @Nullable CharSequence contentDescription) { super(context); + mContentDescription = contentDescription; } private void dimIcon(boolean dimmed) { @@ -60,4 +71,13 @@ public class DimmableIconPreference extends Preference { dimIcon(!enabled); super.setEnabled(enabled); } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + if (!TextUtils.isEmpty(mContentDescription)) { + final TextView titleView = (TextView) view.findViewById(android.R.id.title); + titleView.setContentDescription(mContentDescription); + } + } } diff --git a/src/com/android/settings/location/InjectedSetting.java b/src/com/android/settings/location/InjectedSetting.java index 5f4440a..bb009b9 100644 --- a/src/com/android/settings/location/InjectedSetting.java +++ b/src/com/android/settings/location/InjectedSetting.java @@ -19,6 +19,7 @@ package com.android.settings.location; import android.content.Intent; import android.text.TextUtils; import android.util.Log; +import android.os.UserHandle; import com.android.internal.annotations.Immutable; import com.android.internal.util.Preconditions; @@ -53,17 +54,23 @@ class InjectedSetting { public final int iconId; /** + * The user/profile associated with this setting (e.g. managed profile) + */ + public final UserHandle mUserHandle; + + /** * The activity to launch to allow the user to modify the settings value. Assumed to be in the * {@link #packageName} package. */ public final String settingsActivity; private InjectedSetting(String packageName, String className, - String title, int iconId, String settingsActivity) { + 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); } @@ -71,7 +78,7 @@ class InjectedSetting { * Returns a new instance, or null. */ public static InjectedSetting newInstance(String packageName, String className, - String title, int iconId, String settingsActivity) { + 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)) { @@ -81,7 +88,8 @@ class InjectedSetting { } return null; } - return new InjectedSetting(packageName, className, title, iconId, settingsActivity); + return new InjectedSetting(packageName, className, title, iconId, userHandle, + settingsActivity); } @Override @@ -91,6 +99,7 @@ class InjectedSetting { ", mClassName='" + className + '\'' + ", label=" + title + ", iconId=" + iconId + + ", userId=" + mUserHandle.getIdentifier() + ", settingsActivity='" + settingsActivity + '\'' + '}'; } @@ -113,6 +122,7 @@ class InjectedSetting { return packageName.equals(that.packageName) && className.equals(that.className) && title.equals(that.title) && iconId == that.iconId + && mUserHandle.equals(that.mUserHandle) && settingsActivity.equals(that.settingsActivity); } @@ -122,6 +132,7 @@ class InjectedSetting { result = 31 * result + className.hashCode(); result = 31 * result + title.hashCode(); result = 31 * result + iconId; + result = 31 * result + mUserHandle.hashCode(); result = 31 * result + settingsActivity.hashCode(); return result; } diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index c0a13c1..ba5cd92 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -21,16 +21,22 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.location.SettingInjectorService; +import android.os.Binder; import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; +import android.provider.Settings; import android.util.Log; import android.widget.Switch; import com.android.settings.R; import com.android.settings.SettingsActivity; +import com.android.settings.Utils; import com.android.settings.widget.SwitchBar; import java.util.Collections; @@ -45,6 +51,17 @@ public class LocationSettings extends LocationSettingsBase private static final String TAG = "LocationSettings"; + /** + * Key for managed profile location preference category. Category is shown only + * if there is a managed profile + */ + private static final String KEY_MANAGED_PROFILE_CATEGORY = "managed_profile_location_category"; + /** + * Key for managed profile location preference. Note it used to be a switch pref and we had to + * keep the key as strings had been submitted for string freeze before the decision to + * demote this to a simple preference was made. TODO: Candidate for refactoring. + */ + private static final String KEY_MANAGED_PROFILE_PREFERENCE = "managed_profile_location_switch"; /** Key for preference screen "Mode" */ private static final String KEY_LOCATION_MODE = "location_mode"; /** Key for preference category "Recent location requests" */ @@ -55,17 +72,21 @@ public class LocationSettings extends LocationSettingsBase private SwitchBar mSwitchBar; private Switch mSwitch; private boolean mValidListener = false; + private UserHandle mManagedProfile; + private Preference mManagedProfilePreference; private Preference mLocationMode; private PreferenceCategory mCategoryRecentLocationRequests; /** Receives UPDATE_INTENT */ private BroadcastReceiver mReceiver; private SettingsInjector injector; + private UserManager mUm; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); final SettingsActivity activity = (SettingsActivity) getActivity(); + mUm = (UserManager) activity.getSystemService(Context.USER_SERVICE); mSwitchBar = activity.getSwitchBar(); mSwitch = mSwitchBar.getSwitch(); @@ -127,6 +148,7 @@ public class LocationSettings extends LocationSettingsBase addPreferencesFromResource(R.xml.location_settings); root = getPreferenceScreen(); + setupManagedProfileCategory(root); mLocationMode = root.findPreference(KEY_LOCATION_MODE); mLocationMode.setOnPreferenceClickListener( new Preference.OnPreferenceClickListener() { @@ -155,12 +177,42 @@ public class LocationSettings extends LocationSettingsBase mCategoryRecentLocationRequests.addPreference(banner); } - addLocationServices(activity, root); + boolean lockdownOnLocationAccess = false; + // Checking if device policy has put a location access lock-down on the managed + // profile. If managed profile has lock-down on location access then its + // injected location services must not be shown. + if (mManagedProfile != null + && mUm.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile)) { + lockdownOnLocationAccess = true; + } + addLocationServices(activity, root, lockdownOnLocationAccess); refreshLocationMode(); return root; } + private void setupManagedProfileCategory(PreferenceScreen root) { + // Looking for a managed profile. If there are no managed profiles then we are removing the + // managed profile category. + mManagedProfile = Utils.getManagedProfile(mUm); + if (mManagedProfile == null) { + // There is no managed profile + root.removePreference(root.findPreference(KEY_MANAGED_PROFILE_CATEGORY)); + mManagedProfilePreference = null; + } else { + mManagedProfilePreference = root.findPreference(KEY_MANAGED_PROFILE_PREFERENCE); + mManagedProfilePreference.setOnPreferenceClickListener(null); + } + } + + private void changeManagedProfileLocationAccessStatus(boolean enabled, int summaryResId) { + if (mManagedProfilePreference == null) { + return; + } + mManagedProfilePreference.setEnabled(enabled); + mManagedProfilePreference.setSummary(summaryResId); + } + /** * Add the settings injected by external apps into the "App Settings" category. Hides the * category if there are no injected settings. @@ -168,11 +220,15 @@ public class LocationSettings extends LocationSettingsBase * Reloads the settings whenever receives * {@link SettingInjectorService#ACTION_INJECTED_SETTING_CHANGED}. */ - private void addLocationServices(Context context, PreferenceScreen root) { + private void addLocationServices(Context context, PreferenceScreen root, + boolean lockdownOnLocationAccess) { PreferenceCategory categoryLocationServices = (PreferenceCategory) root.findPreference(KEY_LOCATION_SERVICES); injector = new SettingsInjector(context); - List<Preference> locationServices = injector.getInjectedSettings(); + // If location access is locked down by device policy then we only show injected settings + // for the primary profile. + List<Preference> locationServices = injector.getInjectedSettings(lockdownOnLocationAccess ? + UserHandle.myUserId() : UserHandle.USER_CURRENT); mReceiver = new BroadcastReceiver() { @Override @@ -223,7 +279,7 @@ public class LocationSettings extends LocationSettingsBase // Restricted user can't change the location mode, so disable the master switch. But in some // corner cases, the location might still be enabled. In such case the master switch should // be disabled but checked. - boolean enabled = (mode != android.provider.Settings.Secure.LOCATION_MODE_OFF); + final boolean enabled = (mode != android.provider.Settings.Secure.LOCATION_MODE_OFF); // Disable the whole switch bar instead of the switch itself. If we disabled the switch // only, it would be re-enabled again if the switch bar is not disabled. mSwitchBar.setEnabled(!restricted); @@ -240,6 +296,20 @@ public class LocationSettings extends LocationSettingsBase mSwitchBar.addOnSwitchChangeListener(this); } } + + if (mManagedProfilePreference != null) { + if (mUm.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile)) { + changeManagedProfileLocationAccessStatus(false, + R.string.managed_profile_location_switch_lockdown); + } else { + if (enabled) { + changeManagedProfileLocationAccessStatus(true, R.string.switch_on_text); + } else { + changeManagedProfileLocationAccessStatus(false, R.string.switch_off_text); + } + } + } + // As a safety measure, also reloads on location mode change to ensure the settings are // up-to-date even if an affected app doesn't send the setting changed broadcast. injector.reloadStatusMessages(); diff --git a/src/com/android/settings/location/RecentLocationApps.java b/src/com/android/settings/location/RecentLocationApps.java index 7e99725..9f2b8ab 100644 --- a/src/com/android/settings/location/RecentLocationApps.java +++ b/src/com/android/settings/location/RecentLocationApps.java @@ -77,35 +77,13 @@ public class RecentLocationApps { } } - /** - * Subclass of {@link Preference} to intercept views and allow content description to be set on - * them for accessibility purposes. - */ - private static class AccessiblePreference extends DimmableIconPreference { - public CharSequence mContentDescription; - - public AccessiblePreference(Context context, CharSequence contentDescription) { - super(context); - mContentDescription = contentDescription; - } - - @Override - protected void onBindView(View view) { - super.onBindView(view); - if (mContentDescription != null) { - final TextView titleView = (TextView) view.findViewById(android.R.id.title); - titleView.setContentDescription(mContentDescription); - } - } - } - - private AccessiblePreference createRecentLocationEntry( + private DimmableIconPreference createRecentLocationEntry( Drawable icon, CharSequence label, boolean isHighBattery, CharSequence contentDescription, Preference.OnPreferenceClickListener listener) { - AccessiblePreference pref = new AccessiblePreference(mActivity, contentDescription); + DimmableIconPreference pref = new DimmableIconPreference(mActivity, contentDescription); pref.setIcon(icon); pref.setTitle(label); if (isHighBattery) { @@ -198,7 +176,7 @@ public class RecentLocationApps { int uid = ops.getUid(); int userId = UserHandle.getUserId(uid); - AccessiblePreference preference = null; + DimmableIconPreference preference = null; try { IPackageManager ipm = AppGlobals.getPackageManager(); ApplicationInfo appInfo = @@ -215,6 +193,11 @@ public class RecentLocationApps { Drawable icon = mPackageManager.getUserBadgedIcon(appIcon, userHandle); CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo); CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle); + if (appLabel.toString().contentEquals(badgedAppLabel)) { + // If badged label is not different from original then no need for it as + // a separate content description. + badgedAppLabel = null; + } preference = createRecentLocationEntry(icon, appLabel, highBattery, badgedAppLabel, new PackageEntryClickedListener(packageName)); diff --git a/src/com/android/settings/location/SettingsInjector.java b/src/com/android/settings/location/SettingsInjector.java index edf67b8..f0a0ff3 100644 --- a/src/com/android/settings/location/SettingsInjector.java +++ b/src/com/android/settings/location/SettingsInjector.java @@ -32,11 +32,15 @@ import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.os.SystemClock; +import android.os.UserHandle; +import android.os.UserManager; import android.preference.Preference; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; + import com.android.settings.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -99,27 +103,29 @@ class SettingsInjector { } /** - * Returns a list with one {@link InjectedSetting} object for each {@link android.app.Service} - * that responds to {@link SettingInjectorService#ACTION_SERVICE_INTENT} and provides the - * expected setting metadata. + * Returns a list for a profile with one {@link InjectedSetting} object for each + * {@link android.app.Service} that responds to + * {@link SettingInjectorService#ACTION_SERVICE_INTENT} and provides the expected setting + * metadata. * * Duplicates some code from {@link android.content.pm.RegisteredServicesCache}. * * TODO: unit test */ - private List<InjectedSetting> getSettings() { + private List<InjectedSetting> getSettings(final UserHandle userHandle) { PackageManager pm = mContext.getPackageManager(); Intent intent = new Intent(SettingInjectorService.ACTION_SERVICE_INTENT); + final int profileId = userHandle.getIdentifier(); List<ResolveInfo> resolveInfos = - pm.queryIntentServices(intent, PackageManager.GET_META_DATA); + pm.queryIntentServicesAsUser(intent, PackageManager.GET_META_DATA, profileId); if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Found services: " + resolveInfos); + Log.d(TAG, "Found services for profile id " + profileId + ": " + resolveInfos); } List<InjectedSetting> settings = new ArrayList<InjectedSetting>(resolveInfos.size()); for (ResolveInfo resolveInfo : resolveInfos) { try { - InjectedSetting setting = parseServiceInfo(resolveInfo, pm); + InjectedSetting setting = parseServiceInfo(resolveInfo, userHandle, pm); if (setting == null) { Log.w(TAG, "Unable to load service info " + resolveInfo); } else { @@ -132,7 +138,7 @@ class SettingsInjector { } } if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Loaded settings: " + settings); + Log.d(TAG, "Loaded settings for profile id " + profileId + ": " + settings); } return settings; @@ -144,8 +150,8 @@ class SettingsInjector { * * Duplicates some code from {@link android.content.pm.RegisteredServicesCache}. */ - private static InjectedSetting parseServiceInfo(ResolveInfo service, PackageManager pm) - throws XmlPullParserException, IOException { + private static InjectedSetting parseServiceInfo(ResolveInfo service, UserHandle userHandle, + PackageManager pm) throws XmlPullParserException, IOException { ServiceInfo si = service.serviceInfo; ApplicationInfo ai = si.applicationInfo; @@ -179,8 +185,9 @@ class SettingsInjector { + SettingInjectorService.ATTRIBUTES_NAME + " tag"); } - Resources res = pm.getResourcesForApplication(ai); - return parseAttributes(si.packageName, si.name, res, attrs); + Resources res = pm.getResourcesForApplicationAsUser(si.packageName, + userHandle.getIdentifier()); + return parseAttributes(si.packageName, si.name, userHandle, res, attrs); } catch (PackageManager.NameNotFoundException e) { throw new XmlPullParserException( "Unable to load resources for package " + si.packageName); @@ -194,8 +201,8 @@ class SettingsInjector { /** * Returns an immutable representation of the static attributes for the setting, or null. */ - private static InjectedSetting parseAttributes( - String packageName, String className, Resources res, AttributeSet attrs) { + private static InjectedSetting parseAttributes(String packageName, String className, + UserHandle userHandle, Resources res, AttributeSet attrs) { TypedArray sa = res.obtainAttributes(attrs, android.R.styleable.SettingInjectorService); try { @@ -211,7 +218,7 @@ class SettingsInjector { + ", settingsActivity: " + settingsActivity); } return InjectedSetting.newInstance(packageName, className, - title, iconId, settingsActivity); + title, iconId, userHandle, settingsActivity); } finally { sa.recycle(); } @@ -219,13 +226,24 @@ class SettingsInjector { /** * Gets a list of preferences that other apps have injected. + * + * @param profileId Identifier of the user/profile to obtain the injected settings for or + * UserHandle.USER_CURRENT for all profiles associated with current user. */ - public List<Preference> getInjectedSettings() { - Iterable<InjectedSetting> settings = getSettings(); + public List<Preference> getInjectedSettings(final int profileId) { + final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + final List<UserHandle> profiles = um.getUserProfiles(); ArrayList<Preference> prefs = new ArrayList<Preference>(); - for (InjectedSetting setting : settings) { - Preference pref = addServiceSetting(prefs, setting); - mSettings.add(new Setting(setting, pref)); + final int profileCount = profiles.size(); + for (int i = 0; i < profileCount; ++i) { + final UserHandle userHandle = profiles.get(i); + if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) { + Iterable<InjectedSetting> settings = getSettings(userHandle); + for (InjectedSetting setting : settings) { + Preference pref = addServiceSetting(prefs, setting); + mSettings.add(new Setting(setting, pref)); + } + } } reloadStatusMessages(); @@ -247,24 +265,46 @@ class SettingsInjector { * Adds an injected setting to the root. */ private Preference addServiceSetting(List<Preference> prefs, InjectedSetting info) { - Preference pref = new DimmableIconPreference(mContext); + PackageManager pm = mContext.getPackageManager(); + Drawable appIcon = pm.getDrawable(info.packageName, info.iconId, null); + Drawable icon = pm.getUserBadgedIcon(appIcon, info.mUserHandle); + CharSequence badgedAppLabel = pm.getUserBadgedLabel(info.title, info.mUserHandle); + if (info.title.contentEquals(badgedAppLabel)) { + // If badged label is not different from original then no need for it as + // a separate content description. + badgedAppLabel = null; + } + Preference pref = new DimmableIconPreference(mContext, badgedAppLabel); pref.setTitle(info.title); pref.setSummary(null); - PackageManager pm = mContext.getPackageManager(); - Drawable icon = pm.getDrawable(info.packageName, info.iconId, null); pref.setIcon(icon); - - // Activity to start if they click on the preference. Must start in new task to ensure - // that "android.settings.LOCATION_SOURCE_SETTINGS" brings user back to Settings > Location. - Intent settingIntent = new Intent(); - settingIntent.setClassName(info.packageName, info.settingsActivity); - settingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - pref.setIntent(settingIntent); + pref.setOnPreferenceClickListener(new ServiceSettingClickedListener(info)); prefs.add(pref); return pref; } + private class ServiceSettingClickedListener + implements Preference.OnPreferenceClickListener { + private InjectedSetting mInfo; + + public ServiceSettingClickedListener(InjectedSetting info) { + mInfo = info; + } + + @Override + public boolean onPreferenceClick(Preference preference) { + // Activity to start if they click on the preference. Must start in new task to ensure + // that "android.settings.LOCATION_SOURCE_SETTINGS" brings user back to + // Settings > Location. + Intent settingIntent = new Intent(); + settingIntent.setClassName(mInfo.packageName, mInfo.settingsActivity); + settingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivityAsUser(settingIntent, mInfo.mUserHandle); + return true; + } + } + /** * Loads the setting status values one at a time. Each load starts a subclass of {@link * SettingInjectorService}, so to reduce memory pressure we don't want to load too many at @@ -451,9 +491,9 @@ class SettingsInjector { startMillis = 0; } - // Start the service, making sure that this is attributed to the current user rather - // than the system user. - mContext.startServiceAsUser(intent, android.os.Process.myUserHandle()); + // Start the service, making sure that this is attributed to the user associated with + // the setting rather than the system user. + mContext.startServiceAsUser(intent, setting.mUserHandle); } public long getElapsedTime() { |