1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.systemui.statusbar.policy; 18 19 import android.annotation.WorkerThread; 20 import android.content.pm.PackageManager; 21 import android.hardware.camera2.CameraAccessException; 22 import android.hardware.camera2.CameraCharacteristics; 23 import android.hardware.camera2.CameraManager; 24 import android.provider.Settings; 25 import android.provider.Settings.Secure; 26 import android.text.TextUtils; 27 import android.util.Log; 28 29 import androidx.annotation.NonNull; 30 31 import com.android.internal.annotations.GuardedBy; 32 import com.android.systemui.broadcast.BroadcastSender; 33 import com.android.systemui.dagger.SysUISingleton; 34 import com.android.systemui.dagger.qualifiers.Background; 35 import com.android.systemui.dump.DumpManager; 36 import com.android.systemui.util.settings.SecureSettings; 37 38 import java.io.PrintWriter; 39 import java.lang.ref.WeakReference; 40 import java.util.ArrayList; 41 import java.util.concurrent.Executor; 42 import java.util.concurrent.atomic.AtomicBoolean; 43 import java.util.concurrent.atomic.AtomicReference; 44 45 import javax.inject.Inject; 46 47 /** 48 * Manages the flashlight. 49 */ 50 @SysUISingleton 51 public class FlashlightControllerImpl implements FlashlightController { 52 53 private static final String TAG = "FlashlightController"; 54 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 55 56 private static final int DISPATCH_ERROR = 0; 57 private static final int DISPATCH_CHANGED = 1; 58 private static final int DISPATCH_AVAILABILITY_CHANGED = 2; 59 60 private static final String ACTION_FLASHLIGHT_CHANGED = 61 "com.android.settings.flashlight.action.FLASHLIGHT_CHANGED"; 62 63 private final CameraManager mCameraManager; 64 private final Executor mExecutor; 65 private final SecureSettings mSecureSettings; 66 private final DumpManager mDumpManager; 67 private final BroadcastSender mBroadcastSender; 68 69 private final boolean mHasFlashlight; 70 71 @GuardedBy("mListeners") 72 private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1); 73 74 @GuardedBy("this") 75 private boolean mFlashlightEnabled; 76 @GuardedBy("this") 77 private boolean mTorchAvailable; 78 79 private final AtomicReference<String> mCameraId; 80 private final AtomicBoolean mInitted = new AtomicBoolean(false); 81 82 @Inject FlashlightControllerImpl( DumpManager dumpManager, CameraManager cameraManager, @Background Executor bgExecutor, SecureSettings secureSettings, BroadcastSender broadcastSender, PackageManager packageManager )83 public FlashlightControllerImpl( 84 DumpManager dumpManager, 85 CameraManager cameraManager, 86 @Background Executor bgExecutor, 87 SecureSettings secureSettings, 88 BroadcastSender broadcastSender, 89 PackageManager packageManager 90 ) { 91 mCameraManager = cameraManager; 92 mExecutor = bgExecutor; 93 mCameraId = new AtomicReference<>(null); 94 mSecureSettings = secureSettings; 95 mDumpManager = dumpManager; 96 mBroadcastSender = broadcastSender; 97 98 mHasFlashlight = packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH); 99 init(); 100 } 101 init()102 private void init() { 103 if (!mInitted.getAndSet(true)) { 104 mDumpManager.registerDumpable(getClass().getSimpleName(), this); 105 mExecutor.execute(this::tryInitCamera); 106 } 107 } 108 109 @WorkerThread tryInitCamera()110 private void tryInitCamera() { 111 if (!mHasFlashlight || mCameraId.get() != null) return; 112 try { 113 mCameraId.set(getCameraId()); 114 } catch (Throwable e) { 115 Log.e(TAG, "Couldn't initialize.", e); 116 return; 117 } 118 119 if (mCameraId.get() != null) { 120 mCameraManager.registerTorchCallback(mExecutor, mTorchCallback); 121 } 122 } 123 setFlashlight(boolean enabled)124 public void setFlashlight(boolean enabled) { 125 if (!mHasFlashlight) return; 126 if (mCameraId.get() == null) { 127 mExecutor.execute(this::tryInitCamera); 128 } 129 mExecutor.execute(() -> { 130 if (mCameraId.get() == null) return; 131 synchronized (this) { 132 if (mFlashlightEnabled != enabled) { 133 try { 134 mCameraManager.setTorchMode(mCameraId.get(), enabled); 135 } catch (CameraAccessException e) { 136 Log.e(TAG, "Couldn't set torch mode", e); 137 dispatchError(); 138 } 139 } 140 } 141 }); 142 } 143 hasFlashlight()144 public boolean hasFlashlight() { 145 return mHasFlashlight; 146 } 147 isEnabled()148 public synchronized boolean isEnabled() { 149 return mFlashlightEnabled; 150 } 151 isAvailable()152 public synchronized boolean isAvailable() { 153 return mTorchAvailable; 154 } 155 156 @Override addCallback(@onNull FlashlightListener l)157 public void addCallback(@NonNull FlashlightListener l) { 158 synchronized (mListeners) { 159 if (mCameraId.get() == null) { 160 mExecutor.execute(this::tryInitCamera); 161 } 162 cleanUpListenersLocked(l); 163 mListeners.add(new WeakReference<>(l)); 164 l.onFlashlightAvailabilityChanged(isAvailable()); 165 l.onFlashlightChanged(isEnabled()); 166 } 167 } 168 169 @Override removeCallback(@onNull FlashlightListener l)170 public void removeCallback(@NonNull FlashlightListener l) { 171 synchronized (mListeners) { 172 cleanUpListenersLocked(l); 173 } 174 } 175 176 @WorkerThread getCameraId()177 private String getCameraId() throws CameraAccessException { 178 String[] ids = mCameraManager.getCameraIdList(); 179 for (String id : ids) { 180 CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id); 181 Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); 182 Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING); 183 if (flashAvailable != null && flashAvailable 184 && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) { 185 return id; 186 } 187 } 188 return null; 189 } 190 dispatchModeChanged(boolean enabled)191 private void dispatchModeChanged(boolean enabled) { 192 dispatchListeners(DISPATCH_CHANGED, enabled); 193 } 194 dispatchError()195 private void dispatchError() { 196 dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */); 197 } 198 dispatchAvailabilityChanged(boolean available)199 private void dispatchAvailabilityChanged(boolean available) { 200 dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available); 201 } 202 dispatchListeners(int message, boolean argument)203 private void dispatchListeners(int message, boolean argument) { 204 synchronized (mListeners) { 205 final int N = mListeners.size(); 206 boolean cleanup = false; 207 for (int i = 0; i < N; i++) { 208 FlashlightListener l = mListeners.get(i).get(); 209 if (l != null) { 210 if (message == DISPATCH_ERROR) { 211 l.onFlashlightError(); 212 } else if (message == DISPATCH_CHANGED) { 213 l.onFlashlightChanged(argument); 214 } else if (message == DISPATCH_AVAILABILITY_CHANGED) { 215 l.onFlashlightAvailabilityChanged(argument); 216 } 217 } else { 218 cleanup = true; 219 } 220 } 221 if (cleanup) { 222 cleanUpListenersLocked(null); 223 } 224 } 225 } 226 cleanUpListenersLocked(FlashlightListener listener)227 private void cleanUpListenersLocked(FlashlightListener listener) { 228 for (int i = mListeners.size() - 1; i >= 0; i--) { 229 FlashlightListener found = mListeners.get(i).get(); 230 if (found == null || found == listener) { 231 mListeners.remove(i); 232 } 233 } 234 } 235 236 private final CameraManager.TorchCallback mTorchCallback = 237 new CameraManager.TorchCallback() { 238 239 @Override 240 @WorkerThread 241 public void onTorchModeUnavailable(String cameraId) { 242 if (TextUtils.equals(cameraId, mCameraId.get())) { 243 setCameraAvailable(false); 244 mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 0); 245 246 } 247 } 248 249 @Override 250 @WorkerThread 251 public void onTorchModeChanged(String cameraId, boolean enabled) { 252 if (TextUtils.equals(cameraId, mCameraId.get())) { 253 setCameraAvailable(true); 254 setTorchMode(enabled); 255 mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 1); 256 mSecureSettings.putInt(Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0); 257 } 258 } 259 260 private void setCameraAvailable(boolean available) { 261 boolean changed; 262 synchronized (FlashlightControllerImpl.this) { 263 changed = mTorchAvailable != available; 264 mTorchAvailable = available; 265 } 266 if (changed) { 267 if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")"); 268 dispatchAvailabilityChanged(available); 269 } 270 } 271 272 private void setTorchMode(boolean enabled) { 273 boolean changed; 274 synchronized (FlashlightControllerImpl.this) { 275 changed = mFlashlightEnabled != enabled; 276 mFlashlightEnabled = enabled; 277 } 278 if (changed) { 279 if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")"); 280 dispatchModeChanged(enabled); 281 } 282 } 283 }; 284 dump(PrintWriter pw, String[] args)285 public void dump(PrintWriter pw, String[] args) { 286 pw.println("FlashlightController state:"); 287 288 pw.print(" mCameraId="); 289 pw.println(mCameraId); 290 pw.print(" mFlashlightEnabled="); 291 pw.println(mFlashlightEnabled); 292 pw.print(" mTorchAvailable="); 293 pw.println(mTorchAvailable); 294 } 295 } 296