diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4f9de630..26bcfe37 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ android:name="android.hardware.camera" android:required="true"/> + diff --git a/app/src/main/java/com/simplemobiletools/camera/MainActivity.java b/app/src/main/java/com/simplemobiletools/camera/MainActivity.java index e405f175..ce68dc2f 100644 --- a/app/src/main/java/com/simplemobiletools/camera/MainActivity.java +++ b/app/src/main/java/com/simplemobiletools/camera/MainActivity.java @@ -87,7 +87,15 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen if (isPhoto) { preview.takePicture(); } else { - + final Resources res = getResources(); + final boolean isRecording = preview.toggleRecording(); + if (isRecording) { + shutterBtn.setImageDrawable(res.getDrawable(R.mipmap.video_stop)); + toggleCameraBtn.setVisibility(View.INVISIBLE); + } else { + shutterBtn.setImageDrawable(res.getDrawable(R.mipmap.video_rec)); + toggleCameraBtn.setVisibility(View.VISIBLE); + } } } @@ -99,18 +107,27 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen @OnClick(R.id.toggle_videocam) public void toggleVideo() { - final Resources res = getResources(); isPhoto = !isPhoto; + toggleCameraBtn.setVisibility(View.VISIBLE); if (isPhoto) { + final Resources res = getResources(); togglePhotoVideoBtn.setImageDrawable(res.getDrawable(R.mipmap.videocam)); shutterBtn.setImageDrawable(res.getDrawable(R.mipmap.camera)); + preview.initPhotoMode(); } else { - togglePhotoVideoBtn.setImageDrawable(res.getDrawable(R.mipmap.photo)); - shutterBtn.setImageDrawable(res.getDrawable(R.mipmap.video_rec)); + initVideo(); } } + private void initVideo() { + final Resources res = getResources(); + togglePhotoVideoBtn.setImageDrawable(res.getDrawable(R.mipmap.photo)); + shutterBtn.setImageDrawable(res.getDrawable(R.mipmap.video_rec)); + preview.initRecorder(); + toggleCameraBtn.setVisibility(View.VISIBLE); + } + private void hideNavigationBarIcons() { getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); } @@ -130,12 +147,19 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen final Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME); } + + if (!isPhoto) { + preview.initRecorder(); + initVideo(); + } } @Override protected void onPause() { super.onPause(); - preview.releaseCamera(); + if (preview != null) { + preview.releaseCamera(); + } if (sensorManager != null) sensorManager.unregisterListener(this); diff --git a/app/src/main/java/com/simplemobiletools/camera/PhotoProcessor.java b/app/src/main/java/com/simplemobiletools/camera/PhotoProcessor.java index 85f88bc2..f68008e7 100644 --- a/app/src/main/java/com/simplemobiletools/camera/PhotoProcessor.java +++ b/app/src/main/java/com/simplemobiletools/camera/PhotoProcessor.java @@ -6,17 +6,13 @@ import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.hardware.Camera; import android.media.ExifInterface; -import android.media.MediaScannerConnection; import android.os.AsyncTask; -import android.os.Environment; import android.util.Log; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; public class PhotoProcessor extends AsyncTask { private static final String TAG = PhotoProcessor.class.getSimpleName(); @@ -30,12 +26,13 @@ public class PhotoProcessor extends AsyncTask { @Override protected Void doInBackground(byte[]... params) { - final File photoFile = getOutputMediaFile(); - if (photoFile == null) { + final String photoPath = Utils.getOutputMediaFile(mContext, true); + if (photoPath.isEmpty()) { return null; } try { + final File photoFile = new File(photoPath); final byte[] data = params[0]; final FileOutputStream fos = new FileOutputStream(photoFile); Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); @@ -45,7 +42,7 @@ public class PhotoProcessor extends AsyncTask { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); fos.close(); - scanPhoto(photoFile); + Utils.scanFile(photoPath, mContext); } catch (FileNotFoundException e) { Log.d(TAG, "onPictureTaken file not found: " + e.getMessage()); } catch (IOException e) { @@ -55,20 +52,6 @@ public class PhotoProcessor extends AsyncTask { return null; } - private static File getOutputMediaFile() { - final String appName = mContext.getResources().getString(R.string.app_name); - final File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), appName); - - if (!mediaStorageDir.exists()) { - if (!mediaStorageDir.mkdirs()) { - return null; - } - } - - final String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); - return new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"); - } - private Bitmap setBitmapRotation(Bitmap bitmap, String path) throws IOException { final ExifInterface exif = new ExifInterface(path); final String orientation = exif.getAttribute(ExifInterface.TAG_ORIENTATION); @@ -127,9 +110,4 @@ public class PhotoProcessor extends AsyncTask { } return bitmap; } - - private void scanPhoto(File photo) { - final String[] photoPath = {photo.getAbsolutePath()}; - MediaScannerConnection.scanFile(mContext, photoPath, null, null); - } } diff --git a/app/src/main/java/com/simplemobiletools/camera/Preview.java b/app/src/main/java/com/simplemobiletools/camera/Preview.java index aa7ddb54..f7432fa0 100644 --- a/app/src/main/java/com/simplemobiletools/camera/Preview.java +++ b/app/src/main/java/com/simplemobiletools/camera/Preview.java @@ -4,7 +4,9 @@ import android.app.Activity; import android.content.Context; import android.graphics.Rect; import android.hardware.Camera; +import android.media.CamcorderProfile; import android.media.MediaPlayer; +import android.media.MediaRecorder; import android.os.Handler; import android.util.Log; import android.view.MotionEvent; @@ -34,6 +36,11 @@ public class Preview extends ViewGroup implements SurfaceHolder.Callback, View.O private static boolean isFlashEnabled; private static Camera.Parameters parameters; + private static MediaRecorder recorder; + private static boolean isRecording; + private static boolean isVideoMode; + private static String curVideoPath; + public Preview(Context context) { super(context); } @@ -68,7 +75,6 @@ public class Preview extends ViewGroup implements SurfaceHolder.Callback, View.O releaseCamera(); camera = newCamera; - if (camera != null) { parameters = camera.getParameters(); supportedPreviewSizes = parameters.getSupportedPreviewSizes(); @@ -90,6 +96,9 @@ public class Preview extends ViewGroup implements SurfaceHolder.Callback, View.O setupPreview(); } } + + if (isVideoMode) + initRecorder(); } public static void setCameraDisplayOrientation(int cameraId, android.hardware.Camera camera) { @@ -141,8 +150,9 @@ public class Preview extends ViewGroup implements SurfaceHolder.Callback, View.O new Handler().postDelayed(new Runnable() { @Override public void run() { - if (camera != null) + if (camera != null) { camera.startPreview(); + } canTakePicture = true; } @@ -200,6 +210,8 @@ public class Preview extends ViewGroup implements SurfaceHolder.Callback, View.O } public void releaseCamera() { + stopRecording(); + if (camera != null) { camera.stopPreview(); camera.release(); @@ -240,6 +252,19 @@ public class Preview extends ViewGroup implements SurfaceHolder.Callback, View.O if (camera != null) { camera.stopPreview(); } + + cleanupRecorder(); + } + + private void cleanupRecorder() { + if (recorder != null) { + if (isRecording) { + recorder.stop(); + } + + recorder.release(); + recorder = null; + } } @Override @@ -307,4 +332,64 @@ public class Preview extends ViewGroup implements SurfaceHolder.Callback, View.O public void disableFlash() { isFlashEnabled = false; } + + public void initPhotoMode() { + isRecording = false; + isVideoMode = false; + stopRecording(); + cleanupRecorder(); + } + + // VIDEO RECORDING + public void initRecorder() { + isRecording = false; + isVideoMode = true; + recorder = new MediaRecorder(); + recorder.setCamera(camera); + recorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); + recorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT); + + curVideoPath = Utils.getOutputMediaFile(getContext(), false); + if (curVideoPath.isEmpty()) { + Utils.showToast(getContext(), R.string.video_creating_error); + return; + } + + final CamcorderProfile cpHigh = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); + recorder.setProfile(cpHigh); + recorder.setOutputFile(curVideoPath); + recorder.setPreviewDisplay(surfaceHolder.getSurface()); + recorder.setOrientationHint(90); + + try { + recorder.prepare(); + } catch (IllegalStateException e) { + Log.e(TAG, "initRecorder " + e.getMessage()); + } catch (IOException e) { + Log.e(TAG, "initRecorder " + e.getMessage()); + } + } + + public boolean toggleRecording() { + if (isRecording) { + stopRecording(); + initRecorder(); + } else { + camera.lock(); + camera.unlock(); + recorder.start(); + isRecording = true; + } + return isRecording; + } + + private void stopRecording() { + if (recorder != null && isRecording) { + recorder.stop(); + recorder = null; + } + + isRecording = false; + Utils.scanFile(curVideoPath, getContext()); + } } diff --git a/app/src/main/java/com/simplemobiletools/camera/Utils.java b/app/src/main/java/com/simplemobiletools/camera/Utils.java index a8e34252..5a77d25f 100644 --- a/app/src/main/java/com/simplemobiletools/camera/Utils.java +++ b/app/src/main/java/com/simplemobiletools/camera/Utils.java @@ -1,10 +1,17 @@ package com.simplemobiletools.camera; import android.content.Context; +import android.content.res.Resources; import android.hardware.Camera; +import android.media.MediaScannerConnection; +import android.os.Environment; import android.widget.Toast; +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; +import java.util.Locale; public class Utils { public static Camera.CameraInfo getCameraInfo(int cameraId) { @@ -36,4 +43,38 @@ public class Utils { return true; } + + public static String getOutputMediaFile(Context context, boolean isPhoto) { + final File mediaStorageDir = getFolderName(context, isPhoto); + + if (!mediaStorageDir.exists()) { + if (!mediaStorageDir.mkdirs()) { + return ""; + } + } + + final String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); + if (isPhoto) { + return mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"; + } else { + return mediaStorageDir.getPath() + File.separator + "VID_" + timeStamp + ".mp4"; + } + } + + private static File getFolderName(Context context, boolean isPhoto) { + final Resources res = context.getResources(); + final String appName = res.getString(R.string.app_name); + final String sharedPath = new File(Environment.getExternalStorageDirectory(), appName).getAbsolutePath(); + String typeDirectory = res.getString(R.string.photo_directory); + if (!isPhoto) { + typeDirectory = res.getString(R.string.video_directory); + } + + return new File(sharedPath, typeDirectory); + } + + public static void scanFile(String path, Context context) { + final String[] paths = {path}; + MediaScannerConnection.scanFile(context, paths, null, null); + } } diff --git a/app/src/main/res/mipmap-hdpi/video_stop.png b/app/src/main/res/mipmap-hdpi/video_stop.png new file mode 100644 index 00000000..72847bb3 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/video_stop.png differ diff --git a/app/src/main/res/mipmap-mdpi/video_stop.png b/app/src/main/res/mipmap-mdpi/video_stop.png new file mode 100644 index 00000000..e649243a Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/video_stop.png differ diff --git a/app/src/main/res/mipmap-xhdpi/video_stop.png b/app/src/main/res/mipmap-xhdpi/video_stop.png new file mode 100644 index 00000000..4726160d Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/video_stop.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/video_stop.png b/app/src/main/res/mipmap-xxhdpi/video_stop.png new file mode 100644 index 00000000..c6896893 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/video_stop.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/video_stop.png b/app/src/main/res/mipmap-xxxhdpi/video_stop.png new file mode 100644 index 00000000..c3670ced Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/video_stop.png differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d416ca48..025402ac 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,9 @@ Simple Camera An error occurred at obtaining the camera + An error occurred at creating the video file + Simple Videos + Simple Photos About