diff --git a/app/src/main/java/com/simplemobiletools/camera/PhotoProcessor.java b/app/src/main/java/com/simplemobiletools/camera/PhotoProcessor.java index 1ad0948c..37e8c5f0 100644 --- a/app/src/main/java/com/simplemobiletools/camera/PhotoProcessor.java +++ b/app/src/main/java/com/simplemobiletools/camera/PhotoProcessor.java @@ -12,7 +12,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.lang.ref.WeakReference; -public class PhotoProcessor extends AsyncTask { +public class PhotoProcessor extends AsyncTask { private static final String TAG = PhotoProcessor.class.getSimpleName(); private static WeakReference mActivity; private static Uri mUri; @@ -23,7 +23,7 @@ public class PhotoProcessor extends AsyncTask { } @Override - protected Void doInBackground(byte[]... params) { + protected String doInBackground(byte[]... params) { FileOutputStream fos = null; String path; try { @@ -34,7 +34,7 @@ public class PhotoProcessor extends AsyncTask { } if (path.isEmpty()) { - return null; + return ""; } final File photoFile = new File(path); @@ -42,7 +42,7 @@ public class PhotoProcessor extends AsyncTask { fos = new FileOutputStream(photoFile); fos.write(data); fos.close(); - Utils.scanFile(path, mActivity.get().getApplicationContext()); + return photoFile.getAbsolutePath(); } catch (FileNotFoundException e) { Log.e(TAG, "PhotoProcessor file not found: " + e.getMessage()); } catch (IOException e) { @@ -57,19 +57,19 @@ public class PhotoProcessor extends AsyncTask { } } - return null; + return ""; } @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); + protected void onPostExecute(String path) { + super.onPostExecute(path); final MediaSavedListener listener = mActivity.get(); if (listener != null) { - listener.mediaSaved(); + listener.mediaSaved(path); } } public interface MediaSavedListener { - void mediaSaved(); + void mediaSaved(String path); } } diff --git a/app/src/main/java/com/simplemobiletools/camera/Preview.java b/app/src/main/java/com/simplemobiletools/camera/Preview.java index d0525db6..11776796 100644 --- a/app/src/main/java/com/simplemobiletools/camera/Preview.java +++ b/app/src/main/java/com/simplemobiletools/camera/Preview.java @@ -621,7 +621,7 @@ public class Preview extends ViewGroup implements SurfaceHolder.Callback, View.O try { mRecorder.stop(); final String[] paths = {mCurVideoPath}; - MediaScannerConnection.scanFile(getContext(), paths, null, mIsVideoCaptureIntent ? this : null); + MediaScannerConnection.scanFile(getContext(), paths, null, this); } catch (RuntimeException e) { new File(mCurVideoPath).delete(); Utils.showToast(getContext(), R.string.video_saving_error); diff --git a/app/src/main/java/com/simplemobiletools/camera/Utils.java b/app/src/main/java/com/simplemobiletools/camera/Utils.java index 21cc086c..445e08d8 100644 --- a/app/src/main/java/com/simplemobiletools/camera/Utils.java +++ b/app/src/main/java/com/simplemobiletools/camera/Utils.java @@ -7,7 +7,6 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Point; import android.hardware.Camera; -import android.media.MediaScannerConnection; import android.os.Environment; import android.support.v4.content.ContextCompat; import android.view.Display; @@ -85,11 +84,6 @@ public class Utils { return Environment.getExternalStoragePublicDirectory(type); } - public static void scanFile(String path, Context context) { - final String[] paths = {path}; - MediaScannerConnection.scanFile(context, paths, null, null); - } - public static String formatSeconds(int duration) { final StringBuilder sb = new StringBuilder(8); final int hours = duration / (60 * 60); diff --git a/app/src/main/java/com/simplemobiletools/camera/activities/MainActivity.java b/app/src/main/java/com/simplemobiletools/camera/activities/MainActivity.java index 3526f87e..82fe4ee0 100644 --- a/app/src/main/java/com/simplemobiletools/camera/activities/MainActivity.java +++ b/app/src/main/java/com/simplemobiletools/camera/activities/MainActivity.java @@ -1,14 +1,20 @@ package com.simplemobiletools.camera.activities; import android.Manifest; +import android.content.ActivityNotFoundException; +import android.content.ContentResolver; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Matrix; import android.hardware.Camera; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -41,7 +47,8 @@ import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -public class MainActivity extends AppCompatActivity implements SensorEventListener, PreviewListener, PhotoProcessor.MediaSavedListener { +public class MainActivity extends AppCompatActivity + implements SensorEventListener, PreviewListener, PhotoProcessor.MediaSavedListener, MediaScannerConnection.OnScanCompletedListener { @BindView(R.id.viewHolder) RelativeLayout mViewHolder; @BindView(R.id.toggle_camera) ImageView mToggleCameraBtn; @BindView(R.id.toggle_flash) ImageView mToggleFlashBtn; @@ -49,13 +56,16 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen @BindView(R.id.shutter) ImageView mShutterBtn; @BindView(R.id.video_rec_curr_timer) TextView mRecCurrTimer; @BindView(R.id.about) View mAboutBtn; + @BindView(R.id.last_photo_video_preview) ImageView mLastPhotoVideoPreview; + private static final String TAG = MainActivity.class.getSimpleName(); private static final int CAMERA_STORAGE_PERMISSION = 1; private static final int AUDIO_PERMISSION = 2; private static SensorManager mSensorManager; private static Preview mPreview; private static Handler mTimerHandler; + private static Uri mPreviewUri; private static boolean mIsFlashEnabled; private static boolean mIsInPhotoMode; @@ -153,6 +163,7 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); mIsInPhotoMode = true; mTimerHandler = new Handler(); + setupPreviewImage(true); } private boolean hasCameraAndStoragePermission() { @@ -209,6 +220,25 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen } } + @OnClick(R.id.last_photo_video_preview) + public void showLastMediaPreview() { + if (mPreviewUri == null) + return; + + try { + final String REVIEW_ACTION = "com.android.camera.action.REVIEW"; + Intent intent = new Intent(REVIEW_ACTION, mPreviewUri); + this.startActivity(intent); + } catch (ActivityNotFoundException e) { + Intent intent = new Intent(Intent.ACTION_VIEW, mPreviewUri); + if (intent.resolveActivity(getPackageManager()) != null) { + startActivity(intent); + } else { + Utils.showToast(getApplicationContext(), R.string.no_gallery_app_available); + } + } + } + @OnClick(R.id.toggle_flash) public void toggleFlash() { if (!checkCameraAvailable()) { @@ -304,7 +334,7 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen if (mIsInPhotoMode) { initPhotoButtons(); } else { - initVideoButtons(); + tryInitVideoButtons(); } } @@ -313,11 +343,12 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen mTogglePhotoVideoBtn.setImageDrawable(res.getDrawable(R.mipmap.videocam)); mShutterBtn.setImageDrawable(res.getDrawable(R.mipmap.camera)); mPreview.initPhotoMode(); + setupPreviewImage(true); } - private void initVideoButtons() { + private void tryInitVideoButtons() { if (mPreview.initRecorder()) { - setupVideoIcons(); + initVideoButtons(); } else { if (!mIsVideoCaptureIntent) { Utils.showToast(getApplicationContext(), R.string.video_mode_error); @@ -325,12 +356,88 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen } } - private void setupVideoIcons() { + private void initVideoButtons() { final Resources res = getResources(); mTogglePhotoVideoBtn.setImageDrawable(res.getDrawable(R.mipmap.photo)); mToggleCameraBtn.setVisibility(View.VISIBLE); mShutterBtn.setImageDrawable(res.getDrawable(R.mipmap.video_rec)); checkFlash(); + setupPreviewImage(false); + } + + private void setupPreviewImage(boolean isPhoto) { + final Uri uri = (isPhoto) ? MediaStore.Images.Media.EXTERNAL_CONTENT_URI : MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + final long lastMediaId = getLastMediaId(uri); + if (lastMediaId == 0) { + return; + } + final ContentResolver cr = getContentResolver(); + mPreviewUri = Uri.withAppendedPath(uri, String.valueOf(lastMediaId)); + Bitmap tmb; + + if (isPhoto) { + tmb = MediaStore.Images.Thumbnails.getThumbnail(cr, lastMediaId, MediaStore.Images.Thumbnails.MICRO_KIND, null); + final int rotationDegrees = getImageRotation(); + tmb = rotateThumbnail(tmb, rotationDegrees); + } else { + tmb = MediaStore.Video.Thumbnails.getThumbnail(cr, lastMediaId, MediaStore.Video.Thumbnails.MICRO_KIND, null); + } + + setPreviewImage(tmb); + } + + private int getImageRotation() { + final String[] projection = {MediaStore.Images.ImageColumns.ORIENTATION}; + Cursor cursor = null; + try { + cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + final int orientationIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.ORIENTATION); + return cursor.getInt(orientationIndex); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return 0; + } + + private Bitmap rotateThumbnail(Bitmap tmb, int degrees) { + if (degrees == 0) + return tmb; + + final Matrix matrix = new Matrix(); + matrix.setRotate(degrees, tmb.getWidth() / 2, tmb.getHeight() / 2); + return Bitmap.createBitmap(tmb, 0, 0, tmb.getWidth(), tmb.getHeight(), matrix, true); + } + + private void setPreviewImage(final Bitmap bmp) { + if (bmp != null) { + mLastPhotoVideoPreview.post(new Runnable() { + @Override + public void run() { + mLastPhotoVideoPreview.setImageBitmap(bmp); + } + }); + } + } + + private long getLastMediaId(Uri uri) { + final String[] projection = {MediaStore.Images.ImageColumns._ID}; + Cursor cursor = null; + try { + cursor = getContentResolver().query(uri, projection, null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC"); + if (cursor != null && cursor.moveToFirst()) { + final int idIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID); + return cursor.getLong(idIndex); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return 0; } private void hideNavigationBarIcons() { @@ -364,6 +471,7 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen super.onResume(); if (hasCameraAndStoragePermission()) { resumeCameraItems(); + setupPreviewImage(mIsInPhotoMode); if (mIsVideoCaptureIntent && mIsInPhotoMode) { togglePhotoVideo(); @@ -387,7 +495,7 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen } if (!mIsInPhotoMode) { - setupVideoIcons(); + initVideoButtons(); } } else { Utils.showToast(getApplicationContext(), R.string.camera_switch_error); @@ -461,6 +569,7 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen @Override public void videoSaved(Uri uri) { + setupPreviewImage(mIsInPhotoMode); if (mIsVideoCaptureIntent) { final Intent intent = new Intent(); intent.setData(uri); @@ -471,7 +580,10 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen } @Override - public void mediaSaved() { + public void mediaSaved(String path) { + final String[] paths = {path}; + MediaScannerConnection.scanFile(getApplicationContext(), paths, null, this); + if (mIsImageCaptureIntent) { setResult(RESULT_OK); finish(); @@ -485,4 +597,9 @@ public class MainActivity extends AppCompatActivity implements SensorEventListen if (mPreview != null) mPreview.releaseCamera(); } + + @Override + public void onScanCompleted(String path, Uri uri) { + setupPreviewImage(mIsInPhotoMode); + } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c2208799..26bbd1c9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -16,7 +16,8 @@ android:layout_width="@dimen/icon_size" android:layout_height="@dimen/icon_size" android:layout_alignParentRight="true" - android:layout_margin="@dimen/side_icon_padding" + android:layout_marginRight="@dimen/side_icon_padding" + android:layout_marginTop="@dimen/side_icon_padding" android:padding="@dimen/side_icon_padding" android:src="@mipmap/about"/> @@ -30,6 +31,15 @@ android:padding="@dimen/side_icon_padding" android:src="@mipmap/videocam"/> + + - 12dp 64dp 12dp 12dp diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 30651896..d6ac605c 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -2,6 +2,7 @@ 16dp 32dp 12dp + 8dp 56dp 8dp 8dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 56c943bd..915daf06 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ Switching camera failed Not much to do without accessing your camera and storage We need the audio permission for recording videos + No gallery app available Settings