OSDN Git Service

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