Cleanup and refactoring of test utilities
- Make leak checking faster by converting to fakes
- Requires making clean interfaces for all CallbackControllers
- Integrate leak checking into the TestableContext
Test: runtest systemui
Change-Id: Ic57a06360d01a0323ef26735a543e9d1805459e2
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
new file mode 100644
index 0000000..008d837
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Manages the flashlight.
+ */
+public class FlashlightControllerImpl implements FlashlightController {
+
+ private static final String TAG = "FlashlightController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final int DISPATCH_ERROR = 0;
+ private static final int DISPATCH_CHANGED = 1;
+ private static final int DISPATCH_AVAILABILITY_CHANGED = 2;
+
+ private final CameraManager mCameraManager;
+ private final Context mContext;
+ /** Call {@link #ensureHandler()} before using */
+ private Handler mHandler;
+
+ /** Lock on mListeners when accessing */
+ private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
+
+ /** Lock on {@code this} when accessing */
+ private boolean mFlashlightEnabled;
+
+ private String mCameraId;
+ private boolean mTorchAvailable;
+
+ public FlashlightControllerImpl(Context context) {
+ mContext = context;
+ mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+
+ tryInitCamera();
+ }
+
+ private void tryInitCamera() {
+ try {
+ mCameraId = getCameraId();
+ } catch (Throwable e) {
+ Log.e(TAG, "Couldn't initialize.", e);
+ return;
+ }
+
+ if (mCameraId != null) {
+ ensureHandler();
+ mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
+ }
+ }
+
+ public void setFlashlight(boolean enabled) {
+ boolean pendingError = false;
+ synchronized (this) {
+ if (mCameraId == null) return;
+ if (mFlashlightEnabled != enabled) {
+ mFlashlightEnabled = enabled;
+ try {
+ mCameraManager.setTorchMode(mCameraId, enabled);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Couldn't set torch mode", e);
+ mFlashlightEnabled = false;
+ pendingError = true;
+ }
+ }
+ }
+ dispatchModeChanged(mFlashlightEnabled);
+ if (pendingError) {
+ dispatchError();
+ }
+ }
+
+ public boolean hasFlashlight() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
+ }
+
+ public synchronized boolean isEnabled() {
+ return mFlashlightEnabled;
+ }
+
+ public synchronized boolean isAvailable() {
+ return mTorchAvailable;
+ }
+
+ public void addCallback(FlashlightListener l) {
+ synchronized (mListeners) {
+ if (mCameraId == null) {
+ tryInitCamera();
+ }
+ cleanUpListenersLocked(l);
+ mListeners.add(new WeakReference<>(l));
+ }
+ }
+
+ public void removeCallback(FlashlightListener l) {
+ synchronized (mListeners) {
+ cleanUpListenersLocked(l);
+ }
+ }
+
+ private synchronized void ensureHandler() {
+ if (mHandler == null) {
+ HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ mHandler = new Handler(thread.getLooper());
+ }
+ }
+
+ private String getCameraId() throws CameraAccessException {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (String id : ids) {
+ CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
+ Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+ Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
+ if (flashAvailable != null && flashAvailable
+ && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
+ return id;
+ }
+ }
+ return null;
+ }
+
+ private void dispatchModeChanged(boolean enabled) {
+ dispatchListeners(DISPATCH_CHANGED, enabled);
+ }
+
+ private void dispatchError() {
+ dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */);
+ }
+
+ private void dispatchAvailabilityChanged(boolean available) {
+ dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available);
+ }
+
+ private void dispatchListeners(int message, boolean argument) {
+ synchronized (mListeners) {
+ final int N = mListeners.size();
+ boolean cleanup = false;
+ for (int i = 0; i < N; i++) {
+ FlashlightListener l = mListeners.get(i).get();
+ if (l != null) {
+ if (message == DISPATCH_ERROR) {
+ l.onFlashlightError();
+ } else if (message == DISPATCH_CHANGED) {
+ l.onFlashlightChanged(argument);
+ } else if (message == DISPATCH_AVAILABILITY_CHANGED) {
+ l.onFlashlightAvailabilityChanged(argument);
+ }
+ } else {
+ cleanup = true;
+ }
+ }
+ if (cleanup) {
+ cleanUpListenersLocked(null);
+ }
+ }
+ }
+
+ private void cleanUpListenersLocked(FlashlightListener listener) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ FlashlightListener found = mListeners.get(i).get();
+ if (found == null || found == listener) {
+ mListeners.remove(i);
+ }
+ }
+ }
+
+ private final CameraManager.TorchCallback mTorchCallback =
+ new CameraManager.TorchCallback() {
+
+ @Override
+ public void onTorchModeUnavailable(String cameraId) {
+ if (TextUtils.equals(cameraId, mCameraId)) {
+ setCameraAvailable(false);
+ }
+ }
+
+ @Override
+ public void onTorchModeChanged(String cameraId, boolean enabled) {
+ if (TextUtils.equals(cameraId, mCameraId)) {
+ setCameraAvailable(true);
+ setTorchMode(enabled);
+ }
+ }
+
+ private void setCameraAvailable(boolean available) {
+ boolean changed;
+ synchronized (FlashlightControllerImpl.this) {
+ changed = mTorchAvailable != available;
+ mTorchAvailable = available;
+ }
+ if (changed) {
+ if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")");
+ dispatchAvailabilityChanged(available);
+ }
+ }
+
+ private void setTorchMode(boolean enabled) {
+ boolean changed;
+ synchronized (FlashlightControllerImpl.this) {
+ changed = mFlashlightEnabled != enabled;
+ mFlashlightEnabled = enabled;
+ }
+ if (changed) {
+ if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")");
+ dispatchModeChanged(enabled);
+ }
+ }
+ };
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("FlashlightController state:");
+
+ pw.print(" mCameraId=");
+ pw.println(mCameraId);
+ pw.print(" mFlashlightEnabled=");
+ pw.println(mFlashlightEnabled);
+ pw.print(" mTorchAvailable=");
+ pw.println(mTorchAvailable);
+ }
+}