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.content.Context; 20 import android.content.Intent; 21 import android.content.pm.PackageManager; 22 import android.hardware.camera2.CameraAccessException; 23 import android.hardware.camera2.CameraCharacteristics; 24 import android.hardware.camera2.CameraManager; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.os.Process; 28 import android.provider.Settings; 29 import android.provider.Settings.Secure; 30 import android.text.TextUtils; 31 import android.util.Log; 32 33 import java.io.FileDescriptor; 34 import java.io.PrintWriter; 35 import java.lang.ref.WeakReference; 36 import java.util.ArrayList; 37 38 import javax.inject.Inject; 39 import javax.inject.Singleton; 40 41 /** 42 * Manages the flashlight. 43 */ 44 @Singleton 45 public class FlashlightControllerImpl implements FlashlightController { 46 47 private static final String TAG = "FlashlightController"; 48 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 49 50 private static final int DISPATCH_ERROR = 0; 51 private static final int DISPATCH_CHANGED = 1; 52 private static final int DISPATCH_AVAILABILITY_CHANGED = 2; 53 54 private static final String ACTION_FLASHLIGHT_CHANGED = 55 "com.android.settings.flashlight.action.FLASHLIGHT_CHANGED"; 56 57 private final CameraManager mCameraManager; 58 private final Context mContext; 59 /** Call {@link #ensureHandler()} before using */ 60 private Handler mHandler; 61 62 /** Lock on mListeners when accessing */ 63 private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1); 64 65 /** Lock on {@code this} when accessing */ 66 private boolean mFlashlightEnabled; 67 68 private String mCameraId; 69 private boolean mTorchAvailable; 70 71 @Inject FlashlightControllerImpl(Context context)72 public FlashlightControllerImpl(Context context) { 73 mContext = context; 74 mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); 75 76 tryInitCamera(); 77 } 78 tryInitCamera()79 private void tryInitCamera() { 80 try { 81 mCameraId = getCameraId(); 82 } catch (Throwable e) { 83 Log.e(TAG, "Couldn't initialize.", e); 84 return; 85 } 86 87 if (mCameraId != null) { 88 ensureHandler(); 89 mCameraManager.registerTorchCallback(mTorchCallback, mHandler); 90 } 91 } 92 setFlashlight(boolean enabled)93 public void setFlashlight(boolean enabled) { 94 boolean pendingError = false; 95 synchronized (this) { 96 if (mCameraId == null) return; 97 if (mFlashlightEnabled != enabled) { 98 mFlashlightEnabled = enabled; 99 try { 100 mCameraManager.setTorchMode(mCameraId, enabled); 101 } catch (CameraAccessException e) { 102 Log.e(TAG, "Couldn't set torch mode", e); 103 mFlashlightEnabled = false; 104 pendingError = true; 105 } 106 } 107 } 108 dispatchModeChanged(mFlashlightEnabled); 109 if (pendingError) { 110 dispatchError(); 111 } 112 } 113 hasFlashlight()114 public boolean hasFlashlight() { 115 return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH); 116 } 117 isEnabled()118 public synchronized boolean isEnabled() { 119 return mFlashlightEnabled; 120 } 121 isAvailable()122 public synchronized boolean isAvailable() { 123 return mTorchAvailable; 124 } 125 addCallback(FlashlightListener l)126 public void addCallback(FlashlightListener l) { 127 synchronized (mListeners) { 128 if (mCameraId == null) { 129 tryInitCamera(); 130 } 131 cleanUpListenersLocked(l); 132 mListeners.add(new WeakReference<>(l)); 133 l.onFlashlightAvailabilityChanged(mTorchAvailable); 134 l.onFlashlightChanged(mFlashlightEnabled); 135 } 136 } 137 removeCallback(FlashlightListener l)138 public void removeCallback(FlashlightListener l) { 139 synchronized (mListeners) { 140 cleanUpListenersLocked(l); 141 } 142 } 143 ensureHandler()144 private synchronized void ensureHandler() { 145 if (mHandler == null) { 146 HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); 147 thread.start(); 148 mHandler = new Handler(thread.getLooper()); 149 } 150 } 151 getCameraId()152 private String getCameraId() throws CameraAccessException { 153 String[] ids = mCameraManager.getCameraIdList(); 154 for (String id : ids) { 155 CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id); 156 Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); 157 Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING); 158 if (flashAvailable != null && flashAvailable 159 && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) { 160 return id; 161 } 162 } 163 return null; 164 } 165 dispatchModeChanged(boolean enabled)166 private void dispatchModeChanged(boolean enabled) { 167 dispatchListeners(DISPATCH_CHANGED, enabled); 168 } 169 dispatchError()170 private void dispatchError() { 171 dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */); 172 } 173 dispatchAvailabilityChanged(boolean available)174 private void dispatchAvailabilityChanged(boolean available) { 175 dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available); 176 } 177 dispatchListeners(int message, boolean argument)178 private void dispatchListeners(int message, boolean argument) { 179 synchronized (mListeners) { 180 final int N = mListeners.size(); 181 boolean cleanup = false; 182 for (int i = 0; i < N; i++) { 183 FlashlightListener l = mListeners.get(i).get(); 184 if (l != null) { 185 if (message == DISPATCH_ERROR) { 186 l.onFlashlightError(); 187 } else if (message == DISPATCH_CHANGED) { 188 l.onFlashlightChanged(argument); 189 } else if (message == DISPATCH_AVAILABILITY_CHANGED) { 190 l.onFlashlightAvailabilityChanged(argument); 191 } 192 } else { 193 cleanup = true; 194 } 195 } 196 if (cleanup) { 197 cleanUpListenersLocked(null); 198 } 199 } 200 } 201 cleanUpListenersLocked(FlashlightListener listener)202 private void cleanUpListenersLocked(FlashlightListener listener) { 203 for (int i = mListeners.size() - 1; i >= 0; i--) { 204 FlashlightListener found = mListeners.get(i).get(); 205 if (found == null || found == listener) { 206 mListeners.remove(i); 207 } 208 } 209 } 210 211 private final CameraManager.TorchCallback mTorchCallback = 212 new CameraManager.TorchCallback() { 213 214 @Override 215 public void onTorchModeUnavailable(String cameraId) { 216 if (TextUtils.equals(cameraId, mCameraId)) { 217 setCameraAvailable(false); 218 Settings.Secure.putInt( 219 mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 0); 220 221 } 222 } 223 224 @Override 225 public void onTorchModeChanged(String cameraId, boolean enabled) { 226 if (TextUtils.equals(cameraId, mCameraId)) { 227 setCameraAvailable(true); 228 setTorchMode(enabled); 229 Settings.Secure.putInt( 230 mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 1); 231 Settings.Secure.putInt( 232 mContext.getContentResolver(), Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0); 233 mContext.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED)); 234 } 235 } 236 237 private void setCameraAvailable(boolean available) { 238 boolean changed; 239 synchronized (FlashlightControllerImpl.this) { 240 changed = mTorchAvailable != available; 241 mTorchAvailable = available; 242 } 243 if (changed) { 244 if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")"); 245 dispatchAvailabilityChanged(available); 246 } 247 } 248 249 private void setTorchMode(boolean enabled) { 250 boolean changed; 251 synchronized (FlashlightControllerImpl.this) { 252 changed = mFlashlightEnabled != enabled; 253 mFlashlightEnabled = enabled; 254 } 255 if (changed) { 256 if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")"); 257 dispatchModeChanged(enabled); 258 } 259 } 260 }; 261 dump(FileDescriptor fd, PrintWriter pw, String[] args)262 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 263 pw.println("FlashlightController state:"); 264 265 pw.print(" mCameraId="); 266 pw.println(mCameraId); 267 pw.print(" mFlashlightEnabled="); 268 pw.println(mFlashlightEnabled); 269 pw.print(" mTorchAvailable="); 270 pw.println(mTorchAvailable); 271 } 272 } 273