• 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.Binder;
25 import android.os.Bundle;
26 import android.os.UserHandle;
27 import android.os.UserManager;
28 import android.preference.Preference;
29 import android.preference.PreferenceCategory;
30 import android.preference.PreferenceGroup;
31 import android.preference.PreferenceScreen;
32 import android.preference.SwitchPreference;
33 import android.provider.Settings;
34 import android.util.Log;
35 import android.widget.Switch;
36 
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  * Location access settings.
48  */
49 public class LocationSettings extends LocationSettingsBase
50         implements SwitchBar.OnSwitchChangeListener {
51 
52     private static final String TAG = "LocationSettings";
53 
54     /**
55      * Key for managed profile location preference category. Category is shown only
56      * if there is a managed profile
57      */
58     private static final String KEY_MANAGED_PROFILE_CATEGORY = "managed_profile_location_category";
59     /**
60      * Key for managed profile location preference. Note it used to be a switch pref and we had to
61      * keep the key as strings had been submitted for string freeze before the decision to
62      * demote this to a simple preference was made. TODO: Candidate for refactoring.
63      */
64     private static final String KEY_MANAGED_PROFILE_PREFERENCE = "managed_profile_location_switch";
65     /** Key for preference screen "Mode" */
66     private static final String KEY_LOCATION_MODE = "location_mode";
67     /** Key for preference category "Recent location requests" */
68     private static final String KEY_RECENT_LOCATION_REQUESTS = "recent_location_requests";
69     /** Key for preference category "Location services" */
70     private static final String KEY_LOCATION_SERVICES = "location_services";
71 
72     private SwitchBar mSwitchBar;
73     private Switch mSwitch;
74     private boolean mValidListener = false;
75     private UserHandle mManagedProfile;
76     private Preference mManagedProfilePreference;
77     private Preference mLocationMode;
78     private PreferenceCategory mCategoryRecentLocationRequests;
79     /** Receives UPDATE_INTENT  */
80     private BroadcastReceiver mReceiver;
81     private SettingsInjector injector;
82     private UserManager mUm;
83 
84     @Override
onActivityCreated(Bundle savedInstanceState)85     public void onActivityCreated(Bundle savedInstanceState) {
86         super.onActivityCreated(savedInstanceState);
87 
88         final SettingsActivity activity = (SettingsActivity) getActivity();
89         mUm = (UserManager) activity.getSystemService(Context.USER_SERVICE);
90 
91         mSwitchBar = activity.getSwitchBar();
92         mSwitch = mSwitchBar.getSwitch();
93         mSwitchBar.show();
94     }
95 
96     @Override
onDestroyView()97     public void onDestroyView() {
98         super.onDestroyView();
99         mSwitchBar.hide();
100     }
101 
102     @Override
onResume()103     public void onResume() {
104         super.onResume();
105         createPreferenceHierarchy();
106         if (!mValidListener) {
107             mSwitchBar.addOnSwitchChangeListener(this);
108             mValidListener = true;
109         }
110     }
111 
112     @Override
onPause()113     public void onPause() {
114         try {
115             getActivity().unregisterReceiver(mReceiver);
116         } catch (RuntimeException e) {
117             // Ignore exceptions caused by race condition
118             if (Log.isLoggable(TAG, Log.VERBOSE)) {
119                 Log.v(TAG, "Swallowing " + e);
120             }
121         }
122         if (mValidListener) {
123             mSwitchBar.removeOnSwitchChangeListener(this);
124             mValidListener = false;
125         }
126         super.onPause();
127     }
128 
addPreferencesSorted(List<Preference> prefs, PreferenceGroup container)129     private void addPreferencesSorted(List<Preference> prefs, PreferenceGroup container) {
130         // If there's some items to display, sort the items and add them to the container.
131         Collections.sort(prefs, new Comparator<Preference>() {
132             @Override
133             public int compare(Preference lhs, Preference rhs) {
134                 return lhs.getTitle().toString().compareTo(rhs.getTitle().toString());
135             }
136         });
137         for (Preference entry : prefs) {
138             container.addPreference(entry);
139         }
140     }
141 
createPreferenceHierarchy()142     private PreferenceScreen createPreferenceHierarchy() {
143         final SettingsActivity activity = (SettingsActivity) getActivity();
144         PreferenceScreen root = getPreferenceScreen();
145         if (root != null) {
146             root.removeAll();
147         }
148         addPreferencesFromResource(R.xml.location_settings);
149         root = getPreferenceScreen();
150 
151         setupManagedProfileCategory(root);
152         mLocationMode = root.findPreference(KEY_LOCATION_MODE);
153         mLocationMode.setOnPreferenceClickListener(
154                 new Preference.OnPreferenceClickListener() {
155                     @Override
156                     public boolean onPreferenceClick(Preference preference) {
157                         activity.startPreferencePanel(
158                                 LocationMode.class.getName(), null,
159                                 R.string.location_mode_screen_title, null, LocationSettings.this,
160                                 0);
161                         return true;
162                     }
163                 });
164 
165         mCategoryRecentLocationRequests =
166                 (PreferenceCategory) root.findPreference(KEY_RECENT_LOCATION_REQUESTS);
167         RecentLocationApps recentApps = new RecentLocationApps(activity);
168         List<Preference> recentLocationRequests = recentApps.getAppList();
169         if (recentLocationRequests.size() > 0) {
170             addPreferencesSorted(recentLocationRequests, mCategoryRecentLocationRequests);
171         } else {
172             // If there's no item to display, add a "No recent apps" item.
173             Preference banner = new Preference(activity);
174             banner.setLayoutResource(R.layout.location_list_no_item);
175             banner.setTitle(R.string.location_no_recent_apps);
176             banner.setSelectable(false);
177             mCategoryRecentLocationRequests.addPreference(banner);
178         }
179 
180         boolean lockdownOnLocationAccess = false;
181         // Checking if device policy has put a location access lock-down on the managed
182         // profile. If managed profile has lock-down on location access then its
183         // injected location services must not be shown.
184         if (mManagedProfile != null
185                 && mUm.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile)) {
186             lockdownOnLocationAccess = true;
187         }
188         addLocationServices(activity, root, lockdownOnLocationAccess);
189 
190         refreshLocationMode();
191         return root;
192     }
193 
setupManagedProfileCategory(PreferenceScreen root)194     private void setupManagedProfileCategory(PreferenceScreen root) {
195         // Looking for a managed profile. If there are no managed profiles then we are removing the
196         // managed profile category.
197         mManagedProfile = Utils.getManagedProfile(mUm);
198         if (mManagedProfile == null) {
199             // There is no managed profile
200             root.removePreference(root.findPreference(KEY_MANAGED_PROFILE_CATEGORY));
201             mManagedProfilePreference = null;
202         } else {
203             mManagedProfilePreference = root.findPreference(KEY_MANAGED_PROFILE_PREFERENCE);
204             mManagedProfilePreference.setOnPreferenceClickListener(null);
205         }
206     }
207 
changeManagedProfileLocationAccessStatus(boolean enabled, int summaryResId)208     private void changeManagedProfileLocationAccessStatus(boolean enabled, int summaryResId) {
209         if (mManagedProfilePreference == null) {
210             return;
211         }
212         mManagedProfilePreference.setEnabled(enabled);
213         mManagedProfilePreference.setSummary(summaryResId);
214     }
215 
216     /**
217      * Add the settings injected by external apps into the "App Settings" category. Hides the
218      * category if there are no injected settings.
219      *
220      * Reloads the settings whenever receives
221      * {@link SettingInjectorService#ACTION_INJECTED_SETTING_CHANGED}.
222      */
addLocationServices(Context context, PreferenceScreen root, boolean lockdownOnLocationAccess)223     private void addLocationServices(Context context, PreferenceScreen root,
224             boolean lockdownOnLocationAccess) {
225         PreferenceCategory categoryLocationServices =
226                 (PreferenceCategory) root.findPreference(KEY_LOCATION_SERVICES);
227         injector = new SettingsInjector(context);
228         // If location access is locked down by device policy then we only show injected settings
229         // for the primary profile.
230         List<Preference> locationServices = injector.getInjectedSettings(lockdownOnLocationAccess ?
231                 UserHandle.myUserId() : UserHandle.USER_CURRENT);
232 
233         mReceiver = new BroadcastReceiver() {
234             @Override
235             public void onReceive(Context context, Intent intent) {
236                 if (Log.isLoggable(TAG, Log.DEBUG)) {
237                     Log.d(TAG, "Received settings change intent: " + intent);
238                 }
239                 injector.reloadStatusMessages();
240             }
241         };
242 
243         IntentFilter filter = new IntentFilter();
244         filter.addAction(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED);
245         context.registerReceiver(mReceiver, filter);
246 
247         if (locationServices.size() > 0) {
248             addPreferencesSorted(locationServices, categoryLocationServices);
249         } else {
250             // If there's no item to display, remove the whole category.
251             root.removePreference(categoryLocationServices);
252         }
253     }
254 
255     @Override
getHelpResource()256     public int getHelpResource() {
257         return R.string.help_url_location_access;
258     }
259 
260     @Override
onModeChanged(int mode, boolean restricted)261     public void onModeChanged(int mode, boolean restricted) {
262         switch (mode) {
263             case android.provider.Settings.Secure.LOCATION_MODE_OFF:
264                 mLocationMode.setSummary(R.string.location_mode_location_off_title);
265                 break;
266             case android.provider.Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
267                 mLocationMode.setSummary(R.string.location_mode_sensors_only_title);
268                 break;
269             case android.provider.Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
270                 mLocationMode.setSummary(R.string.location_mode_battery_saving_title);
271                 break;
272             case android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
273                 mLocationMode.setSummary(R.string.location_mode_high_accuracy_title);
274                 break;
275             default:
276                 break;
277         }
278 
279         // Restricted user can't change the location mode, so disable the master switch. But in some
280         // corner cases, the location might still be enabled. In such case the master switch should
281         // be disabled but checked.
282         final boolean enabled = (mode != android.provider.Settings.Secure.LOCATION_MODE_OFF);
283         // Disable the whole switch bar instead of the switch itself. If we disabled the switch
284         // only, it would be re-enabled again if the switch bar is not disabled.
285         mSwitchBar.setEnabled(!restricted);
286         mLocationMode.setEnabled(enabled && !restricted);
287         mCategoryRecentLocationRequests.setEnabled(enabled);
288 
289         if (enabled != mSwitch.isChecked()) {
290             // set listener to null so that that code below doesn't trigger onCheckedChanged()
291             if (mValidListener) {
292                 mSwitchBar.removeOnSwitchChangeListener(this);
293             }
294             mSwitch.setChecked(enabled);
295             if (mValidListener) {
296                 mSwitchBar.addOnSwitchChangeListener(this);
297             }
298         }
299 
300         if (mManagedProfilePreference != null) {
301             if (mUm.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile)) {
302                 changeManagedProfileLocationAccessStatus(false,
303                         R.string.managed_profile_location_switch_lockdown);
304             } else {
305                 if (enabled) {
306                     changeManagedProfileLocationAccessStatus(true, R.string.switch_on_text);
307                 } else {
308                     changeManagedProfileLocationAccessStatus(false, R.string.switch_off_text);
309                 }
310             }
311         }
312 
313         // As a safety measure, also reloads on location mode change to ensure the settings are
314         // up-to-date even if an affected app doesn't send the setting changed broadcast.
315         injector.reloadStatusMessages();
316     }
317 
318     /**
319      * Listens to the state change of the location master switch.
320      */
321     @Override
onSwitchChanged(Switch switchView, boolean isChecked)322     public void onSwitchChanged(Switch switchView, boolean isChecked) {
323         if (isChecked) {
324             setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY);
325         } else {
326             setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_OFF);
327         }
328     }
329 }
330