/*Created by Spreadst for freeze_display*/ package com.android.camera.ui; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import com.android.camera.Exif; import com.android.camera2.R; import com.android.camera.RotateDialogController; import com.android.camera.Thumbnail; import com.android.camera.util.CameraUtil; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Handler; import android.os.Message; import android.provider.MediaStore.Images.ImageColumns; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; import android.util.*; public class FreezeFrameDisplayView extends RelativeLayout { private static final String TAG = "CAM_CameraFreezeFrameDisplayView"; public static interface ProxyFreezeFrameClick { // The method runs in main thread public void proxyDoneClicked(); // The method runs in main thread public void proxyFinishDeleted(boolean deleted); // The method runs in main thread public void proxyRestartViews(); // SPRD: bug 251198 public void proxyRetakeClicked(); } // String resource private final int VAL_NOTICE_FAILED = R.string.notice_progress_text_failed; private final int VAL_NOTICE_LOADING = R.string.notice_progress_text_loading; private Bitmap mResource; // hold bitmap object private ImageView mImage; // R.id.image_freeze_frame_display private TextView mNoticeView; // R.id.tv_progress_notice private ProgressBar mProgressBar; // R.id.pb_progress_notice private FrameLayout mProgressPanel; // R.id.fl_progress_panel private RotateLayout mRotatableView;// R.id.view_rotate_freeze_frame_display private RelativeLayout mRelativeLayoutView;// R.id.view_rotate_freeze_frame_display private RotateImageView mDone; // R.id.btn_freeze_frame_done private RotateImageView mCancel; // R.id.btn_feeze_frame_cancel private ControlOnClickListener mClickListener; // SPRD: bug 251198 private View mReviewRetakeButton; private ViewHandler sHandler; // main | work thread communicate private AsyncLoadResource mLoadTask;// load resource task in work thread private AsyncDeleteResource mDeleteTask; // delete resource taks in work thread // notice camera activity private ProxyFreezeFrameClick mListener; //SPRD: bug 251198 private byte [] mJpagByteArry; private boolean mIsImageCaptureIntent; private boolean mIsAutoCapture = false; // default construct public FreezeFrameDisplayView(Context ctx, AttributeSet attrs) { super(ctx, attrs); // the view default visibility is gone setVisibility(View.GONE); // initialize objects mLoadTask = new AsyncLoadResource(); mDeleteTask = new AsyncDeleteResource(); sHandler = new ViewHandler(); } @Override protected void onFinishInflate() { // initialize control panel mClickListener = new ControlOnClickListener(); mDone = (RotateImageView) findViewById(R.id.btn_freeze_frame_done); mCancel = (RotateImageView) findViewById(R.id.btn_feeze_frame_cancel); // SPRD: bug 251198 mReviewRetakeButton = findViewById(R.id.btn_feeze_frame_retake); mDone.setOnClickListener(mClickListener); // default just initialize mDone event // SPRD: bug 251198 mReviewRetakeButton.setOnClickListener(mClickListener); // default just initialize mDone event // initialize image view mImage = (ImageView) findViewById(R.id.image_freeze_frame_display); // initialize progress panel and notice text and progress bar mProgressPanel = (FrameLayout) findViewById(R.id.fl_progress_panel); mProgressBar = (ProgressBar) mProgressPanel.findViewById(R.id.pb_progress_notice); mNoticeView = ((TextView) mProgressPanel.findViewById(R.id.tv_progress_notice)); // initialize rotate view group mRotatableView = (RotateLayout) findViewById(R.id.view_rotate_freeze_frame_display); mRelativeLayoutView = (RelativeLayout) findViewById(R.id.control_freeze_frame_display); mRotatableView.onFinishInflate(); } public void setListener(ProxyFreezeFrameClick listener) { mListener = listener; } public void proxySetRotateDialogController(RotateDialogController controller) { if (mClickListener != null) { mClickListener.setRotateDialogController(controller); } } private void updateViews(boolean into, boolean isFreez) { // anyway, mCancel is disable & haven't listener //@{ SPRD: bug 251198 if(mIsImageCaptureIntent) { CameraUtil.fadeIn(mReviewRetakeButton); } //@} mCancel.setEnabled(false); // mCancel default is disable mDone.setEnabled(false); mCancel.setOnClickListener(null); // mCancel default haven't listener mDone.setOnClickListener(null); Log.d(TAG, "if is freez not dismiss Dialog, isFreez = " + isFreez); if (mClickListener != null && !isFreez) // we must runs "proxyDismissDialog()" mClickListener.proxyDismissDialog(); // Cause dialog visible default value is View.VISIBLE if (into) { mImage.setVisibility(View.GONE); // image view default visibility is gone mNoticeView.setText(VAL_NOTICE_LOADING); // default string is "loading" mProgressBar.setVisibility(View.VISIBLE); // progress bar default visibility is visible mProgressPanel.setVisibility(View.VISIBLE); // progress panel default visibility is visible } else { boolean failed = mLoadTask.failed(); mImage.setVisibility(failed ? View.GONE : View.VISIBLE); mProgressBar.setVisibility(View.GONE); mProgressPanel.setVisibility(failed ? View.VISIBLE : View.GONE); // if load resource failed, then we must reset load thread state to UNKNOW or FINISHED mLoadTask.proxySyncReset(failed ? AsyncLoadResource.VAL_STATE_FINISHED : AsyncLoadResource.VAL_STATE_UNKNOW); } } // Must runs in main thread public void proxyFadeIn(boolean isIntent, boolean isFreez) { mIsImageCaptureIntent = isIntent; updateViews(true, isFreez); setVisibility(View.VISIBLE); } // Must runs in main thread public void proxyFadeOut() { updateViews(false, false); setVisibility(View.GONE); } // Anyway threads public boolean displayed() { return (View.VISIBLE == getVisibility()); } // Runs in work thread public void runLoadResource(final Uri uri) { mLoadTask.run(uri); } //@{ SPRD: bug 251198 public void runLoadResource(final byte [] jpagByte, boolean auto) { mIsAutoCapture = auto; mLoadTask.run(jpagByte); } //@} private class ControlOnClickListener implements View.OnClickListener { private Runnable sRunnableDone; private int sLastOrientation; private RotateDialogController sRotateDialog; // default construct private ControlOnClickListener() { sRunnableDone = new Runnable() { @Override public void run() { Log.d(TAG, "execute delete picture work thread ... ..."); mListener.proxyRestartViews(); mDeleteTask.execute(); } }; } // new "ControlOnClickListener" class after must runs following method /*package*/ void setRotateDialogController(RotateDialogController controller) { if (controller != null) { sRotateDialog = controller; } } /*package*/ void proxySetOrientation(int orientation, boolean animation) { if (sLastOrientation != orientation) sLastOrientation = orientation; if (sRotateDialog != null) { sRotateDialog.setOrientation(orientation, animation); } } /*package*/ void proxyDismissDialog() { if (sRotateDialog != null) { View v = sRotateDialog.getmDialogRootLayout(); if (v != null) v.setVisibility(View.GONE); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_freeze_frame_done: mListener.proxyDoneClicked(); break; case R.id.btn_feeze_frame_cancel: Log.d(TAG,"on cancle click"); //@{ SPRD: bug 251198 if(mIsImageCaptureIntent) { sRunnableDone.run(); break; } //@} if (sRotateDialog != null) { Context ctx = getContext(); sRotateDialog.showAlertDialog( null, ctx.getString(R.string.dialog_freeze_frame_confirm_delete_text), ctx.getString(android.R.string.ok), sRunnableDone, ctx.getString(android.R.string.cancel), null); proxySetOrientation(sLastOrientation, true); } break; //@{ SPRD: bug 251198 case R.id.btn_feeze_frame_retake: if(mIsImageCaptureIntent) { CameraUtil.fadeIn(mReviewRetakeButton); // SPRD: remove the picture when retake a new one mDeleteTask.execute(); mListener.proxyRetakeClicked(); break; } //@} } } } private class AsyncDeleteResource implements Runnable { /*package*/ static final int VAL_STATE_DELETE = 11; // mutex lock object /*package*/ final Object mMutexLock = new Object(); // _data column private String[] mColumns = new String[] { ImageColumns.DATA }; // default construct private AsyncDeleteResource() { } public void execute() { Thread t = new Thread(this); t.start(); } @Override public void run() { synchronized (mMutexLock) { Uri delUri = mLoadTask.syncGetUri(); boolean result = false; Log.d(TAG, "delete picture uri = " + delUri); if (delUri != null) { ContentResolver resolver = getContext().getContentResolver(); // search item from database Cursor cursor = null; String path = null; try { cursor = resolver.query(delUri, mColumns, null, null, null); if (cursor != null && cursor.moveToFirst()) { path = cursor.getString(0); } } catch (Exception e) { Log.d(TAG, "serach data from database failed, PLS ignore. URI = " + delUri, e); } finally { if (cursor != null) { cursor.close(); } } Log.d(TAG, "delete file path = " + path); // delete database data by delUri if (result = (path != null)) { try { resolver.delete(delUri, null, null); } catch (Exception e) { Log.d(TAG, "delete data from database failed, PLS ignore. URI = " + delUri, e); } } // delete file if (result = (path != null)) { File d = new File(path); if (d.exists() && d.isFile() && !d.isHidden()) { result = d.delete(); } } Log.d(TAG, "delete picture success = " + result); } Message msg = sHandler.obtainMessage(VAL_STATE_DELETE, result); sHandler.sendMessage(msg); } } } private class ViewHandler extends Handler { // default construct private ViewHandler() { } @Override public void handleMessage(Message msg) { super.handleMessage(msg); int what = msg.what; switch (what) { case AsyncDeleteResource.VAL_STATE_DELETE: synchronized (mDeleteTask.mMutexLock) { boolean success = ((Boolean) msg.obj); mListener.proxyFinishDeleted(success); break; } case AsyncLoadResource.VAL_STATE_FAILED: synchronized (mLoadTask.mStateLock) { if (mImage != null) mImage.setImageBitmap(null); if (mResource != null && !mResource.isRecycled()) { mResource.recycle(); mResource = null; } if (mNoticeView != null) mNoticeView.setText(VAL_NOTICE_FAILED); if (mCancel != null) { mCancel.setEnabled(false); mCancel.setOnClickListener(null); } if (mDone != null) { mDone.setEnabled(true); mDone.setOnClickListener(mClickListener); } resetNotice(false); // load failed break; } case AsyncLoadResource.VAL_STATE_UNKNOW: case AsyncLoadResource.VAL_STATE_FINISHED: synchronized (mLoadTask.mStateLock) { if (mResource != null && mImage != null) { mImage.setImageBitmap(mResource); if (mCancel != null) { mCancel.setEnabled(true); mCancel.setOnClickListener(mClickListener); } if (mDone != null) { mDone.setEnabled(true); mDone.setOnClickListener(mClickListener); } } resetNotice(true); // load success break; } } } private void resetNotice(boolean success) { if (View.VISIBLE == getVisibility()) { if (success) { mProgressPanel.setVisibility(View.GONE); mImage.setVisibility(View.VISIBLE); if (mIsAutoCapture) { mListener.proxyDoneClicked(); } } else { mImage.setVisibility(View.GONE); mProgressBar.setVisibility(View.GONE); mProgressPanel.setVisibility(View.VISIBLE); } } } } // use work thread loading URI resource convert to bitmap private class AsyncLoadResource implements Runnable { // URI from main thread private Uri mUri; // work thread state private int mState; // mutex lock object /*package*/ final Object mMutexLock = new Object(); // mState lock /*package*/ final Object mStateLock = new Object(); // default bitmap compress ratio private final int mCompressionRatio = 4; private final int mBuffSize = 1024; /*package*/ static final int VAL_STATE_UNKNOW = 0; /*package*/ static final int VAL_STATE_FAILED = 1; /*package*/ static final int VAL_STATE_RUNNING = 2; /*package*/ static final int VAL_STATE_FINISHED = 3; // default construct private AsyncLoadResource() { } // Must runs in main thread /*package*/ void proxySyncReset(int state) { syncReset(state); } /*package*/ Uri syncGetUri() { synchronized (mMutexLock) { return mUri; } } /*package*/ boolean failed() { synchronized (mStateLock) { return (VAL_STATE_FAILED == mState); } } /*package*/ boolean finished() { synchronized (mStateLock) { return (VAL_STATE_FINISHED == mState || VAL_STATE_UNKNOW == mState); } } private void syncReset(int state) { synchronized (mStateLock) { mState = state; } } private void syncNotice() { synchronized (mStateLock) { sHandler.sendEmptyMessage(mState); } } @Override public String toString() { String result = null; synchronized (mStateLock) { switch (mState) { case VAL_STATE_FAILED: result = "failed"; break; case VAL_STATE_RUNNING: result = "running"; break; case VAL_STATE_FINISHED: result = "finished"; break; default: result = "unknow"; } } return result; } // override load run method public void run(Uri uri) { boolean result = finished(); Log.d(TAG, String.format("we can start thread load resource? [%b] state = %s", new Object[] { result, toString() })); if (result) { // SPRD: bug 251198 mJpagByteArry = null; mUri = uri; Thread t = new Thread(this); t.start(); } } //@{ SPRD: bug 251198 public void run(byte [] jpagByte) { boolean bResult = finished(); if(bResult) { mUri = null; mJpagByteArry = jpagByte; Thread t = new Thread(this); t.start(); } } //@} @Override public void run() { synchronized (mMutexLock) { syncReset(VAL_STATE_RUNNING); // starting load resource // convert to byte array from input stream by URI //@{ SPRD: bug 251198 //byte[] dataJpeg = readStream(mUri); byte[] dataJpeg = (mJpagByteArry == null)?readStream(mUri): mJpagByteArry; //@} boolean success = (dataJpeg != null); boolean tryOnce = !success; int orientation = (success ? Exif.getOrientation(dataJpeg) : -1); Bitmap tmpBitmap = null; int compress = mCompressionRatio; // default compression ratio is 16 // if throw OOM error, we need try to once, changed compression ratio to 32 while (success) { try { Log.d(TAG, "decode bitmap by byte array stream, compress = " + compress); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; options.inDither = true; options.inSampleSize = compress; // first loading resource by input stream tmpBitmap = BitmapFactory.decodeByteArray(dataJpeg, 0, dataJpeg.length, options); // rotate bitmap tmpBitmap = Thumbnail.proxyRotateImage(tmpBitmap, orientation); } catch (OutOfMemoryError e) { Log.d(TAG, "convert to bitmap has OOM error, PLS ignore", e); compress *= 2; // current compress ratio is compress x 2 } finally { success = (mCompressionRatio != compress); if (tryOnce) success = false; // try to once finished if (success) tryOnce = true; // set try to once flag } } // load resource failed if (!success) syncReset(VAL_STATE_FAILED); // initialize or recycle bitmap if (tmpBitmap != null) { if (mResource == null) { mResource = tmpBitmap; } else if (mResource != tmpBitmap) { mResource.recycle(); mResource = tmpBitmap; } syncReset(VAL_STATE_FINISHED); // load resource success } syncNotice(); // notice main thread } } private byte[] readStream(Uri uri) { byte[] result = null; if (uri != null) { InputStream input = null; ByteArrayOutputStream output = null; try { byte[] buff = new byte[mBuffSize]; input = getContext().getContentResolver().openInputStream(uri); output = new ByteArrayOutputStream(); int len = 0; while ((len = input.read(buff)) != -1) { output.write(buff, 0, len); } result = output.toByteArray(); } catch (FileNotFoundException e) { Log.e(TAG, "open input stream by uri failed, uri = " + uri, e); } catch (IOException e) { Log.e(TAG, "read input stream failed", e); } finally { CameraUtil.closeSilently(output); CameraUtil.closeSilently(input); } } return result; } } // SPRD: change the orientation of freezeframe view's child view public void updateFreezeChildUi(int orientation) { if(mRotatableView != null && mRelativeLayoutView != null) { switch (orientation) { case 180: mRotatableView.setRotationY(180); mRelativeLayoutView.setRotationY(180); break; case 270: mRotatableView.setRotationX(180); mRelativeLayoutView.setRotationX(180); } } } }