| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.location; |
| |
| import android.app.Activity; |
| import android.app.admin.DevicePolicyManager; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.location.SettingInjectorService; |
| import android.os.Bundle; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.provider.Settings; |
| import android.support.v7.preference.Preference; |
| import android.support.v7.preference.PreferenceCategory; |
| import android.support.v7.preference.PreferenceGroup; |
| import android.support.v7.preference.PreferenceScreen; |
| import android.util.Log; |
| import android.view.Menu; |
| import android.view.MenuInflater; |
| import android.view.MenuItem; |
| import android.widget.Switch; |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| import com.android.settings.DimmableIconPreference; |
| import com.android.settings.R; |
| import com.android.settings.SettingsActivity; |
| import com.android.settings.Utils; |
| import com.android.settings.applications.InstalledAppDetails; |
| import com.android.settings.dashboard.SummaryLoader; |
| import com.android.settings.widget.SwitchBar; |
| import com.android.settingslib.RestrictedLockUtils; |
| import com.android.settingslib.RestrictedSwitchPreference; |
| import com.android.settingslib.location.RecentLocationApps; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| |
| import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; |
| |
| /** |
| * System location settings (Settings > Location). The screen has three parts: |
| * <ul> |
| * <li>Platform location controls</li> |
| * <ul> |
| * <li>In switch bar: location master switch. Used to toggle |
| * {@link android.provider.Settings.Secure#LOCATION_MODE} between |
| * {@link android.provider.Settings.Secure#LOCATION_MODE_OFF} and another location mode. |
| * </li> |
| * <li>Mode preference: only available if the master switch is on, selects between |
| * {@link android.provider.Settings.Secure#LOCATION_MODE} of |
| * {@link android.provider.Settings.Secure#LOCATION_MODE_HIGH_ACCURACY}, |
| * {@link android.provider.Settings.Secure#LOCATION_MODE_BATTERY_SAVING}, or |
| * {@link android.provider.Settings.Secure#LOCATION_MODE_SENSORS_ONLY}.</li> |
| * </ul> |
| * <li>Recent location requests: automatically populated by {@link RecentLocationApps}</li> |
| * <li>Location services: multi-app settings provided from outside the Android framework. Each |
| * is injected by a system-partition app via the {@link SettingInjectorService} API.</li> |
| * </ul> |
| * <p> |
| * Note that as of KitKat, the {@link SettingInjectorService} is the preferred method for OEMs to |
| * add their own settings to this page, rather than directly modifying the framework code. Among |
| * other things, this simplifies integration with future changes to the default (AOSP) |
| * implementation. |
| */ |
| public class LocationSettings extends LocationSettingsBase |
| implements SwitchBar.OnSwitchChangeListener { |
| |
| private static final String TAG = "LocationSettings"; |
| |
| /** |
| * Key for managed profile location switch preference. Shown only |
| * if there is a managed profile. |
| */ |
| private static final String KEY_MANAGED_PROFILE_SWITCH = "managed_profile_location_switch"; |
| /** Key for preference screen "Mode" */ |
| private static final String KEY_LOCATION_MODE = "location_mode"; |
| /** Key for preference category "Recent location requests" */ |
| private static final String KEY_RECENT_LOCATION_REQUESTS = "recent_location_requests"; |
| /** Key for preference category "Location services" */ |
| private static final String KEY_LOCATION_SERVICES = "location_services"; |
| |
| private static final int MENU_SCANNING = Menu.FIRST; |
| |
| private SwitchBar mSwitchBar; |
| private Switch mSwitch; |
| private boolean mValidListener = false; |
| private UserHandle mManagedProfile; |
| private RestrictedSwitchPreference mManagedProfileSwitch; |
| private Preference mLocationMode; |
| private PreferenceCategory mCategoryRecentLocationRequests; |
| /** Receives UPDATE_INTENT */ |
| private BroadcastReceiver mReceiver; |
| private SettingsInjector injector; |
| private UserManager mUm; |
| |
| @Override |
| public int getMetricsCategory() { |
| return MetricsEvent.LOCATION; |
| } |
| |
| @Override |
| public void onActivityCreated(Bundle savedInstanceState) { |
| super.onActivityCreated(savedInstanceState); |
| |
| final SettingsActivity activity = (SettingsActivity) getActivity(); |
| mUm = (UserManager) activity.getSystemService(Context.USER_SERVICE); |
| |
| setHasOptionsMenu(true); |
| mSwitchBar = activity.getSwitchBar(); |
| mSwitch = mSwitchBar.getSwitch(); |
| mSwitchBar.show(); |
| |
| setHasOptionsMenu(true); |
| } |
| |
| @Override |
| public void onDestroyView() { |
| super.onDestroyView(); |
| mSwitchBar.hide(); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| createPreferenceHierarchy(); |
| if (!mValidListener) { |
| mSwitchBar.addOnSwitchChangeListener(this); |
| mValidListener = true; |
| } |
| } |
| |
| @Override |
| public void onPause() { |
| try { |
| getActivity().unregisterReceiver(mReceiver); |
| } catch (RuntimeException e) { |
| // Ignore exceptions caused by race condition |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "Swallowing " + e); |
| } |
| } |
| if (mValidListener) { |
| mSwitchBar.removeOnSwitchChangeListener(this); |
| mValidListener = false; |
| } |
| super.onPause(); |
| } |
| |
| private void addPreferencesSorted(List<Preference> prefs, PreferenceGroup container) { |
| // If there's some items to display, sort the items and add them to the container. |
| Collections.sort(prefs, new Comparator<Preference>() { |
| @Override |
| public int compare(Preference lhs, Preference rhs) { |
| return lhs.getTitle().toString().compareTo(rhs.getTitle().toString()); |
| } |
| }); |
| for (Preference entry : prefs) { |
| container.addPreference(entry); |
| } |
| } |
| |
| private PreferenceScreen createPreferenceHierarchy() { |
| final SettingsActivity activity = (SettingsActivity) getActivity(); |
| PreferenceScreen root = getPreferenceScreen(); |
| if (root != null) { |
| root.removeAll(); |
| } |
| addPreferencesFromResource(R.xml.location_settings); |
| root = getPreferenceScreen(); |
| |
| setupManagedProfileCategory(root); |
| mLocationMode = root.findPreference(KEY_LOCATION_MODE); |
| mLocationMode.setOnPreferenceClickListener( |
| new Preference.OnPreferenceClickListener() { |
| @Override |
| public boolean onPreferenceClick(Preference preference) { |
| activity.startPreferencePanel( |
| LocationMode.class.getName(), null, |
| R.string.location_mode_screen_title, null, LocationSettings.this, |
| 0); |
| return true; |
| } |
| }); |
| |
| mCategoryRecentLocationRequests = |
| (PreferenceCategory) root.findPreference(KEY_RECENT_LOCATION_REQUESTS); |
| RecentLocationApps recentApps = new RecentLocationApps(activity); |
| List<RecentLocationApps.Request> recentLocationRequests = recentApps.getAppList(); |
| List<Preference> recentLocationPrefs = new ArrayList<>(recentLocationRequests.size()); |
| for (final RecentLocationApps.Request request : recentLocationRequests) { |
| DimmableIconPreference pref = new DimmableIconPreference(getPrefContext(), |
| request.contentDescription); |
| pref.setIcon(request.icon); |
| pref.setTitle(request.label); |
| if (request.isHighBattery) { |
| pref.setSummary(R.string.location_high_battery_use); |
| } else { |
| pref.setSummary(R.string.location_low_battery_use); |
| } |
| pref.setOnPreferenceClickListener( |
| new PackageEntryClickedListener(request.packageName, request.userHandle)); |
| recentLocationPrefs.add(pref); |
| |
| } |
| if (recentLocationRequests.size() > 0) { |
| addPreferencesSorted(recentLocationPrefs, mCategoryRecentLocationRequests); |
| } else { |
| // If there's no item to display, add a "No recent apps" item. |
| Preference banner = new Preference(getPrefContext()); |
| banner.setLayoutResource(R.layout.location_list_no_item); |
| banner.setTitle(R.string.location_no_recent_apps); |
| banner.setSelectable(false); |
| mCategoryRecentLocationRequests.addPreference(banner); |
| } |
| |
| boolean lockdownOnLocationAccess = false; |
| // Checking if device policy has put a location access lock-down on the managed |
| // profile. If managed profile has lock-down on location access then its |
| // injected location services must not be shown. |
| if (mManagedProfile != null |
| && mUm.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile)) { |
| lockdownOnLocationAccess = true; |
| } |
| addLocationServices(activity, root, lockdownOnLocationAccess); |
| |
| refreshLocationMode(); |
| return root; |
| } |
| |
| private void setupManagedProfileCategory(PreferenceScreen root) { |
| // Looking for a managed profile. If there are no managed profiles then we are removing the |
| // managed profile category. |
| mManagedProfile = Utils.getManagedProfile(mUm); |
| if (mManagedProfile == null) { |
| // There is no managed profile |
| root.removePreference(root.findPreference(KEY_MANAGED_PROFILE_SWITCH)); |
| mManagedProfileSwitch = null; |
| } else { |
| mManagedProfileSwitch = (RestrictedSwitchPreference)root |
| .findPreference(KEY_MANAGED_PROFILE_SWITCH); |
| mManagedProfileSwitch.setOnPreferenceClickListener(null); |
| } |
| } |
| |
| private void changeManagedProfileLocationAccessStatus(boolean mainSwitchOn) { |
| if (mManagedProfileSwitch == null) { |
| return; |
| } |
| mManagedProfileSwitch.setOnPreferenceClickListener(null); |
| final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(), |
| UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile.getIdentifier()); |
| final boolean isRestrictedByBase = isManagedProfileRestrictedByBase(); |
| if (!isRestrictedByBase && admin != null) { |
| mManagedProfileSwitch.setDisabledByAdmin(admin); |
| mManagedProfileSwitch.setChecked(false); |
| } else { |
| boolean enabled = mainSwitchOn; |
| mManagedProfileSwitch.setEnabled(enabled); |
| |
| int summaryResId = R.string.switch_off_text; |
| if (!enabled) { |
| mManagedProfileSwitch.setChecked(false); |
| } else { |
| mManagedProfileSwitch.setChecked(!isRestrictedByBase); |
| summaryResId = (isRestrictedByBase ? |
| R.string.switch_off_text : R.string.switch_on_text); |
| mManagedProfileSwitch.setOnPreferenceClickListener( |
| mManagedProfileSwitchClickListener); |
| } |
| mManagedProfileSwitch.setSummary(summaryResId); |
| } |
| } |
| |
| /** |
| * Add the settings injected by external apps into the "App Settings" category. Hides the |
| * category if there are no injected settings. |
| * |
| * Reloads the settings whenever receives |
| * {@link SettingInjectorService#ACTION_INJECTED_SETTING_CHANGED}. |
| */ |
| private void addLocationServices(Context context, PreferenceScreen root, |
| boolean lockdownOnLocationAccess) { |
| PreferenceCategory categoryLocationServices = |
| (PreferenceCategory) root.findPreference(KEY_LOCATION_SERVICES); |
| injector = new SettingsInjector(context); |
| // If location access is locked down by device policy then we only show injected settings |
| // for the primary profile. |
| List<Preference> locationServices = injector.getInjectedSettings(lockdownOnLocationAccess ? |
| UserHandle.myUserId() : UserHandle.USER_CURRENT); |
| |
| mReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "Received settings change intent: " + intent); |
| } |
| injector.reloadStatusMessages(); |
| } |
| }; |
| |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED); |
| context.registerReceiver(mReceiver, filter); |
| |
| if (locationServices.size() > 0) { |
| addPreferencesSorted(locationServices, categoryLocationServices); |
| } else { |
| // If there's no item to display, remove the whole category. |
| root.removePreference(categoryLocationServices); |
| } |
| } |
| |
| @Override |
| public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { |
| menu.add(0, MENU_SCANNING, 0, R.string.location_menu_scanning); |
| // The super class adds "Help & Feedback" menu item. |
| super.onCreateOptionsMenu(menu, inflater); |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| final SettingsActivity activity = (SettingsActivity) getActivity(); |
| switch (item.getItemId()) { |
| case MENU_SCANNING: |
| activity.startPreferencePanel( |
| ScanningSettings.class.getName(), null, |
| R.string.location_scanning_screen_title, null, LocationSettings.this, |
| 0); |
| return true; |
| default: |
| return super.onOptionsItemSelected(item); |
| } |
| } |
| |
| @Override |
| public int getHelpResource() { |
| return R.string.help_url_location_access; |
| } |
| |
| private static int getLocationString(int mode) { |
| switch (mode) { |
| case android.provider.Settings.Secure.LOCATION_MODE_OFF: |
| return R.string.location_mode_location_off_title; |
| case android.provider.Settings.Secure.LOCATION_MODE_SENSORS_ONLY: |
| return R.string.location_mode_sensors_only_title; |
| case android.provider.Settings.Secure.LOCATION_MODE_BATTERY_SAVING: |
| return R.string.location_mode_battery_saving_title; |
| case android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY: |
| return R.string.location_mode_high_accuracy_title; |
| } |
| return 0; |
| } |
| |
| @Override |
| public void onModeChanged(int mode, boolean restricted) { |
| int modeDescription = getLocationString(mode); |
| if (modeDescription != 0) { |
| mLocationMode.setSummary(modeDescription); |
| } |
| |
| // Restricted user can't change the location mode, so disable the master switch. But in some |
| // corner cases, the location might still be enabled. In such case the master switch should |
| // be disabled but checked. |
| final boolean enabled = (mode != android.provider.Settings.Secure.LOCATION_MODE_OFF); |
| EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(), |
| UserManager.DISALLOW_SHARE_LOCATION, UserHandle.myUserId()); |
| boolean hasBaseUserRestriction = RestrictedLockUtils.hasBaseUserRestriction(getActivity(), |
| UserManager.DISALLOW_SHARE_LOCATION, UserHandle.myUserId()); |
| // Disable the whole switch bar instead of the switch itself. If we disabled the switch |
| // only, it would be re-enabled again if the switch bar is not disabled. |
| if (!hasBaseUserRestriction && admin != null) { |
| mSwitchBar.setDisabledByAdmin(admin); |
| } else { |
| mSwitchBar.setEnabled(!restricted); |
| } |
| mLocationMode.setEnabled(enabled && !restricted); |
| mCategoryRecentLocationRequests.setEnabled(enabled); |
| |
| if (enabled != mSwitch.isChecked()) { |
| // set listener to null so that that code below doesn't trigger onCheckedChanged() |
| if (mValidListener) { |
| mSwitchBar.removeOnSwitchChangeListener(this); |
| } |
| mSwitch.setChecked(enabled); |
| if (mValidListener) { |
| mSwitchBar.addOnSwitchChangeListener(this); |
| } |
| } |
| |
| changeManagedProfileLocationAccessStatus(enabled); |
| |
| // As a safety measure, also reloads on location mode change to ensure the settings are |
| // up-to-date even if an affected app doesn't send the setting changed broadcast. |
| injector.reloadStatusMessages(); |
| } |
| |
| /** |
| * Listens to the state change of the location master switch. |
| */ |
| @Override |
| public void onSwitchChanged(Switch switchView, boolean isChecked) { |
| if (isChecked) { |
| setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_PREVIOUS); |
| } else { |
| setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_OFF); |
| } |
| } |
| |
| private boolean isManagedProfileRestrictedByBase() { |
| if (mManagedProfile == null) { |
| return false; |
| } |
| return mUm.hasBaseUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, mManagedProfile); |
| } |
| |
| private Preference.OnPreferenceClickListener mManagedProfileSwitchClickListener = |
| new Preference.OnPreferenceClickListener() { |
| @Override |
| public boolean onPreferenceClick(Preference preference) { |
| final boolean switchState = mManagedProfileSwitch.isChecked(); |
| mUm.setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, |
| !switchState, mManagedProfile); |
| mManagedProfileSwitch.setSummary(switchState ? |
| R.string.switch_on_text : R.string.switch_off_text); |
| return true; |
| } |
| }; |
| |
| private class PackageEntryClickedListener |
| implements Preference.OnPreferenceClickListener { |
| private String mPackage; |
| private UserHandle mUserHandle; |
| |
| public PackageEntryClickedListener(String packageName, UserHandle userHandle) { |
| mPackage = packageName; |
| mUserHandle = userHandle; |
| } |
| |
| @Override |
| public boolean onPreferenceClick(Preference preference) { |
| // start new fragment to display extended information |
| Bundle args = new Bundle(); |
| args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackage); |
| ((SettingsActivity) getActivity()).startPreferencePanelAsUser( |
| InstalledAppDetails.class.getName(), args, |
| R.string.application_info_label, null, mUserHandle); |
| return true; |
| } |
| } |
| |
| private static class SummaryProvider implements SummaryLoader.SummaryProvider { |
| |
| private final Context mContext; |
| private final SummaryLoader mSummaryLoader; |
| |
| public SummaryProvider(Context context, SummaryLoader summaryLoader) { |
| mContext = context; |
| mSummaryLoader = summaryLoader; |
| } |
| |
| @Override |
| public void setListening(boolean listening) { |
| if (listening) { |
| int mode = Settings.Secure.getInt(mContext.getContentResolver(), |
| Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); |
| if (mode != Settings.Secure.LOCATION_MODE_OFF) { |
| mSummaryLoader.setSummary(this, mContext.getString(R.string.location_on_summary, |
| mContext.getString(getLocationString(mode)))); |
| } else { |
| mSummaryLoader.setSummary(this, |
| mContext.getString(R.string.location_off_summary)); |
| } |
| } |
| } |
| } |
| |
| public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY |
| = new SummaryLoader.SummaryProviderFactory() { |
| @Override |
| public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, |
| SummaryLoader summaryLoader) { |
| return new SummaryProvider(activity, summaryLoader); |
| } |
| }; |
| } |