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