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