Merge "Use ambient light sensor to adjust camera privacy light brightness" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
1b025f37ff
@@ -448,5 +448,6 @@
|
||||
<color name="accessibility_color_inversion_background">#546E7A</color>
|
||||
|
||||
<!-- Color of camera light when camera is in use -->
|
||||
<color name="camera_privacy_light">#FFFFFF</color>
|
||||
<color name="camera_privacy_light_day">#FFFFFF</color>
|
||||
<color name="camera_privacy_light_night">#FFFFFF</color>
|
||||
</resources>
|
||||
|
||||
@@ -5790,4 +5790,9 @@
|
||||
|
||||
<!-- List of the labels of requestable device state config values -->
|
||||
<string-array name="config_deviceStatesAvailableForAppRequests"/>
|
||||
|
||||
<!-- Interval in milliseconds to average light sensor values for camera light brightness -->
|
||||
<integer name="config_cameraPrivacyLightAlsAveragingIntervalMillis">3000</integer>
|
||||
<!-- Light sensor's lux value to use as the threshold between using day or night brightness -->
|
||||
<integer name="config_cameraPrivacyLightAlsNightThreshold">4</integer>
|
||||
</resources>
|
||||
|
||||
@@ -4754,7 +4754,10 @@
|
||||
<!-- For VirtualDeviceManager -->
|
||||
<java-symbol type="string" name="vdm_camera_access_denied" />
|
||||
|
||||
<java-symbol type="color" name="camera_privacy_light"/>
|
||||
<java-symbol type="color" name="camera_privacy_light_day"/>
|
||||
<java-symbol type="color" name="camera_privacy_light_night"/>
|
||||
<java-symbol type="integer" name="config_cameraPrivacyLightAlsAveragingIntervalMillis"/>
|
||||
<java-symbol type="integer" name="config_cameraPrivacyLightAlsNightThreshold"/>
|
||||
|
||||
<java-symbol type="bool" name="config_bg_current_drain_monitor_enabled" />
|
||||
<java-symbol type="array" name="config_bg_current_drain_threshold_to_restricted_bucket" />
|
||||
|
||||
@@ -16,44 +16,105 @@
|
||||
|
||||
package com.android.server.sensorprivacy;
|
||||
|
||||
import static android.hardware.SensorManager.SENSOR_DELAY_NORMAL;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.hardware.lights.Light;
|
||||
import android.hardware.lights.LightState;
|
||||
import android.hardware.lights.LightsManager;
|
||||
import android.hardware.lights.LightsRequest;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerExecutor;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.permission.PermissionManager;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.FgThread;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener {
|
||||
class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener,
|
||||
SensorEventListener {
|
||||
|
||||
@VisibleForTesting
|
||||
static final double LIGHT_VALUE_MULTIPLIER = 1 / Math.log(1.1);
|
||||
|
||||
private final Handler mHandler;
|
||||
private final Executor mExecutor;
|
||||
private final Context mContext;
|
||||
private final AppOpsManager mAppOpsManager;
|
||||
private final LightsManager mLightsManager;
|
||||
private final SensorManager mSensorManager;
|
||||
|
||||
private final Set<String> mActivePackages = new ArraySet<>();
|
||||
private final Set<String> mActivePhonePackages = new ArraySet<>();
|
||||
|
||||
private final int mCameraPrivacyLightColor;
|
||||
|
||||
private final List<Light> mCameraLights = new ArrayList<>();
|
||||
private final AppOpsManager mAppOpsManager;
|
||||
|
||||
private LightsManager.LightsSession mLightsSession = null;
|
||||
|
||||
@ColorInt
|
||||
private final int mDayColor;
|
||||
@ColorInt
|
||||
private final int mNightColor;
|
||||
|
||||
private final Sensor mLightSensor;
|
||||
|
||||
private boolean mIsAmbientLightListenerRegistered = false;
|
||||
private final long mMovingAverageIntervalMillis;
|
||||
/** When average of the time integral over the past {@link #mMovingAverageIntervalMillis}
|
||||
* milliseconds of the log_1.1(lux(t)) is greater than this value, use the daytime brightness
|
||||
* else use nighttime brightness. */
|
||||
private final long mNightThreshold;
|
||||
private final ArrayDeque<Pair<Long, Integer>> mAmbientLightValues = new ArrayDeque<>();
|
||||
/** Tracks the Riemann sum of {@link #mAmbientLightValues} to avoid O(n) operations when sum is
|
||||
* needed */
|
||||
private long mAlvSum = 0;
|
||||
private int mLastLightColor = 0;
|
||||
/** The elapsed real time that the ALS was started watching */
|
||||
private long mElapsedTimeStartedReading;
|
||||
|
||||
private final Object mDelayedUpdateToken = new Object();
|
||||
|
||||
// Can't mock static native methods, workaround for testing
|
||||
private long mElapsedRealTime = -1;
|
||||
|
||||
CameraPrivacyLightController(Context context) {
|
||||
this(context, FgThread.get().getLooper());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
CameraPrivacyLightController(Context context, Looper looper) {
|
||||
mContext = context;
|
||||
|
||||
mHandler = new Handler(looper);
|
||||
mExecutor = new HandlerExecutor(mHandler);
|
||||
|
||||
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
|
||||
mLightsManager = mContext.getSystemService(LightsManager.class);
|
||||
mSensorManager = mContext.getSystemService(SensorManager.class);
|
||||
|
||||
mCameraPrivacyLightColor = mContext.getColor(R.color.camera_privacy_light);
|
||||
mDayColor = mContext.getColor(R.color.camera_privacy_light_day);
|
||||
mNightColor = mContext.getColor(R.color.camera_privacy_light_night);
|
||||
mMovingAverageIntervalMillis = mContext.getResources()
|
||||
.getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis);
|
||||
mNightThreshold = (long) (Math.log(mContext.getResources()
|
||||
.getInteger(R.integer.config_cameraPrivacyLightAlsNightThreshold))
|
||||
* LIGHT_VALUE_MULTIPLIER);
|
||||
|
||||
List<Light> lights = mLightsManager.getLights();
|
||||
for (int i = 0; i < lights.size(); i++) {
|
||||
@@ -64,12 +125,60 @@ class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedLis
|
||||
}
|
||||
|
||||
if (mCameraLights.isEmpty()) {
|
||||
mLightSensor = null;
|
||||
return;
|
||||
}
|
||||
|
||||
mAppOpsManager.startWatchingActive(
|
||||
new String[] {AppOpsManager.OPSTR_CAMERA, AppOpsManager.OPSTR_PHONE_CALL_CAMERA},
|
||||
FgThread.getExecutor(), this);
|
||||
mExecutor, this);
|
||||
|
||||
// It may be useful in the future to configure devices to know which lights are near which
|
||||
// sensors so that we can control individual lights based on their environment.
|
||||
mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
|
||||
}
|
||||
|
||||
private void addElement(long time, int value) {
|
||||
if (mAmbientLightValues.isEmpty()) {
|
||||
// Eliminate the size == 1 edge case and assume the light value has been constant for
|
||||
// the previous interval
|
||||
mAmbientLightValues.add(new Pair<>(time - getCurrentIntervalMillis() - 1, value));
|
||||
}
|
||||
Pair<Long, Integer> lastElement = mAmbientLightValues.peekLast();
|
||||
mAmbientLightValues.add(new Pair<>(time, value));
|
||||
|
||||
mAlvSum += (time - lastElement.first) * lastElement.second;
|
||||
removeObsoleteData(time);
|
||||
}
|
||||
|
||||
private void removeObsoleteData(long time) {
|
||||
while (mAmbientLightValues.size() > 1) {
|
||||
Pair<Long, Integer> element0 = mAmbientLightValues.pollFirst(); // NOTICE: POLL
|
||||
Pair<Long, Integer> element1 = mAmbientLightValues.peekFirst(); // NOTICE: PEEK
|
||||
if (element1.first > time - getCurrentIntervalMillis()) {
|
||||
mAmbientLightValues.addFirst(element0);
|
||||
break;
|
||||
}
|
||||
mAlvSum -= (element1.first - element0.first) * element0.second;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the Riemann sum of {@link #mAmbientLightValues} where the part of the interval that
|
||||
* stretches outside the time window is removed and the time since the last change is added in.
|
||||
*/
|
||||
private long getLiveAmbientLightTotal() {
|
||||
if (mAmbientLightValues.isEmpty()) {
|
||||
return mAlvSum;
|
||||
}
|
||||
long time = getElapsedRealTime();
|
||||
removeObsoleteData(time);
|
||||
|
||||
Pair<Long, Integer> firstElement = mAmbientLightValues.peekFirst();
|
||||
Pair<Long, Integer> lastElement = mAmbientLightValues.peekLast();
|
||||
|
||||
return mAlvSum - Math.max(0, time - getCurrentIntervalMillis() - firstElement.first)
|
||||
* firstElement.second + (time - lastElement.first) * lastElement.second;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,10 +202,16 @@ class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedLis
|
||||
}
|
||||
|
||||
private void updateLightSession() {
|
||||
if (Looper.myLooper() != mHandler.getLooper()) {
|
||||
mHandler.post(this::updateLightSession);
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> exemptedPackages = PermissionManager.getIndicatorExemptedPackages(mContext);
|
||||
|
||||
boolean shouldSessionEnd = exemptedPackages.containsAll(mActivePackages)
|
||||
&& exemptedPackages.containsAll(mActivePhonePackages);
|
||||
updateSensorListener(shouldSessionEnd);
|
||||
|
||||
if (shouldSessionEnd) {
|
||||
if (mLightsSession == null) {
|
||||
@@ -106,20 +221,73 @@ class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedLis
|
||||
mLightsSession.close();
|
||||
mLightsSession = null;
|
||||
} else {
|
||||
if (mLightsSession != null) {
|
||||
int lightColor;
|
||||
if (mLightSensor != null && getLiveAmbientLightTotal()
|
||||
< getCurrentIntervalMillis() * mNightThreshold) {
|
||||
lightColor = mNightColor;
|
||||
} else {
|
||||
lightColor = mDayColor;
|
||||
}
|
||||
|
||||
if (mLastLightColor == lightColor && mLightsSession != null) {
|
||||
return;
|
||||
}
|
||||
mLastLightColor = lightColor;
|
||||
|
||||
LightsRequest.Builder requestBuilder = new LightsRequest.Builder();
|
||||
for (int i = 0; i < mCameraLights.size(); i++) {
|
||||
requestBuilder.addLight(mCameraLights.get(i),
|
||||
new LightState.Builder()
|
||||
.setColor(mCameraPrivacyLightColor)
|
||||
.setColor(lightColor)
|
||||
.build());
|
||||
}
|
||||
|
||||
mLightsSession = mLightsManager.openSession(Integer.MAX_VALUE);
|
||||
if (mLightsSession == null) {
|
||||
mLightsSession = mLightsManager.openSession(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
mLightsSession.requestLights(requestBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSensorListener(boolean shouldSessionEnd) {
|
||||
if (shouldSessionEnd && mIsAmbientLightListenerRegistered) {
|
||||
mSensorManager.unregisterListener(this);
|
||||
mIsAmbientLightListenerRegistered = false;
|
||||
}
|
||||
if (!shouldSessionEnd && !mIsAmbientLightListenerRegistered && mLightSensor != null) {
|
||||
mSensorManager.registerListener(this, mLightSensor, SENSOR_DELAY_NORMAL, mHandler);
|
||||
mIsAmbientLightListenerRegistered = true;
|
||||
mElapsedTimeStartedReading = getElapsedRealTime();
|
||||
}
|
||||
}
|
||||
|
||||
private long getElapsedRealTime() {
|
||||
return mElapsedRealTime == -1 ? SystemClock.elapsedRealtime() : mElapsedRealTime;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setElapsedRealTime(long time) {
|
||||
mElapsedRealTime = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
// Using log space to represent human sensation (Fechner's Law) instead of lux
|
||||
// because lux values causes bright flashes to skew the average very high.
|
||||
addElement(event.timestamp, Math.max(0,
|
||||
(int) (Math.log(event.values[0]) * LIGHT_VALUE_MULTIPLIER)));
|
||||
updateLightSession();
|
||||
mHandler.removeCallbacksAndMessages(mDelayedUpdateToken);
|
||||
mHandler.postDelayed(CameraPrivacyLightController.this::updateLightSession,
|
||||
mDelayedUpdateToken, mMovingAverageIntervalMillis);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
|
||||
|
||||
private long getCurrentIntervalMillis() {
|
||||
return Math.min(mMovingAverageIntervalMillis,
|
||||
getElapsedRealTime() - mElapsedTimeStartedReading);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,20 +24,33 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.hardware.lights.Light;
|
||||
import android.hardware.lights.LightState;
|
||||
import android.hardware.lights.LightsManager;
|
||||
import android.hardware.lights.LightsRequest;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.permission.PermissionManager;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.android.dx.mockito.inline.extended.ExtendedMockito;
|
||||
import com.android.internal.R;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
@@ -53,26 +66,43 @@ import java.util.stream.Collectors;
|
||||
|
||||
public class CameraPrivacyLightControllerTest {
|
||||
|
||||
private int mDayColor = 1;
|
||||
private int mNightColor = 0;
|
||||
private int mCameraPrivacyLightAlsAveragingIntervalMillis = 5000;
|
||||
private int mCameraPrivacyLightAlsNightThreshold = (int) getLightSensorValue(15);
|
||||
|
||||
private MockitoSession mMockitoSession;
|
||||
|
||||
@Mock
|
||||
private Context mContext;
|
||||
|
||||
@Mock
|
||||
private Resources mResources;
|
||||
|
||||
@Mock
|
||||
private LightsManager mLightsManager;
|
||||
|
||||
@Mock
|
||||
private AppOpsManager mAppOpsManager;
|
||||
|
||||
@Mock
|
||||
private SensorManager mSensorManager;
|
||||
|
||||
@Mock
|
||||
private LightsManager.LightsSession mLightsSession;
|
||||
|
||||
@Mock
|
||||
private Sensor mLightSensor;
|
||||
|
||||
private ArgumentCaptor<AppOpsManager.OnOpActiveChangedListener> mAppOpsListenerCaptor =
|
||||
ArgumentCaptor.forClass(AppOpsManager.OnOpActiveChangedListener.class);
|
||||
|
||||
private ArgumentCaptor<LightsRequest> mLightsRequestCaptor =
|
||||
ArgumentCaptor.forClass(LightsRequest.class);
|
||||
|
||||
private ArgumentCaptor<SensorEventListener> mLightSensorListenerCaptor =
|
||||
ArgumentCaptor.forClass(SensorEventListener.class);
|
||||
|
||||
private Set<String> mExemptedPackages = new ArraySet<>();
|
||||
private List<Light> mLights = new ArrayList<>();
|
||||
|
||||
@@ -86,11 +116,22 @@ public class CameraPrivacyLightControllerTest {
|
||||
.spyStatic(PermissionManager.class)
|
||||
.startMocking();
|
||||
|
||||
doReturn(mDayColor).when(mContext).getColor(R.color.camera_privacy_light_day);
|
||||
doReturn(mNightColor).when(mContext).getColor(R.color.camera_privacy_light_night);
|
||||
|
||||
doReturn(mResources).when(mContext).getResources();
|
||||
doReturn(mCameraPrivacyLightAlsAveragingIntervalMillis).when(mResources)
|
||||
.getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis);
|
||||
doReturn(mCameraPrivacyLightAlsNightThreshold).when(mResources)
|
||||
.getInteger(R.integer.config_cameraPrivacyLightAlsNightThreshold);
|
||||
|
||||
doReturn(mLightsManager).when(mContext).getSystemService(LightsManager.class);
|
||||
doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
|
||||
doReturn(mSensorManager).when(mContext).getSystemService(SensorManager.class);
|
||||
|
||||
doReturn(mLights).when(mLightsManager).getLights();
|
||||
doReturn(mLightsSession).when(mLightsManager).openSession(anyInt());
|
||||
doReturn(mLightSensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
|
||||
|
||||
doReturn(mExemptedPackages)
|
||||
.when(() -> PermissionManager.getIndicatorExemptedPackages(any()));
|
||||
@@ -107,7 +148,7 @@ public class CameraPrivacyLightControllerTest {
|
||||
@Test
|
||||
public void testAppsOpsListenerNotRegisteredWithoutCameraLights() {
|
||||
mLights.add(getNextLight(false));
|
||||
new CameraPrivacyLightController(mContext);
|
||||
createCameraPrivacyLightController();
|
||||
|
||||
verify(mAppOpsManager, times(0)).startWatchingActive(any(), any(), any());
|
||||
}
|
||||
@@ -116,7 +157,7 @@ public class CameraPrivacyLightControllerTest {
|
||||
public void testAppsOpsListenerRegisteredWithCameraLight() {
|
||||
mLights.add(getNextLight(true));
|
||||
|
||||
new CameraPrivacyLightController(mContext);
|
||||
createCameraPrivacyLightController();
|
||||
|
||||
verify(mAppOpsManager, times(1)).startWatchingActive(any(), any(), any());
|
||||
}
|
||||
@@ -128,14 +169,13 @@ public class CameraPrivacyLightControllerTest {
|
||||
mLights.add(getNextLight(r.nextBoolean()));
|
||||
}
|
||||
|
||||
new CameraPrivacyLightController(mContext);
|
||||
createCameraPrivacyLightController();
|
||||
|
||||
// Verify no session has been opened at this point.
|
||||
verify(mLightsManager, times(0)).openSession(anyInt());
|
||||
|
||||
// Set camera op as active.
|
||||
verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
|
||||
mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true);
|
||||
openCamera();
|
||||
|
||||
// Verify session has been opened exactly once
|
||||
verify(mLightsManager, times(1)).openSession(anyInt());
|
||||
@@ -161,7 +201,7 @@ public class CameraPrivacyLightControllerTest {
|
||||
public void testWillOnlyOpenOnceWhenTwoPackagesStartOp() {
|
||||
mLights.add(getNextLight(true));
|
||||
|
||||
new CameraPrivacyLightController(mContext);
|
||||
createCameraPrivacyLightController();
|
||||
|
||||
verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
|
||||
|
||||
@@ -176,7 +216,7 @@ public class CameraPrivacyLightControllerTest {
|
||||
public void testWillCloseOnFinishOp() {
|
||||
mLights.add(getNextLight(true));
|
||||
|
||||
new CameraPrivacyLightController(mContext);
|
||||
createCameraPrivacyLightController();
|
||||
|
||||
verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
|
||||
|
||||
@@ -192,7 +232,7 @@ public class CameraPrivacyLightControllerTest {
|
||||
public void testWillCloseOnFinishOpForAllPackages() {
|
||||
mLights.add(getNextLight(true));
|
||||
|
||||
new CameraPrivacyLightController(mContext);
|
||||
createCameraPrivacyLightController();
|
||||
|
||||
int numUids = 100;
|
||||
List<Integer> uids = new ArrayList<>(numUids);
|
||||
@@ -226,7 +266,7 @@ public class CameraPrivacyLightControllerTest {
|
||||
mLights.add(getNextLight(true));
|
||||
mExemptedPackages.add("pkg1");
|
||||
|
||||
new CameraPrivacyLightController(mContext);
|
||||
createCameraPrivacyLightController();
|
||||
|
||||
verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
|
||||
|
||||
@@ -235,6 +275,147 @@ public class CameraPrivacyLightControllerTest {
|
||||
verify(mLightsManager, times(0)).openSession(anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoLightSensor() {
|
||||
mLights.add(getNextLight(true));
|
||||
doReturn(null).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
|
||||
|
||||
createCameraPrivacyLightController();
|
||||
|
||||
openCamera();
|
||||
|
||||
verify(mLightsSession).requestLights(mLightsRequestCaptor.capture());
|
||||
LightsRequest lightsRequest = mLightsRequestCaptor.getValue();
|
||||
for (LightState lightState : lightsRequest.getLightStates()) {
|
||||
assertEquals(mDayColor, lightState.getColor());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testALSListenerNotRegisteredUntilCameraIsOpened() {
|
||||
mLights.add(getNextLight(true));
|
||||
Sensor sensor = mock(Sensor.class);
|
||||
doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
|
||||
|
||||
CameraPrivacyLightController cplc = createCameraPrivacyLightController();
|
||||
|
||||
verify(mSensorManager, never()).registerListener(any(SensorEventListener.class),
|
||||
any(Sensor.class), anyInt(), any(Handler.class));
|
||||
|
||||
openCamera();
|
||||
|
||||
verify(mSensorManager, times(1)).registerListener(mLightSensorListenerCaptor.capture(),
|
||||
any(Sensor.class), anyInt(), any(Handler.class));
|
||||
|
||||
mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", false);
|
||||
verify(mSensorManager, times(1)).unregisterListener(mLightSensorListenerCaptor.getValue());
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testDayColor() {
|
||||
testBrightnessToColor(20, mDayColor);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testNightColor() {
|
||||
testBrightnessToColor(10, mNightColor);
|
||||
}
|
||||
|
||||
private void testBrightnessToColor(int brightnessValue, int color) {
|
||||
mLights.add(getNextLight(true));
|
||||
Sensor sensor = mock(Sensor.class);
|
||||
doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
|
||||
|
||||
CameraPrivacyLightController cplc = createCameraPrivacyLightController();
|
||||
cplc.setElapsedRealTime(0);
|
||||
|
||||
openCamera();
|
||||
|
||||
verify(mSensorManager).registerListener(mLightSensorListenerCaptor.capture(),
|
||||
any(Sensor.class), anyInt(), any(Handler.class));
|
||||
SensorEventListener sensorListener = mLightSensorListenerCaptor.getValue();
|
||||
float[] sensorEventValues = new float[1];
|
||||
SensorEvent sensorEvent = new SensorEvent(sensor, 0, 0, sensorEventValues);
|
||||
|
||||
sensorEventValues[0] = getLightSensorValue(brightnessValue);
|
||||
sensorListener.onSensorChanged(sensorEvent);
|
||||
|
||||
verify(mLightsSession, atLeastOnce()).requestLights(mLightsRequestCaptor.capture());
|
||||
for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
|
||||
assertEquals(color, lightState.getColor());
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testDayToNightTransistion() {
|
||||
mLights.add(getNextLight(true));
|
||||
Sensor sensor = mock(Sensor.class);
|
||||
doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
|
||||
|
||||
CameraPrivacyLightController cplc = createCameraPrivacyLightController();
|
||||
cplc.setElapsedRealTime(0);
|
||||
|
||||
openCamera();
|
||||
// There will be an initial call at brightness 0
|
||||
verify(mLightsSession, times(1)).requestLights(any(LightsRequest.class));
|
||||
|
||||
verify(mSensorManager).registerListener(mLightSensorListenerCaptor.capture(),
|
||||
any(Sensor.class), anyInt(), any(Handler.class));
|
||||
SensorEventListener sensorListener = mLightSensorListenerCaptor.getValue();
|
||||
|
||||
onSensorEvent(cplc, sensorListener, sensor, 0, 20);
|
||||
|
||||
// 5 sec avg = 20
|
||||
onSensorEvent(cplc, sensorListener, sensor, 5000, 30);
|
||||
|
||||
verify(mLightsSession, times(2)).requestLights(mLightsRequestCaptor.capture());
|
||||
for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
|
||||
assertEquals(mDayColor, lightState.getColor());
|
||||
}
|
||||
|
||||
// 5 sec avg = 22
|
||||
|
||||
onSensorEvent(cplc, sensorListener, sensor, 6000, 10);
|
||||
|
||||
// 5 sec avg = 18
|
||||
|
||||
onSensorEvent(cplc, sensorListener, sensor, 8000, 5);
|
||||
|
||||
// Should have always been day
|
||||
verify(mLightsSession, times(2)).requestLights(mLightsRequestCaptor.capture());
|
||||
for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
|
||||
assertEquals(mDayColor, lightState.getColor());
|
||||
}
|
||||
|
||||
// 5 sec avg = 12
|
||||
|
||||
onSensorEvent(cplc, sensorListener, sensor, 10000, 5);
|
||||
|
||||
// Should now be night
|
||||
verify(mLightsSession, times(3)).requestLights(mLightsRequestCaptor.capture());
|
||||
for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
|
||||
assertEquals(mNightColor, lightState.getColor());
|
||||
}
|
||||
}
|
||||
|
||||
private void onSensorEvent(CameraPrivacyLightController cplc,
|
||||
SensorEventListener sensorListener, Sensor sensor, long timestamp, int value) {
|
||||
cplc.setElapsedRealTime(timestamp);
|
||||
sensorListener.onSensorChanged(new SensorEvent(sensor, 0, timestamp,
|
||||
new float[] {getLightSensorValue(value)}));
|
||||
}
|
||||
|
||||
// Use the test thread so that the test is deterministic
|
||||
private CameraPrivacyLightController createCameraPrivacyLightController() {
|
||||
if (Looper.myLooper() == null) {
|
||||
Looper.prepare();
|
||||
}
|
||||
return new CameraPrivacyLightController(mContext, Looper.myLooper());
|
||||
}
|
||||
|
||||
private Light getNextLight(boolean cameraType) {
|
||||
Light light = ExtendedMockito.mock(Light.class);
|
||||
if (cameraType) {
|
||||
@@ -245,4 +426,13 @@ public class CameraPrivacyLightControllerTest {
|
||||
doReturn(mNextLightId++).when(light).getId();
|
||||
return light;
|
||||
}
|
||||
|
||||
private float getLightSensorValue(int i) {
|
||||
return (float) Math.exp(i / CameraPrivacyLightController.LIGHT_VALUE_MULTIPLIER);
|
||||
}
|
||||
|
||||
private void openCamera() {
|
||||
verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
|
||||
mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", true);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user