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 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 97 public void onDestroyView() { 98 super.onDestroyView(); 99 mSwitchBar.hide(); 100 } 101 102 @Override 103 public void onResume() { 104 super.onResume(); 105 createPreferenceHierarchy(); 106 if (!mValidListener) { 107 mSwitchBar.addOnSwitchChangeListener(this); 108 mValidListener = true; 109 } 110 } 111 112 @Override 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 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 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 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 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 */ 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 256 public int getHelpResource() { 257 return R.string.help_url_location_access; 258 } 259 260 @Override 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 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