Java tutorial
/** * Copyright 2015 Adam Feinstein * <p/> * This file is part of Selfr. * <p/> * Selfr is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * <p/> * Selfr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * <p/> * You should have received a copy of the GNU General Public License * along with Selfr. If not, see <http://www.gnu.org/licenses/>. */ package com.gelakinetic.selfr; import android.Manifest; import android.app.Dialog; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.hardware.Camera; import android.hardware.SensorManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.Html; import android.text.Spanned; import android.text.SpannedString; import android.text.method.LinkMovementMethod; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.OrientationEventListener; import android.view.View; import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @SuppressWarnings("deprecation") public class CameraActivity extends AppCompatActivity implements IAudioReceiver { private static final int DIALOG_ABOUT = 1; /* Enums */ public enum ViewState { VISIBLE, IN_TRANSITION, GONE } /* Constants */ private static final int UI_ANIMATION_DELAY = 200; private static final int PERMISSION_REQUEST_CODE = 162; /* UI Objects */ private FrameLayout mContentView; private FrameLayout mFlashView; private TextView mNoStickWarningView; private View mControlsView; private CameraPreview mCameraPreview; private TextView mDebugTextView; /* State objects */ private ViewState mSystemBarVisible; private ViewState mControlsVisible; private int mCameraType = Camera.CameraInfo.CAMERA_FACING_FRONT; private String mFlashMode = Camera.Parameters.FLASH_MODE_OFF; private boolean mHardwareFlashSupported = false; private boolean mDebounce = false; private int mDeviceRotation = 0; private float mOldBrightness; /* Hardware interface objects */ private HeadsetStateReceiver mHeadsetStateReceiver; private AudioCapturer mAudioCapturer; private Camera mCamera; private OrientationEventListener mOrientationEventListener; private EnvelopeDetector mEnvelopeDetector; private float mButtonThreshold; /* Handler and Runnables */ private Handler mHandler; private final Runnable mHideAllRunnable = new Runnable() { /** * Calls hide(), can be posted delayed with a handler */ @Override public void run() { hideControls(); } }; private final Runnable mHideSystemBarRunnable = new Runnable() { /** * Delayed removal of status and navigation bar * Note that some of these constants are new as of API 16 (Jelly Bean) * and API 19 (KitKat). It is safe to use them, as they are inlined * at compile-time and do nothing on earlier devices. */ @Override public void run() { int flags = View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { flags |= View.SYSTEM_UI_FLAG_FULLSCREEN; flags |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE; flags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; } mContentView.setSystemUiVisibility(flags); mSystemBarVisible = ViewState.GONE; } }; private final Runnable mShowControlsRunnable = new Runnable() { /** * Makes the controls visible again, and animates their entrance */ @Override public void run() { /* Show the controls view */ mControlsView.setVisibility(View.VISIBLE); /* Animate it's entrance */ Animation flyInAnimation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.animation_fly_in); flyInAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { /* Unused */ } @Override public void onAnimationEnd(Animation animation) { /* Mark the controls as visible after the animation finishes */ mControlsVisible = ViewState.VISIBLE; } @Override public void onAnimationRepeat(Animation animation) { /* Unused */ } }); mControlsView.startAnimation(flyInAnimation); } }; private final Runnable mSetFrontFlashRunnable = new Runnable() { /** * Shows a maximum brightness, white "flash" view, waits two seconds * for the screen and camera to adjust, and takes a picture */ @Override public void run() { /* Save the old brightness, set current to max */ WindowManager.LayoutParams layout = getWindow().getAttributes(); mOldBrightness = layout.screenBrightness; layout.screenBrightness = 1F; getWindow().setAttributes(layout); /* Show the "flash" screen */ mFlashView.setVisibility(View.VISIBLE); /* Take a picture, after letting the "flash" settle */ mHandler.removeCallbacks(mTakePictureRunnable); mHandler.postDelayed(mTakePictureRunnable, 2000); } }; private final Runnable mClearFrontFlashRunnable = new Runnable() { /** * Restores the brightness and hides the white "flash" view */ @Override public void run() { /* Restore the brightness */ WindowManager.LayoutParams layout = getWindow().getAttributes(); layout.screenBrightness = mOldBrightness; getWindow().setAttributes(layout); /* Hide the "flash" view */ mFlashView.setVisibility(View.GONE); } }; private final Runnable mTakePictureRunnable = new Runnable() { /** * Take a picture and set a timer to not allow another picture * for three seconds */ @Override public void run() { try { /* Just to be sure */ if (mCamera == null) { return; } mCamera.takePicture(null, null, mPicture); mDebounce = true; mHandler.removeCallbacks(mClearDebounceRunnable); mHandler.postDelayed(mClearDebounceRunnable, 3000); } catch (RuntimeException e) { /* That didn't work... */ } } }; private final Runnable mClearDebounceRunnable = new Runnable() { /** * Clear the debounce timer, three seconds after a picture is taken */ @Override public void run() { mDebounce = false; } }; private final Runnable mSetFrontShutterRunnable = new Runnable() { /** * Blackout the screen, like a shutter snap */ @Override public void run() { mFlashView.setBackgroundColor(getResources().getColor(android.R.color.black)); mFlashView.setVisibility(View.VISIBLE); } }; private final Runnable mClearFrontShutterRunnable = new Runnable() { /** * Clear the black, shutter view from the screen */ @Override public void run() { mFlashView.setVisibility(View.GONE); mFlashView.setBackgroundColor(getResources().getColor(android.R.color.white)); } }; private final Camera.PictureCallback mPicture = new Camera.PictureCallback() { /** * Callback for after a picture was taken * @param data The bytes to be saved as an image * @param camera The Camera object that took the picture */ @Override public void onPictureTaken(byte[] data, Camera camera) { /* If there is no hardware flash and the front facing camera was used */ if (!mHardwareFlashSupported && mCameraType == Camera.CameraInfo.CAMERA_FACING_FRONT && mFlashMode.equals(Camera.Parameters.FLASH_MODE_ON)) { /* Clear the "flash" screen */ mClearFrontFlashRunnable.run(); } else { /* Otherwise, make a shutter effect */ mSetFrontShutterRunnable.run(); mHandler.removeCallbacks(mClearFrontShutterRunnable); mHandler.postDelayed(mClearFrontShutterRunnable, 500); } /* Get a file to write the picture to */ File pictureFile = getOutputImageFile(); if (pictureFile == null) { return; } try { /* Save the image */ FileOutputStream fos = new FileOutputStream(pictureFile); fos.write(data); fos.close(); /* Notify the media scanner so it displays in the gallery */ sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(pictureFile))); } catch (IOException e) { /* Eat it */ } } }; /** * A safe way to get an instance of the Camera object. Also sets up picture size & focus type * * @param cameraType Camera.CameraInfo.CAMERA_FACING_FRONT or * Camera.CameraInfo.CAMERA_FACING_BACK * @return A Camera object if it was created, or null */ @Nullable private static Camera getCameraInstance(int cameraType) { Camera camera = null; try { Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); /* Scan through all the cameras for one of the specified type */ for (int camIdx = 0; camIdx < Camera.getNumberOfCameras(); camIdx++) { Camera.getCameraInfo(camIdx, cameraInfo); if (cameraInfo.facing == cameraType) { try { /* Open the camera, get default parameters */ camera = Camera.open(camIdx); Camera.Parameters parameters = camera.getParameters(); /* Set the image to native resolution */ List<Camera.Size> sizes = parameters.getSupportedPictureSizes(); Camera.Size nativeSize = null; int maxHeight = Integer.MIN_VALUE; for (Camera.Size size : sizes) { if (size.height > maxHeight) { maxHeight = size.height; nativeSize = size; } } if (nativeSize != null) { parameters.setPictureSize(nativeSize.width, nativeSize.height); } /* Set auto-focus, if we can */ List<String> focusModes = parameters.getSupportedFocusModes(); if (focusModes != null && focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); } /* Set the parameters */ camera.setParameters(parameters); } catch (RuntimeException e) { /* Eat it */ } } } } catch (Exception e) { /* Camera is not available (in use or does not exist) */ } return camera; /* returns null if camera is unavailable */ } /** * Perform initialization of all non-camera views * * @param savedInstanceState If the activity is being re-initialized after previously being shut * down then this Bundle contains the data it most recently supplied * in onSaveInstanceState. Note: Otherwise it is null. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera); mControlsView = findViewById(R.id.fullscreen_content_controls); mContentView = (FrameLayout) findViewById(R.id.fullscreen_content); mFlashView = (FrameLayout) findViewById(R.id.flash_view); mNoStickWarningView = (TextView) findViewById(R.id.no_stick_text); mDebugTextView = (TextView) findViewById(R.id.debug_text_view); mControlsVisible = ViewState.VISIBLE; mSystemBarVisible = ViewState.VISIBLE; mHandler = new Handler(); /* Set up the user interaction to manually show or hide the system UI. */ mContentView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { toggleControls(); } }); /* Set up the Toolbar */ Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(mToolbar); mEnvelopeDetector = new EnvelopeDetector(); } /** * When the activity resumes, request permissions, or if the app has them initialize: * - audio capturer (to detect button presses) * - camera (to take pictures) * - headset state receiver (to detect the selfie stick) * - accelerometer (to properly set picture rotation) */ @Override protected void onResume() { super.onResume(); /* Check to see what permissions we're granted */ ArrayList<String> requestedPermissions = new ArrayList<>(3); if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { requestedPermissions.add(Manifest.permission.CAMERA); } if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { requestedPermissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { requestedPermissions.add(Manifest.permission.RECORD_AUDIO); } /* If we don't have all of the permissions */ if (requestedPermissions.size() > 0) { String permissionStrings[] = new String[requestedPermissions.size()]; requestedPermissions.toArray(permissionStrings); /* Request the permissions */ ActivityCompat.requestPermissions(this, permissionStrings, PERMISSION_REQUEST_CODE); } else { /* Otherwise, fire everything up */ /* Set things based on preferences */ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); if (prefs.getBoolean(getString(R.string.display_value_key), false)) { mDebugTextView.setVisibility(View.VISIBLE); } else { mDebugTextView.setVisibility(View.GONE); } mButtonThreshold = Float.parseFloat( prefs.getString(getString(R.string.threshold_key), getString(R.string.default_threshold))); /* Set up the audio capture */ mAudioCapturer = AudioCapturer.getInstance(this); /* Set up the camera */ mCamera = getCameraInstance(mCameraType); if (mCamera != null) { mCameraPreview = new CameraPreview(this, mCamera); mContentView.addView(mCameraPreview); } /* Register the headset state receiver */ mHeadsetStateReceiver = new HeadsetStateReceiver(this); registerReceiver(mHeadsetStateReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); /* Set up the accelerometer */ mOrientationEventListener = new OrientationEventListener(CameraActivity.this, SensorManager.SENSOR_DELAY_NORMAL) { /** * Called when the orientation of the device has changed. orientation parameter is * in degrees, ranging from 0 to 359. orientation is 0 degrees when the device is * oriented in its natural position, 90 degrees when its left side is at the top, * 180 degrees when it is upside down, and 270 degrees when its right side is to the * top. ORIENTATION_UNKNOWN is returned when the device is close to flat and the * orientation cannot be determined. * * @param orientation The orientation of the device. (0->359, ORIENTATION_UNKNOWN) */ @Override public void onOrientationChanged(int orientation) { /* If the orientation is unknown, don't bother */ if (orientation == ORIENTATION_UNKNOWN) { return; } /* Clamp rotation to nearest 90 degree wedge, depending on camera */ if (mCameraType == Camera.CameraInfo.CAMERA_FACING_FRONT) { if (315 <= orientation || orientation < 45) { mDeviceRotation = 270; } else if (45 <= orientation && orientation < 135) { mDeviceRotation = 180; } else if (135 <= orientation && orientation < 225) { mDeviceRotation = 90; } else if (225 <= orientation && orientation < 315) { mDeviceRotation = 0; } } else { if (315 <= orientation || orientation < 45) { mDeviceRotation = 90; } else if (45 <= orientation && orientation < 135) { mDeviceRotation = 180; } else if (135 <= orientation && orientation < 225) { mDeviceRotation = 270; } else if (225 <= orientation && orientation < 315) { mDeviceRotation = 0; } } } }; if (mOrientationEventListener.canDetectOrientation()) { mOrientationEventListener.enable(); } /* Trigger the initial hide() shortly after the activity has been * created, to briefly hint to the user that UI controls * are available. */ delayedHide(2000); } } /** * Clean up and release all hardware resources when the activity pauses */ @Override protected void onPause() { super.onPause(); /* Clean up the camera & preview */ if (mCameraPreview != null) { mContentView.removeView(mCameraPreview); mCameraPreview = null; } if (mCamera != null) { mCamera.stopPreview(); mCamera.release(); mCamera = null; } /* Clean up the audio */ if (mAudioCapturer != null) { mAudioCapturer.stop(); mAudioCapturer = null; } /* Clean up the receiver */ if (mHeadsetStateReceiver != null) { unregisterReceiver(mHeadsetStateReceiver); mHeadsetStateReceiver = null; } /* Clean up the accelerometer */ if (mOrientationEventListener != null) { mOrientationEventListener.disable(); mOrientationEventListener = null; } } /** * Build a dialog, currently only the about dialog * * @param id An ID corresponding to the dialog to be shown * @return A Dialog to be displayed by the system */ @Override protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_ABOUT: { /* Make a new builder */ AlertDialog.Builder builder = new AlertDialog.Builder(CameraActivity.this); /* Inflate the dialog view */ View dialogView = View.inflate(CameraActivity.this, R.layout.about_dialog, null); /* Set the title */ String title; try { title = getString(R.string.app_name) + " " + getPackageManager().getPackageInfo(getPackageName(), 0).versionName; } catch (PackageManager.NameNotFoundException e) { title = getString(R.string.app_name); } builder.setTitle(title); /* Set the text, with links enabled */ TextView dialogText = (TextView) dialogView.findViewById(R.id.about_dialog_text); String aboutText = getString(R.string.about_message); /* Add the build date if possible */ String buildDate = getBuildDate(); if (buildDate != null) { aboutText += buildDate + "<br>"; } dialogText.setText(formatHtmlString(aboutText)); dialogText.setMovementMethod(LinkMovementMethod.getInstance()); builder.setView(dialogView); /* Show the dialog */ return builder.create(); } default: { return super.onCreateDialog(id); } } } /** * Helper function to return the build date of this APK * * @return A build date for this APK, or null if it could not be determined */ private String getBuildDate() { try { ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), 0); ZipFile zf = new ZipFile(ai.sourceDir); ZipEntry ze = zf.getEntry("classes.dex"); long time = ze.getTime(); String s = SimpleDateFormat.getInstance().format(new java.util.Date(time)); zf.close(); return s; } catch (Exception e) { return null; } } /** * Callback for the result from requesting permissions. This method is invoked for every call * on requestPermissions(String[], int). If permissions aren't granted, pop a toast & finish() * * @param requestCode The request code passed in requestPermissions(String[], int). * @param permissions The requested permissions. Never null. * @param grantResults The grant results for the corresponding permissions which is either * android.content.pm.PackageManager.PERMISSION_GRANTED or * android.content.pm.PackageManager.PERMISSION_DENIED. Never null. */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { boolean allPermissionsGranted = true; if (requestCode == PERMISSION_REQUEST_CODE) { for (int i = 0; i < grantResults.length; i++) { switch (permissions[i]) { case Manifest.permission.CAMERA: case Manifest.permission.WRITE_EXTERNAL_STORAGE: case Manifest.permission.RECORD_AUDIO: { if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { allPermissionsGranted = false; } break; } default: { /* Some unknown permission */ } } } } if (!allPermissionsGranted) { Toast.makeText(this, getString(R.string.permission_failure), Toast.LENGTH_LONG).show(); this.finish(); } } /** * Toggle the visible state of the controls */ private void toggleControls() { /* If there is no animation in progress */ if (mControlsVisible != ViewState.IN_TRANSITION && mSystemBarVisible != ViewState.IN_TRANSITION) { /* If the controls are visible */ if (mControlsVisible == ViewState.VISIBLE && mSystemBarVisible == ViewState.VISIBLE) { /* Hide them */ hideControls(); } else if (mControlsVisible == ViewState.GONE && mSystemBarVisible == ViewState.GONE) { /* Otherwise, show them */ showControls(); } } } /** * Hide the system bar & toolbar */ private void hideControls() { /* Mark the views as animating */ mControlsVisible = ViewState.IN_TRANSITION; mSystemBarVisible = ViewState.IN_TRANSITION; /* Hide UI first */ Animation flyInAnimation = AnimationUtils.loadAnimation(this, R.anim.animation_fly_out); flyInAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { /* Unused */ } /** * When the animation completes, hide the controls view * @param animation The animation which reached its end. */ @Override public void onAnimationEnd(Animation animation) { mControlsView.setVisibility(View.GONE); mControlsVisible = ViewState.GONE; } @Override public void onAnimationRepeat(Animation animation) { /* Unused */ } }); mControlsView.startAnimation(flyInAnimation); /* Schedule a runnable to remove the status and navigation bar after a delay */ mHandler.removeCallbacks(mShowControlsRunnable); mHandler.removeCallbacks(mHideSystemBarRunnable); mHandler.removeCallbacks(mHideAllRunnable); mHandler.postDelayed(mHideSystemBarRunnable, UI_ANIMATION_DELAY); } /** * Schedules a call to hide() in [delay] milliseconds, canceling any * previously scheduled calls. * * @param delayMillis How long to delay before hiding the UI */ private void delayedHide(int delayMillis) { mHandler.removeCallbacks(mHideSystemBarRunnable); mHandler.removeCallbacks(mHideAllRunnable); mHandler.postDelayed(mHideAllRunnable, delayMillis); } /** * Show the system bar & toolbar */ private void showControls() { /* Mark the views as animating */ mSystemBarVisible = ViewState.IN_TRANSITION; mControlsVisible = ViewState.IN_TRANSITION; /* Show the system bar */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { mContentView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); } mSystemBarVisible = ViewState.VISIBLE; /* Schedule a runnable to display UI elements after a delay */ mHandler.removeCallbacks(mHideSystemBarRunnable); mHandler.removeCallbacks(mHideAllRunnable); mHandler.removeCallbacks(mShowControlsRunnable); mHandler.postDelayed(mShowControlsRunnable, UI_ANIMATION_DELAY); /* Hide the UI in 5 seconds, should be enough for a button press */ delayedHide(5000); } /** * Initialize the contents of the Activity's standard options menu. The menu is inflated from * R.menu.camera_menu * * @param menu The options menu in which you place your items. * @return true, since the menu should be displayed */ @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.camera_menu, menu); return true; } /** * This hook is called whenever an item in your options menu is selected. * * @param item The menu item that was selected. * @return false to allow normal menu processing to proceed, true to consume it here. */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.photo_library: /* Launch an intent to open a gallery app */ Intent i = new Intent(Intent.ACTION_VIEW, android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI); startActivity(i); return true; case R.id.camera_switch: { /* Switch the camera between front and rear */ switchCamera(item); return true; } case R.id.flash_setting: { /* Switch the flash between on and off */ switchFlash(item); return true; } case R.id.preferences: { /* Launch the preference activity */ Intent intent = new Intent(this, PrefActivity.class); startActivity(intent); return true; } case R.id.about: { /* Show the about dialog */ showDialog(DIALOG_ABOUT); return true; } default: { /* Pass the event along */ return super.onOptionsItemSelected(item); } } } /** * If the flash is on, turn it off, and vice versa. Front facing cameras without hardware * flash will briefly display a max brightness, pure white screen * * @param item The menu item to change the icon in order to reflect the current state */ private void switchFlash(MenuItem item) { /* Change the flash mode & icon */ switch (mFlashMode) { case Camera.Parameters.FLASH_MODE_OFF: { mFlashMode = Camera.Parameters.FLASH_MODE_ON; item.setIcon(R.drawable.ic_flash_on_white_24dp); break; } case Camera.Parameters.FLASH_MODE_ON: { mFlashMode = Camera.Parameters.FLASH_MODE_OFF; item.setIcon(R.drawable.ic_flash_off_white_24dp); break; } } /* Then set the parameter */ setFlashParameter(); /* Hide the UI */ delayedHide(2500); } /** * If the rear camera is being used, switch to the front camera, and vice versa * This blocks the UI thread, which is generally bad, but nothing is happening anyway, * and swapping camera resources on a separate thread is a recipe for crashes * * @param item The menu item to change the icon in order to reflect the current state */ private void switchCamera(MenuItem item) { /* Switch from one camera type to the other, adjust the icon as necessary */ switch (mCameraType) { case Camera.CameraInfo.CAMERA_FACING_FRONT: { mCameraType = Camera.CameraInfo.CAMERA_FACING_BACK; item.setIcon(R.drawable.ic_camera_rear_white_24dp); break; } case Camera.CameraInfo.CAMERA_FACING_BACK: { mCameraType = Camera.CameraInfo.CAMERA_FACING_FRONT; item.setIcon(R.drawable.ic_camera_front_white_24dp); break; } } /* Remove old camera & preview */ if (mCameraPreview != null) { mContentView.removeView(mCameraPreview); } /* Release current camera resources */ if (mCamera != null) { mCamera.stopPreview(); mCamera.release(); mCamera = null; } /* Get a new camera instance */ mCamera = getCameraInstance(mCameraType); /* Set the preview with the new Camera object */ if (mCamera != null) { mCameraPreview = new CameraPreview(getApplicationContext(), mCamera); mContentView.addView(mCameraPreview); /* Make sure the flash parameter is correct */ setFlashParameter(); } /* Hide the UI */ delayedHide(2500); } /** * Sets the current flash mode to the current Camera object */ private void setFlashParameter() { if (mCamera == null) { return; } /* If the camera supports flash, set the parameter */ Camera.Parameters parameters = mCamera.getParameters(); List<String> flashModes = parameters.getSupportedFlashModes(); if (flashModes != null && flashModes.contains(Camera.Parameters.FLASH_MODE_OFF) && flashModes.contains(Camera.Parameters.FLASH_MODE_ON)) { switch (mFlashMode) { case Camera.Parameters.FLASH_MODE_OFF: { parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); break; } case Camera.Parameters.FLASH_MODE_ON: { parameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON); break; } } mCamera.setParameters(parameters); mHardwareFlashSupported = true; } else { mHardwareFlashSupported = false; } } /** * Create a File for saving an image */ @Nullable private File getOutputImageFile() { /* Make sure the external storage is mounted first */ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { return null; } /* Make a Selfr folder in the DCIM directory */ File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), getString(R.string.app_name)); /* Create the storage directory if it does not exist */ if (!mediaStorageDir.exists()) { if (!mediaStorageDir.mkdirs()) { /* Can't make the folder */ return null; } } /* Create a media file name */ String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); return new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"); } /** * Called from AudioCapturer when a buffer of audio was received * * @param tempBuf A buffer of audio received */ @Override public void capturedAudioReceived(short[] tempBuf) { /* Keep the envelope detector running, no matter what */ float outputs[] = new float[tempBuf.length + 1]; mEnvelopeDetector.findEnvelope(tempBuf, outputs); final float average = EnvelopeDetector.average(outputs); /* Update the debug text view */ if (mDebugTextView.getVisibility() == View.VISIBLE) { runOnUiThread(new Runnable() { /** * Update the debug text view on the UI thread, not the audio thread */ @Override public void run() { mDebugTextView.setText(String.format(Locale.getDefault(), "%d", (int) average)); } }); } /* If the app just took a picture, and is debouncing, don't look for button presses */ if (mDebounce) { return; } /* Check to see if the average of the envelope crosses a threshold */ boolean buttonPressed = false; if (average > mButtonThreshold) { buttonPressed = true; } /* If a button press was detected */ if (buttonPressed) { /* And the camera isn't null */ if (mCamera != null) { /* Set rotation */ Camera.Parameters parameters = mCamera.getParameters(); parameters.setRotation(mDeviceRotation); mCamera.setParameters(parameters); if (!mHardwareFlashSupported && mCameraType == Camera.CameraInfo.CAMERA_FACING_FRONT && mFlashMode.equals(Camera.Parameters.FLASH_MODE_ON)) { /* No hardware flash & front camera, draw the screen bright white */ runOnUiThread(mSetFrontFlashRunnable); } else { /* Take a picture immediately */ mTakePictureRunnable.run(); } } } } /** * Called from HeadsetStateReceiver when a selfie stick is attached or removed. * This is called when the activity is created too. * * @param selfieStickConnected true if a stick was connected, false if it was removed */ public void setSelfieStickConnected(boolean selfieStickConnected) { if (selfieStickConnected) { mNoStickWarningView.setVisibility(View.GONE); if (!mAudioCapturer.start()) { Toast.makeText(this, getString(R.string.stick_error), Toast.LENGTH_LONG).show(); } } else { mNoStickWarningView.setVisibility(View.VISIBLE); mAudioCapturer.stop(); } } /** * Jellybean had a weird bug, and this fixes it. Silly google! * https://code.google.com/p/android/issues/detail?id=35466#c2 * <p/> * Process HTML tags within a String before displaying (i.e. <br> becomes \n) * * @param source A string of HTML * @return a formatted Spanned which JellyBean is happy with */ private static Spanned formatHtmlString(String source) { /* Make sure we're not formatting a null string */ if (source == null) { return new SpannedString(""); } if (Build.VERSION.SDK_INT == 16) { source = source.replace("<", " <").replace(">", " >").replace(" ", " "); } return Html.fromHtml(source); } }