Home | History | Annotate | Download | only in location
      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