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