diff options
Diffstat (limited to 'src/com/android/gallery3d/app')
23 files changed, 437 insertions, 1311 deletions
diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java index 88ac028e1..081b87aee 100644 --- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java +++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java @@ -17,22 +17,25 @@ package com.android.gallery3d.app; import android.annotation.TargetApi; -import android.app.Activity; import android.app.AlertDialog; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.res.Configuration; import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; +import android.os.IBinder; import android.view.Window; import android.view.WindowManager; +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.data.BitmapPool; @@ -40,10 +43,10 @@ import com.android.gallery3d.data.DataManager; import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.ui.GLRoot; import com.android.gallery3d.ui.GLRootView; -import com.android.gallery3d.util.ThreadPool; import com.android.gallery3d.util.LightCycleHelper.PanoramaViewHelper; +import com.android.gallery3d.util.ThreadPool; -public class AbstractGalleryActivity extends Activity implements GalleryContext { +public class AbstractGalleryActivity extends SherlockActivity implements GalleryContext { @SuppressWarnings("unused") private static final String TAG = "AbstractGalleryActivity"; private GLRootView mGLRootView; @@ -71,6 +74,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext getWindow().setBackgroundDrawable(null); mPanoramaViewHelper = new PanoramaViewHelper(this); mPanoramaViewHelper.onCreate(); + doBindBatchService(); } @Override @@ -237,6 +241,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext } finally { mGLRootView.unlockRenderThread(); } + doUnbindBatchService(); } @Override @@ -308,4 +313,39 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext return (getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0; } + + private BatchService mBatchService; + private boolean mBatchServiceIsBound = false; + private ServiceConnection mBatchServiceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + mBatchService = ((BatchService.LocalBinder)service).getService(); + } + + public void onServiceDisconnected(ComponentName className) { + mBatchService = null; + } + }; + + private void doBindBatchService() { + bindService(new Intent(this, BatchService.class), mBatchServiceConnection, Context.BIND_AUTO_CREATE); + mBatchServiceIsBound = true; + } + + private void doUnbindBatchService() { + if (mBatchServiceIsBound) { + // Detach our existing connection. + unbindService(mBatchServiceConnection); + mBatchServiceIsBound = false; + } + } + + public ThreadPool getBatchServiceThreadPoolIfAvailable() { + if (mBatchServiceIsBound && mBatchService != null) { + return mBatchService.getThreadPool(); + } else { + // Fall back on the old behavior if for some reason the + // service is not available. + return getThreadPool(); + } + } } diff --git a/src/com/android/gallery3d/app/ActivityState.java b/src/com/android/gallery3d/app/ActivityState.java index cdd91ff4d..b2e39b1cb 100644 --- a/src/com/android/gallery3d/app/ActivityState.java +++ b/src/com/android/gallery3d/app/ActivityState.java @@ -16,24 +16,23 @@ package com.android.gallery3d.app; -import android.app.ActionBar; import android.app.Activity; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.os.BatteryManager; import android.os.Bundle; -import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; +import android.view.HapticFeedbackConstants; import android.view.Window; import android.view.WindowManager; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.anim.StateTransitionAnimation; import com.android.gallery3d.ui.GLView; @@ -62,9 +61,6 @@ abstract public class ActivityState { public Intent resultData; } - protected boolean mHapticsEnabled; - private ContentResolver mContentResolver; - private boolean mDestroyed = false; private boolean mPlugged = false; boolean mIsFinishing = false; @@ -92,7 +88,6 @@ abstract public class ActivityState { void initialize(AbstractGalleryActivity activity, Bundle data) { mActivity = activity; mData = data; - mContentResolver = activity.getAndroidContext().getContentResolver(); } public Bundle getData() { @@ -175,15 +170,20 @@ abstract public class ActivityState { protected void transitionOnNextPause(Class<? extends ActivityState> outgoing, Class<? extends ActivityState> incoming, StateTransitionAnimation.Transition hint) { - if (outgoing == PhotoPage.class && incoming == AlbumPage.class) { + if (outgoing == SinglePhotoPage.class && incoming == AlbumPage.class) { mNextTransition = StateTransitionAnimation.Transition.Outgoing; - } else if (outgoing == AlbumPage.class && incoming == PhotoPage.class) { + } else if (outgoing == AlbumPage.class && incoming == SinglePhotoPage.class) { mNextTransition = StateTransitionAnimation.Transition.PhotoIncoming; } else { mNextTransition = hint; } } + protected void performHapticFeedback(int feedbackConstant) { + mActivity.getWindow().getDecorView().performHapticFeedback(feedbackConstant, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } + protected void onPause() { if (0 != (mFlags & FLAG_SCREEN_ON_WHEN_PLUGGED)) { ((Activity) mActivity).unregisterReceiver(mPowerIntentReceiver); @@ -198,7 +198,7 @@ abstract public class ActivityState { // should only be called by StateManager void resume() { AbstractGalleryActivity activity = mActivity; - ActionBar actionBar = activity.getActionBar(); + ActionBar actionBar = ((SherlockActivity) activity).getSupportActionBar(); if (actionBar != null) { if ((mFlags & FLAG_HIDE_ACTION_BAR) != 0) { actionBar.hide(); @@ -231,13 +231,6 @@ abstract public class ActivityState { activity.registerReceiver(mPowerIntentReceiver, filter); } - try { - mHapticsEnabled = Settings.System.getInt(mContentResolver, - Settings.System.HAPTIC_FEEDBACK_ENABLED) != 0; - } catch (SettingNotFoundException e) { - mHapticsEnabled = false; - } - onResume(); // the transition store should be cleared after resume; @@ -279,6 +272,6 @@ abstract public class ActivityState { } protected MenuInflater getSupportMenuInflater() { - return mActivity.getMenuInflater(); + return ((SherlockActivity) mActivity).getSupportMenuInflater(); } } diff --git a/src/com/android/gallery3d/app/AlbumDataLoader.java b/src/com/android/gallery3d/app/AlbumDataLoader.java index 0ee1b03af..28a822830 100644 --- a/src/com/android/gallery3d/app/AlbumDataLoader.java +++ b/src/com/android/gallery3d/app/AlbumDataLoader.java @@ -120,8 +120,7 @@ public class AlbumDataLoader { public MediaItem get(int index) { if (!isActive(index)) { - throw new IllegalArgumentException(String.format( - "%s not in (%s, %s)", index, mActiveStart, mActiveEnd)); + return mSource.getMediaItem(index, 1).get(0); } return mData[index % mData.length]; } diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java index ee7a107fd..60000903d 100644 --- a/src/com/android/gallery3d/app/AlbumPage.java +++ b/src/com/android/gallery3d/app/AlbumPage.java @@ -24,13 +24,13 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.os.Vibrator; import android.provider.MediaStore; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; +import android.view.HapticFeedbackConstants; import android.widget.Toast; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.DataManager; @@ -57,6 +57,9 @@ import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.util.Future; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.MediaSetUtils; +import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.filtershow.CropExtras; + public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner, SelectionManager.SelectionListener, MediaSet.SyncListener, GalleryActionBar.OnAlbumModeSelectedListener { @@ -89,7 +92,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster private AlbumDataLoader mAlbumDataAdapter; protected SelectionManager mSelectionManager; - private Vibrator mVibrator; private boolean mGetContent; private boolean mShowClusterMenu; @@ -304,10 +306,10 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster startInFilmstrip); data.putBoolean(PhotoPage.KEY_IN_CAMERA_ROLL, mMediaSet.isCameraRoll()); if (startInFilmstrip) { - mActivity.getStateManager().switchState(this, PhotoPage.class, data); + mActivity.getStateManager().switchState(this, FilmstripPage.class, data); } else { mActivity.getStateManager().startStateForResult( - PhotoPage.class, REQUEST_PHOTO, data); + SinglePhotoPage.class, REQUEST_PHOTO, data); } } } @@ -318,11 +320,11 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster if (mData.getString(Gallery.EXTRA_CROP) != null) { // TODO: Handle MtpImagew Uri uri = dm.getContentUri(item.getPath()); - Intent intent = new Intent(CropImage.ACTION_CROP, uri) + Intent intent = new Intent(FilterShowActivity.CROP_ACTION, uri) .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) .putExtras(getData()); if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) { - intent.putExtra(CropImage.KEY_RETURN_DATA, true); + intent.putExtra(CropExtras.KEY_RETURN_DATA, true); } activity.startActivity(intent); activity.finish(); @@ -371,7 +373,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false); mDetailsSource = new MyDetailsSource(); Context context = mActivity.getAndroidContext(); - mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); // Enable auto-select-all for mtp album if (data.getBoolean(KEY_AUTO_SELECT_ALL)) { @@ -379,7 +380,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster } mLaunchedFromPhotoPage = - mActivity.getStateManager().hasStateClass(PhotoPage.class); + mActivity.getStateManager().hasStateClass(FilmstripPage.class); mInCameraApp = data.getBoolean(PhotoPage.KEY_APP_BRIDGE, false); mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { @@ -443,7 +444,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mSelectionManager.leaveSelectionMode(); } mAlbumView.setSlotFilter(null); - + mActionModeHandler.pause(); mAlbumDataAdapter.pause(); mAlbumView.pause(); DetailsHelper.pause(); @@ -456,7 +457,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mSyncTask = null; clearLoadingBit(BIT_LOADING_SYNC); } - mActionModeHandler.pause(); } @Override @@ -465,6 +465,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster if (mAlbumDataAdapter != null) { mAlbumDataAdapter.setLoadingListener(null); } + mActionModeHandler.destroy(); } private void initializeViews() { @@ -662,7 +663,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster switch (mode) { case SelectionManager.ENTER_SELECTION_MODE: { mActionModeHandler.startActionMode(); - if (mHapticsEnabled) mVibrator.vibrate(100); + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); break; } case SelectionManager.LEAVE_SELECTION_MODE: { diff --git a/src/com/android/gallery3d/app/AlbumSetPage.java b/src/com/android/gallery3d/app/AlbumSetPage.java index cae606be1..df68ffbaa 100644 --- a/src/com/android/gallery3d/app/AlbumSetPage.java +++ b/src/com/android/gallery3d/app/AlbumSetPage.java @@ -23,16 +23,16 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.os.Vibrator; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; +import android.view.HapticFeedbackConstants; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.RelativeLayout; import android.widget.Toast; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.DataManager; @@ -92,7 +92,6 @@ public class AlbumSetPage extends ActivityState implements private boolean mShowClusterMenu; private GalleryActionBar mActionBar; private int mSelectedAction; - private Vibrator mVibrator; protected SelectionManager mSelectionManager; private AlbumSetDataLoader mAlbumSetDataAdapter; @@ -275,7 +274,7 @@ public class AlbumSetPage extends ActivityState implements data.putBoolean(PhotoPage.KEY_START_IN_FILMSTRIP, true); data.putBoolean(PhotoPage.KEY_IN_CAMERA_ROLL, targetSet.isCameraRoll()); mActivity.getStateManager().startStateForResult( - PhotoPage.class, AlbumPage.REQUEST_PHOTO, data); + FilmstripPage.class, AlbumPage.REQUEST_PHOTO, data); return; } data.putString(AlbumPage.KEY_MEDIA_PATH, mediaPath); @@ -332,7 +331,6 @@ public class AlbumSetPage extends ActivityState implements mSubtitle = data.getString(AlbumSetPage.KEY_SET_SUBTITLE); mEyePosition = new EyePosition(context, this); mDetailsSource = new MyDetailsSource(); - mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mActionBar = mActivity.getGalleryActionBar(); mSelectedAction = data.getInt(AlbumSetPage.KEY_SELECTED_CLUSTER_TYPE, FilterUtils.CLUSTER_BY_ALBUM); @@ -353,8 +351,9 @@ public class AlbumSetPage extends ActivityState implements @Override public void onDestroy() { - cleanupCameraButton(); super.onDestroy(); + cleanupCameraButton(); + mActionModeHandler.destroy(); } private boolean setupCameraButton() { @@ -439,9 +438,9 @@ public class AlbumSetPage extends ActivityState implements public void onPause() { super.onPause(); mIsActive = false; - mActionModeHandler.pause(); mAlbumSetDataAdapter.pause(); mAlbumSetView.pause(); + mActionModeHandler.pause(); mEyePosition.pause(); DetailsHelper.pause(); // Call disableClusterMenu to avoid receiving callback after paused. @@ -655,7 +654,7 @@ public class AlbumSetPage extends ActivityState implements case SelectionManager.ENTER_SELECTION_MODE: { mActionBar.disableClusterMenu(true); mActionModeHandler.startActionMode(); - if (mHapticsEnabled) mVibrator.vibrate(100); + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); break; } case SelectionManager.LEAVE_SELECTION_MODE: { diff --git a/src/com/android/gallery3d/app/BatchService.java b/src/com/android/gallery3d/app/BatchService.java new file mode 100644 index 000000000..98a1d8215 --- /dev/null +++ b/src/com/android/gallery3d/app/BatchService.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.app; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; + +import com.android.gallery3d.util.ThreadPool; + +public class BatchService extends Service { + + public class LocalBinder extends Binder { + BatchService getService() { + return BatchService.this; + } + } + + private final IBinder mBinder = new LocalBinder(); + private ThreadPool mThreadPool = new ThreadPool(1, 1); + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + public ThreadPool getThreadPool() { + return mThreadPool; + } +} diff --git a/src/com/android/gallery3d/app/CommonControllerOverlay.java b/src/com/android/gallery3d/app/CommonControllerOverlay.java index ab43dada5..089872fa5 100644 --- a/src/com/android/gallery3d/app/CommonControllerOverlay.java +++ b/src/com/android/gallery3d/app/CommonControllerOverlay.java @@ -154,7 +154,7 @@ public abstract class CommonControllerOverlay extends FrameLayout implements @Override public void showEnded() { mState = State.ENDED; - showMainView(mPlayPauseReplayView); + if (mCanReplay) showMainView(mPlayPauseReplayView); } @Override diff --git a/src/com/android/gallery3d/app/CropImage.java b/src/com/android/gallery3d/app/CropImage.java deleted file mode 100644 index 89ca63d44..000000000 --- a/src/com/android/gallery3d/app/CropImage.java +++ /dev/null @@ -1,1040 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.app; - -import android.annotation.TargetApi; -import android.app.ActionBar; -import android.app.ProgressDialog; -import android.app.WallpaperManager; -import android.content.ContentValues; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Bitmap.CompressFormat; -import android.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.RectF; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.Message; -import android.provider.MediaStore; -import android.provider.MediaStore.Images; -import android.util.FloatMath; -import android.view.Menu; -import android.view.MenuItem; -import android.view.Window; -import android.view.WindowManager; -import android.widget.Toast; - -import com.android.camera.Util; -import com.android.gallery3d.R; -import com.android.gallery3d.common.ApiHelper; -import com.android.gallery3d.common.BitmapUtils; -import com.android.gallery3d.common.Utils; -import com.android.gallery3d.data.DataManager; -import com.android.gallery3d.data.LocalImage; -import com.android.gallery3d.data.MediaItem; -import com.android.gallery3d.data.MediaObject; -import com.android.gallery3d.data.Path; -import com.android.gallery3d.exif.ExifData; -import com.android.gallery3d.exif.ExifOutputStream; -import com.android.gallery3d.exif.ExifReader; -import com.android.gallery3d.exif.ExifTag; -import com.android.gallery3d.picasasource.PicasaSource; -import com.android.gallery3d.ui.BitmapScreenNail; -import com.android.gallery3d.ui.BitmapTileProvider; -import com.android.gallery3d.ui.CropView; -import com.android.gallery3d.ui.GLRoot; -import com.android.gallery3d.ui.SynchronizedHandler; -import com.android.gallery3d.ui.TileImageViewAdapter; -import com.android.gallery3d.util.BucketNames; -import com.android.gallery3d.util.Future; -import com.android.gallery3d.util.FutureListener; -import com.android.gallery3d.util.GalleryUtils; -import com.android.gallery3d.util.InterruptableOutputStream; -import com.android.gallery3d.util.ThreadPool.CancelListener; -import com.android.gallery3d.util.ThreadPool.Job; -import com.android.gallery3d.util.ThreadPool.JobContext; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteOrder; -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * The activity can crop specific region of interest from an image. - */ -public class CropImage extends AbstractGalleryActivity { - private static final String TAG = "CropImage"; - public static final String ACTION_CROP = "com.android.camera.action.CROP"; - - private static final int MAX_PIXEL_COUNT = 5 * 1000000; // 5M pixels - private static final int MAX_FILE_INDEX = 1000; - private static final int TILE_SIZE = 512; - private static final int BACKUP_PIXEL_COUNT = 480000; // around 800x600 - - private static final int MSG_LARGE_BITMAP = 1; - private static final int MSG_BITMAP = 2; - private static final int MSG_SAVE_COMPLETE = 3; - private static final int MSG_SHOW_SAVE_ERROR = 4; - private static final int MSG_CANCEL_DIALOG = 5; - - private static final int MAX_BACKUP_IMAGE_SIZE = 320; - private static final int DEFAULT_COMPRESS_QUALITY = 90; - private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss"; - - public static final String KEY_RETURN_DATA = "return-data"; - public static final String KEY_CROPPED_RECT = "cropped-rect"; - public static final String KEY_ASPECT_X = "aspectX"; - public static final String KEY_ASPECT_Y = "aspectY"; - public static final String KEY_SPOTLIGHT_X = "spotlightX"; - public static final String KEY_SPOTLIGHT_Y = "spotlightY"; - public static final String KEY_OUTPUT_X = "outputX"; - public static final String KEY_OUTPUT_Y = "outputY"; - public static final String KEY_SCALE = "scale"; - public static final String KEY_DATA = "data"; - public static final String KEY_SCALE_UP_IF_NEEDED = "scaleUpIfNeeded"; - public static final String KEY_OUTPUT_FORMAT = "outputFormat"; - public static final String KEY_SET_AS_WALLPAPER = "set-as-wallpaper"; - public static final String KEY_NO_FACE_DETECTION = "noFaceDetection"; - public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked"; - - private static final String KEY_STATE = "state"; - - private static final int STATE_INIT = 0; - private static final int STATE_LOADED = 1; - private static final int STATE_SAVING = 2; - - public static final File DOWNLOAD_BUCKET = new File( - Environment.getExternalStorageDirectory(), BucketNames.DOWNLOAD); - - public static final String CROP_ACTION = "com.android.camera.action.CROP"; - - private int mState = STATE_INIT; - - private CropView mCropView; - - private boolean mDoFaceDetection = true; - - private Handler mMainHandler; - - // We keep the following members so that we can free them - - // mBitmap is the unrotated bitmap we pass in to mCropView for detect faces. - // mCropView is responsible for rotating it to the way that it is viewed by users. - private Bitmap mBitmap; - private BitmapTileProvider mBitmapTileProvider; - private BitmapRegionDecoder mRegionDecoder; - private Bitmap mBitmapInIntent; - private boolean mUseRegionDecoder = false; - private BitmapScreenNail mBitmapScreenNail; - - private ProgressDialog mProgressDialog; - private Future<BitmapRegionDecoder> mLoadTask; - private Future<Bitmap> mLoadBitmapTask; - private Future<Intent> mSaveTask; - - private MediaItem mMediaItem; - - @Override - public void onCreate(Bundle bundle) { - super.onCreate(bundle); - requestWindowFeature(Window.FEATURE_ACTION_BAR); - requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); - - // Initialize UI - setContentView(R.layout.cropimage); - mCropView = new CropView(this); - getGLRoot().setContentPane(mCropView); - - ActionBar actionBar = getActionBar(); - int displayOptions = ActionBar.DISPLAY_HOME_AS_UP - | ActionBar.DISPLAY_SHOW_TITLE; - actionBar.setDisplayOptions(displayOptions, displayOptions); - - Bundle extra = getIntent().getExtras(); - if (extra != null) { - if (extra.getBoolean(KEY_SET_AS_WALLPAPER, false)) { - actionBar.setTitle(getString(R.string.set_wallpaper)); - } - if (extra.getBoolean(KEY_SHOW_WHEN_LOCKED, false)) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - } - } - - mMainHandler = new SynchronizedHandler(getGLRoot()) { - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MSG_LARGE_BITMAP: { - dismissProgressDialogIfShown(); - onBitmapRegionDecoderAvailable((BitmapRegionDecoder) message.obj); - break; - } - case MSG_BITMAP: { - dismissProgressDialogIfShown(); - onBitmapAvailable((Bitmap) message.obj); - break; - } - case MSG_SHOW_SAVE_ERROR: { - dismissProgressDialogIfShown(); - setResult(RESULT_CANCELED); - Toast.makeText(CropImage.this, - CropImage.this.getString(R.string.save_error), - Toast.LENGTH_LONG).show(); - finish(); - } - case MSG_SAVE_COMPLETE: { - dismissProgressDialogIfShown(); - setResult(RESULT_OK, (Intent) message.obj); - finish(); - break; - } - case MSG_CANCEL_DIALOG: { - setResult(RESULT_CANCELED); - finish(); - break; - } - } - } - }; - - setCropParameters(); - } - - @Override - protected void onSaveInstanceState(Bundle saveState) { - saveState.putInt(KEY_STATE, mState); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.crop, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: { - finish(); - break; - } - case R.id.cancel: { - setResult(RESULT_CANCELED); - finish(); - break; - } - case R.id.save: { - onSaveClicked(); - break; - } - } - return true; - } - - @Override - public void onBackPressed() { - finish(); - } - - private class SaveOutput implements Job<Intent> { - private final RectF mCropRect; - - public SaveOutput(RectF cropRect) { - mCropRect = cropRect; - } - - @Override - public Intent run(JobContext jc) { - RectF cropRect = mCropRect; - Bundle extra = getIntent().getExtras(); - - Rect rect = new Rect( - Math.round(cropRect.left), Math.round(cropRect.top), - Math.round(cropRect.right), Math.round(cropRect.bottom)); - - Intent result = new Intent(); - result.putExtra(KEY_CROPPED_RECT, rect); - Bitmap cropped = null; - boolean outputted = false; - if (extra != null) { - Uri uri = (Uri) extra.getParcelable(MediaStore.EXTRA_OUTPUT); - if (uri != null) { - if (jc.isCancelled()) return null; - outputted = true; - cropped = getCroppedImage(rect); - if (!saveBitmapToUri(jc, cropped, uri)) return null; - } - if (extra.getBoolean(KEY_RETURN_DATA, false)) { - if (jc.isCancelled()) return null; - outputted = true; - if (cropped == null) cropped = getCroppedImage(rect); - result.putExtra(KEY_DATA, cropped); - } - if (extra.getBoolean(KEY_SET_AS_WALLPAPER, false)) { - if (jc.isCancelled()) return null; - outputted = true; - if (cropped == null) cropped = getCroppedImage(rect); - if (!setAsWallpaper(jc, cropped)) return null; - } - } - if (!outputted) { - if (jc.isCancelled()) return null; - if (cropped == null) cropped = getCroppedImage(rect); - Uri data = saveToMediaProvider(jc, cropped); - if (data != null) result.setData(data); - } - return result; - } - } - - public static String determineCompressFormat(MediaObject obj) { - String compressFormat = "JPEG"; - if (obj instanceof MediaItem) { - String mime = ((MediaItem) obj).getMimeType(); - if (mime.contains("png") || mime.contains("gif")) { - // Set the compress format to PNG for png and gif images - // because they may contain alpha values. - compressFormat = "PNG"; - } - } - return compressFormat; - } - - private boolean setAsWallpaper(JobContext jc, Bitmap wallpaper) { - try { - WallpaperManager.getInstance(this).setBitmap(wallpaper); - } catch (IOException e) { - Log.w(TAG, "fail to set wall paper", e); - } - return true; - } - - private File saveMedia( - JobContext jc, Bitmap cropped, File directory, String filename, ExifData exifData) { - // Try file-1.jpg, file-2.jpg, ... until we find a filename - // which does not exist yet. - File candidate = null; - String fileExtension = getFileExtension(); - for (int i = 1; i < MAX_FILE_INDEX; ++i) { - candidate = new File(directory, filename + "-" + i + "." - + fileExtension); - try { - if (candidate.createNewFile()) break; - } catch (IOException e) { - Log.e(TAG, "fail to create new file: " - + candidate.getAbsolutePath(), e); - return null; - } - } - if (!candidate.exists() || !candidate.isFile()) { - throw new RuntimeException("cannot create file: " + filename); - } - - candidate.setReadable(true, false); - candidate.setWritable(true, false); - - try { - FileOutputStream fos = new FileOutputStream(candidate); - try { - if (exifData != null) { - ExifOutputStream eos = new ExifOutputStream(fos); - eos.setExifData(exifData); - saveBitmapToOutputStream(jc, cropped, - convertExtensionToCompressFormat(fileExtension), eos); - } else { - saveBitmapToOutputStream(jc, cropped, - convertExtensionToCompressFormat(fileExtension), fos); - } - } finally { - fos.close(); - } - } catch (IOException e) { - Log.e(TAG, "fail to save image: " - + candidate.getAbsolutePath(), e); - candidate.delete(); - return null; - } - - if (jc.isCancelled()) { - candidate.delete(); - return null; - } - - return candidate; - } - - private ExifData getExifData(String path) { - FileInputStream is = null; - try { - is = new FileInputStream(path); - ExifReader reader = new ExifReader(); - ExifData data = reader.read(is); - return data; - } catch (Throwable t) { - Log.w(TAG, "Cannot read EXIF data", t); - return null; - } finally { - Util.closeSilently(is); - } - } - - private static final String EXIF_SOFTWARE_VALUE = "Android Gallery"; - - private void changeExifData(ExifData data, int width, int height) { - data.addTag(ExifTag.TAG_IMAGE_WIDTH).setValue(width); - data.addTag(ExifTag.TAG_IMAGE_LENGTH).setValue(height); - data.addTag(ExifTag.TAG_SOFTWARE).setValue(EXIF_SOFTWARE_VALUE); - data.addTag(ExifTag.TAG_DATE_TIME).setTimeValue(System.currentTimeMillis()); - // Remove the original thumbnail - // TODO: generate a new thumbnail for the cropped image. - data.removeThumbnailData(); - } - - private Uri saveToMediaProvider(JobContext jc, Bitmap cropped) { - if (PicasaSource.isPicasaImage(mMediaItem)) { - return savePicasaImage(jc, cropped); - } else if (mMediaItem instanceof LocalImage) { - return saveLocalImage(jc, cropped); - } else { - return saveGenericImage(jc, cropped); - } - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private static void setImageSize(ContentValues values, int width, int height) { - // The two fields are available since ICS but got published in JB - if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) { - values.put(Images.Media.WIDTH, width); - values.put(Images.Media.HEIGHT, height); - } - } - - private Uri savePicasaImage(JobContext jc, Bitmap cropped) { - if (!DOWNLOAD_BUCKET.isDirectory() && !DOWNLOAD_BUCKET.mkdirs()) { - throw new RuntimeException("cannot create download folder"); - } - String filename = PicasaSource.getImageTitle(mMediaItem); - int pos = filename.lastIndexOf('.'); - if (pos >= 0) filename = filename.substring(0, pos); - ExifData exifData = new ExifData(ByteOrder.BIG_ENDIAN); - PicasaSource.extractExifValues(mMediaItem, exifData); - changeExifData(exifData, cropped.getWidth(), cropped.getHeight()); - File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename, exifData); - if (output == null) return null; - - long now = System.currentTimeMillis() / 1000; - ContentValues values = new ContentValues(); - values.put(Images.Media.TITLE, PicasaSource.getImageTitle(mMediaItem)); - values.put(Images.Media.DISPLAY_NAME, output.getName()); - values.put(Images.Media.DATE_TAKEN, PicasaSource.getDateTaken(mMediaItem)); - values.put(Images.Media.DATE_MODIFIED, now); - values.put(Images.Media.DATE_ADDED, now); - values.put(Images.Media.MIME_TYPE, getOutputMimeType()); - values.put(Images.Media.ORIENTATION, 0); - values.put(Images.Media.DATA, output.getAbsolutePath()); - values.put(Images.Media.SIZE, output.length()); - setImageSize(values, cropped.getWidth(), cropped.getHeight()); - - double latitude = PicasaSource.getLatitude(mMediaItem); - double longitude = PicasaSource.getLongitude(mMediaItem); - if (GalleryUtils.isValidLocation(latitude, longitude)) { - values.put(Images.Media.LATITUDE, latitude); - values.put(Images.Media.LONGITUDE, longitude); - } - return getContentResolver().insert( - Images.Media.EXTERNAL_CONTENT_URI, values); - } - - private Uri saveLocalImage(JobContext jc, Bitmap cropped) { - LocalImage localImage = (LocalImage) mMediaItem; - - File oldPath = new File(localImage.filePath); - File directory = new File(oldPath.getParent()); - - String filename = oldPath.getName(); - int pos = filename.lastIndexOf('.'); - if (pos >= 0) filename = filename.substring(0, pos); - File output = null; - - ExifData exifData = null; - if (convertExtensionToCompressFormat(getFileExtension()) == CompressFormat.JPEG) { - exifData = getExifData(oldPath.getAbsolutePath()); - if (exifData != null) { - changeExifData(exifData, cropped.getWidth(), cropped.getHeight()); - } - } - output = saveMedia(jc, cropped, directory, filename, exifData); - if (output == null) return null; - - long now = System.currentTimeMillis() / 1000; - ContentValues values = new ContentValues(); - values.put(Images.Media.TITLE, localImage.caption); - values.put(Images.Media.DISPLAY_NAME, output.getName()); - values.put(Images.Media.DATE_TAKEN, localImage.dateTakenInMs); - values.put(Images.Media.DATE_MODIFIED, now); - values.put(Images.Media.DATE_ADDED, now); - values.put(Images.Media.MIME_TYPE, getOutputMimeType()); - values.put(Images.Media.ORIENTATION, 0); - values.put(Images.Media.DATA, output.getAbsolutePath()); - values.put(Images.Media.SIZE, output.length()); - - setImageSize(values, cropped.getWidth(), cropped.getHeight()); - - if (GalleryUtils.isValidLocation(localImage.latitude, localImage.longitude)) { - values.put(Images.Media.LATITUDE, localImage.latitude); - values.put(Images.Media.LONGITUDE, localImage.longitude); - } - return getContentResolver().insert( - Images.Media.EXTERNAL_CONTENT_URI, values); - } - - private Uri saveGenericImage(JobContext jc, Bitmap cropped) { - if (!DOWNLOAD_BUCKET.isDirectory() && !DOWNLOAD_BUCKET.mkdirs()) { - throw new RuntimeException("cannot create download folder"); - } - - long now = System.currentTimeMillis(); - String filename = new SimpleDateFormat(TIME_STAMP_NAME). - format(new Date(now)); - - File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename, null); - if (output == null) return null; - - ContentValues values = new ContentValues(); - values.put(Images.Media.TITLE, filename); - values.put(Images.Media.DISPLAY_NAME, output.getName()); - values.put(Images.Media.DATE_TAKEN, now); - values.put(Images.Media.DATE_MODIFIED, now / 1000); - values.put(Images.Media.DATE_ADDED, now / 1000); - values.put(Images.Media.MIME_TYPE, getOutputMimeType()); - values.put(Images.Media.ORIENTATION, 0); - values.put(Images.Media.DATA, output.getAbsolutePath()); - values.put(Images.Media.SIZE, output.length()); - - setImageSize(values, cropped.getWidth(), cropped.getHeight()); - - return getContentResolver().insert( - Images.Media.EXTERNAL_CONTENT_URI, values); - } - - private boolean saveBitmapToOutputStream( - JobContext jc, Bitmap bitmap, CompressFormat format, OutputStream os) { - // We wrap the OutputStream so that it can be interrupted. - final InterruptableOutputStream ios = new InterruptableOutputStream(os); - jc.setCancelListener(new CancelListener() { - @Override - public void onCancel() { - ios.interrupt(); - } - }); - try { - bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, ios); - return !jc.isCancelled(); - } finally { - jc.setCancelListener(null); - Utils.closeSilently(ios); - } - } - - private boolean saveBitmapToUri(JobContext jc, Bitmap bitmap, Uri uri) { - try { - OutputStream out = getContentResolver().openOutputStream(uri); - try { - return saveBitmapToOutputStream(jc, bitmap, - convertExtensionToCompressFormat(getFileExtension()), out); - } finally { - Utils.closeSilently(out); - } - } catch (FileNotFoundException e) { - Log.w(TAG, "cannot write output", e); - } - return true; - } - - private CompressFormat convertExtensionToCompressFormat(String extension) { - return extension.equals("png") - ? CompressFormat.PNG - : CompressFormat.JPEG; - } - - private String getOutputMimeType() { - return getFileExtension().equals("png") ? "image/png" : "image/jpeg"; - } - - private String getFileExtension() { - String requestFormat = getIntent().getStringExtra(KEY_OUTPUT_FORMAT); - String outputFormat = (requestFormat == null) - ? determineCompressFormat(mMediaItem) - : requestFormat; - - outputFormat = outputFormat.toLowerCase(); - return (outputFormat.equals("png") || outputFormat.equals("gif")) - ? "png" // We don't support gif compression. - : "jpg"; - } - - private void onSaveClicked() { - Bundle extra = getIntent().getExtras(); - RectF cropRect = mCropView.getCropRectangle(); - if (cropRect == null) return; - mState = STATE_SAVING; - int messageId = extra != null && extra.getBoolean(KEY_SET_AS_WALLPAPER) - ? R.string.wallpaper - : R.string.saving_image; - mProgressDialog = ProgressDialog.show( - this, null, getString(messageId), true, false); - mSaveTask = getThreadPool().submit(new SaveOutput(cropRect), - new FutureListener<Intent>() { - @Override - public void onFutureDone(Future<Intent> future) { - mSaveTask = null; - if (future.isCancelled()) return; - Intent intent = future.get(); - if (intent != null) { - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_SAVE_COMPLETE, intent)); - } else { - mMainHandler.sendEmptyMessage(MSG_SHOW_SAVE_ERROR); - } - } - }); - } - - private Bitmap getCroppedImage(Rect rect) { - Utils.assertTrue(rect.width() > 0 && rect.height() > 0); - - Bundle extras = getIntent().getExtras(); - // (outputX, outputY) = the width and height of the returning bitmap. - int outputX = rect.width(); - int outputY = rect.height(); - if (extras != null) { - outputX = extras.getInt(KEY_OUTPUT_X, outputX); - outputY = extras.getInt(KEY_OUTPUT_Y, outputY); - } - - if (outputX * outputY > MAX_PIXEL_COUNT) { - float scale = FloatMath.sqrt((float) MAX_PIXEL_COUNT / outputX / outputY); - Log.w(TAG, "scale down the cropped image: " + scale); - outputX = Math.round(scale * outputX); - outputY = Math.round(scale * outputY); - } - - // (rect.width() * scaleX, rect.height() * scaleY) = - // the size of drawing area in output bitmap - float scaleX = 1; - float scaleY = 1; - Rect dest = new Rect(0, 0, outputX, outputY); - if (extras == null || extras.getBoolean(KEY_SCALE, true)) { - scaleX = (float) outputX / rect.width(); - scaleY = (float) outputY / rect.height(); - if (extras == null || !extras.getBoolean( - KEY_SCALE_UP_IF_NEEDED, false)) { - if (scaleX > 1f) scaleX = 1; - if (scaleY > 1f) scaleY = 1; - } - } - - // Keep the content in the center (or crop the content) - int rectWidth = Math.round(rect.width() * scaleX); - int rectHeight = Math.round(rect.height() * scaleY); - dest.set(Math.round((outputX - rectWidth) / 2f), - Math.round((outputY - rectHeight) / 2f), - Math.round((outputX + rectWidth) / 2f), - Math.round((outputY + rectHeight) / 2f)); - - if (mBitmapInIntent != null) { - Bitmap source = mBitmapInIntent; - Bitmap result = Bitmap.createBitmap( - outputX, outputY, Config.ARGB_8888); - Canvas canvas = new Canvas(result); - canvas.drawBitmap(source, rect, dest, null); - return result; - } - - if (mUseRegionDecoder) { - int rotation = mMediaItem.getFullImageRotation(); - rotateRectangle(rect, mCropView.getImageWidth(), - mCropView.getImageHeight(), 360 - rotation); - rotateRectangle(dest, outputX, outputY, 360 - rotation); - - BitmapFactory.Options options = new BitmapFactory.Options(); - int sample = BitmapUtils.computeSampleSizeLarger( - Math.max(scaleX, scaleY)); - options.inSampleSize = sample; - - // The decoding result is what we want if - // 1. The size of the decoded bitmap match the destination's size - // 2. The destination covers the whole output bitmap - // 3. No rotation - if ((rect.width() / sample) == dest.width() - && (rect.height() / sample) == dest.height() - && (outputX == dest.width()) && (outputY == dest.height()) - && rotation == 0) { - // To prevent concurrent access in GLThread - synchronized (mRegionDecoder) { - return mRegionDecoder.decodeRegion(rect, options); - } - } - Bitmap result = Bitmap.createBitmap( - outputX, outputY, Config.ARGB_8888); - Canvas canvas = new Canvas(result); - rotateCanvas(canvas, outputX, outputY, rotation); - drawInTiles(canvas, mRegionDecoder, rect, dest, sample); - return result; - } else { - int rotation = mMediaItem.getRotation(); - rotateRectangle(rect, mCropView.getImageWidth(), - mCropView.getImageHeight(), 360 - rotation); - rotateRectangle(dest, outputX, outputY, 360 - rotation); - Bitmap result = Bitmap.createBitmap(outputX, outputY, Config.ARGB_8888); - Canvas canvas = new Canvas(result); - rotateCanvas(canvas, outputX, outputY, rotation); - canvas.drawBitmap(mBitmap, - rect, dest, new Paint(Paint.FILTER_BITMAP_FLAG)); - return result; - } - } - - private static void rotateCanvas( - Canvas canvas, int width, int height, int rotation) { - canvas.translate(width / 2, height / 2); - canvas.rotate(rotation); - if (((rotation / 90) & 0x01) == 0) { - canvas.translate(-width / 2, -height / 2); - } else { - canvas.translate(-height / 2, -width / 2); - } - } - - private static void rotateRectangle( - Rect rect, int width, int height, int rotation) { - if (rotation == 0 || rotation == 360) return; - - int w = rect.width(); - int h = rect.height(); - switch (rotation) { - case 90: { - rect.top = rect.left; - rect.left = height - rect.bottom; - rect.right = rect.left + h; - rect.bottom = rect.top + w; - return; - } - case 180: { - rect.left = width - rect.right; - rect.top = height - rect.bottom; - rect.right = rect.left + w; - rect.bottom = rect.top + h; - return; - } - case 270: { - rect.left = rect.top; - rect.top = width - rect.right; - rect.right = rect.left + h; - rect.bottom = rect.top + w; - return; - } - default: throw new AssertionError(); - } - } - - private void drawInTiles(Canvas canvas, - BitmapRegionDecoder decoder, Rect rect, Rect dest, int sample) { - int tileSize = TILE_SIZE * sample; - Rect tileRect = new Rect(); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inPreferredConfig = Config.ARGB_8888; - options.inSampleSize = sample; - canvas.translate(dest.left, dest.top); - canvas.scale((float) sample * dest.width() / rect.width(), - (float) sample * dest.height() / rect.height()); - Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG); - for (int tx = rect.left, x = 0; - tx < rect.right; tx += tileSize, x += TILE_SIZE) { - for (int ty = rect.top, y = 0; - ty < rect.bottom; ty += tileSize, y += TILE_SIZE) { - tileRect.set(tx, ty, tx + tileSize, ty + tileSize); - if (tileRect.intersect(rect)) { - Bitmap bitmap; - - // To prevent concurrent access in GLThread - synchronized (decoder) { - bitmap = decoder.decodeRegion(tileRect, options); - } - canvas.drawBitmap(bitmap, x, y, paint); - bitmap.recycle(); - } - } - } - } - - private void onBitmapRegionDecoderAvailable( - BitmapRegionDecoder regionDecoder) { - - if (regionDecoder == null) { - Toast.makeText(this, R.string.fail_to_load_image, Toast.LENGTH_SHORT).show(); - finish(); - return; - } - mRegionDecoder = regionDecoder; - mUseRegionDecoder = true; - mState = STATE_LOADED; - - BitmapFactory.Options options = new BitmapFactory.Options(); - int width = regionDecoder.getWidth(); - int height = regionDecoder.getHeight(); - options.inSampleSize = BitmapUtils.computeSampleSize(width, height, - BitmapUtils.UNCONSTRAINED, BACKUP_PIXEL_COUNT); - mBitmap = regionDecoder.decodeRegion( - new Rect(0, 0, width, height), options); - - mBitmapScreenNail = new BitmapScreenNail(mBitmap); - - TileImageViewAdapter adapter = new TileImageViewAdapter(); - adapter.setScreenNail(mBitmapScreenNail, width, height); - adapter.setRegionDecoder(regionDecoder); - - mCropView.setDataModel(adapter, mMediaItem.getFullImageRotation()); - if (mDoFaceDetection) { - mCropView.detectFaces(mBitmap); - } else { - mCropView.initializeHighlightRectangle(); - } - } - - private void onBitmapAvailable(Bitmap bitmap) { - if (bitmap == null) { - Toast.makeText(this, R.string.fail_to_load_image, Toast.LENGTH_SHORT).show(); - finish(); - return; - } - mUseRegionDecoder = false; - mState = STATE_LOADED; - - mBitmap = bitmap; - BitmapFactory.Options options = new BitmapFactory.Options(); - mCropView.setDataModel(new BitmapTileProvider(bitmap, 512), - mMediaItem.getRotation()); - if (mDoFaceDetection) { - mCropView.detectFaces(bitmap); - } else { - mCropView.initializeHighlightRectangle(); - } - } - - private void setCropParameters() { - Bundle extras = getIntent().getExtras(); - if (extras == null) - return; - int aspectX = extras.getInt(KEY_ASPECT_X, 0); - int aspectY = extras.getInt(KEY_ASPECT_Y, 0); - if (aspectX != 0 && aspectY != 0) { - mCropView.setAspectRatio((float) aspectX / aspectY); - } - - float spotlightX = extras.getFloat(KEY_SPOTLIGHT_X, 0); - float spotlightY = extras.getFloat(KEY_SPOTLIGHT_Y, 0); - if (spotlightX != 0 && spotlightY != 0) { - mCropView.setSpotlightRatio(spotlightX, spotlightY); - } - } - - private void initializeData() { - Bundle extras = getIntent().getExtras(); - - if (extras != null) { - if (extras.containsKey(KEY_NO_FACE_DETECTION)) { - mDoFaceDetection = !extras.getBoolean(KEY_NO_FACE_DETECTION); - } - - mBitmapInIntent = extras.getParcelable(KEY_DATA); - - if (mBitmapInIntent != null) { - mBitmapTileProvider = - new BitmapTileProvider(mBitmapInIntent, MAX_BACKUP_IMAGE_SIZE); - mCropView.setDataModel(mBitmapTileProvider, 0); - if (mDoFaceDetection) { - mCropView.detectFaces(mBitmapInIntent); - } else { - mCropView.initializeHighlightRectangle(); - } - mState = STATE_LOADED; - return; - } - } - - mProgressDialog = ProgressDialog.show( - this, null, getString(R.string.loading_image), true, true); - mProgressDialog.setCanceledOnTouchOutside(false); - mProgressDialog.setCancelMessage(mMainHandler.obtainMessage(MSG_CANCEL_DIALOG)); - - mMediaItem = getMediaItemFromIntentData(); - if (mMediaItem == null) return; - - boolean supportedByBitmapRegionDecoder = - (mMediaItem.getSupportedOperations() & MediaItem.SUPPORT_FULL_IMAGE) != 0; - if (supportedByBitmapRegionDecoder) { - mLoadTask = getThreadPool().submit(new LoadDataTask(mMediaItem), - new FutureListener<BitmapRegionDecoder>() { - @Override - public void onFutureDone(Future<BitmapRegionDecoder> future) { - mLoadTask = null; - BitmapRegionDecoder decoder = future.get(); - if (future.isCancelled()) { - if (decoder != null) decoder.recycle(); - return; - } - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_LARGE_BITMAP, decoder)); - } - }); - } else { - mLoadBitmapTask = getThreadPool().submit(new LoadBitmapDataTask(mMediaItem), - new FutureListener<Bitmap>() { - @Override - public void onFutureDone(Future<Bitmap> future) { - mLoadBitmapTask = null; - Bitmap bitmap = future.get(); - if (future.isCancelled()) { - if (bitmap != null) bitmap.recycle(); - return; - } - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_BITMAP, bitmap)); - } - }); - } - } - - @Override - protected void onResume() { - super.onResume(); - if (mState == STATE_INIT) initializeData(); - if (mState == STATE_SAVING) onSaveClicked(); - - // TODO: consider to do it in GLView system - GLRoot root = getGLRoot(); - root.lockRenderThread(); - try { - mCropView.resume(); - } finally { - root.unlockRenderThread(); - } - } - - @Override - protected void onPause() { - super.onPause(); - dismissProgressDialogIfShown(); - - Future<BitmapRegionDecoder> loadTask = mLoadTask; - if (loadTask != null && !loadTask.isDone()) { - // load in progress, try to cancel it - loadTask.cancel(); - loadTask.waitDone(); - } - - Future<Bitmap> loadBitmapTask = mLoadBitmapTask; - if (loadBitmapTask != null && !loadBitmapTask.isDone()) { - // load in progress, try to cancel it - loadBitmapTask.cancel(); - loadBitmapTask.waitDone(); - } - - Future<Intent> saveTask = mSaveTask; - if (saveTask != null && !saveTask.isDone()) { - // save in progress, try to cancel it - saveTask.cancel(); - saveTask.waitDone(); - } - GLRoot root = getGLRoot(); - root.lockRenderThread(); - try { - mCropView.pause(); - } finally { - root.unlockRenderThread(); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (mBitmapScreenNail != null) { - mBitmapScreenNail.recycle(); - mBitmapScreenNail = null; - } - } - - private void dismissProgressDialogIfShown() { - if (mProgressDialog != null) { - mProgressDialog.dismiss(); - mProgressDialog = null; - } - } - - private MediaItem getMediaItemFromIntentData() { - Uri uri = getIntent().getData(); - DataManager manager = getDataManager(); - Path path = manager.findPathByUri(uri, getIntent().getType()); - if (path == null) { - Log.w(TAG, "cannot get path for: " + uri + ", or no data given"); - return null; - } - return (MediaItem) manager.getMediaObject(path); - } - - private class LoadDataTask implements Job<BitmapRegionDecoder> { - MediaItem mItem; - - public LoadDataTask(MediaItem item) { - mItem = item; - } - - @Override - public BitmapRegionDecoder run(JobContext jc) { - return mItem == null ? null : mItem.requestLargeImage().run(jc); - } - } - - private class LoadBitmapDataTask implements Job<Bitmap> { - MediaItem mItem; - - public LoadBitmapDataTask(MediaItem item) { - mItem = item; - } - @Override - public Bitmap run(JobContext jc) { - return mItem == null - ? null - : mItem.requestImage(MediaItem.TYPE_THUMBNAIL).run(jc); - } - } -} diff --git a/src/com/android/gallery3d/app/FilmstripPage.java b/src/com/android/gallery3d/app/FilmstripPage.java new file mode 100644 index 000000000..a9726cdc9 --- /dev/null +++ b/src/com/android/gallery3d/app/FilmstripPage.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.app; + +public class FilmstripPage extends PhotoPage { + +} diff --git a/src/com/android/gallery3d/app/Gallery.java b/src/com/android/gallery3d/app/Gallery.java index e28404fac..354e325d4 100644 --- a/src/com/android/gallery3d/app/Gallery.java +++ b/src/com/android/gallery3d/app/Gallery.java @@ -26,10 +26,9 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.provider.OpenableColumns; -import android.view.Window; -import android.view.WindowManager; import android.widget.Toast; +import com.actionbarsherlock.view.Window; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.DataManager; @@ -49,7 +48,6 @@ public final class Gallery extends AbstractGalleryActivity implements OnCancelLi public static final String KEY_GET_ALBUM = "get-album"; public static final String KEY_TYPE_BITS = "type-bits"; public static final String KEY_MEDIA_TYPES = "mediaTypes"; - public static final String KEY_DISMISS_KEYGUARD = "dismiss-keyguard"; private static final String TAG = "Gallery"; private Dialog mVersionCheckDialog; @@ -60,11 +58,6 @@ public final class Gallery extends AbstractGalleryActivity implements OnCancelLi requestWindowFeature(Window.FEATURE_ACTION_BAR); requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); - if (getIntent().getBooleanExtra(KEY_DISMISS_KEYGUARD, false)) { - getWindow().addFlags( - WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); - } - setContentView(R.layout.main); if (savedInstanceState != null) { @@ -222,7 +215,7 @@ public final class Gallery extends AbstractGalleryActivity implements OnCancelLi } } - getStateManager().startState(PhotoPage.class, data); + getStateManager().startState(SinglePhotoPage.class, data); } } } diff --git a/src/com/android/gallery3d/app/GalleryActionBar.java b/src/com/android/gallery3d/app/GalleryActionBar.java index 0fb5e51b4..49f4186e8 100644 --- a/src/com/android/gallery3d/app/GalleryActionBar.java +++ b/src/com/android/gallery3d/app/GalleryActionBar.java @@ -17,9 +17,6 @@ package com.android.gallery3d.app; import android.annotation.TargetApi; -import android.app.ActionBar; -import android.app.ActionBar.OnMenuVisibilityListener; -import android.app.ActionBar.OnNavigationListener; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; @@ -27,15 +24,18 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; -import android.widget.ShareActionProvider; import android.widget.TextView; import android.widget.TwoLineListItem; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.ActionBar.OnMenuVisibilityListener; +import com.actionbarsherlock.app.ActionBar.OnNavigationListener; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.widget.ShareActionProvider; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; @@ -186,7 +186,7 @@ public class GalleryActionBar implements OnNavigationListener { } public GalleryActionBar(AbstractGalleryActivity activity) { - mActionBar = activity.getActionBar(); + mActionBar = activity.getSupportActionBar(); mContext = activity.getAndroidContext(); mActivity = activity; mInflater = ((Activity) mActivity).getLayoutInflater(); @@ -396,7 +396,7 @@ public class GalleryActionBar implements OnNavigationListener { private Intent mShareIntent; public void createActionBarMenu(int menuRes, Menu menu) { - mActivity.getMenuInflater().inflate(menuRes, menu); + mActivity.getSupportMenuInflater().inflate(menuRes, menu); mActionBarMenu = menu; MenuItem item = menu.findItem(R.id.action_share_panorama); diff --git a/src/com/android/gallery3d/app/MovieActivity.java b/src/com/android/gallery3d/app/MovieActivity.java index 3123644c7..d725e6d12 100644 --- a/src/com/android/gallery3d/app/MovieActivity.java +++ b/src/com/android/gallery3d/app/MovieActivity.java @@ -17,8 +17,6 @@ package com.android.gallery3d.app; import android.annotation.TargetApi; -import android.app.ActionBar; -import android.app.Activity; import android.content.AsyncQueryHandler; import android.content.ContentResolver; import android.content.Intent; @@ -33,13 +31,15 @@ import android.os.Bundle; import android.provider.MediaStore; import android.provider.OpenableColumns; import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.view.Window; import android.view.WindowManager; -import android.widget.ShareActionProvider; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.widget.ShareActionProvider; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.Utils; @@ -51,7 +51,7 @@ import com.android.gallery3d.common.Utils; * to set the action bar logo so the playback process looks more seamlessly integrated with * the original activity. */ -public class MovieActivity extends Activity { +public class MovieActivity extends SherlockActivity { @SuppressWarnings("unused") private static final String TAG = "MovieActivity"; public static final String KEY_LOGO_BITMAP = "logo-bitmap"; @@ -75,8 +75,8 @@ public class MovieActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_ACTION_BAR); - requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); + getSherlock().requestFeature(Window.FEATURE_ACTION_BAR); + getSherlock().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY); setContentView(R.layout.movie_view); View rootView = findViewById(R.id.movie_view_root); @@ -119,14 +119,14 @@ public class MovieActivity extends Activity { private void setActionBarLogoFromIntent(Intent intent) { Bitmap logo = intent.getParcelableExtra(KEY_LOGO_BITMAP); if (logo != null) { - getActionBar().setLogo( + getSupportActionBar().setLogo( new BitmapDrawable(getResources(), logo)); } } private void initializeActionBar(Intent intent) { mUri = intent.getData(); - final ActionBar actionBar = getActionBar(); + final ActionBar actionBar = getSupportActionBar(); setActionBarLogoFromIntent(intent); actionBar.setDisplayOptions( ActionBar.DISPLAY_HOME_AS_UP, @@ -166,7 +166,7 @@ public class MovieActivity extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.movie, menu); + getSupportMenuInflater().inflate(R.menu.movie, menu); // Document says EXTRA_STREAM should be a content: Uri // So, we only share the video if it's "content:". diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java index 85dc4427e..00e4cd63b 100644 --- a/src/com/android/gallery3d/app/MoviePlayer.java +++ b/src/com/android/gallery3d/app/MoviePlayer.java @@ -74,8 +74,8 @@ public class MoviePlayer implements private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins private Context mContext; - private final View mRootView; private final VideoView mVideoView; + private final View mRootView; private final Bookmarker mBookmarker; private final Uri mUri; private final Handler mHandler = new Handler(); @@ -191,7 +191,6 @@ public class MoviePlayer implements if ((diff & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 && (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { mController.show(); - mRootView.setBackgroundColor(Color.BLACK); } } }); diff --git a/src/com/android/gallery3d/app/MuteVideo.java b/src/com/android/gallery3d/app/MuteVideo.java new file mode 100644 index 000000000..012b682ef --- /dev/null +++ b/src/com/android/gallery3d/app/MuteVideo.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.app; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.provider.MediaStore; +import android.widget.Toast; + +import com.android.gallery3d.R; +import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.util.SaveVideoFileInfo; +import com.android.gallery3d.util.SaveVideoFileUtils; + +import java.io.IOException; + +public class MuteVideo { + + private ProgressDialog mMuteProgress; + + private MediaItem mCurrentItem = null; + private Uri mUri = null; + private SaveVideoFileInfo mDstFileInfo = null; + private Activity mActivity = null; + private final Handler mHandler = new Handler(); + + final String TIME_STAMP_NAME = "'MUTE'_yyyyMMdd_HHmmss"; + + public MuteVideo(MediaItem current, Uri uri, Activity activity) { + mUri = uri; + mCurrentItem = current; + mActivity = activity; + } + + public void muteInBackground() { + mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME, + mActivity.getContentResolver(), mUri, + mActivity.getString(R.string.folder_download)); + + showProgressDialog(); + new Thread(new Runnable() { + @Override + public void run() { + try { + VideoUtils.startMute(mCurrentItem.getFilePath(), mDstFileInfo); + SaveVideoFileUtils.insertContent( + mDstFileInfo, mActivity.getContentResolver(), mUri); + } catch (IOException e) { + Toast.makeText(mActivity, mActivity.getString(R.string.video_mute_err), + Toast.LENGTH_SHORT).show(); + } + // After muting is done, trigger the UI changed. + mHandler.post(new Runnable() { + @Override + public void run() { + Toast.makeText(mActivity.getApplicationContext(), + mActivity.getString(R.string.save_into, + mDstFileInfo.mFolderName), + Toast.LENGTH_SHORT) + .show(); + + if (mMuteProgress != null) { + mMuteProgress.dismiss(); + mMuteProgress = null; + + // Show the result only when the activity not + // stopped. + Intent intent = new Intent(android.content.Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(mDstFileInfo.mFile), "video/*"); + intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false); + mActivity.startActivity(intent); + } + } + }); + } + }).start(); + } + + private void showProgressDialog() { + mMuteProgress = new ProgressDialog(mActivity); + mMuteProgress.setTitle(mActivity.getString(R.string.muting)); + mMuteProgress.setMessage(mActivity.getString(R.string.please_wait)); + mMuteProgress.setCancelable(false); + mMuteProgress.setCanceledOnTouchOutside(false); + mMuteProgress.show(); + } +} diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java index 506d1ca6f..3c0c4330b 100644 --- a/src/com/android/gallery3d/app/PhotoPage.java +++ b/src/com/android/gallery3d/app/PhotoPage.java @@ -17,7 +17,6 @@ package com.android.gallery3d.app; import android.annotation.TargetApi; -import android.app.ActionBar.OnMenuVisibilityListener; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; @@ -32,13 +31,15 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.SystemClock; -import android.view.Menu; -import android.view.MenuItem; import android.widget.RelativeLayout; import android.widget.Toast; import com.android.camera.CameraActivity; import com.android.camera.ProxyLauncher; + +import com.actionbarsherlock.app.ActionBar.OnMenuVisibilityListener; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.data.ComboAlbum; @@ -71,7 +72,7 @@ import com.android.gallery3d.ui.SelectionManager; import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.util.GalleryUtils; -public class PhotoPage extends ActivityState implements +public abstract class PhotoPage extends ActivityState implements PhotoView.Listener, AppBridge.Server, PhotoPageBottomControls.Delegate, GalleryActionBar.OnAlbumModeSelectedListener { private static final String TAG = "PhotoPage"; @@ -1019,11 +1020,16 @@ public class PhotoPage extends ActivityState implements refreshHidingMessage(); MediaItem current = mModel.getMediaItem(0); + // This is a shield for monkey when it clicks the action bar + // menu when transitioning from filmstrip to camera + if (current instanceof SnailItem) return true; + // TODO: We should check the current photo against the MediaItem + // that the menu was initially created for. We need to fix this + // after PhotoPage being refactored. if (current == null) { // item is not ready, ignore return true; } - int currentIndex = mModel.getCurrentIndex(); Path path = current.getPath(); @@ -1064,6 +1070,12 @@ public class PhotoPage extends ActivityState implements mActivity.startActivityForResult(intent, REQUEST_TRIM); return true; } + case R.id.action_mute: { + MuteVideo muteVideo = new MuteVideo(current, + manager.getContentUri(path), mActivity); + muteVideo.muteInBackground(); + return true; + } case R.id.action_edit: { launchPhotoEditor(); return true; @@ -1156,9 +1168,7 @@ public class PhotoPage extends ActivityState implements } else if (goBack) { onBackPressed(); } else if (unlock) { - Intent intent = new Intent(mActivity, Gallery.class); - intent.putExtra(Gallery.KEY_DISMISS_KEYGUARD, true); - mActivity.startActivity(intent); + mActivity.getStateManager().finishState(this); } else if (launchCamera) { launchCamera(); } else { @@ -1242,7 +1252,7 @@ public class PhotoPage extends ActivityState implements Bundle data = new Bundle(getData()); data.putString(KEY_MEDIA_SET_PATH, albumPath.toString()); data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path.toString()); - mActivity.getStateManager().startState(PhotoPage.class, data); + mActivity.getStateManager().startState(SinglePhotoPage.class, data); return; } mModel.setCurrentPhoto(path, mCurrentIndex); diff --git a/src/com/android/gallery3d/app/PickerActivity.java b/src/com/android/gallery3d/app/PickerActivity.java index d5bb218ea..1eb95d0c6 100644 --- a/src/com/android/gallery3d/app/PickerActivity.java +++ b/src/com/android/gallery3d/app/PickerActivity.java @@ -17,13 +17,13 @@ package com.android.gallery3d.app; import android.os.Bundle; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; -import android.view.Window; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.Window; import com.android.gallery3d.R; import com.android.gallery3d.ui.GLRootView; @@ -62,7 +62,7 @@ public class PickerActivity extends AbstractGalleryActivity @Override public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); + MenuInflater inflater = getSupportMenuInflater(); inflater.inflate(R.menu.pickup, menu); return true; } diff --git a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java index 00f2fe78f..f0848ad22 100644 --- a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java +++ b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java @@ -34,6 +34,7 @@ import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.ui.TileImageViewAdapter; import com.android.gallery3d.util.Future; import com.android.gallery3d.util.FutureListener; +import com.android.gallery3d.util.LightCycleHelper; import com.android.gallery3d.util.ThreadPool; public class SinglePhotoDataAdapter extends TileImageViewAdapter diff --git a/src/com/android/gallery3d/app/SinglePhotoPage.java b/src/com/android/gallery3d/app/SinglePhotoPage.java new file mode 100644 index 000000000..beb87d358 --- /dev/null +++ b/src/com/android/gallery3d/app/SinglePhotoPage.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.app; + +public class SinglePhotoPage extends PhotoPage { + +} diff --git a/src/com/android/gallery3d/app/StateManager.java b/src/com/android/gallery3d/app/StateManager.java index d77279f78..64daa6afe 100644 --- a/src/com/android/gallery3d/app/StateManager.java +++ b/src/com/android/gallery3d/app/StateManager.java @@ -21,10 +21,10 @@ import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.os.Parcelable; -import android.view.Menu; -import android.view.MenuItem; import com.android.gallery3d.anim.StateTransitionAnimation; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.common.Utils; import java.util.Stack; diff --git a/src/com/android/gallery3d/app/TrimControllerOverlay.java b/src/com/android/gallery3d/app/TrimControllerOverlay.java index 9127ad159..cae016626 100644 --- a/src/com/android/gallery3d/app/TrimControllerOverlay.java +++ b/src/com/android/gallery3d/app/TrimControllerOverlay.java @@ -23,6 +23,8 @@ import android.content.Context; import android.view.MotionEvent; import android.view.View; +import com.android.gallery3d.common.ApiHelper; + /** * The controller for the Trimming Video. */ @@ -41,36 +43,41 @@ public class TrimControllerOverlay extends CommonControllerOverlay { if (mState == State.PLAYING) { mPlayPauseReplayView.setVisibility(View.INVISIBLE); } - mPlayPauseReplayView.setAlpha(1f); + if (ApiHelper.HAS_OBJECT_ANIMATION) { + mPlayPauseReplayView.setAlpha(1f); + } } @Override public void showPlaying() { super.showPlaying(); + if (ApiHelper.HAS_OBJECT_ANIMATION) { + // Add animation to hide the play button while playing. + ObjectAnimator anim = ObjectAnimator.ofFloat(mPlayPauseReplayView, "alpha", 1f, 0f); + anim.setDuration(200); + anim.start(); + anim.addListener(new AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } - // Add animation to hide the play button while playing. - ObjectAnimator anim = ObjectAnimator.ofFloat(mPlayPauseReplayView, "alpha", 1f, 0f); - anim.setDuration(200); - anim.start(); - anim.addListener(new AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - hidePlayButtonIfPlaying(); - } + @Override + public void onAnimationEnd(Animator animation) { + hidePlayButtonIfPlaying(); + } - @Override - public void onAnimationCancel(Animator animation) { - hidePlayButtonIfPlaying(); - } + @Override + public void onAnimationCancel(Animator animation) { + hidePlayButtonIfPlaying(); + } - @Override - public void onAnimationRepeat(Animator animation) { - } - }); + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + } else { + hidePlayButtonIfPlaying(); + } } @Override diff --git a/src/com/android/gallery3d/app/TrimVideo.java b/src/com/android/gallery3d/app/TrimVideo.java index 38b403b10..7a76be5dc 100644 --- a/src/com/android/gallery3d/app/TrimVideo.java +++ b/src/com/android/gallery3d/app/TrimVideo.java @@ -16,8 +16,6 @@ package com.android.gallery3d.app; -import android.app.ActionBar; -import android.app.Activity; import android.app.ProgressDialog; import android.content.ContentResolver; import android.content.ContentValues; @@ -39,20 +37,25 @@ import android.widget.TextView; import android.widget.Toast; import android.widget.VideoView; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockActivity; import com.android.gallery3d.R; import com.android.gallery3d.util.BucketNames; +import com.android.gallery3d.util.SaveVideoFileInfo; +import com.android.gallery3d.util.SaveVideoFileUtils; import java.io.File; import java.io.IOException; import java.sql.Date; import java.text.SimpleDateFormat; -public class TrimVideo extends Activity implements +public class TrimVideo extends SherlockActivity implements MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, ControllerOverlay.Listener { private VideoView mVideoView; + private TextView mSaveVideoTextView; private TrimControllerOverlay mController; private Context mContext; private Uri mUri; @@ -70,13 +73,8 @@ public class TrimVideo extends Activity implements private boolean mHasPaused = false; private String mSrcVideoPath = null; - private String mSaveFileName = null; private static final String TIME_STAMP_NAME = "'TRIM'_yyyyMMdd_HHmmss"; - private File mSrcFile = null; - private File mDstFile = null; - private File mSaveDirectory = null; - // For showing the result. - private String saveFolderName = null; + private SaveVideoFileInfo mDstFileInfo = null; @Override public void onCreate(Bundle savedInstanceState) { @@ -86,20 +84,21 @@ public class TrimVideo extends Activity implements requestWindowFeature(Window.FEATURE_ACTION_BAR); requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); - ActionBar actionBar = getActionBar(); + ActionBar actionBar = getSupportActionBar(); int displayOptions = ActionBar.DISPLAY_SHOW_HOME; actionBar.setDisplayOptions(0, displayOptions); displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM; actionBar.setDisplayOptions(displayOptions, displayOptions); actionBar.setCustomView(R.layout.trim_menu); - TextView mSaveVideoTextView = (TextView) findViewById(R.id.start_trim); + mSaveVideoTextView = (TextView) findViewById(R.id.start_trim); mSaveVideoTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { trimVideo(); } }); + mSaveVideoTextView.setEnabled(false); Intent intent = getIntent(); mUri = intent.getData(); @@ -221,72 +220,24 @@ public class TrimVideo extends Activity implements mController.showPaused(); } - // Copy from SaveCopyTask.java in terms of how to handle the destination - // path and filename : querySource() and getSaveDirectory(). - private interface ContentResolverQueryCallback { - void onCursorResult(Cursor cursor); - } - - private void querySource(String[] projection, ContentResolverQueryCallback callback) { - ContentResolver contentResolver = getContentResolver(); - Cursor cursor = null; - try { - cursor = contentResolver.query(mUri, projection, null, null, null); - if ((cursor != null) && cursor.moveToNext()) { - callback.onCursorResult(cursor); - } - } catch (Exception e) { - // Ignore error for lacking the data column from the source. - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - private File getSaveDirectory() { - final File[] dir = new File[1]; - querySource(new String[] { - VideoColumns.DATA }, new ContentResolverQueryCallback() { - @Override - public void onCursorResult(Cursor cursor) { - dir[0] = new File(cursor.getString(0)).getParentFile(); - } - }); - return dir[0]; - } - - private void trimVideo() { + private boolean isModified() { int delta = mTrimEndTime - mTrimStartTime; + // Considering that we only trim at sync frame, we don't want to trim // when the time interval is too short or too close to the origin. - if (delta < 100 ) { - Toast.makeText(getApplicationContext(), - getString(R.string.trim_too_short), - Toast.LENGTH_SHORT).show(); - return; - } - if (Math.abs(mVideoView.getDuration() - delta) < 100) { - // If no change has been made, go back - onBackPressed(); - return; - } - // Use the default save directory if the source directory cannot be - // saved. - mSaveDirectory = getSaveDirectory(); - if ((mSaveDirectory == null) || !mSaveDirectory.canWrite()) { - mSaveDirectory = new File(Environment.getExternalStorageDirectory(), - BucketNames.DOWNLOAD); - saveFolderName = getString(R.string.folder_download); + if (delta < 100 || Math.abs(mVideoView.getDuration() - delta) < 100) { + return false; } else { - saveFolderName = mSaveDirectory.getName(); + return true; } - mSaveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format( - new Date(System.currentTimeMillis())); + } - mDstFile = new File(mSaveDirectory, mSaveFileName + ".mp4"); - mSrcFile = new File(mSrcVideoPath); + private void trimVideo() { + + mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME, + getContentResolver(), mUri, getString(R.string.folder_download)); + final File mSrcFile = new File(mSrcVideoPath); showProgressDialog(); @@ -294,9 +245,11 @@ public class TrimVideo extends Activity implements @Override public void run() { try { - TrimVideoUtils.startTrim(mSrcFile, mDstFile, mTrimStartTime, mTrimEndTime); + VideoUtils.startTrim(mSrcFile, mDstFileInfo.mFile, + mTrimStartTime, mTrimEndTime, mVideoView.getDuration()); // Update the database for adding a new video file. - insertContent(mDstFile); + SaveVideoFileUtils.insertContent(mDstFileInfo, + getContentResolver(), mUri); } catch (IOException e) { e.printStackTrace(); } @@ -305,7 +258,7 @@ public class TrimVideo extends Activity implements @Override public void run() { Toast.makeText(getApplicationContext(), - getString(R.string.save_into) + " " + saveFolderName, + getString(R.string.save_into, mDstFileInfo.mFolderName), Toast.LENGTH_SHORT) .show(); // TODO: change trimming into a service to avoid @@ -315,7 +268,7 @@ public class TrimVideo extends Activity implements mProgress = null; // Show the result only when the activity not stopped. Intent intent = new Intent(android.content.Intent.ACTION_VIEW); - intent.setDataAndTypeAndNormalize(Uri.fromFile(mDstFile), "video/*"); + intent.setDataAndType(Uri.fromFile(mDstFileInfo.mFile), "video/*"); intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false); startActivity(intent); finish(); @@ -338,53 +291,6 @@ public class TrimVideo extends Activity implements mProgress.show(); } - /** - * Insert the content (saved file) with proper video properties. - */ - private Uri insertContent(File file) { - long nowInMs = System.currentTimeMillis(); - long nowInSec = nowInMs / 1000; - final ContentValues values = new ContentValues(12); - values.put(Video.Media.TITLE, mSaveFileName); - values.put(Video.Media.DISPLAY_NAME, file.getName()); - values.put(Video.Media.MIME_TYPE, "video/mp4"); - values.put(Video.Media.DATE_TAKEN, nowInMs); - values.put(Video.Media.DATE_MODIFIED, nowInSec); - values.put(Video.Media.DATE_ADDED, nowInSec); - values.put(Video.Media.DATA, file.getAbsolutePath()); - values.put(Video.Media.SIZE, file.length()); - // Copy the data taken and location info from src. - String[] projection = new String[] { - VideoColumns.DATE_TAKEN, - VideoColumns.LATITUDE, - VideoColumns.LONGITUDE, - VideoColumns.RESOLUTION, - }; - - // Copy some info from the source file. - querySource(projection, new ContentResolverQueryCallback() { - @Override - public void onCursorResult(Cursor cursor) { - long timeTaken = cursor.getLong(0); - if (timeTaken > 0) { - values.put(Video.Media.DATE_TAKEN, timeTaken); - } - double latitude = cursor.getDouble(1); - double longitude = cursor.getDouble(2); - // TODO: Change || to && after the default location issue is - // fixed. - if ((latitude != 0f) || (longitude != 0f)) { - values.put(Video.Media.LATITUDE, latitude); - values.put(Video.Media.LONGITUDE, longitude); - } - values.put(Video.Media.RESOLUTION, cursor.getString(3)); - - } - }); - - return getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values); - } - @Override public void onPlayPause() { if (mVideoView.isPlaying()) { @@ -410,6 +316,8 @@ public class TrimVideo extends Activity implements mTrimStartTime = start; mTrimEndTime = end; setProgress(); + // Enable save if there's modifications + mSaveVideoTextView.setEnabled(isModified()); } @Override diff --git a/src/com/android/gallery3d/app/TrimVideoUtils.java b/src/com/android/gallery3d/app/VideoUtils.java index ae9b1e9ce..8ffc3d5eb 100644 --- a/src/com/android/gallery3d/app/TrimVideoUtils.java +++ b/src/com/android/gallery3d/app/VideoUtils.java @@ -19,6 +19,7 @@ package com.android.gallery3d.app; +import com.android.gallery3d.util.SaveVideoFileInfo; import com.coremedia.iso.IsoFile; import com.coremedia.iso.boxes.TimeToSampleBox; import com.googlecode.mp4parser.authoring.Movie; @@ -36,12 +37,46 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; -/** - * Shortens/Crops a track - */ -public class TrimVideoUtils { +public class VideoUtils { - public static void startTrim(File src, File dst, int startMs, int endMs) throws IOException { + public static void startMute(String filePath, SaveVideoFileInfo dstFileInfo) throws IOException { + File dst = dstFileInfo.mFile; + File src = new File(filePath); + RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r"); + Movie movie = MovieCreator.build(randomAccessFile.getChannel()); + + // remove all tracks we will create new tracks from the old + List<Track> tracks = movie.getTracks(); + movie.setTracks(new LinkedList<Track>()); + + for (Track track : tracks) { + if (track.getHandler().equals("vide")) { + movie.addTrack(track); + } + } + writeMovieIntoFile(dst, movie); + randomAccessFile.close(); + } + + private static void writeMovieIntoFile(File dst, Movie movie) + throws IOException { + if (!dst.exists()) { + dst.createNewFile(); + } + + IsoFile out = new DefaultMp4Builder().build(movie); + FileOutputStream fos = new FileOutputStream(dst); + FileChannel fc = fos.getChannel(); + out.getBox(fc); // This one build up the memory. + + fc.close(); + fos.close(); + } + + /** + * Shortens/Crops a track + */ + public static void startTrim(File src, File dst, int startMs, int endMs, int totalMs) throws IOException { RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r"); Movie movie = MovieCreator.build(randomAccessFile.getChannel()); @@ -100,18 +135,7 @@ public class TrimVideoUtils { } movie.addTrack(new CroppedTrack(track, startSample, endSample)); } - IsoFile out = new DefaultMp4Builder().build(movie); - - if (!dst.exists()) { - dst.createNewFile(); - } - - FileOutputStream fos = new FileOutputStream(dst); - FileChannel fc = fos.getChannel(); - out.getBox(fc); // This one build up the memory. - - fc.close(); - fos.close(); + writeMovieIntoFile(dst, movie); randomAccessFile.close(); } diff --git a/src/com/android/gallery3d/app/Wallpaper.java b/src/com/android/gallery3d/app/Wallpaper.java index 996d3f080..1bbe8d2c6 100644 --- a/src/com/android/gallery3d/app/Wallpaper.java +++ b/src/com/android/gallery3d/app/Wallpaper.java @@ -26,6 +26,8 @@ import android.os.Bundle; import android.view.Display; import com.android.gallery3d.common.ApiHelper; +import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.filtershow.CropExtras; /** * Wallpaper picker for the gallery application. This just redirects to the @@ -98,19 +100,18 @@ public class Wallpaper extends Activity { Point size = getDefaultDisplaySize(new Point()); float spotlightX = (float) size.x / width; float spotlightY = (float) size.y / height; - Intent request = new Intent(CropImage.ACTION_CROP) + Intent request = new Intent(FilterShowActivity.CROP_ACTION) .setDataAndType(mPickedItem, IMAGE_TYPE) .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) - .putExtra(CropImage.KEY_OUTPUT_X, width) - .putExtra(CropImage.KEY_OUTPUT_Y, height) - .putExtra(CropImage.KEY_ASPECT_X, width) - .putExtra(CropImage.KEY_ASPECT_Y, height) - .putExtra(CropImage.KEY_SPOTLIGHT_X, spotlightX) - .putExtra(CropImage.KEY_SPOTLIGHT_Y, spotlightY) - .putExtra(CropImage.KEY_SCALE, true) - .putExtra(CropImage.KEY_SCALE_UP_IF_NEEDED, true) - .putExtra(CropImage.KEY_NO_FACE_DETECTION, true) - .putExtra(CropImage.KEY_SET_AS_WALLPAPER, true); + .putExtra(CropExtras.KEY_OUTPUT_X, width) + .putExtra(CropExtras.KEY_OUTPUT_Y, height) + .putExtra(CropExtras.KEY_ASPECT_X, width) + .putExtra(CropExtras.KEY_ASPECT_Y, height) + .putExtra(CropExtras.KEY_SPOTLIGHT_X, spotlightX) + .putExtra(CropExtras.KEY_SPOTLIGHT_Y, spotlightY) + .putExtra(CropExtras.KEY_SCALE, true) + .putExtra(CropExtras.KEY_SCALE_UP_IF_NEEDED, true) + .putExtra(CropExtras.KEY_SET_AS_WALLPAPER, true); startActivity(request); finish(); } |