Java tutorial
/* Copyright Statement: * * This software/firmware and related documentation ("MediaTek Software") are * protected under relevant copyright laws. The information contained herein is * confidential and proprietary to MediaTek Inc. and/or its licensors. Without * the prior written permission of MediaTek inc. and/or its licensors, any * reproduction, modification, use or disclosure of MediaTek Software, and * information contained herein, in whole or in part, shall be strictly * prohibited. * * MediaTek Inc. (C) 2014. All rights reserved. * * BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES * THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE") * RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER * ON AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL * WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR * NONINFRINGEMENT. NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH * RESPECT TO THE SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, * INCORPORATED IN, OR SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES * TO LOOK ONLY TO SUCH THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. * RECEIVER EXPRESSLY ACKNOWLEDGES THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO * OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES CONTAINED IN MEDIATEK * SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE * RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR * STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S * ENTIRE AND CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE * RELEASED HEREUNDER WILL BE, AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE * MEDIATEK SOFTWARE AT ISSUE, OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE * CHARGE PAID BY RECEIVER TO MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE. * * The following software/firmware and/or related documentation ("MediaTek * Software") have been modified by MediaTek Inc. All revisions are subject to * any receiver's applicable license agreements with MediaTek Inc. */ package com.android.camera.manager; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.graphics.Rect; import android.graphics.YuvImage; import android.media.CameraProfile; import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.SystemProperties; import android.support.v4.content.LocalBroadcastManager; import android.view.View; import android.view.View.OnClickListener; import com.android.camera.CameraActivity; import com.android.camera.FileSaver; import com.android.camera.Log; import com.android.camera.R; import com.android.camera.SaveRequest; import com.android.camera.Storage; import com.android.camera.Thumbnail; import com.android.camera.Util; import com.android.camera.ui.RotateImageView; import com.mediatek.camera.util.CameraAnimation; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; //TODO: we should do decoupling to move it into mediatek/ui public class ThumbnailViewManager extends ViewManager implements OnClickListener, FileSaver.FileSaverListener, CameraActivity.Resumable { private static final String TAG = "ThumbnailViewManager"; private static final int MSG_SAVE_THUMBNAIL = 0; private static final int MSG_UPDATE_THUMBNAIL = 1; private static final int MSG_SAVE_THUMBNAIL_WITH_YUV = 2; private static final int MSG_RELEASE_URI = 3; // TODO:dependency with Google packages,should change the code when do // decoupling // An image view showing the last captured picture thumbnail. private RotateImageView mThumbnailView; public RotateImageView mPreviewThumb; private SaveRequest mCurrentSaveRequest; private Thumbnail mThumbnail; // private AsyncTask<Void, Void, Thumbnail> mLoadThumbnailTask; private WorkerHandler mWorkerHandler; private CameraAnimation mCameraAnimation; private Object mLock = new Object(); private long mRefreshInterval = 0; private long mLastRefreshTime; private boolean mIsSavingThumbnail; private boolean mIsUpdatingThumbnail; // add for update thumbnail with yuv data from post view callback. private byte[] mYuvData; private int mYuvCount; private int mYuvWidth; private int mYuvHeight; private int mYuvOrientation; private int mYuvImageFormat; //this interface just used for when animation end,will call updatethumbnail view public interface AnimationEndListener { void onAnianmationEnd(); } public AnimationEndListener mListener = new AnimationEndListener() { @Override public void onAnianmationEnd() { Log.i(TAG, "[onAnianmationEnd]"); mIsUpdatingThumbnail = false; updateThumbnailView(); } }; public ThumbnailViewManager(CameraActivity context) { super(context); Log.d(TAG, "[ThumbnailViewManager]new..."); setFileter(false); context.addResumable(this); mCameraAnimation = new CameraAnimation(); } @Override public void begin() { Log.i(TAG, "[begin]..."); if (mWorkerHandler == null) { HandlerThread t = new HandlerThread("thumbnail-creation-thread"); t.start(); mWorkerHandler = new WorkerHandler(t.getLooper()); } } @Override public void resume() { Log.i(TAG, "[resume]..."); } @Override public void pause() { Log.i(TAG, "[pause]..."); mWorkerHandler.sendEmptyMessage(MSG_RELEASE_URI); } @Override public void setEnabled(boolean enabled) { Log.d(TAG, "[setEnabled]enabled = " + enabled); super.setEnabled(enabled); if (mThumbnailView != null) { mThumbnailView.setEnabled(enabled); mThumbnailView.setClickable(enabled); } } @Override public void finish() { Log.i(TAG, "[finish]..."); if (mWorkerHandler != null) { mWorkerHandler.getLooper().quit(); } } @Override protected View getView() { View view = inflate(R.layout.thumbnail); mThumbnailView = (RotateImageView) view.findViewById(R.id.thumbnail); mThumbnailView.setOnClickListener(this); mPreviewThumb = (RotateImageView) view.findViewById(R.id.preview_thumb); return view; } @Override protected void onRefresh() { Log.i(TAG, "[onRefresh]..."); updateThumbnailView(); } @Override public void onFileSaved(SaveRequest request) { Log.d(TAG, "[onFileSaved]... mYuvCount = " + mYuvCount); if (request.isIgnoreThumbnail()) { return; } // If current URI is not valid, don't create thumbnail. mCurrentSaveRequest = request; if (mYuvCount == 0 && request.getUri() != null) { Log.d(TAG, "[onFileSaved],send MSG_SAVE_THUMBNAIL."); cancelLoadThumbnail(); mWorkerHandler.removeMessages(MSG_SAVE_THUMBNAIL); mWorkerHandler.sendEmptyMessage(MSG_SAVE_THUMBNAIL); } if (mYuvCount > 0) { --mYuvCount; } } @Override public void onClick(View v) { if (getContext().isCameraIdle() && mThumbnail != null && getThumbnailUri() != null) { Log.d(TAG, "[onClick]call gotoGallery."); getContext().gotoGallery(); } } public void forceUpdate() { Log.d(TAG, "[forceUpdate]..."); // when MediaScanner Scan done, we should get thumbnail form Media Store getLastThumbnailUncached(); } public Uri getThumbnailUri() { Uri uri = mThumbnail.getUri(); if (uri == null && mCurrentSaveRequest != null && mCurrentSaveRequest.getUri() != null) { uri = mCurrentSaveRequest.getUri(); } Log.d(TAG, "getThumbnailUri = " + uri); return uri; } public String getThumbnailMimeType() { if (mThumbnail != null && mThumbnail.getFilePath() != null) { return getMimeType(mThumbnail.getFilePath()); } else if (mCurrentSaveRequest != null && mCurrentSaveRequest.getUri() != null) { return getMimeType(mCurrentSaveRequest.getFilePath()); } else { Log.w(TAG, "[getThumbnailMimeType], null"); return null; } } private String getMimeType(String filePath) { MediaMetadataRetriever retriever = new MediaMetadataRetriever(); String mime = "image/jpeg"; if (filePath != null) { try { retriever.setDataSource(filePath); mime = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE); } catch (IllegalStateException e) { return mime; } catch (IllegalArgumentException e) { return mime; } catch (RuntimeException e) { return mime; } } Log.d(TAG, "[getMimeType] mime = " + mime); return mime; } /** * the interface to update thumbnail view, if the data is YUV from post view * callback. * @param yuvData the YUV data from post view callback. * @param yuvWidth the width of YUV image. * @param yuvHeight the height of YUV image. * @param orientation the phone orientation set to native. * @param imageFormat the image format of YUV image. */ public void updateThumbnailViewWithYuv(byte[] yuvData, int yuvWidth, int yuvHeight, int orientation, int imageFormat) { if (isNeedDumpYuv()) { dumpYuv("/sdcard/postView.yuv", yuvData); } ++mYuvCount; Log.d(TAG, "[updateThumbnailViewWithYuv] yuvData = " + yuvData + ", yuvWidth = " + yuvWidth + ", yuvHeight = " + yuvHeight + ", orientation = " + orientation + ", imageFormat = " + imageFormat + ", mYuvCount = " + mYuvCount); mYuvData = yuvData; mYuvWidth = yuvWidth; mYuvHeight = yuvHeight; mYuvOrientation = orientation; mYuvImageFormat = imageFormat; cancelLoadThumbnail(); mWorkerHandler.removeMessages(MSG_SAVE_THUMBNAIL_WITH_YUV); mWorkerHandler.sendEmptyMessage(MSG_SAVE_THUMBNAIL_WITH_YUV); } public void addFileSaver(FileSaver saver) { if (saver != null) { saver.addListener(this); } } public void removeFileSaver(FileSaver saver) { if (saver != null) { saver.removeListener(this); } } public void setRefreshInterval(int ms) { mRefreshInterval = ms; mLastRefreshTime = System.currentTimeMillis(); } public void updateThumbnailView() { Log.i(TAG, "[updateThumbnailView]this = " + this + ", mIsUpdatingThumbnail = " + mIsUpdatingThumbnail); if (mThumbnailView != null && !mIsUpdatingThumbnail) { if (super.isShowing()) { Log.d(TAG, "[updateThumbnailView]showing is true"); if (mThumbnail != null && mThumbnail.getBitmap() != null) { // here set bitmap null to avoid show last thumbnail in a // moment. Log.d(TAG, "[updateThumbnailView]showing is true,set VISIBLE."); mThumbnailView.setBitmap(null); mThumbnailView.setBitmap(mThumbnail.getBitmap()); mThumbnailView.setVisibility(View.VISIBLE); } else { Log.d(TAG, "[updateThumbnailView]thumbnail is null,set INVISIBLE!"); mThumbnailView.setBitmap(null); mThumbnailView.setVisibility(View.INVISIBLE); } } else { Log.d(TAG, "[updateThumbnailView]showing is false,set INVISIBLE."); mThumbnailView.setVisibility(View.INVISIBLE); } } }; private class LoadThumbnailTask extends AsyncTask<Void, Void, Thumbnail> { public LoadThumbnailTask() { } @Override protected Thumbnail doInBackground(Void... params) { Log.d(TAG, "[doInBackground]begin."); // Load the thumbnail from the file. try { ContentResolver resolver = getContext().getContentResolver(); Thumbnail t = null; if (isCancelled()) { Log.w(TAG, "[doInBackground]task is cancel,return."); return null; } if (t == null && Storage.isStorageReady()) { Thumbnail result[] = new Thumbnail[1]; // Load the thumbnail from the media provider. int code = Thumbnail.getLastThumbnailFromContentResolver(resolver, result, mThumbnail); Log.d(TAG, "getLastThumbnailFromContentResolver code = " + code); switch (code) { case Thumbnail.THUMBNAIL_FOUND: return result[0]; case Thumbnail.THUMBNAIL_NOT_FOUND: return null; case Thumbnail.THUMBNAIL_DELETED: // in secure camera, if getContext().getSecureAlbumCount() <= 0, // should continuous to do onPostExecute(). if (getContext().isSecureCamera() && getContext().getSecureAlbumCount() <= 0) { return null; } cancel(true); return null; default: return null; } } return t; } catch (Exception ex) { ex.printStackTrace(); return null; } } @Override protected void onPostExecute(Thumbnail thumbnail) { Log.d(TAG, "[onPostExecute]isCancelled()=" + isCancelled()); if (isCancelled()) { return; } // in secure camera, if getContext().getMediaItemCount() <= 0, // there is no need to get thumbnail and should invisible thumbnail // view if (getContext().isSecureCamera() && getContext().getSecureAlbumCount() <= 0) { mThumbnail = null; } else { mThumbnail = thumbnail; } updateThumbnailView(); } } private void getLastThumbnailUncached() { Log.d(TAG, "[getLastThumbnailUncached]..."); cancelLoadThumbnail(); synchronized (mLock) { mLoadThumbnailTask = new LoadThumbnailTask().execute(); } } private void sendUpdateThumbnail() { Log.d(TAG, "[sendUpdateThumbnail]..."); mIsUpdatingThumbnail = true; mMainHandler.removeMessages(MSG_UPDATE_THUMBNAIL); Message msg = mMainHandler.obtainMessage(MSG_UPDATE_THUMBNAIL, mThumbnail); msg.sendToTarget(); } private class WorkerHandler extends Handler { public WorkerHandler(Looper looper) { super(looper); Log.i(TAG, "[WorkerHandler]new..."); } @Override public void handleMessage(Message msg) { Log.i(TAG, "[handleMessage]WorkerHandler,msg.what = " + msg.what); long now = System.currentTimeMillis(); switch (msg.what) { case MSG_SAVE_THUMBNAIL: mIsSavingThumbnail = true; SaveRequest curRequest = mCurrentSaveRequest; // M: initialize the ThumbnaiView to create thumbnail when // camera // launched by 3rd apps.@{ if (mThumbnailView == null) { getView(); } // @} if (mThumbnailView != null) { if (mRefreshInterval != 0 && (now - mLastRefreshTime < mRefreshInterval)) { Log.d(TAG, "[handleMessage]WorkerHandler, sendEmptyMessageDelayed."); long delay = mRefreshInterval - (now - mLastRefreshTime); sendEmptyMessageDelayed(MSG_SAVE_THUMBNAIL, delay); } else { mLastRefreshTime = now; int thumbnailWidth = mThumbnailView.getLayoutParams().width; Thumbnail thumb = curRequest.createThumbnail(thumbnailWidth); if (thumb != null) { // just update when thumbnail valid mThumbnail = thumb; } else { Log.w(TAG, "[handleMessage]WorkerHandler,thumb is null!"); } sendUpdateThumbnail(); } } mIsSavingThumbnail = false; break; case MSG_SAVE_THUMBNAIL_WITH_YUV: mIsSavingThumbnail = true; // M: initialize the ThumbnaiView to create thumbnail when // camera // launched by 3rd apps.@{ if (mThumbnailView == null) { getView(); } // @} if (mThumbnailView != null) { int thumbnailWidth = mThumbnailView.getLayoutParams().width; Thumbnail thumb = createThumbnailWithYuv(mYuvData, thumbnailWidth, mYuvWidth, mYuvHeight, mYuvOrientation, mYuvImageFormat); if (thumb != null) { // just update when thumbnail valid mThumbnail = thumb; sendUpdateThumbnail(); } else { Log.w(TAG, "[handleMessage]WorkerHandler,thumb is null!"); } } mIsSavingThumbnail = false; break; case MSG_RELEASE_URI: if (mCurrentSaveRequest != null) { mCurrentSaveRequest.releaseUri(); } break; default: break; } } } private void cancelLoadThumbnail() { synchronized (mLock) { if (mLoadThumbnailTask != null) { Log.d(TAG, "[cancelLoadThumbnail]..."); mLoadThumbnailTask.cancel(true); mLoadThumbnailTask = null; } } } private Handler mMainHandler = new Handler() { public void handleMessage(Message msg) { if (mThumbnail == null) { Log.w(TAG, "[handleMessage]mMainHandler,mThumbnail is null,return!"); return; } Log.i(TAG, "[handleMessage]mMainHandler,msg.what = " + msg.what); switch (msg.what) { case MSG_UPDATE_THUMBNAIL: // here set bitmap null to avoid show last thumbnail in a // moment. // M:Add for CMCC capture performance test case Log.i(TAG, "[CMCC Performance test][Camera][Camera] camera capture end [" + System.currentTimeMillis() + "]"); Log.d(TAG, "[handleMessage]doCaptureAnimation."); mPreviewThumb.setBitmap(null); mPreviewThumb.setBitmap(mThumbnail.getBitmap()); mCameraAnimation.doCaptureAnimation(mPreviewThumb, getContext(), mListener); break; default: break; } } }; private Thumbnail createThumbnailWithYuv(byte[] yuvData, int thumbnailWidth, int yuvWidth, int yuvHeight, int orientation, int imageFormat) { Thumbnail thumb = null; if (yuvData != null) { Log.d(TAG, "[createThumbnailWithYuv]..."); // Create a thumbnail whose width is equal or bigger than // that of the thumbnail view. int ratio = (int) Math.ceil((double) yuvWidth / thumbnailWidth); int inSampleSize = Integer.highestOneBit(ratio); thumb = Thumbnail.createThumbnail(covertYuvDataToJpeg(yuvData, yuvWidth, yuvHeight, imageFormat), orientation, inSampleSize, null, null); Log.i(TAG, "[createThumbnailWithYuv] end"); } return thumb; } // Encode YUV to jpeg, and crop it private byte[] covertYuvDataToJpeg(byte[] data, int yuvWidth, int yuvHeight, int imageFormat) { byte[] jpeg; Rect rect = new Rect(0, 0, yuvWidth, yuvHeight); // TODO: the yuv data from native must be NV21 or YUY2. YuvImage yuvImg = new YuvImage(data, imageFormat, yuvWidth, yuvHeight, null); ByteArrayOutputStream outputstream = new ByteArrayOutputStream(); int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(CameraProfile.QUALITY_HIGH); yuvImg.compressToJpeg(rect, jpegQuality, outputstream); jpeg = outputstream.toByteArray(); return jpeg; } private boolean isNeedDumpYuv() { boolean enable = SystemProperties.getInt("debug.thumbnailFromYuv.enable", 0) == 1 ? true : false; Log.d(TAG, "[isNeedDumpYuv] return :" + enable); return enable; } private void dumpYuv(String filePath, byte[] data) { FileOutputStream out = null; try { Log.d(TAG, "[dumpYuv] begin"); out = new FileOutputStream(filePath); out.write(data); out.close(); } catch (IOException e) { Log.e(TAG, "[dumpYuv]Failed to write image,ex:", e); } finally { if (out != null) { try { out.close(); } catch (IOException e) { Log.e(TAG, "[dumpYuv]IOException:", e); } } } Log.d(TAG, "[dumpYuv] end"); } }