settings:keywords="@string/keywords_location_mode"
android:summary="@string/location_mode_location_off_title" />
+ <!-- This preference category gets removed if there is no managed profile -->
+ <PreferenceCategory
+ android:key="managed_profile_location_category"
+ android:title="@string/managed_profile_location_category">
+
+ <Preference
+ android:key="managed_profile_location_switch"
+ android:title="@string/managed_profile_location_switch_title"
+ android:summary="@string/managed_profile_location_switch_lockdown"
+ android:persistent="false"
+ android:enabled="false"
+ android:selectable="false" />
+
+ </PreferenceCategory>
+
<PreferenceCategory
android:key="recent_location_requests"
android:title="@string/location_category_recent_location_requests" />
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
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) {
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);
+ }
+ }
}
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;
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);
}
* 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)) {
}
return null;
}
- return new InjectedSetting(packageName, className, title, iconId, settingsActivity);
+ return new InjectedSetting(packageName, className, title, iconId, userHandle,
+ settingsActivity);
}
@Override
", mClassName='" + className + '\'' +
", label=" + title +
", iconId=" + iconId +
+ ", userId=" + mUserHandle.getIdentifier() +
", settingsActivity='" + settingsActivity + '\'' +
'}';
}
return packageName.equals(that.packageName) && className.equals(that.className)
&& title.equals(that.title) && iconId == that.iconId
+ && mUserHandle.equals(that.mUserHandle)
&& settingsActivity.equals(that.settingsActivity);
}
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;
}
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;
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" */
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();
addPreferencesFromResource(R.xml.location_settings);
root = getPreferenceScreen();
+ setupManagedProfileCategory(root);
mLocationMode = root.findPreference(KEY_LOCATION_MODE);
mLocationMode.setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
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.
* 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
// 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);
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();
}
}
- /**
- * 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) {
int uid = ops.getUid();
int userId = UserHandle.getUserId(uid);
- AccessiblePreference preference = null;
+ DimmableIconPreference preference = null;
try {
IPackageManager ipm = AppGlobals.getPackageManager();
ApplicationInfo appInfo =
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));
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;
}
/**
- * 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 {
}
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Loaded settings: " + settings);
+ Log.d(TAG, "Loaded settings for profile id " + profileId + ": " + settings);
}
return settings;
*
* 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;
+ 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);
/**
* 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 {
+ ", settingsActivity: " + settingsActivity);
}
return InjectedSetting.newInstance(packageName, className,
- title, iconId, settingsActivity);
+ title, iconId, userHandle, settingsActivity);
} finally {
sa.recycle();
}
/**
* 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();
* 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
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() {