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.content.res.Resources;
|
||||||
import android.hardware.SensorManager;
|
import android.hardware.SensorManager;
|
||||||
import android.hardware.SensorPrivacyManager;
|
import android.hardware.SensorPrivacyManager;
|
||||||
|
import android.hardware.camera2.CameraManager;
|
||||||
import android.hardware.devicestate.DeviceStateManager;
|
import android.hardware.devicestate.DeviceStateManager;
|
||||||
import android.hardware.display.AmbientDisplayConfiguration;
|
import android.hardware.display.AmbientDisplayConfiguration;
|
||||||
import android.hardware.display.ColorDisplayManager;
|
import android.hardware.display.ColorDisplayManager;
|
||||||
@@ -553,4 +554,10 @@ public class FrameworkServicesModule {
|
|||||||
static SafetyCenterManager provideSafetyCenterManager(Context context) {
|
static SafetyCenterManager provideSafetyCenterManager(Context context) {
|
||||||
return context.getSystemService(SafetyCenterManager.class);
|
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;
|
package com.android.systemui.statusbar.policy;
|
||||||
|
|
||||||
import android.annotation.WorkerThread;
|
import android.annotation.WorkerThread;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.hardware.camera2.CameraAccessException;
|
import android.hardware.camera2.CameraAccessException;
|
||||||
import android.hardware.camera2.CameraCharacteristics;
|
import android.hardware.camera2.CameraCharacteristics;
|
||||||
import android.hardware.camera2.CameraManager;
|
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;
|
||||||
import android.provider.Settings.Secure;
|
import android.provider.Settings.Secure;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -33,12 +29,19 @@ import android.util.Log;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
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.SysUISingleton;
|
||||||
|
import com.android.systemui.dagger.qualifiers.Background;
|
||||||
import com.android.systemui.dump.DumpManager;
|
import com.android.systemui.dump.DumpManager;
|
||||||
|
import com.android.systemui.util.settings.SecureSettings;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
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;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@@ -59,65 +62,88 @@ public class FlashlightControllerImpl implements FlashlightController {
|
|||||||
"com.android.settings.flashlight.action.FLASHLIGHT_CHANGED";
|
"com.android.settings.flashlight.action.FLASHLIGHT_CHANGED";
|
||||||
|
|
||||||
private final CameraManager mCameraManager;
|
private final CameraManager mCameraManager;
|
||||||
private final Context mContext;
|
private final Executor mExecutor;
|
||||||
/** Call {@link #ensureHandler()} before using */
|
private final SecureSettings mSecureSettings;
|
||||||
private Handler mHandler;
|
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);
|
private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
|
||||||
|
|
||||||
/** Lock on {@code this} when accessing */
|
@GuardedBy("this")
|
||||||
private boolean mFlashlightEnabled;
|
private boolean mFlashlightEnabled;
|
||||||
|
@GuardedBy("this")
|
||||||
private String mCameraId;
|
|
||||||
private boolean mTorchAvailable;
|
private boolean mTorchAvailable;
|
||||||
|
|
||||||
@Inject
|
private final AtomicReference<String> mCameraId;
|
||||||
public FlashlightControllerImpl(Context context, DumpManager dumpManager) {
|
private final AtomicBoolean mInitted = new AtomicBoolean(false);
|
||||||
mContext = context;
|
|
||||||
mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
|
|
||||||
|
|
||||||
dumpManager.registerDumpable(getClass().getSimpleName(), this);
|
@Inject
|
||||||
tryInitCamera();
|
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() {
|
private void tryInitCamera() {
|
||||||
|
if (!mHasFlashlight || mCameraId.get() != null) return;
|
||||||
try {
|
try {
|
||||||
mCameraId = getCameraId();
|
mCameraId.set(getCameraId());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.e(TAG, "Couldn't initialize.", e);
|
Log.e(TAG, "Couldn't initialize.", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mCameraId != null) {
|
if (mCameraId.get() != null) {
|
||||||
ensureHandler();
|
mCameraManager.registerTorchCallback(mExecutor, mTorchCallback);
|
||||||
mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFlashlight(boolean enabled) {
|
public void setFlashlight(boolean enabled) {
|
||||||
boolean pendingError = false;
|
if (!mHasFlashlight) return;
|
||||||
synchronized (this) {
|
if (mCameraId.get() == null) {
|
||||||
if (mCameraId == null) return;
|
mExecutor.execute(this::tryInitCamera);
|
||||||
if (mFlashlightEnabled != enabled) {
|
}
|
||||||
mFlashlightEnabled = enabled;
|
mExecutor.execute(() -> {
|
||||||
try {
|
if (mCameraId.get() == null) return;
|
||||||
mCameraManager.setTorchMode(mCameraId, enabled);
|
synchronized (this) {
|
||||||
} catch (CameraAccessException e) {
|
if (mFlashlightEnabled != enabled) {
|
||||||
Log.e(TAG, "Couldn't set torch mode", e);
|
try {
|
||||||
mFlashlightEnabled = false;
|
mCameraManager.setTorchMode(mCameraId.get(), enabled);
|
||||||
pendingError = true;
|
} catch (CameraAccessException e) {
|
||||||
|
Log.e(TAG, "Couldn't set torch mode", e);
|
||||||
|
dispatchError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
dispatchModeChanged(mFlashlightEnabled);
|
|
||||||
if (pendingError) {
|
|
||||||
dispatchError();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasFlashlight() {
|
public boolean hasFlashlight() {
|
||||||
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
|
return mHasFlashlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean isEnabled() {
|
public synchronized boolean isEnabled() {
|
||||||
@@ -131,13 +157,13 @@ public class FlashlightControllerImpl implements FlashlightController {
|
|||||||
@Override
|
@Override
|
||||||
public void addCallback(@NonNull FlashlightListener l) {
|
public void addCallback(@NonNull FlashlightListener l) {
|
||||||
synchronized (mListeners) {
|
synchronized (mListeners) {
|
||||||
if (mCameraId == null) {
|
if (mCameraId.get() == null) {
|
||||||
tryInitCamera();
|
mExecutor.execute(this::tryInitCamera);
|
||||||
}
|
}
|
||||||
cleanUpListenersLocked(l);
|
cleanUpListenersLocked(l);
|
||||||
mListeners.add(new WeakReference<>(l));
|
mListeners.add(new WeakReference<>(l));
|
||||||
l.onFlashlightAvailabilityChanged(mTorchAvailable);
|
l.onFlashlightAvailabilityChanged(isAvailable());
|
||||||
l.onFlashlightChanged(mFlashlightEnabled);
|
l.onFlashlightChanged(isEnabled());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,14 +174,7 @@ public class FlashlightControllerImpl implements FlashlightController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void ensureHandler() {
|
@WorkerThread
|
||||||
if (mHandler == null) {
|
|
||||||
HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
|
|
||||||
thread.start();
|
|
||||||
mHandler = new Handler(thread.getLooper());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCameraId() throws CameraAccessException {
|
private String getCameraId() throws CameraAccessException {
|
||||||
String[] ids = mCameraManager.getCameraIdList();
|
String[] ids = mCameraManager.getCameraIdList();
|
||||||
for (String id : ids) {
|
for (String id : ids) {
|
||||||
@@ -221,10 +240,9 @@ public class FlashlightControllerImpl implements FlashlightController {
|
|||||||
@Override
|
@Override
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
public void onTorchModeUnavailable(String cameraId) {
|
public void onTorchModeUnavailable(String cameraId) {
|
||||||
if (TextUtils.equals(cameraId, mCameraId)) {
|
if (TextUtils.equals(cameraId, mCameraId.get())) {
|
||||||
setCameraAvailable(false);
|
setCameraAvailable(false);
|
||||||
Settings.Secure.putInt(
|
mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 0);
|
||||||
mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 0);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,14 +250,12 @@ public class FlashlightControllerImpl implements FlashlightController {
|
|||||||
@Override
|
@Override
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
public void onTorchModeChanged(String cameraId, boolean enabled) {
|
public void onTorchModeChanged(String cameraId, boolean enabled) {
|
||||||
if (TextUtils.equals(cameraId, mCameraId)) {
|
if (TextUtils.equals(cameraId, mCameraId.get())) {
|
||||||
setCameraAvailable(true);
|
setCameraAvailable(true);
|
||||||
setTorchMode(enabled);
|
setTorchMode(enabled);
|
||||||
Settings.Secure.putInt(
|
mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
|
||||||
mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
|
mSecureSettings.putInt(Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
|
||||||
Settings.Secure.putInt(
|
mBroadcastSender.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
|
||||||
mContext.getContentResolver(), Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
|
|
||||||
mContext.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