Make SensorToggleControllers lifecycle aware

We need to watch the lifecycle so that we can unregister callbacks and
not cause leaks.

This change also rewrites the SensorPrivacyManagerHelper. The previous
implmementation was using deprecated apis. It also had an issue where
if a callback was added it would not necessarily register alistener with
the callback with the service since that was only done when the value is
checked. Now we register a listener when the class is instantiated and
with the new API there will only be the 1.

Finally we impove the tests to have more coverage and test both
SensorToggleControllers and the SensorPRivacyManagerHelper class.

Test: Use profiler to verify no more leaks
      SensorToggleControllerTest, SensorPrivacyManagerHelperTest
Bug: 244280065
Change-Id: Ibf0bcee455444a104ca6800302907c3dc0de8f1f
This commit is contained in:
Evan Severson
2022-09-01 14:08:38 -07:00
parent 1051fb16d8
commit 5ba4065703
8 changed files with 921 additions and 508 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}