diff --git a/res/layout/storage_summary_donut.xml b/res/layout/storage_summary_donut.xml index ce7e272a85e..b183b2e9917 100644 --- a/res/layout/storage_summary_donut.xml +++ b/res/layout/storage_summary_donut.xml @@ -17,7 +17,7 @@ diff --git a/res/values/config.xml b/res/values/config.xml index 8e55ea95e36..f46134f301b 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -134,4 +134,9 @@ true + + false diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml index f81fd791fde..41d3e9b6f9a 100644 --- a/res/xml/connected_devices_advanced.xml +++ b/res/xml/connected_devices_advanced.xml @@ -32,12 +32,14 @@ android:title="@string/nfc_quick_toggle_title" android:icon="@drawable/ic_nfc" android:summary="@string/nfc_quick_toggle_summary" + settings:controller="com.android.settings.nfc.NfcPreferenceController" android:order="-7"/> diff --git a/res/xml/my_device_info.xml b/res/xml/my_device_info.xml index 213ff1c1b7b..3dac8b879f2 100644 --- a/res/xml/my_device_info.xml +++ b/res/xml/my_device_info.xml @@ -19,8 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:key="my_device_info_pref_screen" - android:title="@string/about_settings" - settings:initialExpandedChildrenCount="7"> + android:title="@string/about_settings"> mCandidates; private final SettingsObserver mSettingsObserver; public VibrationPreferenceFragment() { mCandidates = new ArrayMap<>(); - mCandidates.put(KEY_INTENSITY_OFF, - new VibrationIntensityCandidateInfo(KEY_INTENSITY_OFF, - R.string.accessibility_vibration_intensity_off, - Vibrator.VIBRATION_INTENSITY_OFF)); - mCandidates.put(KEY_INTENSITY_LOW, - new VibrationIntensityCandidateInfo(KEY_INTENSITY_LOW, - R.string.accessibility_vibration_intensity_low, - Vibrator.VIBRATION_INTENSITY_LOW)); - mCandidates.put(KEY_INTENSITY_MEDIUM, - new VibrationIntensityCandidateInfo(KEY_INTENSITY_MEDIUM, - R.string.accessibility_vibration_intensity_medium, - Vibrator.VIBRATION_INTENSITY_MEDIUM)); - mCandidates.put(KEY_INTENSITY_HIGH, - new VibrationIntensityCandidateInfo(KEY_INTENSITY_HIGH, - R.string.accessibility_vibration_intensity_high, - Vibrator.VIBRATION_INTENSITY_HIGH)); mSettingsObserver = new SettingsObserver(); } @@ -82,6 +71,39 @@ public abstract class VibrationPreferenceFragment extends RadioButtonPickerFragm public void onAttach(Context context) { super.onAttach(context); mSettingsObserver.register(); + if (mCandidates.isEmpty()) { + loadCandidates(context); + } + } + + private void loadCandidates(Context context) { + final boolean supportsMultipleIntensities = context.getResources().getBoolean( + R.bool.config_vibration_supports_multiple_intensities); + if (supportsMultipleIntensities) { + mCandidates.put(KEY_INTENSITY_OFF, + new VibrationIntensityCandidateInfo(KEY_INTENSITY_OFF, + R.string.accessibility_vibration_intensity_off, + Vibrator.VIBRATION_INTENSITY_OFF)); + mCandidates.put(KEY_INTENSITY_LOW, + new VibrationIntensityCandidateInfo(KEY_INTENSITY_LOW, + R.string.accessibility_vibration_intensity_low, + Vibrator.VIBRATION_INTENSITY_LOW)); + mCandidates.put(KEY_INTENSITY_MEDIUM, + new VibrationIntensityCandidateInfo(KEY_INTENSITY_MEDIUM, + R.string.accessibility_vibration_intensity_medium, + Vibrator.VIBRATION_INTENSITY_MEDIUM)); + mCandidates.put(KEY_INTENSITY_HIGH, + new VibrationIntensityCandidateInfo(KEY_INTENSITY_HIGH, + R.string.accessibility_vibration_intensity_high, + Vibrator.VIBRATION_INTENSITY_HIGH)); + } else { + mCandidates.put(KEY_INTENSITY_OFF, + new VibrationIntensityCandidateInfo(KEY_INTENSITY_OFF, + R.string.switch_off_text, Vibrator.VIBRATION_INTENSITY_OFF)); + mCandidates.put(KEY_INTENSITY_ON, + new VibrationIntensityCandidateInfo(KEY_INTENSITY_ON, + R.string.switch_on_text, getDefaultVibrationIntensity())); + } } @Override @@ -105,6 +127,24 @@ public abstract class VibrationPreferenceFragment extends RadioButtonPickerFragm */ protected void onVibrationIntensitySelected(int intensity) { } + /** + * Play a vibration effect with intensity just selected by user + */ + protected void playVibrationPreview() { + Vibrator vibrator = getContext().getSystemService(Vibrator.class); + VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + AudioAttributes.Builder builder = new AudioAttributes.Builder(); + builder.setUsage(getPreviewVibrationAudioAttributesUsage()); + vibrator.vibrate(effect, builder.build()); + } + + /** + * Get the AudioAttributes usage for vibration preview. + */ + protected int getPreviewVibrationAudioAttributesUsage() { + return AudioAttributes.USAGE_UNKNOWN; + } + @Override protected List getCandidates() { List candidates = new ArrayList<>(mCandidates.values()); @@ -118,7 +158,10 @@ public abstract class VibrationPreferenceFragment extends RadioButtonPickerFragm final int vibrationIntensity = Settings.System.getInt(getContext().getContentResolver(), getVibrationIntensitySetting(), getDefaultVibrationIntensity()); for (VibrationIntensityCandidateInfo candidate : mCandidates.values()) { - if (candidate.getIntensity() == vibrationIntensity) { + final boolean matchesIntensity = candidate.getIntensity() == vibrationIntensity; + final boolean matchesOn = candidate.getKey().equals(KEY_INTENSITY_ON) + && vibrationIntensity != Vibrator.VIBRATION_INTENSITY_OFF; + if (matchesIntensity || matchesOn) { return candidate.getKey(); } } @@ -189,6 +232,7 @@ public abstract class VibrationPreferenceFragment extends RadioButtonPickerFragm @Override public void onChange(boolean selfChange, Uri uri) { updateCandidates(); + playVibrationPreview(); } } } diff --git a/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java b/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java index da759a33e5e..9a9f45588e7 100644 --- a/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java +++ b/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java @@ -17,8 +17,6 @@ package com.android.settings.bluetooth; import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; -import static androidx.slice.builders.ListBuilder.ICON_IMAGE; - import android.annotation.ColorInt; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; @@ -28,6 +26,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.provider.SettingsSlicesContract; +import android.support.v4.graphics.drawable.IconCompat; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; @@ -42,8 +41,6 @@ import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; import androidx.slice.builders.SliceAction; -import android.support.v4.graphics.drawable.IconCompat; - public class BluetoothSliceBuilder { private static final String TAG = "BluetoothSliceBuilder"; @@ -71,7 +68,8 @@ public class BluetoothSliceBuilder { INTENT_FILTER.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); } - private BluetoothSliceBuilder() {} + private BluetoothSliceBuilder() { + } /** * Return a Bluetooth Slice bound to {@link #BLUETOOTH_URI}. @@ -80,7 +78,7 @@ public class BluetoothSliceBuilder { * Bluetooth. */ public static Slice getSlice(Context context) { - final boolean isBluetoothEnabled = isBluetoothEnabled(context); + final boolean isBluetoothEnabled = isBluetoothEnabled(); final CharSequence title = context.getText(R.string.bluetooth_settings); final IconCompat icon = IconCompat.createWithResource(context, R.drawable.ic_settings_bluetooth); @@ -115,9 +113,8 @@ public class BluetoothSliceBuilder { // handle it. } - private static boolean isBluetoothEnabled(Context context) { - final LocalBluetoothAdapter adapter = LocalBluetoothManager.getInstance(context, - null /* callback */).getBluetoothAdapter(); + private static boolean isBluetoothEnabled() { + final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); return adapter.isEnabled(); } diff --git a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceController.java b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceController.java index eb93eb178eb..3976c92f7b3 100644 --- a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceController.java +++ b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceController.java @@ -50,7 +50,7 @@ public class AdvancedConnectedDeviceController extends BasePreferenceController */ public static int getConnectedDevicesSummaryResourceId(Context context) { final NfcPreferenceController nfcPreferenceController = - new NfcPreferenceController(context); + new NfcPreferenceController(context, NfcPreferenceController.KEY_TOGGLE_NFC); return getConnectedDevicesSummaryResourceId(nfcPreferenceController, isDrivingModeAvailable(context)); diff --git a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java index a64a430e638..9795e9faacb 100644 --- a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java +++ b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java @@ -24,7 +24,6 @@ import com.android.settings.R; import com.android.settings.bluetooth.BluetoothFilesPreferenceController; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.nfc.AndroidBeamPreferenceController; -import com.android.settings.nfc.NfcPreferenceController; import com.android.settings.print.PrintSettingPreferenceController; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; @@ -70,25 +69,15 @@ public class AdvancedConnectedDeviceDashboardFragment extends DashboardFragment Lifecycle lifecycle) { final List controllers = new ArrayList<>(); - final AndroidBeamPreferenceController beamPreferenceController = - new AndroidBeamPreferenceController(context); - controllers.add(beamPreferenceController); - controllers.add(new BluetoothFilesPreferenceController(context)); controllers.add(new BluetoothOnWhileDrivingPreferenceController(context)); final PrintSettingPreferenceController printerController = new PrintSettingPreferenceController(context); - final NfcPreferenceController nfcPreferenceController = - new NfcPreferenceController(context); if (lifecycle != null) { - lifecycle.addObserver(beamPreferenceController); lifecycle.addObserver(printerController); - lifecycle.addObserver(nfcPreferenceController); } - - controllers.add(nfcPreferenceController); controllers.add(printerController); return controllers; diff --git a/src/com/android/settings/core/BasePreferenceController.java b/src/com/android/settings/core/BasePreferenceController.java index c2d1cf52fa1..efd944866f8 100644 --- a/src/com/android/settings/core/BasePreferenceController.java +++ b/src/com/android/settings/core/BasePreferenceController.java @@ -246,6 +246,16 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl return false; } + /** + * @return {@code true} if the setting update asynchronously. + *

+ * For example, a Wifi controller would return true, because it needs to update the radio + * and wait for it to turn on. + */ + public boolean hasAsyncUpdate() { + return false; + } + /** * Updates non-indexable keys for search provider. * diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java index 8e56b12ec8f..e8f9771898a 100644 --- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java +++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java @@ -57,14 +57,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public class StorageDashboardFragment extends DashboardFragment - implements +public class StorageDashboardFragment extends DashboardFragment implements LoaderManager.LoaderCallbacks> { private static final String TAG = "StorageDashboardFrag"; private static final int STORAGE_JOB_ID = 0; private static final int ICON_JOB_ID = 1; private static final int VOLUME_SIZE_JOB_ID = 2; - private static final int OPTIONS_MENU_MIGRATE_DATA = 100; private VolumeInfo mVolume; private PrivateStorageInfo mStorageInfo; diff --git a/src/com/android/settings/fuelgauge/SmartBatteryPreferenceController.java b/src/com/android/settings/fuelgauge/SmartBatteryPreferenceController.java index 3e80c352198..8b7cde80679 100644 --- a/src/com/android/settings/fuelgauge/SmartBatteryPreferenceController.java +++ b/src/com/android/settings/fuelgauge/SmartBatteryPreferenceController.java @@ -21,6 +21,7 @@ import android.content.Context; import android.provider.Settings; import android.support.v14.preference.SwitchPreference; import android.support.v7.preference.Preference; +import android.text.TextUtils; import com.android.settings.core.BasePreferenceController; import com.android.settings.overlay.FeatureFactory; @@ -48,6 +49,11 @@ public class SmartBatteryPreferenceController extends BasePreferenceController i : UNSUPPORTED_ON_DEVICE; } + @Override + public boolean isSliceable() { + return TextUtils.equals(getPreferenceKey(), "smart_battery"); + } + @Override public void updateState(Preference preference) { super.updateState(preference); diff --git a/src/com/android/settings/nfc/AndroidBeamEnabler.java b/src/com/android/settings/nfc/AndroidBeamEnabler.java index 66e42b64188..18087753531 100644 --- a/src/com/android/settings/nfc/AndroidBeamEnabler.java +++ b/src/com/android/settings/nfc/AndroidBeamEnabler.java @@ -20,7 +20,6 @@ import android.content.Context; import android.nfc.NfcAdapter; import android.os.UserHandle; import android.os.UserManager; -import android.support.v7.preference.Preference; import com.android.settings.R; import com.android.settingslib.RestrictedLockUtils; @@ -36,18 +35,14 @@ public class AndroidBeamEnabler extends BaseNfcEnabler { public AndroidBeamEnabler(Context context, RestrictedPreference preference) { super(context); - mPreference = preference; - mBeamDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(context, UserManager.DISALLOW_OUTGOING_BEAM, UserHandle.myUserId()); - if (!isNfcAvailable()) { // NFC is not supported mPreference.setEnabled(false); return; } - if (mBeamDisallowedBySystem) { mPreference.setEnabled(false); } diff --git a/src/com/android/settings/nfc/AndroidBeamPreferenceController.java b/src/com/android/settings/nfc/AndroidBeamPreferenceController.java index 6ae7fe5ccf2..b4026de15af 100644 --- a/src/com/android/settings/nfc/AndroidBeamPreferenceController.java +++ b/src/com/android/settings/nfc/AndroidBeamPreferenceController.java @@ -16,34 +16,75 @@ package com.android.settings.nfc; import android.content.Context; +import android.nfc.NfcAdapter; +import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; -import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.BasePreferenceController; import com.android.settingslib.RestrictedPreference; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; -public class AndroidBeamPreferenceController extends BaseNfcPreferenceController { +import java.util.List; + +public class AndroidBeamPreferenceController extends BasePreferenceController + implements LifecycleObserver, OnResume, OnPause { public static final String KEY_ANDROID_BEAM_SETTINGS = "android_beam_settings"; + private final NfcAdapter mNfcAdapter; + private AndroidBeamEnabler mAndroidBeamEnabler; + private NfcAirplaneModeObserver mAirplaneModeObserver; - public AndroidBeamPreferenceController(Context context) { - super(context); + public AndroidBeamPreferenceController(Context context, String key) { + super(context, key); + mNfcAdapter = NfcAdapter.getDefaultAdapter(context); } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); if (!isAvailable()) { + mAndroidBeamEnabler = null; return; } - mNfcEnabler = new AndroidBeamEnabler(mContext, (RestrictedPreference) mPreference); + final RestrictedPreference restrictedPreference = + (RestrictedPreference) screen.findPreference(getPreferenceKey()); + mAndroidBeamEnabler = new AndroidBeamEnabler(mContext, restrictedPreference); + + // Manually set dependencies for NFC when not toggleable. + if (!NfcPreferenceController.isToggleableInAirplaneMode(mContext)) { + mAirplaneModeObserver = new NfcAirplaneModeObserver(mContext, mNfcAdapter, + (Preference) restrictedPreference); + } } @Override - public String getPreferenceKey() { - return KEY_ANDROID_BEAM_SETTINGS; + @AvailabilityStatus + public int getAvailabilityStatus() { + return mNfcAdapter != null + ? AVAILABLE + : UNSUPPORTED_ON_DEVICE; + } + + @Override + public void onResume() { + if (mAirplaneModeObserver != null) { + mAirplaneModeObserver.register(); + } + if (mAndroidBeamEnabler != null) { + mAndroidBeamEnabler.resume(); + } + } + + @Override + public void onPause() { + if (mAirplaneModeObserver != null) { + mAirplaneModeObserver.unregister(); + } + if (mAndroidBeamEnabler != null) { + mAndroidBeamEnabler.pause(); + } } } diff --git a/src/com/android/settings/nfc/BaseNfcPreferenceController.java b/src/com/android/settings/nfc/BaseNfcPreferenceController.java deleted file mode 100644 index 33d75fab672..00000000000 --- a/src/com/android/settings/nfc/BaseNfcPreferenceController.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2018 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.nfc; - -import android.content.Context; -import android.database.ContentObserver; -import android.net.Uri; -import android.nfc.NfcAdapter; -import android.nfc.NfcManager; -import android.os.Handler; -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; -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 abstract class BaseNfcPreferenceController extends AbstractPreferenceController - implements PreferenceControllerMixin, LifecycleObserver, OnResume, OnPause { - - protected BaseNfcEnabler mNfcEnabler; - private NfcAdapter mNfcAdapter; - private int mAirplaneMode; - private AirplaneModeObserver mAirplaneModeObserver; - protected Preference mPreference; - - public BaseNfcPreferenceController(Context context) { - super(context); - mNfcAdapter = NfcAdapter.getDefaultAdapter(context); - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - - if (!isAvailable()) { - mNfcEnabler = null; - return; - } - - mPreference = screen.findPreference(getPreferenceKey()); - - // Manually set dependencies for NFC when not toggleable. - if (!isToggleableInAirplaneMode(mContext)) { - mAirplaneModeObserver = new AirplaneModeObserver(); - updateNfcPreference(); - } - } - - @Override - public void updateNonIndexableKeys(List keys) { - if (!isAvailable()) { - keys.add(getPreferenceKey()); - } - } - - @Override - public boolean isAvailable() { - return mNfcAdapter != null; - } - - public abstract String getPreferenceKey(); - - @Override - public void onResume() { - if (mAirplaneModeObserver != null) { - mAirplaneModeObserver.register(); - } - if (mNfcEnabler != null) { - mNfcEnabler.resume(); - } - } - - @Override - public void onPause() { - if (mAirplaneModeObserver != null) { - mAirplaneModeObserver.unregister(); - } - if (mNfcEnabler != null) { - mNfcEnabler.pause(); - } - } - - private void updateNfcPreference() { - final int airplaneMode = Settings.Global.getInt( - mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, mAirplaneMode); - if (airplaneMode == mAirplaneMode) { - return; - } - mAirplaneMode = airplaneMode; - boolean toggleable = mAirplaneMode != 1; - if (toggleable) { - mNfcAdapter.enable(); - } else { - mNfcAdapter.disable(); - } - mPreference.setEnabled(toggleable); - } - - public static boolean isToggleableInAirplaneMode(Context context) { - String toggleable = Settings.Global.getString(context.getContentResolver(), - Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); - return toggleable != null && toggleable.contains(Settings.Global.RADIO_NFC); - } - - private final class AirplaneModeObserver extends ContentObserver { - private final Uri AIRPLANE_MODE_URI = - Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON); - - private AirplaneModeObserver() { - super(new Handler()); - } - - public void register() { - mContext.getContentResolver().registerContentObserver(AIRPLANE_MODE_URI, false, this); - } - - public void unregister() { - mContext.getContentResolver().unregisterContentObserver(this); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - super.onChange(selfChange, uri); - updateNfcPreference(); - } - } - -} diff --git a/src/com/android/settings/nfc/NfcAirplaneModeObserver.java b/src/com/android/settings/nfc/NfcAirplaneModeObserver.java new file mode 100644 index 00000000000..723d814391e --- /dev/null +++ b/src/com/android/settings/nfc/NfcAirplaneModeObserver.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 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.nfc; + +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.nfc.NfcAdapter; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; + +/** + * NfcAirplaneModeObserver is a helper to manage the Nfc on/off when airplane mode status + * is changed. + */ +public class NfcAirplaneModeObserver extends ContentObserver { + + private final Context mContext; + private final NfcAdapter mNfcAdapter; + private final Preference mPreference; + private int mAirplaneMode; + + @VisibleForTesting + final static Uri AIRPLANE_MODE_URI = + Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON); + + public NfcAirplaneModeObserver(Context context, NfcAdapter nfcAdapter, Preference preference) { + super(new Handler(Looper.getMainLooper())); + mContext = context; + mNfcAdapter = nfcAdapter; + mPreference = preference; + updateNfcPreference(); + } + + public void register() { + mContext.getContentResolver().registerContentObserver(AIRPLANE_MODE_URI, false, this); + } + + public void unregister() { + mContext.getContentResolver().unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + updateNfcPreference(); + } + + private void updateNfcPreference() { + final int airplaneMode = Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, mAirplaneMode); + if (airplaneMode == mAirplaneMode) { + return; + } + + mAirplaneMode = airplaneMode; + boolean toggleable = mAirplaneMode != 1; + if (toggleable) { + mNfcAdapter.enable(); + } else { + mNfcAdapter.disable(); + } + mPreference.setEnabled(toggleable); + } +} diff --git a/src/com/android/settings/nfc/NfcEnabler.java b/src/com/android/settings/nfc/NfcEnabler.java index 29cef993552..f39a0a1a466 100644 --- a/src/com/android/settings/nfc/NfcEnabler.java +++ b/src/com/android/settings/nfc/NfcEnabler.java @@ -18,52 +18,20 @@ package com.android.settings.nfc; import android.content.Context; import android.nfc.NfcAdapter; -import android.support.v7.preference.Preference; import android.support.v14.preference.SwitchPreference; /** * NfcEnabler is a helper to manage the Nfc on/off checkbox preference. It turns on/off Nfc * and ensures the summary of the preference reflects the current state. */ -public class NfcEnabler extends BaseNfcEnabler implements Preference.OnPreferenceChangeListener { +public class NfcEnabler extends BaseNfcEnabler { private final SwitchPreference mPreference; public NfcEnabler(Context context, SwitchPreference preference) { super(context); - mPreference = preference; } - public void resume() { - super.resume(); - if (isNfcAvailable()) { - mPreference.setOnPreferenceChangeListener(this); - } - } - - public void pause() { - super.pause(); - if (isNfcAvailable()) { - mPreference.setOnPreferenceChangeListener(null); - } - } - - public boolean onPreferenceChange(Preference preference, Object value) { - // Turn NFC on/off - - final boolean desiredState = (Boolean) value; - mPreference.setChecked(desiredState); - mPreference.setEnabled(false); - - if (desiredState) { - mNfcAdapter.enable(); - } else { - mNfcAdapter.disable(); - } - - return false; - } - @Override protected void handleNfcStateChanged(int newState) { switch (newState) { diff --git a/src/com/android/settings/nfc/NfcPreferenceController.java b/src/com/android/settings/nfc/NfcPreferenceController.java index a0678e0afab..a5d7c768ecf 100644 --- a/src/com/android/settings/nfc/NfcPreferenceController.java +++ b/src/com/android/settings/nfc/NfcPreferenceController.java @@ -16,35 +16,119 @@ package com.android.settings.nfc; import android.content.Context; +import android.content.IntentFilter; +import android.nfc.NfcAdapter; +import android.provider.Settings; +import android.text.TextUtils; + +import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; import android.support.v14.preference.SwitchPreference; -import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.TogglePreferenceController; 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 NfcPreferenceController extends BaseNfcPreferenceController { +public class NfcPreferenceController extends TogglePreferenceController + implements LifecycleObserver, OnResume, OnPause { public static final String KEY_TOGGLE_NFC = "toggle_nfc"; + private final NfcAdapter mNfcAdapter; + private NfcEnabler mNfcEnabler; + private NfcAirplaneModeObserver mAirplaneModeObserver; - public NfcPreferenceController(Context context) { - super(context); + public NfcPreferenceController(Context context, String key) { + super(context, key); + mNfcAdapter = NfcAdapter.getDefaultAdapter(context); } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); if (!isAvailable()) { + mNfcEnabler = null; return; } - mNfcEnabler = new NfcEnabler(mContext, (SwitchPreference) mPreference); + final SwitchPreference switchPreference = + (SwitchPreference) screen.findPreference(getPreferenceKey()); + + mNfcEnabler = new NfcEnabler(mContext, switchPreference); + + // Manually set dependencies for NFC when not toggleable. + if (!isToggleableInAirplaneMode(mContext)) { + mAirplaneModeObserver = new NfcAirplaneModeObserver(mContext, + mNfcAdapter, (Preference) switchPreference); + } } @Override - public String getPreferenceKey() { - return KEY_TOGGLE_NFC; + public boolean isChecked() { + return mNfcAdapter.isEnabled(); + } + + @Override + public boolean setChecked(boolean isChecked) { + if (isChecked) { + mNfcAdapter.enable(); + } else { + mNfcAdapter.disable(); + } + return true; + } + + @Override + @AvailabilityStatus + public int getAvailabilityStatus() { + return mNfcAdapter != null + ? AVAILABLE + : UNSUPPORTED_ON_DEVICE; + } + + @Override + public IntentFilter getIntentFilter() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED); + filter.addAction(NfcAdapter.EXTRA_ADAPTER_STATE); + return filter; + } + + @Override + public boolean hasAsyncUpdate() { + return true; + } + + @Override + public boolean isSliceable() { + return TextUtils.equals(getPreferenceKey(), KEY_TOGGLE_NFC); + } + + @Override + public void onResume() { + if (mAirplaneModeObserver != null) { + mAirplaneModeObserver.register(); + } + if (mNfcEnabler != null) { + mNfcEnabler.resume(); + } + } + + @Override + public void onPause() { + if (mAirplaneModeObserver != null) { + mAirplaneModeObserver.unregister(); + } + if (mNfcEnabler != null) { + mNfcEnabler.pause(); + } + } + + public static boolean isToggleableInAirplaneMode(Context context) { + final String toggleable = Settings.Global.getString(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); + return toggleable != null && toggleable.contains(Settings.Global.RADIO_NFC); } } diff --git a/src/com/android/settings/notification/AlarmVolumePreferenceController.java b/src/com/android/settings/notification/AlarmVolumePreferenceController.java index 3c238aa7f29..92a1e907766 100644 --- a/src/com/android/settings/notification/AlarmVolumePreferenceController.java +++ b/src/com/android/settings/notification/AlarmVolumePreferenceController.java @@ -18,6 +18,7 @@ package com.android.settings.notification; import android.content.Context; import android.media.AudioManager; +import android.text.TextUtils; import com.android.settings.R; @@ -36,6 +37,11 @@ public class AlarmVolumePreferenceController extends && !mHelper.isSingleVolume() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } + @Override + public boolean isSliceable() { + return TextUtils.equals(getPreferenceKey(), "alarm_volume"); + } + @Override public String getPreferenceKey() { return KEY_ALARM_VOLUME; diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index 47736dfac2e..841247b1d64 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -23,23 +23,23 @@ import android.content.ContentResolver; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; +import android.os.StrictMode; import android.provider.Settings; import android.provider.SettingsSlicesContract; import android.support.annotation.VisibleForTesting; -import android.support.v4.graphics.drawable.IconCompat; import android.text.TextUtils; import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Log; import android.util.Pair; -import com.android.settings.location.LocationSliceBuilder; -import com.android.settings.overlay.FeatureFactory; +import com.android.settings.bluetooth.BluetoothSliceBuilder; import com.android.settings.core.BasePreferenceController; +import com.android.settings.location.LocationSliceBuilder; +import com.android.settings.notification.ZenModeSliceBuilder; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.wifi.WifiSliceBuilder; import com.android.settings.wifi.calling.WifiCallingSliceHelper; -import com.android.settings.bluetooth.BluetoothSliceBuilder; -import com.android.settings.notification.ZenModeSliceBuilder; import com.android.settingslib.SliceBroadcastRelay; import com.android.settingslib.utils.ThreadUtils; @@ -150,7 +150,7 @@ public class SettingsSliceProvider extends SliceProvider { @Override public void onSlicePinned(Uri sliceUri) { if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) { - registerIntentToUri(WifiSliceBuilder.INTENT_FILTER , sliceUri); + registerIntentToUri(WifiSliceBuilder.INTENT_FILTER, sliceUri); return; } else if (ZenModeSliceBuilder.ZEN_MODE_URI.equals(sliceUri)) { registerIntentToUri(ZenModeSliceBuilder.INTENT_FILTER, sliceUri); @@ -176,41 +176,51 @@ public class SettingsSliceProvider extends SliceProvider { @Override public Slice onBindSlice(Uri sliceUri) { - final Set blockedKeys = getBlockedKeys(); - final String key = sliceUri.getLastPathSegment(); - if (blockedKeys.contains(key)) { - Log.e(TAG, "Requested blocked slice with Uri: " + sliceUri); - return null; - } + final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + try { + if (!ThreadUtils.isMainThread()) { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .permitAll() + .build()); + } + final Set blockedKeys = getBlockedKeys(); + final String key = sliceUri.getLastPathSegment(); + if (blockedKeys.contains(key)) { + Log.e(TAG, "Requested blocked slice with Uri: " + sliceUri); + return null; + } - // If adding a new Slice, do not directly match Slice URIs. - // Use {@link SlicesDatabaseAccessor}. - if (WifiCallingSliceHelper.WIFI_CALLING_URI.equals(sliceUri)) { - return FeatureFactory.getFactory(getContext()) - .getSlicesFeatureProvider() - .getNewWifiCallingSliceHelper(getContext()) - .createWifiCallingSlice(sliceUri); - } else if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) { - return WifiSliceBuilder.getSlice(getContext()); - } else if (ZenModeSliceBuilder.ZEN_MODE_URI.equals(sliceUri)) { - return ZenModeSliceBuilder.getSlice(getContext()); - } else if (BluetoothSliceBuilder.BLUETOOTH_URI.equals(sliceUri)) { - return BluetoothSliceBuilder.getSlice(getContext()); - } else if (LocationSliceBuilder.LOCATION_URI.equals(sliceUri)) { - return LocationSliceBuilder.getSlice(getContext()); - } + // If adding a new Slice, do not directly match Slice URIs. + // Use {@link SlicesDatabaseAccessor}. + if (WifiCallingSliceHelper.WIFI_CALLING_URI.equals(sliceUri)) { + return FeatureFactory.getFactory(getContext()) + .getSlicesFeatureProvider() + .getNewWifiCallingSliceHelper(getContext()) + .createWifiCallingSlice(sliceUri); + } else if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) { + return WifiSliceBuilder.getSlice(getContext()); + } else if (ZenModeSliceBuilder.ZEN_MODE_URI.equals(sliceUri)) { + return ZenModeSliceBuilder.getSlice(getContext()); + } else if (BluetoothSliceBuilder.BLUETOOTH_URI.equals(sliceUri)) { + return BluetoothSliceBuilder.getSlice(getContext()); + } else if (LocationSliceBuilder.LOCATION_URI.equals(sliceUri)) { + return LocationSliceBuilder.getSlice(getContext()); + } - SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri); - if (cachedSliceData == null) { - loadSliceInBackground(sliceUri); - return getSliceStub(sliceUri); - } + SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri); + if (cachedSliceData == null) { + loadSliceInBackground(sliceUri); + return getSliceStub(sliceUri); + } - // Remove the SliceData from the cache after it has been used to prevent a memory-leak. - if (!mSliceDataCache.containsKey(sliceUri)) { - mSliceWeakDataCache.remove(sliceUri); + // Remove the SliceData from the cache after it has been used to prevent a memory-leak. + if (!mSliceDataCache.containsKey(sliceUri)) { + mSliceWeakDataCache.remove(sliceUri); + } + return SliceBuilderUtils.buildSlice(getContext(), cachedSliceData); + } finally { + StrictMode.setThreadPolicy(oldPolicy); } - return SliceBuilderUtils.buildSlice(getContext(), cachedSliceData); } /** diff --git a/src/com/android/settings/slices/SliceBroadcastReceiver.java b/src/com/android/settings/slices/SliceBroadcastReceiver.java index 80e3e3c1ef1..d81734a974d 100644 --- a/src/com/android/settings/slices/SliceBroadcastReceiver.java +++ b/src/com/android/settings/slices/SliceBroadcastReceiver.java @@ -27,6 +27,7 @@ import static com.android.settings.wifi.WifiSliceBuilder.ACTION_WIFI_SLICE_CHANG import android.app.slice.Slice; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -106,7 +107,9 @@ public class SliceBroadcastReceiver extends BroadcastReceiver { if (!controller.isAvailable()) { Log.w(TAG, "Can't update " + key + " since the setting is unavailable"); - updateUri(context, key, isPlatformSlice); + if (!controller.hasAsyncUpdate()) { + updateUri(context, key, isPlatformSlice); + } return; } @@ -115,7 +118,9 @@ public class SliceBroadcastReceiver extends BroadcastReceiver { final TogglePreferenceController toggleController = (TogglePreferenceController) controller; toggleController.setChecked(isChecked); logSliceValueChange(context, key, isChecked ? 1 : 0); - updateUri(context, key, isPlatformSlice); + if (!controller.hasAsyncUpdate()) { + updateUri(context, key, isPlatformSlice); + } } private void handleSliderAction(Context context, String key, int newPosition, @@ -151,6 +156,7 @@ public class SliceBroadcastReceiver extends BroadcastReceiver { sliderController.setSliderPosition(newPosition); logSliceValueChange(context, key, newPosition); + updateUri(context, key, isPlatformSlice); } /** @@ -173,8 +179,15 @@ public class SliceBroadcastReceiver extends BroadcastReceiver { } private void updateUri(Context context, String key, boolean isPlatformDefined) { - final String path = SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key; - final Uri uri = SliceBuilderUtils.getUri(path, isPlatformDefined); + final String authority = isPlatformDefined + ? SettingsSlicesContract.AUTHORITY + : SettingsSliceProvider.SLICE_AUTHORITY; + final Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); context.getContentResolver().notifyChange(uri, null /* observer */); } } diff --git a/tests/robotests/src/com/android/settings/accessibility/MagnificationGesturesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/MagnificationGesturesPreferenceControllerTest.java index cd0558afad7..abb1cf3641a 100644 --- a/tests/robotests/src/com/android/settings/accessibility/MagnificationGesturesPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/MagnificationGesturesPreferenceControllerTest.java @@ -114,4 +114,19 @@ public class MagnificationGesturesPreferenceControllerTest { Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, -1)) .isEqualTo(OFF); } + + @Test + public void isSliceableCorrectKey_returnsTrue() { + final MagnificationGesturesPreferenceController controller = + new MagnificationGesturesPreferenceController(mContext, + "screen_magnification_gestures_preference_screen"); + assertThat(controller.isSliceable()).isTrue(); + } + + @Test + public void isSliceableIncorrectKey_returnsFalse() { + final MagnificationGesturesPreferenceController controller = + new MagnificationGesturesPreferenceController(mContext, "bad_key"); + assertThat(controller.isSliceable()).isFalse(); + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/MagnificationNavbarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/MagnificationNavbarPreferenceControllerTest.java index bca81a21903..8a401d53284 100644 --- a/tests/robotests/src/com/android/settings/accessibility/MagnificationNavbarPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/MagnificationNavbarPreferenceControllerTest.java @@ -158,4 +158,19 @@ public class MagnificationNavbarPreferenceControllerTest { sIsApplicable = applicable; } } + + @Test + public void isSliceableCorrectKey_returnsTrue() { + final MagnificationNavbarPreferenceController controller = + new MagnificationNavbarPreferenceController(mContext, + "screen_magnification_navbar_preference_screen"); + assertThat(controller.isSliceable()).isTrue(); + } + + @Test + public void isSliceableIncorrectKey_returnsFalse() { + final MagnificationNavbarPreferenceController controller = + new MagnificationNavbarPreferenceController(mContext, "bad_key"); + assertThat(controller.isSliceable()).isFalse(); + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceControllerTest.java index 067f015e85e..aa9efc51f88 100644 --- a/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceControllerTest.java @@ -18,10 +18,12 @@ package com.android.settings.accessibility; import static android.provider.Settings.System.NOTIFICATION_VIBRATION_INTENSITY; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.arch.lifecycle.LifecycleOwner; import android.content.Context; +import android.content.res.Resources; import android.os.Vibrator; import android.provider.Settings; import android.support.v7.preference.Preference; @@ -48,6 +50,7 @@ public class NotificationVibrationIntensityPreferenceControllerTest { private LifecycleOwner mLifecycleOwner; private Lifecycle mLifecycle; private Context mContext; + private Resources mResources; private NotificationVibrationIntensityPreferenceController mController; private Preference mPreference; @@ -56,7 +59,11 @@ public class NotificationVibrationIntensityPreferenceControllerTest { MockitoAnnotations.initMocks(this); mLifecycleOwner = () -> mLifecycle; mLifecycle = new Lifecycle(mLifecycleOwner); - mContext = RuntimeEnvironment.application; + mContext = spy(RuntimeEnvironment.application); + mResources = spy(mContext.getResources()); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getBoolean(R.bool.config_vibration_supports_multiple_intensities)) + .thenReturn(true); mController = new NotificationVibrationIntensityPreferenceController(mContext) { @Override protected int getDefaultIntensity() { @@ -68,7 +75,6 @@ public class NotificationVibrationIntensityPreferenceControllerTest { mPreference.setSummary("Test"); when(mScreen.findPreference(mController.getPreferenceKey())) .thenReturn(mPreference); - mController.displayPreference(mScreen); } @Test @@ -80,7 +86,10 @@ public class NotificationVibrationIntensityPreferenceControllerTest { } @Test - public void updateState_shouldRefreshSummary() { + public void updateState_withMultipleIntensitySuport_shouldRefreshSummary() { + setSupportsMultipleIntensities(true); + showPreference(); + Settings.System.putInt(mContext.getContentResolver(), NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW); mController.updateState(mPreference); @@ -105,4 +114,43 @@ public class NotificationVibrationIntensityPreferenceControllerTest { assertThat(mPreference.getSummary()) .isEqualTo(mContext.getString(R.string.accessibility_vibration_intensity_off)); } + + @Test + public void updateState_withoutMultipleIntensitySupport_shouldRefreshSummary() { + setSupportsMultipleIntensities(false); + showPreference(); + + Settings.System.putInt(mContext.getContentResolver(), + NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW); + mController.updateState(mPreference); + assertThat(mPreference.getSummary()) + .isEqualTo(mContext.getString(R.string.switch_on_text)); + + Settings.System.putInt(mContext.getContentResolver(), + NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH); + mController.updateState(mPreference); + assertThat(mPreference.getSummary()) + .isEqualTo(mContext.getString(R.string.switch_on_text)); + + Settings.System.putInt(mContext.getContentResolver(), + NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_MEDIUM); + mController.updateState(mPreference); + assertThat(mPreference.getSummary()) + .isEqualTo(mContext.getString(R.string.switch_on_text)); + + Settings.System.putInt(mContext.getContentResolver(), + NOTIFICATION_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF); + mController.updateState(mPreference); + assertThat(mPreference.getSummary()) + .isEqualTo(mContext.getString(R.string.switch_off_text)); + } + + private void setSupportsMultipleIntensities(boolean hasSupport) { + when(mResources.getBoolean(R.bool.config_vibration_supports_multiple_intensities)) + .thenReturn(hasSupport); + } + + private void showPreference() { + mController.displayPreference(mScreen); + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/VibrationPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/VibrationPreferenceFragmentTest.java index 8550cf98916..ecd2ff9ce57 100644 --- a/tests/robotests/src/com/android/settings/accessibility/VibrationPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/VibrationPreferenceFragmentTest.java @@ -20,16 +20,19 @@ import static com.android.settings.accessibility.VibrationPreferenceFragment.KEY import static com.android.settings.accessibility.VibrationPreferenceFragment.KEY_INTENSITY_LOW; import static com.android.settings.accessibility.VibrationPreferenceFragment.KEY_INTENSITY_MEDIUM; import static com.android.settings.accessibility.VibrationPreferenceFragment.KEY_INTENSITY_OFF; +import static com.android.settings.accessibility.VibrationPreferenceFragment.KEY_INTENSITY_ON; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.app.Activity; import android.content.Context; +import android.content.res.Resources; import android.os.UserManager; import android.os.Vibrator; import android.provider.Settings; +import com.android.settings.R; import com.android.settings.accessibility.VibrationPreferenceFragment.VibrationIntensityCandidateInfo; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -58,12 +61,11 @@ public class VibrationPreferenceFragmentTest { INTENSITY_TO_KEY.put(Vibrator.VIBRATION_INTENSITY_HIGH, KEY_INTENSITY_HIGH); } - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Activity mActivity; @Mock private UserManager mUserManager; private Context mContext; + private Resources mResources; private TestVibrationPreferenceFragment mFragment; @Before @@ -71,16 +73,18 @@ public class VibrationPreferenceFragmentTest { MockitoAnnotations.initMocks(this); FakeFeatureFactory.setupForTest(); - mContext = RuntimeEnvironment.application; + mContext = spy(RuntimeEnvironment.application); + mResources = spy(mContext.getResources()); + when(mContext.getResources()).thenReturn(mResources); mFragment = spy(new TestVibrationPreferenceFragment()); - doReturn(mUserManager).when(mActivity).getSystemService(Context.USER_SERVICE); - doReturn(mContext).when(mFragment).getContext(); - mFragment.onAttach(mActivity); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); } @Test public void changeIntensitySetting_shouldResultInCorrespondingKey() { + setSupportsMultipleIntensities(true); + mFragment.onAttach(mContext); for (Map.Entry entry : INTENSITY_TO_KEY.entrySet()) { Settings.System.putInt(mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_INTENSITY, entry.getKey()); @@ -88,13 +92,38 @@ public class VibrationPreferenceFragmentTest { } } + @Test + public void changeIntensitySetting_WithoutMultipleIntensitySupport_shouldResultInOn() { + setSupportsMultipleIntensities(false); + mFragment.onAttach(mContext); + for (int intensity : INTENSITY_TO_KEY.keySet()) { + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.HAPTIC_FEEDBACK_INTENSITY, intensity); + final String expectedKey = intensity == Vibrator.VIBRATION_INTENSITY_OFF + ? KEY_INTENSITY_OFF + : KEY_INTENSITY_ON; + assertThat(mFragment.getDefaultKey()).isEqualTo(expectedKey); + } + } + @Test public void initialDefaultKey_shouldBeMedium() { + setSupportsMultipleIntensities(true); + mFragment.onAttach(mContext); assertThat(mFragment.getDefaultKey()).isEqualTo(KEY_INTENSITY_MEDIUM); } + @Test + public void initialDefaultKey_WithoutMultipleIntensitySupport_shouldBeOn() { + setSupportsMultipleIntensities(false); + mFragment.onAttach(mContext); + assertThat(mFragment.getDefaultKey()).isEqualTo(KEY_INTENSITY_ON); + } + @Test public void candidates_shouldBeSortedByIntensity() { + setSupportsMultipleIntensities(true); + mFragment.onAttach(mContext); final List candidates = mFragment.getCandidates(); assertThat(candidates.size()).isEqualTo(INTENSITY_TO_KEY.size()); VibrationIntensityCandidateInfo prevCandidate = @@ -106,6 +135,11 @@ public class VibrationPreferenceFragmentTest { } } + private void setSupportsMultipleIntensities(boolean hasSupport) { + when(mResources.getBoolean(R.bool.config_vibration_supports_multiple_intensities)) + .thenReturn(hasSupport); + } + private class TestVibrationPreferenceFragment extends VibrationPreferenceFragment { @Override protected int getPreferenceScreenResId() { @@ -129,5 +163,10 @@ public class VibrationPreferenceFragmentTest { protected int getDefaultVibrationIntensity() { return Vibrator.VIBRATION_INTENSITY_MEDIUM; } + + @Override + public Context getContext() { + return mContext; + } } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceControllerTest.java index ceea81eae8e..ce5580242f2 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceControllerTest.java @@ -55,7 +55,8 @@ public class AdvancedConnectedDeviceControllerTest { mContext = spy(RuntimeEnvironment.application); mContentResolver = mContext.getContentResolver(); - mNfcController = new NfcPreferenceController(mContext); + mNfcController = new NfcPreferenceController(mContext, + NfcPreferenceController.KEY_TOGGLE_NFC); mShadowNfcAdapter = shadowOf(ShadowNfcAdapter.getNfcAdapter(mContext)); } diff --git a/tests/robotests/src/com/android/settings/core/BasePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/core/BasePreferenceControllerTest.java index eec4e37ccf2..ad2dabb1b7b 100644 --- a/tests/robotests/src/com/android/settings/core/BasePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/core/BasePreferenceControllerTest.java @@ -134,6 +134,11 @@ public class BasePreferenceControllerTest { assertThat(mPreferenceController.getSliceType()).isEqualTo(SliceData.SliceType.INTENT); } + @Test + public void hasAsyncUpdate_shouldReturnFalse() { + assertThat(mPreferenceController.hasAsyncUpdate()).isFalse(); + } + @Test public void settingAvailable_disabledOnDisplayPreference_preferenceEnabled() { final PreferenceScreen screen = mock(PreferenceScreen.class); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/SmartBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/SmartBatteryPreferenceControllerTest.java index 717ba8388b1..b1e9f51239b 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/SmartBatteryPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/SmartBatteryPreferenceControllerTest.java @@ -115,4 +115,11 @@ public class SmartBatteryPreferenceControllerTest { return Settings.Global.getInt(mContentResolver, Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, ON); } + + @Test + public void isSliceableCorrectKey_returnsTrue() { + final SmartBatteryPreferenceController controller = + new SmartBatteryPreferenceController(mContext); + assertThat(controller.isSliceable()).isTrue(); + } } diff --git a/tests/robotests/src/com/android/settings/nfc/AndroidBeamPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/nfc/AndroidBeamPreferenceControllerTest.java index df5bb64c1e8..8232afefd5f 100644 --- a/tests/robotests/src/com/android/settings/nfc/AndroidBeamPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/nfc/AndroidBeamPreferenceControllerTest.java @@ -72,7 +72,8 @@ public class AndroidBeamPreferenceControllerTest { UserManager.DISALLOW_OUTGOING_BEAM, UserHandle.myUserId())).thenReturn(false); when(NfcAdapter.getDefaultAdapter(mContext)).thenReturn(mNfcAdapter); - mAndroidBeamController = new AndroidBeamPreferenceController(mContext); + mAndroidBeamController = new AndroidBeamPreferenceController(mContext, + AndroidBeamPreferenceController.KEY_ANDROID_BEAM_SETTINGS); mAndroidBeamPreference = new RestrictedPreference(RuntimeEnvironment.application); when(mScreen.findPreference(mAndroidBeamController.getPreferenceKey())).thenReturn( mAndroidBeamPreference); diff --git a/tests/robotests/src/com/android/settings/nfc/NfcAirplaneModeObserverTest.java b/tests/robotests/src/com/android/settings/nfc/NfcAirplaneModeObserverTest.java new file mode 100644 index 00000000000..b6d28d192cb --- /dev/null +++ b/tests/robotests/src/com/android/settings/nfc/NfcAirplaneModeObserverTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018 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.nfc; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.nfc.NfcAdapter; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v14.preference.SwitchPreference; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowNfcAdapter; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = {ShadowNfcAdapter.class}) +public class NfcAirplaneModeObserverTest { + + Context mContext; + private NfcAdapter mNfcAdapter; + private SwitchPreference mNfcPreference; + private NfcAirplaneModeObserver mNfcAirplaneModeObserver; + + @Before + public void setUp() { + mContext = ShadowApplication.getInstance().getApplicationContext(); + mNfcAdapter = NfcAdapter.getDefaultAdapter(mContext); + + mNfcPreference = new SwitchPreference(RuntimeEnvironment.application); + + mNfcAirplaneModeObserver = new NfcAirplaneModeObserver(mContext, mNfcAdapter, + (Preference) mNfcPreference); + } + + @Test + public void NfcAirplaneModeObserver_airplaneOn_shouldDisableNfc() { + ReflectionHelpers.setField(mNfcAirplaneModeObserver, + "mAirplaneMode", 0); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 1); + + mNfcAirplaneModeObserver.onChange(false, + NfcAirplaneModeObserver.AIRPLANE_MODE_URI); + + assertThat(mNfcAdapter.isEnabled()).isFalse(); + assertThat(mNfcPreference.isEnabled()).isFalse(); + } + + @Test + public void NfcAirplaneModeObserver_airplaneOff_shouldEnableNfc() { + ReflectionHelpers.setField(mNfcAirplaneModeObserver, + "mAirplaneMode", 1); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0); + + mNfcAirplaneModeObserver.onChange(false, + NfcAirplaneModeObserver.AIRPLANE_MODE_URI); + + assertThat(mNfcAdapter.isEnabled()).isTrue(); + assertThat(mNfcPreference.isEnabled()).isTrue(); + } +} diff --git a/tests/robotests/src/com/android/settings/nfc/NfcPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/nfc/NfcPreferenceControllerTest.java index 802e199abf2..008d1585705 100644 --- a/tests/robotests/src/com/android/settings/nfc/NfcPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/nfc/NfcPreferenceControllerTest.java @@ -19,6 +19,7 @@ package com.android.settings.nfc; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; @@ -26,8 +27,8 @@ import android.nfc.NfcAdapter; import android.nfc.NfcManager; import android.os.UserManager; import android.provider.Settings; -import android.support.v14.preference.SwitchPreference; import android.support.v7.preference.PreferenceScreen; +import android.support.v14.preference.SwitchPreference; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -68,8 +69,10 @@ public class NfcPreferenceControllerTest { when(mContext.getSystemService(Context.NFC_SERVICE)).thenReturn(mManager); when(NfcAdapter.getDefaultAdapter(mContext)).thenReturn(mNfcAdapter); - mNfcController = new NfcPreferenceController(mContext); + mNfcController = new NfcPreferenceController(mContext, + NfcPreferenceController.KEY_TOGGLE_NFC); mNfcPreference = new SwitchPreference(RuntimeEnvironment.application); + when(mScreen.findPreference(mNfcController.getPreferenceKey())).thenReturn(mNfcPreference); Settings.Global.putString(mContext.getContentResolver(), @@ -83,15 +86,17 @@ public class NfcPreferenceControllerTest { } @Test - public void isAvailable_hasNfc_shouldReturnTrue() { + public void getAvailabilityStatus_hasNfc_shouldReturnAvailable() { when(mNfcAdapter.isEnabled()).thenReturn(true); - assertThat(mNfcController.isAvailable()).isTrue(); + assertThat(mNfcController.getAvailabilityStatus()) + .isEqualTo(NfcPreferenceController.AVAILABLE); } @Test - public void isAvailable_noNfcAdapter_shouldReturnFalse() { + public void getAvailabilityStatus_noNfcAdapter_shouldReturnDisabledUnsupported() { ReflectionHelpers.setField(mNfcController, "mNfcAdapter", null); - assertThat(mNfcController.isAvailable()).isFalse(); + assertThat(mNfcController.getAvailabilityStatus()) + .isEqualTo(NfcPreferenceController.UNSUPPORTED_ON_DEVICE); } @Test @@ -157,4 +162,46 @@ public class NfcPreferenceControllerTest { assertThat(keys).hasSize(1); } + @Test + public void setChecked_True_nfcShouldEnable() { + mNfcController.setChecked(true); + mNfcController.onResume(); + + verify(mNfcAdapter).enable(); + } + + @Test + public void setChecked_False_nfcShouldDisable() { + mNfcController.setChecked(false); + mNfcController.onResume(); + + verify(mNfcAdapter).disable(); + } + + @Test + public void hasAsyncUpdate_shouldReturnTrue() { + assertThat(mNfcController.hasAsyncUpdate()).isTrue(); + } + + @Test + public void isToggleableInAirplaneMode_containNfc_shouldReturnTrue() { + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS, + Settings.Global.RADIO_NFC); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 1); + + assertThat(NfcPreferenceController.isToggleableInAirplaneMode(mContext)).isTrue(); + } + + @Test + public void isToggleableInAirplaneMode_withoutNfc_shouldReturnFalse() { + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS, + "null"); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 1); + + assertThat(NfcPreferenceController.isToggleableInAirplaneMode(mContext)).isFalse(); + } } diff --git a/tests/robotests/src/com/android/settings/notification/AlarmVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/AlarmVolumePreferenceControllerTest.java index 6e8476c4bb5..4f48b779b6c 100644 --- a/tests/robotests/src/com/android/settings/notification/AlarmVolumePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/AlarmVolumePreferenceControllerTest.java @@ -73,4 +73,9 @@ public class AlarmVolumePreferenceControllerTest { public void getAudioStream_shouldReturnAlarm() { assertThat(mController.getAudioStream()).isEqualTo(AudioManager.STREAM_ALARM); } + + @Test + public void isSliceableCorrectKey_returnsTrue() { + assertThat(mController.isSliceable()).isTrue(); + } } diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java index df960d8a912..21bef6131c9 100644 --- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java +++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java @@ -18,9 +18,7 @@ package com.android.settings.slices; import static android.content.ContentResolver.SCHEME_CONTENT; - import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -38,19 +36,24 @@ import android.os.StrictMode; import android.provider.SettingsSlicesContract; import android.util.ArraySet; -import com.android.settings.location.LocationSliceBuilder; -import com.android.settings.wifi.WifiSliceBuilder; import com.android.settings.bluetooth.BluetoothSliceBuilder; +import com.android.settings.location.LocationSliceBuilder; import com.android.settings.notification.ZenModeSliceBuilder; import com.android.settings.testutils.DatabaseTestUtils; import com.android.settings.testutils.FakeToggleController; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowThreadUtils; +import com.android.settings.wifi.WifiSliceBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; import java.util.Arrays; import java.util.Collection; @@ -66,6 +69,7 @@ import androidx.slice.Slice; * TODO Investigate using ShadowContentResolver.registerProviderInternal(String, ContentProvider) */ @RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = ShadowThreadUtils.class) public class SettingsSliceProviderTest { private static final String KEY = "KEY"; @@ -98,6 +102,7 @@ public class SettingsSliceProviderTest { public void setUp() { mContext = spy(RuntimeEnvironment.application); mProvider = spy(new SettingsSliceProvider()); + ShadowStrictMode.reset(); mProvider.mSliceWeakDataCache = new HashMap<>(); mProvider.mSliceDataCache = new HashMap<>(); mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext); @@ -112,6 +117,7 @@ public class SettingsSliceProviderTest { @After public void cleanUp() { + ShadowThreadUtils.reset(); DatabaseTestUtils.clearDb(mContext); } @@ -184,7 +190,8 @@ public class SettingsSliceProviderTest { } @Test - public void onBindSlice_shouldNotOverrideStrictMode() { + public void onBindSlice_mainThread_shouldNotOverrideStrictMode() { + ShadowThreadUtils.setIsMainThread(true); final StrictMode.ThreadPolicy oldThreadPolicy = StrictMode.getThreadPolicy(); SliceData data = getDummyData(); mProvider.mSliceWeakDataCache.put(data.getUri(), data); @@ -195,6 +202,18 @@ public class SettingsSliceProviderTest { assertThat(newThreadPolicy.toString()).isEqualTo(oldThreadPolicy.toString()); } + @Test + @Config(shadows = ShadowStrictMode.class) + public void onBindSlice_backgroundThread_shouldOverrideStrictMode() { + ShadowThreadUtils.setIsMainThread(false); + + SliceData data = getDummyData(); + mProvider.mSliceWeakDataCache.put(data.getUri(), data); + mProvider.onBindSlice(data.getUri()); + + assertThat(ShadowStrictMode.isThreadPolicyOverridden()).isTrue(); + } + @Test public void onBindSlice_requestsBlockedSlice_retunsNull() { final String blockedKey = "blocked_key"; @@ -456,7 +475,7 @@ public class SettingsSliceProviderTest { mDb.replaceOrThrow(SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX, null, values); } - private SliceData getDummyData() { + private static SliceData getDummyData() { return new SliceData.Builder() .setKey(KEY) .setTitle(TITLE) @@ -468,4 +487,24 @@ public class SettingsSliceProviderTest { .setPreferenceControllerClassName(PREF_CONTROLLER) .build(); } + + @Implements(value = StrictMode.class, inheritImplementationMethods = true) + public static class ShadowStrictMode { + + private static int sSetThreadPolicyCount; + + @Resetter + public static void reset() { + sSetThreadPolicyCount = 0; + } + + @Implementation + public static void setThreadPolicy(final StrictMode.ThreadPolicy policy) { + sSetThreadPolicyCount++; + } + + public static boolean isThreadPolicyOverridden() { + return sSetThreadPolicyCount != 0; + } + } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java index f03f88ed99a..25f0d0cb163 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java @@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -95,8 +96,16 @@ public class SliceBroadcastReceiverTest { @Test public void onReceive_toggleChanged() { final String key = "key"; + final Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); insertSpecialCase(key); + final ContentResolver resolver = mock(ContentResolver.class); + doReturn(resolver).when(mContext).getContentResolver(); // Turn on toggle setting FakeToggleController fakeToggleController = new FakeToggleController(mContext, key); fakeToggleController.setChecked(true); @@ -120,14 +129,77 @@ public class SliceBroadcastReceiverTest { assertThat(namePair.first).isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME); assertThat(namePair.second).isEqualTo(fakeToggleController.getPreferenceKey()); + verify(resolver).notifyChange(uri, null); assertThat(valuePair.first) .isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE); assertThat(valuePair.second).isEqualTo(0); } + @Test + public void toggleUpdate_synchronously_notifyChange_should_be_called() { + // Monitor the ContentResolver + final ContentResolver resolver = spy(mContext.getContentResolver()); + doReturn(resolver).when(mContext).getContentResolver(); + + final String key = "key"; + mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); + insertSpecialCase(key); + + FakeToggleController fakeToggleController = new FakeToggleController(mContext, key); + fakeToggleController.setChecked(true); + // Set the toggle setting update synchronously. + fakeToggleController.setAsyncUpdate(false); + Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED); + intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); + + assertThat(fakeToggleController.isChecked()).isTrue(); + + // Toggle setting + mReceiver.onReceive(mContext, intent); + + assertThat(fakeToggleController.isChecked()).isFalse(); + + final Uri expectedUri = SliceBuilderUtils.getUri( + SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key, false); + verify(resolver).notifyChange(eq(expectedUri), eq(null)); + } + + @Test + public void toggleUpdate_asynchronously_notifyChange_should_not_be_called() { + // Monitor the ContentResolver + final ContentResolver resolver = spy(mContext.getContentResolver()); + doReturn(resolver).when(mContext).getContentResolver(); + + final String key = "key"; + mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); + insertSpecialCase(key); + + FakeToggleController fakeToggleController = new FakeToggleController(mContext, key); + fakeToggleController.setChecked(true); + // Set the toggle setting update asynchronously. + fakeToggleController.setAsyncUpdate(true); + Intent intent = new Intent(SettingsSliceProvider.ACTION_TOGGLE_CHANGED); + intent.putExtra(SettingsSliceProvider.EXTRA_SLICE_KEY, key); + + assertThat(fakeToggleController.isChecked()).isTrue(); + + // Toggle setting + mReceiver.onReceive(mContext, intent); + + verify(resolver, never()).notifyChange(null, null); + } + @Test public void onReceive_sliderChanged() { final String key = "key"; + final Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); + final ContentResolver resolver = mock(ContentResolver.class); + doReturn(resolver).when(mContext).getContentResolver(); final int position = FakeSliderController.MAX_STEPS - 1; final int oldPosition = FakeSliderController.MAX_STEPS; mSearchFeatureProvider.getSearchIndexableResources().getProviderValues().clear(); @@ -158,6 +230,7 @@ public class SliceBroadcastReceiverTest { assertThat(namePair.first).isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME); assertThat(namePair.second).isEqualTo(key); + verify(resolver).notifyChange(uri, null); assertThat(valuePair.first) .isEqualTo(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE); assertThat(valuePair.second).isEqualTo(position); @@ -231,8 +304,12 @@ public class SliceBroadcastReceiverTest { // Check the value is the same and the Uri has been notified. assertThat(fakeToggleController.isChecked()).isTrue(); - final Uri expectedUri = SliceBuilderUtils.getUri( - SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key, false); + final Uri expectedUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); verify(resolver).notifyChange(eq(expectedUri), eq(null)); } @@ -268,8 +345,12 @@ public class SliceBroadcastReceiverTest { // Check position is the same and the Uri has been notified. assertThat(fakeSliderController.getSliderPosition()).isEqualTo(oldPosition); - final Uri expectedUri = SliceBuilderUtils.getUri( - SettingsSlicesContract.PATH_SETTING_ACTION + "/" + key, false); + final Uri expectedUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(key) + .build(); verify(resolver).notifyChange(eq(expectedUri), eq(null)); } diff --git a/tests/robotests/src/com/android/settings/testutils/FakeToggleController.java b/tests/robotests/src/com/android/settings/testutils/FakeToggleController.java index 680a04de106..8e408f00729 100644 --- a/tests/robotests/src/com/android/settings/testutils/FakeToggleController.java +++ b/tests/robotests/src/com/android/settings/testutils/FakeToggleController.java @@ -36,6 +36,8 @@ public class FakeToggleController extends TogglePreferenceController { private final int ON = 1; private final int OFF = 0; + private boolean mIsAsyncUpdate = false; + public FakeToggleController(Context context, String preferenceKey) { super(context, preferenceKey); } @@ -67,4 +69,13 @@ public class FakeToggleController extends TogglePreferenceController { public boolean isSliceable() { return true; } + + @Override + public boolean hasAsyncUpdate() { + return mIsAsyncUpdate; + } + + public void setAsyncUpdate(boolean isAsyncUpdate) { + mIsAsyncUpdate = isAsyncUpdate; + } } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowNfcAdapter.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowNfcAdapter.java index e4421eee813..07c8e544d6a 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowNfcAdapter.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowNfcAdapter.java @@ -33,6 +33,7 @@ import org.robolectric.util.ReflectionHelpers.ClassParameter; @Implements(NfcAdapter.class) public class ShadowNfcAdapter { private static boolean sReaderModeEnabled; + private boolean mIsNfcEnabled = false; @Implementation public void enableReaderMode(Activity activity, NfcAdapter.ReaderCallback callback, int flags, @@ -46,6 +47,23 @@ public class ShadowNfcAdapter { NfcAdapter.class, ClassParameter.from(Context.class, context)); } + @Implementation + public boolean isEnabled() { + return mIsNfcEnabled; + } + + @Implementation + public boolean enable() { + mIsNfcEnabled = true; + return true; + } + + @Implementation + public boolean disable() { + mIsNfcEnabled = false; + return true; + } + public static boolean isReaderModeEnabled() { return sReaderModeEnabled; } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowThreadUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowThreadUtils.java index 6b0411e9ab0..9513098e862 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowThreadUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowThreadUtils.java @@ -20,10 +20,18 @@ import com.android.settingslib.utils.ThreadUtils; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; @Implements(ThreadUtils.class) public class ShadowThreadUtils { + private static boolean sIsMainThread = true; + + @Resetter + public static void reset() { + sIsMainThread = true; + } + @Implementation public static void postOnBackgroundThread(Runnable runnable) { runnable.run(); @@ -33,4 +41,14 @@ public class ShadowThreadUtils { public static void postOnMainThread(Runnable runnable) { runnable.run(); } + + @Implementation + public static boolean isMainThread() { + return sIsMainThread; + } + + public static void setIsMainThread(boolean isMainThread) { + sIsMainThread = isMainThread; + } + }