Merge "Move CameraManager interactions to background" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
592682e191
@@ -45,6 +45,7 @@ import android.content.pm.ShortcutManager;
|
||||
import android.content.res.Resources;
|
||||
import android.hardware.SensorManager;
|
||||
import android.hardware.SensorPrivacyManager;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.hardware.devicestate.DeviceStateManager;
|
||||
import android.hardware.display.AmbientDisplayConfiguration;
|
||||
import android.hardware.display.ColorDisplayManager;
|
||||
@@ -553,4 +554,10 @@ public class FrameworkServicesModule {
|
||||
static SafetyCenterManager provideSafetyCenterManager(Context context) {
|
||||
return context.getSystemService(SafetyCenterManager.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static CameraManager provideCameraManager(Context context) {
|
||||
return context.getSystemService(CameraManager.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,11 @@
|
||||
package com.android.systemui.statusbar.policy;
|
||||
|
||||
import android.annotation.WorkerThread;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Process;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.Secure;
|
||||
import android.text.TextUtils;
|
||||
@@ -33,12 +29,19 @@ import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.systemui.broadcast.BroadcastSender;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.util.settings.SecureSettings;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -59,65 +62,88 @@ public class FlashlightControllerImpl implements FlashlightController {
|
||||
"com.android.settings.flashlight.action.FLASHLIGHT_CHANGED";
|
||||
|
||||
private final CameraManager mCameraManager;
|
||||
private final Context mContext;
|
||||
/** Call {@link #ensureHandler()} before using */
|
||||
private Handler mHandler;
|
||||
private final Executor mExecutor;
|
||||
private final SecureSettings mSecureSettings;
|
||||
private final DumpManager mDumpManager;
|
||||
private final BroadcastSender mBroadcastSender;
|
||||
|
||||
/** Lock on mListeners when accessing */
|
||||
private final boolean mHasFlashlight;
|
||||
|
||||
@GuardedBy("mListeners")
|
||||
private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
|
||||
|
||||
/** Lock on {@code this} when accessing */
|
||||
@GuardedBy("this")
|
||||
private boolean mFlashlightEnabled;
|
||||
|
||||
private String mCameraId;
|
||||
@GuardedBy("this")
|
||||
private boolean mTorchAvailable;
|
||||
|
||||
@Inject
|
||||
public FlashlightControllerImpl(Context context, DumpManager dumpManager) {
|
||||
mContext = context;
|
||||
mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
|
||||
private final AtomicReference<String> mCameraId;
|
||||
private final AtomicBoolean mInitted = new AtomicBoolean(false);
|
||||
|
||||
dumpManager.registerDumpable(getClass().getSimpleName(), this);
|
||||
tryInitCamera();
|
||||
@Inject
|
||||
public FlashlightControllerImpl(
|
||||
DumpManager dumpManager,
|
||||
CameraManager cameraManager,
|
||||
@Background Executor bgExecutor,
|
||||
SecureSettings secureSettings,
|
||||
BroadcastSender broadcastSender,
|
||||
PackageManager packageManager
|
||||
) {
|
||||
mCameraManager = cameraManager;
|
||||
mExecutor = bgExecutor;
|
||||
mCameraId = new AtomicReference<>(null);
|
||||
mSecureSettings = secureSettings;
|
||||
mDumpManager = dumpManager;
|
||||
mBroadcastSender = broadcastSender;
|
||||
|
||||
mHasFlashlight = packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (!mInitted.getAndSet(true)) {
|
||||
mDumpManager.registerDumpable(getClass().getSimpleName(), this);
|
||||
mExecutor.execute(this::tryInitCamera);
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void tryInitCamera() {
|
||||
if (!mHasFlashlight || mCameraId.get() != null) return;
|
||||
try {
|
||||
mCameraId = getCameraId();
|
||||
mCameraId.set(getCameraId());
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Couldn't initialize.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCameraId != null) {
|
||||
ensureHandler();
|
||||
mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
|
||||
if (mCameraId.get() != null) {
|
||||
mCameraManager.registerTorchCallback(mExecutor, mTorchCallback);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFlashlight(boolean enabled) {
|
||||
boolean pendingError = false;
|
||||
synchronized (this) {
|
||||
if (mCameraId == null) return;
|
||||
if (mFlashlightEnabled != enabled) {
|
||||
mFlashlightEnabled = enabled;
|
||||
try {
|
||||
mCameraManager.setTorchMode(mCameraId, enabled);
|
||||
} catch (CameraAccessException e) {
|
||||
Log.e(TAG, "Couldn't set torch mode", e);
|
||||
mFlashlightEnabled = false;
|
||||
pendingError = true;
|
||||
if (!mHasFlashlight) return;
|
||||
if (mCameraId.get() == null) {
|
||||
mExecutor.execute(this::tryInitCamera);
|
||||
}
|
||||
mExecutor.execute(() -> {
|
||||
if (mCameraId.get() == null) return;
|
||||
synchronized (this) {
|
||||
if (mFlashlightEnabled != enabled) {
|
||||
try {
|
||||
mCameraManager.setTorchMode(mCameraId.get(), enabled);
|
||||
} catch (CameraAccessException e) {
|
||||
Log.e(TAG, "Couldn't set torch mode", e);
|
||||
dispatchError();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatchModeChanged(mFlashlightEnabled);
|
||||
if (pendingError) {
|
||||
dispatchError();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean hasFlashlight() {
|
||||
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
|
||||
return mHasFlashlight;
|
||||
}
|
||||
|
||||
public synchronized boolean isEnabled() {
|
||||
@@ -131,13 +157,13 @@ public class FlashlightControllerImpl implements FlashlightController {
|
||||
@Override
|
||||
public void addCallback(@NonNull FlashlightListener l) {
|
||||
synchronized (mListeners) {
|
||||
if (mCameraId == null) {
|
||||
tryInitCamera();
|
||||
if (mCameraId.get() == null) {
|
||||
mExecutor.execute(this::tryInitCamera);
|
||||
}
|
||||
cleanUpListenersLocked(l);
|
||||
mListeners.add(new WeakReference<>(l));
|
||||
l.onFlashlightAvailabilityChanged(mTorchAvailable);
|
||||
l.onFlashlightChanged(mFlashlightEnabled);
|
||||
l.onFlashlightAvailabilityChanged(isAvailable());
|
||||
l.onFlashlightChanged(isEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,14 +174,7 @@ public class FlashlightControllerImpl implements FlashlightController {
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void ensureHandler() {
|
||||
if (mHandler == null) {
|
||||
HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
|
||||
thread.start();
|
||||
mHandler = new Handler(thread.getLooper());
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private String getCameraId() throws CameraAccessException {
|
||||
String[] ids = mCameraManager.getCameraIdList();
|
||||
for (String id : ids) {
|
||||
@@ -221,10 +240,9 @@ public class FlashlightControllerImpl implements FlashlightController {
|
||||
@Override
|
||||
@WorkerThread
|
||||
public void onTorchModeUnavailable(String cameraId) {
|
||||
if (TextUtils.equals(cameraId, mCameraId)) {
|
||||
if (TextUtils.equals(cameraId, mCameraId.get())) {
|
||||
setCameraAvailable(false);
|
||||
Settings.Secure.putInt(
|
||||
mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 0);
|
||||
mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 0);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -232,14 +250,12 @@ public class FlashlightControllerImpl implements FlashlightController {
|
||||
@Override
|
||||
@WorkerThread
|
||||
public void onTorchModeChanged(String cameraId, boolean enabled) {
|
||||
if (TextUtils.equals(cameraId, mCameraId)) {
|
||||
if (TextUtils.equals(cameraId, mCameraId.get())) {
|
||||
setCameraAvailable(true);
|
||||
setTorchMode(enabled);
|
||||
Settings.Secure.putInt(
|
||||
mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
|
||||
Settings.Secure.putInt(
|
||||
mContext.getContentResolver(), Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
|
||||
mContext.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
|
||||
mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
|
||||
mSecureSettings.putInt(Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
|
||||
mBroadcastSender.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.systemui.statusbar.policy
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.hardware.camera2.CameraCharacteristics
|
||||
import android.hardware.camera2.CameraManager
|
||||
import android.hardware.camera2.impl.CameraMetadataNative
|
||||
import android.test.suitebuilder.annotation.SmallTest
|
||||
import android.testing.AndroidTestingRunner
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.broadcast.BroadcastSender
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.settings.FakeSettings
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import java.util.concurrent.Executor
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.any
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.eq
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.verifyNoMoreInteractions
|
||||
import org.mockito.Mockito.verifyZeroInteractions
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class FlashlightControllerImplTest : SysuiTestCase() {
|
||||
|
||||
@Mock
|
||||
private lateinit var dumpManager: DumpManager
|
||||
|
||||
@Mock
|
||||
private lateinit var cameraManager: CameraManager
|
||||
|
||||
@Mock
|
||||
private lateinit var broadcastSender: BroadcastSender
|
||||
|
||||
@Mock
|
||||
private lateinit var packageManager: PackageManager
|
||||
|
||||
private lateinit var fakeSettings: FakeSettings
|
||||
private lateinit var fakeSystemClock: FakeSystemClock
|
||||
private lateinit var backgroundExecutor: FakeExecutor
|
||||
private lateinit var controller: FlashlightControllerImpl
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
fakeSystemClock = FakeSystemClock()
|
||||
backgroundExecutor = FakeExecutor(fakeSystemClock)
|
||||
fakeSettings = FakeSettings()
|
||||
|
||||
`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH))
|
||||
.thenReturn(true)
|
||||
|
||||
controller = FlashlightControllerImpl(
|
||||
dumpManager,
|
||||
cameraManager,
|
||||
backgroundExecutor,
|
||||
fakeSettings,
|
||||
broadcastSender,
|
||||
packageManager
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoCameraManagerInteractionDirectlyOnConstructor() {
|
||||
verifyZeroInteractions(cameraManager)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCameraManagerInitAfterConstructionOnExecutor() {
|
||||
injectCamera()
|
||||
backgroundExecutor.runAllReady()
|
||||
|
||||
verify(cameraManager).registerTorchCallback(eq(backgroundExecutor), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoCallbackIfNoFlashCamera() {
|
||||
injectCamera(flash = false)
|
||||
backgroundExecutor.runAllReady()
|
||||
|
||||
verify(cameraManager, never()).registerTorchCallback(any<Executor>(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoCallbackIfNoBackCamera() {
|
||||
injectCamera(facing = CameraCharacteristics.LENS_FACING_FRONT)
|
||||
backgroundExecutor.runAllReady()
|
||||
|
||||
verify(cameraManager, never()).registerTorchCallback(any<Executor>(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetFlashlightInBackgroundExecutor() {
|
||||
val id = injectCamera()
|
||||
backgroundExecutor.runAllReady()
|
||||
|
||||
clearInvocations(cameraManager)
|
||||
val enable = !controller.isEnabled
|
||||
controller.setFlashlight(enable)
|
||||
verifyNoMoreInteractions(cameraManager)
|
||||
|
||||
backgroundExecutor.runAllReady()
|
||||
verify(cameraManager).setTorchMode(id, enable)
|
||||
}
|
||||
|
||||
private fun injectCamera(
|
||||
flash: Boolean = true,
|
||||
facing: Int = CameraCharacteristics.LENS_FACING_BACK
|
||||
): String {
|
||||
val cameraID = "ID"
|
||||
val camera = CameraCharacteristics(CameraMetadataNative().apply {
|
||||
set(CameraCharacteristics.FLASH_INFO_AVAILABLE, flash)
|
||||
set(CameraCharacteristics.LENS_FACING, facing)
|
||||
})
|
||||
`when`(cameraManager.cameraIdList).thenReturn(arrayOf(cameraID))
|
||||
`when`(cameraManager.getCameraCharacteristics(cameraID)).thenReturn(camera)
|
||||
return cameraID
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user