diff --git a/proguard.flags b/proguard.flags index 5482f1c52d0..492404ce7be 100644 --- a/proguard.flags +++ b/proguard.flags @@ -6,7 +6,7 @@ -keepattributes Exceptions # Keep all Fragments in this package, which are used by reflection. --keep public class com.android.settings.** extends androidx.fragment.app.Fragment +-keep public class com.android.settings*.** extends androidx.fragment.app.Fragment # Keep all preference controllers needed by slice and DashboardFragment. -keep class * extends com.android.settings.core.BasePreferenceController { @@ -18,13 +18,13 @@ } # We want to keep methods in Activity that could be used in the XML attribute onClick. --keepclassmembers class * extends android.app.Activity { +-keepclassmembers class com.android.settings*.** extends android.app.Activity { public void *(android.view.View); public void *(android.view.MenuItem); } # Keep setters in Views so that animations can still work. --keep public class * extends android.view.View { +-keep public class com.android.settings*.** extends android.view.View { public (android.content.Context); public (android.content.Context, android.util.AttributeSet); public (android.content.Context, android.util.AttributeSet, int); @@ -34,22 +34,16 @@ } # Keep classes that may be inflated from XML. --keepclasseswithmembers class * { +-keepclasseswithmembers class com.android.settings*.** { public (android.content.Context, android.util.AttributeSet); } --keepclasseswithmembers class * { +-keepclasseswithmembers class com.android.settings*.** { public (android.content.Context, android.util.AttributeSet, int); } --keepclasseswithmembers class * { +-keepclasseswithmembers class com.android.settings*.** { public (android.content.Context, android.util.AttributeSet, int, int); } -# Keep annotated classes or class members. --keep @androidx.annotation.Keep class * --keepclassmembers class * { - @androidx.annotation.Keep *; -} - # Keep specific fields used via reflection. -keepclassmembers class * { public static ** SEARCH_INDEX_DATA_PROVIDER; diff --git a/res/values/strings.xml b/res/values/strings.xml index 3c79253141b..b569f27e80a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -11909,13 +11909,6 @@ QR code isn\u0027t a valid format - - - - Gesture detection area hints - - Shift gesture detection region above keyboard - diff --git a/res/xml/accessibility_cursor_settings.xml b/res/xml/accessibility_cursor_settings.xml index 1d23051ebbd..6bc50bb58ae 100644 --- a/res/xml/accessibility_cursor_settings.xml +++ b/res/xml/accessibility_cursor_settings.xml @@ -22,14 +22,4 @@ android:key="screen_software_cursor_preference_switch" android:title="@string/accessibility_screen_software_cursor_title" settings:controller="com.android.settings.accessibility.SoftwareCursorTogglePreferenceController"/> - - - - diff --git a/src/com/android/settings/accessibility/SoftwareCursorKeyboardShiftPreferenceController.java b/src/com/android/settings/accessibility/SoftwareCursorKeyboardShiftPreferenceController.java deleted file mode 100644 index 6ff49a3a853..00000000000 --- a/src/com/android/settings/accessibility/SoftwareCursorKeyboardShiftPreferenceController.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2022 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.accessibility; - -import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; -import static com.android.settings.accessibility.AccessibilityUtil.State.ON; - -import android.content.ContentResolver; -import android.content.Context; -import android.provider.Settings; - -import androidx.preference.PreferenceScreen; -import androidx.preference.SwitchPreference; - -import com.android.settings.R; -import com.android.settings.core.TogglePreferenceController; - -/** Controller class for the Software Cursor keyboard shift setting. */ -public class SoftwareCursorKeyboardShiftPreferenceController extends - TogglePreferenceController { - - private static final String SETTINGS_VALUE = - Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED; - - private final ContentResolver mContentResolver; - - - public SoftwareCursorKeyboardShiftPreferenceController(Context context, String preferenceKey) { - super(context, preferenceKey); - mContentResolver = context.getContentResolver(); - } - - @Override - public boolean isChecked() { - return Settings.Secure.getInt(mContentResolver, SETTINGS_VALUE, OFF) == ON; - } - - @Override - public boolean setChecked(boolean isChecked) { - Settings.Secure.putInt(mContentResolver, SETTINGS_VALUE, isChecked ? ON : OFF); - return true; - } - - @Override - public int getAvailabilityStatus() { - return AVAILABLE; - } - - @Override - public int getSliceHighlightMenuRes() { - return R.string.menu_key_accessibility; - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - - SwitchPreference preference = screen.findPreference(getPreferenceKey()); - if (preference != null) { - preference.setOnPreferenceChangeListener(this); - preference.setChecked(isChecked()); - } - } -} diff --git a/src/com/android/settings/accessibility/SoftwareCursorTriggerHintsPreferenceController.java b/src/com/android/settings/accessibility/SoftwareCursorTriggerHintsPreferenceController.java deleted file mode 100644 index bd6e1470ac0..00000000000 --- a/src/com/android/settings/accessibility/SoftwareCursorTriggerHintsPreferenceController.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2022 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.accessibility; - -import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; -import static com.android.settings.accessibility.AccessibilityUtil.State.ON; - -import android.content.ContentResolver; -import android.content.Context; -import android.provider.Settings; - -import androidx.preference.PreferenceScreen; -import androidx.preference.SwitchPreference; - -import com.android.settings.R; -import com.android.settings.core.TogglePreferenceController; - -/** Controller class that control accessibility software cursor trigger hints settings. */ -public class SoftwareCursorTriggerHintsPreferenceController extends TogglePreferenceController { - - private static final String SETTINGS_VALUE = - Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED; - - private final ContentResolver mContentResolver; - - public SoftwareCursorTriggerHintsPreferenceController(Context context, String preferenceKey) { - super(context, preferenceKey); - mContentResolver = context.getContentResolver(); - } - - @Override - public boolean isChecked() { - return Settings.Secure.getInt(mContentResolver, SETTINGS_VALUE, OFF) == ON; - } - - @Override - public boolean setChecked(boolean isChecked) { - Settings.Secure.putInt(mContentResolver, SETTINGS_VALUE, isChecked ? ON : OFF); - return true; - } - - @Override - public int getSliceHighlightMenuRes() { - return R.string.menu_key_accessibility; - } - - @Override - public int getAvailabilityStatus() { - return AVAILABLE; - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - - SwitchPreference preference = screen.findPreference(getPreferenceKey()); - if (preference != null) { - preference.setOnPreferenceChangeListener(this); - preference.setChecked(isChecked()); - } - } -} diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java index 259829643ef..7698d9a23f9 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java @@ -171,7 +171,7 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { final SensorPrivacyManagerHelper helper = SensorPrivacyManagerHelper .getInstance(getApplicationContext()); final boolean cameraPrivacyEnabled = helper - .isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA, mUserId); + .isSensorBlocked(SensorPrivacyManagerHelper.SENSOR_CAMERA); Log.v(TAG, "cameraPrivacyEnabled : " + cameraPrivacyEnabled); } @@ -370,7 +370,7 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { .getBooleanExtra(BiometricEnrollActivity.EXTRA_REQUIRE_PARENTAL_CONSENT, false); final boolean cameraPrivacyEnabled = SensorPrivacyManagerHelper .getInstance(getApplicationContext()) - .isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA, mUserId); + .isSensorBlocked(SensorPrivacyManagerHelper.SENSOR_CAMERA); final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); final boolean isSettingUp = isSetupWizard || (parentelConsentRequired && !WizardManagerHelper.isUserSetupComplete(this)); diff --git a/src/com/android/settings/development/DSULoader.java b/src/com/android/settings/development/DSULoader.java index d4fa0c64646..fede67a7766 100644 --- a/src/com/android/settings/development/DSULoader.java +++ b/src/com/android/settings/development/DSULoader.java @@ -26,6 +26,7 @@ import android.text.TextUtils; import android.util.Slog; import android.view.LayoutInflater; import android.view.View; +import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListView; @@ -117,6 +118,20 @@ public class DSULoader extends ListActivity { } } } + + private void resizeListView() { + ListView view = getListView(); + View item_view = mAdapter.getView(0, null, view); + item_view.measure( + MeasureSpec.makeMeasureSpec(view.getWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + ViewGroup.LayoutParams params = view.getLayoutParams(); + params.height = item_view.getMeasuredHeight() * (mAdapter.getCount() + 1); + Slog.e(TAG, "resizeListView height=" + params.height); + view.setLayoutParams(params); + view.requestLayout(); + } + // Fetcher fetches mDSUList in backgroud private class Fetcher implements Runnable { private URL mDsuList; @@ -172,6 +187,9 @@ public class DSULoader extends ListActivity { public void run() { try { fetch(mDsuList); + if (mDSUList.size() == 0) { + mDSUList.add(0, "No DSU available for this device"); + } } catch (IOException e) { Slog.e(TAG, e.toString()); mDSUList.add(0, "Network Error"); @@ -179,14 +197,12 @@ public class DSULoader extends ListActivity { Slog.e(TAG, e.toString()); mDSUList.add(0, "Metadata Error"); } - if (mDSUList.size() == 0) { - mDSUList.add(0, "No DSU available for this device"); - } runOnUiThread( new Runnable() { public void run() { mAdapter.clear(); mAdapter.addAll(mDSUList); + resizeListView(); } }); } diff --git a/src/com/android/settings/network/CarrierConfigChangedReceiver.java b/src/com/android/settings/network/CarrierConfigChangedReceiver.java index 8a6d47d96fc..d9ff03efb6a 100644 --- a/src/com/android/settings/network/CarrierConfigChangedReceiver.java +++ b/src/com/android/settings/network/CarrierConfigChangedReceiver.java @@ -32,9 +32,20 @@ public class CarrierConfigChangedReceiver extends BroadcastReceiver { CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; private final CountDownLatch mLatch; + private final boolean mIsWaitingForValidSubId; - public CarrierConfigChangedReceiver(CountDownLatch latch) { + /** + * This is the CarrierConfigChanged receiver. If it receives the carrier config changed, then it + * call the CountDownLatch.countDown(). + * If the "isWaitingForValidSubId" is true, then the receiver skip the carrier config changed + * with the subId = -1. The receiver executes the countDown when the CarrierConfigChanged + * with valid subId. + * If the "isWaitingForValidSubId" is false, then the receiver executes the countDown when + * receiving any CarrierConfigChanged. + */ + public CarrierConfigChangedReceiver(CountDownLatch latch, boolean isWaitingForValidSubId) { mLatch = latch; + mIsWaitingForValidSubId = isWaitingForValidSubId; } public void registerOn(Context context) { @@ -53,7 +64,8 @@ public class CarrierConfigChangedReceiver extends BroadcastReceiver { } private void checkSubscriptionIndex(Intent intent) { - if (intent.hasExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX)) { + if (intent.hasExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX) + || !mIsWaitingForValidSubId) { int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, -1); Log.i(TAG, "subId from config changed: " + subId); mLatch.countDown(); diff --git a/src/com/android/settings/network/UiccSlotUtil.java b/src/com/android/settings/network/UiccSlotUtil.java index e5323d11b99..7b8d330834b 100644 --- a/src/com/android/settings/network/UiccSlotUtil.java +++ b/src/com/android/settings/network/UiccSlotUtil.java @@ -141,7 +141,8 @@ public class UiccSlotUtil { inactiveRemovableSlot, /*removable sim's port Id*/ TelephonyManager.DEFAULT_PORT_INDEX, excludedLogicalSlotIndex), - context); + context, + /*isWaitingForValidSubId=*/ true); } /** @@ -179,7 +180,8 @@ public class UiccSlotUtil { performSwitchToSlot(telMgr, prepareUiccSlotMappings(uiccSlotMappings, /*slot is not psim*/ false, physicalSlotId, port, excludedLogicalSlotIndex), - context); + context, + /*isWaitingForValidSubId=*/ false); } /** @@ -231,7 +233,8 @@ public class UiccSlotUtil { } private static void performSwitchToSlot(TelephonyManager telMgr, - Collection uiccSlotMappings, Context context) + Collection uiccSlotMappings, Context context, + boolean isWaitingForValidSubId) throws UiccSlotsException { CarrierConfigChangedReceiver receiver = null; long waitingTimeMillis = @@ -241,7 +244,7 @@ public class UiccSlotUtil { DEFAULT_WAIT_AFTER_SWITCH_TIMEOUT_MILLIS); try { CountDownLatch latch = new CountDownLatch(1); - receiver = new CarrierConfigChangedReceiver(latch); + receiver = new CarrierConfigChangedReceiver(latch, isWaitingForValidSubId); receiver.registerOn(context); telMgr.setSimSlotMapping(uiccSlotMappings); latch.await(waitingTimeMillis, TimeUnit.MILLISECONDS); diff --git a/src/com/android/settings/privacy/CameraToggleController.java b/src/com/android/settings/privacy/CameraToggleController.java index 756553c4bf6..4e53ef98c10 100644 --- a/src/com/android/settings/privacy/CameraToggleController.java +++ b/src/com/android/settings/privacy/CameraToggleController.java @@ -18,29 +18,37 @@ package com.android.settings.privacy; import static android.os.UserManager.DISALLOW_CAMERA_TOGGLE; -import static com.android.settings.utils.SensorPrivacyManagerHelper.CAMERA; +import static com.android.settings.utils.SensorPrivacyManagerHelper.SENSOR_CAMERA; import android.content.Context; -import android.provider.DeviceConfig; + +import androidx.annotation.VisibleForTesting; + +import com.android.settings.utils.SensorPrivacyManagerHelper; /** * Controller for microphone toggle */ public class CameraToggleController extends SensorToggleController { + public CameraToggleController(Context context, String preferenceKey) { super(context, preferenceKey); } - @Override - public int getSensor() { - return CAMERA; + @VisibleForTesting + public CameraToggleController(Context context, String preferenceKey, + SensorPrivacyManagerHelper sensorPrivacyManagerHelper) { + super(context, preferenceKey, sensorPrivacyManagerHelper, /* ignoreDeviceConfig */ true); } @Override - public int getAvailabilityStatus() { - return mSensorPrivacyManagerHelper.supportsSensorToggle(getSensor()) - && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "camera_toggle_enabled", - true) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + public int getSensor() { + return SENSOR_CAMERA; + } + + @Override + public String getDeviceConfigKey() { + return "camera_toggle_enabled"; } @Override diff --git a/src/com/android/settings/privacy/MicToggleController.java b/src/com/android/settings/privacy/MicToggleController.java index c2389d2439e..782c11692bd 100644 --- a/src/com/android/settings/privacy/MicToggleController.java +++ b/src/com/android/settings/privacy/MicToggleController.java @@ -18,10 +18,11 @@ package com.android.settings.privacy; import static android.os.UserManager.DISALLOW_MICROPHONE_TOGGLE; -import static com.android.settings.utils.SensorPrivacyManagerHelper.MICROPHONE; +import static com.android.settings.utils.SensorPrivacyManagerHelper.SENSOR_MICROPHONE; import android.content.Context; -import android.provider.DeviceConfig; + +import com.android.settings.utils.SensorPrivacyManagerHelper; /** * Controller for camera toggle @@ -31,16 +32,19 @@ public class MicToggleController extends SensorToggleController { super(context, preferenceKey); } - @Override - public int getSensor() { - return MICROPHONE; + public MicToggleController(Context context, String preferenceKey, + SensorPrivacyManagerHelper sensorPrivacyManagerHelper) { + super(context, preferenceKey, sensorPrivacyManagerHelper, /* ignoreDeviceConfig */ true); } @Override - public int getAvailabilityStatus() { - return mSensorPrivacyManagerHelper.supportsSensorToggle(getSensor()) - && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "mic_toggle_enabled", - true) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + public int getSensor() { + return SENSOR_MICROPHONE; + } + + @Override + public String getDeviceConfigKey() { + return "mic_toggle_enabled"; } @Override diff --git a/src/com/android/settings/privacy/SensorToggleController.java b/src/com/android/settings/privacy/SensorToggleController.java index 53b1a2f65e0..7aa9f9f4ce8 100644 --- a/src/com/android/settings/privacy/SensorToggleController.java +++ b/src/com/android/settings/privacy/SensorToggleController.java @@ -16,10 +16,12 @@ package com.android.settings.privacy; -import static android.hardware.SensorPrivacyManager.Sources.SETTINGS; - import android.content.Context; +import android.provider.DeviceConfig; +import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.OnLifecycleEvent; import androidx.preference.PreferenceScreen; import com.android.settings.R; @@ -27,20 +29,35 @@ import com.android.settings.core.TogglePreferenceController; import com.android.settings.utils.SensorPrivacyManagerHelper; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; import java.util.concurrent.Executor; /** * Base class for sensor toggle controllers */ -public abstract class SensorToggleController extends TogglePreferenceController { +public abstract class SensorToggleController extends TogglePreferenceController implements + SensorPrivacyManagerHelper.Callback, LifecycleObserver { protected final SensorPrivacyManagerHelper mSensorPrivacyManagerHelper; private final Executor mCallbackExecutor; + private PreferenceScreen mScreen; + + /** For testing since DeviceConfig uses static method calls */ + private boolean mIgnoreDeviceConfig; + public SensorToggleController(Context context, String preferenceKey) { + this(context, preferenceKey, SensorPrivacyManagerHelper.getInstance(context), false); + } + + @VisibleForTesting + SensorToggleController(Context context, String preferenceKey, + SensorPrivacyManagerHelper sensorPrivacyManagerHelper, boolean ignoreDeviceConfig) { super(context, preferenceKey); - mSensorPrivacyManagerHelper = SensorPrivacyManagerHelper.getInstance(context); + + mIgnoreDeviceConfig = ignoreDeviceConfig; + mSensorPrivacyManagerHelper = sensorPrivacyManagerHelper; mCallbackExecutor = context.getMainExecutor(); } @@ -49,10 +66,22 @@ public abstract class SensorToggleController extends TogglePreferenceController */ public abstract int getSensor(); + /** + * The key for the device config setting for whether the feature is enabled. + */ + public abstract String getDeviceConfigKey(); + protected String getRestriction() { return null; } + @Override + public int getAvailabilityStatus() { + return mSensorPrivacyManagerHelper.supportsSensorToggle(getSensor()) + && (mIgnoreDeviceConfig || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + getDeviceConfigKey(), true)) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + @Override public boolean isChecked() { return !mSensorPrivacyManagerHelper.isSensorBlocked(getSensor()); @@ -60,8 +89,7 @@ public abstract class SensorToggleController extends TogglePreferenceController @Override public boolean setChecked(boolean isChecked) { - mSensorPrivacyManagerHelper.setSensorBlockedForProfileGroup(SETTINGS, getSensor(), - !isChecked); + mSensorPrivacyManagerHelper.setSensorBlocked(getSensor(), !isChecked); return true; } @@ -69,21 +97,38 @@ public abstract class SensorToggleController extends TogglePreferenceController public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - RestrictedSwitchPreference preference = - (RestrictedSwitchPreference) screen.findPreference(getPreferenceKey()); + mScreen = screen; + + RestrictedSwitchPreference preference = mScreen.findPreference(getPreferenceKey()); if (preference != null) { preference.setDisabledByAdmin(RestrictedLockUtilsInternal .checkIfRestrictionEnforced(mContext, getRestriction(), mContext.getUserId())); } - - mSensorPrivacyManagerHelper.addSensorBlockedListener( - getSensor(), - (sensor, blocked) -> updateState(screen.findPreference(mPreferenceKey)), - mCallbackExecutor); } @Override public int getSliceHighlightMenuRes() { return R.string.menu_key_privacy; } + + @Override + public void onSensorPrivacyChanged(int toggleType, int sensor, boolean blocked) { + updateState(mScreen.findPreference(mPreferenceKey)); + } + + /** + * onStart lifecycle event + */ + @OnLifecycleEvent(Lifecycle.Event.ON_START) + public void onStart() { + mSensorPrivacyManagerHelper.addSensorBlockedListener(getSensor(), mCallbackExecutor, this); + } + + /** + * onStop lifecycle event + */ + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + public void onStop() { + mSensorPrivacyManagerHelper.removeSensorBlockedListener(this); + } } diff --git a/src/com/android/settings/utils/SensorPrivacyManagerHelper.java b/src/com/android/settings/utils/SensorPrivacyManagerHelper.java deleted file mode 100644 index b0300427298..00000000000 --- a/src/com/android/settings/utils/SensorPrivacyManagerHelper.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2021 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.utils; - -import android.content.Context; -import android.hardware.SensorPrivacyManager; -import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener; -import android.util.ArraySet; -import android.util.SparseArray; - -import java.util.concurrent.Executor; - -/** - * A class to help with calls to the sensor privacy manager. This class caches state when needed and - * multiplexes multiple listeners to a minimal set of binder calls. - */ -public class SensorPrivacyManagerHelper { - - public static final int MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE; - public static final int CAMERA = SensorPrivacyManager.Sensors.CAMERA; - - private static SensorPrivacyManagerHelper sInstance; - - private final SensorPrivacyManager mSensorPrivacyManager; - - private final SparseArray mCurrentUserCachedState = new SparseArray<>(); - private final SparseArray> mCachedState = new SparseArray<>(); - - private final SparseArray - mCurrentUserServiceListeners = new SparseArray<>(); - private final SparseArray> - mServiceListeners = new SparseArray<>(); - - private final ArraySet mCallbacks = new ArraySet<>(); - - private final Object mLock = new Object(); - - /** - * Callback for when the state of the sensor privacy changes. - */ - public interface Callback { - - /** - * Method invoked when the sensor privacy changes. - * @param sensor The sensor which changed - * @param blocked If the sensor is blocked - */ - void onSensorPrivacyChanged(int sensor, boolean blocked); - } - - private static class CallbackInfo { - static final int CURRENT_USER = -1; - - Callback mCallback; - Executor mExecutor; - int mSensor; - int mUserId; - - CallbackInfo(Callback callback, Executor executor, int sensor, int userId) { - mCallback = callback; - mExecutor = executor; - mSensor = sensor; - mUserId = userId; - } - } - - /** - * Gets the singleton instance - * @param context The context which is needed if the instance hasn't been created - * @return the instance - */ - public static SensorPrivacyManagerHelper getInstance(Context context) { - if (sInstance == null) { - sInstance = new SensorPrivacyManagerHelper(context); - } - return sInstance; - } - - /** - * Only to be used in tests - */ - private static void clearInstance() { - sInstance = null; - } - - private SensorPrivacyManagerHelper(Context context) { - mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); - } - - /** - * Checks if the given toggle is supported on this device - * @param sensor The sensor to check - * @return whether the toggle for the sensor is supported on this device. - */ - public boolean supportsSensorToggle(int sensor) { - return mSensorPrivacyManager.supportsSensorToggle(sensor); - } - - /** - * Checks if the sensor is blocked for the current user. If the user switches and the state of - * the new user is different, this value will change. - * @param sensor the sensor to check - * @return true if the sensor is blocked for the current user - */ - public boolean isSensorBlocked(int sensor) { - synchronized (mLock) { - Boolean blocked = mCurrentUserCachedState.get(sensor); - if (blocked == null) { - registerCurrentUserListenerIfNeeded(sensor); - - blocked = mSensorPrivacyManager.isSensorPrivacyEnabled(sensor); - mCurrentUserCachedState.put(sensor, blocked); - } - - return blocked; - } - } - - /** - * Checks if the sensor is or would be blocked if the given user is the foreground user - * @param sensor the sensor to check - * @param userId the user to check - * @return true if the sensor is or would be blocked if the given user is the foreground user - */ - public boolean isSensorBlocked(int sensor, int userId) { - synchronized (mLock) { - SparseArray userCachedState = createUserCachedStateIfNeededLocked(userId); - Boolean blocked = userCachedState.get(sensor); - if (blocked == null) { - registerListenerIfNeeded(sensor, userId); - - blocked = mSensorPrivacyManager.isSensorPrivacyEnabled(sensor); - userCachedState.put(sensor, blocked); - } - - return blocked; - } - } - - /** - * Sets the sensor privacy for the current user. - * @param source The source with which sensor privacy is toggled. - * @param sensor The sensor to set for - * @param blocked The state to set to - */ - public void setSensorBlocked(int source, int sensor, boolean blocked) { - mSensorPrivacyManager.setSensorPrivacy(source, sensor, blocked); - } - - /** - * Sets the sensor privacy for the given user. - * @param source The source with which sensor privacy is toggled. - * @param sensor The sensor to set for - * @param blocked The state to set to - * @param userId The user to set for - */ - public void setSensorBlocked(int source, int sensor, boolean blocked, int userId) { - mSensorPrivacyManager.setSensorPrivacy(source, sensor, blocked, userId); - } - - /** - * Sets the sensor privacy for the current profile group. - * @param source The source with which sensor privacy is toggled. - * @param sensor The sensor to set for - * @param blocked The state to set to - */ - public void setSensorBlockedForProfileGroup(int source, int sensor, boolean blocked) { - mSensorPrivacyManager.setSensorPrivacyForProfileGroup(source, sensor, blocked); - } - - /** - * Sets the sensor privacy for the given user's profile group. - * @param source The source with which sensor privacy is toggled. - * @param sensor The sensor to set for - * @param blocked The state to set to - */ - public void setSensorBlockedForProfileGroup(int source, int sensor, boolean blocked, - int userId) { - mSensorPrivacyManager.setSensorPrivacyForProfileGroup(source, sensor, blocked, userId); - } - - /** - * Adds a listener for the state of the current user. If the current user changes and the state - * of the new user is different, a callback will be received. - * @param sensor The sensor to watch - * @param callback The callback to invoke - * @param executor The executor to invoke on - */ - public void addSensorBlockedListener(int sensor, Callback callback, Executor executor) { - synchronized (mLock) { - mCallbacks.add(new CallbackInfo(callback, executor, sensor, CallbackInfo.CURRENT_USER)); - } - } - - /** - * Adds a listener for the state of the given user - * @param sensor The sensor to watch - * @param callback The callback to invoke - * @param executor The executor to invoke on - */ - public void addSensorBlockedListener(int sensor, int userId, Callback callback, - Executor executor) { - synchronized (mLock) { - mCallbacks.add(new CallbackInfo(callback, executor, sensor, userId)); - } - } - - /** - * Removes a callback - * @param callback The callback to remove - */ - public void removeBlockedListener(Callback callback) { - synchronized (mLock) { - mCallbacks.removeIf(callbackInfo -> callbackInfo.mCallback == callback); - } - } - - private void registerCurrentUserListenerIfNeeded(int sensor) { - synchronized (mLock) { - if (!mCurrentUserServiceListeners.contains(sensor)) { - OnSensorPrivacyChangedListener listener = (s, enabled) -> { - mCurrentUserCachedState.put(sensor, enabled); - dispatchStateChangedLocked(sensor, enabled, CallbackInfo.CURRENT_USER); - }; - mCurrentUserServiceListeners.put(sensor, listener); - mSensorPrivacyManager.addSensorPrivacyListener(sensor, listener); - } - } - } - - private void registerListenerIfNeeded(int sensor, int userId) { - synchronized (mLock) { - SparseArray - userServiceListeners = createUserServiceListenersIfNeededLocked(userId); - - if (!userServiceListeners.contains(sensor)) { - OnSensorPrivacyChangedListener listener = (s, enabled) -> { - SparseArray userCachedState = - createUserCachedStateIfNeededLocked(userId); - userCachedState.put(sensor, enabled); - dispatchStateChangedLocked(sensor, enabled, userId); - }; - mCurrentUserServiceListeners.put(sensor, listener); - mSensorPrivacyManager.addSensorPrivacyListener(sensor, listener); - } - } - } - - private void dispatchStateChangedLocked(int sensor, boolean blocked, int userId) { - for (CallbackInfo callbackInfo : mCallbacks) { - if (callbackInfo.mUserId == userId && callbackInfo.mSensor == sensor) { - Callback callback = callbackInfo.mCallback; - Executor executor = callbackInfo.mExecutor; - - executor.execute(() -> callback.onSensorPrivacyChanged(sensor, blocked)); - } - } - } - - private SparseArray createUserCachedStateIfNeededLocked(int userId) { - SparseArray userCachedState = mCachedState.get(userId); - if (userCachedState == null) { - userCachedState = new SparseArray<>(); - mCachedState.put(userId, userCachedState); - } - return userCachedState; - } - - private SparseArray createUserServiceListenersIfNeededLocked( - int userId) { - SparseArray userServiceListeners = - mServiceListeners.get(userId); - if (userServiceListeners == null) { - userServiceListeners = new SparseArray<>(); - mServiceListeners.put(userId, userServiceListeners); - } - return userServiceListeners; - } -} diff --git a/src/com/android/settings/utils/SensorPrivacyManagerHelper.kt b/src/com/android/settings/utils/SensorPrivacyManagerHelper.kt new file mode 100644 index 00000000000..3fe3ab0d50c --- /dev/null +++ b/src/com/android/settings/utils/SensorPrivacyManagerHelper.kt @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2022 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.utils + +import android.content.Context +import android.hardware.SensorPrivacyManager +import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams +import android.hardware.SensorPrivacyManager.Sources.SETTINGS +import android.util.Log +import java.util.concurrent.Executor + +/** + * A class to help with calls to the sensor privacy manager. This class caches state when needed and + * multiplexes multiple listeners to a minimal set of binder calls. + * + * If you are not a test use [SensorPrivacyManagerHelper.getInstance] + */ +// This class uses `open` a lot for mockito +open class SensorPrivacyManagerHelper(context: Context) : + SensorPrivacyManager.OnSensorPrivacyChangedListener { + private val sensorPrivacyManager: SensorPrivacyManager + private val cache: MutableMap, Boolean> = mutableMapOf() + private val callbacks: MutableMap, MutableSet>> = + mutableMapOf() + private val lock = Any() + + /** + * Callback for when the state of the sensor privacy changes. + */ + interface Callback { + /** + * Method invoked when the sensor privacy changes. + * @param sensor The sensor which changed + * @param blocked If the sensor is blocked + */ + fun onSensorPrivacyChanged(toggleType: Int, sensor: Int, blocked: Boolean) + } + + init { + sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!! + + sensorPrivacyManager.addSensorPrivacyListener(context.mainExecutor, this) + } + + /** + * Checks if the given toggle is supported on this device + * @param sensor The sensor to check + * @return whether the toggle for the sensor is supported on this device. + */ + open fun supportsSensorToggle(sensor: Int): Boolean { + return sensorPrivacyManager.supportsSensorToggle(sensor) + } + + @JvmOverloads + open fun isSensorBlocked(toggleType: Int = TOGGLE_TYPE_ANY, sensor: Int): Boolean { + synchronized(lock) { + if (toggleType == TOGGLE_TYPE_ANY) { + return isSensorBlocked(TOGGLE_TYPE_SOFTWARE, sensor) || + isSensorBlocked(TOGGLE_TYPE_HARDWARE, sensor) + } + return cache.getOrPut(toggleType to sensor) { + sensorPrivacyManager.isSensorPrivacyEnabled(toggleType, sensor) + } + } + } + + open fun setSensorBlocked(sensor: Int, blocked: Boolean) { + sensorPrivacyManager.setSensorPrivacy(SETTINGS, sensor, blocked) + } + + open fun addSensorBlockedListener(executor: Executor?, callback: Callback?) { + // Not using defaults for mockito + addSensorBlockedListener(SENSOR_ANY, executor, callback) + } + + open fun addSensorBlockedListener(sensor: Int, executor: Executor?, callback: Callback?) { + // Not using defaults for mockito + addSensorBlockedListener(TOGGLE_TYPE_ANY, sensor, executor, callback) + } + + open fun addSensorBlockedListener(toggleType: Int, sensor: Int, + executor: Executor?, callback: Callback?) { + // Note: executor and callback should be nonnull, but we want to use mockito + if (toggleType == TOGGLE_TYPE_ANY) { + addSensorBlockedListener(TOGGLE_TYPE_SOFTWARE, sensor, executor, callback) + addSensorBlockedListener(TOGGLE_TYPE_HARDWARE, sensor, executor, callback) + return + } + + if (sensor == SENSOR_ANY) { + addSensorBlockedListener(toggleType, SENSOR_MICROPHONE, executor, callback) + addSensorBlockedListener(toggleType, SENSOR_CAMERA, executor, callback) + return + } + + synchronized(lock) { + callbacks.getOrPut(toggleType to sensor) { mutableSetOf() } + .add(callback!! to executor!!) + } + } + + open fun removeSensorBlockedListener(callback: Callback) { + val keysToRemove = mutableListOf>() + synchronized(lock) { + callbacks.forEach { entry -> + entry.value.removeIf { + it.first == callback + } + + if (entry.value.isEmpty()) { + keysToRemove.add(entry.key) + } + } + + keysToRemove.forEach { + callbacks.remove(it) + } + } + } + + companion object { + const val TOGGLE_TYPE_SOFTWARE = SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE + const val TOGGLE_TYPE_HARDWARE = SensorPrivacyManager.TOGGLE_TYPE_HARDWARE + const val SENSOR_MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE + const val SENSOR_CAMERA = SensorPrivacyManager.Sensors.CAMERA + + private const val TOGGLE_TYPE_ANY = -1 + private const val SENSOR_ANY = -1 + private var sInstance: SensorPrivacyManagerHelper? = null + + /** + * Gets the singleton instance + * @param context The context which is needed if the instance hasn't been created + * @return the instance + */ + @JvmStatic + fun getInstance(context: Context): SensorPrivacyManagerHelper? { + if (sInstance == null) { + sInstance = SensorPrivacyManagerHelper(context) + } + return sInstance + } + } + + override fun onSensorPrivacyChanged(sensor: Int, enabled: Boolean) { + // ignored + } + + override fun onSensorPrivacyChanged(params: SensorPrivacyChangedParams) { + var changed: Boolean + synchronized(lock) { + changed = cache.put(params.toggleType to params.sensor, params.isEnabled) != + params.isEnabled + + if (changed) { + callbacks[params.toggleType to params.sensor]?.forEach { + it.second.execute { + it.first.onSensorPrivacyChanged(params.toggleType, params.sensor, + params.isEnabled) + } + } + } + } + } +} diff --git a/src/com/android/settings/wifi/AddWifiNetworkPreference.java b/src/com/android/settings/wifi/AddWifiNetworkPreference.java index 18205391525..6aafbcacfb1 100644 --- a/src/com/android/settings/wifi/AddWifiNetworkPreference.java +++ b/src/com/android/settings/wifi/AddWifiNetworkPreference.java @@ -19,28 +19,25 @@ package com.android.settings.wifi; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.os.UserManager; import android.util.Log; import android.widget.ImageButton; import androidx.annotation.DrawableRes; -import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; -import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.wifi.dpp.WifiDppUtils; -import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; +import com.android.settingslib.RestrictedPreference; /** * The Preference for users to add Wi-Fi networks in WifiSettings */ -public class AddWifiNetworkPreference extends Preference { +public class AddWifiNetworkPreference extends RestrictedPreference { private static final String TAG = "AddWifiNetworkPreference"; private final Drawable mScanIconDrawable; - @VisibleForTesting - boolean mIsAddWifiConfigAllow; public AddWifiNetworkPreference(Context context) { super(context); @@ -51,8 +48,7 @@ public class AddWifiNetworkPreference extends Preference { setTitle(R.string.wifi_add_network); mScanIconDrawable = getDrawable(R.drawable.ic_scan_24dp); - mIsAddWifiConfigAllow = WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(context); - updatePreferenceForRestriction(); + checkRestrictionAndSetDisabled(UserManager.DISALLOW_ADD_WIFI_CONFIG); } @Override @@ -79,12 +75,4 @@ public class AddWifiNetworkPreference extends Preference { } return buttonIcon; } - - @VisibleForTesting - void updatePreferenceForRestriction() { - if (!mIsAddWifiConfigAllow) { - setEnabled(false); - setSummary(R.string.not_allowed_by_ent); - } - } } diff --git a/src/com/android/settings/wifi/WifiEntryPreference.java b/src/com/android/settings/wifi/WifiEntryPreference.java index fedd9c977e9..5763abdbd81 100644 --- a/src/com/android/settings/wifi/WifiEntryPreference.java +++ b/src/com/android/settings/wifi/WifiEntryPreference.java @@ -21,6 +21,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; +import android.os.UserManager; import android.text.TextUtils; import android.view.View; import android.widget.ImageButton; @@ -30,10 +31,10 @@ import android.widget.TextView; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import com.android.settingslib.R; +import com.android.settingslib.RestrictedPreference; import com.android.settingslib.Utils; import com.android.settingslib.wifi.WifiUtils; import com.android.wifitrackerlib.BaseWifiTracker; @@ -42,7 +43,8 @@ import com.android.wifitrackerlib.WifiEntry; /** * Preference to display a WifiEntry in a wifi picker. */ -public class WifiEntryPreference extends Preference implements WifiEntry.WifiEntryCallback, +public class WifiEntryPreference extends RestrictedPreference implements + WifiEntry.WifiEntryCallback, View.OnClickListener { private static final int[] STATE_SECURED = { @@ -86,6 +88,7 @@ public class WifiEntryPreference extends Preference implements WifiEntry.WifiEnt mWifiEntry = wifiEntry; mWifiEntry.setListener(this); mIconInjector = iconInjector; + checkRestrictionAndSetDisabled(UserManager.DISALLOW_ADD_WIFI_CONFIG); refresh(); } @@ -309,5 +312,4 @@ public class WifiEntryPreference extends Preference implements WifiEntry.WifiEnt } return buttonIcon; } - } diff --git a/tests/robotests/src/com/android/settings/privacy/SensorPrivacyManagerHelperTest.java b/tests/robotests/src/com/android/settings/privacy/SensorPrivacyManagerHelperTest.java new file mode 100644 index 00000000000..71c613c06ed --- /dev/null +++ b/tests/robotests/src/com/android/settings/privacy/SensorPrivacyManagerHelperTest.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2022 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.privacy; + +import static android.hardware.SensorPrivacyManager.Sources.SETTINGS; + +import static com.android.settings.utils.SensorPrivacyManagerHelper.SENSOR_CAMERA; +import static com.android.settings.utils.SensorPrivacyManagerHelper.SENSOR_MICROPHONE; +import static com.android.settings.utils.SensorPrivacyManagerHelper.TOGGLE_TYPE_HARDWARE; +import static com.android.settings.utils.SensorPrivacyManagerHelper.TOGGLE_TYPE_SOFTWARE; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +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.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.hardware.SensorPrivacyManager; +import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener; +import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams; + +import com.android.settings.utils.SensorPrivacyManagerHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.robolectric.RobolectricTestRunner; + +import java.util.concurrent.Executor; + +@RunWith(RobolectricTestRunner.class) +public class SensorPrivacyManagerHelperTest { + + private MockitoSession mMockitoSession; + + /** Execute synchronously */ + private Executor mExecutor = r -> r.run(); + + @Mock + private Context mContext; + @Mock + private SensorPrivacyManager mSensorPrivacyManager; + + private SensorPrivacyManagerHelper mSensorPrivacyManagerHelper; + + @Before + public void setUp() { + mMockitoSession = Mockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.WARN) + .startMocking(); + + doReturn(mExecutor).when(mContext) + .getMainExecutor(); + doReturn(mSensorPrivacyManager).when(mContext) + .getSystemService(eq(SensorPrivacyManager.class)); + + mSensorPrivacyManagerHelper = new SensorPrivacyManagerHelper(mContext); + } + + @After + public void tearDown() { + mMockitoSession.finishMocking(); + } + + /** + * Verify that a sensor privacy listener is added in constructor. + */ + @Test + public void constructor_invokeAddSensorPrivacyListener() { + verify(mSensorPrivacyManager, times(1)).addSensorPrivacyListener(eq(mExecutor), + any(OnSensorPrivacyChangedListener.class)); + } + + /** + * Verify when SensorPrivacyManagerHelper#setSensorBlocked(microphone, true) called, + * SensorPrivacyManager#setSensorPrivacy(microphone, true) is invoked. + */ + @Test + public void invokeSetMicrophoneBlocked_invokeSetMicrophonePrivacyTrue() { + mSensorPrivacyManagerHelper.setSensorBlocked(SENSOR_MICROPHONE, true); + verify(mSensorPrivacyManager, times(1)) + .setSensorPrivacy(eq(SETTINGS), eq(SENSOR_MICROPHONE), eq(true)); + } + + /** + * Verify when SensorPrivacyManagerHelper#setSensorBlocked(microphone, false) called, + * SensorPrivacyManager#setSensorPrivacy(microphone, false) is invoked. + */ + @Test + public void invokeSetMicrophoneUnBlocked_invokeSetMicrophonePrivacyFalse() { + mSensorPrivacyManagerHelper.setSensorBlocked(SENSOR_MICROPHONE, false); + verify(mSensorPrivacyManager, times(1)) + .setSensorPrivacy(eq(SETTINGS), eq(SENSOR_MICROPHONE), eq(false)); + } + + /** + * Verify when a callback is added with no toggleType and no sensor filter, then the + * callback is invoked on changes to all states. + */ + @Test + public void addCallbackNoFilter_invokeCallback() { + SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener(); + + SensorPrivacyManagerHelper.Callback callback = + mock(SensorPrivacyManagerHelper.Callback.class); + mSensorPrivacyManagerHelper.addSensorBlockedListener(mExecutor, callback); + + verifyAllCases(listener, (t, s, e, i) -> { + verify(callback, times(1)).onSensorPrivacyChanged(eq(t), eq(s), eq(e)); + verify(callback, times(i + 1)).onSensorPrivacyChanged(anyInt(), anyInt(), anyBoolean()); + }); + } + + /** + * Verify when a callback is added with a filter to only dispatch microphone events, then the + * callback is only invoked on changes to microphone state. + */ + @Test + public void addCallbackMicrophoneOnlyFilter_invokeCallbackMicrophoneOnly() { + SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener(); + + SensorPrivacyManagerHelper.Callback callback = + mock(SensorPrivacyManagerHelper.Callback.class); + mSensorPrivacyManagerHelper.addSensorBlockedListener(SENSOR_MICROPHONE, mExecutor, + callback); + mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback); + + verifyAllCases(listener, (t, s, e, i) -> { + verify(callback, never()).onSensorPrivacyChanged(anyInt(), eq(SENSOR_MICROPHONE), + anyBoolean()); + }); + } + + /** + * Verify when a callback is added with a filter to only dispatch camera events, then the + * callback is only invoked on changes to camera state. + */ + @Test + public void addCallbackCameraOnlyFilter_invokeCallbackCameraOnly() { + SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener(); + + SensorPrivacyManagerHelper.Callback callback = + mock(SensorPrivacyManagerHelper.Callback.class); + mSensorPrivacyManagerHelper.addSensorBlockedListener(SENSOR_CAMERA, mExecutor, + callback); + mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback); + + verifyAllCases(listener, (t, s, e, i) -> { + verify(callback, never()).onSensorPrivacyChanged(anyInt(), eq(SENSOR_CAMERA), + anyBoolean()); + }); + } + + /** + * Verify when a callback is added with a filter to only dispatch software_toggle+microphone + * events, then the callback is only invoked on changes to microphone state. + */ + @Test + public void addCallbackSoftwareMicrophoneOnlyFilter_invokeCallbackSoftwareMicrophoneOnly() { + SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener(); + + SensorPrivacyManagerHelper.Callback callback = + mock(SensorPrivacyManagerHelper.Callback.class); + mSensorPrivacyManagerHelper.addSensorBlockedListener(TOGGLE_TYPE_SOFTWARE, + SENSOR_MICROPHONE, mExecutor, callback); + mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback); + + verifyAllCases(listener, (t, s, e, i) -> { + verify(callback, never()).onSensorPrivacyChanged(eq(TOGGLE_TYPE_SOFTWARE), + eq(SENSOR_MICROPHONE), anyBoolean()); + }); + } + + /** + * Verify when a callback is added with a filter to only dispatch software_toggle+camera + * events, then the callback is only invoked on changes to camera state. + */ + @Test + public void addCallbackSoftwareCameraOnlyFilter_invokeCallbackSoftwareCameraOnly() { + SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener(); + + SensorPrivacyManagerHelper.Callback callback = + mock(SensorPrivacyManagerHelper.Callback.class); + mSensorPrivacyManagerHelper.addSensorBlockedListener(TOGGLE_TYPE_SOFTWARE, + SENSOR_CAMERA, mExecutor, callback); + mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback); + + verifyAllCases(listener, (t, s, e, i) -> { + verify(callback, never()).onSensorPrivacyChanged(eq(TOGGLE_TYPE_SOFTWARE), + eq(SENSOR_CAMERA), anyBoolean()); + }); + } + + /** + * Verify when a callback is added with a filter to only dispatch hardware_toggle+microphone + * events, then the callback is only invoked on changes to microphone state. + */ + @Test + public void addCallbackHardwareMicrophoneOnlyFilter_invokeCallbackHardwareMicrophoneOnly() { + SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener(); + + SensorPrivacyManagerHelper.Callback callback = + mock(SensorPrivacyManagerHelper.Callback.class); + mSensorPrivacyManagerHelper.addSensorBlockedListener(TOGGLE_TYPE_HARDWARE, + SENSOR_MICROPHONE, mExecutor, callback); + mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback); + + verifyAllCases(listener, (t, s, e, i) -> { + verify(callback, never()).onSensorPrivacyChanged(eq(TOGGLE_TYPE_HARDWARE), + eq(SENSOR_MICROPHONE), anyBoolean()); + }); + } + + /** + * Verify when a callback is added with a filter to only dispatch hardware_toggle+camera + * events, then the callback is only invoked on changes to camera state. + */ + @Test + public void addCallbackHardwareCameraOnlyFilter_invokeCallbackHardwareCameraOnly() { + SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener(); + + SensorPrivacyManagerHelper.Callback callback = + mock(SensorPrivacyManagerHelper.Callback.class); + mSensorPrivacyManagerHelper.addSensorBlockedListener(TOGGLE_TYPE_HARDWARE, + SENSOR_CAMERA, mExecutor, callback); + mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback); + + verifyAllCases(listener, (t, s, e, i) -> { + verify(callback, never()).onSensorPrivacyChanged(eq(TOGGLE_TYPE_HARDWARE), + eq(SENSOR_CAMERA), anyBoolean()); + }); + } + + /** + * Verify when a callback is removed, then the callback is never invoked on changes to state. + */ + @Test + public void removeCallback_noInvokeCallback() { + SensorPrivacyManager.OnSensorPrivacyChangedListener listener = getServiceListener(); + + SensorPrivacyManagerHelper.Callback callback = + mock(SensorPrivacyManagerHelper.Callback.class); + mSensorPrivacyManagerHelper.addSensorBlockedListener(mExecutor, callback); + mSensorPrivacyManagerHelper.removeSensorBlockedListener(callback); + + verifyAllCases(listener, (t, s, e, i) -> { + verify(callback, never()).onSensorPrivacyChanged(anyInt(), anyInt(), anyBoolean()); + }); + } + + private interface Verifier { + + /** + * This method should throw in the fail case. + */ + void verifyCallback(int toggleType, int sensor, boolean isEnabled, int iterationNumber); + } + + private void verifyAllCases(SensorPrivacyManager.OnSensorPrivacyChangedListener listener, + Verifier verifier) { + int[] toggleTypes = {TOGGLE_TYPE_SOFTWARE, TOGGLE_TYPE_HARDWARE}; + int[] sensors = {SENSOR_MICROPHONE, SENSOR_CAMERA}; + boolean[] enabledValues = {false, true}; + + int i = 0; + for (int t : toggleTypes) { + for (int s : sensors) { + for (boolean e : enabledValues) { + listener.onSensorPrivacyChanged(createParams(t, s, e)); + + verifier.verifyCallback(t, s, e, i++); + } + } + } + } + + private OnSensorPrivacyChangedListener getServiceListener() { + ArgumentCaptor captor = + ArgumentCaptor.forClass(OnSensorPrivacyChangedListener.class); + verify(mSensorPrivacyManager).addSensorPrivacyListener(eq(mExecutor), + captor.capture()); + + OnSensorPrivacyChangedListener listener = captor.getValue(); + return listener; + } + + private SensorPrivacyChangedParams createParams(int toggleType, int sensor, boolean enabled) { + SensorPrivacyChangedParams params = mock(SensorPrivacyChangedParams.class); + doReturn(toggleType).when(params).getToggleType(); + doReturn(sensor).when(params).getSensor(); + doReturn(enabled).when(params).isEnabled(); + return params; + } +} diff --git a/tests/robotests/src/com/android/settings/privacy/SensorToggleControllerTest.java b/tests/robotests/src/com/android/settings/privacy/SensorToggleControllerTest.java index 2c04e3bd004..9f0bb736a2c 100644 --- a/tests/robotests/src/com/android/settings/privacy/SensorToggleControllerTest.java +++ b/tests/robotests/src/com/android/settings/privacy/SensorToggleControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2021 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. @@ -16,275 +16,426 @@ package com.android.settings.privacy; -import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; -import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; -import static android.hardware.SensorPrivacyManager.Sources.OTHER; +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; +import static com.android.settings.utils.SensorPrivacyManagerHelper.SENSOR_CAMERA; +import static com.android.settings.utils.SensorPrivacyManagerHelper.SENSOR_MICROPHONE; +import static com.android.settings.utils.SensorPrivacyManagerHelper.TOGGLE_TYPE_SOFTWARE; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.content.Context; -import android.hardware.SensorPrivacyManager; -import android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener; -import android.util.ArraySet; -import android.util.SparseArray; -import android.util.SparseBooleanArray; + +import androidx.preference.PreferenceScreen; import com.android.settings.utils.SensorPrivacyManagerHelper; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import org.robolectric.RobolectricTestRunner; -import java.lang.reflect.Method; -import java.util.Set; +import java.util.concurrent.Executor; @RunWith(RobolectricTestRunner.class) public class SensorToggleControllerTest { + private MockitoSession mMockitoSession; + @Mock private Context mContext; @Mock - private SensorPrivacyManager mSensorPrivacyManager; - private SparseBooleanArray mMicState; - private SparseBooleanArray mCamState; - private SparseArray> mMicListeners; - private SparseArray> mCamListeners; + private SensorPrivacyManagerHelper mSensorPrivacyManagerHelper; @Before public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = Mockito.mock(Context.class); - mSensorPrivacyManager = Mockito.mock(SensorPrivacyManager.class); + mMockitoSession = Mockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.WARN) + .startMocking(); - try { - Method clearInstance = - SensorPrivacyManagerHelper.class.getDeclaredMethod("clearInstance"); - clearInstance.setAccessible(true); - clearInstance.invoke(null); - } catch (Exception e) { - throw new RuntimeException(e); - } - mMicState = new SparseBooleanArray(); - mCamState = new SparseBooleanArray(); - mMicState.put(0, false); - mCamState.put(0, false); - mMicState.put(10, false); - mCamState.put(10, false); - mMicListeners = new SparseArray<>(); - mCamListeners = new SparseArray<>(); - mMicListeners.put(0, new ArraySet<>()); - mMicListeners.put(10, new ArraySet<>()); - mCamListeners.put(0, new ArraySet<>()); - mCamListeners.put(10, new ArraySet<>()); - - doReturn(0).when(mContext).getUserId(); - doReturn(mSensorPrivacyManager).when(mContext) - .getSystemService(SensorPrivacyManager.class); - - doAnswer(invocation -> mMicState.get(0)) - .when(mSensorPrivacyManager).isSensorPrivacyEnabled(eq(MICROPHONE)); - doAnswer(invocation -> mCamState.get(0)) - .when(mSensorPrivacyManager).isSensorPrivacyEnabled(eq(CAMERA)); - doAnswer(invocation -> mMicState.get(invocation.getArgument(1))) - .when(mSensorPrivacyManager).isSensorPrivacyEnabled(eq(MICROPHONE), anyInt()); - doAnswer(invocation -> mCamState.get(invocation.getArgument(1))) - .when(mSensorPrivacyManager).isSensorPrivacyEnabled(eq(CAMERA), anyInt()); - - doAnswer(invocation -> { - mMicState.put(0, invocation.getArgument(2)); - mMicState.put(10, invocation.getArgument(2)); - for (OnSensorPrivacyChangedListener listener : mMicListeners.get(0)) { - listener.onSensorPrivacyChanged(MICROPHONE, mMicState.get(0)); - } - return null; - }).when(mSensorPrivacyManager).setSensorPrivacy(anyInt(), eq(MICROPHONE), anyBoolean()); - doAnswer(invocation -> { - mCamState.put(0, invocation.getArgument(2)); - mCamState.put(10, invocation.getArgument(2)); - for (OnSensorPrivacyChangedListener listener : mMicListeners.get(0)) { - listener.onSensorPrivacyChanged(CAMERA, mMicState.get(0)); - } - return null; - }).when(mSensorPrivacyManager).setSensorPrivacy(anyInt(), eq(CAMERA), anyBoolean()); - - doAnswer(invocation -> { - mMicState.put(0, invocation.getArgument(2)); - mMicState.put(10, invocation.getArgument(2)); - for (OnSensorPrivacyChangedListener listener : mMicListeners.get(0)) { - listener.onSensorPrivacyChanged(MICROPHONE, mMicState.get(0)); - } - for (OnSensorPrivacyChangedListener listener : mMicListeners.get(10)) { - listener.onSensorPrivacyChanged(MICROPHONE, mMicState.get(10)); - } - return null; - }).when(mSensorPrivacyManager) - .setSensorPrivacyForProfileGroup(anyInt(), eq(MICROPHONE), anyBoolean()); - doAnswer(invocation -> { - mCamState.put(0, invocation.getArgument(2)); - mCamState.put(10, invocation.getArgument(2)); - for (OnSensorPrivacyChangedListener listener : mCamListeners.get(0)) { - listener.onSensorPrivacyChanged(CAMERA, mCamState.get(0)); - } - for (OnSensorPrivacyChangedListener listener : mCamListeners.get(10)) { - listener.onSensorPrivacyChanged(CAMERA, mCamState.get(10)); - } - return null; - }).when(mSensorPrivacyManager) - .setSensorPrivacyForProfileGroup(anyInt(), eq(CAMERA), anyBoolean()); - - doAnswer(invocation -> mMicListeners.get(0).add(invocation.getArgument(1))) - .when(mSensorPrivacyManager).addSensorPrivacyListener(eq(MICROPHONE), any()); - doAnswer(invocation -> mCamListeners.get(0).add(invocation.getArgument(1))) - .when(mSensorPrivacyManager).addSensorPrivacyListener(eq(CAMERA), any()); - - doAnswer(invocation -> mMicListeners.get(invocation.getArgument(2)) - .add(invocation.getArgument(1))).when(mSensorPrivacyManager) - .addSensorPrivacyListener(eq(MICROPHONE), anyInt(), any()); - doAnswer(invocation -> mCamListeners.get(invocation.getArgument(2)) - .add(invocation.getArgument(1))).when(mSensorPrivacyManager) - .addSensorPrivacyListener(eq(CAMERA), anyInt(), any()); + doReturn((Executor) r -> r.run()).when(mContext).getMainExecutor(); } + @After + public void tearDown() { + mMockitoSession.finishMocking(); + } + + /** + * Test the availability status when mic toggle is not supported. + */ + @Test + public void getAvailabilityStatus_MicrophoneToggleNotSupported_returnUnsupported() { + // Return not supported + doReturn(false).when(mSensorPrivacyManagerHelper).supportsSensorToggle(SENSOR_MICROPHONE); + MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle", + mSensorPrivacyManagerHelper); + // Verify not available + assertEquals(UNSUPPORTED_ON_DEVICE, micToggleController.getAvailabilityStatus()); + } + + /** + * Test the availability status when mic toggle is supported. + */ + @Test + public void getAvailabilityStatus_MicrophoneToggleSupported_returnAvailable() { + // Return supported + doReturn(true).when(mSensorPrivacyManagerHelper).supportsSensorToggle(SENSOR_MICROPHONE); + MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle", + mSensorPrivacyManagerHelper); + // Verify available + assertEquals(AVAILABLE, micToggleController.getAvailabilityStatus()); + } + + /** + * Test the initial state shows mic unblocked when created. + */ @Test public void isChecked_disableMicrophoneSensorPrivacy_returnTrue() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, MICROPHONE, false); - MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle"); + // Starts off unblocked + doReturn(false).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_MICROPHONE); + MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle", + mSensorPrivacyManagerHelper); + // Verify the controller is checked assertTrue(micToggleController.isChecked()); } + /** + * Test the initial state shows mic blocked when created. + */ @Test public void isChecked_enableMicrophoneSensorPrivacy_returnFalse() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, MICROPHONE, true); - MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle"); + // Starts off blocked + doReturn(true).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_MICROPHONE); + MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle", + mSensorPrivacyManagerHelper); + // Verify the controller is unchecked assertFalse(micToggleController.isChecked()); } + @Test + public void startMicrophoneToggleController_invokeAddSensorBlockedListener() { + MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle", + mSensorPrivacyManagerHelper); + micToggleController.onStart(); + verify(mSensorPrivacyManagerHelper, times(1)) + .addSensorBlockedListener(eq(SENSOR_MICROPHONE), any(), any()); + } + + @Test + public void stopMicrophoneToggleController_invokeRemoveSensorBlockedListener() { + MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle", + mSensorPrivacyManagerHelper); + micToggleController.onStart(); + + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(SensorPrivacyManagerHelper.Callback.class); + verify(mSensorPrivacyManagerHelper, times(1)) + .addSensorBlockedListener(eq(SENSOR_MICROPHONE), any(), callbackCaptor.capture()); + + micToggleController.onStop(); + + verify(mSensorPrivacyManagerHelper, times(1)) + .removeSensorBlockedListener(callbackCaptor.getValue()); + } + + /** + * Test the state of the mic controller switches from unblocked to blocked when state is changed + * externally. + */ @Test public void isChecked_disableMicrophoneSensorPrivacyThenChanged_returnFalse() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, MICROPHONE, false); - MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle"); - mSensorPrivacyManager.setSensorPrivacy(OTHER, MICROPHONE, true); + // Starts off unblocked + doReturn(false).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_MICROPHONE); + MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle", + mSensorPrivacyManagerHelper); + // Preference is started + micToggleController.displayPreference(mock(PreferenceScreen.class)); + micToggleController.onStart(); + + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(SensorPrivacyManagerHelper.Callback.class); + verify(mSensorPrivacyManagerHelper, times(1)) + .addSensorBlockedListener(eq(SENSOR_MICROPHONE), any(), callbackCaptor.capture()); + + // The state changed externally, update return value of isSensorBlocked and invoke callback + doReturn(true).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_MICROPHONE); + callbackCaptor.getValue() + .onSensorPrivacyChanged(TOGGLE_TYPE_SOFTWARE, SENSOR_MICROPHONE, true); assertFalse(micToggleController.isChecked()); } + + /** + * Test the state of the mic controller switches from blocked to unblocked when state is changed + * externally. + */ @Test public void isChecked_enableMicrophoneSensorPrivacyThenChanged_returnTrue() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, MICROPHONE, true); - MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle"); - mSensorPrivacyManager.setSensorPrivacy(OTHER, MICROPHONE, false); + // Starts off blocked + doReturn(true).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_MICROPHONE); + MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle", + mSensorPrivacyManagerHelper); + // Preference is started + micToggleController.displayPreference(mock(PreferenceScreen.class)); + micToggleController.onStart(); + + // The state changed externally, update return value of isSensorBlocked and invoke callback + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(SensorPrivacyManagerHelper.Callback.class); + verify(mSensorPrivacyManagerHelper, times(1)) + .addSensorBlockedListener(eq(SENSOR_MICROPHONE), any(), callbackCaptor.capture()); + + doReturn(false).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_MICROPHONE); + callbackCaptor.getValue() + .onSensorPrivacyChanged(TOGGLE_TYPE_SOFTWARE, SENSOR_MICROPHONE, false); assertTrue(micToggleController.isChecked()); } + + /** + * Test the mic controller requests to block the mic when unblocked on invocation of setChecked. + */ @Test - public void isMicrophoneSensorPrivacyEnabled_uncheckMicToggle_returnTrue() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, MICROPHONE, false); - MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle"); + public void blocked_uncheckMicToggle_returnTrue() { + // Starts off unblocked + doReturn(false).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_MICROPHONE); + + MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle", + mSensorPrivacyManagerHelper); + verify(mSensorPrivacyManagerHelper, never()).setSensorBlocked(anyInt(), anyBoolean()); + + // User set blocked micToggleController.setChecked(false); - assertTrue(mMicState.get(0)); + + ArgumentCaptor blockedResult = ArgumentCaptor.forClass(Boolean.class); + verify(mSensorPrivacyManagerHelper, times(1)) + .setSensorBlocked(ArgumentMatchers.eq(SENSOR_MICROPHONE), blockedResult.capture()); + + // Verify attempt to block + assertTrue(blockedResult.getValue()); } + + + /** + * Test the mic controller requests to unblock the mic when blocked on invocation of setChecked. + */ @Test - public void isMicrophoneSensorPrivacyEnabled_checkMicToggle_returnFalse() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, MICROPHONE, true); - MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle"); + public void blocked_checkMicToggle_returnFalse() { + // Starts off blocked + doReturn(true).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_MICROPHONE); + + MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle", + mSensorPrivacyManagerHelper); + verify(mSensorPrivacyManagerHelper, never()).setSensorBlocked(anyInt(), anyBoolean()); + + // User set unblocked micToggleController.setChecked(true); - assertFalse(mMicState.get(0)); + + ArgumentCaptor blockedResult = ArgumentCaptor.forClass(Boolean.class); + verify(mSensorPrivacyManagerHelper, times(1)) + .setSensorBlocked(ArgumentMatchers.eq(SENSOR_MICROPHONE), blockedResult.capture()); + + // Verify attempt to unblock + assertFalse(blockedResult.getValue()); } + /** + * Test the availability status when cam toggle is not supported. + */ @Test - public void isMicrophoneSensorPrivacyEnabledForProfileUser_uncheckMicToggle_returnTrue() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, MICROPHONE, false); - MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle"); - micToggleController.setChecked(false); - assertTrue(mMicState.get(10)); + public void getAvailabilityStatus_CameraToggleNotSupported_returnUnsupported() { + // Return not supported + doReturn(false).when(mSensorPrivacyManagerHelper).supportsSensorToggle(SENSOR_CAMERA); + CameraToggleController cameraToggleController = new CameraToggleController(mContext, + "cam_toggle", mSensorPrivacyManagerHelper); + // Verify not available + assertEquals(UNSUPPORTED_ON_DEVICE, cameraToggleController.getAvailabilityStatus()); } + /** + * Test the availability status when cam toggle is supported. + */ @Test - public void isMicrophoneSensorPrivacyEnabledProfileUser_checkMicToggle_returnFalse() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, MICROPHONE, true); - MicToggleController micToggleController = new MicToggleController(mContext, "mic_toggle"); - micToggleController.setChecked(true); - assertFalse(mMicState.get(10)); + public void getAvailabilityStatus_CameraToggleSupported_returnAvailable() { + // Return supported + doReturn(true).when(mSensorPrivacyManagerHelper).supportsSensorToggle(SENSOR_CAMERA); + CameraToggleController cameraToggleController = new CameraToggleController(mContext, + "cam_toggle", mSensorPrivacyManagerHelper); + // Verify available + assertEquals(AVAILABLE, cameraToggleController.getAvailabilityStatus()); } + /** + * Test the initial state shows cam unblocked when created. + */ @Test public void isChecked_disableCameraSensorPrivacy_returnTrue() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, CAMERA, false); - CameraToggleController camToggleController = - new CameraToggleController(mContext, "cam_toggle"); - assertTrue(camToggleController.isChecked()); + // Starts off unblocked + doReturn(false).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_CAMERA); + CameraToggleController cameraToggleController = new CameraToggleController(mContext, + "cam_toggle", mSensorPrivacyManagerHelper); + // Verify the controller is checked + assertTrue(cameraToggleController.isChecked()); } + /** + * Test the initial state shows cam blocked when created. + */ @Test public void isChecked_enableCameraSensorPrivacy_returnFalse() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, CAMERA, true); - CameraToggleController camToggleController = - new CameraToggleController(mContext, "cam_toggle"); - assertFalse(camToggleController.isChecked()); + // Starts off blocked + doReturn(true).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_CAMERA); + CameraToggleController cameraToggleController = new CameraToggleController(mContext, + "cam_toggle", mSensorPrivacyManagerHelper); + // Verify the controller is unchecked + assertFalse(cameraToggleController.isChecked()); } + @Test + public void startCameraToggleController_invokeAddSensorBlockedListener() { + CameraToggleController cameraToggleController = + new CameraToggleController(mContext, "cam_toggle", mSensorPrivacyManagerHelper); + cameraToggleController.onStart(); + verify(mSensorPrivacyManagerHelper, times(1)) + .addSensorBlockedListener(eq(SENSOR_CAMERA), any(), any()); + } + + @Test + public void stopCameraToggleController_invokeRemoveSensorBlockedListener() { + CameraToggleController cameraToggleController = + new CameraToggleController(mContext, "cam_toggle", mSensorPrivacyManagerHelper); + cameraToggleController.onStart(); + + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(SensorPrivacyManagerHelper.Callback.class); + verify(mSensorPrivacyManagerHelper, times(1)) + .addSensorBlockedListener(eq(SENSOR_CAMERA), any(), callbackCaptor.capture()); + + cameraToggleController.onStop(); + + verify(mSensorPrivacyManagerHelper, times(1)) + .removeSensorBlockedListener(callbackCaptor.getValue()); + } + + /** + * Test the state of the cam controller switches from unblocked to blocked when state is changed + * externally. + */ @Test public void isChecked_disableCameraSensorPrivacyThenChanged_returnFalse() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, CAMERA, false); - CameraToggleController camToggleController = - new CameraToggleController(mContext, "cam_toggle"); - mSensorPrivacyManager.setSensorPrivacy(OTHER, CAMERA, true); - assertFalse(camToggleController.isChecked()); + // Starts off unblocked + doReturn(false).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_CAMERA); + CameraToggleController cameraToggleController = new CameraToggleController(mContext, + "cam_toggle", mSensorPrivacyManagerHelper); + // Preference is started + cameraToggleController.displayPreference(mock(PreferenceScreen.class)); + cameraToggleController.onStart(); + + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(SensorPrivacyManagerHelper.Callback.class); + verify(mSensorPrivacyManagerHelper, times(1)) + .addSensorBlockedListener(eq(SENSOR_CAMERA), any(), callbackCaptor.capture()); + + // The state changed externally, update return value of isSensorBlocked and invoke callback + doReturn(true).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_CAMERA); + callbackCaptor.getValue().onSensorPrivacyChanged(TOGGLE_TYPE_SOFTWARE, SENSOR_CAMERA, true); + assertFalse(cameraToggleController.isChecked()); } + + /** + * Test the state of the cam controller switches from blocked to unblocked when state is changed + * externally. + */ @Test public void isChecked_enableCameraSensorPrivacyThenChanged_returnTrue() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, CAMERA, true); - CameraToggleController camToggleController = - new CameraToggleController(mContext, "cam_toggle"); - mSensorPrivacyManager.setSensorPrivacy(OTHER, CAMERA, false); - assertTrue(camToggleController.isChecked()); + // Starts off blocked + doReturn(true).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_CAMERA); + CameraToggleController cameraToggleController = new CameraToggleController(mContext, + "cam_toggle", mSensorPrivacyManagerHelper); + // Preference is started + cameraToggleController.displayPreference(mock(PreferenceScreen.class)); + cameraToggleController.onStart(); + + // The state changed externally, update return value of isSensorBlocked and invoke callback + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(SensorPrivacyManagerHelper.Callback.class); + verify(mSensorPrivacyManagerHelper, times(1)) + .addSensorBlockedListener(eq(SENSOR_CAMERA), any(), callbackCaptor.capture()); + + doReturn(false).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_CAMERA); + callbackCaptor.getValue() + .onSensorPrivacyChanged(TOGGLE_TYPE_SOFTWARE, SENSOR_CAMERA, false); + assertTrue(cameraToggleController.isChecked()); } + + /** + * Test the cam controller requests to block the cam when unblocked on invocation of setChecked. + */ @Test - public void isCameraSensorPrivacyEnabled_uncheckCanToggle_returnTrue() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, CAMERA, false); - CameraToggleController camToggleController = - new CameraToggleController(mContext, "cam_toggle"); - camToggleController.setChecked(false); - assertTrue(mCamState.get(0)); + public void blocked_uncheckCamToggle_returnTrue() { + // Starts off unblocked + doReturn(false).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_CAMERA); + + CameraToggleController cameraToggleController = new CameraToggleController(mContext, + "cam_toggle", mSensorPrivacyManagerHelper); + verify(mSensorPrivacyManagerHelper, never()).setSensorBlocked(anyInt(), anyBoolean()); + + // User set blocked + cameraToggleController.setChecked(false); + + ArgumentCaptor blockedResult = ArgumentCaptor.forClass(Boolean.class); + verify(mSensorPrivacyManagerHelper, times(1)) + .setSensorBlocked(ArgumentMatchers.eq(SENSOR_CAMERA), blockedResult.capture()); + + // Verify attempt to block + assertTrue(blockedResult.getValue()); } - @Test - public void isCameraSensorPrivacyEnabled_checkCamToggle_returnFalse() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, CAMERA, true); - CameraToggleController camToggleController = - new CameraToggleController(mContext, "cam_toggle"); - camToggleController.setChecked(true); - assertFalse(mCamState.get(0)); - } - @Test - public void isCameraSensorPrivacyEnabledForProfileUser_uncheckCamToggle_returnTrue() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, CAMERA, false); - CameraToggleController camToggleController = - new CameraToggleController(mContext, "cam_toggle"); - camToggleController.setChecked(false); - assertTrue(mCamState.get(10)); - } + /** + * Test the cam controller requests to unblock the cam when blocked on invocation of setChecked. + */ @Test - public void isCameraSensorPrivacyEnabledProfileUser_checkCamToggle_returnFalse() { - mSensorPrivacyManager.setSensorPrivacy(OTHER, CAMERA, true); - CameraToggleController camToggleController = - new CameraToggleController(mContext, "cam_toggle"); - camToggleController.setChecked(true); - assertFalse(mCamState.get(10)); + public void blocked_checkCamToggle_returnFalse() { + // Starts off blocked + doReturn(true).when(mSensorPrivacyManagerHelper).isSensorBlocked(SENSOR_CAMERA); + + CameraToggleController cameraToggleController = new CameraToggleController(mContext, + "cam_toggle", mSensorPrivacyManagerHelper); + verify(mSensorPrivacyManagerHelper, never()).setSensorBlocked(anyInt(), anyBoolean()); + + // User set unblocked + cameraToggleController.setChecked(true); + + ArgumentCaptor blockedResult = ArgumentCaptor.forClass(Boolean.class); + verify(mSensorPrivacyManagerHelper, times(1)) + .setSensorBlocked(ArgumentMatchers.eq(SENSOR_CAMERA), blockedResult.capture()); + + // Verify attempt to unblock + assertFalse(blockedResult.getValue()); } } diff --git a/tests/robotests/src/com/android/settings/wifi/AddWifiNetworkPreferenceTest.java b/tests/robotests/src/com/android/settings/wifi/AddWifiNetworkPreferenceTest.java index 04ecdabf9fb..5f88eb2a083 100644 --- a/tests/robotests/src/com/android/settings/wifi/AddWifiNetworkPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/AddWifiNetworkPreferenceTest.java @@ -17,61 +17,56 @@ package com.android.settings.wifi; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - import android.content.Context; import android.os.UserManager; import androidx.test.core.app.ApplicationProvider; -import com.android.settings.R; +import com.android.settingslib.RestrictedLockUtils; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowRestrictedPreference.class) public class AddWifiNetworkPreferenceTest { - private Context mContext; - private AddWifiNetworkPreference mPreference; + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + Context mContext = ApplicationProvider.getApplicationContext(); - @Mock - private UserManager mUserManager; + private AddWifiNetworkPreference mPreference; @Before public void setUp() { - MockitoAnnotations.initMocks(this); - - mContext = spy(ApplicationProvider.getApplicationContext()); - when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); mPreference = new AddWifiNetworkPreference(mContext); - } @Test public void updatePreferenceForRestriction_isAddWifiConfigAllowed_prefIsEnabled() { - mPreference.mIsAddWifiConfigAllow = true; + // If the user is allowed to add Wi-Fi configuration then the EnforcedAdmin will be null. + RestrictedLockUtils.EnforcedAdmin enforcedAdmin = null; - mPreference.updatePreferenceForRestriction(); + mPreference.setDisabledByAdmin(enforcedAdmin); assertThat(mPreference.isEnabled()).isTrue(); - assertThat(mPreference.getSummary()).isNull(); } @Test public void updatePreferenceForRestriction_isAddWifiConfigNotAllowed_prefIsDisabled() { - mPreference.mIsAddWifiConfigAllow = false; + RestrictedLockUtils.EnforcedAdmin enforcedAdmin = new RestrictedLockUtils.EnforcedAdmin( + null /* component */, UserManager.DISALLOW_ADD_WIFI_CONFIG, null /* user */); - mPreference.updatePreferenceForRestriction(); + mPreference.setDisabledByAdmin(enforcedAdmin); assertThat(mPreference.isEnabled()).isFalse(); - assertThat(mPreference.getSummary()) - .isEqualTo(mContext.getString(R.string.not_allowed_by_ent)); } } diff --git a/tests/robotests/src/com/android/settings/wifi/ConnectedWifiEntryPreferenceTest.java b/tests/robotests/src/com/android/settings/wifi/ConnectedWifiEntryPreferenceTest.java index 401fdcff6e7..46a6132ab56 100644 --- a/tests/robotests/src/com/android/settings/wifi/ConnectedWifiEntryPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/ConnectedWifiEntryPreferenceTest.java @@ -35,8 +35,10 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowRestrictedPreference.class) public class ConnectedWifiEntryPreferenceTest { @Mock diff --git a/tests/robotests/src/com/android/settings/wifi/LongPressWifiEntryPreferenceTest.java b/tests/robotests/src/com/android/settings/wifi/LongPressWifiEntryPreferenceTest.java index fd7cc8943c9..efc2018ec85 100644 --- a/tests/robotests/src/com/android/settings/wifi/LongPressWifiEntryPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/LongPressWifiEntryPreferenceTest.java @@ -36,8 +36,10 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowRestrictedPreference.class) public class LongPressWifiEntryPreferenceTest { @Rule diff --git a/tests/robotests/src/com/android/settings/wifi/ShadowRestrictedPreference.java b/tests/robotests/src/com/android/settings/wifi/ShadowRestrictedPreference.java new file mode 100644 index 00000000000..4135ea080e7 --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/ShadowRestrictedPreference.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 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.wifi; + +import com.android.settingslib.RestrictedPreference; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(RestrictedPreference.class) +public class ShadowRestrictedPreference { + + @Implementation + public void checkRestrictionAndSetDisabled(String userRestriction) { + // do nothing + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java b/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java index a5d63dbd34d..0f51ef3874a 100644 --- a/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/WifiEntryPreferenceTest.java @@ -40,11 +40,13 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; import java.util.ArrayList; import java.util.List; @RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowRestrictedPreference.class) public class WifiEntryPreferenceTest { private Context mContext; diff --git a/tests/robotests/src/com/android/settings/wifi/WifiSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/WifiSettingsTest.java index 649f4cb45e4..b7c7270b908 100644 --- a/tests/robotests/src/com/android/settings/wifi/WifiSettingsTest.java +++ b/tests/robotests/src/com/android/settings/wifi/WifiSettingsTest.java @@ -70,6 +70,7 @@ import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowToast; @RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowRestrictedPreference.class) public class WifiSettingsTest { private static final int NUM_NETWORKS = 4; diff --git a/tests/robotests/src/com/android/settings/wifi/dpp/WifiNetworkListFragmentTest.java b/tests/robotests/src/com/android/settings/wifi/dpp/WifiNetworkListFragmentTest.java index e78f1c11348..e99b64003a2 100644 --- a/tests/robotests/src/com/android/settings/wifi/dpp/WifiNetworkListFragmentTest.java +++ b/tests/robotests/src/com/android/settings/wifi/dpp/WifiNetworkListFragmentTest.java @@ -36,6 +36,7 @@ import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.test.InstrumentationRegistry; +import com.android.settings.wifi.ShadowRestrictedPreference; import com.android.settings.wifi.WifiEntryPreference; import com.android.wifitrackerlib.SavedNetworkTracker; import com.android.wifitrackerlib.WifiEntry; @@ -45,10 +46,12 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import java.util.Arrays; @RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowRestrictedPreference.class) public class WifiNetworkListFragmentTest { private WifiNetworkListFragment mWifiNetworkListFragment; private Context mContext; diff --git a/tests/unit/src/com/android/settings/accessibility/SoftwareCursorKeyboardShiftPreferenceControllerTest.java b/tests/unit/src/com/android/settings/accessibility/SoftwareCursorKeyboardShiftPreferenceControllerTest.java deleted file mode 100644 index 2df291ab9bd..00000000000 --- a/tests/unit/src/com/android/settings/accessibility/SoftwareCursorKeyboardShiftPreferenceControllerTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2022 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.accessibility; - -import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; -import static com.android.settings.accessibility.AccessibilityUtil.State.ON; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Context; -import android.os.Looper; -import android.provider.Settings; - -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceScreen; -import androidx.preference.SwitchPreference; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.android.settings.core.BasePreferenceController; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** Tests for {@link SoftwareCursorKeyboardShiftPreferenceController}. */ -@RunWith(AndroidJUnit4.class) -public class SoftwareCursorKeyboardShiftPreferenceControllerTest { - - private PreferenceScreen mScreen; - private final Context mContext = ApplicationProvider.getApplicationContext(); - private SoftwareCursorKeyboardShiftPreferenceController mController; - private SwitchPreference mSwitchPreference; - - @Before - public void setUp() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - mController = new SoftwareCursorKeyboardShiftPreferenceController(mContext, - "cursor_trigger_keyboard_shift_enabled"); - mSwitchPreference = new SwitchPreference(mContext); - mSwitchPreference.setKey(mController.getPreferenceKey()); - PreferenceManager preferenceManager = new PreferenceManager(mContext); - mScreen = preferenceManager.createPreferenceScreen(mContext); - mScreen.addPreference(mSwitchPreference); - } - - @After - public void tearDown() { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED, OFF); - } - - @Test - public void getAvailabilityStatus_shouldReturnAvailable() { - assertThat(mController.getAvailabilityStatus()) - .isEqualTo(BasePreferenceController.AVAILABLE); - } - - @Test - public void performClick_cursorEnabled_shouldSetSettingDisabled() { - mController.setChecked(true); - mController.displayPreference(mScreen); - - mSwitchPreference.performClick(); - - assertThat(mSwitchPreference.isChecked()).isFalse(); - assertThat(isKeyboardShiftEnabled()).isFalse(); - } - - @Test - public void performClick_cursorDisabled_shouldSetSettingEnabled() { - mController.setChecked(false); - mController.displayPreference(mScreen); - - mSwitchPreference.performClick(); - - assertThat(mSwitchPreference.isChecked()).isTrue(); - assertThat(isKeyboardShiftEnabled()).isTrue(); - } - - @Test - public void setChecked_switchChecked_shouldSetSettingEnabled() { - mController.displayPreference(mScreen); - - mController.setChecked(/* isChecked= */ true); - - assertThat(isKeyboardShiftEnabled()).isTrue(); - } - - @Test - public void setChecked_switchUnchecked_shouldSetSettingDisabled() { - mController.displayPreference(mScreen); - - mController.setChecked(/* isChecked= */ false); - - assertThat(isKeyboardShiftEnabled()).isFalse(); - } - - private boolean isKeyboardShiftEnabled() { - return Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED, OFF) == ON; - } -} diff --git a/tests/unit/src/com/android/settings/accessibility/SoftwareCursorTriggerHintsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/accessibility/SoftwareCursorTriggerHintsPreferenceControllerTest.java deleted file mode 100644 index 73c118fa12d..00000000000 --- a/tests/unit/src/com/android/settings/accessibility/SoftwareCursorTriggerHintsPreferenceControllerTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2022 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.accessibility; - -import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; -import static com.android.settings.accessibility.AccessibilityUtil.State.ON; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Context; -import android.os.Looper; -import android.provider.Settings; - -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceScreen; -import androidx.preference.SwitchPreference; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.android.settings.core.BasePreferenceController; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** Tests for {@link SoftwareCursorTriggerHintsPreferenceController}. */ -@RunWith(AndroidJUnit4.class) -public class SoftwareCursorTriggerHintsPreferenceControllerTest { - - private PreferenceScreen mScreen; - private final Context mContext = ApplicationProvider.getApplicationContext(); - private SoftwareCursorTriggerHintsPreferenceController mController; - private SwitchPreference mSwitchPreference; - - @Before - public void setUp() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - mController = new SoftwareCursorTriggerHintsPreferenceController(mContext, - "cursor_trigger_hints_enabled"); - mSwitchPreference = new SwitchPreference(mContext); - mSwitchPreference.setKey(mController.getPreferenceKey()); - PreferenceManager preferenceManager = new PreferenceManager(mContext); - mScreen = preferenceManager.createPreferenceScreen(mContext); - mScreen.addPreference(mSwitchPreference); - } - - @After - public void tearDown() { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED, OFF); - } - - @Test - public void getAvailabilityStatus_shouldReturnAvailable() { - assertThat(mController.getAvailabilityStatus()) - .isEqualTo(BasePreferenceController.AVAILABLE); - } - - @Test - public void performClick_cursorEnabled_shouldSetSettingDisabled() { - mController.setChecked(true); - mController.displayPreference(mScreen); - - mSwitchPreference.performClick(); - - assertThat(mSwitchPreference.isChecked()).isFalse(); - assertThat(areTriggerHintsEnabled()).isFalse(); - } - - @Test - public void performClick_cursorDisabled_shouldSetSettingEnabled() { - mController.setChecked(false); - mController.displayPreference(mScreen); - - mSwitchPreference.performClick(); - - assertThat(mSwitchPreference.isChecked()).isTrue(); - assertThat(areTriggerHintsEnabled()).isTrue(); - } - - @Test - public void setChecked_switchChecked_shouldSetSettingEnabled() { - mController.displayPreference(mScreen); - - mController.setChecked(/* isChecked= */ true); - - assertThat(areTriggerHintsEnabled()).isTrue(); - } - - @Test - public void setChecked_switchUnchecked_shouldSetSettingDisabled() { - mController.displayPreference(mScreen); - - mController.setChecked(/* isChecked= */ false); - - assertThat(areTriggerHintsEnabled()).isFalse(); - } - - private boolean areTriggerHintsEnabled() { - return Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED, OFF) == ON; - } -}