package nl.xservices.plugins; import android.Manifest; import android.annotation.TargetApi; import android.content.Context; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; import android.os.Build; import android.util.Log; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.PermissionHelper; import org.apache.cordova.PluginResult; import org.json.JSONArray; import org.json.JSONException; import java.io.IOException; import java.lang.reflect.Method; public class Flashlight extends CordovaPlugin { private static final String ACTION_AVAILABLE = "available"; private static final String ACTION_SWITCH_ON = "switchOn"; private static final String ACTION_SWITCH_OFF = "switchOff"; private static Boolean capable; private boolean releasing; @SuppressWarnings("deprecation") private Camera mCamera; private static final int PERMISSION_CALLBACK_CAMERA = 33; private String[] permissions = {Manifest.permission.CAMERA}; private CallbackContext callbackContext; @Override public boolean execute(final String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException { Log.d("Flashlight", "Plugin Called: " + action); this.callbackContext = callbackContext; try { if (action.equals(ACTION_SWITCH_ON)) { cordova.getThreadPool().execute(new Runnable() { public void run() { // When switching on immediately after checking for isAvailable, // the release method may still be running, so wait a bit. while (releasing) { try { Thread.sleep(10); } catch (InterruptedException ignore) { } } // android M permission if (!hasPermisssion()) { requestPermissions(PERMISSION_CALLBACK_CAMERA); } else { toggleTorch(true); } } }); return true; } else if (action.equals(ACTION_SWITCH_OFF)) { cordova.getThreadPool().execute(new Runnable() { public void run() { toggleTorch(false); releaseCamera(); } }); return true; } else if (action.equals(ACTION_AVAILABLE)) { callbackContext.success(isCapable() ? 1 : 0); return true; } else { callbackContext.error("flashlight." + action + " is not a supported function."); return false; } } catch (Exception e) { callbackContext.error(e.getMessage()); return false; } } private boolean isCapable() { if (capable == null) { capable = false; final PackageManager packageManager = this.cordova.getActivity().getPackageManager(); for (final FeatureInfo feature : packageManager.getSystemAvailableFeatures()) { if (PackageManager.FEATURE_CAMERA_FLASH.equalsIgnoreCase(feature.name)) { capable = true; break; } } } return capable; } private void toggleTorch(boolean switchOn) { try { if (isCapable()) { doToggleTorch(switchOn); } else { callbackContext.error("Device is not capable of using the flashlight. Please test with flashlight.available()"); } } catch (Exception e) { callbackContext.error(e.getMessage()); } } @SuppressWarnings("deprecation") private void doToggleTorch(boolean switchOn) throws IOException, CameraAccessException { if (Build.VERSION.SDK_INT >= 23) { // Android M has such an easy API! <3 doToggleTorchSdk23(switchOn); } else { if (mCamera == null) { mCamera = Camera.open(); if (Build.VERSION.SDK_INT >= 11) { // honeycomb // required for (at least) the Nexus 5 mCamera.setPreviewTexture(new SurfaceTexture(0)); } } final Camera.Parameters mParameters = mCamera.getParameters(); mParameters.setFlashMode(switchOn ? Camera.Parameters.FLASH_MODE_TORCH : Camera.Parameters.FLASH_MODE_OFF); mCamera.setParameters(mParameters); mCamera.startPreview(); callbackContext.success(); } } @TargetApi(21) private void doToggleTorchSdk23(boolean switchOn) throws IOException, CameraAccessException { final CameraManager cameraManager = (CameraManager) cordova.getActivity().getSystemService(Context.CAMERA_SERVICE); for (final String id : cameraManager.getCameraIdList()) { // Turn on the flash if the camera has one (usually the one at index 0 has one) final Boolean hasFlash = cameraManager.getCameraCharacteristics(id).get(CameraCharacteristics.FLASH_INFO_AVAILABLE); if (Boolean.TRUE.equals(hasFlash)) { setTorchMode(cameraManager, id, switchOn); break; } } } @TargetApi(23) private void setTorchMode(CameraManager cameraManager, String id, boolean switchOn) throws CameraAccessException { // since folks may not use SDK 23 to compile we'll use reflection as a temporary solution try { final Method setTorchMode = cameraManager.getClass().getMethod("setTorchMode", String.class, boolean.class); setTorchMode.invoke(cameraManager, id, switchOn); callbackContext.success(); } catch (ReflectiveOperationException e) { callbackContext.error(e.getMessage()); } catch (Throwable t) { callbackContext.error(t.getMessage()); } } public boolean hasPermisssion() { for (final String p : permissions) { if (!PermissionHelper.hasPermission(this, p)) { return false; } } return true; } public void requestPermissions(int requestCode) { PermissionHelper.requestPermissions(this, requestCode, permissions); } public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { PluginResult result; for (int r : grantResults) { if (r == PackageManager.PERMISSION_DENIED) { result = new PluginResult(PluginResult.Status.ILLEGAL_ACCESS_EXCEPTION); this.callbackContext.sendPluginResult(result); return; } } switch (requestCode) { case PERMISSION_CALLBACK_CAMERA: toggleTorch(true); break; } } private void releaseCamera() { releasing = true; // we need to release the camera, so other apps can use it new Thread(new Runnable() { public void run() { if (mCamera != null) { mCamera.stopPreview(); mCamera.setPreviewCallback(null); mCamera.unlock(); mCamera.release(); mCamera = null; } releasing = false; } }).start(); } }