Snap for 9153524 from 38eb8fea44 to udc-release
Change-Id: I81fc2bde0fc1c35dd2284969d177cc4d6f0281a6
This commit is contained in:
@@ -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 <init>(android.content.Context);
|
||||
public <init>(android.content.Context, android.util.AttributeSet);
|
||||
public <init>(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 <init>(android.content.Context, android.util.AttributeSet);
|
||||
}
|
||||
-keepclasseswithmembers class * {
|
||||
-keepclasseswithmembers class com.android.settings*.** {
|
||||
public <init>(android.content.Context, android.util.AttributeSet, int);
|
||||
}
|
||||
-keepclasseswithmembers class * {
|
||||
-keepclasseswithmembers class com.android.settings*.** {
|
||||
public <init>(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;
|
||||
|
||||
@@ -11909,13 +11909,6 @@
|
||||
<!-- [CHAR LIMIT=NONE] Hint for QR code process failure -->
|
||||
<string name="bt_le_audio_qr_code_is_not_valid_format">QR code isn\u0027t a valid format</string>
|
||||
|
||||
<!-- Accessibility Software Cursor -->
|
||||
|
||||
<!-- [CHAR LIMIT=NONE] Title for Accessibility Software Cursor setting for trigger hints. -->
|
||||
<string name="software_cursor_trigger_hints_enabled_title" translatable="false">Gesture detection area hints</string>
|
||||
<!-- [CHAR LIMIT=NONE] Title for Accessibility Software Cursor setting for keyboard shift. -->
|
||||
<string name="software_cursor_trigger_keyboard_shift_enabled_title" translatable="false">Shift gesture detection region above keyboard</string>
|
||||
|
||||
<!-- Mobile Bundled Apps Transparency Metadata-->
|
||||
|
||||
<!-- [CHAR_LIMIT=NONE] Label for mobile bundled apps screen -->
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:key="cursor_trigger_hints_enabled"
|
||||
android:title="@string/software_cursor_trigger_hints_enabled_title"
|
||||
settings:controller="com.android.settings.accessibility.SoftwareCursorTriggerHintsPreferenceController"/>
|
||||
<SwitchPreference
|
||||
android:key="cursor_trigger_keyboard_shift_enabled"
|
||||
android:title="@string/software_cursor_trigger_keyboard_shift_enabled_title"
|
||||
settings:controller="com.android.settings.accessibility.SoftwareCursorKeyboardShiftPreferenceController"/>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<UiccSlotMapping> uiccSlotMappings, Context context)
|
||||
Collection<UiccSlotMapping> 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Boolean> mCurrentUserCachedState = new SparseArray<>();
|
||||
private final SparseArray<SparseArray<Boolean>> mCachedState = new SparseArray<>();
|
||||
|
||||
private final SparseArray<OnSensorPrivacyChangedListener>
|
||||
mCurrentUserServiceListeners = new SparseArray<>();
|
||||
private final SparseArray<SparseArray<OnSensorPrivacyChangedListener>>
|
||||
mServiceListeners = new SparseArray<>();
|
||||
|
||||
private final ArraySet<CallbackInfo> 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<Boolean> 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<OnSensorPrivacyChangedListener>
|
||||
userServiceListeners = createUserServiceListenersIfNeededLocked(userId);
|
||||
|
||||
if (!userServiceListeners.contains(sensor)) {
|
||||
OnSensorPrivacyChangedListener listener = (s, enabled) -> {
|
||||
SparseArray<Boolean> 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<Boolean> createUserCachedStateIfNeededLocked(int userId) {
|
||||
SparseArray<Boolean> userCachedState = mCachedState.get(userId);
|
||||
if (userCachedState == null) {
|
||||
userCachedState = new SparseArray<>();
|
||||
mCachedState.put(userId, userCachedState);
|
||||
}
|
||||
return userCachedState;
|
||||
}
|
||||
|
||||
private SparseArray<OnSensorPrivacyChangedListener> createUserServiceListenersIfNeededLocked(
|
||||
int userId) {
|
||||
SparseArray<OnSensorPrivacyChangedListener> userServiceListeners =
|
||||
mServiceListeners.get(userId);
|
||||
if (userServiceListeners == null) {
|
||||
userServiceListeners = new SparseArray<>();
|
||||
mServiceListeners.put(userId, userServiceListeners);
|
||||
}
|
||||
return userServiceListeners;
|
||||
}
|
||||
}
|
||||
178
src/com/android/settings/utils/SensorPrivacyManagerHelper.kt
Normal file
178
src/com/android/settings/utils/SensorPrivacyManagerHelper.kt
Normal file
@@ -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<Pair<Int, Int>, Boolean> = mutableMapOf()
|
||||
private val callbacks: MutableMap<Pair<Int, Int>, MutableSet<Pair<Callback, Executor>>> =
|
||||
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<Pair<Int, Int>>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<OnSensorPrivacyChangedListener> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Set<OnSensorPrivacyChangedListener>> mMicListeners;
|
||||
private SparseArray<Set<OnSensorPrivacyChangedListener>> 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<SensorPrivacyManagerHelper.Callback> 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<SensorPrivacyManagerHelper.Callback> 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<SensorPrivacyManagerHelper.Callback> 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<Boolean> 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<Boolean> 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<SensorPrivacyManagerHelper.Callback> 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<SensorPrivacyManagerHelper.Callback> 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<SensorPrivacyManagerHelper.Callback> 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<Boolean> 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<Boolean> blockedResult = ArgumentCaptor.forClass(Boolean.class);
|
||||
verify(mSensorPrivacyManagerHelper, times(1))
|
||||
.setSensorBlocked(ArgumentMatchers.eq(SENSOR_CAMERA), blockedResult.capture());
|
||||
|
||||
// Verify attempt to unblock
|
||||
assertFalse(blockedResult.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user