blob: 9946b4b9ecaaa700b385dfe598b878bd746159a1 [file] [log] [blame]
/*
* Copyright (C) 2014 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.annotation.WorkerThread;
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.provider.Settings;
import android.provider.Settings.Secure;
import android.text.TextUtils;
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;
/**
* Manages the flashlight.
*/
@SysUISingleton
public class FlashlightControllerImpl implements FlashlightController {
private static final String TAG = "FlashlightController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int DISPATCH_ERROR = 0;
private static final int DISPATCH_CHANGED = 1;
private static final int DISPATCH_AVAILABILITY_CHANGED = 2;
private static final String ACTION_FLASHLIGHT_CHANGED =
"com.android.settings.flashlight.action.FLASHLIGHT_CHANGED";
private final CameraManager mCameraManager;
private final Executor mExecutor;
private final SecureSettings mSecureSettings;
private final DumpManager mDumpManager;
private final BroadcastSender mBroadcastSender;
private final boolean mHasFlashlight;
@GuardedBy("mListeners")
private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
@GuardedBy("this")
private boolean mFlashlightEnabled;
@GuardedBy("this")
private boolean mTorchAvailable;
private final AtomicReference<String> mCameraId;
private final AtomicBoolean mInitted = new AtomicBoolean(false);
@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.set(getCameraId());
} catch (Throwable e) {
Log.e(TAG, "Couldn't initialize.", e);
return;
}
if (mCameraId.get() != null) {
mCameraManager.registerTorchCallback(mExecutor, mTorchCallback);
}
}
public void setFlashlight(boolean enabled) {
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();
}
}
}
});
}
public boolean hasFlashlight() {
return mHasFlashlight;
}
public synchronized boolean isEnabled() {
return mFlashlightEnabled;
}
public synchronized boolean isAvailable() {
return mTorchAvailable;
}
@Override
public void addCallback(@NonNull FlashlightListener l) {
synchronized (mListeners) {
if (mCameraId.get() == null) {
mExecutor.execute(this::tryInitCamera);
}
cleanUpListenersLocked(l);
mListeners.add(new WeakReference<>(l));
l.onFlashlightAvailabilityChanged(isAvailable());
l.onFlashlightChanged(isEnabled());
}
}
@Override
public void removeCallback(@NonNull FlashlightListener l) {
synchronized (mListeners) {
cleanUpListenersLocked(l);
}
}
@WorkerThread
private String getCameraId() throws CameraAccessException {
String[] ids = mCameraManager.getCameraIdList();
for (String id : ids) {
CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
if (flashAvailable != null && flashAvailable
&& lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
return id;
}
}
return null;
}
private void dispatchModeChanged(boolean enabled) {
dispatchListeners(DISPATCH_CHANGED, enabled);
}
private void dispatchError() {
dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */);
}
private void dispatchAvailabilityChanged(boolean available) {
dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available);
}
private void dispatchListeners(int message, boolean argument) {
synchronized (mListeners) {
final int N = mListeners.size();
boolean cleanup = false;
for (int i = 0; i < N; i++) {
FlashlightListener l = mListeners.get(i).get();
if (l != null) {
if (message == DISPATCH_ERROR) {
l.onFlashlightError();
} else if (message == DISPATCH_CHANGED) {
l.onFlashlightChanged(argument);
} else if (message == DISPATCH_AVAILABILITY_CHANGED) {
l.onFlashlightAvailabilityChanged(argument);
}
} else {
cleanup = true;
}
}
if (cleanup) {
cleanUpListenersLocked(null);
}
}
}
private void cleanUpListenersLocked(FlashlightListener listener) {
for (int i = mListeners.size() - 1; i >= 0; i--) {
FlashlightListener found = mListeners.get(i).get();
if (found == null || found == listener) {
mListeners.remove(i);
}
}
}
private final CameraManager.TorchCallback mTorchCallback =
new CameraManager.TorchCallback() {
@Override
@WorkerThread
public void onTorchModeUnavailable(String cameraId) {
if (TextUtils.equals(cameraId, mCameraId.get())) {
setCameraAvailable(false);
mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 0);
}
}
@Override
@WorkerThread
public void onTorchModeChanged(String cameraId, boolean enabled) {
if (TextUtils.equals(cameraId, mCameraId.get())) {
setCameraAvailable(true);
setTorchMode(enabled);
mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
mSecureSettings.putInt(Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
mBroadcastSender.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
}
}
private void setCameraAvailable(boolean available) {
boolean changed;
synchronized (FlashlightControllerImpl.this) {
changed = mTorchAvailable != available;
mTorchAvailable = available;
}
if (changed) {
if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")");
dispatchAvailabilityChanged(available);
}
}
private void setTorchMode(boolean enabled) {
boolean changed;
synchronized (FlashlightControllerImpl.this) {
changed = mFlashlightEnabled != enabled;
mFlashlightEnabled = enabled;
}
if (changed) {
if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")");
dispatchModeChanged(enabled);
}
}
};
public void dump(PrintWriter pw, String[] args) {
pw.println("FlashlightController state:");
pw.print(" mCameraId=");
pw.println(mCameraId);
pw.print(" mFlashlightEnabled=");
pw.println(mFlashlightEnabled);
pw.print(" mTorchAvailable=");
pw.println(mTorchAvailable);
}
}