diff options
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(); + } +} |