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 static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
     20 
     21 import android.app.Activity;
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.location.SettingInjectorService;
     27 import android.os.Bundle;
     28 import android.os.UserHandle;
     29 import android.os.UserManager;
     30 import android.support.v7.preference.Preference;
     31 import android.support.v7.preference.PreferenceCategory;
     32 import android.support.v7.preference.PreferenceGroup;
     33 import android.support.v7.preference.PreferenceScreen;
     34 import android.util.Log;
     35 import android.widget.Switch;
     36 
     37 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     38 import com.android.settings.DimmableIconPreference;
     39 import com.android.settings.R;
     40 import com.android.settings.SettingsActivity;
     41 import com.android.settings.Utils;
     42 import com.android.settings.applications.InstalledAppDetails;
     43 import com.android.settings.dashboard.SummaryLoader;
     44 import com.android.settings.widget.SwitchBar;
     45 import com.android.settingslib.RestrictedLockUtils;
     46 import com.android.settingslib.RestrictedSwitchPreference;
     47 import com.android.settingslib.location.RecentLocationApps;
     48 
     49 import java.util.ArrayList;
     50 import java.util.Collections;
     51 import java.util.Comparator;
     52 import java.util.List;
     53 
     54 /**
     55  * System location settings (Settings > Location). The screen has three parts:
     56  * <ul>
     57  *     <li>Platform location controls</li>
     58  *     <ul>
     59  *         <li>In switch bar: location master switch. Used to toggle
     60  *         {@link android.provider.Settings.Secure#LOCATION_MODE} between
     61  *         {@link android.provider.Settings.Secure#LOCATION_MODE_OFF} and another location mode.
     62  *         </li>
     63  *         <li>Mode preference: only available if the master switch is on, selects between
     64  *         {@link android.provider.Settings.Secure#LOCATION_MODE} of
     65  *         {@link android.provider.Settings.Secure#LOCATION_MODE_HIGH_ACCURACY},
     66  *         {@link android.provider.Settings.Secure#LOCATION_MODE_BATTERY_SAVING}, or
     67  *         {@link android.provider.Settings.Secure#LOCATION_MODE_SENSORS_ONLY}.</li>
     68  *     </ul>
     69  *     <li>Recent location requests: automatically populated by {@link RecentLocationApps}</li>
     70  *     <li>Location services: multi-app settings provided from outside the Android framework. Each
     71  *     is injected by a system-partition app via the {@link SettingInjectorService} API.</li>
     72  * </ul>
     73  * <p>
     74  * Note that as of KitKat, the {@link SettingInjectorService} is the preferred method for OEMs to
     75  * add their own settings to this page, rather than directly modifying the framework code. Among
     76  * other things, this simplifies integration with future changes to the default (AOSP)
     77  * implementation.
     78  */
     79 public class LocationSettings extends LocationSettingsBase
     80         implements SwitchBar.OnSwitchChangeListener {
     81 
     82     private static final String TAG = "LocationSettings";
     83 
     84     /**
     85      * Key for managed profile location switch preference. Shown only
     86      * if there is a managed profile.
     87      */
     88     private static final String KEY_MANAGED_PROFILE_SWITCH = "managed_profile_location_switch";
     89     /** Key for preference screen "Mode" */
     90     private static final String KEY_LOCATION_MODE = "location_mode";
     91     /** Key for preference category "Recent location requests" */
     92     private static final String KEY_RECENT_LOCATION_REQUESTS = "recent_location_requests";
     93     /** Key for preference category "Location services" */
     94     private static final String KEY_LOCATION_SERVICES = "location_services";
     95 
     96     private SwitchBar mSwitchBar;
     97     private Switch mSwitch;
     98     private boolean mValidListener = false;
     99     private UserHandle mManagedProfile;
    100     private RestrictedSwitchPreference mManagedProfileSwitch;
    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
    109     public int getMetricsCategory() {
    110         return MetricsEvent.LOCATION;
    111     }
    112 
    113     @Override
    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         setHasOptionsMenu(true);
    121         mSwitchBar = activity.getSwitchBar();
    122         mSwitch = mSwitchBar.getSwitch();
    123         mSwitchBar.show();
    124 
    125         setHasOptionsMenu(true);
    126     }
    127 
    128     @Override
    129     public void onDestroyView() {
    130         super.onDestroyView();
    131         mSwitchBar.hide();
    132     }
    133 
    134     @Override
    135     public void onResume() {
    136         super.onResume();
    137         createPreferenceHierarchy();
    138         if (!mValidListener) {
    139             mSwitchBar.addOnSwitchChangeListener(this);
    140             mValidListener = true;
    141         }
    142     }
    143 
    144     @Override
    145     public void onPause() {
    146         try {
    147             getActivity().unregisterReceiver(mReceiver);
    148         } catch (RuntimeException e) {
    149             // Ignore exceptions caused by race condition
    150             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    151                 Log.v(TAG, "Swallowing " + e);
    152             }
    153         }
    154         if (mValidListener) {
    155             mSwitchBar.removeOnSwitchChangeListener(this);
    156             mValidListener = false;
    157         }
    158         super.onPause();
    159     }
    160 
    161     private void addPreferencesSorted(List<Preference> prefs, PreferenceGroup container) {
    162         // If there's some items to display, sort the items and add them to the container.
    163         Collections.sort(prefs, new Comparator<Preference>() {
    164             @Override
    165             public int compare(Preference lhs, Preference rhs) {
    166                 return lhs.getTitle().toString().compareTo(rhs.getTitle().toString());
    167             }
    168         });
    169         for (Preference entry : prefs) {
    170             container.addPreference(entry);
    171         }
    172     }
    173 
    174     private PreferenceScreen createPreferenceHierarchy() {
    175         final SettingsActivity activity = (SettingsActivity) getActivity();
    176         PreferenceScreen root = getPreferenceScreen();
    177         if (root != null) {
    178             root.removeAll();
    179         }
    180         addPreferencesFromResource(R.xml.location_settings);
    181         root = getPreferenceScreen();
    182 
    183         setupManagedProfileCategory(root);
    184         mLocationMode = root.findPreference(KEY_LOCATION_MODE);
    185         mLocationMode.setOnPreferenceClickListener(
    186                 new Preference.OnPreferenceClickListener() {
    187                     @Override
    188                     public boolean onPreferenceClick(Preference preference) {
    189                         activity.startPreferencePanel(
    190                                 LocationSettings.this,
    191                                 LocationMode.class.getName(), null,
    192                                 R.string.location_mode_screen_title, null, LocationSettings.this,
    193                                 0);
    194                         return true;
    195                     }
    196                 });
    197 
    198         RecentLocationApps recentApps = new RecentLocationApps(activity);
    199         List<RecentLocationApps.Request> recentLocationRequests = recentApps.getAppList();
    200 
    201         final AppLocationPermissionPreferenceController preferenceController =
    202                 new AppLocationPermissionPreferenceController(activity);
    203         preferenceController.displayPreference(root);
    204 
    205         mCategoryRecentLocationRequests =
    206                 (PreferenceCategory) root.findPreference(KEY_RECENT_LOCATION_REQUESTS);
    207 
    208         List<Preference> recentLocationPrefs = new ArrayList<>(recentLocationRequests.size());
    209         for (final RecentLocationApps.Request request : recentLocationRequests) {
    210             DimmableIconPreference pref = new DimmableIconPreference(getPrefContext(),
    211                     request.contentDescription);
    212             pref.setIcon(request.icon);
    213             pref.setTitle(request.label);
    214             if (request.isHighBattery) {
    215                 pref.setSummary(R.string.location_high_battery_use);
    216             } else {
    217                 pref.setSummary(R.string.location_low_battery_use);
    218             }
    219             pref.setOnPreferenceClickListener(
    220                     new PackageEntryClickedListener(request.packageName, request.userHandle));
    221             recentLocationPrefs.add(pref);
    222 
    223         }
    224         if (recentLocationRequests.size() > 0) {
    225             addPreferencesSorted(recentLocationPrefs, mCategoryRecentLocationRequests);
    226         } else {
    227             // If there's no item to display, add a "No recent apps" item.
    228             Preference banner = new Preference(getPrefContext());
    229             banner.setLayoutResource(R.layout.location_list_no_item);
    230             banner.setTitle(R.string.location_no_recent_apps);
    231             banner.setSelectable(false);
    232             mCategoryRecentLocationRequests.addPreference(banner);
    233         }
    234 
    235         boolean lockdownOnLocationAccess = false;
    236         // Checking if device policy has put a location access lock-down on the managed
    237         // profile. If managed profile has lock-down on location access then its
    238         // injected location services must not be shown.
    239         if (mManagedProfile != null
    240                 && mUm.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile)) {
    241             lockdownOnLocationAccess = true;
    242         }
    243         addLocationServices(activity, root, lockdownOnLocationAccess);
    244 
    245         refreshLocationMode();
    246         return root;
    247     }
    248 
    249     private void setupManagedProfileCategory(PreferenceScreen root) {
    250         // Looking for a managed profile. If there are no managed profiles then we are removing the
    251         // managed profile category.
    252         mManagedProfile = Utils.getManagedProfile(mUm);
    253         if (mManagedProfile == null) {
    254             // There is no managed profile
    255             root.removePreference(root.findPreference(KEY_MANAGED_PROFILE_SWITCH));
    256             mManagedProfileSwitch = null;
    257         } else {
    258             mManagedProfileSwitch = (RestrictedSwitchPreference)root
    259                     .findPreference(KEY_MANAGED_PROFILE_SWITCH);
    260             mManagedProfileSwitch.setOnPreferenceClickListener(null);
    261         }
    262     }
    263 
    264     private void changeManagedProfileLocationAccessStatus(boolean mainSwitchOn) {
    265         if (mManagedProfileSwitch == null) {
    266             return;
    267         }
    268         mManagedProfileSwitch.setOnPreferenceClickListener(null);
    269         final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(),
    270                 UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile.getIdentifier());
    271         final boolean isRestrictedByBase = isManagedProfileRestrictedByBase();
    272         if (!isRestrictedByBase && admin != null) {
    273             mManagedProfileSwitch.setDisabledByAdmin(admin);
    274             mManagedProfileSwitch.setChecked(false);
    275         } else {
    276             boolean enabled = mainSwitchOn;
    277             mManagedProfileSwitch.setEnabled(enabled);
    278 
    279             int summaryResId = R.string.switch_off_text;
    280             if (!enabled) {
    281                 mManagedProfileSwitch.setChecked(false);
    282             } else {
    283                 mManagedProfileSwitch.setChecked(!isRestrictedByBase);
    284                 summaryResId = (isRestrictedByBase ?
    285                         R.string.switch_off_text : R.string.switch_on_text);
    286                 mManagedProfileSwitch.setOnPreferenceClickListener(
    287                         mManagedProfileSwitchClickListener);
    288             }
    289             mManagedProfileSwitch.setSummary(summaryResId);
    290         }
    291     }
    292 
    293     /**
    294      * Add the settings injected by external apps into the "App Settings" category. Hides the
    295      * category if there are no injected settings.
    296      *
    297      * Reloads the settings whenever receives
    298      * {@link SettingInjectorService#ACTION_INJECTED_SETTING_CHANGED}.
    299      */
    300     private void addLocationServices(Context context, PreferenceScreen root,
    301             boolean lockdownOnLocationAccess) {
    302         PreferenceCategory categoryLocationServices =
    303                 (PreferenceCategory) root.findPreference(KEY_LOCATION_SERVICES);
    304         injector = new SettingsInjector(context);
    305         // If location access is locked down by device policy then we only show injected settings
    306         // for the primary profile.
    307         final Context prefContext = categoryLocationServices.getContext();
    308         final List<Preference> locationServices = injector.getInjectedSettings(prefContext,
    309                 lockdownOnLocationAccess ? UserHandle.myUserId() : UserHandle.USER_CURRENT);
    310 
    311         mReceiver = new BroadcastReceiver() {
    312             @Override
    313             public void onReceive(Context context, Intent intent) {
    314                 if (Log.isLoggable(TAG, Log.DEBUG)) {
    315                     Log.d(TAG, "Received settings change intent: " + intent);
    316                 }
    317                 injector.reloadStatusMessages();
    318             }
    319         };
    320 
    321         IntentFilter filter = new IntentFilter();
    322         filter.addAction(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED);
    323         context.registerReceiver(mReceiver, filter);
    324 
    325         if (locationServices.size() > 0) {
    326             addPreferencesSorted(locationServices, categoryLocationServices);
    327         } else {
    328             // If there's no item to display, remove the whole category.
    329             root.removePreference(categoryLocationServices);
    330         }
    331     }
    332 
    333     @Override
    334     public int getHelpResource() {
    335         return R.string.help_url_location_access;
    336     }
    337 
    338     @Override
    339     public void onModeChanged(int mode, boolean restricted) {
    340         int modeDescription = LocationPreferenceController.getLocationString(mode);
    341         if (modeDescription != 0) {
    342             mLocationMode.setSummary(modeDescription);
    343         }
    344 
    345         // Restricted user can't change the location mode, so disable the master switch. But in some
    346         // corner cases, the location might still be enabled. In such case the master switch should
    347         // be disabled but checked.
    348         final boolean enabled = (mode != android.provider.Settings.Secure.LOCATION_MODE_OFF);
    349         EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(),
    350                 UserManager.DISALLOW_SHARE_LOCATION, UserHandle.myUserId());
    351         boolean hasBaseUserRestriction = RestrictedLockUtils.hasBaseUserRestriction(getActivity(),
    352                 UserManager.DISALLOW_SHARE_LOCATION, UserHandle.myUserId());
    353         // Disable the whole switch bar instead of the switch itself. If we disabled the switch
    354         // only, it would be re-enabled again if the switch bar is not disabled.
    355         if (!hasBaseUserRestriction && admin != null) {
    356             mSwitchBar.setDisabledByAdmin(admin);
    357         } else {
    358             mSwitchBar.setEnabled(!restricted);
    359         }
    360         mLocationMode.setEnabled(enabled && !restricted);
    361         mCategoryRecentLocationRequests.setEnabled(enabled);
    362 
    363         if (enabled != mSwitch.isChecked()) {
    364             // set listener to null so that that code below doesn't trigger onCheckedChanged()
    365             if (mValidListener) {
    366                 mSwitchBar.removeOnSwitchChangeListener(this);
    367             }
    368             mSwitch.setChecked(enabled);
    369             if (mValidListener) {
    370                 mSwitchBar.addOnSwitchChangeListener(this);
    371             }
    372         }
    373 
    374         changeManagedProfileLocationAccessStatus(enabled);
    375 
    376         // As a safety measure, also reloads on location mode change to ensure the settings are
    377         // up-to-date even if an affected app doesn't send the setting changed broadcast.
    378         injector.reloadStatusMessages();
    379     }
    380 
    381     /**
    382      * Listens to the state change of the location master switch.
    383      */
    384     @Override
    385     public void onSwitchChanged(Switch switchView, boolean isChecked) {
    386         if (isChecked) {
    387             setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_PREVIOUS);
    388         } else {
    389             setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_OFF);
    390         }
    391     }
    392 
    393     private boolean isManagedProfileRestrictedByBase() {
    394         if (mManagedProfile == null) {
    395             return false;
    396         }
    397         return mUm.hasBaseUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile);
    398     }
    399 
    400     private Preference.OnPreferenceClickListener mManagedProfileSwitchClickListener =
    401             new Preference.OnPreferenceClickListener() {
    402                 @Override
    403                 public boolean onPreferenceClick(Preference preference) {
    404                     final boolean switchState = mManagedProfileSwitch.isChecked();
    405                     mUm.setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION,
    406                             !switchState, mManagedProfile);
    407                     mManagedProfileSwitch.setSummary(switchState ?
    408                             R.string.switch_on_text : R.string.switch_off_text);
    409                     return true;
    410                 }
    411             };
    412 
    413     private class PackageEntryClickedListener
    414             implements Preference.OnPreferenceClickListener {
    415         private String mPackage;
    416         private UserHandle mUserHandle;
    417 
    418         public PackageEntryClickedListener(String packageName, UserHandle userHandle) {
    419             mPackage = packageName;
    420             mUserHandle = userHandle;
    421         }
    422 
    423         @Override
    424         public boolean onPreferenceClick(Preference preference) {
    425             // start new fragment to display extended information
    426             Bundle args = new Bundle();
    427             args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackage);
    428             ((SettingsActivity) getActivity()).startPreferencePanelAsUser(
    429                     LocationSettings.this,
    430                     InstalledAppDetails.class.getName(), args,
    431                     R.string.application_info_label, null, mUserHandle);
    432             return true;
    433         }
    434     }
    435 
    436     private static class SummaryProvider implements SummaryLoader.SummaryProvider {
    437 
    438         private final Context mContext;
    439         private final SummaryLoader mSummaryLoader;
    440 
    441         public SummaryProvider(Context context, SummaryLoader summaryLoader) {
    442             mContext = context;
    443             mSummaryLoader = summaryLoader;
    444         }
    445 
    446         @Override
    447         public void setListening(boolean listening) {
    448             if (listening) {
    449                 mSummaryLoader.setSummary(
    450                     this, LocationPreferenceController.getLocationSummary(mContext));
    451             }
    452         }
    453     }
    454 
    455     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
    456             = new SummaryLoader.SummaryProviderFactory() {
    457         @Override
    458         public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
    459                                                                    SummaryLoader summaryLoader) {
    460             return new SummaryProvider(activity, summaryLoader);
    461         }
    462     };
    463 }
    464