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