diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 5df3dde82b3ec..b515abc4000fa 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -448,5 +448,6 @@
#546E7A
- #FFFFFF
+ #FFFFFF
+ #FFFFFF
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 21abd2237bfa3..eec5869d8a682 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5790,4 +5790,9 @@
+
+
+ 3000
+
+ 4
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 678dd9ffa6658..5bcef689a9c98 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4754,7 +4754,10 @@
-
+
+
+
+
diff --git a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
index 750d4004cb604..fd6ec065d421c 100644
--- a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
+++ b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
@@ -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 mActivePackages = new ArraySet<>();
private final Set mActivePhonePackages = new ArraySet<>();
- private final int mCameraPrivacyLightColor;
-
private final List 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> 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 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 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 element0 = mAmbientLightValues.pollFirst(); // NOTICE: POLL
+ Pair 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 firstElement = mAmbientLightValues.peekFirst();
+ Pair 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 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);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
index fa3e05a6b0012..20cfd59973c36 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
@@ -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 mAppOpsListenerCaptor =
ArgumentCaptor.forClass(AppOpsManager.OnOpActiveChangedListener.class);
private ArgumentCaptor mLightsRequestCaptor =
ArgumentCaptor.forClass(LightsRequest.class);
+ private ArgumentCaptor mLightSensorListenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+
private Set mExemptedPackages = new ArraySet<>();
private List 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 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);
+ }
}