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             pref.setOnPreferenceClickListener(
    215                     new PackageEntryClickedListener(request.packageName, request.userHandle));
    216             recentLocationPrefs.add(pref);
    217 
    218         }
    219         if (recentLocationRequests.size() > 0) {
    220             addPreferencesSorted(recentLocationPrefs, mCategoryRecentLocationRequests);
    221         } else {
    222             // If there's no item to display, add a "No recent apps" item.
    223             Preference banner = new Preference(getPrefContext());
    224             banner.setLayoutResource(R.layout.location_list_no_item);
    225             banner.setTitle(R.string.location_no_recent_apps);
    226             banner.setSelectable(false);
    227             mCategoryRecentLocationRequests.addPreference(banner);
    228         }
    229 
    230         boolean lockdownOnLocationAccess = false;
    231         // Checking if device policy has put a location access lock-down on the managed
    232         // profile. If managed profile has lock-down on location access then its
    233         // injected location services must not be shown.
    234         if (mManagedProfile != null
    235                 && mUm.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile)) {
    236             lockdownOnLocationAccess = true;
    237         }
    238         addLocationServices(activity, root, lockdownOnLocationAccess);
    239 
    240         refreshLocationMode();
    241         return root;
    242     }
    243 
    244     private void setupManagedProfileCategory(PreferenceScreen root) {
    245         // Looking for a managed profile. If there are no managed profiles then we are removing the
    246         // managed profile category.
    247         mManagedProfile = Utils.getManagedProfile(mUm);
    248         if (mManagedProfile == null) {
    249             // There is no managed profile
    250             root.removePreference(root.findPreference(KEY_MANAGED_PROFILE_SWITCH));
    251             mManagedProfileSwitch = null;
    252         } else {
    253             mManagedProfileSwitch = (RestrictedSwitchPreference)root
    254                     .findPreference(KEY_MANAGED_PROFILE_SWITCH);
    255             mManagedProfileSwitch.setOnPreferenceClickListener(null);
    256         }
    257     }
    258 
    259     private void changeManagedProfileLocationAccessStatus(boolean mainSwitchOn) {
    260         if (mManagedProfileSwitch == null) {
    261             return;
    262         }
    263         mManagedProfileSwitch.setOnPreferenceClickListener(null);
    264         final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(),
    265                 UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile.getIdentifier());
    266         final boolean isRestrictedByBase = isManagedProfileRestrictedByBase();
    267         if (!isRestrictedByBase && admin != null) {
    268             mManagedProfileSwitch.setDisabledByAdmin(admin);
    269             mManagedProfileSwitch.setChecked(false);
    270         } else {
    271             boolean enabled = mainSwitchOn;
    272             mManagedProfileSwitch.setEnabled(enabled);
    273 
    274             int summaryResId = R.string.switch_off_text;
    275             if (!enabled) {
    276                 mManagedProfileSwitch.setChecked(false);
    277             } else {
    278                 mManagedProfileSwitch.setChecked(!isRestrictedByBase);
    279                 summaryResId = (isRestrictedByBase ?
    280                         R.string.switch_off_text : R.string.switch_on_text);
    281                 mManagedProfileSwitch.setOnPreferenceClickListener(
    282                         mManagedProfileSwitchClickListener);
    283             }
    284             mManagedProfileSwitch.setSummary(summaryResId);
    285         }
    286     }
    287 
    288     /**
    289      * Add the settings injected by external apps into the "App Settings" category. Hides the
    290      * category if there are no injected settings.
    291      *
    292      * Reloads the settings whenever receives
    293      * {@link SettingInjectorService#ACTION_INJECTED_SETTING_CHANGED}.
    294      */
    295     private void addLocationServices(Context context, PreferenceScreen root,
    296             boolean lockdownOnLocationAccess) {
    297         PreferenceCategory categoryLocationServices =
    298                 (PreferenceCategory) root.findPreference(KEY_LOCATION_SERVICES);
    299         injector = new SettingsInjector(context);
    300         // If location access is locked down by device policy then we only show injected settings
    301         // for the primary profile.
    302         final Context prefContext = categoryLocationServices.getContext();
    303         final List<Preference> locationServices = injector.getInjectedSettings(prefContext,
    304                 lockdownOnLocationAccess ? UserHandle.myUserId() : UserHandle.USER_CURRENT);
    305 
    306         mReceiver = new BroadcastReceiver() {
    307             @Override
    308             public void onReceive(Context context, Intent intent) {
    309                 if (Log.isLoggable(TAG, Log.DEBUG)) {
    310                     Log.d(TAG, "Received settings change intent: " + intent);
    311                 }
    312                 injector.reloadStatusMessages();
    313             }
    314         };
    315 
    316         IntentFilter filter = new IntentFilter();
    317         filter.addAction(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED);
    318         context.registerReceiver(mReceiver, filter);
    319 
    320         if (locationServices.size() > 0) {
    321             addPreferencesSorted(locationServices, categoryLocationServices);
    322         } else {
    323             // If there's no item to display, remove the whole category.
    324             root.removePreference(categoryLocationServices);
    325         }
    326     }
    327 
    328     @Override
    329     public int getHelpResource() {
    330         return R.string.help_url_location_access;
    331     }
    332 
    333     @Override
    334     public void onModeChanged(int mode, boolean restricted) {
    335         int modeDescription = LocationPreferenceController.getLocationString(mode);
    336         if (modeDescription != 0) {
    337             mLocationMode.setSummary(modeDescription);
    338         }
    339 
    340         // Restricted user can't change the location mode, so disable the master switch. But in some
    341         // corner cases, the location might still be enabled. In such case the master switch should
    342         // be disabled but checked.
    343         final boolean enabled = (mode != android.provider.Settings.Secure.LOCATION_MODE_OFF);
    344         EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(),
    345                 UserManager.DISALLOW_SHARE_LOCATION, UserHandle.myUserId());
    346         boolean hasBaseUserRestriction = RestrictedLockUtils.hasBaseUserRestriction(getActivity(),
    347                 UserManager.DISALLOW_SHARE_LOCATION, UserHandle.myUserId());
    348         // Disable the whole switch bar instead of the switch itself. If we disabled the switch
    349         // only, it would be re-enabled again if the switch bar is not disabled.
    350         if (!hasBaseUserRestriction && admin != null) {
    351             mSwitchBar.setDisabledByAdmin(admin);
    352         } else {
    353             mSwitchBar.setEnabled(!restricted);
    354         }
    355         mLocationMode.setEnabled(enabled && !restricted);
    356         mCategoryRecentLocationRequests.setEnabled(enabled);
    357 
    358         if (enabled != mSwitch.isChecked()) {
    359             // set listener to null so that that code below doesn't trigger onCheckedChanged()
    360             if (mValidListener) {
    361                 mSwitchBar.removeOnSwitchChangeListener(this);
    362             }
    363             mSwitch.setChecked(enabled);
    364             if (mValidListener) {
    365                 mSwitchBar.addOnSwitchChangeListener(this);
    366             }
    367         }
    368 
    369         changeManagedProfileLocationAccessStatus(enabled);
    370 
    371         // As a safety measure, also reloads on location mode change to ensure the settings are
    372         // up-to-date even if an affected app doesn't send the setting changed broadcast.
    373         injector.reloadStatusMessages();
    374     }
    375 
    376     /**
    377      * Listens to the state change of the location master switch.
    378      */
    379     @Override
    380     public void onSwitchChanged(Switch switchView, boolean isChecked) {
    381         if (isChecked) {
    382             setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_PREVIOUS);
    383         } else {
    384             setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_OFF);
    385         }
    386     }
    387 
    388     private boolean isManagedProfileRestrictedByBase() {
    389         if (mManagedProfile == null) {
    390             return false;
    391         }
    392         return mUm.hasBaseUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile);
    393     }
    394 
    395     private Preference.OnPreferenceClickListener mManagedProfileSwitchClickListener =
    396             new Preference.OnPreferenceClickListener() {
    397                 @Override
    398                 public boolean onPreferenceClick(Preference preference) {
    399                     final boolean switchState = mManagedProfileSwitch.isChecked();
    400                     mUm.setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION,
    401                             !switchState, mManagedProfile);
    402                     mManagedProfileSwitch.setSummary(switchState ?
    403                             R.string.switch_on_text : R.string.switch_off_text);
    404                     return true;
    405                 }
    406             };
    407 
    408     private class PackageEntryClickedListener
    409             implements Preference.OnPreferenceClickListener {
    410         private String mPackage;
    411         private UserHandle mUserHandle;
    412 
    413         public PackageEntryClickedListener(String packageName, UserHandle userHandle) {
    414             mPackage = packageName;
    415             mUserHandle = userHandle;
    416         }
    417 
    418         @Override
    419         public boolean onPreferenceClick(Preference preference) {
    420             // start new fragment to display extended information
    421             Bundle args = new Bundle();
    422             args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackage);
    423             ((SettingsActivity) getActivity()).startPreferencePanelAsUser(
    424                     LocationSettings.this,
    425                     InstalledAppDetails.class.getName(), args,
    426                     R.string.application_info_label, null, mUserHandle);
    427             return true;
    428         }
    429     }
    430 
    431     private static class SummaryProvider implements SummaryLoader.SummaryProvider {
    432 
    433         private final Context mContext;
    434         private final SummaryLoader mSummaryLoader;
    435 
    436         public SummaryProvider(Context context, SummaryLoader summaryLoader) {
    437             mContext = context;
    438             mSummaryLoader = summaryLoader;
    439         }
    440 
    441         @Override
    442         public void setListening(boolean listening) {
    443             if (listening) {
    444                 mSummaryLoader.setSummary(
    445                     this, LocationPreferenceController.getLocationSummary(mContext));
    446             }
    447         }
    448     }
    449 
    450     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
    451             = new SummaryLoader.SummaryProviderFactory() {
    452         @Override
    453         public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
    454                                                                    SummaryLoader summaryLoader) {
    455             return new SummaryProvider(activity, summaryLoader);
    456         }
    457     };
    458 }
    459