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