summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/app
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/app')
-rw-r--r--src/com/android/gallery3d/app/AbstractGalleryActivity.java66
-rw-r--r--src/com/android/gallery3d/app/AlbumDataLoader.java7
-rw-r--r--src/com/android/gallery3d/app/AlbumPage.java92
-rw-r--r--src/com/android/gallery3d/app/AlbumSetPage.java32
-rw-r--r--src/com/android/gallery3d/app/CommonControllerOverlay.java16
-rw-r--r--src/com/android/gallery3d/app/Config.java17
-rw-r--r--src/com/android/gallery3d/app/ControllerOverlay.java8
-rw-r--r--src/com/android/gallery3d/app/GalleryActionBar.java8
-rw-r--r--src/com/android/gallery3d/app/GalleryActivity.java5
-rw-r--r--src/com/android/gallery3d/app/GalleryAppImpl.java6
-rw-r--r--src/com/android/gallery3d/app/MovieActivity.java605
-rw-r--r--src/com/android/gallery3d/app/MovieControllerOverlay.java837
-rwxr-xr-x[-rw-r--r--]src/com/android/gallery3d/app/MoviePlayer.java1251
-rwxr-xr-x[-rw-r--r--]src/com/android/gallery3d/app/MuteVideo.java53
-rwxr-xr-x[-rw-r--r--]src/com/android/gallery3d/app/PhotoDataAdapter.java132
-rwxr-xr-x[-rw-r--r--]src/com/android/gallery3d/app/PhotoPage.java147
-rwxr-xr-x[-rw-r--r--]src/com/android/gallery3d/app/SinglePhotoDataAdapter.java17
-rw-r--r--src/com/android/gallery3d/app/SlideshowPage.java16
-rw-r--r--src/com/android/gallery3d/app/StorageChangeReceiver.java39
-rwxr-xr-x[-rw-r--r--]src/com/android/gallery3d/app/TimeBar.java373
-rw-r--r--src/com/android/gallery3d/app/TrimControllerOverlay.java10
-rw-r--r--src/com/android/gallery3d/app/TrimVideo.java33
-rwxr-xr-x[-rw-r--r--]src/com/android/gallery3d/app/VideoUtils.java20
-rw-r--r--src/com/android/gallery3d/app/Wallpaper.java38
24 files changed, 3636 insertions, 192 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..629e88210 100644
--- a/src/com/android/gallery3d/app/AlbumPage.java
+++ b/src/com/android/gallery3d/app/AlbumPage.java
@@ -19,16 +19,19 @@ package com.android.gallery3d.app;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.drm.DrmHelper;
import android.graphics.Rect;
import android.net.Uri;
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 +62,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 +86,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 +160,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 +171,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 +274,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 +297,31 @@ 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
+ }
+ if (DrmHelper.isDrmFile(DrmHelper.getFilePath(
+ mActivity.getAndroidContext(), item.getContentUri()))) {
+ Toast.makeText(mActivity, R.string.no_permission_for_drm,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
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 +337,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 +512,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 +618,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 +695,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..f6fcaf87c 100644
--- a/src/com/android/gallery3d/app/MovieActivity.java
+++ b/src/com/android/gallery3d/app/MovieActivity.java
@@ -18,31 +18,63 @@ 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.drm.DrmHelper;
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 +85,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 +179,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 +217,39 @@ 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());
+ }
+ });
+
+ // DRM validation
+ Uri original = intent.getData();
+ String mimeType = intent.getType();
+ String filepath = DrmHelper.getFilePath(this, original);
+ if (DrmHelper.isDrmFile(filepath)) {
+ if (!DrmHelper.validateLicense(this, filepath, mimeType)) {
+ finish();
+ }
+ }
}
private void setActionBarLogoFromIntent(Intent intent) {
@@ -132,9 +268,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 +318,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;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ mMovieHooker.onPrepareOptionsMenu(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());
+ 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 +512,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 +537,8 @@ public class MovieActivity extends Activity {
.requestAudioFocus(null, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
super.onStart();
+ mMovieHooker.onStart();
+ registerScreenReceiver();
}
@Override
@@ -223,18 +546,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 +622,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 +653,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..5812bf82d 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();
@@ -506,9 +522,16 @@ public class PhotoDataAdapter implements PhotoPage.Model {
@Override
public boolean isVideo(int offset) {
MediaItem item = getItem(mCurrentIndex + offset);
- return (item == null)
- ? false
- : item.getMediaType() == MediaItem.MEDIA_TYPE_VIDEO;
+ return (item == null) ? false
+ : item.getMediaType() == MediaItem.MEDIA_TYPE_VIDEO
+ || item.getMediaType() == MediaItem.MEDIA_TYPE_DRM_VIDEO;
+ }
+
+ @Override
+ public boolean isGif(int offset) {
+ MediaItem item = getItem(mCurrentIndex + offset);
+ return (item != null) &&
+ MediaItem.MIME_TYPE_GIF.equalsIgnoreCase(item.getMimeType());
}
@Override
@@ -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..65c26278a 100644..100755
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -1,4 +1,4 @@
-/*
+/**
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,7 +24,9 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.drm.DrmHelper;
import android.graphics.Rect;
+import android.media.MediaFile;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateBeamUrisCallback;
@@ -33,6 +35,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 +75,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 +109,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 +124,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 +173,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;
@@ -362,8 +378,9 @@ public abstract class PhotoPage extends ActivityState implements
panoramaIntent = createSharePanoramaIntent(contentUri);
}
Intent shareIntent = createShareIntent(mCurrentPhoto);
-
- mActionBar.setShareIntents(panoramaIntent, shareIntent, PhotoPage.this);
+ if (shareIntent != null) {
+ mActionBar.setShareIntents(panoramaIntent, shareIntent, PhotoPage.this);
+ }
setNfcBeamPushUri(contentUri);
}
break;
@@ -389,6 +406,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 +483,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 +515,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
@@ -637,7 +685,7 @@ public abstract class PhotoPage extends ActivityState implements
mNfcPushUris[0] = uri;
}
- private static Intent createShareIntent(MediaObject mediaObject) {
+ private Intent createShareIntent(MediaObject mediaObject) {
int type = mediaObject.getMediaType();
return new Intent(Intent.ACTION_SEND)
.setType(MenuExecutor.getMimeType(type))
@@ -756,6 +804,20 @@ public abstract class PhotoPage extends ActivityState implements
requestDeferredUpdate();
} else {
updateUIForCurrentPhoto();
+
+ // Manage DRM rights while image selection changed. this
+ // flow will comes for both image and video, but here
+ // we will consume rights for image files only.
+ // Do not consume rights of a GIF image and video here.
+ // ViewGifImage will take care of GIF rights consumption stub.
+ // MediaPlayer will handle the video rights consumption stub.
+ String mime = mCurrentPhoto.getMimeType();
+ if (!TextUtils.isEmpty(mime) && !mime.equals("image/gif")
+ && !mime.startsWith("video/")) {
+ DrmHelper.manageDrmLicense(mActivity.getAndroidContext(),
+ mHandler, mCurrentPhoto.getFilePath(),
+ mCurrentPhoto.getMimeType());
+ }
}
}
@@ -773,7 +835,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 +1084,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 +1125,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: {
@@ -1098,6 +1173,12 @@ public abstract class PhotoPage extends ActivityState implements
mSelectionManager.toggle(path);
mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener);
return true;
+ case R.id.action_drm_info:
+ String filepath = current.getFilePath();
+ if (DrmHelper.isDrmFile(filepath)) {
+ DrmHelper.showDrmInfo(mActivity.getAndroidContext(), filepath);
+ }
+ return true;
default :
return false;
}
@@ -1136,6 +1217,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 +1285,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 +1376,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);
}
@@ -1347,6 +1445,23 @@ public abstract class PhotoPage extends ActivityState implements
UsageStatistics.onContentViewChanged(
UsageStatistics.COMPONENT_CAMERA, "Unknown"); // TODO
}
+
+ // Manage DRM rights while image selection changed. this
+ // flow will comes for both image and video, but here
+ // we will consume rights for image files only.
+ // Do not consume rights of a GIF image and video here.
+ // ViewGifImage will take care of GIF rights consumption stub.
+ // MediaPlayer will handle the video rights consumption stub.
+ if ((mMediaSet != null && mMediaSet.getMediaItemCount() > 1)
+ || !(this instanceof SinglePhotoPage)) {
+ String mime = mCurrentPhoto.getMimeType();
+ if (!TextUtils.isEmpty(mime) && !mime.equals("image/gif")
+ && !mime.startsWith("video/")) {
+ DrmHelper.manageDrmLicense(mActivity.getAndroidContext(),
+ mHandler, mCurrentPhoto.getFilePath(),
+ mCurrentPhoto.getMimeType());
+ }
+ }
}
}
@@ -1384,6 +1499,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 +1654,11 @@ public abstract class PhotoPage extends ActivityState implements
}
}
+ private static void viewAnimateGif(Activity activity, Uri uri) {
+ Intent intent = new Intent(ViewGifImage.VIEW_GIF_ACTION, uri);
+ if (DrmHelper.isDrmFile(uri.toString())) {
+ intent.setDataAndType(uri, "image/gif");
+ }
+ 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/SlideshowPage.java b/src/com/android/gallery3d/app/SlideshowPage.java
index 174058dc8..2b15ab96e 100644
--- a/src/com/android/gallery3d/app/SlideshowPage.java
+++ b/src/com/android/gallery3d/app/SlideshowPage.java
@@ -16,8 +16,12 @@
package com.android.gallery3d.app;
+import java.util.ArrayList;
+import java.util.Random;
+
import android.app.Activity;
import android.content.Intent;
+import android.drm.DrmHelper;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
@@ -38,9 +42,6 @@ import com.android.gallery3d.ui.SynchronizedHandler;
import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.FutureListener;
-import java.util.ArrayList;
-import java.util.Random;
-
public class SlideshowPage extends ActivityState {
private static final String TAG = "SlideshowPage";
@@ -338,6 +339,15 @@ public class SlideshowPage extends ActivityState {
mData = mMediaSet.getMediaItem(index, DATA_SIZE);
mDataStart = index;
dataEnd = index + mData.size();
+
+ // Consume license once in each element of the slide-show
+ // This is a non-blocking loop operation
+ for (int i = 0; i < mData.size(); i++) {
+ String path = mData.get(i).getFilePath();
+ if (DrmHelper.isDrmFile(path)) {
+ DrmHelper.consumeDrmRights(path, "image/*");
+ }
+ }
}
return (index < mDataStart || index >= dataEnd) ? null : mData.get(index - mDataStart);
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();
}