• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
getMetricsCategory()109     public int getMetricsCategory() {
110         return MetricsEvent.LOCATION;
111     }
112 
113     @Override
onActivityCreated(Bundle savedInstanceState)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
onDestroyView()129     public void onDestroyView() {
130         super.onDestroyView();
131         mSwitchBar.hide();
132     }
133 
134     @Override
onResume()135     public void onResume() {
136         super.onResume();
137         createPreferenceHierarchy();
138         if (!mValidListener) {
139             mSwitchBar.addOnSwitchChangeListener(this);
140             mValidListener = true;
141         }
142     }
143 
144     @Override
onPause()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 
addPreferencesSorted(List<Preference> prefs, PreferenceGroup container)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 
createPreferenceHierarchy()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 
setupManagedProfileCategory(PreferenceScreen root)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 
changeManagedProfileLocationAccessStatus(boolean mainSwitchOn)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      */
addLocationServices(Context context, PreferenceScreen root, boolean lockdownOnLocationAccess)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
getHelpResource()329     public int getHelpResource() {
330         return R.string.help_url_location_access;
331     }
332 
333     @Override
onModeChanged(int mode, boolean restricted)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
onSwitchChanged(Switch switchView, boolean isChecked)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 
isManagedProfileRestrictedByBase()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 
PackageEntryClickedListener(String packageName, UserHandle userHandle)413         public PackageEntryClickedListener(String packageName, UserHandle userHandle) {
414             mPackage = packageName;
415             mUserHandle = userHandle;
416         }
417 
418         @Override
onPreferenceClick(Preference preference)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 
SummaryProvider(Context context, SummaryLoader summaryLoader)436         public SummaryProvider(Context context, SummaryLoader summaryLoader) {
437             mContext = context;
438             mSummaryLoader = summaryLoader;
439         }
440 
441         @Override
setListening(boolean listening)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