2 * Copyright (C) 2011 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.settings.location;
19 import android.app.Activity;
20 import android.app.admin.DevicePolicyManager;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.location.SettingInjectorService;
27 import android.os.Bundle;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.provider.Settings;
31 import android.support.v7.preference.Preference;
32 import android.support.v7.preference.PreferenceCategory;
33 import android.support.v7.preference.PreferenceGroup;
34 import android.support.v7.preference.PreferenceScreen;
35 import android.util.Log;
36 import android.view.Menu;
37 import android.view.MenuInflater;
38 import android.view.MenuItem;
39 import android.widget.Switch;
40 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
41 import com.android.settings.DimmableIconPreference;
42 import com.android.settings.R;
43 import com.android.settings.SettingsActivity;
44 import com.android.settings.Utils;
45 import com.android.settings.applications.InstalledAppDetails;
46 import com.android.settings.dashboard.SummaryLoader;
47 import com.android.settings.widget.SwitchBar;
48 import com.android.settingslib.RestrictedLockUtils;
49 import com.android.settingslib.RestrictedSwitchPreference;
50 import com.android.settingslib.location.RecentLocationApps;
52 import java.util.ArrayList;
53 import java.util.Collections;
54 import java.util.Comparator;
55 import java.util.List;
57 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
60 * System location settings (Settings > Location). The screen has three parts:
62 * <li>Platform location controls</li>
64 * <li>In switch bar: location master switch. Used to toggle
65 * {@link android.provider.Settings.Secure#LOCATION_MODE} between
66 * {@link android.provider.Settings.Secure#LOCATION_MODE_OFF} and another location mode.
68 * <li>Mode preference: only available if the master switch is on, selects between
69 * {@link android.provider.Settings.Secure#LOCATION_MODE} of
70 * {@link android.provider.Settings.Secure#LOCATION_MODE_HIGH_ACCURACY},
71 * {@link android.provider.Settings.Secure#LOCATION_MODE_BATTERY_SAVING}, or
72 * {@link android.provider.Settings.Secure#LOCATION_MODE_SENSORS_ONLY}.</li>
74 * <li>Recent location requests: automatically populated by {@link RecentLocationApps}</li>
75 * <li>Location services: multi-app settings provided from outside the Android framework. Each
76 * is injected by a system-partition app via the {@link SettingInjectorService} API.</li>
79 * Note that as of KitKat, the {@link SettingInjectorService} is the preferred method for OEMs to
80 * add their own settings to this page, rather than directly modifying the framework code. Among
81 * other things, this simplifies integration with future changes to the default (AOSP)
84 public class LocationSettings extends LocationSettingsBase
85 implements SwitchBar.OnSwitchChangeListener {
87 private static final String TAG = "LocationSettings";
90 * Key for managed profile location switch preference. Shown only
91 * if there is a managed profile.
93 private static final String KEY_MANAGED_PROFILE_SWITCH = "managed_profile_location_switch";
94 /** Key for preference screen "Mode" */
95 private static final String KEY_LOCATION_MODE = "location_mode";
96 /** Key for preference category "Recent location requests" */
97 private static final String KEY_RECENT_LOCATION_REQUESTS = "recent_location_requests";
98 /** Key for preference category "Location services" */
99 private static final String KEY_LOCATION_SERVICES = "location_services";
101 private static final int MENU_SCANNING = Menu.FIRST;
103 private SwitchBar mSwitchBar;
104 private Switch mSwitch;
105 private boolean mValidListener = false;
106 private UserHandle mManagedProfile;
107 private RestrictedSwitchPreference mManagedProfileSwitch;
108 private Preference mLocationMode;
109 private PreferenceCategory mCategoryRecentLocationRequests;
110 /** Receives UPDATE_INTENT */
111 private BroadcastReceiver mReceiver;
112 private SettingsInjector injector;
113 private UserManager mUm;
116 public int getMetricsCategory() {
117 return MetricsEvent.LOCATION;
121 public void onActivityCreated(Bundle savedInstanceState) {
122 super.onActivityCreated(savedInstanceState);
124 final SettingsActivity activity = (SettingsActivity) getActivity();
125 mUm = (UserManager) activity.getSystemService(Context.USER_SERVICE);
127 setHasOptionsMenu(true);
128 mSwitchBar = activity.getSwitchBar();
129 mSwitch = mSwitchBar.getSwitch();
132 setHasOptionsMenu(true);
136 public void onDestroyView() {
137 super.onDestroyView();
142 public void onResume() {
144 createPreferenceHierarchy();
145 if (!mValidListener) {
146 mSwitchBar.addOnSwitchChangeListener(this);
147 mValidListener = true;
152 public void onPause() {
154 getActivity().unregisterReceiver(mReceiver);
155 } catch (RuntimeException e) {
156 // Ignore exceptions caused by race condition
157 if (Log.isLoggable(TAG, Log.VERBOSE)) {
158 Log.v(TAG, "Swallowing " + e);
161 if (mValidListener) {
162 mSwitchBar.removeOnSwitchChangeListener(this);
163 mValidListener = false;
168 private void addPreferencesSorted(List<Preference> prefs, PreferenceGroup container) {
169 // If there's some items to display, sort the items and add them to the container.
170 Collections.sort(prefs, new Comparator<Preference>() {
172 public int compare(Preference lhs, Preference rhs) {
173 return lhs.getTitle().toString().compareTo(rhs.getTitle().toString());
176 for (Preference entry : prefs) {
177 container.addPreference(entry);
181 private PreferenceScreen createPreferenceHierarchy() {
182 final SettingsActivity activity = (SettingsActivity) getActivity();
183 PreferenceScreen root = getPreferenceScreen();
187 addPreferencesFromResource(R.xml.location_settings);
188 root = getPreferenceScreen();
190 setupManagedProfileCategory(root);
191 mLocationMode = root.findPreference(KEY_LOCATION_MODE);
192 mLocationMode.setOnPreferenceClickListener(
193 new Preference.OnPreferenceClickListener() {
195 public boolean onPreferenceClick(Preference preference) {
196 activity.startPreferencePanel(
197 LocationSettings.this,
198 LocationMode.class.getName(), null,
199 R.string.location_mode_screen_title, null, LocationSettings.this,
205 RecentLocationApps recentApps = new RecentLocationApps(activity);
206 List<RecentLocationApps.Request> recentLocationRequests = recentApps.getAppList();
208 final AppLocationPermissionPreferenceController preferenceController =
209 new AppLocationPermissionPreferenceController(activity);
210 preferenceController.displayPreference(root);
212 mCategoryRecentLocationRequests =
213 (PreferenceCategory) root.findPreference(KEY_RECENT_LOCATION_REQUESTS);
215 List<Preference> recentLocationPrefs = new ArrayList<>(recentLocationRequests.size());
216 for (final RecentLocationApps.Request request : recentLocationRequests) {
217 DimmableIconPreference pref = new DimmableIconPreference(getPrefContext(),
218 request.contentDescription);
219 pref.setIcon(request.icon);
220 pref.setTitle(request.label);
221 if (request.isHighBattery) {
222 pref.setSummary(R.string.location_high_battery_use);
224 pref.setSummary(R.string.location_low_battery_use);
226 pref.setOnPreferenceClickListener(
227 new PackageEntryClickedListener(request.packageName, request.userHandle));
228 recentLocationPrefs.add(pref);
231 if (recentLocationRequests.size() > 0) {
232 addPreferencesSorted(recentLocationPrefs, mCategoryRecentLocationRequests);
234 // If there's no item to display, add a "No recent apps" item.
235 Preference banner = new Preference(getPrefContext());
236 banner.setLayoutResource(R.layout.location_list_no_item);
237 banner.setTitle(R.string.location_no_recent_apps);
238 banner.setSelectable(false);
239 mCategoryRecentLocationRequests.addPreference(banner);
242 boolean lockdownOnLocationAccess = false;
243 // Checking if device policy has put a location access lock-down on the managed
244 // profile. If managed profile has lock-down on location access then its
245 // injected location services must not be shown.
246 if (mManagedProfile != null
247 && mUm.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile)) {
248 lockdownOnLocationAccess = true;
250 addLocationServices(activity, root, lockdownOnLocationAccess);
252 refreshLocationMode();
256 private void setupManagedProfileCategory(PreferenceScreen root) {
257 // Looking for a managed profile. If there are no managed profiles then we are removing the
258 // managed profile category.
259 mManagedProfile = Utils.getManagedProfile(mUm);
260 if (mManagedProfile == null) {
261 // There is no managed profile
262 root.removePreference(root.findPreference(KEY_MANAGED_PROFILE_SWITCH));
263 mManagedProfileSwitch = null;
265 mManagedProfileSwitch = (RestrictedSwitchPreference)root
266 .findPreference(KEY_MANAGED_PROFILE_SWITCH);
267 mManagedProfileSwitch.setOnPreferenceClickListener(null);
271 private void changeManagedProfileLocationAccessStatus(boolean mainSwitchOn) {
272 if (mManagedProfileSwitch == null) {
275 mManagedProfileSwitch.setOnPreferenceClickListener(null);
276 final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(),
277 UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile.getIdentifier());
278 final boolean isRestrictedByBase = isManagedProfileRestrictedByBase();
279 if (!isRestrictedByBase && admin != null) {
280 mManagedProfileSwitch.setDisabledByAdmin(admin);
281 mManagedProfileSwitch.setChecked(false);
283 boolean enabled = mainSwitchOn;
284 mManagedProfileSwitch.setEnabled(enabled);
286 int summaryResId = R.string.switch_off_text;
288 mManagedProfileSwitch.setChecked(false);
290 mManagedProfileSwitch.setChecked(!isRestrictedByBase);
291 summaryResId = (isRestrictedByBase ?
292 R.string.switch_off_text : R.string.switch_on_text);
293 mManagedProfileSwitch.setOnPreferenceClickListener(
294 mManagedProfileSwitchClickListener);
296 mManagedProfileSwitch.setSummary(summaryResId);
301 * Add the settings injected by external apps into the "App Settings" category. Hides the
302 * category if there are no injected settings.
304 * Reloads the settings whenever receives
305 * {@link SettingInjectorService#ACTION_INJECTED_SETTING_CHANGED}.
307 private void addLocationServices(Context context, PreferenceScreen root,
308 boolean lockdownOnLocationAccess) {
309 PreferenceCategory categoryLocationServices =
310 (PreferenceCategory) root.findPreference(KEY_LOCATION_SERVICES);
311 injector = new SettingsInjector(context);
312 // If location access is locked down by device policy then we only show injected settings
313 // for the primary profile.
314 List<Preference> locationServices = injector.getInjectedSettings(lockdownOnLocationAccess ?
315 UserHandle.myUserId() : UserHandle.USER_CURRENT);
317 mReceiver = new BroadcastReceiver() {
319 public void onReceive(Context context, Intent intent) {
320 if (Log.isLoggable(TAG, Log.DEBUG)) {
321 Log.d(TAG, "Received settings change intent: " + intent);
323 injector.reloadStatusMessages();
327 IntentFilter filter = new IntentFilter();
328 filter.addAction(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED);
329 context.registerReceiver(mReceiver, filter);
331 if (locationServices.size() > 0) {
332 addPreferencesSorted(locationServices, categoryLocationServices);
334 // If there's no item to display, remove the whole category.
335 root.removePreference(categoryLocationServices);
340 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
341 menu.add(0, MENU_SCANNING, 0, R.string.location_menu_scanning);
342 // The super class adds "Help & Feedback" menu item.
343 super.onCreateOptionsMenu(menu, inflater);
347 public boolean onOptionsItemSelected(MenuItem item) {
348 final SettingsActivity activity = (SettingsActivity) getActivity();
349 switch (item.getItemId()) {
351 activity.startPreferencePanel(
353 ScanningSettings.class.getName(), null,
354 R.string.location_scanning_screen_title, null, LocationSettings.this,
358 return super.onOptionsItemSelected(item);
363 public int getHelpResource() {
364 return R.string.help_url_location_access;
368 public void onModeChanged(int mode, boolean restricted) {
369 int modeDescription = LocationPreferenceController.getLocationString(mode);
370 if (modeDescription != 0) {
371 mLocationMode.setSummary(modeDescription);
374 // Restricted user can't change the location mode, so disable the master switch. But in some
375 // corner cases, the location might still be enabled. In such case the master switch should
376 // be disabled but checked.
377 final boolean enabled = (mode != android.provider.Settings.Secure.LOCATION_MODE_OFF);
378 EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(),
379 UserManager.DISALLOW_SHARE_LOCATION, UserHandle.myUserId());
380 boolean hasBaseUserRestriction = RestrictedLockUtils.hasBaseUserRestriction(getActivity(),
381 UserManager.DISALLOW_SHARE_LOCATION, UserHandle.myUserId());
382 // Disable the whole switch bar instead of the switch itself. If we disabled the switch
383 // only, it would be re-enabled again if the switch bar is not disabled.
384 if (!hasBaseUserRestriction && admin != null) {
385 mSwitchBar.setDisabledByAdmin(admin);
387 mSwitchBar.setEnabled(!restricted);
389 mLocationMode.setEnabled(enabled && !restricted);
390 mCategoryRecentLocationRequests.setEnabled(enabled);
392 if (enabled != mSwitch.isChecked()) {
393 // set listener to null so that that code below doesn't trigger onCheckedChanged()
394 if (mValidListener) {
395 mSwitchBar.removeOnSwitchChangeListener(this);
397 mSwitch.setChecked(enabled);
398 if (mValidListener) {
399 mSwitchBar.addOnSwitchChangeListener(this);
403 changeManagedProfileLocationAccessStatus(enabled);
405 // As a safety measure, also reloads on location mode change to ensure the settings are
406 // up-to-date even if an affected app doesn't send the setting changed broadcast.
407 injector.reloadStatusMessages();
411 * Listens to the state change of the location master switch.
414 public void onSwitchChanged(Switch switchView, boolean isChecked) {
416 setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_PREVIOUS);
418 setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_OFF);
422 private boolean isManagedProfileRestrictedByBase() {
423 if (mManagedProfile == null) {
426 return mUm.hasBaseUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile);
429 private Preference.OnPreferenceClickListener mManagedProfileSwitchClickListener =
430 new Preference.OnPreferenceClickListener() {
432 public boolean onPreferenceClick(Preference preference) {
433 final boolean switchState = mManagedProfileSwitch.isChecked();
434 mUm.setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION,
435 !switchState, mManagedProfile);
436 mManagedProfileSwitch.setSummary(switchState ?
437 R.string.switch_on_text : R.string.switch_off_text);
442 private class PackageEntryClickedListener
443 implements Preference.OnPreferenceClickListener {
444 private String mPackage;
445 private UserHandle mUserHandle;
447 public PackageEntryClickedListener(String packageName, UserHandle userHandle) {
448 mPackage = packageName;
449 mUserHandle = userHandle;
453 public boolean onPreferenceClick(Preference preference) {
454 // start new fragment to display extended information
455 Bundle args = new Bundle();
456 args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackage);
457 ((SettingsActivity) getActivity()).startPreferencePanelAsUser(
458 LocationSettings.this,
459 InstalledAppDetails.class.getName(), args,
460 R.string.application_info_label, null, mUserHandle);
465 private static class SummaryProvider implements SummaryLoader.SummaryProvider {
467 private final Context mContext;
468 private final SummaryLoader mSummaryLoader;
470 public SummaryProvider(Context context, SummaryLoader summaryLoader) {
472 mSummaryLoader = summaryLoader;
476 public void setListening(boolean listening) {
478 mSummaryLoader.setSummary(
479 this, LocationPreferenceController.getLocationSummary(mContext));
484 public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
485 = new SummaryLoader.SummaryProviderFactory() {
487 public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
488 SummaryLoader summaryLoader) {
489 return new SummaryProvider(activity, summaryLoader);