diff --git a/src/com/android/settings/location/AppLocationPermissionPreferenceController.java b/src/com/android/settings/location/AppLocationPermissionPreferenceController.java index 75c9e2f92b2..fabe295feff 100644 --- a/src/com/android/settings/location/AppLocationPermissionPreferenceController.java +++ b/src/com/android/settings/location/AppLocationPermissionPreferenceController.java @@ -2,8 +2,6 @@ package com.android.settings.location; import android.content.Context; import android.provider.Settings; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceScreen; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.AbstractPreferenceController; @@ -12,20 +10,11 @@ public class AppLocationPermissionPreferenceController extends AbstractPreferenceController implements PreferenceControllerMixin { private static final String KEY_APP_LEVEL_PERMISSIONS = "app_level_permissions"; - private Preference mPreference; public AppLocationPermissionPreferenceController(Context context) { super(context); } - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - if (isAvailable()) { - mPreference = screen.findPreference(KEY_APP_LEVEL_PERMISSIONS); - } - } - @Override public String getPreferenceKey() { return KEY_APP_LEVEL_PERMISSIONS; diff --git a/src/com/android/settings/location/BluetoothScanningPreferenceController.java b/src/com/android/settings/location/BluetoothScanningPreferenceController.java new file mode 100644 index 00000000000..c97bc7d8033 --- /dev/null +++ b/src/com/android/settings/location/BluetoothScanningPreferenceController.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.Preference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.AbstractPreferenceController; + +public class BluetoothScanningPreferenceController extends AbstractPreferenceController + implements PreferenceControllerMixin { + + private static final String KEY_BLUETOOTH_SCAN_ALWAYS_AVAILABLE = "bluetooth_always_scanning"; + + public BluetoothScanningPreferenceController(Context context) { + super(context); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return KEY_BLUETOOTH_SCAN_ALWAYS_AVAILABLE; + } + + @Override + public void updateState(Preference preference) { + ((SwitchPreference) preference).setChecked( + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (KEY_BLUETOOTH_SCAN_ALWAYS_AVAILABLE.equals(preference.getKey())) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, + ((SwitchPreference) preference).isChecked() ? 1 : 0); + return true; + } + return false; + } +} diff --git a/src/com/android/settings/location/LocationBasePreferenceController.java b/src/com/android/settings/location/LocationBasePreferenceController.java new file mode 100644 index 00000000000..6cf8626ebcd --- /dev/null +++ b/src/com/android/settings/location/LocationBasePreferenceController.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.os.UserManager; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; + +/** + * A base controller for preferences that listens to location settings change and modifies location + * settings. + */ +public abstract class LocationBasePreferenceController extends AbstractPreferenceController + implements PreferenceControllerMixin, LocationEnabler.LocationModeChangeListener { + + protected final UserManager mUserManager; + protected final LocationEnabler mLocationEnabler; + + public LocationBasePreferenceController(Context context, Lifecycle lifecycle) { + super(context); + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + mLocationEnabler = new LocationEnabler(context, this /* listener */, lifecycle); + } + + @Override + public boolean isAvailable() { + return true; + } + +} diff --git a/src/com/android/settings/location/LocationEnabler.java b/src/com/android/settings/location/LocationEnabler.java new file mode 100644 index 00000000000..0bec6ba1f0d --- /dev/null +++ b/src/com/android/settings/location/LocationEnabler.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2017 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.Manifest.permission; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.location.LocationManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.support.annotation.VisibleForTesting; +import android.util.Log; + +import com.android.settings.Utils; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; + +/** + * A class that listens to location settings change and modifies location settings + * settings. + */ +public class LocationEnabler implements LifecycleObserver, OnResume, OnPause { + + private static final String TAG = "LocationEnabler"; + @VisibleForTesting + static final String MODE_CHANGING_ACTION = + "com.android.settings.location.MODE_CHANGING"; + private static final String CURRENT_MODE_KEY = "CURRENT_MODE"; + private static final String NEW_MODE_KEY = "NEW_MODE"; + @VisibleForTesting + static final IntentFilter INTENT_FILTER_LOCATION_MODE_CHANGED = + new IntentFilter(LocationManager.MODE_CHANGED_ACTION); + + private final Context mContext; + private final UserManager mUserManager; + private final LocationModeChangeListener mListener; + + @VisibleForTesting + BroadcastReceiver mReceiver; + + public interface LocationModeChangeListener { + /** Called when location mode has changed. */ + void onLocationModeChanged(int mode, boolean restricted); + } + + public LocationEnabler(Context context, LocationModeChangeListener listener, + Lifecycle lifecycle) { + mContext = context; + mListener = listener; + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public void onResume() { + if (mReceiver == null) { + mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Received location mode change intent: " + intent); + } + refreshLocationMode(); + } + }; + } + mContext.registerReceiver(mReceiver, INTENT_FILTER_LOCATION_MODE_CHANGED); + refreshLocationMode(); + } + + @Override + public void onPause() { + try { + mContext.unregisterReceiver(mReceiver); + } catch (RuntimeException e) { + // Ignore exceptions caused by race condition + } + } + + void refreshLocationMode() { + final int mode = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); + if (Log.isLoggable(TAG, Log.INFO)) { + Log.i(TAG, "Location mode has been changed"); + } + if (mListener != null) { + mListener.onLocationModeChanged(mode, isRestricted()); + } + } + + void setLocationMode(int mode) { + final int currentMode = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); + if (isRestricted()) { + // Location toggling disabled by user restriction. Read the current location mode to + // update the location master switch. + if (Log.isLoggable(TAG, Log.INFO)) { + Log.i(TAG, "Restricted user, not setting location mode"); + } + if (mListener != null) { + mListener.onLocationModeChanged(currentMode, true); + } + return; + } + + updateLocationMode(currentMode, mode); + refreshLocationMode(); + } + + boolean isEnabled(int mode) { + return mode != Settings.Secure.LOCATION_MODE_OFF && !isRestricted(); + } + + /** + * Checking if device policy has put a location access lock-down on the managed profile. + * + * @return true if device policy has put a location access lock-down on the managed profile + */ + boolean isManagedProfileRestrictedByBase() { + final UserHandle managedProfile = Utils.getManagedProfile(mUserManager); + return managedProfile != null + && hasShareLocationRestriction(managedProfile.getIdentifier()); + } + + RestrictedLockUtils.EnforcedAdmin getShareLocationEnforcedAdmin(int userId) { + return RestrictedLockUtils.checkIfRestrictionEnforced( + mContext, UserManager.DISALLOW_SHARE_LOCATION, userId); + } + + boolean hasShareLocationRestriction(int userId) { + return RestrictedLockUtils.hasBaseUserRestriction( + mContext, UserManager.DISALLOW_SHARE_LOCATION, userId); + } + + private boolean isRestricted() { + return mUserManager.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION); + } + + private boolean updateLocationMode(int oldMode, int newMode) { + final Intent intent = new Intent(MODE_CHANGING_ACTION); + intent.putExtra(CURRENT_MODE_KEY, oldMode); + intent.putExtra(NEW_MODE_KEY, newMode); + mContext.sendBroadcast(intent, permission.WRITE_SECURE_SETTINGS); + return Settings.Secure.putInt( + mContext.getContentResolver(), Settings.Secure.LOCATION_MODE, newMode); + } +} diff --git a/src/com/android/settings/location/LocationForWorkPreferenceController.java b/src/com/android/settings/location/LocationForWorkPreferenceController.java new file mode 100644 index 00000000000..a81c9d49268 --- /dev/null +++ b/src/com/android/settings/location/LocationForWorkPreferenceController.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.os.UserManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class LocationForWorkPreferenceController extends LocationBasePreferenceController { + + /** + * 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"; + + private RestrictedSwitchPreference mPreference; + + public LocationForWorkPreferenceController(Context context, Lifecycle lifecycle) { + super(context, lifecycle); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (KEY_MANAGED_PROFILE_SWITCH.equals(preference.getKey())) { + final boolean switchState = mPreference.isChecked(); + mUserManager.setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, !switchState, + Utils.getManagedProfile(mUserManager)); + mPreference.setSummary(switchState ? + R.string.switch_on_text : R.string.switch_off_text); + return true; + } + return false; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = + (RestrictedSwitchPreference) screen.findPreference(KEY_MANAGED_PROFILE_SWITCH); + } + + @Override + public boolean isAvailable() { + // Looking for a managed profile. If there are no managed profiles then we are removing the + // managed profile category. + return Utils.getManagedProfile(mUserManager) != null; + } + + @Override + public String getPreferenceKey() { + return KEY_MANAGED_PROFILE_SWITCH; + } + + @Override + public void onLocationModeChanged(int mode, boolean restricted) { + if (!mPreference.isVisible() || !isAvailable()) { + return; + } + final RestrictedLockUtils.EnforcedAdmin admin = + mLocationEnabler.getShareLocationEnforcedAdmin( + Utils.getManagedProfile(mUserManager).getIdentifier()); + final boolean isRestrictedByBase = mLocationEnabler.isManagedProfileRestrictedByBase(); + if (!isRestrictedByBase && admin != null) { + mPreference.setDisabledByAdmin(admin); + mPreference.setChecked(false); + } else { + final boolean enabled = mLocationEnabler.isEnabled(mode); + mPreference.setEnabled(enabled); + + int summaryResId = R.string.switch_off_text; + if (!enabled) { + mPreference.setChecked(false); + } else { + mPreference.setChecked(!isRestrictedByBase); + summaryResId = (isRestrictedByBase ? + R.string.switch_off_text : R.string.switch_on_text); + } + mPreference.setSummary(summaryResId); + } + } +} + diff --git a/src/com/android/settings/location/LocationMode.java b/src/com/android/settings/location/LocationMode.java index 4ca098de22f..34f082b226a 100644 --- a/src/com/android/settings/location/LocationMode.java +++ b/src/com/android/settings/location/LocationMode.java @@ -16,12 +16,20 @@ package com.android.settings.location; -import android.provider.Settings; -import android.support.v7.preference.PreferenceScreen; +import android.content.Context; +import android.provider.SearchIndexableResource; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; -import com.android.settings.widget.RadioButtonPreference; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** * A page with 3 radio buttons to choose the location mode. @@ -34,14 +42,9 @@ import com.android.settings.widget.RadioButtonPreference; * * Sensors only: use GPS location only. */ -public class LocationMode extends LocationSettingsBase - implements RadioButtonPreference.OnClickListener { - private static final String KEY_HIGH_ACCURACY = "high_accuracy"; - private RadioButtonPreference mHighAccuracy; - private static final String KEY_BATTERY_SAVING = "battery_saving"; - private RadioButtonPreference mBatterySaving; - private static final String KEY_SENSORS_ONLY = "sensors_only"; - private RadioButtonPreference mSensorsOnly; +public class LocationMode extends DashboardFragment { + + private static final String TAG = "LocationMode"; @Override public int getMetricsCategory() { @@ -49,95 +52,52 @@ public class LocationMode extends LocationSettingsBase } @Override - public void onResume() { - super.onResume(); - createPreferenceHierarchy(); + protected int getPreferenceScreenResId() { + return R.xml.location_mode; } @Override - public void onPause() { - super.onPause(); - } - - private PreferenceScreen createPreferenceHierarchy() { - PreferenceScreen root = getPreferenceScreen(); - if (root != null) { - root.removeAll(); - } - addPreferencesFromResource(R.xml.location_mode); - root = getPreferenceScreen(); - - mHighAccuracy = (RadioButtonPreference) root.findPreference(KEY_HIGH_ACCURACY); - mBatterySaving = (RadioButtonPreference) root.findPreference(KEY_BATTERY_SAVING); - mSensorsOnly = (RadioButtonPreference) root.findPreference(KEY_SENSORS_ONLY); - mHighAccuracy.setOnClickListener(this); - mBatterySaving.setOnClickListener(this); - mSensorsOnly.setOnClickListener(this); - - refreshLocationMode(); - return root; - } - - private void updateRadioButtons(RadioButtonPreference activated) { - if (activated == null) { - mHighAccuracy.setChecked(false); - mBatterySaving.setChecked(false); - mSensorsOnly.setChecked(false); - } else if (activated == mHighAccuracy) { - mHighAccuracy.setChecked(true); - mBatterySaving.setChecked(false); - mSensorsOnly.setChecked(false); - } else if (activated == mBatterySaving) { - mHighAccuracy.setChecked(false); - mBatterySaving.setChecked(true); - mSensorsOnly.setChecked(false); - } else if (activated == mSensorsOnly) { - mHighAccuracy.setChecked(false); - mBatterySaving.setChecked(false); - mSensorsOnly.setChecked(true); - } + protected String getLogTag() { + return TAG; } @Override - public void onRadioButtonClicked(RadioButtonPreference emiter) { - int mode = Settings.Secure.LOCATION_MODE_OFF; - if (emiter == mHighAccuracy) { - mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY; - } else if (emiter == mBatterySaving) { - mode = Settings.Secure.LOCATION_MODE_BATTERY_SAVING; - } else if (emiter == mSensorsOnly) { - mode = Settings.Secure.LOCATION_MODE_SENSORS_ONLY; - } - setLocationMode(mode); - } - - @Override - public void onModeChanged(int mode, boolean restricted) { - switch (mode) { - case Settings.Secure.LOCATION_MODE_OFF: - updateRadioButtons(null); - break; - case Settings.Secure.LOCATION_MODE_SENSORS_ONLY: - updateRadioButtons(mSensorsOnly); - break; - case Settings.Secure.LOCATION_MODE_BATTERY_SAVING: - updateRadioButtons(mBatterySaving); - break; - case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY: - updateRadioButtons(mHighAccuracy); - break; - default: - break; - } - - boolean enabled = (mode != Settings.Secure.LOCATION_MODE_OFF) && !restricted; - mHighAccuracy.setEnabled(enabled); - mBatterySaving.setEnabled(enabled); - mSensorsOnly.setEnabled(enabled); + protected List getPreferenceControllers(Context context) { + return buildPreferenceControllers(context, getLifecycle()); } @Override public int getHelpResource() { return R.string.help_url_location_access; } + + private static List buildPreferenceControllers( + Context context, Lifecycle lifecycle) { + final List controllers = new ArrayList<>(); + controllers.add(new LocationModeHighAccuracyPreferenceController(context, lifecycle)); + controllers.add( + new LocationModeBatterySavingPreferenceController(context, lifecycle)); + controllers.add(new LocationModeSensorsOnlyPreferenceController(context, lifecycle)); + return controllers; + } + + /** + * For Search. + */ + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex( + Context context, boolean enabled) { + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.location_mode; + return Arrays.asList(sir); + } + + @Override + public List getPreferenceControllers(Context + context) { + return buildPreferenceControllers(context, null /* lifecycle */); + } + }; } diff --git a/src/com/android/settings/location/LocationModeBatterySavingPreferenceController.java b/src/com/android/settings/location/LocationModeBatterySavingPreferenceController.java new file mode 100644 index 00000000000..70c7013fbbd --- /dev/null +++ b/src/com/android/settings/location/LocationModeBatterySavingPreferenceController.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.provider.Settings; + +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class LocationModeBatterySavingPreferenceController + extends LocationModeRadioButtonPreferenceController { + + private static final String KEY_BATTERY_SAVING = "battery_saving"; + + public LocationModeBatterySavingPreferenceController(Context context, + Lifecycle lifecycle) { + super(context, lifecycle); + } + + @Override + public String getPreferenceKey() { + return KEY_BATTERY_SAVING; + } + + @Override + protected int getLocationMode() { + return Settings.Secure.LOCATION_MODE_BATTERY_SAVING; + } +} diff --git a/src/com/android/settings/location/LocationModeHighAccuracyPreferenceController.java b/src/com/android/settings/location/LocationModeHighAccuracyPreferenceController.java new file mode 100644 index 00000000000..bd2d07e9452 --- /dev/null +++ b/src/com/android/settings/location/LocationModeHighAccuracyPreferenceController.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.provider.Settings; + +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class LocationModeHighAccuracyPreferenceController + extends LocationModeRadioButtonPreferenceController { + + private static final String KEY_HIGH_ACCURACY = "high_accuracy"; + + public LocationModeHighAccuracyPreferenceController(Context context, + Lifecycle lifecycle) { + super(context, lifecycle); + } + + @Override + public String getPreferenceKey() { + return KEY_HIGH_ACCURACY; + } + + @Override + protected int getLocationMode() { + return Settings.Secure.LOCATION_MODE_HIGH_ACCURACY; + } +} diff --git a/src/com/android/settings/location/LocationModePreferenceController.java b/src/com/android/settings/location/LocationModePreferenceController.java new file mode 100644 index 00000000000..a9ed6ad6190 --- /dev/null +++ b/src/com/android/settings/location/LocationModePreferenceController.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class LocationModePreferenceController extends LocationBasePreferenceController { + + /** Key for preference screen "Mode" */ + private static final String KEY_LOCATION_MODE = "location_mode"; + + private final LocationSettings mParentFragment; + private Preference mPreference; + + public LocationModePreferenceController(Context context, LocationSettings parent, + Lifecycle lifecycle) { + super(context, lifecycle); + mParentFragment = parent; + } + + @Override + public String getPreferenceKey() { + return KEY_LOCATION_MODE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(KEY_LOCATION_MODE); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (KEY_LOCATION_MODE.equals(preference.getKey())) { + final SettingsActivity activity = (SettingsActivity) mParentFragment.getActivity(); + activity.startPreferencePanel(mParentFragment, LocationMode.class.getName(), null, + R.string.location_mode_screen_title, null, mParentFragment, 0); + return true; + } + return false; + } + + @Override + public void onLocationModeChanged(int mode, boolean restricted) { + final int modeDescription = LocationPreferenceController.getLocationString(mode); + if (modeDescription != 0) { + mPreference.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. + mPreference.setEnabled(mLocationEnabler.isEnabled(mode)); + } +} diff --git a/src/com/android/settings/location/LocationModeRadioButtonPreferenceController.java b/src/com/android/settings/location/LocationModeRadioButtonPreferenceController.java new file mode 100644 index 00000000000..bdf7c8f1ba8 --- /dev/null +++ b/src/com/android/settings/location/LocationModeRadioButtonPreferenceController.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.widget.RadioButtonPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; + +public abstract class LocationModeRadioButtonPreferenceController + extends LocationBasePreferenceController + implements RadioButtonPreference.OnClickListener { + + protected RadioButtonPreference mPreference; + + public LocationModeRadioButtonPreferenceController(Context context, Lifecycle lifecycle) { + super(context, lifecycle); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = (RadioButtonPreference) screen.findPreference(getPreferenceKey()); + mPreference.setOnClickListener(this); + } + + @Override + public void onRadioButtonClicked(RadioButtonPreference emiter) { + mLocationEnabler.setLocationMode(getLocationMode()); + } + + @Override + public void onLocationModeChanged(int mode, boolean restricted) { + mPreference.setChecked(mode == getLocationMode()); + mPreference.setEnabled(mLocationEnabler.isEnabled(mode)); + } + + /** Gets the location mode that this controller monitors. */ + protected abstract int getLocationMode(); + +} diff --git a/src/com/android/settings/location/LocationModeSensorsOnlyPreferenceController.java b/src/com/android/settings/location/LocationModeSensorsOnlyPreferenceController.java new file mode 100644 index 00000000000..b79dbf24869 --- /dev/null +++ b/src/com/android/settings/location/LocationModeSensorsOnlyPreferenceController.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.provider.Settings; + +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class LocationModeSensorsOnlyPreferenceController + extends LocationModeRadioButtonPreferenceController { + + private static final String KEY_SENSORS_ONLY = "sensors_only"; + + public LocationModeSensorsOnlyPreferenceController(Context context, + Lifecycle lifecycle) { + super(context, lifecycle); + } + + @Override + public String getPreferenceKey() { + return KEY_SENSORS_ONLY; + } + + @Override + protected int getLocationMode() { + return Settings.Secure.LOCATION_MODE_SENSORS_ONLY; + } +} diff --git a/src/com/android/settings/location/LocationServicePreferenceController.java b/src/com/android/settings/location/LocationServicePreferenceController.java new file mode 100644 index 00000000000..0a6a5c14006 --- /dev/null +++ b/src/com/android/settings/location/LocationServicePreferenceController.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2017 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.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.location.SettingInjectorService; +import android.os.UserHandle; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceScreen; +import android.util.Log; + +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; + +import java.util.List; + +public class LocationServicePreferenceController extends LocationBasePreferenceController + implements LifecycleObserver, OnResume, OnPause { + + private static final String TAG = "LocationServicePrefCtrl"; + /** Key for preference category "Location services" */ + private static final String KEY_LOCATION_SERVICES = "location_services"; + @VisibleForTesting + static final IntentFilter INTENT_FILTER_INJECTED_SETTING_CHANGED = + new IntentFilter(SettingInjectorService.ACTION_INJECTED_SETTING_CHANGED); + + private PreferenceCategory mCategoryLocationServices; + private final LocationSettings mFragment; + private final SettingsInjector mInjector; + /** Receives UPDATE_INTENT */ + @VisibleForTesting + BroadcastReceiver mInjectedSettingsReceiver; + + public LocationServicePreferenceController(Context context, LocationSettings fragment, + Lifecycle lifecycle) { + this(context, fragment, lifecycle, new SettingsInjector(context)); + } + + @VisibleForTesting + LocationServicePreferenceController(Context context, LocationSettings fragment, + Lifecycle lifecycle, SettingsInjector injector) { + super(context, lifecycle); + mFragment = fragment; + mInjector = injector; + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public String getPreferenceKey() { + return KEY_LOCATION_SERVICES; + } + + @Override + public boolean isAvailable() { + // If managed profile has lock-down on location access then its injected location services + // must not be shown. + return mInjector.hasInjectedSettings(mLocationEnabler.isManagedProfileRestrictedByBase() + ? UserHandle.myUserId() : UserHandle.USER_CURRENT); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mCategoryLocationServices = + (PreferenceCategory) screen.findPreference(KEY_LOCATION_SERVICES); + } + + @Override + public void updateState(Preference preference) { + mCategoryLocationServices.removeAll(); + LocationSettings.addPreferencesSorted(getLocationServices(), mCategoryLocationServices); + } + + @Override + public void onLocationModeChanged(int mode, boolean restricted) { + // 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. + mInjector.reloadStatusMessages(); + } + + @Override + public void onResume() { + if (mInjectedSettingsReceiver == null) { + mInjectedSettingsReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Received settings change intent: " + intent); + } + mInjector.reloadStatusMessages(); + } + }; + } + mContext.registerReceiver( + mInjectedSettingsReceiver, INTENT_FILTER_INJECTED_SETTING_CHANGED); + } + + @Override + public void onPause() { + mContext.unregisterReceiver(mInjectedSettingsReceiver); + } + + private List getLocationServices() { + // If location access is locked down by device policy then we only show injected settings + // for the primary profile. + return mInjector.getInjectedSettings(mFragment.getPreferenceManager().getContext(), + mLocationEnabler.isManagedProfileRestrictedByBase() + ? UserHandle.myUserId() : UserHandle.USER_CURRENT); + } +} diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index 7d61f7fa3f2..333042132d4 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -16,37 +16,23 @@ package com.android.settings.location; -import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; - import android.app.Activity; -import android.content.BroadcastReceiver; 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.SearchIndexableResource; 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.widget.Switch; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 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.DashboardFragment; import com.android.settings.dashboard.SummaryLoader; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; -import com.android.settings.widget.AppPreference; -import com.android.settings.widget.SwitchBar; -import com.android.settingslib.RestrictedLockUtils; -import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.location.RecentLocationApps; import java.util.ArrayList; @@ -80,34 +66,11 @@ import java.util.List; * other things, this simplifies integration with future changes to the default (AOSP) * implementation. */ -public class LocationSettings extends LocationSettingsBase - implements SwitchBar.OnSwitchChangeListener, Indexable { +public class LocationSettings extends DashboardFragment { 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 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; + private LocationSwitchBarController mSwitchBarController; @Override public int getMetricsCategory() { @@ -117,52 +80,27 @@ public class LocationSettings extends LocationSettingsBase @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); + mSwitchBarController = new LocationSwitchBarController( + activity, activity.getSwitchBar(), getLifecycle()); } @Override - public void onDestroyView() { - super.onDestroyView(); - mSwitchBar.hide(); + protected int getPreferenceScreenResId() { + return R.xml.location_settings; } @Override - public void onResume() { - super.onResume(); - createPreferenceHierarchy(); - if (!mValidListener) { - mSwitchBar.addOnSwitchChangeListener(this); - mValidListener = true; - } + protected String getLogTag() { + return TAG; } @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(); + protected List getPreferenceControllers(Context context) { + return buildPreferenceControllers(context, this, getLifecycle()); } - private void addPreferencesSorted(List prefs, PreferenceGroup container) { + static void addPreferencesSorted(List prefs, PreferenceGroup container) { // If there's some items to display, sort the items and add them to the container. Collections.sort(prefs, new Comparator() { @Override @@ -175,259 +113,22 @@ public class LocationSettings extends LocationSettingsBase } } - 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( - LocationSettings.this, - LocationMode.class.getName(), null, - R.string.location_mode_screen_title, null, LocationSettings.this, - 0); - return true; - } - }); - - RecentLocationApps recentApps = new RecentLocationApps(activity); - List recentLocationRequests = recentApps.getAppList(); - - final AppLocationPermissionPreferenceController preferenceController = - new AppLocationPermissionPreferenceController(activity); - preferenceController.displayPreference(root); - - mCategoryRecentLocationRequests = - (PreferenceCategory) root.findPreference(KEY_RECENT_LOCATION_REQUESTS); - - List recentLocationPrefs = new ArrayList<>(recentLocationRequests.size()); - for (final RecentLocationApps.Request request : recentLocationRequests) { - final AppPreference pref = new AppPreference(getPrefContext()); - pref.setSummary(request.contentDescription); - pref.setIcon(request.icon); - pref.setTitle(request.label); - 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 AppPreference(getPrefContext()); - 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. - final Context prefContext = categoryLocationServices.getContext(); - final List locationServices = injector.getInjectedSettings(prefContext, - 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 int getHelpResource() { return R.string.help_url_location_access; } - @Override - public void onModeChanged(int mode, boolean restricted) { - int modeDescription = LocationPreferenceController.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( - LocationSettings.this, - InstalledAppDetails.class.getName(), args, - R.string.application_info_label, null, mUserHandle); - return true; - } + private static List buildPreferenceControllers( + Context context, LocationSettings fragment, Lifecycle lifecycle) { + final List controllers = new ArrayList<>(); + controllers.add(new LocationModePreferenceController(context, fragment, lifecycle)); + controllers.add(new AppLocationPermissionPreferenceController(context)); + controllers.add(new LocationForWorkPreferenceController(context, lifecycle)); + controllers.add( + new RecentLocationRequestPreferenceController(context, fragment, lifecycle)); + controllers.add( + new LocationServicePreferenceController(context, fragment, lifecycle)); + return controllers; } private static class SummaryProvider implements SummaryLoader.SummaryProvider { @@ -470,5 +171,12 @@ public class LocationSettings extends LocationSettingsBase sir.xmlResId = R.xml.location_settings; return Arrays.asList(sir); } + + @Override + public List getPreferenceControllers(Context + context) { + return buildPreferenceControllers(context, null /* fragment */, + null /* lifecycle */); + } }; } diff --git a/src/com/android/settings/location/LocationSettingsBase.java b/src/com/android/settings/location/LocationSettingsBase.java deleted file mode 100644 index 741d607a3b3..00000000000 --- a/src/com/android/settings/location/LocationSettingsBase.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.location.LocationManager; -import android.os.Bundle; -import android.os.UserManager; -import android.provider.Settings; -import android.util.Log; - -import com.android.settings.SettingsPreferenceFragment; - -/** - * A base class that listens to location settings change and modifies location - * settings. - */ -public abstract class LocationSettingsBase extends SettingsPreferenceFragment { - private static final String TAG = "LocationSettingsBase"; - /** Broadcast intent action when the location mode is about to change. */ - private static final String MODE_CHANGING_ACTION = - "com.android.settings.location.MODE_CHANGING"; - private static final String CURRENT_MODE_KEY = "CURRENT_MODE"; - private static final String NEW_MODE_KEY = "NEW_MODE"; - - private int mCurrentMode; - private BroadcastReceiver mReceiver; - - /** - * Whether the fragment is actively running. - */ - private boolean mActive = false; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Received location mode change intent: " + intent); - } - refreshLocationMode(); - } - }; - } - - @Override - public void onResume() { - super.onResume(); - mActive = true; - IntentFilter filter = new IntentFilter(); - filter.addAction(LocationManager.MODE_CHANGED_ACTION); - getActivity().registerReceiver(mReceiver, filter); - } - - @Override - public void onPause() { - try { - getActivity().unregisterReceiver(mReceiver); - } catch (RuntimeException e) { - // Ignore exceptions caused by race condition - } - super.onPause(); - mActive = false; - } - - /** Called when location mode has changed. */ - public abstract void onModeChanged(int mode, boolean restricted); - - public static boolean isRestricted(Context context) { - final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); - return um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION); - } - - public static boolean updateLocationMode(Context context, int oldMode, int newMode) { - Intent intent = new Intent(MODE_CHANGING_ACTION); - intent.putExtra(CURRENT_MODE_KEY, oldMode); - intent.putExtra(NEW_MODE_KEY, newMode); - context.sendBroadcast(intent, android.Manifest.permission.WRITE_SECURE_SETTINGS); - return Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE, - newMode); - } - - public void setLocationMode(int mode) { - Context context = getActivity(); - if (isRestricted(context)) { - // Location toggling disabled by user restriction. Read the current location mode to - // update the location master switch. - if (Log.isLoggable(TAG, Log.INFO)) { - Log.i(TAG, "Restricted user, not setting location mode"); - } - mode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE, - Settings.Secure.LOCATION_MODE_OFF); - if (mActive) { - onModeChanged(mode, true); - } - return; - } - - updateLocationMode(context, mCurrentMode, mode); - refreshLocationMode(); - } - - public void refreshLocationMode() { - if (mActive) { - int mode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE, - Settings.Secure.LOCATION_MODE_OFF); - mCurrentMode = mode; - if (Log.isLoggable(TAG, Log.INFO)) { - Log.i(TAG, "Location mode has been changed"); - } - onModeChanged(mode, isRestricted(getActivity())); - } - } -} diff --git a/src/com/android/settings/location/LocationSwitchBarController.java b/src/com/android/settings/location/LocationSwitchBarController.java new file mode 100644 index 00000000000..6522dc75be2 --- /dev/null +++ b/src/com/android/settings/location/LocationSwitchBarController.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.os.UserHandle; +import android.widget.Switch; + +import com.android.settings.widget.SwitchBar; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +public class LocationSwitchBarController implements SwitchBar.OnSwitchChangeListener, + LocationEnabler.LocationModeChangeListener, LifecycleObserver, OnStart, OnStop { + + private final SwitchBar mSwitchBar; + private final Switch mSwitch; + private final LocationEnabler mLocationEnabler; + private boolean mValidListener; + + public LocationSwitchBarController(Context context, SwitchBar switchBar, + Lifecycle lifecycle) { + mSwitchBar = switchBar; + mSwitch = mSwitchBar.getSwitch(); + mLocationEnabler = new LocationEnabler(context, this /* listener */, lifecycle); + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public void onStart() { + if (!mValidListener) { + mSwitchBar.addOnSwitchChangeListener(this); + mValidListener = true; + } + mSwitchBar.show(); + } + + @Override + public void onStop() { + mSwitchBar.hide(); + if (mValidListener) { + mSwitchBar.removeOnSwitchChangeListener(this); + mValidListener = false; + } + } + + @Override + public void onLocationModeChanged(int mode, boolean restricted) { + // 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 = mLocationEnabler.isEnabled(mode); + final int userId = UserHandle.myUserId(); + final RestrictedLockUtils.EnforcedAdmin admin = + mLocationEnabler.getShareLocationEnforcedAdmin(userId); + final boolean hasBaseUserRestriction = + mLocationEnabler.hasShareLocationRestriction(userId); + // 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); + } + + 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); + } + } + } + + /** + * Listens to the state change of the location master switch. + */ + @Override + public void onSwitchChanged(Switch switchView, boolean isChecked) { + mLocationEnabler.setLocationMode(isChecked + ? android.provider.Settings.Secure.LOCATION_MODE_PREVIOUS + : android.provider.Settings.Secure.LOCATION_MODE_OFF); + } + +} diff --git a/src/com/android/settings/location/RecentLocationRequestPreferenceController.java b/src/com/android/settings/location/RecentLocationRequestPreferenceController.java new file mode 100644 index 00000000000..92e5169811c --- /dev/null +++ b/src/com/android/settings/location/RecentLocationRequestPreferenceController.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.os.Bundle; +import android.os.UserHandle; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.applications.InstalledAppDetails; +import com.android.settings.widget.AppPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.location.RecentLocationApps; + +import java.util.ArrayList; +import java.util.List; + +public class RecentLocationRequestPreferenceController extends LocationBasePreferenceController { + + /** Key for preference category "Recent location requests" */ + private static final String KEY_RECENT_LOCATION_REQUESTS = "recent_location_requests"; + private final LocationSettings mFragment; + private final RecentLocationApps mRecentLocationApps; + private PreferenceCategory mCategoryRecentLocationRequests; + + @VisibleForTesting + static class PackageEntryClickedListener implements Preference.OnPreferenceClickListener { + private final LocationSettings mFragment; + private final String mPackage; + private final UserHandle mUserHandle; + + public PackageEntryClickedListener(LocationSettings fragment, String packageName, + UserHandle userHandle) { + mFragment = fragment; + mPackage = packageName; + mUserHandle = userHandle; + } + + @Override + public boolean onPreferenceClick(Preference preference) { + // start new fragment to display extended information + final Bundle args = new Bundle(); + args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mPackage); + ((SettingsActivity) mFragment.getActivity()).startPreferencePanelAsUser( + mFragment, + InstalledAppDetails.class.getName(), args, + R.string.application_info_label, null, mUserHandle); + return true; + } + } + + public RecentLocationRequestPreferenceController(Context context, LocationSettings fragment, + Lifecycle lifecycle) { + this(context, fragment, lifecycle, new RecentLocationApps(context)); + } + + @VisibleForTesting + RecentLocationRequestPreferenceController(Context context, LocationSettings fragment, + Lifecycle lifecycle, RecentLocationApps recentApps) { + super(context, lifecycle); + mFragment = fragment; + mRecentLocationApps = recentApps; + } + + @Override + public String getPreferenceKey() { + return KEY_RECENT_LOCATION_REQUESTS; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mCategoryRecentLocationRequests = + (PreferenceCategory) screen.findPreference(KEY_RECENT_LOCATION_REQUESTS); + } + + @Override + public void updateState(Preference preference) { + mCategoryRecentLocationRequests.removeAll(); + + final Context prefContext = preference.getContext(); + final List recentLocationRequests = + mRecentLocationApps.getAppList(); + + final List recentLocationPrefs = new ArrayList<>(recentLocationRequests.size()); + for (final RecentLocationApps.Request request : recentLocationRequests) { + recentLocationPrefs.add(createAppPreference(prefContext, request)); + } + if (recentLocationRequests.size() > 0) { + LocationSettings.addPreferencesSorted( + recentLocationPrefs, mCategoryRecentLocationRequests); + } else { + // If there's no item to display, add a "No recent apps" item. + final Preference banner = createAppPreference(prefContext); + banner.setTitle(R.string.location_no_recent_apps); + banner.setSelectable(false); + mCategoryRecentLocationRequests.addPreference(banner); + } + } + + @Override + public void onLocationModeChanged(int mode, boolean restricted) { + mCategoryRecentLocationRequests.setEnabled(mLocationEnabler.isEnabled(mode)); + } + + @VisibleForTesting + AppPreference createAppPreference(Context prefContext) { + return new AppPreference(prefContext); + } + + @VisibleForTesting + AppPreference createAppPreference(Context prefContext, RecentLocationApps.Request request) { + final AppPreference pref = createAppPreference(prefContext); + pref.setSummary(request.contentDescription); + pref.setIcon(request.icon); + pref.setTitle(request.label); + pref.setOnPreferenceClickListener(new PackageEntryClickedListener( + mFragment, request.packageName, request.userHandle)); + return pref; + } +} diff --git a/src/com/android/settings/location/ScanningSettings.java b/src/com/android/settings/location/ScanningSettings.java index 5c34b62b6a4..4585870dd66 100644 --- a/src/com/android/settings/location/ScanningSettings.java +++ b/src/com/android/settings/location/ScanningSettings.java @@ -18,26 +18,23 @@ package com.android.settings.location; import android.content.Context; import android.provider.SearchIndexableResource; -import android.provider.Settings.Global; -import android.support.v14.preference.SwitchPreference; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceScreen; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; +import com.android.settingslib.core.AbstractPreferenceController; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * A page that configures the background scanning settings for Wi-Fi and Bluetooth. */ -public class ScanningSettings extends SettingsPreferenceFragment implements Indexable { - private static final String KEY_WIFI_SCAN_ALWAYS_AVAILABLE = "wifi_always_scanning"; - private static final String KEY_BLUETOOTH_SCAN_ALWAYS_AVAILABLE = "bluetooth_always_scanning"; +public class ScanningSettings extends DashboardFragment { + private static final String TAG = "ScanningSettings"; @Override public int getMetricsCategory() { @@ -45,48 +42,25 @@ public class ScanningSettings extends SettingsPreferenceFragment implements Inde } @Override - public void onResume() { - super.onResume(); - createPreferenceHierarchy(); - } - - private PreferenceScreen createPreferenceHierarchy() { - PreferenceScreen root = getPreferenceScreen(); - if (root != null) { - root.removeAll(); - } - addPreferencesFromResource(R.xml.location_scanning); - root = getPreferenceScreen(); - initPreferences(); - return root; - } - - private void initPreferences() { - final SwitchPreference wifiScanAlwaysAvailable = - (SwitchPreference) findPreference(KEY_WIFI_SCAN_ALWAYS_AVAILABLE); - wifiScanAlwaysAvailable.setChecked(Global.getInt(getContentResolver(), - Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1); - final SwitchPreference bleScanAlwaysAvailable = - (SwitchPreference) findPreference(KEY_BLUETOOTH_SCAN_ALWAYS_AVAILABLE); - bleScanAlwaysAvailable.setChecked(Global.getInt(getContentResolver(), - Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1); + protected int getPreferenceScreenResId() { + return R.xml.location_scanning; } @Override - public boolean onPreferenceTreeClick(Preference preference) { - String key = preference.getKey(); - if (KEY_WIFI_SCAN_ALWAYS_AVAILABLE.equals(key)) { - Global.putInt(getContentResolver(), - Global.WIFI_SCAN_ALWAYS_AVAILABLE, - ((SwitchPreference) preference).isChecked() ? 1 : 0); - } else if (KEY_BLUETOOTH_SCAN_ALWAYS_AVAILABLE.equals(key)) { - Global.putInt(getContentResolver(), - Global.BLE_SCAN_ALWAYS_AVAILABLE, - ((SwitchPreference) preference).isChecked() ? 1 : 0); - } else { - return super.onPreferenceTreeClick(preference); - } - return true; + protected String getLogTag() { + return TAG; + } + + @Override + protected List getPreferenceControllers(Context context) { + return buildPreferenceControllers(context); + } + + private static List buildPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + controllers.add(new WifiScanningPreferenceController(context)); + controllers.add(new BluetoothScanningPreferenceController(context)); + return controllers; } /** @@ -101,5 +75,11 @@ public class ScanningSettings extends SettingsPreferenceFragment implements Inde sir.xmlResId = R.xml.location_scanning; return Arrays.asList(sir); } + + @Override + public List getPreferenceControllers(Context + context) { + return buildPreferenceControllers(context); + } }; } diff --git a/src/com/android/settings/location/SettingsInjector.java b/src/com/android/settings/location/SettingsInjector.java index c6d1f37c2a3..dfa51433820 100644 --- a/src/com/android/settings/location/SettingsInjector.java +++ b/src/com/android/settings/location/SettingsInjector.java @@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable; import android.location.SettingInjectorService; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.SystemClock; @@ -252,6 +253,28 @@ class SettingsInjector { return prefs; } + /** + * Checks wheteher there is any preference that other apps have injected. + * + * @param profileId Identifier of the user/profile to obtain the injected settings for or + * UserHandle.USER_CURRENT for all profiles associated with current user. + */ + public boolean hasInjectedSettings(final int profileId) { + final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + final List profiles = um.getUserProfiles(); + final int profileCount = profiles.size(); + for (int i = 0; i < profileCount; ++i) { + final UserHandle userHandle = profiles.get(i); + if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) { + Iterable settings = getSettings(userHandle); + for (InjectedSetting setting : settings) { + return true; + } + } + } + return false; + } + /** * Reloads the status messages for all the preference items. */ @@ -338,6 +361,9 @@ class SettingsInjector { private boolean mReloadRequested; + private StatusLoadingHandler() { + super(Looper.getMainLooper()); + } @Override public void handleMessage(Message msg) { if (Log.isLoggable(TAG, Log.DEBUG)) { diff --git a/src/com/android/settings/location/WifiScanningPreferenceController.java b/src/com/android/settings/location/WifiScanningPreferenceController.java new file mode 100644 index 00000000000..8d4cf985d49 --- /dev/null +++ b/src/com/android/settings/location/WifiScanningPreferenceController.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.Preference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.AbstractPreferenceController; + +public class WifiScanningPreferenceController extends AbstractPreferenceController + implements PreferenceControllerMixin { + + private static final String KEY_WIFI_SCAN_ALWAYS_AVAILABLE = "wifi_always_scanning"; + + public WifiScanningPreferenceController(Context context) { + super(context); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return KEY_WIFI_SCAN_ALWAYS_AVAILABLE; + } + + @Override + public void updateState(Preference preference) { + ((SwitchPreference) preference).setChecked( + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (KEY_WIFI_SCAN_ALWAYS_AVAILABLE.equals(preference.getKey())) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, + ((SwitchPreference) preference).isChecked() ? 1 : 0); + return true; + } + return false; + } +} diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java index c5243460eb3..46e693d265b 100644 --- a/src/com/android/settings/search/SearchIndexableResources.java +++ b/src/com/android/settings/search/SearchIndexableResources.java @@ -60,6 +60,7 @@ import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment; import com.android.settings.inputmethod.PhysicalKeyboardFragment; import com.android.settings.inputmethod.VirtualKeyboardFragment; import com.android.settings.language.LanguageAndInputSettings; +import com.android.settings.location.LocationMode; import com.android.settings.location.LocationSettings; import com.android.settings.location.ScanningSettings; import com.android.settings.network.NetworkDashboardFragment; @@ -129,6 +130,7 @@ public final class SearchIndexableResources { addIndex(GestureSettings.class); addIndex(LanguageAndInputSettings.class); addIndex(LocationSettings.class); + addIndex(LocationMode.class); addIndex(ScanningSettings.class); addIndex(SecuritySettings.class); addIndex(ScreenLockSettings.class); diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable index c2a084c5360..8c441126421 100644 --- a/tests/robotests/assets/grandfather_not_implementing_indexable +++ b/tests/robotests/assets/grandfather_not_implementing_indexable @@ -1,4 +1,3 @@ -com.android.settings.location.LocationMode com.android.settings.accessibility.ToggleScreenMagnificationPreferenceFragment com.android.settings.deviceinfo.SimStatus com.android.settings.deviceinfo.PrivateVolumeForget diff --git a/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java index 60410071a33..61a90381e52 100644 --- a/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java @@ -1,13 +1,9 @@ package com.android.settings.location; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Answers.RETURNS_DEEP_STUBS; -import static org.mockito.Mockito.when; import android.content.Context; import android.provider.Settings; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceScreen; import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -24,43 +20,33 @@ import org.robolectric.annotation.Config; @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) public class AppLocationPermissionPreferenceControllerTest { - @Mock(answer = RETURNS_DEEP_STUBS) - private PreferenceScreen mScreen; - private AppLocationPermissionPreferenceController mController; @Mock private Context mContext; - private Preference mPreference; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mController = new AppLocationPermissionPreferenceController(mContext); - mPreference = new Preference(mContext); - mPreference.setKey(mController.getPreferenceKey()); - when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference); } @Test - public void displayPreference_shouldRemovePreference() { + public void isAvailable_noLocationLinkPermission_shouldReturnFalse() { Settings.System.putInt(mContext.getContentResolver(), android.provider.Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED, 0); - mController.displayPreference(mScreen); - - assertThat(mPreference.isVisible()).isFalse(); + assertThat(mController.isAvailable()).isFalse(); } @Test - public void displayPreference_shouldNotRemovePreference() { + public void displayPreference_hasLocationLinkPermission_shouldReturnTrue() { Settings.System.putInt(mContext.getContentResolver(), android.provider.Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED, 1); - mController.displayPreference(mScreen); - assertThat(mPreference.isVisible()).isTrue(); + assertThat(mController.isAvailable()).isTrue(); } } diff --git a/tests/robotests/src/com/android/settings/location/BluetoothScanningPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/BluetoothScanningPreferenceControllerTest.java new file mode 100644 index 00000000000..ffbf530f6d6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/BluetoothScanningPreferenceControllerTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class BluetoothScanningPreferenceControllerTest { + + @Mock + private SwitchPreference mPreference; + + private Context mContext; + private BluetoothScanningPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = new BluetoothScanningPreferenceController(mContext); + when(mPreference.getKey()).thenReturn(mController.getPreferenceKey()); + } + + @Test + public void updateState_bluetoothScanningEnabled_shouldCheckedPreference() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 1); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(true); + } + + @Test + public void updateState_bluetoothScanningDisabled_shouldUncheckedPreference() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(false); + } + + @Test + public void handlePreferenceTreeClick_checked_shouldEnableBluetoothScanning() { + when(mPreference.isChecked()).thenReturn(true); + + mController.handlePreferenceTreeClick(mPreference); + + assertThat(Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0)).isEqualTo(1); + + } + + @Test + public void handlePreferenceTreeClick_unchecked_shouldDisableBluetoothScanning() { + when(mPreference.isChecked()).thenReturn(false); + + mController.handlePreferenceTreeClick(mPreference); + + assertThat(Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 1)).isEqualTo(0); + + } +} diff --git a/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java b/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java new file mode 100644 index 00000000000..8bced8bc739 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2017 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 static android.Manifest.permission.WRITE_SECURE_SETTINGS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.text.TextUtils; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class LocationEnablerTest { + + @Mock + private UserManager mUserManager; + @Mock + private LocationEnabler.LocationModeChangeListener mListener; + + private Context mContext; + private LocationEnabler mEnabler; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + mEnabler = spy(new LocationEnabler(mContext, mListener, new Lifecycle())); + } + + @Test + public void onResume_shouldSetActiveAndRegisterListener() { + mEnabler.onResume(); + + verify(mContext).registerReceiver(eq(mEnabler.mReceiver), + eq(mEnabler.INTENT_FILTER_LOCATION_MODE_CHANGED)); + } + + @Test + public void onResume_shouldRefreshLocationMode() { + mEnabler.onResume(); + + verify(mEnabler).refreshLocationMode(); + } + + @Test + public void onPause_shouldUnregisterListener() { + mEnabler.onPause(); + + verify(mContext).unregisterReceiver(mEnabler.mReceiver); + } + + @Test + public void onReceive_shouldRefreshLocationMode() { + mEnabler.onResume(); + reset(mListener); + mEnabler.mReceiver.onReceive(mContext, new Intent()); + + verify(mListener).onLocationModeChanged(anyInt(), anyBoolean()); + } + + @Test + public void isEnabled_locationOff_shouldReturnFalse() { + assertThat(mEnabler.isEnabled(Settings.Secure.LOCATION_MODE_OFF)).isFalse(); + } + + @Test + public void isEnabled_restricted_shouldReturnFalse() { + when(mUserManager.hasUserRestriction(anyString())).thenReturn(true); + + assertThat(mEnabler.isEnabled(Settings.Secure.LOCATION_MODE_OFF)).isFalse(); + } + + @Test + public void isEnabled_locationONotRestricted_shouldReturnTrue() { + when(mUserManager.hasUserRestriction(anyString())).thenReturn(false); + + assertThat(mEnabler.isEnabled(Settings.Secure.LOCATION_MODE_BATTERY_SAVING)).isTrue(); + } + + @Test + public void refreshLocationMode_shouldCallOnLocationModeChanged() { + mEnabler.refreshLocationMode(); + + verify(mListener).onLocationModeChanged(anyInt(), anyBoolean()); + } + + @Test + public void setLocationMode_restricted_shouldSetCurrentMode() { + when(mUserManager.hasUserRestriction(anyString())).thenReturn(true); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_BATTERY_SAVING); + + mEnabler.setLocationMode(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); + + verify(mListener) + .onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, true); + } + + @Test + public void setLocationMode_notRestricted_shouldUpdateSecureSettings() { + when(mUserManager.hasUserRestriction(anyString())).thenReturn(false); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_BATTERY_SAVING); + + mEnabler.setLocationMode(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); + + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_BATTERY_SAVING)) + .isEqualTo(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); + } + + @Test + public void setLocationMode_notRestricted_shouldRefreshLocation() { + when(mUserManager.hasUserRestriction(anyString())).thenReturn(false); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_BATTERY_SAVING); + + mEnabler.setLocationMode(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); + + verify(mEnabler).refreshLocationMode(); + } + + @Test + public void setLocationMode_notRestricted_shouldBroadcastUpdate() { + when(mUserManager.hasUserRestriction(anyString())).thenReturn(false); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_BATTERY_SAVING); + + mEnabler.setLocationMode(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); + + verify(mContext).sendBroadcast(argThat(actionMatches(mEnabler.MODE_CHANGING_ACTION)), + eq(WRITE_SECURE_SETTINGS)); + } + + @Test + public void isManagedProfileRestrictedByBase_notManagedProfile_shouldReturnFalse() { + assertThat(mEnabler.isManagedProfileRestrictedByBase()).isFalse(); + } + + @Test + public void isManagedProfileRestrictedByBase_notRestricted_shouldReturnFalse() { + mockManagedProfile(); + doReturn(false).when(mEnabler).hasShareLocationRestriction(anyInt()); + + assertThat(mEnabler.isManagedProfileRestrictedByBase()).isFalse(); + } + + @Test + public void isManagedProfileRestrictedByBase_hasManagedProfile_shouldReturnFalse() { + mockManagedProfile(); + doReturn(true).when(mEnabler).hasShareLocationRestriction(anyInt()); + + assertThat(mEnabler.isManagedProfileRestrictedByBase()).isTrue(); + } + + private void mockManagedProfile() { + final List userProfiles = new ArrayList<>(); + final UserHandle userHandle = mock(UserHandle.class); + when(userHandle.getIdentifier()).thenReturn(5); + userProfiles.add(userHandle); + when(mUserManager.getUserProfiles()).thenReturn(userProfiles); + when(mUserManager.getUserHandle()).thenReturn(1); + when(mUserManager.getUserInfo(5)) + .thenReturn(new UserInfo(5, "user 5", UserInfo.FLAG_MANAGED_PROFILE)); + } + + private static ArgumentMatcher actionMatches(String expected) { + return intent -> TextUtils.equals(expected, intent.getAction()); + } +} diff --git a/tests/robotests/src/com/android/settings/location/LocationForWorkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationForWorkPreferenceControllerTest.java new file mode 100644 index 00000000000..db3a229a9d6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/LocationForWorkPreferenceControllerTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class LocationForWorkPreferenceControllerTest { + + @Mock + private RestrictedSwitchPreference mPreference; + @Mock + private PreferenceScreen mScreen; + @Mock + private UserManager mUserManager; + @Mock + private LocationEnabler mEnabler; + @Mock + private UserHandle mUserHandle; + + private Context mContext; + private LocationForWorkPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + mController = spy(new LocationForWorkPreferenceController(mContext, new Lifecycle())); + mockManagedProfile(); + ReflectionHelpers.setField(mController, "mLocationEnabler", mEnabler); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + final String key = mController.getPreferenceKey(); + when(mPreference.getKey()).thenReturn(key); + when(mPreference.isVisible()).thenReturn(true); + } + + @Test + public void handlePreferenceTreeClick_preferenceChecked_shouldSetRestrictionAndOnSummary() { + mController.displayPreference(mScreen); + when(mPreference.isChecked()).thenReturn(true); + + mController.handlePreferenceTreeClick(mPreference); + + verify(mUserManager).setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, false, + mUserHandle); + verify(mPreference).setSummary(R.string.switch_on_text); + } + + @Test + public void handlePreferenceTreeClick_preferenceUnchecked_shouldSetRestritionAndOffSummary() { + mController.displayPreference(mScreen); + when(mPreference.isChecked()).thenReturn(false); + + mController.handlePreferenceTreeClick(mPreference); + + verify(mUserManager).setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, true, + mUserHandle); + verify(mPreference).setSummary(R.string.switch_off_text); + } + + @Test + public void isAvailable_noManagedProfile_shouldReturnFalse() { + when(mUserManager.getUserProfiles()).thenReturn(new ArrayList()); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_hasManagedProfile_shouldReturnTrue() { + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void onLocationModeChanged_disabledByAdmin_shouldDisablePreference() { + mController.displayPreference(mScreen); + final EnforcedAdmin admin = mock(EnforcedAdmin.class); + doReturn(admin).when(mEnabler).getShareLocationEnforcedAdmin(anyInt()); + doReturn(false).when(mEnabler).isManagedProfileRestrictedByBase(); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mPreference).setDisabledByAdmin(any()); + verify(mPreference).setChecked(false); + } + + @Test + public void onLocationModeChanged_locationOff_shouldDisablePreference() { + mController.displayPreference(mScreen); + doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt()); + doReturn(false).when(mEnabler).isManagedProfileRestrictedByBase(); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_OFF, false); + + verify(mPreference).setEnabled(false); + verify(mPreference).setChecked(false); + verify(mPreference).setSummary(R.string.switch_off_text); + } + + @Test + public void onLocationModeChanged_locationOn_shouldEnablePreference() { + mController.displayPreference(mScreen); + doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt()); + doReturn(false).when(mEnabler).isManagedProfileRestrictedByBase(); + doReturn(true).when(mEnabler).isEnabled(anyInt()); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mPreference).setEnabled(true); + verify(mPreference).setSummary(R.string.switch_on_text); + } + + @Test + public void onLocationModeChanged_noRestriction_shouldCheckedPreference() { + mController.displayPreference(mScreen); + doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt()); + doReturn(false).when(mEnabler).isManagedProfileRestrictedByBase(); + doReturn(true).when(mEnabler).isEnabled(anyInt()); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mPreference).setChecked(true); + } + + @Test + public void onLocationModeChanged_hasRestriction_shouldCheckedPreference() { + mController.displayPreference(mScreen); + doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt()); + doReturn(true).when(mEnabler).isManagedProfileRestrictedByBase(); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mPreference).setChecked(false); + } + + private void mockManagedProfile() { + final List userProfiles = new ArrayList<>(); + when(mUserHandle.getIdentifier()).thenReturn(5); + userProfiles.add(mUserHandle); + when(mUserManager.getUserProfiles()).thenReturn(userProfiles); + when(mUserManager.getUserHandle()).thenReturn(1); + when(mUserManager.getUserInfo(5)) + .thenReturn(new UserInfo(5, "user 5", UserInfo.FLAG_MANAGED_PROFILE)); + } + +} diff --git a/tests/robotests/src/com/android/settings/location/LocationModeBatterySavingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationModeBatterySavingPreferenceControllerTest.java new file mode 100644 index 00000000000..5b1aa1969e8 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/LocationModeBatterySavingPreferenceControllerTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class LocationModeBatterySavingPreferenceControllerTest { + + @Test + public void getLocationMode_shouldReturnModeBatterySaving() { + final LocationModeBatterySavingPreferenceController controller = + new LocationModeBatterySavingPreferenceController(mock(Context.class), + new Lifecycle()); + + assertThat(controller.getLocationMode()) + .isEqualTo(Settings.Secure.LOCATION_MODE_BATTERY_SAVING); + } + +} diff --git a/tests/robotests/src/com/android/settings/location/LocationModeHighAccuracyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationModeHighAccuracyPreferenceControllerTest.java new file mode 100644 index 00000000000..bd55bdca37c --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/LocationModeHighAccuracyPreferenceControllerTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class LocationModeHighAccuracyPreferenceControllerTest { + + @Test + public void getLocationMode_shouldReturnModeHighAccuracy() { + final LocationModeHighAccuracyPreferenceController controller = + new LocationModeHighAccuracyPreferenceController(mock(Context.class), + new Lifecycle()); + + assertThat(controller.getLocationMode()) + .isEqualTo(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); + } + +} diff --git a/tests/robotests/src/com/android/settings/location/LocationModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationModePreferenceControllerTest.java new file mode 100644 index 00000000000..767447751a6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/LocationModePreferenceControllerTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017 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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.UserManager; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class LocationModePreferenceControllerTest { + + @Mock + private LocationSettings mFragment; + @Mock + private SettingsActivity mActivity; + @Mock + private Preference mPreference; + @Mock + private PreferenceScreen mScreen; + @Mock + private UserManager mUserManager; + + private Context mContext; + private LocationModePreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + mController = new LocationModePreferenceController(mContext, mFragment, new Lifecycle()); + when(mFragment.getActivity()).thenReturn(mActivity); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + } + + @Test + public void onLocationModeChanged_locationOff_shouldDisablePreference() { + when(mUserManager.hasUserRestriction(any())).thenReturn(false); + mController.displayPreference(mScreen); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_OFF, false); + + verify(mPreference).setEnabled(false); + } + + @Test + public void onLocationModeChanged_restricted_shouldDisablePreference() { + when(mUserManager.hasUserRestriction(any())).thenReturn(true); + mController.displayPreference(mScreen); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mPreference).setEnabled(false); + } + + @Test + public void onLocationModeChanged_locationOnNotRestricted_shouldEnablePreference() { + when(mUserManager.hasUserRestriction(any())).thenReturn(false); + mController.displayPreference(mScreen); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mPreference).setEnabled(true); + } + + @Test + public void onLocationModeChanged_shouldUpdateSummary() { + mController.displayPreference(mScreen); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mPreference).setSummary(anyInt()); + } + + @Test + public void handlePreferenceTreeClick_shouldStartLocationModeFragment() { + final Preference preference = new Preference(mContext); + preference.setKey(mController.getPreferenceKey()); + + mController.handlePreferenceTreeClick(preference); + + verify(mActivity).startPreferencePanel(any(), eq(LocationMode.class.getName()), any(), + eq(R.string.location_mode_screen_title), any(), any(), anyInt()); + } + +} diff --git a/tests/robotests/src/com/android/settings/location/LocationModeRadioButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationModeRadioButtonPreferenceControllerTest.java new file mode 100644 index 00000000000..46fea5ef6ed --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/LocationModeRadioButtonPreferenceControllerTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.widget.RadioButtonPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class LocationModeRadioButtonPreferenceControllerTest { + + @Mock + private RadioButtonPreference mPreference; + @Mock + private PreferenceScreen mScreen; + + private Context mContext; + private LocationModeRadioButtonPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = new LocationModeRadioButtonPreferenceControllerTestable(mContext); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + } + + @Test + public void displayPreference_shouldAddClickListener() { + mController.displayPreference(mScreen); + + verify(mPreference).setOnClickListener(mController); + } + + @Test + public void onRadioButtonClicked_shouldSetLocationModeToOwnMode() { + mController.displayPreference(mScreen); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF); + + mController.onRadioButtonClicked(mPreference); + + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF)) + .isEqualTo(mController.getLocationMode()); + } + + @Test + public void onLocationModeChanged_otherModeSelected_shouldUncheckPreference() { + mController.displayPreference(mScreen); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mPreference).setChecked(false); + } + + @Test + public void onLocationModeChanged_ownModeSelected_shouldCheckPreference() { + mController.displayPreference(mScreen); + + mController.onLocationModeChanged(mController.getLocationMode(), false); + + verify(mPreference).setChecked(true); + } + + @Test + public void onLocationModeChanged_locationOff_shouldDisablePreference() { + mController.displayPreference(mScreen); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_OFF, false); + + verify(mPreference).setEnabled(false); + } + + @Test + public void onLocationModeChanged_locationOn_shouldDisablePreference() { + mController.displayPreference(mScreen); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mPreference).setEnabled(true); + } + + private class LocationModeRadioButtonPreferenceControllerTestable + extends LocationModeRadioButtonPreferenceController { + + public LocationModeRadioButtonPreferenceControllerTestable(Context context) { + super(context, new Lifecycle()); + } + + @Override + public String getPreferenceKey() { + return "test"; + } + + @Override + protected int getLocationMode() { + return Settings.Secure.LOCATION_MODE_HIGH_ACCURACY; + } + } +} diff --git a/tests/robotests/src/com/android/settings/location/LocationModeSensorsOnlyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationModeSensorsOnlyPreferenceControllerTest.java new file mode 100644 index 00000000000..04d7d8cd195 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/LocationModeSensorsOnlyPreferenceControllerTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class LocationModeSensorsOnlyPreferenceControllerTest { + + @Test + public void getLocationMode_shouldReturnModeSensorsOnly() { + final LocationModeSensorsOnlyPreferenceController controller = + new LocationModeSensorsOnlyPreferenceController(mock(Context.class), + new Lifecycle()); + + assertThat(controller.getLocationMode()) + .isEqualTo(Settings.Secure.LOCATION_MODE_SENSORS_ONLY); + } + +} diff --git a/tests/robotests/src/com/android/settings/location/LocationServicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationServicePreferenceControllerTest.java new file mode 100644 index 00000000000..62ffa49a765 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/LocationServicePreferenceControllerTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class LocationServicePreferenceControllerTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private LocationSettings mFragment; + @Mock + private PreferenceCategory mCategory; + @Mock + private PreferenceScreen mScreen; + @Mock + private SettingsInjector mSettingsInjector; + + private Context mContext; + private LocationServicePreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mController = spy(new LocationServicePreferenceController( + mContext, mFragment, new Lifecycle(), mSettingsInjector)); + final String key = mController.getPreferenceKey(); + when(mScreen.findPreference(key)).thenReturn(mCategory); + when(mCategory.getKey()).thenReturn(key); + } + + @Test + public void isAvailable_noInjectedSettings_shouldReturnFalse() { + doReturn(false).when(mSettingsInjector).hasInjectedSettings(anyInt()); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_hasInjectedSettings_shouldReturnFalse() { + doReturn(true).when(mSettingsInjector).hasInjectedSettings(anyInt()); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void onResume_shouldRegisterListener() { + mController.onResume(); + + verify(mContext).registerReceiver(eq(mController.mInjectedSettingsReceiver), + eq(mController.INTENT_FILTER_INJECTED_SETTING_CHANGED)); + } + + @Test + public void onPause_shouldUnregisterListener() { + mController.onResume(); + mController.onPause(); + + verify(mContext).unregisterReceiver(mController.mInjectedSettingsReceiver); + } + + @Test + public void updateState_shouldRemoveAllAndAddInjectedSettings() { + final List preferences = new ArrayList<>(); + final Preference pref1 = new Preference(mContext); + pref1.setTitle("Title1"); + final Preference pref2 = new Preference(mContext); + pref2.setTitle("Title2"); + preferences.add(pref1); + preferences.add(pref2); + doReturn(preferences).when(mSettingsInjector) + .getInjectedSettings(any(Context.class), anyInt()); + when(mFragment.getPreferenceManager().getContext()).thenReturn(mContext); + mController.displayPreference(mScreen); + + mController.updateState(mCategory); + + verify(mCategory).removeAll(); + verify(mCategory).addPreference(pref1); + verify(mCategory).addPreference(pref2); + } + + @Test + public void onLocationModeChanged_shouldRequestReloadInjectedSettigns() { + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mSettingsInjector).reloadStatusMessages(); + } +} diff --git a/tests/robotests/src/com/android/settings/location/LocationSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationSwitchBarControllerTest.java new file mode 100644 index 00000000000..73e8d2c8faf --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/LocationSwitchBarControllerTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2017 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 static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.widget.SwitchBar; +import com.android.settings.widget.ToggleSwitch; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class LocationSwitchBarControllerTest { + + @Mock + private SwitchBar mSwitchBar; + @Mock + private ToggleSwitch mSwitch; + @Mock + private LocationEnabler mEnabler; + + private Context mContext; + private LocationSwitchBarController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + ReflectionHelpers.setField(mSwitchBar, "mSwitch", mSwitch); + mController = spy(new LocationSwitchBarController( + mContext, mSwitchBar, new Lifecycle())); + ReflectionHelpers.setField(mController, "mLocationEnabler", mEnabler); + } + + @Test + public void onStart_shouldShowSwitchBarAndAddOnSwitchChangeListener() { + mController.onStart(); + + verify(mSwitchBar).show(); + verify(mSwitchBar).addOnSwitchChangeListener(mController); + } + + @Test + public void onStop_shouldHideSwitchBarAndRemoveOnSwitchChangeListener() { + mController.onStart(); + mController.onStop(); + + verify(mSwitchBar).hide(); + verify(mSwitchBar).removeOnSwitchChangeListener(mController); + } + + @Test + public void onSwitchChanged_switchChecked_shouldSetPreviousLocationMode() { + mController.onSwitchChanged(mSwitch, true); + + verify(mEnabler).setLocationMode( + android.provider.Settings.Secure.LOCATION_MODE_PREVIOUS); + } + + @Test + public void onSwitchChanged_switchUnchecked_shouldSetLocationModeOff() { + mController.onSwitchChanged(mSwitch, false); + + verify(mEnabler).setLocationMode(android.provider.Settings.Secure.LOCATION_MODE_OFF); + } + + @Test + public void onLocationModeChanged_hasEnforcedAdmin_shouldDisableSwitchByAdmin() { + final RestrictedLockUtils.EnforcedAdmin admin = + mock(RestrictedLockUtils.EnforcedAdmin.class); + doReturn(admin).when(mEnabler).getShareLocationEnforcedAdmin(anyInt()); + doReturn(false).when(mEnabler).hasShareLocationRestriction(anyInt()); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mSwitchBar).setDisabledByAdmin(admin); + } + + @Test + public void onLocationModeChanged_Restricted_shouldDisableSwitch() { + doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt()); + doReturn(true).when(mEnabler).hasShareLocationRestriction(anyInt()); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, true); + + verify(mSwitchBar).setEnabled(false); + } + + @Test + public void onLocationModeChanged_notRestricted_shouldEnableSwitch() { + doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt()); + doReturn(false).when(mEnabler).hasShareLocationRestriction(anyInt()); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mSwitchBar).setEnabled(true); + } + + @Test + public void onLocationModeChanged_locationOn_shouldCheckSwitch() { + doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt()); + doReturn(false).when(mEnabler).hasShareLocationRestriction(anyInt()); + when(mSwitch.isChecked()).thenReturn(false); + doReturn(true).when(mEnabler).isEnabled(anyInt()); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mSwitch).setChecked(true); + } + + @Test + public void onLocationModeChanged_locationOff_shouldUncheckSwitch() { + doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt()); + doReturn(false).when(mEnabler).hasShareLocationRestriction(anyInt()); + when(mSwitch.isChecked()).thenReturn(true); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_OFF, false); + + verify(mSwitch).setChecked(false); + } + +} diff --git a/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java new file mode 100644 index 00000000000..17f31d17748 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Bundle; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.TestConfig; +import com.android.settings.applications.InstalledAppDetails; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.widget.AppPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.location.RecentLocationApps; +import com.android.settingslib.location.RecentLocationApps.Request; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class RecentLocationRequestPreferenceControllerTest { + + @Mock + private LocationSettings mFragment; + @Mock + private PreferenceCategory mCategory; + @Mock + private PreferenceScreen mScreen; + @Mock + private RecentLocationApps mRecentLocationApps; + + private Context mContext; + private RecentLocationRequestPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mController = spy(new RecentLocationRequestPreferenceController( + mContext, mFragment, new Lifecycle(), mRecentLocationApps)); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mCategory); + final String key = mController.getPreferenceKey(); + when(mCategory.getKey()).thenReturn(key); + when(mCategory.getContext()).thenReturn(mContext); + } + + @Test + public void onLocationModeChanged_LocationOn_shouldEnablePreference() { + mController.displayPreference(mScreen); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); + + verify(mCategory).setEnabled(true); + } + + @Test + public void onLocationModeChanged_LocationOff_shouldDisablePreference() { + mController.displayPreference(mScreen); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_OFF, false); + + verify(mCategory).setEnabled(false); + } + + @Test + public void updateState_noRecentRequest_shouldRemoveAllAndAddBanner() { + doReturn(new ArrayList<>()).when(mRecentLocationApps).getAppList(); + mController.displayPreference(mScreen); + + mController.updateState(mCategory); + + verify(mCategory).removeAll(); + verify(mCategory).addPreference( + argThat(titleMatches(mContext.getString(R.string.location_no_recent_apps)))); + } + + @Test + public void updateState_hasRecentRequest_shouldRemoveAllAndAddInjectedSettings() { + final List requests = new ArrayList<>(); + final Request req1 = mock(Request.class); + final Request req2 = mock(Request.class); + requests.add(req1); + requests.add(req2); + doReturn(requests).when(mRecentLocationApps).getAppList(); + final String title = "testTitle"; + final AppPreference preference = mock(AppPreference.class); + when(preference.getTitle()).thenReturn(title); + doReturn(preference).when(mController) + .createAppPreference(any(Context.class), any(Request.class)); + mController.displayPreference(mScreen); + + mController.updateState(mCategory); + + verify(mCategory).removeAll(); + verify(mCategory, times(2)).addPreference(argThat(titleMatches(title))); + } + + @Test + public void createAppPreference_shouldAddClickListener() { + final Request request = mock(Request.class); + final AppPreference preference = mock(AppPreference.class); + doReturn(preference).when(mController) + .createAppPreference(any(Context.class)); + + mController.createAppPreference(mContext, request); + + verify(preference).setOnPreferenceClickListener( + any(RecentLocationRequestPreferenceController.PackageEntryClickedListener.class)); + } + + @Test + public void onPreferenceClick_shouldLaunchAppDetails() { + final SettingsActivity activity = mock(SettingsActivity.class); + when(mFragment.getActivity()).thenReturn(activity); + final List requests = new ArrayList<>(); + final Request request = mock(Request.class); + requests.add(request); + doReturn(requests).when(mRecentLocationApps).getAppList(); + final AppPreference preference = new AppPreference(mContext); + doReturn(preference).when(mController).createAppPreference(any(Context.class)); + mController.displayPreference(mScreen); + mController.updateState(mCategory); + + preference.performClick(); + + verify(activity).startPreferencePanelAsUser(any(), eq(InstalledAppDetails.class.getName()), + any(Bundle.class), anyInt(), any(), any()); + } + + private static ArgumentMatcher titleMatches(String expected) { + return preference -> TextUtils.equals(expected, preference.getTitle()); + } + +} diff --git a/tests/robotests/src/com/android/settings/location/ScanningSettingsTest.java b/tests/robotests/src/com/android/settings/location/ScanningSettingsTest.java index 47b81bf93a6..dff257bbf0b 100644 --- a/tests/robotests/src/com/android/settings/location/ScanningSettingsTest.java +++ b/tests/robotests/src/com/android/settings/location/ScanningSettingsTest.java @@ -34,13 +34,12 @@ import org.robolectric.annotation.Config; import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) -@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) public class ScanningSettingsTest { private Context mContext; private ScanningSettings mSettings; - @Before public void setUp() { mContext = RuntimeEnvironment.application; diff --git a/tests/robotests/src/com/android/settings/location/WifiScanningPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/WifiScanningPreferenceControllerTest.java new file mode 100644 index 00000000000..d8b6b755a67 --- /dev/null +++ b/tests/robotests/src/com/android/settings/location/WifiScanningPreferenceControllerTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +public class WifiScanningPreferenceControllerTest { + + @Mock + private SwitchPreference mPreference; + + private Context mContext; + private WifiScanningPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = new WifiScanningPreferenceController(mContext); + when(mPreference.getKey()).thenReturn(mController.getPreferenceKey()); + } + + @Test + public void updateState_wifiScanningEnabled_shouldCheckedPreference() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(true); + } + + @Test + public void updateState_wifiScanningDisabled_shouldUncheckedPreference() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0); + + mController.updateState(mPreference); + + verify(mPreference).setChecked(false); + } + + @Test + public void handlePreferenceTreeClick_checked_shouldEnableWifiScanning() { + when(mPreference.isChecked()).thenReturn(true); + + mController.handlePreferenceTreeClick(mPreference); + + assertThat(Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0)).isEqualTo(1); + + } + + @Test + public void handlePreferenceTreeClick_unchecked_shouldDisableWifiScanning() { + when(mPreference.isChecked()).thenReturn(false); + + mController.handlePreferenceTreeClick(mPreference); + + assertThat(Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1)).isEqualTo(0); + + } +}