summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_flashlight_off.xml28
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_flashlight_on.xml28
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-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
9 files changed, 465 insertions, 2 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e12549a..ad27e41 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -106,6 +106,8 @@
<!-- Wifi Display -->
<uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" />
+ <uses-permission android:name="android.permission.CAMERA" />
+
<application
android:name=".SystemUIApplication"
android:persistent="true"
diff --git a/packages/SystemUI/res/drawable/ic_qs_flashlight_off.xml b/packages/SystemUI/res/drawable/ic_qs_flashlight_off.xml
new file mode 100644
index 0000000..49eba22
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_flashlight_off.xml
@@ -0,0 +1,28 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="64.0dp"
+ android:height="64.0dp"/>
+
+ <viewport
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"/>
+
+ <path
+ android:fill="#4DFFFFFF"
+ android:pathData="M3.3,3.0L2.0,4.3l5.0,5.0L7.0,13.0l3.0,0.0l0.0,9.0l3.6,-6.1l4.1,4.1l1.3,-1.3L3.3,3.0zM17.0,10.0l-4.0,0.0l4.0,-8.0L7.0,2.0l0.0,2.2l8.5,8.5L17.0,10.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_flashlight_on.xml b/packages/SystemUI/res/drawable/ic_qs_flashlight_on.xml
new file mode 100644
index 0000000..101ca84
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_flashlight_on.xml
@@ -0,0 +1,28 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="64.0dp"
+ android:height="64.0dp"/>
+
+ <viewport
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"/>
+
+ <path
+ android:fill="#FFFFFFFF"
+ android:pathData="M7.0,2.0l0.0,11.0 3.0,0.0 0.0,9.0 7.0,-12.0 -4.0,0.0 4.0,-8.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f021253..a8799f7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -540,6 +540,8 @@
<string name="quick_settings_hotspot_label">Hotspot</string>
<!-- QuickSettings: Notifications [CHAR LIMIT=NONE] -->
<string name="quick_settings_notifications_label">Notifications</string>
+ <!-- QuickSettings: Flashlight [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_flashlight_label">Flashlight</string>
<!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
<string name="recents_empty_message">No recent apps</string>
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();
+ }
+}