• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package com.android.settings.location;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.location.SettingInjectorService;
24 import android.os.Bundle;
25 import android.os.UserHandle;
26 import android.os.UserManager;
27 import android.preference.Preference;
28 import android.preference.PreferenceCategory;
29 import android.preference.PreferenceGroup;
30 import android.preference.PreferenceScreen;
31 import android.util.Log;
32 import android.view.Menu;
33 import android.view.MenuInflater;
34 import android.view.MenuItem;
35 import android.widget.Switch;
36 import com.android.internal.logging.MetricsLogger;
37 import com.android.settings.R;
38 import com.android.settings.SettingsActivity;
39 import com.android.settings.Utils;
40 import com.android.settings.widget.SwitchBar;
41 
42 import java.util.Collections;
43 import java.util.Comparator;
44 import java.util.List;
45 
46 /**
47  * System location settings (Settings > Location). The screen has three parts:
48  * <ul>
49  *     <li>Platform location controls</li>
50  *     <ul>
51  *         <li>In switch bar: location master switch. Used to toggle
52  *         {@link android.provider.Settings.Secure#LOCATION_MODE} between
53  *         {@link android.provider.Settings.Secure#LOCATION_MODE_OFF} and another location mode.
54  *         </li>
55  *         <li>Mode preference: only available if the master switch is on, selects between
56  *         {@link android.provider.Settings.Secure#LOCATION_MODE} of
57  *         {@link android.provider.Settings.Secure#LOCATION_MODE_HIGH_ACCURACY},
58  *         {@link android.provider.Settings.Secure#LOCATION_MODE_BATTERY_SAVING}, or
59  *         {@link android.provider.Settings.Secure#LOCATION_MODE_SENSORS_ONLY}.</li>
60  *     </ul>
61  *     <li>Recent location requests: automatically populated by {@link RecentLocationApps}</li>
62  *     <li>Location services: multi-app settings provided from outside the Android framework. Each
63  *     is injected by a system-partition app via the {@link SettingInjectorService} API.</li>
64  * </ul>
65  * <p>
66  * Note that as of KitKat, the {@link SettingInjectorService} is the preferred method for OEMs to
67  * add their own settings to this page, rather than directly modifying the framework code. Among
68  * other things, this simplifies integration with future changes to the default (AOSP)
69  * implementation.
70  */
71 public class LocationSettings extends LocationSettingsBase
72         implements SwitchBar.OnSwitchChangeListener {
73 
74     private static final String TAG = "LocationSettings";
75 
76     /**
77      * Key for managed profile location preference category. Category is shown only
78      * if there is a managed profile
79      */
80     private static final String KEY_MANAGED_PROFILE_CATEGORY = "managed_profile_location_category";
81     /**
82      * Key for managed profile location preference. Note it used to be a switch pref and we had to
83      * keep the key as strings had been submitted for string freeze before the decision to
84      * demote this to a simple preference was made. TODO: Candidate for refactoring.
85      */
86     private static final String KEY_MANAGED_PROFILE_PREFERENCE = "managed_profile_location_switch";
87     /** Key for preference screen "Mode" */
88     private static final String KEY_LOCATION_MODE = "location_mode";
89     /** Key for preference category "Recent location requests" */
90     private static final String KEY_RECENT_LOCATION_REQUESTS = "recent_location_requests";
91     /** Key for preference category "Location services" */
92     private static final String KEY_LOCATION_SERVICES = "location_services";
93 
94     private static final int MENU_SCANNING = Menu.FIRST;
95 
96     private SwitchBar mSwitchBar;
97     private Switch mSwitch;
98     private boolean mValidListener = false;
99     private UserHandle mManagedProfile;
100     private Preference mManagedProfilePreference;
101     private Preference mLocationMode;
102     private PreferenceCategory mCategoryRecentLocationRequests;
103     /** Receives UPDATE_INTENT  */
104     private BroadcastReceiver mReceiver;
105     private SettingsInjector injector;
106     private UserManager mUm;
107 
108     @Override
getMetricsCategory()109     protected int getMetricsCategory() {
110         return MetricsLogger.LOCATION;
111     }
112 
113     @Override
onActivityCreated(Bundle savedInstanceState)114     public void onActivityCreated(Bundle savedInstanceState) {
115         super.onActivityCreated(savedInstanceState);
116 
117         final SettingsActivity activity = (SettingsActivity) getActivity();
118         mUm = (UserManager) activity.getSystemService(Context.USER_SERVICE);
119 
120         mSwitchBar = activity.getSwitchBar();
121         mSwitch = mSwitchBar.getSwitch();
122         mSwitchBar.show();
123     }
124 
125     @Override
onDestroyView()126     public void onDestroyView() {
127         super.onDestroyView();
128         mSwitchBar.hide();
129     }
130 
131     @Override
onResume()132     public void onResume() {
133         super.onResume();
134         createPreferenceHierarchy();
135         if (!mValidListener) {
136             mSwitchBar.addOnSwitchChangeListener(this);
137             mValidListener = true;
138         }
139     }
140 
141     @Override
onPause()142     public void onPause() {
143         try {
144             getActivity().unregisterReceiver(mReceiver);
145         } catch (RuntimeException e) {
146             // Ignore exceptions caused by race condition
147             if (Log.isLoggable(TAG, Log.VERBOSE)) {
148                 Log.v(TAG, "Swallowing " + e);
149             }
150         }
151         if (mValidListener) {
152             mSwitchBar.removeOnSwitchChangeListener(this);
153             mValidListener = false;
154         }
155         super.onPause();
156     }
157 
addPreferencesSorted(List<Preference> prefs, PreferenceGroup container)158     private void addPreferencesSorted(List<Preference> prefs, PreferenceGroup container) {
159         // If there's some items to display, sort the items and add them to the container.
160         Collections.sort(prefs, new Comparator<Preference>() {
161             @Override
162             public int compare(Preference lhs, Preference rhs) {
163                 return lhs.getTitle().toString().compareTo(rhs.getTitle().toString());
164             }
165         });
166         for (Preference entry : prefs) {
167             container.addPreference(entry);
168         }
169     }
170 
createPreferenceHierarchy()171     private PreferenceScreen createPreferenceHierarchy() {
172         final SettingsActivity activity = (SettingsActivity) getActivity();
173         PreferenceScreen root = getPreferenceScreen();
174         if (root != null) {
175             root.removeAll();
176         }
177         addPreferencesFromResource(R.xml.location_settings);
178         root = getPreferenceScreen();
179 
180         setupManagedProfileCategory(root);
181         mLocationMode = root.findPreference(KEY_LOCATION_MODE);
182         mLocationMode.setOnPreferenceClickListener(
183                 new Preference.OnPreferenceClickListener() {
184                     @Override
185                     public boolean onPreferenceClick(Preference preference) {
186                         activity.startPreferencePanel(
187                                 LocationMode.class.getName(), null,
188                                 R.string.location_mode_screen_title, null, LocationSettings.this,
189                                 0);
190                         return true;
191                     }
192                 });
193 
194         mCategoryRecentLocationRequests =
195                 (PreferenceCategory) root.findPreference(KEY_RECENT_LOCATION_REQUESTS);
196         RecentLocationApps recentApps = new RecentLocationApps(activity);
197         List<Preference> recentLocationRequests = recentApps.getAppList();
198         if (recentLocationRequests.size() > 0) {
199             addPreferencesSorted(recentLocationRequests, mCategoryRecentLocationRequests);
200         } else {
201             // If there's no item to display, add a "No recent apps" item.
202             Preference banner = new Preference(activity);
203             banner.setLayoutResource(R.layout.location_list_no_item);
204             banner.setTitle(R.string.location_no_recent_apps);
205             banner.setSelectable(false);
206             mCategoryRecentLocationRequests.addPreference(banner);
207         }
208 
209         boolean lockdownOnLocationAccess = false;
210         // Checking if device policy has put a location access lock-down on the managed
211         // profile. If managed profile has lock-down on location access then its
212         // injected location services must not be shown.
213         if (mManagedProfile != null
214                 && mUm.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile)) {
215             lockdownOnLocationAccess = true;
216         }
217         addLocationServices(activity, root, lockdownOnLocationAccess);
218 
219         refreshLocationMode();
220         return root;
221     }
222 
setupManagedProfileCategory(PreferenceScreen root)223     private void setupManagedProfileCategory(PreferenceScreen root) {
224         // Looking for a managed profile. If there are no managed profiles then we are removing the
225         // managed profile category.
226         mManagedProfile = Utils.getManagedProfile(mUm);
227         if (mManagedProfile == null) {
228             // There is no managed profile
229             root.removePreference(root.findPreference(KEY_MANAGED_PROFILE_CATEGORY));
230             mManagedProfilePreference = null;
231         } else {
232             mManagedProfilePreference = root.findPreference(KEY_MANAGED_PROFILE_PREFERENCE);
233             mManagedProfilePreference.setOnPreferenceClickListener(null);
234         }
235     }
236 
changeManagedProfileLocationAccessStatus(boolean enabled, int summaryResId)237     private void changeManagedProfileLocationAccessStatus(boolean enabled, int summaryResId) {
238         if (mManagedProfilePreference == null) {
239             return;
240         }
241         mManagedProfilePreference.setEnabled(enabled);
242         mManagedProfilePreference.setSummary(summaryResId);
243     }
244 
245     /**
246      * Add the settings injected by external apps into the "App Settings" category. Hides the
247      * category if there are no injected settings.
248      *
249      * Reloads the settings whenever receives
250      * {@link SettingInjectorService#ACTION_INJECTED_SETTING_CHANGED}.
251      */
addLocationServices(Context context, PreferenceScreen root, boolean lockdownOnLocationAccess)252     private void addLocationServices(Context context, PreferenceScreen root,
253             boolean lockdownOnLocationAccess) {
254         PreferenceCategory categoryLocationServices =
255                 (PreferenceCategory) root.findPreference(KEY_LOCATION_SERVICES);
256         injector = new SettingsInjector(context);
257         // If location access is locked down by device policy then we only show injected settings
258         // for the primary profile.
259         List<Preference> locationServices = injector.getInjectedSettings(lockdownOnLocationAccess ?
260                 UserHandle.myUserId() : UserHandle.USER_CURRENT);
261 
262         mReceiver = new BroadcastReceiver() {
263             @Override
264             public void onReceive(Context context, Intent intent) {
265                 if (Log.isLoggable(TAG, Log.DEBUG)) {
266                     Log.d(TAG, "Received settings change intent: " + intent);
267                 }
268                 injector.reloadStatusMessages();
269             }
270         };
271 
272         IntentFilter filter = new IntentFilter();
273         filter.addAction(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED);
274         context.registerReceiver(mReceiver, filter);
275 
276         if (locationServices.size() > 0) {
277             addPreferencesSorted(locationServices, categoryLocationServices);
278         } else {
279             // If there's no item to display, remove the whole category.
280             root.removePreference(categoryLocationServices);
281         }
282     }
283 
284     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)285     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
286         menu.add(0, MENU_SCANNING, 0, R.string.location_menu_scanning);
287         // The super class adds "Help & Feedback" menu item.
288         super.onCreateOptionsMenu(menu, inflater);
289     }
290 
291     @Override
onOptionsItemSelected(MenuItem item)292     public boolean onOptionsItemSelected(MenuItem item) {
293         final SettingsActivity activity = (SettingsActivity) getActivity();
294         switch (item.getItemId()) {
295             case MENU_SCANNING:
296                 activity.startPreferencePanel(
297                         ScanningSettings.class.getName(), null,
298                         R.string.location_scanning_screen_title, null, LocationSettings.this,
299                         0);
300                 return true;
301             default:
302                 return super.onOptionsItemSelected(item);
303         }
304     }
305 
306     @Override
getHelpResource()307     public int getHelpResource() {
308         return R.string.help_url_location_access;
309     }
310 
311     @Override
onModeChanged(int mode, boolean restricted)312     public void onModeChanged(int mode, boolean restricted) {
313         switch (mode) {
314             case android.provider.Settings.Secure.LOCATION_MODE_OFF:
315                 mLocationMode.setSummary(R.string.location_mode_location_off_title);
316                 break;
317             case android.provider.Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
318                 mLocationMode.setSummary(R.string.location_mode_sensors_only_title);
319                 break;
320             case android.provider.Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
321                 mLocationMode.setSummary(R.string.location_mode_battery_saving_title);
322                 break;
323             case android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
324                 mLocationMode.setSummary(R.string.location_mode_high_accuracy_title);
325                 break;
326             default:
327                 break;
328         }
329 
330         // Restricted user can't change the location mode, so disable the master switch. But in some
331         // corner cases, the location might still be enabled. In such case the master switch should
332         // be disabled but checked.
333         final boolean enabled = (mode != android.provider.Settings.Secure.LOCATION_MODE_OFF);
334         // Disable the whole switch bar instead of the switch itself. If we disabled the switch
335         // only, it would be re-enabled again if the switch bar is not disabled.
336         mSwitchBar.setEnabled(!restricted);
337         mLocationMode.setEnabled(enabled && !restricted);
338         mCategoryRecentLocationRequests.setEnabled(enabled);
339 
340         if (enabled != mSwitch.isChecked()) {
341             // set listener to null so that that code below doesn't trigger onCheckedChanged()
342             if (mValidListener) {
343                 mSwitchBar.removeOnSwitchChangeListener(this);
344             }
345             mSwitch.setChecked(enabled);
346             if (mValidListener) {
347                 mSwitchBar.addOnSwitchChangeListener(this);
348             }
349         }
350 
351         if (mManagedProfilePreference != null) {
352             if (mUm.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile)) {
353                 changeManagedProfileLocationAccessStatus(false,
354                         R.string.managed_profile_location_switch_lockdown);
355             } else {
356                 if (enabled) {
357                     changeManagedProfileLocationAccessStatus(true, R.string.switch_on_text);
358                 } else {
359                     changeManagedProfileLocationAccessStatus(false, R.string.switch_off_text);
360                 }
361             }
362         }
363 
364         // As a safety measure, also reloads on location mode change to ensure the settings are
365         // up-to-date even if an affected app doesn't send the setting changed broadcast.
366         injector.reloadStatusMessages();
367     }
368 
369     /**
370      * Listens to the state change of the location master switch.
371      */
372     @Override
onSwitchChanged(Switch switchView, boolean isChecked)373     public void onSwitchChanged(Switch switchView, boolean isChecked) {
374         if (isChecked) {
375             setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY);
376         } else {
377             setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_OFF);
378         }
379     }
380 }
381