diff options
Diffstat (limited to 'src/com/android/gallery3d/app')
23 files changed, 3556 insertions, 182 deletions
diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java index 9af1fb8ba..9f43b8de4 100644 --- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java +++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java @@ -28,15 +28,20 @@ import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; +import android.os.Environment; import android.os.IBinder; +import android.os.storage.StorageManager; +import android.preference.PreferenceManager; import android.support.v4.print.PrintHelper; import android.view.Menu; import android.view.MenuItem; import android.view.Window; import android.view.WindowManager; +import android.os.Handler; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; @@ -45,6 +50,7 @@ import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.ui.GLRoot; import com.android.gallery3d.ui.GLRootView; +import com.android.gallery3d.util.MediaSetUtils; import com.android.gallery3d.util.PanoramaViewHelper; import com.android.gallery3d.util.ThreadPool; import com.android.photos.data.GalleryBitmapPool; @@ -60,6 +66,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext private TransitionStore mTransitionStore = new TransitionStore(); private boolean mDisableToggleStatusBar; private PanoramaViewHelper mPanoramaViewHelper; + private static final int ONRESUME_DELAY = 50; private AlertDialog mAlertDialog = null; private BroadcastReceiver mMountReceiver = new BroadcastReceiver() { @@ -73,6 +80,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setStoragePath(); mOrientationManager = new OrientationManager(this); toggleStatusBarByOrientation(); getWindow().setBackgroundDrawable(null); @@ -81,6 +89,21 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext doBindBatchService(); } + private void setStoragePath() { + final String defaultStoragePath = Environment.getExternalStorageDirectory().toString(); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + String storagePath = prefs.getString(StorageChangeReceiver.KEY_STORAGE, + defaultStoragePath); + + // Check if volume is mounted + StorageManager sm = (StorageManager) getSystemService(Context.STORAGE_SERVICE); + if (!sm.getVolumeState(storagePath).equals(Environment.MEDIA_MOUNTED)) { + storagePath = defaultStoragePath; + } + + MediaSetUtils.setRoot(storagePath); + } + @Override protected void onSaveInstanceState(Bundle outState) { mGLRootView.lockRenderThread(); @@ -133,6 +156,15 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext return mGLRootView; } + public void GLRootResume(boolean isResume) { + if (isResume) { + mGLRootView.onResume(); + mGLRootView.lockRenderThread(); + } else { + mGLRootView.unlockRenderThread(); + } + } + public OrientationManager getOrientationManager() { return mOrientationManager; } @@ -203,15 +235,31 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext @Override protected void onResume() { super.onResume(); - mGLRootView.lockRenderThread(); - try { - getStateManager().resume(); - getDataManager().resume(); - } finally { - mGLRootView.unlockRenderThread(); - } - mGLRootView.onResume(); - mOrientationManager.resume(); + delayedOnResume(ONRESUME_DELAY); + } + + private void delayedOnResume(final int delay){ + final Handler handler = new Handler(); + Runnable delayTask = new Runnable() { + @Override + public void run() { + handler.postDelayed(new Runnable() { + @Override + public void run() { + mGLRootView.lockRenderThread(); + try { + getStateManager().resume(); + getDataManager().resume(); + } finally { + mGLRootView.unlockRenderThread(); + } + mGLRootView.onResume(); + mOrientationManager.resume(); + }}, delay); + } + }; + Thread delayThread = new Thread(delayTask); + delayThread.start(); } @Override diff --git a/src/com/android/gallery3d/app/AlbumDataLoader.java b/src/com/android/gallery3d/app/AlbumDataLoader.java index 28a822830..b56304e39 100644 --- a/src/com/android/gallery3d/app/AlbumDataLoader.java +++ b/src/com/android/gallery3d/app/AlbumDataLoader.java @@ -19,6 +19,8 @@ package com.android.gallery3d.app; import android.os.Handler; import android.os.Message; import android.os.Process; +import android.text.TextUtils; +import android.view.View; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.ContentListener; @@ -33,6 +35,7 @@ import java.util.Arrays; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; +import java.util.Locale; public class AlbumDataLoader { @SuppressWarnings("unused") @@ -119,7 +122,9 @@ public class AlbumDataLoader { } public MediaItem get(int index) { - if (!isActive(index)) { + if (!isActive(index) + && View.LAYOUT_DIRECTION_LTR == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { 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 44f24043b..7e2f5f9e4 100644 --- a/src/com/android/gallery3d/app/AlbumPage.java +++ b/src/com/android/gallery3d/app/AlbumPage.java @@ -25,10 +25,12 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.provider.MediaStore; +import android.text.TextUtils; import android.view.HapticFeedbackConstants; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; import android.widget.Toast; import com.android.gallery3d.R; @@ -59,6 +61,7 @@ import com.android.gallery3d.util.Future; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.MediaSetUtils; +import java.util.Locale; public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner, SelectionManager.SelectionListener, MediaSet.SyncListener, GalleryActionBar.OnAlbumModeSelectedListener { @@ -82,11 +85,15 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster private static final float USER_DISTANCE_METER = 0.3f; + // Data cache size, equal to AlbumDataLoader.DATA_CACHE_SIZE + private static final int DATA_CACHE_SIZE = 256; + private boolean mIsActive = false; private AlbumSlotRenderer mAlbumView; private Path mMediaSetPath; private String mParentMediaSetString; private SlotView mSlotView; + private Config.AlbumPage mConfig; private AlbumDataLoader mAlbumDataAdapter; @@ -152,9 +159,9 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster protected void onLayout( boolean changed, int left, int top, int right, int bottom) { - int slotViewTop = mActivity.getGalleryActionBar().getHeight(); - int slotViewBottom = bottom - top; - int slotViewRight = right - left; + int slotViewTop = mActivity.getGalleryActionBar().getHeight() + mConfig.paddingTop; + int slotViewBottom = bottom - top - mConfig.paddingBottom; + int slotViewRight = right - left - mConfig.paddingRight; if (mShowDetails) { mDetailsHelper.layout(left, slotViewTop, right, bottom); @@ -163,8 +170,8 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster } // Set the mSlotView as a reference point to the open animation - mOpenCenter.setReferencePosition(0, slotViewTop); - mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom); + mOpenCenter.setReferencePosition(mConfig.paddingLeft, slotViewTop); + mSlotView.layout(mConfig.paddingLeft, slotViewTop, slotViewRight, slotViewBottom); GalleryUtils.setViewPointMatrix(mMatrix, (right - left) / 2, (bottom - top) / 2, -mUserDistance); } @@ -266,6 +273,17 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster } private void pickPhoto(int slotIndex) { + if ((View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) + && !mGetContent) { + // Fetch corresponding slotIndex from another side, (RTL) + if (slotIndex > DATA_CACHE_SIZE / 2 + && slotIndex < mAlbumDataAdapter.size() - DATA_CACHE_SIZE / 2) { + slotIndex = mAlbumDataAdapter.size() - slotIndex - 2; + } else { + slotIndex = mAlbumDataAdapter.size() - slotIndex - 1; + } + } pickPhoto(slotIndex, false); } @@ -278,10 +296,25 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster } MediaItem item = mAlbumDataAdapter.get(slotIndex); - if (item == null) return; // Item not ready yet, ignore the click + + // Checking it is RTL or not + boolean isLayoutRtl = (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) ? true : false; + + // When not RTL, return directly to ignore the click + if (!isLayoutRtl && item == null) { + return; + } + if (mGetContent) { + if (isLayoutRtl && item == null) { + return; // Item not ready yet, ignore the click + } onGetContent(item); } else if (mLaunchedFromPhotoPage) { + if (isLayoutRtl && item == null) { + return; // Item not ready yet, ignore the click + } TransitionStore transitions = mActivity.getTransitionStore(); transitions.put( PhotoPage.KEY_ALBUMPAGE_TRANSITION, @@ -297,8 +330,12 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mSlotView.getSlotRect(slotIndex, mRootPane)); data.putString(PhotoPage.KEY_MEDIA_SET_PATH, mMediaSetPath.toString()); - data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, - item.getPath().toString()); + + // Item not ready yet, don't pass the photo path to bundle + if (!isLayoutRtl && item != null) { + data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, + item.getPath().toString()); + } data.putInt(PhotoPage.KEY_ALBUMPAGE_TRANSITION, PhotoPage.MSG_ALBUMPAGE_STARTED); data.putBoolean(PhotoPage.KEY_START_IN_FILMSTRIP, @@ -468,10 +505,10 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster private void initializeViews() { mSelectionManager = new SelectionManager(mActivity, false); mSelectionManager.setSelectionListener(this); - Config.AlbumPage config = Config.AlbumPage.get(mActivity); - mSlotView = new SlotView(mActivity, config.slotViewSpec); + mConfig = Config.AlbumPage.get(mActivity); + mSlotView = new SlotView(mActivity, mConfig.slotViewSpec); mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView, - mSelectionManager, config.placeholderColor); + mSelectionManager, mConfig.placeholderColor); mSlotView.setSlotRenderer(mAlbumView); mRootPane.addComponent(mSlotView); mSlotView.setListener(new SlotView.SimpleListener() { @@ -574,8 +611,17 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster } private void switchToFilmstrip() { - if (mAlbumDataAdapter.size() < 1) return; + // Invalid album, return back directly. + if (mAlbumDataAdapter.size() < 1) { + return; + } + int targetPhoto = mSlotView.getVisibleStart(); + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { + // Fetch corresponding index from another side, only in RTL + targetPhoto = mAlbumDataAdapter.size() - targetPhoto - 1; + } prepareAnimationBackToFilmstrip(targetPhoto); if(mLaunchedFromPhotoPage) { onBackPressed(); @@ -642,7 +688,22 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster case REQUEST_PHOTO: { if (data == null) return; mFocusIndex = data.getIntExtra(PhotoPage.KEY_RETURN_INDEX_HINT, 0); + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { + // Fetch corresponding index from another side, only in RTL + mFocusIndex = mAlbumDataAdapter.size() - mFocusIndex - 1; + // Prepare to jump to mFocusIndex position, only enabled in RTL + mSlotView.setIsFromPhotoPage(true); + } + + // Let picture of mFocusIndex visible mSlotView.makeSlotVisible(mFocusIndex); + + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { + // Reset variable + mSlotView.setIsFromPhotoPage(false); + } break; } case REQUEST_DO_ANIMATION: { diff --git a/src/com/android/gallery3d/app/AlbumSetPage.java b/src/com/android/gallery3d/app/AlbumSetPage.java index d56b5b85d..c09b91f6e 100644 --- a/src/com/android/gallery3d/app/AlbumSetPage.java +++ b/src/com/android/gallery3d/app/AlbumSetPage.java @@ -1,4 +1,7 @@ /* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * Not a Contribution. + * * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +23,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Rect; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -134,7 +138,7 @@ public class AlbumSetPage extends ActivityState implements int slotViewTop = mActionBar.getHeight() + mConfig.paddingTop; int slotViewBottom = bottom - top - mConfig.paddingBottom; - int slotViewRight = right - left; + int slotViewRight = right - left - mConfig.paddingRight; if (mShowDetails) { mDetailsHelper.layout(left, slotViewTop, right, bottom); @@ -142,7 +146,7 @@ public class AlbumSetPage extends ActivityState implements mAlbumSetView.setHighlightItemPath(null); } - mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom); + mSlotView.layout(mConfig.paddingLeft, slotViewTop, slotViewRight, slotViewBottom); } @Override @@ -543,6 +547,13 @@ public class AlbumSetPage extends ActivityState implements inflater.inflate(R.menu.albumset, menu); boolean wasShowingClusterMenu = mShowClusterMenu; mShowClusterMenu = !inAlbum; + if (mShowClusterMenu != wasShowingClusterMenu) { + if (mShowClusterMenu) { + mActionBar.enableClusterMenu(mSelectedAction, this); + } else { + mActionBar.disableClusterMenu(true); + } + } boolean selectAlbums = !inAlbum && mActionBar.getClusterTypeAction() == FilterUtils.CLUSTER_BY_ALBUM; MenuItem selectItem = menu.findItem(R.id.action_select); @@ -560,15 +571,13 @@ public class AlbumSetPage extends ActivityState implements helpItem.setVisible(helpIntent != null); if (helpIntent != null) helpItem.setIntent(helpIntent); + MenuItem moreItem = menu.findItem(R.id.action_more_image); + moreItem.setVisible(mActivity.getResources().getBoolean( + R.bool.config_show_more_images)); + + mActionBar.setTitle(mTitle); mActionBar.setSubtitle(mSubtitle); - if (mShowClusterMenu != wasShowingClusterMenu) { - if (mShowClusterMenu) { - mActionBar.enableClusterMenu(mSelectedAction, this); - } else { - mActionBar.disableClusterMenu(true); - } - } } return true; } @@ -577,6 +586,11 @@ public class AlbumSetPage extends ActivityState implements protected boolean onItemSelected(MenuItem item) { Activity activity = mActivity; switch (item.getItemId()) { + case R.id.action_more_image: + Uri moreUri = Uri.parse(mActivity.getString(R.string.website_for_more_image)); + Intent moreIntent = new Intent(Intent.ACTION_VIEW, moreUri); + mActivity.startActivity(moreIntent); + return true; case R.id.action_cancel: activity.setResult(Activity.RESULT_CANCELED); activity.finish(); diff --git a/src/com/android/gallery3d/app/CommonControllerOverlay.java b/src/com/android/gallery3d/app/CommonControllerOverlay.java index 9adb4e7a8..7a553e906 100644 --- a/src/com/android/gallery3d/app/CommonControllerOverlay.java +++ b/src/com/android/gallery3d/app/CommonControllerOverlay.java @@ -47,10 +47,13 @@ public abstract class CommonControllerOverlay extends FrameLayout implements PAUSED, ENDED, ERROR, - LOADING + LOADING, + BUFFERING, + RETRY_CONNECTING, + RETRY_CONNECTING_ERROR } - private static final float ERROR_MESSAGE_RELATIVE_PADDING = 1.0f / 6; + protected static final float ERROR_MESSAGE_RELATIVE_PADDING = 1.0f / 6; protected Listener mListener; @@ -96,13 +99,9 @@ public abstract class CommonControllerOverlay extends FrameLayout implements ProgressBar spinner = new ProgressBar(context); spinner.setIndeterminate(true); mLoadingView.addView(spinner, wrapContent); - TextView loadingText = createOverlayTextView(context); - loadingText.setText(R.string.loading_video); - mLoadingView.addView(loadingText, wrapContent); addView(mLoadingView, wrapContent); mPlayPauseReplayView = new ImageView(context); - mPlayPauseReplayView.setImageResource(R.drawable.ic_vidcontrol_play); mPlayPauseReplayView.setContentDescription( context.getResources().getString(R.string.accessibility_play_video)); mPlayPauseReplayView.setBackgroundResource(R.drawable.bg_vidcontrol); @@ -119,7 +118,6 @@ public abstract class CommonControllerOverlay extends FrameLayout implements new RelativeLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); setLayoutParams(params); - hide(); } abstract protected void createTimeBar(Context context); @@ -252,7 +250,7 @@ public abstract class CommonControllerOverlay extends FrameLayout implements // | Navigation Bar | insets.bottom // +-----------------+/ // Please see View.fitSystemWindows() for more details. - private final Rect mWindowInsets = new Rect(); + protected final Rect mWindowInsets = new Rect(); @Override protected boolean fitSystemWindows(Rect insets) { @@ -290,7 +288,7 @@ public abstract class CommonControllerOverlay extends FrameLayout implements } } - private void layoutCenteredView(View view, int l, int t, int r, int b) { + protected void layoutCenteredView(View view, int l, int t, int r, int b) { int cw = view.getMeasuredWidth(); int ch = view.getMeasuredHeight(); int cl = (r - l - cw) / 2; diff --git a/src/com/android/gallery3d/app/Config.java b/src/com/android/gallery3d/app/Config.java index 7183acc33..3625dafe4 100644 --- a/src/com/android/gallery3d/app/Config.java +++ b/src/com/android/gallery3d/app/Config.java @@ -31,6 +31,8 @@ final class Config { public AlbumSetSlotRenderer.LabelSpec labelSpec; public int paddingTop; public int paddingBottom; + public int paddingLeft; + public int paddingRight; public int placeholderColor; public static synchronized AlbumSetPage get(Context context) { @@ -48,11 +50,15 @@ final class Config { slotViewSpec = new SlotView.Spec(); slotViewSpec.rowsLand = r.getInteger(R.integer.albumset_rows_land); slotViewSpec.rowsPort = r.getInteger(R.integer.albumset_rows_port); + slotViewSpec.colsLand = r.getInteger(R.integer.albumset_cols_land); + slotViewSpec.colsPort = r.getInteger(R.integer.albumset_cols_port); slotViewSpec.slotGap = r.getDimensionPixelSize(R.dimen.albumset_slot_gap); slotViewSpec.slotHeightAdditional = 0; paddingTop = r.getDimensionPixelSize(R.dimen.albumset_padding_top); paddingBottom = r.getDimensionPixelSize(R.dimen.albumset_padding_bottom); + paddingLeft = r.getDimensionPixelSize(R.dimen.albumset_padding_left); + paddingRight = r.getDimensionPixelSize(R.dimen.albumset_padding_right); labelSpec = new AlbumSetSlotRenderer.LabelSpec(); labelSpec.labelBackgroundHeight = r.getDimensionPixelSize( @@ -82,6 +88,10 @@ final class Config { private static AlbumPage sInstance; public SlotView.Spec slotViewSpec; + public int paddingTop; + public int paddingBottom; + public int paddingLeft; + public int paddingRight; public int placeholderColor; public static synchronized AlbumPage get(Context context) { @@ -99,7 +109,14 @@ final class Config { slotViewSpec = new SlotView.Spec(); slotViewSpec.rowsLand = r.getInteger(R.integer.album_rows_land); slotViewSpec.rowsPort = r.getInteger(R.integer.album_rows_port); + slotViewSpec.colsLand = r.getInteger(R.integer.album_cols_land); + slotViewSpec.colsPort = r.getInteger(R.integer.album_cols_port); slotViewSpec.slotGap = r.getDimensionPixelSize(R.dimen.album_slot_gap); + + paddingTop = r.getDimensionPixelSize(R.dimen.album_padding_top); + paddingBottom = r.getDimensionPixelSize(R.dimen.album_padding_bottom); + paddingLeft = r.getDimensionPixelSize(R.dimen.album_padding_left); + paddingRight = r.getDimensionPixelSize(R.dimen.album_padding_right); } } diff --git a/src/com/android/gallery3d/app/ControllerOverlay.java b/src/com/android/gallery3d/app/ControllerOverlay.java index 078f59e28..6f049da2d 100644 --- a/src/com/android/gallery3d/app/ControllerOverlay.java +++ b/src/com/android/gallery3d/app/ControllerOverlay.java @@ -27,6 +27,8 @@ public interface ControllerOverlay { void onSeekEnd(int time, int trimStartTime, int trimEndTime); void onShown(); void onHidden(); + // get current video is from RTSP + boolean onIsRTSP(); void onReplay(); } @@ -53,4 +55,10 @@ public interface ControllerOverlay { void setTimes(int currentTime, int totalTime, int trimStartTime, int trimEndTime); + + //set view enabled (play/pause asynchronous processing) + void setViewEnabled(boolean isEnabled); + + //view from disable to resume (play/pause asynchronous processing) + void setPlayPauseReplayResume(); } diff --git a/src/com/android/gallery3d/app/GalleryActionBar.java b/src/com/android/gallery3d/app/GalleryActionBar.java index 588f5842a..11057fb02 100644 --- a/src/com/android/gallery3d/app/GalleryActionBar.java +++ b/src/com/android/gallery3d/app/GalleryActionBar.java @@ -47,6 +47,7 @@ public class GalleryActionBar implements OnNavigationListener { private ClusterRunner mClusterRunner; private CharSequence[] mTitles; + private CharSequence mTitle; private ArrayList<Integer> mActions; private Context mContext; private LayoutInflater mInflater; @@ -159,7 +160,8 @@ public class GalleryActionBar implements OnNavigationListener { parent, false); } TwoLineListItem view = (TwoLineListItem) convertView; - view.getText1().setText(mActionBar.getTitle()); + CharSequence title = mActionBar.getTitle(); + view.getText1().setText(title == null ? mTitle : title); view.getText2().setText((CharSequence) getItem(position)); return convertView; } @@ -326,12 +328,14 @@ public class GalleryActionBar implements OnNavigationListener { } public void setTitle(String title) { + mTitle = title; if (mActionBar != null) mActionBar.setTitle(title); } public void setTitle(int titleId) { if (mActionBar != null) { - mActionBar.setTitle(mContext.getString(titleId)); + mTitle = mContext.getString(titleId); + mActionBar.setTitle(mTitle); } } diff --git a/src/com/android/gallery3d/app/GalleryActivity.java b/src/com/android/gallery3d/app/GalleryActivity.java index bb2a6b8f1..1be5e73c8 100644 --- a/src/com/android/gallery3d/app/GalleryActivity.java +++ b/src/com/android/gallery3d/app/GalleryActivity.java @@ -50,6 +50,7 @@ public final class GalleryActivity extends AbstractGalleryActivity implements On 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"; + public static final String KEY_FROM_SNAPCAM = "from-snapcam"; private static final String TAG = "GalleryActivity"; private Dialog mVersionCheckDialog; @@ -206,7 +207,9 @@ public final class GalleryActivity extends AbstractGalleryActivity implements On Path albumPath = dm.getDefaultSetOf(itemPath); data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, itemPath.toString()); - data.putBoolean(PhotoPage.KEY_READONLY, true); + if (!intent.getBooleanExtra(KEY_FROM_SNAPCAM, false)) { + data.putBoolean(PhotoPage.KEY_READONLY, true); + } // TODO: Make the parameter "SingleItemOnly" public so other // activities can reference it. diff --git a/src/com/android/gallery3d/app/GalleryAppImpl.java b/src/com/android/gallery3d/app/GalleryAppImpl.java index c6e7a0b57..9c5f232df 100644 --- a/src/com/android/gallery3d/app/GalleryAppImpl.java +++ b/src/com/android/gallery3d/app/GalleryAppImpl.java @@ -36,6 +36,7 @@ public class GalleryAppImpl extends Application implements GalleryApp { private static final String DOWNLOAD_FOLDER = "download"; private static final long DOWNLOAD_CAPACITY = 64 * 1024 * 1024; // 64M + private static GalleryAppImpl sGalleryAppImpl; private ImageCacheService mImageCacheService; private Object mLock = new Object(); @@ -51,6 +52,7 @@ public class GalleryAppImpl extends Application implements GalleryApp { WidgetUtils.initialize(this); PicasaSource.initialize(this); UsageStatistics.initialize(this); + sGalleryAppImpl = this; } @Override @@ -58,6 +60,10 @@ public class GalleryAppImpl extends Application implements GalleryApp { return this; } + public static Context getContext() { + return sGalleryAppImpl; + } + @Override public synchronized DataManager getDataManager() { if (mDataManager == null) { diff --git a/src/com/android/gallery3d/app/MovieActivity.java b/src/com/android/gallery3d/app/MovieActivity.java index 1547f6faf..275b04ab3 100644 --- a/src/com/android/gallery3d/app/MovieActivity.java +++ b/src/com/android/gallery3d/app/MovieActivity.java @@ -18,31 +18,62 @@ package com.android.gallery3d.app; import android.annotation.TargetApi; import android.app.ActionBar; +import android.app.ActionBar.OnMenuVisibilityListener; import android.app.Activity; +import android.app.AlertDialog; +import android.app.KeyguardManager; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; import android.content.AsyncQueryHandler; +import android.content.BroadcastReceiver; import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.media.AudioManager; +import android.media.audiofx.AudioEffect; +import android.media.audiofx.AudioEffect.Descriptor; +import android.media.audiofx.BassBoost; +import android.media.audiofx.Virtualizer; +import android.media.MediaPlayer; import android.net.Uri; +import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.provider.MediaStore; import android.provider.OpenableColumns; +import android.view.Gravity; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.view.WindowManager; +import android.widget.CompoundButton; import android.widget.ShareActionProvider; +import android.widget.ToggleButton; +import android.widget.Toast; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.ui.Knob; +import org.codeaurora.gallery3d.ext.IActivityHooker; +import org.codeaurora.gallery3d.ext.IMovieItem; +import org.codeaurora.gallery3d.ext.MovieItem; +import org.codeaurora.gallery3d.ext.MovieUtils; +import org.codeaurora.gallery3d.video.ExtensionHelper; +import org.codeaurora.gallery3d.video.MovieTitleHelper; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothProfile; /** * This activity plays a video from a specified URI. @@ -53,14 +84,77 @@ import com.android.gallery3d.common.Utils; */ public class MovieActivity extends Activity { @SuppressWarnings("unused") - private static final String TAG = "MovieActivity"; - public static final String KEY_LOGO_BITMAP = "logo-bitmap"; - public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back"; + private static final String TAG = "MovieActivity"; + private static final boolean LOG = false; + public static final String KEY_LOGO_BITMAP = "logo-bitmap"; + public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back"; + private static final String VIDEO_SDP_MIME_TYPE = "application/sdp"; + private static final String VIDEO_SDP_TITLE = "rtsp://"; + private static final String VIDEO_FILE_SCHEMA = "file"; + private static final String VIDEO_MIME_TYPE = "video/*"; + private static final String SHARE_HISTORY_FILE = "video_share_history_file"; private MoviePlayer mPlayer; - private boolean mFinishOnCompletion; - private Uri mUri; - private boolean mTreatUpAsBack; + private boolean mFinishOnCompletion; + private Uri mUri; + + private static final short BASSBOOST_MAX_STRENGTH = 1000; + private static final short VIRTUALIZER_MAX_STRENGTH = 1000; + + private boolean mIsHeadsetOn = false; + private boolean mVirtualizerSupported = false; + private boolean mBassBoostSupported = false; + + static enum Key { + global_enabled, bb_strength, virt_strength + }; + + private BassBoost mBassBoostEffect; + private Virtualizer mVirtualizerEffect; + private AlertDialog mEffectDialog; + private ToggleButton mSwitch; + private Knob mBassBoostKnob; + private Knob mVirtualizerKnob; + + private SharedPreferences mPrefs; + private ShareActionProvider mShareProvider; + private IMovieItem mMovieItem; + private IActivityHooker mMovieHooker; + private KeyguardManager mKeyguardManager; + + private boolean mResumed = false; + private boolean mControlResumed = false; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + final String action = intent.getAction(); + final AudioManager audioManager = + (AudioManager) getSystemService(Context.AUDIO_SERVICE); + if (action.equals(Intent.ACTION_HEADSET_PLUG)) { + mIsHeadsetOn = (intent.getIntExtra("state", 0) == 1) + || audioManager.isBluetoothA2dpOn(); + } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED) + || action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { + final int deviceClass = ((BluetoothDevice) + intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)) + .getBluetoothClass().getDeviceClass(); + if ((deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES) + || (deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET)) { + mIsHeadsetOn = action.equals(BluetoothDevice.ACTION_ACL_CONNECTED) + || audioManager.isWiredHeadsetOn(); + } + } else if (action.equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { + mIsHeadsetOn = false; + } + if (mEffectDialog != null) { + if (!mIsHeadsetOn && !isBtHeadsetConnected() && mEffectDialog.isShowing()) { + mEffectDialog.dismiss(); + showHeadsetPlugToast(); + } + } + } + }; @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void setSystemUiVisibility(View rootView) { @@ -84,16 +178,24 @@ public class MovieActivity extends Activity { setSystemUiVisibility(rootView); Intent intent = getIntent(); + + mMovieHooker = ExtensionHelper.getHooker(this); + initMovieInfo(intent); + initializeActionBar(intent); mFinishOnCompletion = intent.getBooleanExtra( MediaStore.EXTRA_FINISH_ON_COMPLETION, true); - mTreatUpAsBack = intent.getBooleanExtra(KEY_TREAT_UP_AS_BACK, false); - mPlayer = new MoviePlayer(rootView, this, intent.getData(), savedInstanceState, + mPrefs = getSharedPreferences(getApplicationContext().getPackageName(), + Context.MODE_PRIVATE); + mPlayer = new MoviePlayer(rootView, this, mMovieItem, savedInstanceState, !mFinishOnCompletion) { @Override public void onCompletion() { if (mFinishOnCompletion) { - finish(); + finishActivity(); + mControlResumed = false; + Bookmarker mBookmarker = new Bookmarker(MovieActivity.this); + mBookmarker.setBookmark(mMovieItem.getUri(), 0, 1); } } }; @@ -114,6 +216,29 @@ public class MovieActivity extends Activity { // We set the background in the theme to have the launching animation. // But for the performance (and battery), we remove the background here. win.setBackgroundDrawable(null); + mMovieHooker.init(this, intent); + mMovieHooker.setParameter(null, mPlayer.getMoviePlayerExt()); + mMovieHooker.setParameter(null, mMovieItem); + mMovieHooker.setParameter(null, mPlayer.getVideoSurface()); + mMovieHooker.onCreate(savedInstanceState); + + // Determine available/supported effects + final Descriptor[] effects = AudioEffect.queryEffects(); + for (final Descriptor effect : effects) { + if (effect.type.equals(AudioEffect.EFFECT_TYPE_VIRTUALIZER)) { + mVirtualizerSupported = true; + } else if (effect.type.equals(AudioEffect.EFFECT_TYPE_BASS_BOOST)) { + mBassBoostSupported = true; + } + } + + mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + mPlayer.onPrepared(mp); + initEffects(mp.getAudioSessionId()); + } + }); } private void setActionBarLogoFromIntent(Intent intent) { @@ -132,9 +257,21 @@ public class MovieActivity extends Activity { } setActionBarLogoFromIntent(intent); actionBar.setDisplayOptions( - ActionBar.DISPLAY_HOME_AS_UP, - ActionBar.DISPLAY_HOME_AS_UP); + ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE, + ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE); + actionBar.addOnMenuVisibilityListener(new OnMenuVisibilityListener() { + @Override + public void onMenuVisibilityChanged(boolean isVisible) { + if (mPlayer != null) { + if (isVisible) { + mPlayer.cancelHidingController(); + } else { + mPlayer.restartHidingController(); + } + } + } + }); String title = intent.getStringExtra(Intent.EXTRA_TITLE); if (title != null) { actionBar.setTitle(title); @@ -170,24 +307,193 @@ public class MovieActivity extends Activity { public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.movie, menu); + MenuItem shareMenu = menu.findItem(R.id.action_share); + ShareActionProvider provider = (ShareActionProvider) shareMenu.getActionProvider(); + mShareProvider = provider; + if (mShareProvider != null) { + // share provider is singleton, we should refresh our history file. + mShareProvider.setShareHistoryFileName(SHARE_HISTORY_FILE); + } + refreshShareProvider(mMovieItem); + + final MenuItem mi = menu.add(R.string.audio_effects); + mi.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + onAudioEffectsMenuItemClick(); + return true; + } + }); + mMovieHooker.onCreateOptionsMenu(menu); + return true; + } - // Document says EXTRA_STREAM should be a content: Uri - // So, we only share the video if it's "content:". - MenuItem shareItem = menu.findItem(R.id.action_share); - if (ContentResolver.SCHEME_CONTENT.equals(mUri.getScheme())) { - shareItem.setVisible(true); - ((ShareActionProvider) shareItem.getActionProvider()) - .setShareIntent(createShareIntent()); + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + mMovieHooker.onPrepareOptionsMenu(menu); + return true; + } + + private void onAudioEffectsMenuItemClick() { + if (!mIsHeadsetOn && !isBtHeadsetConnected()) { + showHeadsetPlugToast(); } else { - shareItem.setVisible(false); + LayoutInflater factory = LayoutInflater.from(this); + final View content = factory.inflate(R.layout.audio_effects_dialog, null); + final View title = factory.inflate(R.layout.audio_effects_title, null); + + boolean enabled = mPrefs.getBoolean(Key.global_enabled.toString(), false); + + mSwitch = (ToggleButton) title.findViewById(R.id.audio_effects_switch); + mSwitch.setChecked(enabled); + mSwitch.setBackgroundResource(enabled ? + R.drawable.switch_thumb_activated : R.drawable.switch_thumb_off); + + mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mSwitch.setBackgroundResource(isChecked ? + R.drawable.switch_thumb_activated : R.drawable.switch_thumb_off); + if(mBassBoostEffect != null) { + mBassBoostEffect.setEnabled(isChecked); + } + if(mVirtualizerEffect != null) { + mVirtualizerEffect.setEnabled(isChecked); + } + mBassBoostKnob.setEnabled(isChecked); + mVirtualizerKnob.setEnabled(isChecked); + } + }); + + mBassBoostKnob = (Knob) content.findViewById(R.id.bBStrengthKnob); + mBassBoostKnob.setEnabled(enabled); + mBassBoostKnob.setMax(BASSBOOST_MAX_STRENGTH); + mBassBoostKnob.setValue(mPrefs.getInt(Key.bb_strength.toString(), 0)); + mBassBoostKnob.setOnKnobChangeListener(new Knob.OnKnobChangeListener() { + @Override + public void onValueChanged(Knob knob, int value, boolean fromUser) { + if(mBassBoostEffect != null) { + mBassBoostEffect.setStrength((short) value); + } + } + + @Override + public boolean onSwitchChanged(Knob knob, boolean enabled) { + return false; + } + }); + + mVirtualizerKnob = (Knob) content.findViewById(R.id.vIStrengthKnob); + mVirtualizerKnob.setEnabled(enabled); + mVirtualizerKnob.setMax(VIRTUALIZER_MAX_STRENGTH); + mVirtualizerKnob.setValue(mPrefs.getInt(Key.virt_strength.toString(), 0)); + mVirtualizerKnob.setOnKnobChangeListener(new Knob.OnKnobChangeListener() { + @Override + public void onValueChanged(Knob knob, int value, boolean fromUser) { + if(mVirtualizerEffect != null) { + mVirtualizerEffect.setStrength((short) value); + } + } + + @Override + public boolean onSwitchChanged(Knob knob, boolean enabled) { + return false; + } + }); + + mEffectDialog = new AlertDialog.Builder(MovieActivity.this, + AlertDialog.THEME_HOLO_DARK) + .setCustomTitle(title) + .setView(content) + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + SharedPreferences.Editor editor = mPrefs.edit(); + editor.putBoolean(Key.global_enabled.toString(), mSwitch.isChecked()); + editor.putInt(Key.bb_strength.toString(), mBassBoostKnob.getValue()); + editor.putInt(Key.virt_strength.toString(), + mVirtualizerKnob.getValue()); + editor.commit(); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + boolean enabled = mPrefs.getBoolean(Key.global_enabled.toString(), false); + if(mBassBoostEffect != null) { + mBassBoostEffect.setStrength((short) + mPrefs.getInt(Key.bb_strength.toString(), 0)); + mBassBoostEffect.setEnabled(enabled); + } + if(mVirtualizerEffect != null) { + mVirtualizerEffect.setStrength((short) + mPrefs.getInt(Key.virt_strength.toString(), 0)); + mVirtualizerEffect.setEnabled(enabled); + } + } + }) + .setCancelable(false) + .create(); + mEffectDialog.show(); + mEffectDialog.findViewById(com.android.internal.R.id.titleDivider) + .setBackgroundResource(R.color.highlight); + } + } + + public void initEffects(int sessionId) { + // Singleton instance + if ((mBassBoostEffect == null) && mBassBoostSupported) { + mBassBoostEffect = new BassBoost(0, sessionId); + } + + if ((mVirtualizerEffect == null) && mVirtualizerSupported) { + mVirtualizerEffect = new Virtualizer(0, sessionId); + } + + if (mIsHeadsetOn || isBtHeadsetConnected()) { + if (mPrefs.getBoolean(Key.global_enabled.toString(), false)) { + if (mBassBoostSupported) { + mBassBoostEffect.setStrength((short) + mPrefs.getInt(Key.bb_strength.toString(), 0)); + mBassBoostEffect.setEnabled(true); + } + if (mVirtualizerSupported) { + mVirtualizerEffect.setStrength((short) + mPrefs.getInt(Key.virt_strength.toString(), 0)); + mVirtualizerEffect.setEnabled(true); + } + } else { + if (mBassBoostSupported) { + mBassBoostEffect.setStrength((short) + mPrefs.getInt(Key.bb_strength.toString(), 0)); + } + if (mVirtualizerSupported) { + mVirtualizerEffect.setStrength((short) + mPrefs.getInt(Key.virt_strength.toString(), 0)); + } + } + } + + } + + public void releaseEffects() { + if (mBassBoostEffect != null) { + mBassBoostEffect.setEnabled(false); + mBassBoostEffect.release(); + mBassBoostEffect = null; + } + if (mVirtualizerEffect != null) { + mVirtualizerEffect.setEnabled(false); + mVirtualizerEffect.release(); + mVirtualizerEffect = null; } - return true; } private Intent createShareIntent() { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("video/*"); - intent.putExtra(Intent.EXTRA_STREAM, mUri); + intent.putExtra(Intent.EXTRA_STREAM, mMovieItem.getUri()); return intent; } @@ -195,19 +501,23 @@ public class MovieActivity extends Activity { public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == android.R.id.home) { - if (mTreatUpAsBack) { - finish(); - } else { - startActivity(new Intent(this, GalleryActivity.class)); - finish(); - } + // If click back up button, we will always finish current activity and + // back to previous one. + finish(); return true; } else if (id == R.id.action_share) { startActivity(Intent.createChooser(createShareIntent(), getString(R.string.share))); return true; } - return false; + return mMovieHooker.onOptionsItemSelected(item); + } + + public void showHeadsetPlugToast() { + final Toast toast = Toast.makeText(getApplicationContext(), R.string.headset_plug, + Toast.LENGTH_LONG); + toast.setGravity(Gravity.CENTER, toast.getXOffset() / 2, toast.getYOffset() / 2); + toast.show(); } @Override @@ -216,6 +526,8 @@ public class MovieActivity extends Activity { .requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); super.onStart(); + mMovieHooker.onStart(); + registerScreenReceiver(); } @Override @@ -223,18 +535,72 @@ public class MovieActivity extends Activity { ((AudioManager) getSystemService(AUDIO_SERVICE)) .abandonAudioFocus(null); super.onStop(); + if (mControlResumed && mPlayer != null) { + mPlayer.onStop(); + mControlResumed = false; + } + mMovieHooker.onStop(); + unregisterScreenReceiver(); } @Override public void onPause() { - mPlayer.onPause(); + // Audio track will be deallocated for local video playback, + // thus recycle effect here. + releaseEffects(); + try { + unregisterReceiver(mReceiver); + } catch (IllegalArgumentException e) { + // Do nothing + } + mResumed = false; + if (mControlResumed && mPlayer != null) { + mControlResumed = !mPlayer.onPause(); + } super.onPause(); + mMovieHooker.onPause(); } @Override public void onResume() { - mPlayer.onResume(); + invalidateOptionsMenu(); + + if ((mVirtualizerSupported) || (mBassBoostSupported)) { + final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); + intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); + intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + registerReceiver(mReceiver, intentFilter); + } + + mResumed = true; + if (!isKeyguardLocked() && !mControlResumed && mPlayer != null) { + mPlayer.onResume(); + mControlResumed = true; + //initEffects(mPlayer.getAudioSessionId()); + } + enhanceActionBar(); super.onResume(); + mMovieHooker.onResume(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if(this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE || + this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + mPlayer.setDefaultScreenMode(); + } + } + + private boolean isBtHeadsetConnected() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null && adapter.isEnabled() && + (BluetoothProfile.STATE_CONNECTED == adapter.getProfileConnectionState(BluetoothProfile.HEADSET) + || BluetoothProfile.STATE_CONNECTED == adapter.getProfileConnectionState(BluetoothProfile.A2DP))) { + return true; + } + return false; } @Override @@ -245,8 +611,24 @@ public class MovieActivity extends Activity { @Override public void onDestroy() { + releaseEffects(); mPlayer.onDestroy(); super.onDestroy(); + mMovieHooker.onDestroy(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (LOG) { + Log.v(TAG, "onWindowFocusChanged(" + hasFocus + ") isKeyguardLocked=" + + isKeyguardLocked() + + ", mResumed=" + mResumed + ", mControlResumed=" + mControlResumed); + } + if (hasFocus && !isKeyguardLocked() && mResumed && !mControlResumed && mPlayer != null) { + mPlayer.onResume(); + mControlResumed = true; + } } @Override @@ -260,4 +642,156 @@ public class MovieActivity extends Activity { return mPlayer.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); } + + private boolean isSharable() { + String scheme = mUri.getScheme(); + return ContentResolver.SCHEME_FILE.equals(scheme) + || (ContentResolver.SCHEME_CONTENT.equals(scheme) && MediaStore.AUTHORITY + .equals(mUri.getAuthority())); + } + private void initMovieInfo(Intent intent) { + Uri original = intent.getData(); + String mimeType = intent.getType(); + if (VIDEO_SDP_MIME_TYPE.equalsIgnoreCase(mimeType) + && VIDEO_FILE_SCHEMA.equalsIgnoreCase(original.getScheme())) { + mMovieItem = new MovieItem(VIDEO_SDP_TITLE + original, mimeType, null); + } else { + mMovieItem = new MovieItem(original, mimeType, null); + } + mMovieItem.setOriginalUri(original); + } + + // we do not stop live streaming when other dialog overlays it. + private BroadcastReceiver mScreenReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + if (LOG) { + Log.v(TAG, "onReceive(" + intent.getAction() + ") mControlResumed=" + + mControlResumed); + } + if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + // Only stop video. + if (mControlResumed) { + mPlayer.onStop(); + mControlResumed = false; + } + } else if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) { + if (!mControlResumed) { + mPlayer.onResume(); + mControlResumed = true; + } + } + } + + }; + + private void registerScreenReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_USER_PRESENT); + registerReceiver(mScreenReceiver, filter); + } + + private void unregisterScreenReceiver() { + unregisterReceiver(mScreenReceiver); + } + + private boolean isKeyguardLocked() { + if (mKeyguardManager == null) { + mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); + } + // isKeyguardSecure excludes the slide lock case. + boolean locked = (mKeyguardManager != null) + && mKeyguardManager.inKeyguardRestrictedInputMode(); + if (LOG) { + Log.v(TAG, "isKeyguardLocked() locked=" + locked + ", mKeyguardManager=" + + mKeyguardManager); + } + return locked; + } + + public void refreshMovieInfo(IMovieItem info) { + mMovieItem = info; + setActionBarTitle(info.getTitle()); + refreshShareProvider(info); + mMovieHooker.setParameter(null, mMovieItem); + } + + private void refreshShareProvider(IMovieItem info) { + // we only share the video if it's "content:". + if (mShareProvider != null) { + Intent intent = new Intent(Intent.ACTION_SEND); + if (MovieUtils.isLocalFile(info.getUri(), info.getMimeType())) { + intent.setType("video/*"); + intent.putExtra(Intent.EXTRA_STREAM, info.getUri()); + } else { + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(info.getUri())); + } + mShareProvider.setShareIntent(intent); + } + } + + private void enhanceActionBar() { + final IMovieItem movieItem = mMovieItem;// remember original item + final Uri uri = mMovieItem.getUri(); + final String scheme = mMovieItem.getUri().getScheme(); + final String authority = mMovieItem.getUri().getAuthority(); + new AsyncTask<Void, Void, String>() { + @Override + protected String doInBackground(Void... params) { + String title = null; + if (ContentResolver.SCHEME_FILE.equals(scheme)) { + title = MovieTitleHelper.getTitleFromMediaData(MovieActivity.this, uri); + } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) { + title = MovieTitleHelper.getTitleFromDisplayName(MovieActivity.this, uri); + if (title == null) { + title = MovieTitleHelper.getTitleFromData(MovieActivity.this, uri); + } + } + if (title == null) { + title = MovieTitleHelper.getTitleFromUri(uri); + } + if (LOG) { + Log.v(TAG, "enhanceActionBar() task return " + title); + } + return title; + } + + @Override + protected void onPostExecute(String result) { + if (LOG) { + Log.v(TAG, "onPostExecute(" + result + ") movieItem=" + movieItem + + ", mMovieItem=" + mMovieItem); + } + movieItem.setTitle(result); + if (movieItem == mMovieItem) { + setActionBarTitle(result); + } + }; + }.execute(); + if (LOG) { + Log.v(TAG, "enhanceActionBar() " + mMovieItem); + } + } + + public void setActionBarTitle(String title) { + if (LOG) { + Log.v(TAG, "setActionBarTitle(" + title + ")"); + } + ActionBar actionBar = getActionBar(); + if (title != null) { + actionBar.setTitle(title); + } + } + @Override + public void onBackPressed() { + finishActivity(); + } + private void finishActivity(){ + MovieActivity.this.finish(); + overridePendingTransition(0,0); + return; + } } diff --git a/src/com/android/gallery3d/app/MovieControllerOverlay.java b/src/com/android/gallery3d/app/MovieControllerOverlay.java index f01e619c6..c26b12655 100644 --- a/src/com/android/gallery3d/app/MovieControllerOverlay.java +++ b/src/com/android/gallery3d/app/MovieControllerOverlay.java @@ -17,14 +17,39 @@ package com.android.gallery3d.app; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.Rect; import android.os.Handler; +import android.util.DisplayMetrics; +import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; + import com.android.gallery3d.R; +import com.android.gallery3d.app.CommonControllerOverlay.State; +import org.codeaurora.gallery3d.ext.IContrllerOverlayExt; +import org.codeaurora.gallery3d.video.IControllerRewindAndForward; +import org.codeaurora.gallery3d.video.IControllerRewindAndForward.IRewindAndForwardListener; +import org.codeaurora.gallery3d.video.ExtensionHelper; +import org.codeaurora.gallery3d.video.ScreenModeManager; +import org.codeaurora.gallery3d.video.ScreenModeManager.ScreenModeListener; + /** * The playback controller for the Movie Player. @@ -32,15 +57,25 @@ import com.android.gallery3d.R; public class MovieControllerOverlay extends CommonControllerOverlay implements AnimationListener { + private static final String TAG = "Gallery3D/MovieControllerOverlay"; + private static final boolean LOG = false; + + private ScreenModeManager mScreenModeManager; + private ScreenModeExt mScreenModeExt = new ScreenModeExt(); + private ControllerRewindAndForwardExt mControllerRewindAndForwardExt = new ControllerRewindAndForwardExt(); + private OverlayExtension mOverlayExt = new OverlayExtension(); private boolean hidden; private final Handler handler; private final Runnable startHidingRunnable; private final Animation hideAnimation; + private boolean enableRewindAndForward = false; + private Context mContext; + public MovieControllerOverlay(Context context) { super(context); - + mContext = context; handler = new Handler(); startHidingRunnable = new Runnable() { @Override @@ -52,9 +87,64 @@ public class MovieControllerOverlay extends CommonControllerOverlay implements hideAnimation = AnimationUtils.loadAnimation(context, R.anim.player_out); hideAnimation.setAnimationListener(this); + enableRewindAndForward = true; + if (LOG) { + Log.v(TAG, "enableRewindAndForward is " + enableRewindAndForward); + } + mControllerRewindAndForwardExt.init(context); + mScreenModeExt.init(context, mTimeBar); + mBackground.setClickable(true); hide(); } + public void showPlaying() { + if (!mOverlayExt.handleShowPlaying()) { + mState = State.PLAYING; + showMainView(mPlayPauseReplayView); + } + if (LOG) { + Log.v(TAG, "showPlaying() state=" + mState); + } + } + + public void showPaused() { + if (!mOverlayExt.handleShowPaused()) { + mState = State.PAUSED; + showMainView(mPlayPauseReplayView); + } + if (LOG) { + Log.v(TAG, "showPaused() state=" + mState); + } + } + + public void showEnded() { + mOverlayExt.onShowEnded(); + mState = State.ENDED; + showMainView(mPlayPauseReplayView); + if (LOG) { + Log.v(TAG, "showEnded() state=" + mState); + } + } + + public void showLoading() { + mOverlayExt.onShowLoading(); + mState = State.LOADING; + showMainView(mLoadingView); + if (LOG) { + Log.v(TAG, "showLoading() state=" + mState); + } + } + + public void showErrorMessage(String message) { + mOverlayExt.onShowErrorMessage(message); + mState = State.ERROR; + int padding = (int) (getMeasuredWidth() * ERROR_MESSAGE_RELATIVE_PADDING); + mErrorView.setPadding(padding, mErrorView.getPaddingTop(), padding, + mErrorView.getPaddingBottom()); + mErrorView.setText(message); + showMainView(mErrorView); + } + @Override protected void createTimeBar(Context context) { mTimeBar = new TimeBar(context, this); @@ -64,25 +154,51 @@ public class MovieControllerOverlay extends CommonControllerOverlay implements public void hide() { boolean wasHidden = hidden; hidden = true; - super.hide(); + mPlayPauseReplayView.setVisibility(View.INVISIBLE); + mLoadingView.setVisibility(View.INVISIBLE); + if (!mOverlayExt.handleHide()) { + setVisibility(View.INVISIBLE); + } + mBackground.setVisibility(View.INVISIBLE); + mTimeBar.setVisibility(View.INVISIBLE); + mScreenModeExt.onHide(); + if (enableRewindAndForward) { + mControllerRewindAndForwardExt.onHide(); + } + setFocusable(true); + requestFocus(); if (mListener != null && wasHidden != hidden) { mListener.onHidden(); } } + private void showMainView(View view) { + mMainView = view; + mErrorView.setVisibility(mMainView == mErrorView ? View.VISIBLE + : View.INVISIBLE); + mLoadingView.setVisibility(mMainView == mLoadingView ? View.VISIBLE + : View.INVISIBLE); + mPlayPauseReplayView + .setVisibility(mMainView == mPlayPauseReplayView ? View.VISIBLE + : View.INVISIBLE); + mOverlayExt.onShowMainView(view); + show(); + } @Override public void show() { boolean wasHidden = hidden; hidden = false; - super.show(); + updateViews(); + setVisibility(View.VISIBLE); + setFocusable(false); if (mListener != null && wasHidden != hidden) { mListener.onShown(); } maybeStartHiding(); } - private void maybeStartHiding() { + public void maybeStartHiding() { cancelHiding(); if (mState == State.PLAYING) { handler.postDelayed(startHidingRunnable, 2500); @@ -90,8 +206,14 @@ public class MovieControllerOverlay extends CommonControllerOverlay implements } private void startHiding() { - startHideAnimation(mBackground); - startHideAnimation(mTimeBar); + if (mOverlayExt.canHidePanel()) { + startHideAnimation(mBackground); + startHideAnimation(mTimeBar); + mScreenModeExt.onStartHiding(); + if (enableRewindAndForward) { + mControllerRewindAndForwardExt.onStartHiding(); + } + } startHideAnimation(mPlayPauseReplayView); } @@ -101,10 +223,16 @@ public class MovieControllerOverlay extends CommonControllerOverlay implements } } - private void cancelHiding() { + public void cancelHiding() { handler.removeCallbacks(startHidingRunnable); - mBackground.setAnimation(null); - mTimeBar.setAnimation(null); + if (mOverlayExt.canHidePanel()) { + mBackground.setAnimation(null); + mTimeBar.setAnimation(null); + mScreenModeExt.onCancelHiding(); + if (enableRewindAndForward) { + mControllerRewindAndForwardExt.onCancelHiding(); + } + } mPlayPauseReplayView.setAnimation(null); } @@ -123,6 +251,65 @@ public class MovieControllerOverlay extends CommonControllerOverlay implements hide(); } + public void onClick(View view) { + if (LOG) { + Log.v(TAG, "onClick(" + view + ") listener=" + mListener + + ", state=" + mState + ", canReplay=" + mCanReplay); + } + if (mListener != null) { + if (view == mPlayPauseReplayView) { + if (mState == State.ENDED) { + if (mCanReplay) { + mListener.onReplay(); + } + } else if (mState == State.PAUSED || mState == State.PLAYING) { + mListener.onPlayPause(); + // set view disabled (play/pause asynchronous processing) + setViewEnabled(true); + } + } + } else { + mScreenModeExt.onClick(view); + if (enableRewindAndForward) { + mControllerRewindAndForwardExt.onClick(view); + } + } + } + + /* + * set view enable (non-Javadoc) + * @see com.android.gallery3d.app.ControllerOverlay#setViewEnabled(boolean) + */ + @Override + public void setViewEnabled(boolean isEnabled) { + if (mListener.onIsRTSP()) { + if (LOG) { + Log.v(TAG, "setViewEnabled is " + isEnabled); + } + mOverlayExt.setCanScrubbing(isEnabled); + mPlayPauseReplayView.setEnabled(isEnabled); + if (enableRewindAndForward) { + mControllerRewindAndForwardExt.setViewEnabled(isEnabled); + } + } + } + + /* + * set play pause button from disable to normal (non-Javadoc) + * @see + * com.android.gallery3d.app.ControllerOverlay#setPlayPauseReplayResume( + * void) + */ + @Override + public void setPlayPauseReplayResume() { + if (mListener.onIsRTSP()) { + if (LOG) { + Log.v(TAG, "setPlayPauseReplayResume is enabled is true"); + } + mPlayPauseReplayView.setEnabled(true); + } + } + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (hidden) { @@ -144,7 +331,10 @@ public class MovieControllerOverlay extends CommonControllerOverlay implements switch (event.getAction()) { case MotionEvent.ACTION_DOWN: cancelHiding(); - if (mState == State.PLAYING || mState == State.PAUSED) { + // you can click play or pause when view is resumed + // play/pause asynchronous processing + if ((mState == State.PLAYING || mState == State.PAUSED) + && mOverlayExt.mEnableScrubbing) { mListener.onPlayPause(); } break; @@ -156,11 +346,71 @@ public class MovieControllerOverlay extends CommonControllerOverlay implements } @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int width = ((MovieActivity) mContext).getWindowManager().getDefaultDisplay().getWidth(); + Rect insets = mWindowInsets; + int pl = insets.left; // the left paddings + int pr = insets.right; + int pt = insets.top; + int pb = insets.bottom; + + int h = bottom - top; + int w = right - left; + boolean error = mErrorView.getVisibility() == View.VISIBLE; + + int y = h - pb; + // Put both TimeBar and Background just above the bottom system + // component. + // But extend the background to the width of the screen, since we don't + // care if it will be covered by a system component and it looks better. + + // Needed, otherwise the framework will not re-layout in case only the + // padding is changed + if (enableRewindAndForward) { + mBackground.layout(0, y - mTimeBar.getPreferredHeight() - 80, w, y); + mTimeBar.layout(pl, y - mTimeBar.getPreferredHeight() - 120, w - pr, + y - mTimeBar.getBarHeight()); + mControllerRewindAndForwardExt.onLayout(0, width, y); + } else { + mBackground.layout(0, y - mTimeBar.getBarHeight(), w, y); + mTimeBar.layout(pl, y - mTimeBar.getPreferredHeight(), + w - pr - mScreenModeExt.getAddedRightPadding(), y); + } + mScreenModeExt.onLayout(w, pr, y); + // Put the play/pause/next/ previous button in the center of the screen + layoutCenteredView(mPlayPauseReplayView, 0, 0, w, h); + + if (mMainView != null) { + layoutCenteredView(mMainView, 0, 0, w, h); + } + } + + @Override protected void updateViews() { if (hidden) { return; } - super.updateViews(); + mBackground.setVisibility(View.VISIBLE); + mTimeBar.setVisibility(View.VISIBLE); + mPlayPauseReplayView.setImageResource( + mState == State.PAUSED ? R.drawable.videoplayer_play : + mState == State.PLAYING ? R.drawable.videoplayer_pause : + R.drawable.videoplayer_reload); + mScreenModeExt.onShow(); + if (enableRewindAndForward) { + mControllerRewindAndForwardExt.onShow(); + } + if (!mOverlayExt.handleUpdateViews()) { + mPlayPauseReplayView.setVisibility( + (mState != State.LOADING && mState != State.ERROR && + !(mState == State.ENDED && !mCanReplay)) + ? View.VISIBLE : View.GONE); + } + requestLayout(); + if (LOG) { + Log.v(TAG, "updateViews() state=" + mState + ", canReplay=" + + mCanReplay); + } } // TimeBar listener @@ -182,4 +432,569 @@ public class MovieControllerOverlay extends CommonControllerOverlay implements maybeStartHiding(); super.onScrubbingEnd(time, trimStartTime, trimEndTime); } + + public void setScreenModeManager(ScreenModeManager manager) { + mScreenModeManager = manager; + if (mScreenModeManager != null) { + mScreenModeManager.addListener(mScreenModeExt); + } + if (LOG) { + Log.v(TAG, "setScreenModeManager(" + manager + ")"); + } + } + + public void setDefaultScreenMode() { + mScreenModeManager.setScreenMode(ScreenModeManager.SCREENMODE_BIGSCREEN); + } + + public IContrllerOverlayExt getOverlayExt() { + return mOverlayExt; + } + + public IControllerRewindAndForward getControllerRewindAndForwardExt() { + if (enableRewindAndForward) { + return mControllerRewindAndForwardExt; + } + return null; + } + + private class OverlayExtension implements IContrllerOverlayExt { + private State mLastState; + private String mPlayingInfo; + // for pause feature + private boolean mCanPause = true; + private boolean mEnableScrubbing = false; + // for only audio feature + private boolean mAlwaysShowBottom; + + @Override + public void showBuffering(boolean fullBuffer, int percent) { + if (LOG) { + Log.v(TAG, "showBuffering(" + fullBuffer + ", " + percent + + ") " + "lastState=" + mLastState + ", state=" + mState); + } + if (fullBuffer) { + // do not show text and loading + mTimeBar.setSecondaryProgress(percent); + return; + } + if (mState == State.PAUSED || mState == State.PLAYING) { + mLastState = mState; + } + if (percent >= 0 && percent < 100) { // valid value + mState = State.BUFFERING; + String text = "media controller buffering"; + mTimeBar.setInfo(text); + showMainView(mLoadingView); + } else if (percent == 100) { + mState = mLastState; + mTimeBar.setInfo(null); + showMainView(mPlayPauseReplayView);// restore play pause state + } else { // here to restore old state + mState = mLastState; + mTimeBar.setInfo(null); + } + } + + // set buffer percent to unknown value + public void clearBuffering() { + if (LOG) { + Log.v(TAG, "clearBuffering()"); + } + mTimeBar.setSecondaryProgress(TimeBar.UNKNOWN); + showBuffering(false, TimeBar.UNKNOWN); + } + + public void showReconnecting(int times) { + clearBuffering(); + mState = State.RETRY_CONNECTING; + int msgId = R.string.videoview_error_text_cannot_connect_retry; + String text = getResources().getString(msgId, times); + mTimeBar.setInfo(text); + showMainView(mLoadingView); + if (LOG) { + Log.v(TAG, "showReconnecting(" + times + ")"); + } + } + + public void showReconnectingError() { + clearBuffering(); + mState = State.RETRY_CONNECTING_ERROR; + + String text = "can not connect to server"; + mTimeBar.setInfo(text); + showMainView(mPlayPauseReplayView); + if (LOG) { + Log.v(TAG, "showReconnectingError()"); + } + } + + public void setPlayingInfo(boolean liveStreaming) { + int msgId; + // TODO + if (liveStreaming) { + msgId = R.string.media_controller_live; + } else { + msgId = R.string.media_controller_playing; + } + mPlayingInfo = getResources().getString(msgId); + if (LOG) { + Log.v(TAG, "setPlayingInfo(" + liveStreaming + + ") playingInfo=" + mPlayingInfo); + } + } + + public void setCanPause(boolean canPause) { + this.mCanPause = canPause; + if (LOG) { + Log.v(TAG, "setCanPause(" + canPause + ")"); + } + } + + public void setCanScrubbing(boolean enable) { + mEnableScrubbing = enable; + mTimeBar.setScrubbing(enable); + if (LOG) { + Log.v(TAG, "setCanScrubbing(" + enable + ")"); + } + } + + public void setBottomPanel(boolean alwaysShow, boolean foreShow) { + mAlwaysShowBottom = alwaysShow; + if (!alwaysShow) { // clear background + setBackgroundDrawable(null); + setBackgroundColor(Color.TRANSPARENT); + } else { + setBackgroundResource(R.drawable.media_default_bkg); + if (foreShow) { + setVisibility(View.VISIBLE); + } + } + if (LOG) { + Log.v(TAG, "setBottomPanel(" + alwaysShow + ", " + foreShow + + ")"); + } + } + + public boolean isPlayingEnd() { + if (LOG) { + Log.v(TAG, "isPlayingEnd() state=" + mState); + } + boolean end = false; + if (State.ENDED == mState || State.ERROR == mState + || State.RETRY_CONNECTING_ERROR == mState) { + end = true; + } + return end; + } + + public boolean handleShowPlaying() { + if (mState == State.BUFFERING) { + mLastState = State.PLAYING; + return true; + } + return false; + } + + public boolean handleShowPaused() { + mTimeBar.setInfo(null); + if (mState == State.BUFFERING) { + mLastState = State.PAUSED; + return true; + } + return false; + } + + public void onShowLoading() { + // TODO + int msgId = R.string.media_controller_connecting; + String text = getResources().getString(msgId); + mTimeBar.setInfo(text); + } + + public void onShowEnded() { + clearBuffering(); + mTimeBar.setInfo(null); + } + + public void onShowErrorMessage(String message) { + clearBuffering(); + } + + public boolean handleUpdateViews() { + mPlayPauseReplayView + .setVisibility((mState != State.LOADING + && mState != State.ERROR + && mState != State.BUFFERING + && mState != State.RETRY_CONNECTING && !(mState != State.ENDED + && mState != State.RETRY_CONNECTING_ERROR && !mCanPause)) + // for live streaming + ? View.VISIBLE + : View.GONE); + + if (mPlayingInfo != null && mState == State.PLAYING) { + mTimeBar.setInfo(mPlayingInfo); + } + return true; + } + + public boolean handleHide() { + return mAlwaysShowBottom; + } + + public void onShowMainView(View view) { + if (LOG) { + Log.v(TAG, "showMainView(" + view + ") errorView=" + + mErrorView + ", loadingView=" + mLoadingView + + ", playPauseReplayView=" + mPlayPauseReplayView); + Log.v(TAG, "showMainView() enableScrubbing=" + + mEnableScrubbing + ", state=" + mState); + } + if (mEnableScrubbing + && (mState == State.PAUSED || mState == State.PLAYING)) { + mTimeBar.setScrubbing(true); + } else { + mTimeBar.setScrubbing(false); + } + } + + public boolean canHidePanel() { + return !mAlwaysShowBottom; + } + }; + + class ScreenModeExt implements View.OnClickListener, ScreenModeListener { + // for screen mode feature + private ImageView mScreenView; + private int mScreenPadding; + private int mScreenWidth; + + private static final int MARGIN = 10; // dip + private ViewGroup mParent; + private ImageView mSeprator; + + void init(Context context, View myTimeBar) { + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + int padding = (int) (metrics.density * MARGIN); + myTimeBar.setPadding(padding, 0, padding, 0); + + LayoutParams wrapContent = + new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + // add screenView + mScreenView = new ImageView(context); + // default next screen mode + mScreenView.setImageResource(R.drawable.ic_media_fullscreen); + mScreenView.setScaleType(ScaleType.CENTER); + mScreenView.setFocusable(true); + mScreenView.setClickable(true); + mScreenView.setOnClickListener(this); + addView(mScreenView, wrapContent); + + if (enableRewindAndForward) { + if (LOG) { + Log.v(TAG, "ScreenModeExt enableRewindAndForward"); + } + mSeprator = new ImageView(context); + // default next screen mode + mSeprator.setImageResource(R.drawable.ic_separator_line); + mSeprator.setScaleType(ScaleType.CENTER); + mSeprator.setFocusable(true); + mSeprator.setClickable(true); + mSeprator.setOnClickListener(this); + addView(mSeprator, wrapContent); + + } else { + if (LOG) { + Log.v(TAG, "ScreenModeExt unenableRewindAndForward"); + } + } + + // for screen layout + Bitmap screenButton = BitmapFactory.decodeResource(context.getResources(), + R.drawable.ic_media_bigscreen); + mScreenWidth = screenButton.getWidth(); + mScreenPadding = (int) (metrics.density * MARGIN); + screenButton.recycle(); + } + + private void updateScreenModeDrawable() { + int screenMode = mScreenModeManager.getNextScreenMode(); + if (screenMode == ScreenModeManager.SCREENMODE_BIGSCREEN) { + mScreenView.setImageResource(R.drawable.ic_media_bigscreen); + } else if (screenMode == ScreenModeManager.SCREENMODE_FULLSCREEN) { + mScreenView.setImageResource(R.drawable.ic_media_fullscreen); + } else { + mScreenView.setImageResource(R.drawable.ic_media_cropscreen); + } + } + + @Override + public void onClick(View v) { + if (v == mScreenView && mScreenModeManager != null) { + mScreenModeManager.setScreenMode(mScreenModeManager + .getNextScreenMode()); + show(); + } + } + + public void onStartHiding() { + startHideAnimation(mScreenView); + } + + public void onCancelHiding() { + mScreenView.setAnimation(null); + } + + public void onHide() { + mScreenView.setVisibility(View.INVISIBLE); + if (enableRewindAndForward) { + mSeprator.setVisibility(View.INVISIBLE); + } + } + + public void onShow() { + mScreenView.setVisibility(View.VISIBLE); + if (enableRewindAndForward) { + mSeprator.setVisibility(View.VISIBLE); + } + } + + public void onLayout(int width, int paddingRight, int yPosition) { + // layout screen view position + int sw = getAddedRightPadding(); + mScreenView.layout(width - paddingRight - sw, yPosition + - mTimeBar.getPreferredHeight(), width - paddingRight, + yPosition); + if (enableRewindAndForward) { + mSeprator.layout(width - paddingRight - sw - 22, yPosition + - mTimeBar.getPreferredHeight(), width - paddingRight - sw - 20, + yPosition); + } + } + + public int getAddedRightPadding() { + return mScreenPadding * 2 + mScreenWidth; + } + + @Override + public void onScreenModeChanged(int newMode) { + updateScreenModeDrawable(); + } + } + + class ControllerRewindAndForwardExt implements View.OnClickListener, + IControllerRewindAndForward { + private LinearLayout mContollerButtons; + private ImageView mStop; + private ImageView mForward; + private ImageView mRewind; + private IRewindAndForwardListener mListenerForRewind; + private int mButtonWidth; + private static final int BUTTON_PADDING = 40; + private int mTimeBarHeight = 0; + + void init(Context context) { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt init"); + } + mTimeBarHeight = mTimeBar.getPreferredHeight(); + Bitmap button = BitmapFactory.decodeResource(context.getResources(), + R.drawable.ic_menu_forward); + mButtonWidth = button.getWidth(); + button.recycle(); + + mContollerButtons = new LinearLayout(context); + LinearLayout.LayoutParams wrapContent = new LinearLayout.LayoutParams( + getAddedRightPadding(), mTimeBarHeight); + mContollerButtons.setHorizontalGravity(LinearLayout.HORIZONTAL); + mContollerButtons.setVisibility(View.VISIBLE); + mContollerButtons.setGravity(Gravity.CENTER); + + LinearLayout.LayoutParams buttonParam = new LinearLayout.LayoutParams( + mButtonWidth, mTimeBarHeight); + mRewind = new ImageView(context); + mRewind.setImageResource(R.drawable.icn_media_rewind); + mRewind.setScaleType(ScaleType.CENTER); + mRewind.setFocusable(true); + mRewind.setClickable(true); + mRewind.setOnClickListener(this); + mContollerButtons.addView(mRewind, buttonParam); + + mStop = new ImageView(context); + mStop.setImageResource(R.drawable.icn_media_stop); + mStop.setScaleType(ScaleType.CENTER); + mStop.setFocusable(true); + mStop.setClickable(true); + mStop.setOnClickListener(this); + LinearLayout.LayoutParams stopLayoutParam = new LinearLayout.LayoutParams( + mTimeBarHeight, mTimeBarHeight); + stopLayoutParam.setMargins(BUTTON_PADDING, 0, BUTTON_PADDING, 0); + mContollerButtons.addView(mStop, stopLayoutParam); + + mForward = new ImageView(context); + mForward.setImageResource(R.drawable.icn_media_forward); + mForward.setScaleType(ScaleType.CENTER); + mForward.setFocusable(true); + mForward.setClickable(true); + mForward.setOnClickListener(this); + mContollerButtons.addView(mForward, buttonParam); + + // Do NOT RTL for media controller + mContollerButtons.setLayoutDirection(View.LAYOUT_DIRECTION_LTR); + + addView(mContollerButtons, wrapContent); + } + + @Override + public void onClick(View v) { + if (v == mStop) { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onClick mStop"); + } + mListenerForRewind.onStopVideo(); + } else if (v == mRewind) { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onClick mRewind"); + } + mListenerForRewind.onRewind(); + } else if (v == mForward) { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onClick mForward"); + } + mListenerForRewind.onForward(); + } + } + + public void onStartHiding() { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onStartHiding"); + } + startHideAnimation(mContollerButtons); + } + + public void onCancelHiding() { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onCancelHiding"); + } + mContollerButtons.setAnimation(null); + } + + public void onHide() { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onHide"); + } + mContollerButtons.setVisibility(View.INVISIBLE); + } + + public void onShow() { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onShow"); + } + mContollerButtons.setVisibility(View.VISIBLE); + } + + public void onLayout(int l, int r, int b) { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onLayout"); + } + int cl = (r - l - getAddedRightPadding()) / 2; + int cr = cl + getAddedRightPadding(); + mContollerButtons.layout(cl, b - mTimeBar.getPreferredHeight(), cr, b); + } + + public int getAddedRightPadding() { + return mTimeBarHeight * 3 + BUTTON_PADDING * 2; + } + + @Override + public void setIListener(IRewindAndForwardListener listener) { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt setIListener " + listener); + } + mListenerForRewind = listener; + } + + @Override + public void showControllerButtonsView(boolean canStop, boolean canRewind, boolean canForward) { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt showControllerButtonsView " + canStop + + canRewind + canForward); + } + // show ui + mStop.setEnabled(canStop); + mRewind.setEnabled(canRewind); + mForward.setEnabled(canForward); + } + + @Override + public void setListener(Listener listener) { + setListener(listener); + } + + @Override + public boolean getPlayPauseEanbled() { + return mPlayPauseReplayView.isEnabled(); + } + + @Override + public boolean getTimeBarEanbled() { + return mTimeBar.getScrubbing(); + } + + @Override + public void setCanReplay(boolean canReplay) { + setCanReplay(canReplay); + } + + @Override + public View getView() { + return mContollerButtons; + } + + @Override + public void show() { + show(); + } + + @Override + public void showPlaying() { + showPlaying(); + } + + @Override + public void showPaused() { + showPaused(); + } + + @Override + public void showEnded() { + showEnded(); + } + + @Override + public void showLoading() { + showLoading(); + } + + @Override + public void showErrorMessage(String message) { + showErrorMessage(message); + } + + public void setTimes(int currentTime, int totalTime, int trimStartTime, int trimEndTime) { + setTimes(currentTime, totalTime, 0, 0); + } + + public void setPlayPauseReplayResume() { + } + + public void setViewEnabled(boolean isEnabled) { + // TODO Auto-generated method stub + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt setViewEnabled is " + isEnabled); + } + mRewind.setEnabled(isEnabled); + mForward.setEnabled(isEnabled); + } + } } diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java index f6bd36725..2ef7c2e85 100644..100755 --- a/src/com/android/gallery3d/app/MoviePlayer.java +++ b/src/com/android/gallery3d/app/MoviePlayer.java @@ -23,10 +23,15 @@ import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.DialogInterface.OnShowListener; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.graphics.Color; import android.media.AudioManager; import android.media.MediaPlayer; +import android.media.Metadata; import android.media.audiofx.AudioEffect; import android.media.audiofx.Virtualizer; import android.net.Uri; @@ -35,26 +40,47 @@ import android.os.Bundle; import android.os.Handler; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.widget.VideoView; +import android.widget.Toast; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.BlobCache; import com.android.gallery3d.util.CacheManager; import com.android.gallery3d.util.GalleryUtils; +import org.codeaurora.gallery3d.ext.IContrllerOverlayExt; +import org.codeaurora.gallery3d.ext.IMoviePlayer; +import org.codeaurora.gallery3d.ext.IMovieItem; +import org.codeaurora.gallery3d.ext.MovieUtils; +import org.codeaurora.gallery3d.video.BookmarkEnhance; +import org.codeaurora.gallery3d.video.ExtensionHelper; +import org.codeaurora.gallery3d.video.IControllerRewindAndForward; +import org.codeaurora.gallery3d.video.IControllerRewindAndForward.IRewindAndForwardListener; +import org.codeaurora.gallery3d.video.ScreenModeManager; +import org.codeaurora.gallery3d.video.ScreenModeManager.ScreenModeListener; +import org.codeaurora.gallery3d.video.CodeauroraVideoView; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.util.HashMap; +import java.util.Map; public class MoviePlayer implements MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, - ControllerOverlay.Listener { + ControllerOverlay.Listener, + MediaPlayer.OnInfoListener, + MediaPlayer.OnPreparedListener, + MediaPlayer.OnSeekCompleteListener, + MediaPlayer.OnVideoSizeChangedListener, + MediaPlayer.OnBufferingUpdateListener { @SuppressWarnings("unused") private static final String TAG = "MoviePlayer"; + private static final boolean LOG = false; private static final String KEY_VIDEO_POSITION = "video-position"; private static final String KEY_RESUMEABLE_TIME = "resumeable-timeout"; @@ -68,18 +94,32 @@ public class MoviePlayer implements private static final String CMDNAME = "command"; private static final String CMDPAUSE = "pause"; + private static final String KEY_VIDEO_CAN_SEEK = "video_can_seek"; + private static final String KEY_VIDEO_CAN_PAUSE = "video_can_pause"; + private static final String KEY_VIDEO_LAST_DURATION = "video_last_duration"; + private static final String KEY_VIDEO_LAST_DISCONNECT_TIME = "last_disconnect_time"; + private static final String KEY_VIDEO_STREAMING_TYPE = "video_streaming_type"; + private static final String KEY_VIDEO_STATE = "video_state"; + private static final String VIRTUALIZE_EXTRA = "virtualize"; private static final long BLACK_TIMEOUT = 500; + private static final int DELAY_REMOVE_MS = 10000; + public static final int SERVER_TIMEOUT = 8801; // If we resume the acitivty with in RESUMEABLE_TIMEOUT, we will keep playing. // Otherwise, we pause the player. private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins + public static final int STREAMING_LOCAL = 0; + public static final int STREAMING_HTTP = 1; + public static final int STREAMING_RTSP = 2; + public static final int STREAMING_SDP = 3; + private int mStreamingType = STREAMING_LOCAL; + private Context mContext; - private final VideoView mVideoView; + private final CodeauroraVideoView mVideoView; private final View mRootView; private final Bookmarker mBookmarker; - private final Uri mUri; private final Handler mHandler = new Handler(); private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver; private final MovieControllerOverlay mController; @@ -87,6 +127,11 @@ public class MoviePlayer implements private long mResumeableTime = Long.MAX_VALUE; private int mVideoPosition = 0; private boolean mHasPaused = false; + private boolean mVideoHasPaused = false; + private boolean mCanResumed = false; + private boolean mFirstBePlayed = false; + private boolean mKeyguardLocked = false; + private boolean mIsOnlyAudio = false; private int mLastSystemUiVis = 0; // If the time bar is being dragged. @@ -97,6 +142,36 @@ public class MoviePlayer implements private Virtualizer mVirtualizer; + private MovieActivity mActivityContext;//for dialog and toast context + private MoviePlayerExtension mPlayerExt = new MoviePlayerExtension(); + private RetryExtension mRetryExt = new RetryExtension(); + private ServerTimeoutExtension mServerTimeoutExt = new ServerTimeoutExtension(); + private ScreenModeExt mScreenModeExt = new ScreenModeExt(); + private IContrllerOverlayExt mOverlayExt; + private IControllerRewindAndForward mControllerRewindAndForwardExt; + private IRewindAndForwardListener mRewindAndForwardListener = new ControllerRewindAndForwardExt(); + private boolean mCanReplay; + private boolean mVideoCanSeek = false; + private boolean mVideoCanPause = false; + private boolean mWaitMetaData; + private boolean mIsShowResumingDialog; + private TState mTState = TState.PLAYING; + private IMovieItem mMovieItem; + private int mVideoLastDuration;//for duration displayed in init state + + private enum TState { + PLAYING, + PAUSED, + STOPED, + COMPELTED, + RETRY_ERROR + } + + interface Restorable { + void onRestoreInstanceState(Bundle icicle); + void onSaveInstanceState(Bundle outState); + } + private final Runnable mPlayingChecker = new Runnable() { @Override public void run() { @@ -116,22 +191,57 @@ public class MoviePlayer implements } }; + private Runnable mDelayVideoRunnable = new Runnable() { + @Override + public void run() { + if (LOG) { + Log.v(TAG, "mDelayVideoRunnable.run()"); + } + mVideoView.setVisibility(View.VISIBLE); + } + }; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + mKeyguardLocked = true; + } else if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) { + if ((mCanResumed) && (!mVideoHasPaused)) { + playVideo(); + } + mKeyguardLocked = false; + mCanResumed = false; + } else if (Intent.ACTION_SHUTDOWN.equals(intent.getAction())) { + if (LOG) { + Log.v(TAG, "Intent.ACTION_SHUTDOWN received"); + } + mActivityContext.finish(); + } + } + }; + public MoviePlayer(View rootView, final MovieActivity movieActivity, - Uri videoUri, Bundle savedInstance, boolean canReplay) { + IMovieItem info, Bundle savedInstance, boolean canReplay) { mContext = movieActivity.getApplicationContext(); mRootView = rootView; - mVideoView = (VideoView) rootView.findViewById(R.id.surface_view); + mVideoView = (CodeauroraVideoView) rootView.findViewById(R.id.surface_view); mBookmarker = new Bookmarker(movieActivity); - mUri = videoUri; - mController = new MovieControllerOverlay(mContext); + mController = new MovieControllerOverlay(movieActivity); ((ViewGroup)rootView).addView(mController.getView()); mController.setListener(this); mController.setCanReplay(canReplay); + init(movieActivity, info, canReplay); + mVideoView.setOnErrorListener(this); mVideoView.setOnCompletionListener(this); - mVideoView.setVideoURI(mUri); + + if (mVirtualizer != null) { + mVirtualizer.release(); + mVirtualizer = null; + } Intent ai = movieActivity.getIntent(); boolean virtualize = ai.getBooleanExtra(VIRTUALIZE_EXTRA, false); @@ -186,20 +296,39 @@ public class MoviePlayer implements i.putExtra(CMDNAME, CMDPAUSE); movieActivity.sendBroadcast(i); + // Listen for broadcasts related to user-presence + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_USER_PRESENT); + filter.addAction(Intent.ACTION_SHUTDOWN); + mContext.registerReceiver(mReceiver, filter); + if (savedInstance != null) { // this is a resumed activity mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0); mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE); - mVideoView.start(); - mVideoView.suspend(); + onRestoreInstanceState(savedInstance); mHasPaused = true; + doStartVideo(true, mVideoPosition, mVideoLastDuration,false); + mVideoView.start(); + mActivityContext.initEffects(mVideoView.getAudioSessionId()); } else { - final Integer bookmark = mBookmarker.getBookmark(mUri); - if (bookmark != null) { - showResumeDialog(movieActivity, bookmark); + mTState = TState.PLAYING; + mFirstBePlayed = true; + String mUri = mMovieItem.getUri().toString(); + boolean isLive = mUri.startsWith("rtsp://") && (mUri.contains(".sdp") + || mUri.contains(".smil")); + if (!isLive) { + final BookmarkerInfo bookmark = mBookmarker.getBookmark(mMovieItem.getUri()); + if (bookmark != null) { + showResumeDialog(movieActivity, bookmark); + } else { + doStartVideo(false, 0, 0); + } } else { - startVideo(); + doStartVideo(false, 0, 0); } } + mScreenModeExt.setScreenMode(); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @@ -213,11 +342,17 @@ public class MoviePlayer implements new View.OnSystemUiVisibilityChangeListener() { @Override public void onSystemUiVisibilityChange(int visibility) { + boolean finish = (mActivityContext == null ? true : mActivityContext.isFinishing()); int diff = mLastSystemUiVis ^ visibility; mLastSystemUiVis = visibility; if ((diff & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 && (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { mController.show(); + mRootView.setBackgroundColor(Color.BLACK); + } + + if (LOG) { + Log.v(TAG, "onSystemUiVisibilityChange(" + visibility + ") finishing()=" + finish); } } }); @@ -242,14 +377,15 @@ public class MoviePlayer implements public void onSaveInstanceState(Bundle outState) { outState.putInt(KEY_VIDEO_POSITION, mVideoPosition); outState.putLong(KEY_RESUMEABLE_TIME, mResumeableTime); + onSaveInstanceStateMore(outState); } - private void showResumeDialog(Context context, final int bookmark) { + private void showResumeDialog(Context context, final BookmarkerInfo bookmark) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.resume_playing_title); builder.setMessage(String.format( context.getString(R.string.resume_playing_message), - GalleryUtils.formatDuration(context, bookmark / 1000))); + GalleryUtils.formatDuration(context, bookmark.mBookmark / 1000))); builder.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { @@ -260,42 +396,157 @@ public class MoviePlayer implements R.string.resume_playing_resume, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mVideoView.seekTo(bookmark); - startVideo(); + // here try to seek for bookmark + mVideoCanSeek = true; + doStartVideo(true, bookmark.mBookmark, bookmark.mDuration); } }); builder.setNegativeButton( R.string.resume_playing_restart, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - startVideo(); + doStartVideo(true, 0, bookmark.mDuration); + } + }); + AlertDialog dialog = builder.create(); + dialog.setOnShowListener(new OnShowListener() { + @Override + public void onShow(DialogInterface arg0) { + mIsShowResumingDialog = true; } }); - builder.show(); + dialog.setOnDismissListener(new OnDismissListener() { + @Override + public void onDismiss(DialogInterface arg0) { + mIsShowResumingDialog = false; + } + }); + dialog.show(); + } + + public void setDefaultScreenMode() { + addBackground(); + mController.setDefaultScreenMode(); + removeBackground(); + } + + public boolean onPause() { + if (LOG) { + Log.v(TAG, "onPause() isLiveStreaming()=" + isLiveStreaming()); + } + boolean pause = false; + if (isLiveStreaming()) { + pause = false; + } else { + doOnPause(); + pause = true; + } + if (LOG) { + Log.v(TAG, "onPause() , return " + pause); + } + return pause; } - public void onPause() { + // we should stop video anyway after this function called. + public void onStop() { + if (LOG) { + Log.v(TAG, "onStop() mHasPaused=" + mHasPaused); + } + if (!mHasPaused) { + doOnPause(); + } + } + + private void doOnPause() { + long start = System.currentTimeMillis(); + addBackground(); mHasPaused = true; mHandler.removeCallbacksAndMessages(null); - mVideoPosition = mVideoView.getCurrentPosition(); - mBookmarker.setBookmark(mUri, mVideoPosition, mVideoView.getDuration()); + int position = mVideoView.getCurrentPosition(); + mVideoPosition = position >= 0 ? position : mVideoPosition; + Log.v(TAG, "mVideoPosition is " + mVideoPosition); + int duration = mVideoView.getDuration(); + mVideoLastDuration = duration > 0 ? duration : mVideoLastDuration; + mBookmarker.setBookmark(mMovieItem.getUri(), mVideoPosition, mVideoLastDuration); + long end1 = System.currentTimeMillis(); mVideoView.suspend(); mResumeableTime = System.currentTimeMillis() + RESUMEABLE_TIMEOUT; + mVideoView.setResumed(false);// avoid start after surface created + long end2 = System.currentTimeMillis(); + // TODO comments by sunlei + mOverlayExt.clearBuffering(); + mServerTimeoutExt.recordDisconnectTime(); + if (LOG) { + Log.v(TAG, "doOnPause() save video info consume:" + (end1 - start)); + Log.v(TAG, "doOnPause() suspend video consume:" + (end2 - end1)); + Log.v(TAG, "doOnPause() mVideoPosition=" + mVideoPosition + ", mResumeableTime=" + + mResumeableTime + + ", mVideoLastDuration=" + mVideoLastDuration + ", mIsShowResumingDialog=" + + mIsShowResumingDialog); + } } public void onResume() { + mDragging = false;// clear drag info if (mHasPaused) { - mVideoView.seekTo(mVideoPosition); - mVideoView.resume(); + //M: same as launch case to delay transparent. + mVideoView.removeCallbacks(mDelayVideoRunnable); + mVideoView.postDelayed(mDelayVideoRunnable, BLACK_TIMEOUT); - // If we have slept for too long, pause the play - if (System.currentTimeMillis() > mResumeableTime) { - pauseVideo(); + if (mServerTimeoutExt.handleOnResume() || mIsShowResumingDialog) { + mHasPaused = false; + return; + } + switch (mTState) { + case RETRY_ERROR: + mRetryExt.showRetry(); + break; + case STOPED: + mPlayerExt.stopVideo(); + break; + case COMPELTED: + mController.showEnded(); + if (mVideoCanSeek || mVideoView.canSeekForward()) { + mVideoView.seekTo(mVideoPosition); + } + mVideoView.setDuration(mVideoLastDuration); + break; + case PAUSED: + // if video was paused, so it should be started. + doStartVideo(true, mVideoPosition, mVideoLastDuration, false); + pauseVideo(); + break; + default: + mVideoView.seekTo(mVideoPosition); + mVideoView.resume(); + pauseVideoMoreThanThreeMinutes(); + break; } + mHasPaused = false; + } + + if (System.currentTimeMillis() > mResumeableTime) { + mHandler.removeCallbacks(mPlayingChecker); + mHandler.postDelayed(mPlayingChecker, 250); } + mHandler.post(mProgressChecker); } + private void pauseVideoMoreThanThreeMinutes() { + // If we have slept for too long, pause the play + // If is live streaming, do not pause it too + long now = System.currentTimeMillis(); + if (now > mResumeableTime && !isLiveStreaming()) { + if (mVideoCanPause || mVideoView.canPause()) { + pauseVideo(); + } + } + if (LOG) { + Log.v(TAG, "pauseVideoMoreThanThreeMinutes() now=" + now); + } + } + public void onDestroy() { if (mVirtualizer != null) { mVirtualizer.release(); @@ -303,27 +554,36 @@ public class MoviePlayer implements } mVideoView.stopPlayback(); mAudioBecomingNoisyReceiver.unregister(); + mContext.unregisterReceiver(mReceiver); + mServerTimeoutExt.clearTimeoutDialog(); } // This updates the time bar display (if necessary). It is called every // second by mProgressChecker and also from places where the time bar needs // to be updated immediately. private int setProgress() { - if (mDragging || !mShowing) { + if (mDragging || (!mShowing && !mIsOnlyAudio)) { return 0; } int position = mVideoView.getCurrentPosition(); int duration = mVideoView.getDuration(); mController.setTimes(position, duration, 0, 0); + if (mControllerRewindAndForwardExt != null + && mControllerRewindAndForwardExt.getPlayPauseEanbled()) { + updateRewindAndForwardUI(); + } return position; } - private void startVideo() { + private void doStartVideo(final boolean enableFasten, final int position, final int duration, + boolean start) { // For streams that we expect to be slow to start up, show a // progress spinner until playback starts. - String scheme = mUri.getScheme(); - if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) { + String scheme = mMovieItem.getUri().getScheme(); + if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme) + || "https".equalsIgnoreCase(scheme)) { mController.showLoading(); + mOverlayExt.setPlayingInfo(isLiveStreaming()); mHandler.removeCallbacks(mPlayingChecker); mHandler.postDelayed(mPlayingChecker, 250); } else { @@ -331,35 +591,94 @@ public class MoviePlayer implements mController.hide(); } - mVideoView.start(); + if (onIsRTSP()) { + Map<String, String> header = new HashMap<String, String>(1); + header.put("CODEAURORA-ASYNC-RTSP-PAUSE-PLAY", "true"); + mVideoView.setVideoURI(mMovieItem.getUri(), header, !mWaitMetaData); + } else { + mVideoView.setVideoURI(mMovieItem.getUri(), null, !mWaitMetaData); + } + if (start) { + mVideoView.start(); + mVideoView.setVisibility(View.VISIBLE); + mActivityContext.initEffects(mVideoView.getAudioSessionId()); + } + //we may start video from stopVideo, + //this case, we should reset canReplay flag according canReplay and loop + boolean loop = mPlayerExt.getLoop(); + boolean canReplay = loop ? loop : mCanReplay; + mController.setCanReplay(canReplay); + if (position > 0 && (mVideoCanSeek || mVideoView.canSeek())) { + mVideoView.seekTo(position); + } + if (enableFasten) { + mVideoView.setDuration(duration); + } setProgress(); } + private void doStartVideo(boolean enableFasten, int position, int duration) { + doStartVideo(enableFasten, position, duration, true); + } + private void playVideo() { + if (LOG) { + Log.v(TAG, "playVideo()"); + } + mTState = TState.PLAYING; mVideoView.start(); mController.showPlaying(); setProgress(); } private void pauseVideo() { + if (LOG) { + Log.v(TAG, "pauseVideo()"); + } + mTState = TState.PAUSED; mVideoView.pause(); + setProgress(); mController.showPaused(); } // Below are notifications from VideoView @Override public boolean onError(MediaPlayer player, int arg1, int arg2) { + mMovieItem.setError(); + if (mServerTimeoutExt.onError(player, arg1, arg2)) { + return true; + } + if (mRetryExt.onError(player, arg1, arg2)) { + return true; + } mHandler.removeCallbacksAndMessages(null); // VideoView will show an error dialog if we return false, so no need // to show more message. + //M:resume controller + mController.setViewEnabled(true); mController.showErrorMessage(""); return false; } @Override public void onCompletion(MediaPlayer mp) { - mController.showEnded(); - onCompletion(); + if (LOG) { + Log.v(TAG, "onCompletion() mCanReplay=" + mCanReplay); + } + if (mMovieItem.getError()) { + Log.w(TAG, "error occured, exit the video player!"); + mActivityContext.finish(); + return; + } + if (mPlayerExt.getLoop()) { + onReplay(); + } else { //original logic + mTState = TState.COMPELTED; + if (mCanReplay) { + mController.showEnded(); + } + onCompletion(); + } } public void onCompletion() { @@ -369,9 +688,23 @@ public class MoviePlayer implements @Override public void onPlayPause() { if (mVideoView.isPlaying()) { - pauseVideo(); + if (mVideoView.canPause()) { + pauseVideo(); + //set view disabled(play/pause asynchronous processing) + mController.setViewEnabled(true); + if (mControllerRewindAndForwardExt != null) { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), false, false); + } + } } else { playVideo(); + //set view disabled(play/pause asynchronous processing) + mController.setViewEnabled(true); + if (mControllerRewindAndForwardExt != null) { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), false, false); + } } } @@ -382,18 +715,27 @@ public class MoviePlayer implements @Override public void onSeekMove(int time) { - mVideoView.seekTo(time); + if (mVideoView.canSeek()) { + mVideoView.seekTo(time); + } } @Override public void onSeekEnd(int time, int start, int end) { mDragging = false; - mVideoView.seekTo(time); + if (mVideoView.canSeek()) { + mVideoView.seekTo(time); + } + } + + @Override + public void onSeekComplete(MediaPlayer mp) { setProgress(); } @Override public void onShown() { + addBackground(); mShowing = true; setProgress(); showSystemUi(true); @@ -403,11 +745,82 @@ public class MoviePlayer implements public void onHidden() { mShowing = false; showSystemUi(false); + removeBackground(); + } + + @Override + public boolean onInfo(MediaPlayer mp, int what, int extra) { + if (LOG) { + Log.v(TAG, "onInfo() what:" + what + " extra:" + extra); + } + if (what == MediaPlayer.MEDIA_INFO_NOT_SEEKABLE && mOverlayExt != null) { + boolean flag = (extra == 1); + mOverlayExt.setCanPause(flag); + mOverlayExt.setCanScrubbing(flag); + } else if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE && mServerTimeoutExt != null) { + Log.e(TAG, "setServerTimeout " + extra); + mServerTimeoutExt.setTimeout(extra * 1000); + } + if (mRetryExt.onInfo(mp, what, extra)) { + return true; + } + return false; + } + + @Override + public void onBufferingUpdate(MediaPlayer mp, int percent) { + boolean fullBuffer = isFullBuffer(); + mOverlayExt.showBuffering(fullBuffer, percent); + } + + @Override + public void onPrepared(MediaPlayer mp) { + if (LOG) { + Log.v(TAG, "onPrepared(" + mp + ")"); + } + if (!isLocalFile()) { + mOverlayExt.setPlayingInfo(isLiveStreaming()); + } + getVideoInfo(mp); + boolean canPause = mVideoView.canPause(); + boolean canSeek = mVideoView.canSeek(); + mOverlayExt.setCanPause(canPause); + mOverlayExt.setCanScrubbing(canSeek); + mController.setPlayPauseReplayResume(); + if (!canPause && !mVideoView.isTargetPlaying()) { + mVideoView.start(); + } + updateRewindAndForwardUI(); + if (LOG) { + Log.v(TAG, "onPrepared() canPause=" + canPause + ", canSeek=" + canSeek); + } + } + + public boolean onIsRTSP() { + if (MovieUtils.isRtspStreaming(mMovieItem.getUri(), mMovieItem + .getMimeType())) { + if (LOG) { + Log.v(TAG, "onIsRTSP() is RTSP"); + } + return true; + } + if (LOG) { + Log.v(TAG, "onIsRTSP() is not RTSP"); + } + return false; } @Override public void onReplay() { - startVideo(); + if (LOG) { + Log.v(TAG, "onReplay()"); + } + mTState = TState.PLAYING; + mFirstBePlayed = true; + if (mRetryExt.handleOnReplay()) { + return; + } + doStartVideo(false, 0, 0); } // Below are key events passed from MovieActivity. @@ -421,14 +834,14 @@ public class MoviePlayer implements switch (keyCode) { case KeyEvent.KEYCODE_HEADSETHOOK: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - if (mVideoView.isPlaying()) { + if (mVideoView.isPlaying() && mVideoView.canPause()) { pauseVideo(); } else { playVideo(); } return true; case KEYCODE_MEDIA_PAUSE: - if (mVideoView.isPlaying()) { + if (mVideoView.isPlaying() && mVideoView.canPause()) { pauseVideo(); } return true; @@ -450,6 +863,21 @@ public class MoviePlayer implements return isMediaKey(keyCode); } + public void updateRewindAndForwardUI() { + if (LOG) { + Log.v(TAG, "updateRewindAndForwardUI"); + Log.v(TAG, "updateRewindAndForwardUI== getCurrentPosition = " + mVideoView.getCurrentPosition()); + Log.v(TAG, "updateRewindAndForwardUI==getDuration =" + mVideoView.getDuration()); + } + if (mControllerRewindAndForwardExt != null) { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), mVideoView.canSeekBackward() + && mControllerRewindAndForwardExt.getTimeBarEanbled(), mVideoView + .canSeekForward() + && mControllerRewindAndForwardExt.getTimeBarEanbled()); + } + } + private static boolean isMediaKey(int keyCode) { return keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS @@ -459,6 +887,30 @@ public class MoviePlayer implements || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE; } + private void init(MovieActivity movieActivity, IMovieItem info, boolean canReplay) { + mActivityContext = movieActivity; + mCanReplay = canReplay; + mMovieItem = info; + judgeStreamingType(info.getUri(), info.getMimeType()); + + mVideoView.setOnInfoListener(this); + mVideoView.setOnPreparedListener(this); + mVideoView.setOnBufferingUpdateListener(this); + mVideoView.setOnVideoSizeChangedListener(this); + mRootView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + mController.show(); + return true; + } + }); + mOverlayExt = mController.getOverlayExt(); + mControllerRewindAndForwardExt = mController.getControllerRewindAndForwardExt(); + if (mControllerRewindAndForwardExt != null) { + mControllerRewindAndForwardExt.setIListener(mRewindAndForwardListener); + } + } + // We want to pause when the headset is unplugged. private class AudioBecomingNoisyReceiver extends BroadcastReceiver { @@ -473,7 +925,691 @@ public class MoviePlayer implements @Override public void onReceive(Context context, Intent intent) { - if (mVideoView.isPlaying()) pauseVideo(); + if (mVideoView.isPlaying() && mVideoView.canPause()) pauseVideo(); + } + } + + public int getAudioSessionId() { + return mVideoView.getAudioSessionId(); + } + + public void setOnPreparedListener(MediaPlayer.OnPreparedListener listener) { + mVideoView.setOnPreparedListener(listener); + } + + public boolean isFullBuffer() { + if (mStreamingType == STREAMING_RTSP || mStreamingType == STREAMING_SDP) { + return false; + } + return true; + } + + public boolean isLocalFile() { + if (mStreamingType == STREAMING_LOCAL) { + return true; + } + return false; + } + + private void getVideoInfo(MediaPlayer mp) { + if (!MovieUtils.isLocalFile(mMovieItem.getUri(), mMovieItem.getMimeType())) { + Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL, + MediaPlayer.BYPASS_METADATA_FILTER); + if (data != null) { + // TODO comments by sunlei + mServerTimeoutExt.setVideoInfo(data); + } else { + Log.w(TAG, "Metadata is null!"); + } + } + } + + private void judgeStreamingType(Uri uri, String mimeType) { + if (LOG) { + Log.v(TAG, "judgeStreamingType(" + uri + ")"); + } + if (uri == null) { + return; + } + String scheme = uri.getScheme(); + mWaitMetaData = true; + if (MovieUtils.isSdpStreaming(uri, mimeType)) { + mStreamingType = STREAMING_SDP; + mWaitMetaData = false; + } else if (MovieUtils.isRtspStreaming(uri, mimeType)) { + mStreamingType = STREAMING_RTSP; + mWaitMetaData = false; + } else if (MovieUtils.isHttpStreaming(uri, mimeType)) { + mStreamingType = STREAMING_HTTP; + mWaitMetaData = false; + } else { + mStreamingType = STREAMING_LOCAL; + mWaitMetaData = false; + } + if (LOG) { + Log.v(TAG, "mStreamingType=" + mStreamingType + + " mCanGetMetaData=" + mWaitMetaData); + } + } + + public boolean isLiveStreaming() { + boolean isLive = false; + if (mStreamingType == STREAMING_SDP) { + isLive = true; + } + if (LOG) { + Log.v(TAG, "isLiveStreaming() return " + isLive); + } + return isLive; + } + + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + // reget the audio type + if (width != 0 && height != 0) { + mIsOnlyAudio = false; + } else { + mIsOnlyAudio = true; + } + mOverlayExt.setBottomPanel(mIsOnlyAudio, true); + if (LOG) { + Log.v(TAG, "onVideoSizeChanged(" + width + ", " + height + ") mIsOnlyAudio=" + + mIsOnlyAudio); + } + } + + public IMoviePlayer getMoviePlayerExt() { + return mPlayerExt; + } + + public SurfaceView getVideoSurface() { + return mVideoView; + } + + // Wait for any animation, ten seconds should be enough + private final Runnable mRemoveBackground = new Runnable() { + @Override + public void run() { + if (LOG) { + Log.v(TAG, "mRemoveBackground.run()"); + } + mRootView.setBackground(null); + } + }; + + private void removeBackground() { + if (LOG) { + Log.v(TAG, "removeBackground()"); + } + mHandler.removeCallbacks(mRemoveBackground); + mHandler.postDelayed(mRemoveBackground, DELAY_REMOVE_MS); + } + + // add background for removing ghost image. + private void addBackground() { + if (LOG) { + Log.v(TAG, "addBackground()"); + } + mHandler.removeCallbacks(mRemoveBackground); + mRootView.setBackgroundColor(Color.BLACK); + } + + private void clearVideoInfo() { + mVideoPosition = 0; + mVideoLastDuration = 0; + mIsOnlyAudio = false; + + if (mServerTimeoutExt != null) { + mServerTimeoutExt.clearServerInfo(); + } + } + + private void onSaveInstanceStateMore(Bundle outState) { + outState.putInt(KEY_VIDEO_LAST_DURATION, mVideoLastDuration); + outState.putBoolean(KEY_VIDEO_CAN_SEEK, mVideoView.canSeekForward()); + outState.putBoolean(KEY_VIDEO_CAN_PAUSE, mVideoView.canPause()); + outState.putInt(KEY_VIDEO_STREAMING_TYPE, mStreamingType); + outState.putString(KEY_VIDEO_STATE, String.valueOf(mTState)); + mServerTimeoutExt.onSaveInstanceState(outState); + mScreenModeExt.onSaveInstanceState(outState); + mRetryExt.onSaveInstanceState(outState); + mPlayerExt.onSaveInstanceState(outState); + } + + private void onRestoreInstanceState(Bundle icicle) { + mVideoLastDuration = icicle.getInt(KEY_VIDEO_LAST_DURATION); + mVideoCanSeek = icicle.getBoolean(KEY_VIDEO_CAN_SEEK); + mVideoCanPause = icicle.getBoolean(KEY_VIDEO_CAN_PAUSE); + mStreamingType = icicle.getInt(KEY_VIDEO_STREAMING_TYPE); + mTState = TState.valueOf(icicle.getString(KEY_VIDEO_STATE)); + mServerTimeoutExt.onRestoreInstanceState(icicle); + mScreenModeExt.onRestoreInstanceState(icicle); + mRetryExt.onRestoreInstanceState(icicle); + mPlayerExt.onRestoreInstanceState(icicle); + } + + private class MoviePlayerExtension implements IMoviePlayer, Restorable { + + private static final String KEY_VIDEO_IS_LOOP = "video_is_loop"; + + private BookmarkEnhance mBookmark;//for bookmark + private boolean mIsLoop; + private boolean mLastPlaying; + private boolean mLastCanPaused; + + @Override + public boolean getLoop() { + return mIsLoop; + } + + @Override + public void setLoop(boolean loop) { + if (isLocalFile()) { + mIsLoop = loop; + mController.setCanReplay(loop); + } + } + + @Override + public void startNextVideo(IMovieItem item) { + IMovieItem next = item; + if (next != null && next != mMovieItem) { + int position = mVideoView.getCurrentPosition(); + int duration = mVideoView.getDuration(); + mBookmarker.setBookmark(mMovieItem.getUri(), position, duration); + mVideoView.stopPlayback(); + mVideoView.setVisibility(View.INVISIBLE); + clearVideoInfo(); + mActivityContext.releaseEffects(); + mMovieItem = next; + mActivityContext.refreshMovieInfo(mMovieItem); + doStartVideo(false, 0, 0); + mVideoView.setVisibility(View.VISIBLE); + } else { + Log.e(TAG, "Cannot play the next video! " + item); + } + mActivityContext.closeOptionsMenu(); + } + + @Override + public void onRestoreInstanceState(Bundle icicle) { + mIsLoop = icicle.getBoolean(KEY_VIDEO_IS_LOOP, false); + if (mIsLoop) { + mController.setCanReplay(true); + } // else will get can replay from intent. + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putBoolean(KEY_VIDEO_IS_LOOP, mIsLoop); + } + + @Override + public void stopVideo() { + if (LOG) { + Log.v(TAG, "stopVideo()"); + } + mTState = TState.STOPED; + mVideoView.clearSeek(); + mVideoView.clearDuration(); + mVideoView.stopPlayback(); + mVideoView.setResumed(false); + mVideoView.setVisibility(View.INVISIBLE); + clearVideoInfo(); + mActivityContext.releaseEffects(); + mFirstBePlayed = false; + mController.setCanReplay(true); + mController.showEnded(); + mController.setViewEnabled(true); + setProgress(); + } + + @Override + public boolean canStop() { + boolean stopped = false; + if (mController != null) { + stopped = mOverlayExt.isPlayingEnd(); + } + if (LOG) { + Log.v(TAG, "canStop() stopped=" + stopped); + } + return !stopped; + } + + @Override + public void addBookmark() { + if (mBookmark == null) { + mBookmark = new BookmarkEnhance(mActivityContext); + } + String uri = String.valueOf(mMovieItem.getUri()); + if (mBookmark.exists(uri)) { + Toast.makeText(mActivityContext, R.string.bookmark_exist, Toast.LENGTH_SHORT) + .show(); + } else { + mBookmark.insert(mMovieItem.getTitle(), uri, + mMovieItem.getMimeType(), 0); + Toast.makeText(mActivityContext, R.string.bookmark_add_success, Toast.LENGTH_SHORT) + .show(); + } + } + }; + + private class RetryExtension implements Restorable, MediaPlayer.OnErrorListener, + MediaPlayer.OnInfoListener { + private static final String KEY_VIDEO_RETRY_COUNT = "video_retry_count"; + private int mRetryDuration; + private int mRetryPosition; + private int mRetryCount; + + public void retry() { + doStartVideo(true, mRetryPosition, mRetryDuration); + if (LOG) { + Log.v(TAG, "retry() mRetryCount=" + mRetryCount + ", mRetryPosition=" + + mRetryPosition); + } + } + + public void clearRetry() { + if (LOG) { + Log.v(TAG, "clearRetry() mRetryCount=" + mRetryCount); + } + mRetryCount = 0; + } + + public boolean reachRetryCount() { + if (LOG) { + Log.v(TAG, "reachRetryCount() mRetryCount=" + mRetryCount); + } + if (mRetryCount > 3) { + return true; + } + return false; + } + + public int getRetryCount() { + if (LOG) { + Log.v(TAG, "getRetryCount() return " + mRetryCount); + } + return mRetryCount; + } + + public boolean isRetrying() { + boolean retry = false; + if (mRetryCount > 0) { + retry = true; + } + if (LOG) { + Log.v(TAG, "isRetrying() mRetryCount=" + mRetryCount); + } + return retry; + } + + @Override + public void onRestoreInstanceState(Bundle icicle) { + mRetryCount = icicle.getInt(KEY_VIDEO_RETRY_COUNT); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putInt(KEY_VIDEO_RETRY_COUNT, mRetryCount); + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + return false; + } + + @Override + public boolean onInfo(MediaPlayer mp, int what, int extra) { + return false; + } + + public boolean handleOnReplay() { + if (isRetrying()) { // from connecting error + clearRetry(); + int errorPosition = mVideoView.getCurrentPosition(); + int errorDuration = mVideoView.getDuration(); + doStartVideo(errorPosition > 0, errorPosition, errorDuration); + if (LOG) { + Log.v(TAG, "onReplay() errorPosition=" + errorPosition + ", errorDuration=" + + errorDuration); + } + return true; + } + return false; + } + + public void showRetry() { + if (mVideoCanSeek || mVideoView.canSeekForward()) { + mVideoView.seekTo(mVideoPosition); + } + mVideoView.setDuration(mVideoLastDuration); + mRetryPosition = mVideoPosition; + mRetryDuration = mVideoLastDuration; + } + } + private class ServerTimeoutExtension implements Restorable, MediaPlayer.OnErrorListener { + // for cmcc server timeout case + // please remember to clear this value when changed video. + private int mServerTimeout = -1; + private long mLastDisconnectTime; + private boolean mIsShowDialog = false; + private AlertDialog mServerTimeoutDialog; + private int RESUME_DIALOG_TIMEOUT = 3 * 60 * 1000; // 3 mins + + // check whether disconnect from server timeout or not. + // if timeout, return false. otherwise, return true. + private boolean passDisconnectCheck() { + if (!isFullBuffer()) { + // record the time disconnect from server + long now = System.currentTimeMillis(); + if (LOG) { + Log.v(TAG, "passDisconnectCheck() now=" + now + ", mLastDisconnectTime=" + + mLastDisconnectTime + + ", mServerTimeout=" + mServerTimeout); + } + if (mServerTimeout > 0 && (now - mLastDisconnectTime) > mServerTimeout) { + // disconnect time more than server timeout, notify user + notifyServerTimeout(); + return false; + } + } + return true; + } + + private void recordDisconnectTime() { + if (!isFullBuffer()) { + // record the time disconnect from server + mLastDisconnectTime = System.currentTimeMillis(); + } + if (LOG) { + Log.v(TAG, "recordDisconnectTime() mLastDisconnectTime=" + mLastDisconnectTime); + } + } + + private void clearServerInfo() { + mServerTimeout = -1; + } + + private void notifyServerTimeout() { + if (mServerTimeoutDialog == null) { + // for updating last position and duration. + if (mVideoCanSeek || mVideoView.canSeekForward()) { + mVideoView.seekTo(mVideoPosition); + } + mVideoView.setDuration(mVideoLastDuration); + AlertDialog.Builder builder = new AlertDialog.Builder(mActivityContext); + mServerTimeoutDialog = builder.setTitle(R.string.server_timeout_title) + .setMessage(R.string.server_timeout_message) + .setNegativeButton(android.R.string.cancel, new OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + if (LOG) { + Log.v(TAG, "NegativeButton.onClick() mIsShowDialog=" + + mIsShowDialog); + } + mController.showEnded(); + onCompletion(); + } + + }) + .setPositiveButton(R.string.resume_playing_resume, new OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + if (LOG) { + Log.v(TAG, "PositiveButton.onClick() mIsShowDialog=" + + mIsShowDialog); + } + doStartVideo(true, mVideoPosition, mVideoLastDuration); + } + + }) + .setOnCancelListener(new OnCancelListener() { + public void onCancel(DialogInterface dialog) { + mController.showEnded(); + onCompletion(); + } + }) + .create(); + mServerTimeoutDialog.setOnDismissListener(new OnDismissListener() { + + public void onDismiss(DialogInterface dialog) { + if (LOG) { + Log.v(TAG, "mServerTimeoutDialog.onDismiss()"); + } + mVideoView.setDialogShowState(false); + mIsShowDialog = false; + } + + }); + mServerTimeoutDialog.setOnShowListener(new OnShowListener() { + + public void onShow(DialogInterface dialog) { + if (LOG) { + Log.v(TAG, "mServerTimeoutDialog.onShow()"); + } + mVideoView.setDialogShowState(true); + mIsShowDialog = true; + } + + }); + } + mServerTimeoutDialog.show(); + } + + private void clearTimeoutDialog() { + if (mServerTimeoutDialog != null && mServerTimeoutDialog.isShowing()) { + mServerTimeoutDialog.dismiss(); + } + mServerTimeoutDialog = null; + } + + @Override + public void onRestoreInstanceState(Bundle icicle) { + mLastDisconnectTime = icicle.getLong(KEY_VIDEO_LAST_DISCONNECT_TIME); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putLong(KEY_VIDEO_LAST_DISCONNECT_TIME, mLastDisconnectTime); + } + + public boolean handleOnResume() { + if (mIsShowDialog && !isLiveStreaming()) { + // wait for user's operation + return true; + } + if (!passDisconnectCheck()) { + return true; + } + return false; + } + + public void setVideoInfo(Metadata data) { + mServerTimeout = RESUME_DIALOG_TIMEOUT; + if (data.has(SERVER_TIMEOUT)) { + mServerTimeout = data.getInt(SERVER_TIMEOUT); + if (mServerTimeout == 0) { + mServerTimeout = RESUME_DIALOG_TIMEOUT; + } + if (LOG) { + Log.v(TAG, "get server timeout from metadata. mServerTimeout=" + + mServerTimeout); + } + } + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + // if we are showing a dialog, cancel the error dialog + if (mIsShowDialog) { + return true; + } + return false; + } + + public void setTimeout(int timeout) { + mServerTimeout = timeout; + } + } + + private class ScreenModeExt implements Restorable, ScreenModeListener { + private static final String KEY_VIDEO_SCREEN_MODE = "video_screen_mode"; + private int mScreenMode = ScreenModeManager.SCREENMODE_BIGSCREEN; + private ScreenModeManager mScreenModeManager = new ScreenModeManager(); + + public void setScreenMode() { + mVideoView.setScreenModeManager(mScreenModeManager); + mController.setScreenModeManager(mScreenModeManager); + mScreenModeManager.addListener(this); + //notify all listener to change screen mode + mScreenModeManager.setScreenMode(mScreenMode); + if (LOG) { + Log.v(TAG, "setScreenMode() mScreenMode=" + mScreenMode); + } + } + + @Override + public void onScreenModeChanged(int newMode) { + mScreenMode = newMode;// changed from controller + if (LOG) { + Log.v(TAG, "OnScreenModeClicked(" + newMode + ")"); + } + } + + @Override + public void onRestoreInstanceState(Bundle icicle) { + mScreenMode = icicle.getInt(KEY_VIDEO_SCREEN_MODE, + ScreenModeManager.SCREENMODE_BIGSCREEN); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putInt(KEY_VIDEO_SCREEN_MODE, mScreenMode); + } + } + + private class ControllerRewindAndForwardExt implements IRewindAndForwardListener { + @Override + public void onPlayPause() { + onPlayPause(); + } + + @Override + public void onSeekStart() { + onSeekStart(); + } + + @Override + public void onSeekMove(int time) { + onSeekMove(time); + } + + @Override + public void onSeekEnd(int time, int trimStartTime, int trimEndTime) { + onSeekEnd(time, trimStartTime, trimEndTime); + } + + @Override + public void onShown() { + onShown(); + } + + @Override + public void onHidden() { + onHidden(); + } + + @Override + public void onReplay() { + onReplay(); + } + + @Override + public boolean onIsRTSP() { + return false; + } + + @Override + public void onStopVideo() { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onStopVideo()"); + } + if (mPlayerExt.canStop()) { + mPlayerExt.stopVideo(); + mControllerRewindAndForwardExt.showControllerButtonsView(false, + false, false); + } + } + + @Override + public void onRewind() { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onRewind()"); + } + if (mVideoView != null && mVideoView.canSeekBackward()) { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), + false, false); + int stepValue = getStepOptionValue(); + int targetDuration = mVideoView.getCurrentPosition() + - stepValue < 0 ? 0 : mVideoView.getCurrentPosition() + - stepValue; + if (LOG) { + Log.v(TAG, "onRewind targetDuration " + targetDuration); + } + mVideoView.seekTo(targetDuration); + } else { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), + false, false); + } + } + + @Override + public void onForward() { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onForward()"); + } + if (mVideoView != null && mVideoView.canSeekForward()) { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), + false, false); + int stepValue = getStepOptionValue(); + int targetDuration = mVideoView.getCurrentPosition() + + stepValue > mVideoView.getDuration() ? mVideoView + .getDuration() : mVideoView.getCurrentPosition() + + stepValue; + if (LOG) { + Log.v(TAG, "onForward targetDuration " + targetDuration); + } + mVideoView.seekTo(targetDuration); + } else { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), + false, false); + } + } + } + + public int getStepOptionValue() { + final String slectedStepOption = "selected_step_option"; + final String videoPlayerData = "video_player_data"; + final int stepBase = 3000; + final int stepOptionThreeSeconds = 0; + SharedPreferences mPrefs = mContext.getSharedPreferences( + videoPlayerData, 0); + return (mPrefs.getInt(slectedStepOption, stepOptionThreeSeconds) + 1) * stepBase; + } + + public void restartHidingController() { + if (mController != null) { + mController.maybeStartHiding(); + } + } + + public void cancelHidingController() { + if (mController != null) { + mController.cancelHiding(); } } } @@ -497,6 +1633,10 @@ class Bookmarker { public void setBookmark(Uri uri, int bookmark, int duration) { try { + // do not record or override bookmark if duration is not valid. + if (duration <= 0) { + return; + } BlobCache cache = CacheManager.getCache(mContext, BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES, BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION); @@ -505,7 +1645,7 @@ class Bookmarker { DataOutputStream dos = new DataOutputStream(bos); dos.writeUTF(uri.toString()); dos.writeInt(bookmark); - dos.writeInt(duration); + dos.writeInt(Math.abs(duration)); dos.flush(); cache.insert(uri.hashCode(), bos.toByteArray()); } catch (Throwable t) { @@ -513,7 +1653,7 @@ class Bookmarker { } } - public Integer getBookmark(Uri uri) { + public BookmarkerInfo getBookmark(Uri uri) { try { BlobCache cache = CacheManager.getCache(mContext, BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES, @@ -537,10 +1677,31 @@ class Bookmarker { || (bookmark > (duration - HALF_MINUTE))) { return null; } - return Integer.valueOf(bookmark); + return new BookmarkerInfo(bookmark, duration); } catch (Throwable t) { Log.w(TAG, "getBookmark failed", t); } return null; } } + +class BookmarkerInfo { + public final int mBookmark; + public final int mDuration; + + public BookmarkerInfo(int bookmark, int duration) { + this.mBookmark = bookmark; + this.mDuration = duration; + } + + @Override + public String toString() { + return new StringBuilder() + .append("BookmarkInfo(bookmark=") + .append(mBookmark) + .append(", duration=") + .append(mDuration) + .append(")") + .toString(); + } +} diff --git a/src/com/android/gallery3d/app/MuteVideo.java b/src/com/android/gallery3d/app/MuteVideo.java index d3f3aa594..dd05397d2 100644..100755 --- a/src/com/android/gallery3d/app/MuteVideo.java +++ b/src/com/android/gallery3d/app/MuteVideo.java @@ -16,6 +16,8 @@ package com.android.gallery3d.app; +import java.util.ArrayList; + import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; @@ -40,6 +42,13 @@ public class MuteVideo { private SaveVideoFileInfo mDstFileInfo = null; private Activity mActivity = null; private final Handler mHandler = new Handler(); + private String mMimeType; + ArrayList<String> mUnsupportedMuteFileTypes = new ArrayList<String>(); + private final String FILE_TYPE_DIVX = "video/divx"; + private final String FILE_TYPE_AVI = "video/avi"; + private final String FILE_TYPE_WMV = "video/x-ms-wmv"; + private final String FILE_TYPE_ASF = "video/x-ms-asf"; + private final String FILE_TYPE_WEBM = "video/webm"; final String TIME_STAMP_NAME = "'MUTE'_yyyyMMdd_HHmmss"; @@ -47,6 +56,13 @@ public class MuteVideo { mUri = uri; mFilePath = filePath; mActivity = activity; + if (mUnsupportedMuteFileTypes != null) { + mUnsupportedMuteFileTypes.add(FILE_TYPE_DIVX); + mUnsupportedMuteFileTypes.add(FILE_TYPE_AVI); + mUnsupportedMuteFileTypes.add(FILE_TYPE_WMV); + mUnsupportedMuteFileTypes.add(FILE_TYPE_ASF); + mUnsupportedMuteFileTypes.add(FILE_TYPE_WEBM); + } } public void muteInBackground() { @@ -54,6 +70,15 @@ public class MuteVideo { mActivity.getContentResolver(), mUri, mActivity.getString(R.string.folder_download)); + mMimeType = mActivity.getContentResolver().getType(mUri); + if(!isValidFileForMute(mMimeType)) { + Toast.makeText(mActivity.getApplicationContext(), + mActivity.getString(R.string.mute_nosupport), + Toast.LENGTH_SHORT) + .show(); + return; + } + showProgressDialog(); new Thread(new Runnable() { @Override @@ -62,9 +87,19 @@ public class MuteVideo { VideoUtils.startMute(mFilePath, mDstFileInfo); SaveVideoFileUtils.insertContent( mDstFileInfo, mActivity.getContentResolver(), mUri); - } catch (IOException e) { - Toast.makeText(mActivity, mActivity.getString(R.string.video_mute_err), - Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + mHandler.post(new Runnable() { + @Override + public void run() { + Toast.makeText(mActivity, mActivity.getString(R.string.video_mute_err), + Toast.LENGTH_SHORT).show(); + if (mMuteProgress != null) { + mMuteProgress.dismiss(); + mMuteProgress = null; + } + } + }); + return; } // After muting is done, trigger the UI changed. mHandler.post(new Runnable() { @@ -101,4 +136,16 @@ public class MuteVideo { mMuteProgress.setCanceledOnTouchOutside(false); mMuteProgress.show(); } + private boolean isValidFileForMute(String mimeType) { + if (mimeType != null) { + for (String fileType : mUnsupportedMuteFileTypes) { + if (mimeType.equals(fileType)) { + return false; + } + } + return true; + } else { + return false; + } + } } diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java index fd3a7cf73..4322d9b7a 100644..100755 --- a/src/com/android/gallery3d/app/PhotoDataAdapter.java +++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java @@ -20,15 +20,19 @@ import android.graphics.Bitmap; import android.graphics.BitmapRegionDecoder; import android.os.Handler; import android.os.Message; +import android.text.TextUtils; +import android.view.View; import com.android.gallery3d.common.BitmapUtils; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.data.CameraShortcutImage; import com.android.gallery3d.data.ContentListener; import com.android.gallery3d.data.LocalMediaItem; import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaSet; import com.android.gallery3d.data.Path; +import com.android.gallery3d.data.SnailItem; import com.android.gallery3d.glrenderer.TiledTexture; import com.android.gallery3d.ui.PhotoView; import com.android.gallery3d.ui.ScreenNail; @@ -49,6 +53,7 @@ import java.util.HashSet; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; +import java.util.Locale; public class PhotoDataAdapter implements PhotoPage.Model { @SuppressWarnings("unused") @@ -67,6 +72,7 @@ public class PhotoDataAdapter implements PhotoPage.Model { private static final int BIT_SCREEN_NAIL = 1; private static final int BIT_FULL_IMAGE = 2; + private static final long NOTIFY_DIRTY_WAIT_TIME = 10; // sImageFetchSeq is the fetching sequence for images. // We want to fetch the current screennail first (offset = 0), the next // screennail (offset = +1), then the previous screennail (offset = -1) etc. @@ -155,6 +161,9 @@ public class PhotoDataAdapter implements PhotoPage.Model { private int mFocusHintDirection = FOCUS_HINT_NEXT; private Path mFocusHintPath = null; + // If Bundle is from widget, it's true, otherwise it's false. + private boolean mIsFromWidget = false; + public interface DataListener extends LoadingListener { public void onPhotoChanged(int index, Path item); } @@ -292,6 +301,13 @@ public class PhotoDataAdapter implements PhotoPage.Model { mDataListener = listener; } + /** + * Set this to true if it is from widget. + */ + public void setFromWidget(boolean isFromWidget) { + mIsFromWidget = isFromWidget; + } + private void updateScreenNail(Path path, Future<ScreenNail> future) { ImageEntry entry = mImageCache.get(path); ScreenNail screenNail = future.get(); @@ -512,6 +528,13 @@ public class PhotoDataAdapter implements PhotoPage.Model { } @Override + public boolean isGif(int offset) { + MediaItem item = getItem(mCurrentIndex + offset); + return (item != null) && + MediaItem.MIME_TYPE_GIF.equalsIgnoreCase(item.getMimeType()); + } + + @Override public boolean isDeletable(int offset) { MediaItem item = getItem(mCurrentIndex + offset); return (item == null) @@ -653,6 +676,31 @@ public class PhotoDataAdapter implements PhotoPage.Model { } } + /** + * Update the image window and data window for RTL. + */ + private void updateSlidingWindowForRTL() { + // 1. Update the image window + int nStart = Utils.clamp(mCurrentIndex - IMAGE_CACHE_SIZE / 2, + 0, Math.max(0, mSize - IMAGE_CACHE_SIZE)); + int nEnd = Math.min(mSize, nStart + IMAGE_CACHE_SIZE); + + if (mActiveStart == nStart && mActiveEnd == nEnd) { + return; // don't need to refresh + } + + mActiveStart = nStart; + mActiveEnd = nEnd; + + // 2. Update the data window + nStart = Utils.clamp(mCurrentIndex - DATA_CACHE_SIZE / 2, + 0, Math.max(0, mSize - DATA_CACHE_SIZE)); + nEnd = Math.min(mSize, nStart + DATA_CACHE_SIZE); + + mContentStart = nStart; + mContentEnd = nEnd; + } + private void updateImageRequests() { if (!mIsActive) return; @@ -747,7 +795,7 @@ public class PhotoDataAdapter implements PhotoPage.Model { // Must be an item in camera roll. if (!(mediaItem instanceof LocalMediaItem)) return false; LocalMediaItem item = (LocalMediaItem) mediaItem; - if (item.getBucketId() != MediaSetUtils.CAMERA_BUCKET_ID) return false; + if (item.getBucketId() != MediaSetUtils.getCameraBucketId()) return false; // Must have no size, but must have width and height information if (item.getSize() != 0) return false; if (item.getWidth() == 0) return false; @@ -1033,13 +1081,75 @@ public class PhotoDataAdapter implements PhotoPage.Model { UpdateInfo info = executeAndWait(new GetUpdateInfo()); updateLoading(true); long version = mSource.reload(); + + // Used for delete photo, RTL need to re-decide the slide range. + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault()) + && mSource.getCurrectSize() == 1 && mCurrentIndex > 0) { + mCurrentIndex = mCurrentIndex - 1; + mSize = mSource.getMediaItemCount(); + updateSlidingWindowForRTL(); + info = executeAndWait(new GetUpdateInfo()); + } + if (info.version != version) { info.reloadContent = true; info.size = mSource.getMediaItemCount(); } if (!info.reloadContent) continue; - info.items = mSource.getMediaItem( - info.contentStart, info.contentEnd); + + // Check it is from camera or not + boolean isCameraFlag = false; + if (mCameraIndex == mCurrentIndex) { + info.items = mSource.getMediaItem(mCameraIndex, 1); + if (info.items.get(0) instanceof CameraShortcutImage + || info.items.get(0) instanceof SnailItem) { + isCameraFlag = true; + } + } + + // If RTL, need to descending photos + if (!isCameraFlag + && info.contentStart < info.contentEnd + && (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault()))) { + + // Calculate picture index/range etc.. + int nIndex = isCameraFlag ? mCurrentIndex : info.size - mCurrentIndex - 1; + int nStart = Utils.clamp(nIndex - DATA_CACHE_SIZE / 2, 0, + Math.max(0, info.size - DATA_CACHE_SIZE)); + info.items = mSource.getMediaItem(nStart, DATA_CACHE_SIZE); + + // Initialize temporary picture list + ArrayList<MediaItem> mediaItemList = new ArrayList<MediaItem>(); + + // Fetch source, check the first item is camera or not + ArrayList<MediaItem> itemstmpList = mSource.getMediaItem(0, 1); + MediaItem itemstmp = itemstmpList.size() > 0 ? itemstmpList.get(0) : null; + boolean isCameraItem = (itemstmp != null) + && (itemstmp instanceof CameraShortcutImage + || itemstmp instanceof SnailItem); + if (isCameraItem) { + // If it's camera mode, need to put camera to first position + mediaItemList.add(itemstmp); + } + + // Descending + for (int i = info.items.size() - 1; i >= 0; i--) { + if (isCameraItem && 0 == i) { + continue; + } + mediaItemList.add(info.items.get(i)); + } + info.items = (ArrayList<MediaItem>) mediaItemList.clone(); + + // Clear temporary list and free memory immediately + mediaItemList.clear(); + mediaItemList = null; + } else { + info.items = mSource.getMediaItem( + info.contentStart, info.contentEnd); + } // If RTL, need to descending photos end int index = MediaSet.INDEX_NOT_FOUND; @@ -1055,7 +1165,15 @@ public class PhotoDataAdapter implements PhotoPage.Model { if (item != null && item.getPath() == info.target) { index = info.indexHint; } else { - index = findIndexOfTarget(info); + // If RTL and it's not from widget, the index don't need to be amended + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault()) + && !mIsFromWidget) { + index = info.indexHint; + } else { + index = findIndexOfTarget(info); + mIsFromWidget = false; + } } } diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java index 915fdab5a..dd27f2689 100644..100755 --- a/src/com/android/gallery3d/app/PhotoPage.java +++ b/src/com/android/gallery3d/app/PhotoPage.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Rect; +import android.media.MediaFile; import android.net.Uri; import android.nfc.NfcAdapter; import android.nfc.NfcAdapter.CreateBeamUrisCallback; @@ -33,6 +34,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.SystemClock; +import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -72,6 +74,10 @@ import com.android.gallery3d.ui.SelectionManager; import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.UsageStatistics; +import com.android.gallery3d.util.ViewGifImage; + +import java.util.ArrayList; +import java.util.Locale; public abstract class PhotoPage extends ActivityState implements PhotoView.Listener, AppBridge.Server, ShareActionProvider.OnShareTargetSelectedListener, @@ -102,6 +108,9 @@ public abstract class PhotoPage extends ActivityState implements private static final int REQUEST_PLAY_VIDEO = 5; private static final int REQUEST_TRIM = 6; + // Data cache size, equal to AlbumDataLoader.DATA_CACHE_SIZE + private static final int DATA_CACHE_SIZE = 256; + public static final String KEY_MEDIA_SET_PATH = "media-set-path"; public static final String KEY_MEDIA_ITEM_PATH = "media-item-path"; public static final String KEY_INDEX_HINT = "index-hint"; @@ -114,6 +123,10 @@ public abstract class PhotoPage extends ActivityState implements public static final String KEY_IN_CAMERA_ROLL = "in_camera_roll"; public static final String KEY_READONLY = "read-only"; + + // Bundle key, used for checking whether it is from widget + public static final String KEY_IS_FROM_WIDGET = "is_from_widget"; + public static final String KEY_ALBUMPAGE_TRANSITION = "albumpage-transition"; public static final int MSG_ALBUMPAGE_NONE = 0; public static final int MSG_ALBUMPAGE_STARTED = 1; @@ -159,6 +172,8 @@ public abstract class PhotoPage extends ActivityState implements private SnailAlbum mScreenNailSet; private OrientationManager mOrientationManager; private boolean mTreatBackAsUp; + // Used for checking whether it is from widget + private boolean mIsFromWidget; private boolean mStartInFilmstrip; private boolean mHasCameraScreennailOrPlaceholder = false; private boolean mRecenterCameraOnResume = true; @@ -389,6 +404,7 @@ public abstract class PhotoPage extends ActivityState implements Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)) : null; mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false); + mIsFromWidget = data.getBoolean(KEY_IS_FROM_WIDGET, false); mStartInFilmstrip = data.getBoolean(KEY_START_IN_FILMSTRIP, false); boolean inCameraRoll = data.getBoolean(KEY_IN_CAMERA_ROLL, false); mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0); @@ -465,6 +481,30 @@ public abstract class PhotoPage extends ActivityState implements return; } } + + // If it is from widget, need to re-calcuate index and range + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault()) + && mIsFromWidget) { + int nMediaItemCount = mMediaSet.getMediaItemCount(); + ArrayList<MediaItem> mediaItemList = mMediaSet.getMediaItem(0, nMediaItemCount); + int nItemIndex; + for (nItemIndex = 0; nItemIndex < nMediaItemCount; nItemIndex++) { + if (mediaItemList.get(nItemIndex).getPath().toString() + .equals(itemPath.toString())) { + int nIndex; + if (nItemIndex > DATA_CACHE_SIZE / 2 + && nItemIndex < (mMediaSet.getMediaItemCount() - + DATA_CACHE_SIZE / 2)) { + nIndex = mMediaSet.getMediaItemCount() - nItemIndex - 2; + } else { + nIndex = mMediaSet.getMediaItemCount() - nItemIndex - 1; + } + itemPath = mMediaSet.getMediaItem(nIndex, 1).get(0).getPath(); + break; + } + } + } PhotoDataAdapter pda = new PhotoDataAdapter( mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex, mAppBridge == null ? -1 : 0, @@ -473,6 +513,12 @@ public abstract class PhotoPage extends ActivityState implements mModel = pda; mPhotoView.setModel(mModel); + // If RTL and from widget, set the flag into PhotoDataAdapter. + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault()) + && mIsFromWidget) { + pda.setFromWidget(mIsFromWidget); + } pda.setDataListener(new PhotoDataAdapter.DataListener() { @Override @@ -773,7 +819,7 @@ public abstract class PhotoPage extends ActivityState implements int supportedOperations = mCurrentPhoto.getSupportedOperations(); if (mReadOnlyView) { - supportedOperations ^= MediaObject.SUPPORT_EDIT; + supportedOperations &= ~MediaObject.SUPPORT_EDIT; } if (mSecureAlbum != null) { supportedOperations &= MediaObject.SUPPORT_DELETE; @@ -1022,6 +1068,12 @@ public abstract class PhotoPage extends ActivityState implements return true; } int currentIndex = mModel.getCurrentIndex(); + + // If RTL, the current index need be revised. + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { + currentIndex = mMediaSet.getMediaItemCount() - currentIndex - 1; + } Path path = current.getPath(); DataManager manager = mActivity.getDataManager(); @@ -1057,8 +1109,15 @@ public abstract class PhotoPage extends ActivityState implements Intent intent = new Intent(mActivity, TrimVideo.class); intent.setData(manager.getContentUri(path)); // We need the file path to wrap this into a RandomAccessFile. - intent.putExtra(KEY_MEDIA_ITEM_PATH, current.getFilePath()); - mActivity.startActivityForResult(intent, REQUEST_TRIM); + String str = android.media.MediaFile.getMimeTypeForFile(current.getFilePath()); + if("video/mp4".equals(str) || "video/mpeg4".equals(str) + || "video/3gpp".equals(str) || "video/3gpp2".equals(str)) { + intent.putExtra(KEY_MEDIA_ITEM_PATH, current.getFilePath()); + mActivity.startActivityForResult(intent, REQUEST_TRIM); + } else { + Toast.makeText(mActivity,mActivity.getString(R.string.can_not_trim), + Toast.LENGTH_SHORT).show(); + } return true; } case R.id.action_mute: { @@ -1136,6 +1195,10 @@ public abstract class PhotoPage extends ActivityState implements // item is not ready or it is camera preview, ignore return; } + if (item.getMimeType().equals(MediaItem.MIME_TYPE_GIF)) { + viewAnimateGif((Activity) mActivity, item.getContentUri()); + return; + } int supported = item.getSupportedOperations(); boolean playVideo = ((supported & MediaItem.SUPPORT_PLAY) != 0); @@ -1200,7 +1263,14 @@ public abstract class PhotoPage extends ActivityState implements onCommitDeleteImage(); // commit the previous deletion mDeletePath = path; mDeleteIsFocus = (offset == 0); - mMediaSet.addDeletion(path, mCurrentIndex + offset); + + // If RTL, the index need be revised. + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { + mMediaSet.addDeletion(path, mMediaSet.getMediaItemCount() - mCurrentIndex - 1); + } else { + mMediaSet.addDeletion(path, mCurrentIndex + offset); + } } @Override @@ -1284,6 +1354,12 @@ public abstract class PhotoPage extends ActivityState implements if (data == null) break; String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH); int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0); + + // If RTL, the index need be revised. + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { + index = mMediaSet.getMediaItemCount() - index - 1; + } if (path != null) { mModel.setCurrentPhoto(Path.fromString(path), index); } @@ -1384,6 +1460,15 @@ public abstract class PhotoPage extends ActivityState implements } @Override + public void onConfigurationChanged(Configuration config) { + super.onConfigurationChanged(config); + if(mIsActive) return; + mActivity.GLRootResume(true); + mModel.resume(); + mActivity.GLRootResume(false); + } + + @Override protected void onResume() { super.onResume(); @@ -1530,4 +1615,8 @@ public abstract class PhotoPage extends ActivityState implements } } + private static void viewAnimateGif(Activity activity, Uri uri) { + Intent intent = new Intent(ViewGifImage.VIEW_GIF_ACTION, uri); + activity.startActivity(intent); + } } diff --git a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java index 00f2fe78f..8134756f8 100644..100755 --- a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java +++ b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java @@ -89,7 +89,16 @@ public class SinglePhotoDataAdapter extends TileImageViewAdapter @Override public void onFutureDone(Future<BitmapRegionDecoder> future) { BitmapRegionDecoder decoder = future.get(); - if (decoder == null) return; + // cannot get large bitmap, then try to get thumb bitmap + if (decoder == null) { + if (mTask != null && !mTask.isCancelled()) { + Log.w(TAG, "fail to get region decoder, try to request thumb image"); + mHasFullImage = false; + pause(); + resume(); + } + return; + } int width = decoder.getWidth(); int height = decoder.getHeight(); BitmapFactory.Options options = new BitmapFactory.Options(); @@ -136,7 +145,6 @@ public class SinglePhotoDataAdapter extends TileImageViewAdapter Bitmap backup = future.get(); if (backup == null) { mLoadingState = LOADING_FAIL; - return; } else { mLoadingState = LOADING_COMPLETE; } @@ -227,6 +235,11 @@ public class SinglePhotoDataAdapter extends TileImageViewAdapter } @Override + public boolean isGif(int offset) { + return MediaItem.MIME_TYPE_GIF.equalsIgnoreCase(mItem.getMimeType()); + } + + @Override public boolean isDeletable(int offset) { return (mItem.getSupportedOperations() & MediaItem.SUPPORT_DELETE) != 0; } diff --git a/src/com/android/gallery3d/app/StorageChangeReceiver.java b/src/com/android/gallery3d/app/StorageChangeReceiver.java new file mode 100644 index 000000000..f42179c84 --- /dev/null +++ b/src/com/android/gallery3d/app/StorageChangeReceiver.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 The CyanogenMod 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.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +public class StorageChangeReceiver extends BroadcastReceiver { + public static final String KEY_STORAGE = "pref_camera_storage_key"; + + @Override + public void onReceive(Context context, Intent intent) { + String storagePath = intent.getExtras().getString(KEY_STORAGE, null); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + if ((storagePath != null) && !storagePath.equals(prefs.getString(KEY_STORAGE, null))) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(KEY_STORAGE, storagePath); + editor.apply(); + } + } +} diff --git a/src/com/android/gallery3d/app/TimeBar.java b/src/com/android/gallery3d/app/TimeBar.java index 246346a56..2870c489c 100644..100755 --- a/src/com/android/gallery3d/app/TimeBar.java +++ b/src/com/android/gallery3d/app/TimeBar.java @@ -22,13 +22,17 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import android.text.TextUtils; import android.util.DisplayMetrics; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; +import java.util.Locale; + /** * The time bar view, which includes the current and total time, the progress * bar, and the scrubber. @@ -51,6 +55,10 @@ public class TimeBar extends View { private static final int TEXT_SIZE_IN_DP = 14; + private static final String TAG = "Gallery3D/TimeBar"; + private static final boolean LOG = false; + public static final int UNKNOWN = -1; + protected final Listener mListener; // the bars we use for displaying the progress @@ -71,6 +79,7 @@ public class TimeBar extends View { protected boolean mScrubbing; protected boolean mShowTimes; protected boolean mShowScrubber; + private boolean mEnableScrubbing; protected int mTotalTime; protected int mCurrentTime; @@ -78,6 +87,11 @@ public class TimeBar extends View { protected final Rect mTimeBounds; protected int mVPaddingInPx; + private int mLastShowTime = UNKNOWN; + + private ITimeBarSecondaryProgressExt mSecondaryProgressExt = new TimeBarSecondaryProgressExtImpl(); + private ITimeBarInfoExt mInfoExt = new TimeBarInfoExtImpl(); + private ITimeBarLayoutExt mLayoutExt = new TimeBarLayoutExtImpl(); public TimeBar(Context context, Listener listener) { super(context); @@ -108,21 +122,53 @@ public class TimeBar extends View { mScrubberPadding = (int) (metrics.density * SCRUBBER_PADDING_IN_DP); mVPaddingInPx = (int) (metrics.density * V_PADDING_IN_DP); + mLayoutExt.init(mScrubberPadding, mVPaddingInPx); + mInfoExt.init(textSizeInPx); + mSecondaryProgressExt.init(); } private void update() { mPlayedBar.set(mProgressBar); if (mTotalTime > 0) { - mPlayedBar.right = - mPlayedBar.left + (int) ((mProgressBar.width() * (long) mCurrentTime) / mTotalTime); + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { + // The progress bar should be reversed in RTL. + mPlayedBar.left = mPlayedBar.right + - (int) ((mProgressBar.width() * (long) mCurrentTime) / mTotalTime); + } else { + mPlayedBar.right = mPlayedBar.left + + (int) ((mProgressBar.width() * (long) mCurrentTime) / mTotalTime); + } + /* + * M: if duration is not accurate, here just adjust playedBar we + * also show the accurate position text to final user. + */ + if (mPlayedBar.right > mProgressBar.right) { + mPlayedBar.right = mProgressBar.right; + } } else { - mPlayedBar.right = mProgressBar.left; + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { + // The progress bar should be reversed in RTL. + mPlayedBar.left = mProgressBar.right; + } else { + mPlayedBar.right = mProgressBar.left; + } } if (!mScrubbing) { - mScrubberLeft = mPlayedBar.right - mScrubber.getWidth() / 2; + if (View.LAYOUT_DIRECTION_RTL == TextUtils.getLayoutDirectionFromLocale( + Locale.getDefault())) { + // The progress bar should be reversed in RTL. + mScrubberLeft = mPlayedBar.left - mScrubber.getWidth() / 2; + } else { + mScrubberLeft = mPlayedBar.right - mScrubber.getWidth() / 2; + } } + // update text bounds when layout changed or time changed + updateBounds(); + mInfoExt.updateVisibleText(this, mProgressBar, mTimeBounds); invalidate(); } @@ -130,14 +176,16 @@ public class TimeBar extends View { * @return the preferred height of this view, including invisible padding */ public int getPreferredHeight() { - return mTimeBounds.height() + mVPaddingInPx + mScrubberPadding; + int preferredHeight = mTimeBounds.height() + mVPaddingInPx + mScrubberPadding; + return mLayoutExt.getPreferredHeight(preferredHeight, mTimeBounds); } /** * @return the height of the time bar, excluding invisible padding */ public int getBarHeight() { - return mTimeBounds.height() + mVPaddingInPx; + int barHeight = mTimeBounds.height() + mVPaddingInPx; + return mLayoutExt.getBarHeight(barHeight, mTimeBounds); } public void setTime(int currentTime, int totalTime, @@ -146,7 +194,10 @@ public class TimeBar extends View { return; } mCurrentTime = currentTime; - mTotalTime = totalTime; + mTotalTime = Math.abs(totalTime); + if (totalTime <= 0) { /// M: disable scrubbing before mediaplayer ready. + setScrubbing(false); + } update(); } @@ -165,9 +216,17 @@ public class TimeBar extends View { } private int getScrubberTime() { - return (int) ((long) (mScrubberLeft + mScrubber.getWidth() / 2 - mProgressBar.left) - * mTotalTime / mProgressBar.width()); - } + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { + // The progress bar's scrubber time should be reversed in RTL. + return (int) ((long) (mProgressBar.width() - (mScrubberLeft + + mScrubber.getWidth() / 2 - mProgressBar.left)) + * mTotalTime / mProgressBar.width()); + } else { + return (int) ((long) (mScrubberLeft + mScrubber.getWidth() / 2 - mProgressBar.left) + * mTotalTime / mProgressBar.width()); + } + } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { @@ -180,7 +239,8 @@ public class TimeBar extends View { if (mShowTimes) { margin += mTimeBounds.width(); } - int progressY = (h + mScrubberPadding) / 2; + margin = mLayoutExt.getProgressMargin(margin); + int progressY = (h + mScrubberPadding) / 2 + mLayoutExt.getProgressOffset(mTimeBounds); mScrubberTop = progressY - mScrubber.getHeight() / 2 + 1; mProgressBar.set( getPaddingLeft() + margin, progressY, @@ -191,8 +251,10 @@ public class TimeBar extends View { @Override protected void onDraw(Canvas canvas) { + super.onDraw(canvas); // draw progress bars canvas.drawRect(mProgressBar, mProgressPaint); + mSecondaryProgressExt.draw(canvas, mProgressBar); canvas.drawRect(mPlayedBar, mPlayedPaint); // draw scrubber and timers @@ -200,22 +262,44 @@ public class TimeBar extends View { canvas.drawBitmap(mScrubber, mScrubberLeft, mScrubberTop, null); } if (mShowTimes) { - canvas.drawText( - stringForTime(mCurrentTime), - mTimeBounds.width() / 2 + getPaddingLeft(), - mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1, - mTimeTextPaint); - canvas.drawText( - stringForTime(mTotalTime), - getWidth() - getPaddingRight() - mTimeBounds.width() / 2, - mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1, - mTimeTextPaint); + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { + // The progress bar's time should be reversed in RTL. + canvas.drawText( + stringForTime(mCurrentTime), + getWidth() - getPaddingRight() - mTimeBounds.width() / 2, + mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1, + mTimeTextPaint); + canvas.drawText( + stringForTime(mTotalTime), + mTimeBounds.width() / 2 + getPaddingLeft(), + mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1, + mTimeTextPaint); + } else { + canvas.drawText( + stringForTime(mCurrentTime), + mTimeBounds.width() / 2 + getPaddingLeft(), + mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1, + mTimeTextPaint); + canvas.drawText( + stringForTime(mTotalTime), + getWidth() - getPaddingRight() - mTimeBounds.width() / 2, + mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1, + mTimeTextPaint); + } } + mInfoExt.draw(canvas, mLayoutExt.getInfoBounds(this, mTimeBounds)); } @Override public boolean onTouchEvent(MotionEvent event) { - if (mShowScrubber) { + if (LOG) { + Log.v(TAG, "onTouchEvent() showScrubber=" + mShowScrubber + + ", enableScrubbing=" + mEnableScrubbing + ", totalTime=" + + mTotalTime + ", scrubbing=" + mScrubbing + ", event=" + + event); + } + if (mShowScrubber && mEnableScrubbing) { int x = (int) event.getX(); int y = (int) event.getY(); @@ -233,15 +317,19 @@ public class TimeBar extends View { clampScrubber(); mCurrentTime = getScrubberTime(); mListener.onScrubbingMove(mCurrentTime); + update(); invalidate(); return true; } case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - mListener.onScrubbingEnd(getScrubberTime(), 0, 0); - mScrubbing = false; - return true; - } + case MotionEvent.ACTION_UP: + if (mScrubbing) { + mListener.onScrubbingEnd(getScrubberTime(), 0, 0); + mScrubbing = false; + update(); + return true; + } + break; } } return false; @@ -263,4 +351,235 @@ public class TimeBar extends View { mShowScrubber = canSeek; } + private void updateBounds() { + int showTime = mTotalTime > mCurrentTime ? mTotalTime : mCurrentTime; + if (mLastShowTime == showTime) { + // do not need to recompute the bounds. + return; + } + String durationText = stringForTime(showTime); + int length = durationText.length(); + mTimeTextPaint.getTextBounds(durationText, 0, length, mTimeBounds); + mLastShowTime = showTime; + if (LOG) { + Log.v(TAG, "updateBounds() durationText=" + durationText + ", timeBounds=" + + mTimeBounds); + } + } + + public void setScrubbing(boolean enable) { + if (LOG) { + Log.v(TAG, "setScrubbing(" + enable + ") scrubbing=" + mScrubbing); + } + mEnableScrubbing = enable; + if (mScrubbing) { // if it is scrubbing, change it to false + mListener.onScrubbingEnd(getScrubberTime(), 0, 0); + mScrubbing = false; + } + } + + public boolean getScrubbing() { + if (LOG) { + Log.v(TAG, "mEnableScrubbing=" + mEnableScrubbing); + } + return mEnableScrubbing; + } + + public void setInfo(String info) { + if (LOG) { + Log.v(TAG, "setInfo(" + info + ")"); + } + mInfoExt.setInfo(info); + mInfoExt.updateVisibleText(this, mProgressBar, mTimeBounds); + invalidate(); + } + + public void setSecondaryProgress(int percent) { + if (LOG) { + Log.v(TAG, "setSecondaryProgress(" + percent + ")"); + } + mSecondaryProgressExt.setSecondaryProgress(mProgressBar, percent); + invalidate(); + } +} + +interface ITimeBarInfoExt { + void init(float textSizeInPx); + + void setInfo(String info); + + void draw(Canvas canvas, Rect infoBounds); + + void updateVisibleText(View parent, Rect progressBar, Rect timeBounds); +} + +interface ITimeBarSecondaryProgressExt { + void init(); + + void setSecondaryProgress(Rect progressBar, int percent); + + void draw(Canvas canvas, Rect progressBounds); +} + +interface ITimeBarLayoutExt { + void init(int scrubberPadding, int vPaddingInPx); + + int getPreferredHeight(int originalPreferredHeight, Rect timeBounds); + + int getBarHeight(int originalBarHeight, Rect timeBounds); + + int getProgressMargin(int originalMargin); + + int getProgressOffset(Rect timeBounds); + + int getTimeOffset(); + + Rect getInfoBounds(View parent, Rect timeBounds); +} + +class TimeBarInfoExtImpl implements ITimeBarInfoExt { + private static final String TAG = "TimeBarInfoExtensionImpl"; + private static final boolean LOG = false; + private static final String ELLIPSE = "..."; + + private Paint mInfoPaint; + private Rect mInfoBounds; + private String mInfoText; + private String mVisibleText; + private int mEllipseLength; + + @Override + public void init(float textSizeInPx) { + mInfoPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mInfoPaint.setColor(0xFFCECECE); + mInfoPaint.setTextSize(textSizeInPx); + mInfoPaint.setTextAlign(Paint.Align.CENTER); + + mEllipseLength = (int) Math.ceil(mInfoPaint.measureText(ELLIPSE)); + } + + @Override + public void draw(Canvas canvas, Rect infoBounds) { + if (mInfoText != null && mVisibleText != null) { + canvas.drawText(mVisibleText, infoBounds.centerX(), infoBounds.centerY(), mInfoPaint); + } + } + + @Override + public void setInfo(String info) { + mInfoText = info; + } + + public void updateVisibleText(View parent, Rect progressBar, Rect timeBounds) { + if (mInfoText == null) { + mVisibleText = null; + return; + } + float tw = mInfoPaint.measureText(mInfoText); + float space = progressBar.width() - timeBounds.width() * 2 - parent.getPaddingLeft() + - parent.getPaddingRight(); + if (tw > 0 && space > 0 && tw > space) { + // we need to cut the info text for visible + float originalNum = mInfoText.length(); + int realNum = (int) ((space - mEllipseLength) * originalNum / tw); + if (LOG) { + Log.v(TAG, "updateVisibleText() infoText=" + mInfoText + " text width=" + tw + + ", space=" + space + ", originalNum=" + originalNum + ", realNum=" + + realNum + + ", getPaddingLeft()=" + parent.getPaddingLeft() + ", getPaddingRight()=" + + parent.getPaddingRight() + + ", progressBar=" + progressBar + ", timeBounds=" + timeBounds); + } + mVisibleText = mInfoText.substring(0, realNum) + ELLIPSE; + } else { + mVisibleText = mInfoText; + } + if (LOG) { + Log.v(TAG, "updateVisibleText() infoText=" + mInfoText + ", visibleText=" + + mVisibleText + + ", text width=" + tw + ", space=" + space); + } + } +} + +class TimeBarSecondaryProgressExtImpl implements ITimeBarSecondaryProgressExt { + private static final String TAG = "TimeBarSecondaryProgressExtensionImpl"; + private static final boolean LOG = false; + + private int mBufferPercent; + private Rect mSecondaryBar; + private Paint mSecondaryPaint; + + @Override + public void init() { + mSecondaryBar = new Rect(); + mSecondaryPaint = new Paint(); + mSecondaryPaint.setColor(0xFF5CA0C5); + } + + @Override + public void draw(Canvas canvas, Rect progressBounds) { + if (mBufferPercent >= 0) { + mSecondaryBar.set(progressBounds); + mSecondaryBar.right = mSecondaryBar.left + + (int) (mBufferPercent * progressBounds.width() / 100); + canvas.drawRect(mSecondaryBar, mSecondaryPaint); + } + if (LOG) { + Log.v(TAG, "draw() bufferPercent=" + mBufferPercent + ", secondaryBar=" + + mSecondaryBar); + } + } + + @Override + public void setSecondaryProgress(Rect progressBar, int percent) { + mBufferPercent = percent; + } +} + +class TimeBarLayoutExtImpl implements ITimeBarLayoutExt { + private static final String TAG = "TimeBarLayoutExtensionImpl"; + private static final boolean LOG = false; + + private int mTextPadding; + private int mVPaddingInPx; + + @Override + public void init(int scrubberPadding, int vPaddingInPx) { + mTextPadding = scrubberPadding / 2; + mVPaddingInPx = vPaddingInPx; + } + + @Override + public int getPreferredHeight(int originalPreferredHeight, Rect timeBounds) { + return originalPreferredHeight + timeBounds.height() + mTextPadding; + } + + @Override + public int getBarHeight(int originalBarHeight, Rect timeBounds) { + return originalBarHeight + timeBounds.height() + mTextPadding; + } + + @Override + public int getProgressMargin(int originalMargin) { + return 0; + } + + @Override + public int getProgressOffset(Rect timeBounds) { + return (timeBounds.height() + mTextPadding) / 2; + } + + @Override + public int getTimeOffset() { + return mTextPadding - mVPaddingInPx / 2; + } + + @Override + public Rect getInfoBounds(View parent, Rect timeBounds) { + Rect bounds = new Rect(parent.getPaddingLeft(), 0, + parent.getWidth() - parent.getPaddingRight(), + (timeBounds.height() + mTextPadding * 3 + 1) * 2); + return bounds; + } } diff --git a/src/com/android/gallery3d/app/TrimControllerOverlay.java b/src/com/android/gallery3d/app/TrimControllerOverlay.java index cae016626..9d2e7aee1 100644 --- a/src/com/android/gallery3d/app/TrimControllerOverlay.java +++ b/src/com/android/gallery3d/app/TrimControllerOverlay.java @@ -108,4 +108,14 @@ public class TrimControllerOverlay extends CommonControllerOverlay { } return true; } + + @Override + public void setViewEnabled(boolean isEnabled) { + // TODO Auto-generated method stub + } + + @Override + public void setPlayPauseReplayResume() { + // TODO Auto-generated method stub + } } diff --git a/src/com/android/gallery3d/app/TrimVideo.java b/src/com/android/gallery3d/app/TrimVideo.java index b0ed8e635..a52c25606 100644 --- a/src/com/android/gallery3d/app/TrimVideo.java +++ b/src/com/android/gallery3d/app/TrimVideo.java @@ -57,6 +57,7 @@ public class TrimVideo extends Activity implements private int mTrimStartTime = 0; private int mTrimEndTime = 0; + private boolean mCheckTrimStartTime; private int mVideoPosition = 0; public static final String KEY_TRIM_START = "trim_start"; public static final String KEY_TRIM_END = "trim_end"; @@ -177,10 +178,11 @@ public class TrimVideo extends Activity implements mVideoPosition = mVideoView.getCurrentPosition(); // If the video position is smaller than the starting point of trimming, // correct it. - if (mVideoPosition < mTrimStartTime) { + if (mCheckTrimStartTime && (mVideoPosition < mTrimStartTime)) { mVideoView.seekTo(mTrimStartTime); mVideoPosition = mTrimStartTime; } + mCheckTrimStartTime = false; // If the position is bigger than the end point of trimming, show the // replay button and pause. if (mVideoPosition >= mTrimEndTime && mTrimEndTime > 0) { @@ -204,6 +206,7 @@ public class TrimVideo extends Activity implements private void playVideo() { mVideoView.start(); + mCheckTrimStartTime = true; mController.showPlaying(); setProgress(); } @@ -237,6 +240,7 @@ public class TrimVideo extends Activity implements new Thread(new Runnable() { @Override public void run() { + boolean hasError = false; try { VideoUtils.startTrim(mSrcFile, mDstFileInfo.mFile, mTrimStartTime, mTrimEndTime); @@ -244,7 +248,28 @@ public class TrimVideo extends Activity implements SaveVideoFileUtils.insertContent(mDstFileInfo, getContentResolver(), mUri); } catch (IOException e) { + hasError = true; e.printStackTrace(); + } catch (IllegalStateException e) { + hasError = true; + e.printStackTrace(); + } + //If the exception happens,just notify the UI and avoid the crash. + if (hasError){ + mHandler.post(new Runnable(){ + @Override + public void run(){ + Toast.makeText(getApplicationContext(), + getString(R.string.fail_trim), + Toast.LENGTH_SHORT) + .show(); + if (mProgress != null) { + mProgress.dismiss(); + mProgress = null; + } + } + }); + return; } // After trimming is done, trigger the UI changed. mHandler.post(new Runnable() { @@ -320,6 +345,12 @@ public class TrimVideo extends Activity implements } @Override + public boolean onIsRTSP() { + // TODO Auto-generated method stub + return false; + } + + @Override public void onReplay() { mVideoView.seekTo(mTrimStartTime); playVideo(); diff --git a/src/com/android/gallery3d/app/VideoUtils.java b/src/com/android/gallery3d/app/VideoUtils.java index 359cf76f5..4f551a67d 100644..100755 --- a/src/com/android/gallery3d/app/VideoUtils.java +++ b/src/com/android/gallery3d/app/VideoUtils.java @@ -156,11 +156,16 @@ public class VideoUtils { if (selectCurrentTrack) { extractor.selectTrack(i); - int dstIndex = muxer.addTrack(format); - indexMap.put(i, dstIndex); - if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) { - int newSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); - bufferSize = newSize > bufferSize ? newSize : bufferSize; + try { + int dstIndex = muxer.addTrack(format); + indexMap.put(i, dstIndex); + if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) { + int newSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); + bufferSize = newSize > bufferSize ? newSize : bufferSize; + } + } catch (IllegalArgumentException e) { + Log.e(LOGTAG, "Unsupported format '" + mime + "'"); + throw new IOException("Muxer does not support " + mime); } } } @@ -221,6 +226,11 @@ public class VideoUtils { } catch (IllegalStateException e) { // Swallow the exception due to malformed source. Log.w(LOGTAG, "The source video file is malformed"); + File f = new File(dstPath); + if (f.exists()) { + f.delete(); + } + throw e; } finally { muxer.release(); } diff --git a/src/com/android/gallery3d/app/Wallpaper.java b/src/com/android/gallery3d/app/Wallpaper.java index 2022f5a4a..5c19d9016 100644 --- a/src/com/android/gallery3d/app/Wallpaper.java +++ b/src/com/android/gallery3d/app/Wallpaper.java @@ -44,6 +44,11 @@ public class Wallpaper extends Activity { private static final String IMAGE_TYPE = "image/*"; private static final String KEY_STATE = "activity-state"; private static final String KEY_PICKED_ITEM = "picked-item"; + private static final String KEY_ASPECT_X = "aspectX"; + private static final String KEY_ASPECT_Y = "aspectY"; + private static final String KEY_SPOTLIGHT_X = "spotlightX"; + private static final String KEY_SPOTLIGHT_Y = "spotlightY"; + private static final String KEY_FROM_SCREENCOLOR = "fromScreenColor"; private static final int STATE_INIT = 0; private static final int STATE_PHOTO_PICKED = 1; @@ -100,7 +105,14 @@ public class Wallpaper extends Activity { } case STATE_PHOTO_PICKED: { Intent cropAndSetWallpaperIntent; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + boolean fromScreenColor = false; + + // Do this for screencolor select and crop image to preview. + Bundle extras = intent.getExtras(); + if (extras != null) { + fromScreenColor = extras.getBoolean(KEY_FROM_SCREENCOLOR, false); + } + if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) && (!fromScreenColor)) { WallpaperManager wpm = WallpaperManager.getInstance(getApplicationContext()); try { cropAndSetWallpaperIntent = wpm.getCropAndSetWallpaperIntent(mPickedItem); @@ -114,11 +126,23 @@ public class Wallpaper extends Activity { } } - int width = getWallpaperDesiredMinimumWidth(); - int height = getWallpaperDesiredMinimumHeight(); - Point size = getDefaultDisplaySize(new Point()); - float spotlightX = (float) size.x / width; - float spotlightY = (float) size.y / height; + int width,height; + float spotlightX,spotlightY; + + if (fromScreenColor) { + width = extras.getInt(KEY_ASPECT_X, 0); + height = extras.getInt(KEY_ASPECT_Y, 0); + spotlightX = extras.getFloat(KEY_SPOTLIGHT_X, 0); + spotlightY = extras.getFloat(KEY_SPOTLIGHT_Y, 0); + } else { + width = getWallpaperDesiredMinimumWidth(); + height = getWallpaperDesiredMinimumHeight(); + Point size = getDefaultDisplaySize(new Point()); + spotlightX = (float) size.x / width; + spotlightY = (float) size.y / height; + } + + //Don't set wallpaper from screencolor. cropAndSetWallpaperIntent = new Intent(CropActivity.CROP_ACTION) .setClass(this, CropActivity.class) .setDataAndType(mPickedItem, IMAGE_TYPE) @@ -131,7 +155,7 @@ public class Wallpaper extends Activity { .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); + .putExtra(CropExtras.KEY_SET_AS_WALLPAPER, !fromScreenColor); startActivity(cropAndSetWallpaperIntent); finish(); } |