summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src/com/android/systemui
diff options
context:
space:
mode:
authorAdrian Roos <roosa@google.com>2014-06-30 15:11:53 +0200
committerAdrian Roos <roosa@google.com>2014-07-02 12:33:36 +0000
commitb83777ba5c12295224cd566bba59a44b8860e449 (patch)
tree82c04959eb37d4d72114fa1d9ab315610c2e7be0 /packages/SystemUI/src/com/android/systemui
parentf29131f7013dc0d6994556b95e74db608c89beb8 (diff)
downloadframeworks_base-b83777ba5c12295224cd566bba59a44b8860e449.zip
frameworks_base-b83777ba5c12295224cd566bba59a44b8860e449.tar.gz
frameworks_base-b83777ba5c12295224cd566bba59a44b8860e449.tar.bz2
Add flashlight to quick settings
Bug: 15934851 Change-Id: I86f61fa11fe64e76adb032391ce7e7170f59549d
Diffstat (limited to 'packages/SystemUI/src/com/android/systemui')
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java308
5 files changed, 405 insertions, 2 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 786cd9e..ba350e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -30,6 +30,7 @@ import com.android.systemui.R;
import com.android.systemui.qs.QSTile.State;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.Listenable;
import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -221,6 +222,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
TetheringController getTetheringController();
CastController getCastController();
VolumeComponent getVolumeComponent();
+ FlashlightController getFlashlightController();
}
public static class State {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
new file mode 100644
index 0000000..b610cf3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -0,0 +1,80 @@
+/*
+ * 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.qs.tiles;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.statusbar.policy.FlashlightController;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings.Secure;
+
+/** Quick settings tile: Control flashlight **/
+public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
+ FlashlightController.FlashlightListener {
+
+ private final FlashlightController mFlashlightController;
+
+ public FlashlightTile(Host host) {
+ super(host);
+ mFlashlightController = host.getFlashlightController();
+ mFlashlightController.addListener(this);
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ }
+
+ @Override
+ protected void handleUserSwitch(int newUserId) {
+ }
+
+ @Override
+ protected void handleClick() {
+ boolean newState = !mState.value;
+ mFlashlightController.setFlashlight(newState);
+ refreshState(newState);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ if (arg instanceof Boolean) {
+ state.value = (Boolean) arg;
+ }
+ state.visible = mFlashlightController.isAvailable();
+ state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
+ state.iconId = state.value
+ ? R.drawable.ic_qs_flashlight_on : R.drawable.ic_qs_flashlight_off;
+ }
+
+ @Override
+ public void onFlashlightOff() {
+ refreshState(false);
+ }
+
+ @Override
+ public void onFlashlightError() {
+ refreshState(false);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 5c6d279..387f5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -122,6 +122,7 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
import com.android.systemui.statusbar.policy.CastControllerImpl;
import com.android.systemui.statusbar.policy.DateView;
+import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserInfoController;
@@ -193,6 +194,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
CastControllerImpl mCastController;
VolumeComponent mVolumeComponent;
KeyguardUserSwitcher mKeyguardUserSwitcher;
+ FlashlightController mFlashlightController;
int mNaturalBarHeight = -1;
int mIconSize = -1;
@@ -710,6 +712,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
mBatteryController.setStatusBarHeaderView(mHeader);
+ mFlashlightController = new FlashlightController(mContext);
// Set up the quick settings tile panel
mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
@@ -725,7 +728,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
final QSTileHost qsh = new QSTileHost(mContext, this,
mBluetoothController, mLocationController, mRotationLockController,
mNetworkController, mZenModeController, null /*tethering*/,
- mCastController, mVolumeComponent);
+ mCastController, mVolumeComponent, mFlashlightController);
for (QSTile<?> tile : qsh.getTiles()) {
mQSPanel.addTile(tile);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 7c87580..60f38b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -29,6 +29,7 @@ import com.android.systemui.qs.tiles.BugreportTile;
import com.android.systemui.qs.tiles.CastTile;
import com.android.systemui.qs.tiles.CellularTile;
import com.android.systemui.qs.tiles.ColorInversionTile;
+import com.android.systemui.qs.tiles.FlashlightTile;
import com.android.systemui.qs.tiles.LocationTile;
import com.android.systemui.qs.tiles.NotificationsTile;
import com.android.systemui.qs.tiles.RotationLockTile;
@@ -37,6 +38,7 @@ import com.android.systemui.qs.tiles.WifiTile;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -64,12 +66,13 @@ public class QSTileHost implements QSTile.Host {
private final VolumeComponent mVolume;
private final ArrayList<QSTile<?>> mTiles = new ArrayList<QSTile<?>>();
private final int mFeedbackStartDelay;
+ private final FlashlightController mFlashlight;
public QSTileHost(Context context, PhoneStatusBar statusBar,
BluetoothController bluetooth, LocationController location,
RotationLockController rotation, NetworkController network,
ZenModeController zen, TetheringController tethering,
- CastController cast, VolumeComponent volume) {
+ CastController cast, VolumeComponent volume, FlashlightController flashlight) {
mContext = context;
mStatusBar = statusBar;
mBluetooth = bluetooth;
@@ -80,6 +83,7 @@ public class QSTileHost implements QSTile.Host {
mTethering = tethering;
mCast = cast;
mVolume = volume;
+ mFlashlight = flashlight;
final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName());
ht.start();
@@ -95,6 +99,7 @@ public class QSTileHost implements QSTile.Host {
mTiles.add(new LocationTile(this));
mTiles.add(new CastTile(this));
mTiles.add(new HotspotTile(this));
+ mTiles.add(new FlashlightTile(this));
mUserTracker = new CurrentUserTracker(mContext) {
@Override
@@ -177,4 +182,9 @@ public class QSTileHost implements QSTile.Host {
public VolumeComponent getVolumeComponent() {
return mVolume;
}
+
+ @Override
+ public FlashlightController getFlashlightController() {
+ return mFlashlight;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
new file mode 100644
index 0000000..b059043
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -0,0 +1,308 @@
+/*
+ * 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.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Manages the flashlight.
+ */
+public class FlashlightController {
+
+ private static final String TAG = "FlashlightController";
+
+ private final CameraManager mCameraManager;
+ /** 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 CameraDevice mCameraDevice;
+ private CaptureRequest mFlashlightRequest;
+ private CameraCaptureSession mSession;
+ private SurfaceTexture mSurfaceTexture;
+ private Surface mSurface;
+
+ public FlashlightController(Context mContext) {
+ mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+ }
+
+ public synchronized void setFlashlight(boolean enabled) {
+ if (mFlashlightEnabled != enabled) {
+ mFlashlightEnabled = enabled;
+ postUpdateFlashlight();
+ }
+ }
+
+ public boolean isAvailable() {
+ try {
+ return getCameraId() != null;
+ } catch (CameraAccessException e) {
+ return false;
+ }
+ }
+
+ public void addListener(FlashlightListener l) {
+ synchronized (mListeners) {
+ cleanUpListenersLocked(l);
+ mListeners.add(new WeakReference<>(l));
+ }
+ }
+
+ public void removeListener(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 void startDevice() throws CameraAccessException {
+ mCameraManager.openCamera(getCameraId(), mCameraListener, mHandler);
+ }
+
+ private void startSession() throws CameraAccessException {
+ mSurfaceTexture = new SurfaceTexture(false);
+ Size size = getSmallestSize(mCameraDevice.getId());
+ mSurfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight());
+ mSurface = new Surface(mSurfaceTexture);
+ ArrayList<Surface> outputs = new ArrayList<>(1);
+ outputs.add(mSurface);
+ mCameraDevice.createCaptureSession(outputs, mSessionListener, mHandler);
+ }
+
+ private Size getSmallestSize(String cameraId) throws CameraAccessException {
+ Size[] outputSizes = mCameraManager.getCameraCharacteristics(cameraId)
+ .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
+ .getOutputSizes(SurfaceTexture.class);
+ if (outputSizes == null || outputSizes.length == 0) {
+ throw new IllegalStateException(
+ "Camera " + cameraId + "doesn't support any outputSize.");
+ }
+ Size chosen = outputSizes[0];
+ for (Size s : outputSizes) {
+ if (chosen.getWidth() >= s.getWidth() && chosen.getHeight() >= s.getHeight()) {
+ chosen = s;
+ }
+ }
+ return chosen;
+ }
+
+ private void postUpdateFlashlight() {
+ ensureHandler();
+ mHandler.post(mUpdateFlashlightRunnable);
+ }
+
+ 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 updateFlashlight(boolean forceDisable) {
+ try {
+ boolean enabled;
+ synchronized (this) {
+ enabled = mFlashlightEnabled && !forceDisable;
+ }
+ if (enabled) {
+ if (mCameraDevice == null) {
+ startDevice();
+ return;
+ }
+ if (mSession == null) {
+ startSession();
+ return;
+ }
+ if (mFlashlightRequest == null) {
+ CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(
+ CameraDevice.TEMPLATE_PREVIEW);
+ builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH);
+ builder.addTarget(mSurface);
+ CaptureRequest request = builder.build();
+ mSession.capture(request, null, mHandler);
+ mFlashlightRequest = request;
+ }
+ } else {
+ if (mCameraDevice != null) {
+ mCameraDevice.close();
+ teardown();
+ }
+ }
+
+ } catch (CameraAccessException|IllegalStateException|UnsupportedOperationException e) {
+ Log.e(TAG, "Error in updateFlashlight", e);
+ handleError();
+ }
+ }
+
+ private void teardown() {
+ mCameraDevice = null;
+ mSession = null;
+ mFlashlightRequest = null;
+ if (mSurface != null) {
+ mSurface.release();
+ mSurfaceTexture.release();
+ }
+ mSurface = null;
+ mSurfaceTexture = null;
+ }
+
+ private void handleError() {
+ synchronized (this) {
+ mFlashlightEnabled = false;
+ }
+ dispatchError();
+ dispatchOff();
+ updateFlashlight(true /* forceDisable */);
+ }
+
+ private void dispatchOff() {
+ dispatchListeners(false, true /* off */);
+ }
+
+ private void dispatchError() {
+ dispatchListeners(true /* error */, false);
+ }
+
+ private void dispatchListeners(boolean error, boolean off) {
+ 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 (error) {
+ l.onFlashlightError();
+ } else if (off) {
+ l.onFlashlightOff();
+ }
+ } 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 CameraDevice.StateListener mCameraListener = new CameraDevice.StateListener() {
+ @Override
+ public void onOpened(CameraDevice camera) {
+ mCameraDevice = camera;
+ postUpdateFlashlight();
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice camera) {
+ if (mCameraDevice == camera) {
+ dispatchOff();
+ teardown();
+ }
+ }
+
+ @Override
+ public void onError(CameraDevice camera, int error) {
+ Log.e(TAG, "Camera error: camera=" + camera + " error=" + error);
+ if (camera == mCameraDevice || mCameraDevice == null) {
+ handleError();
+ }
+ }
+ };
+
+ private final CameraCaptureSession.StateListener mSessionListener =
+ new CameraCaptureSession.StateListener() {
+ @Override
+ public void onConfigured(CameraCaptureSession session) {
+ mSession = session;
+ postUpdateFlashlight();
+ }
+
+ @Override
+ public void onConfigureFailed(CameraCaptureSession session) {
+ Log.e(TAG, "Configure failed.");
+ if (mSession == null || mSession == session) {
+ handleError();
+ }
+ }
+ };
+
+ private final Runnable mUpdateFlashlightRunnable = new Runnable() {
+ @Override
+ public void run() {
+ updateFlashlight(false /* forceDisable */);
+ }
+ };
+
+ public interface FlashlightListener {
+
+ /**
+ * Called when the flashlight turns off unexpectedly.
+ */
+ void onFlashlightOff();
+
+ /**
+ * Called when there is an error that turns the flashlight off.
+ */
+ void onFlashlightError();
+ }
+}