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 17package com.android.settings.location; 18 19import android.app.ActionBar; 20import android.app.Activity; 21import android.content.BroadcastReceiver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.location.LocationManager; 26import android.location.SettingInjectorService; 27import android.os.Bundle; 28import android.preference.Preference; 29import android.preference.PreferenceActivity; 30import android.preference.PreferenceCategory; 31import android.preference.PreferenceGroup; 32import android.preference.PreferenceScreen; 33import android.provider.Settings; 34import android.util.Log; 35import android.view.Gravity; 36import android.widget.CompoundButton; 37import android.widget.Switch; 38 39import com.android.settings.R; 40 41import java.util.Collections; 42import java.util.Comparator; 43import java.util.List; 44 45/** 46 * Location access settings. 47 */ 48public class LocationSettings extends LocationSettingsBase 49 implements CompoundButton.OnCheckedChangeListener { 50 51 private static final String TAG = "LocationSettings"; 52 53 /** Key for preference screen "Mode" */ 54 private static final String KEY_LOCATION_MODE = "location_mode"; 55 /** Key for preference category "Recent location requests" */ 56 private static final String KEY_RECENT_LOCATION_REQUESTS = "recent_location_requests"; 57 /** Key for preference category "Location services" */ 58 private static final String KEY_LOCATION_SERVICES = "location_services"; 59 60 private Switch mSwitch; 61 private boolean mValidListener; 62 private Preference mLocationMode; 63 private PreferenceCategory mCategoryRecentLocationRequests; 64 /** Receives UPDATE_INTENT */ 65 private BroadcastReceiver mReceiver; 66 67 public LocationSettings() { 68 mValidListener = false; 69 } 70 71 @Override 72 public void onResume() { 73 super.onResume(); 74 mSwitch = new Switch(getActivity()); 75 mSwitch.setOnCheckedChangeListener(this); 76 mValidListener = true; 77 createPreferenceHierarchy(); 78 } 79 80 @Override 81 public void onPause() { 82 try { 83 getActivity().unregisterReceiver(mReceiver); 84 } catch (RuntimeException e) { 85 // Ignore exceptions caused by race condition 86 } 87 super.onPause(); 88 mValidListener = false; 89 mSwitch.setOnCheckedChangeListener(null); 90 } 91 92 private void addPreferencesSorted(List<Preference> prefs, PreferenceGroup container) { 93 // If there's some items to display, sort the items and add them to the container. 94 Collections.sort(prefs, new Comparator<Preference>() { 95 @Override 96 public int compare(Preference lhs, Preference rhs) { 97 return lhs.getTitle().toString().compareTo(rhs.getTitle().toString()); 98 } 99 }); 100 for (Preference entry : prefs) { 101 container.addPreference(entry); 102 } 103 } 104 105 private PreferenceScreen createPreferenceHierarchy() { 106 final PreferenceActivity activity = (PreferenceActivity) getActivity(); 107 PreferenceScreen root = getPreferenceScreen(); 108 if (root != null) { 109 root.removeAll(); 110 } 111 addPreferencesFromResource(R.xml.location_settings); 112 root = getPreferenceScreen(); 113 114 mLocationMode = root.findPreference(KEY_LOCATION_MODE); 115 mLocationMode.setOnPreferenceClickListener( 116 new Preference.OnPreferenceClickListener() { 117 @Override 118 public boolean onPreferenceClick(Preference preference) { 119 activity.startPreferencePanel( 120 LocationMode.class.getName(), null, 121 R.string.location_mode_screen_title, null, LocationSettings.this, 122 0); 123 return true; 124 } 125 }); 126 127 mCategoryRecentLocationRequests = 128 (PreferenceCategory) root.findPreference(KEY_RECENT_LOCATION_REQUESTS); 129 RecentLocationApps recentApps = new RecentLocationApps(activity); 130 List<Preference> recentLocationRequests = recentApps.getAppList(); 131 if (recentLocationRequests.size() > 0) { 132 addPreferencesSorted(recentLocationRequests, mCategoryRecentLocationRequests); 133 } else { 134 // If there's no item to display, add a "No recent apps" item. 135 Preference banner = new Preference(activity); 136 banner.setLayoutResource(R.layout.location_list_no_item); 137 banner.setTitle(R.string.location_no_recent_apps); 138 banner.setSelectable(false); 139 mCategoryRecentLocationRequests.addPreference(banner); 140 } 141 142 addLocationServices(activity, root); 143 144 // Only show the master switch when we're not in multi-pane mode, and not being used as 145 // Setup Wizard. 146 if (activity.onIsHidingHeaders() || !activity.onIsMultiPane()) { 147 final int padding = activity.getResources().getDimensionPixelSize( 148 R.dimen.action_bar_switch_padding); 149 mSwitch.setPaddingRelative(0, 0, padding, 0); 150 activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, 151 ActionBar.DISPLAY_SHOW_CUSTOM); 152 activity.getActionBar().setCustomView(mSwitch, new ActionBar.LayoutParams( 153 ActionBar.LayoutParams.WRAP_CONTENT, 154 ActionBar.LayoutParams.WRAP_CONTENT, 155 Gravity.CENTER_VERTICAL | Gravity.END)); 156 } 157 158 setHasOptionsMenu(true); 159 160 refreshLocationMode(); 161 return root; 162 } 163 164 /** 165 * Add the settings injected by external apps into the "App Settings" category. Hides the 166 * category if there are no injected settings. 167 * 168 * Reloads the settings whenever receives 169 * {@link SettingInjectorService#ACTION_INJECTED_SETTING_CHANGED}. As a safety measure, 170 * also reloads on {@link LocationManager#MODE_CHANGED_ACTION} to ensure the settings are 171 * up-to-date after mode changes even if an affected app doesn't send the setting changed 172 * broadcast. 173 */ 174 private void addLocationServices(Context context, PreferenceScreen root) { 175 PreferenceCategory categoryLocationServices = 176 (PreferenceCategory) root.findPreference(KEY_LOCATION_SERVICES); 177 final SettingsInjector injector = new SettingsInjector(context); 178 List<Preference> locationServices = injector.getInjectedSettings(); 179 180 mReceiver = new BroadcastReceiver() { 181 @Override 182 public void onReceive(Context context, Intent intent) { 183 if (Log.isLoggable(TAG, Log.DEBUG)) { 184 Log.d(TAG, "Received settings change intent: " + intent); 185 } 186 injector.reloadStatusMessages(); 187 } 188 }; 189 190 IntentFilter filter = new IntentFilter(); 191 filter.addAction(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED); 192 filter.addAction(LocationManager.MODE_CHANGED_ACTION); 193 context.registerReceiver(mReceiver, filter); 194 195 if (locationServices.size() > 0) { 196 addPreferencesSorted(locationServices, categoryLocationServices); 197 } else { 198 // If there's no item to display, remove the whole category. 199 root.removePreference(categoryLocationServices); 200 } 201 } 202 203 @Override 204 public int getHelpResource() { 205 return R.string.help_url_location_access; 206 } 207 208 @Override 209 public void onModeChanged(int mode, boolean restricted) { 210 switch (mode) { 211 case Settings.Secure.LOCATION_MODE_OFF: 212 mLocationMode.setSummary(R.string.location_mode_location_off_title); 213 break; 214 case Settings.Secure.LOCATION_MODE_SENSORS_ONLY: 215 mLocationMode.setSummary(R.string.location_mode_sensors_only_title); 216 break; 217 case Settings.Secure.LOCATION_MODE_BATTERY_SAVING: 218 mLocationMode.setSummary(R.string.location_mode_battery_saving_title); 219 break; 220 case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY: 221 mLocationMode.setSummary(R.string.location_mode_high_accuracy_title); 222 break; 223 default: 224 break; 225 } 226 227 // Restricted user can't change the location mode, so disable the master switch. But in some 228 // corner cases, the location might still be enabled. In such case the master switch should 229 // be disabled but checked. 230 boolean enabled = (mode != Settings.Secure.LOCATION_MODE_OFF); 231 mSwitch.setEnabled(!restricted); 232 mLocationMode.setEnabled(enabled && !restricted); 233 mCategoryRecentLocationRequests.setEnabled(enabled); 234 235 if (enabled != mSwitch.isChecked()) { 236 // set listener to null so that that code below doesn't trigger onCheckedChanged() 237 if (mValidListener) { 238 mSwitch.setOnCheckedChangeListener(null); 239 } 240 mSwitch.setChecked(enabled); 241 if (mValidListener) { 242 mSwitch.setOnCheckedChangeListener(this); 243 } 244 } 245 } 246 247 /** 248 * Listens to the state change of the location master switch. 249 */ 250 @Override 251 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 252 if (isChecked) { 253 setLocationMode(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); 254 } else { 255 setLocationMode(Settings.Secure.LOCATION_MODE_OFF); 256 } 257 } 258} 259