diff options
author | Steve Kondik <steve@cyngn.com> | 2015-10-17 23:40:33 -0700 |
---|---|---|
committer | Steve Kondik <steve@cyngn.com> | 2015-10-17 23:40:33 -0700 |
commit | bcbf7c98a521b9ee4a7d03e00dcfc469c9b3a398 (patch) | |
tree | 85d07b2095695fe79235ab16e627f8b22c7bfd8d /src | |
parent | a3fcd50080c0546ebd5f0caf932eef02fabdd7ad (diff) | |
parent | 8de528917bf03cca93ac2a3dd19b7a5719d1a26e (diff) | |
download | android_packages_apps_Gallery2-bcbf7c98a521b9ee4a7d03e00dcfc469c9b3a398.tar.gz android_packages_apps_Gallery2-bcbf7c98a521b9ee4a7d03e00dcfc469c9b3a398.tar.bz2 android_packages_apps_Gallery2-bcbf7c98a521b9ee4a7d03e00dcfc469c9b3a398.zip |
Merge branch 'cm-12.1' of git://github.com/CyanogenMod/android_packages_apps_Gallery2 into cm-13.0
Change-Id: Ib8caa024d2e6feca332e3645038f226fd5a910a2
Diffstat (limited to 'src')
136 files changed, 10903 insertions, 459 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(); } diff --git a/src/com/android/gallery3d/data/DecodeUtils.java b/src/com/android/gallery3d/data/DecodeUtils.java index 2853baffb..7e10191b7 100644 --- a/src/com/android/gallery3d/data/DecodeUtils.java +++ b/src/com/android/gallery3d/data/DecodeUtils.java @@ -17,6 +17,7 @@ package com.android.gallery3d.data; import android.annotation.TargetApi; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; @@ -32,6 +33,7 @@ import com.android.gallery3d.ui.Log; import com.android.gallery3d.util.ThreadPool.CancelListener; import com.android.gallery3d.util.ThreadPool.JobContext; +import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.InputStream; @@ -308,4 +310,46 @@ public class DecodeUtils { decodeBounds(jc, fileDescriptor, options); return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight); } + + public static Bitmap decodeBitmap(Resources res, int resId, int reqWidth, int reqHeight) { + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeResource(res, resId, options); + + // Calculate inSampleSize (use 1024 as maximum size, the minimum supported + // by all the gles20 devices) + options.inSampleSize = calculateBitmapRatio( + options, + Math.min(reqWidth, 1024), + Math.min(reqHeight, 1024)); + + // Decode the bitmap with inSampleSize set + options.inJustDecodeBounds = false; + options.inPreferQualityOverSpeed = false; + options.inPurgeable = true; + options.inInputShareable = true; + options.inDither = true; + return BitmapFactory.decodeResource(res, resId, options); + } + + private static int calculateBitmapRatio(Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + // Calculate ratios of height and width to requested height and width + final int heightRatio = Math.round((float) height / (float) reqHeight); + final int widthRatio = Math.round((float) width / (float) reqWidth); + + // Choose the smallest ratio as inSampleSize value, this will guarantee + // a final image with both dimensions larger than or equal to the + // requested height and width. + inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; + } + + return inSampleSize; + } } diff --git a/src/com/android/gallery3d/data/FaceClustering.java b/src/com/android/gallery3d/data/FaceClustering.java index 819915edb..a0d567902 100644..100755 --- a/src/com/android/gallery3d/data/FaceClustering.java +++ b/src/com/android/gallery3d/data/FaceClustering.java @@ -83,7 +83,7 @@ public class FaceClustering extends Clustering { } public FaceClustering(Context context) { - mUntaggedString = context.getResources().getString(R.string.untagged); + mUntaggedString = context.getResources().getString(R.string.no_faces); mContext = context; } diff --git a/src/com/android/gallery3d/data/FilterDeleteSet.java b/src/com/android/gallery3d/data/FilterDeleteSet.java index c76412ff8..f7329739d 100644 --- a/src/com/android/gallery3d/data/FilterDeleteSet.java +++ b/src/com/android/gallery3d/data/FilterDeleteSet.java @@ -223,6 +223,11 @@ public class FilterDeleteSet extends MediaSet implements ContentListener { return mDataVersion; } + @Override + public int getCurrectSize() { + return mCurrent.size(); + } + private void sendRequest(int type, Path path, int indexHint) { Request r = new Request(type, path, indexHint); synchronized (mRequests) { diff --git a/src/com/android/gallery3d/data/FilterTypeSet.java b/src/com/android/gallery3d/data/FilterTypeSet.java index 477ef73ad..e778ceb12 100644 --- a/src/com/android/gallery3d/data/FilterTypeSet.java +++ b/src/com/android/gallery3d/data/FilterTypeSet.java @@ -102,7 +102,8 @@ public class FilterTypeSet extends MediaSet implements ContentListener { mBaseSet.enumerateMediaItems(new MediaSet.ItemConsumer() { @Override public void consume(int index, MediaItem item) { - if (item.getMediaType() == mMediaType) { + if (item.getMediaType() == mMediaType + || item.getMediaType() == MediaObject.MEDIA_TYPE_DRM_IMAGE) { if (index < 0 || index >= total) return; Path path = item.getPath(); buf[index] = path; diff --git a/src/com/android/gallery3d/data/ImageCacheRequest.java b/src/com/android/gallery3d/data/ImageCacheRequest.java index 6cbc5c5ea..faca5d7d8 100644 --- a/src/com/android/gallery3d/data/ImageCacheRequest.java +++ b/src/com/android/gallery3d/data/ImageCacheRequest.java @@ -16,8 +16,10 @@ package com.android.gallery3d.data; +import android.drm.DrmHelper; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.text.TextUtils; import com.android.gallery3d.app.GalleryApp; import com.android.gallery3d.common.BitmapUtils; @@ -32,6 +34,8 @@ abstract class ImageCacheRequest implements Job<Bitmap> { private Path mPath; private int mType; private int mTargetSize; + private String mFilePath; + private String mMimeType; private long mTimeModified; public ImageCacheRequest(GalleryApp application, @@ -43,6 +47,14 @@ abstract class ImageCacheRequest implements Job<Bitmap> { mTimeModified = timeModified; } + public ImageCacheRequest(GalleryApp application, + Path path, long timeModified, int type, int targetSize, String filepath, String mimeType) { + this(application, path, timeModified, type, + targetSize); + mFilePath = filepath; + mMimeType = mimeType; + } + private String debugTag() { return mPath + "," + mTimeModified + "," + ((mType == MediaItem.TYPE_THUMBNAIL) ? "THUMB" : @@ -51,6 +63,14 @@ abstract class ImageCacheRequest implements Job<Bitmap> { @Override public Bitmap run(JobContext jc) { + if (!TextUtils.isEmpty(mFilePath) && !TextUtils.isEmpty(mMimeType) + && !mMimeType.startsWith("video/")) { + if (DrmHelper.isDrmFile(mFilePath) + && mType != MediaItem.TYPE_MICROTHUMBNAIL) { + return onDecodeOriginal(jc, mType); + } + } + ImageCacheService cacheService = mApplication.getImageCacheService(); BytesBuffer buffer = MediaItem.getBytesBufferPool().get(); @@ -76,6 +96,7 @@ abstract class ImageCacheRequest implements Job<Bitmap> { } finally { MediaItem.getBytesBufferPool().recycle(buffer); } + Bitmap bitmap = onDecodeOriginal(jc, mType); if (jc.isCancelled()) return null; diff --git a/src/com/android/gallery3d/data/LocalAlbum.java b/src/com/android/gallery3d/data/LocalAlbum.java index 7b7015af6..0757d8883 100644 --- a/src/com/android/gallery3d/data/LocalAlbum.java +++ b/src/com/android/gallery3d/data/LocalAlbum.java @@ -95,7 +95,7 @@ public class LocalAlbum extends MediaSet { @Override public boolean isCameraRoll() { - return mBucketId == MediaSetUtils.CAMERA_BUCKET_ID; + return mBucketId == MediaSetUtils.getCameraBucketId(); } @Override @@ -279,7 +279,7 @@ public class LocalAlbum extends MediaSet { public static String getLocalizedName(Resources res, int bucketId, String name) { - if (bucketId == MediaSetUtils.CAMERA_BUCKET_ID) { + if (bucketId == MediaSetUtils.getCameraBucketId()) { return res.getString(R.string.folder_camera); } else if (bucketId == MediaSetUtils.DOWNLOAD_BUCKET_ID) { return res.getString(R.string.folder_download); @@ -297,7 +297,7 @@ public class LocalAlbum extends MediaSet { // Relative path is the absolute path minus external storage path public static String getRelativePath(int bucketId) { String relativePath = "/"; - if (bucketId == MediaSetUtils.CAMERA_BUCKET_ID) { + if (bucketId == MediaSetUtils.getCameraBucketId()) { relativePath += BucketNames.CAMERA; } else if (bucketId == MediaSetUtils.DOWNLOAD_BUCKET_ID) { relativePath += BucketNames.DOWNLOAD; diff --git a/src/com/android/gallery3d/data/LocalAlbumSet.java b/src/com/android/gallery3d/data/LocalAlbumSet.java index b2b4b8c5d..877eaff5c 100644 --- a/src/com/android/gallery3d/data/LocalAlbumSet.java +++ b/src/com/android/gallery3d/data/LocalAlbumSet.java @@ -113,7 +113,7 @@ public class LocalAlbumSet extends MediaSet int offset = 0; // Move camera and download bucket to the front, while keeping the // order of others. - int index = findBucket(entries, MediaSetUtils.CAMERA_BUCKET_ID); + int index = findBucket(entries, MediaSetUtils.getCameraBucketId()); if (index != -1) { circularShiftRight(entries, offset++, index); } diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java index 2b01c1e22..1b0384548 100644 --- a/src/com/android/gallery3d/data/LocalImage.java +++ b/src/com/android/gallery3d/data/LocalImage.java @@ -20,6 +20,7 @@ import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; +import android.drm.DrmHelper; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; @@ -173,7 +174,7 @@ public class LocalImage extends LocalMediaItem { @Override public Job<Bitmap> requestImage(int type) { return new LocalImageRequest(mApplication, mPath, dateModifiedInSec, - type, filePath); + type, filePath, mimeType); } public static class LocalImageRequest extends ImageCacheRequest { @@ -186,10 +187,23 @@ public class LocalImage extends LocalMediaItem { mLocalFilePath = localFilePath; } + LocalImageRequest(GalleryApp application, Path path, long timeModified, + int type, String localFilePath, String mimeType) { + super(application, path, timeModified, type, + MediaItem.getTargetSize(type),localFilePath, mimeType); + mLocalFilePath = localFilePath; + } + @Override public Bitmap onDecodeOriginal(JobContext jc, final int type) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_8888; + + if (DrmHelper.isDrmFile(mLocalFilePath)) { + return DecodeUtils.ensureGLCompatibleBitmap(DrmHelper + .getBitmap(mLocalFilePath, options)); + } + int targetSize = MediaItem.getTargetSize(type); // try to decode from JPEG EXIF @@ -230,24 +244,41 @@ public class LocalImage extends LocalMediaItem { @Override public BitmapRegionDecoder run(JobContext jc) { + if (DrmHelper.isDrmFile(mLocalFilePath)) { + return DrmHelper.createBitmapRegionDecoder(mLocalFilePath, + false); + } + return DecodeUtils.createBitmapRegionDecoder(jc, mLocalFilePath, false); } } @Override public int getSupportedOperations() { - int operation = SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_CROP + int operation = SUPPORT_DELETE | SUPPORT_INFO; + if (DrmHelper.isDrmFile(getFilePath())) { + if (DrmHelper.isDrmFLBlocking(mApplication.getAndroidContext(), + getFilePath())) { + operation |= SUPPORT_SETAS; + } + operation |= SUPPORT_DRM_INFO | SUPPORT_FULL_IMAGE; + if (DrmHelper.isShareableDrmFile(getFilePath())) { + operation |= SUPPORT_SHARE; + } + } else { + operation = SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_CROP | SUPPORT_SETAS | SUPPORT_PRINT | SUPPORT_INFO; - if (BitmapUtils.isSupportedByRegionDecoder(mimeType)) { - operation |= SUPPORT_FULL_IMAGE | SUPPORT_EDIT; - } + if (BitmapUtils.isSupportedByRegionDecoder(mimeType)) { + operation |= SUPPORT_FULL_IMAGE | SUPPORT_EDIT; + } - if (BitmapUtils.isRotationSupported(mimeType)) { - operation |= SUPPORT_ROTATE; - } + if (BitmapUtils.isRotationSupported(mimeType)) { + operation |= SUPPORT_ROTATE; + } - if (GalleryUtils.isValidLocation(latitude, longitude)) { - operation |= SUPPORT_SHOW_ON_MAP; + if (GalleryUtils.isValidLocation(latitude, longitude)) { + operation |= SUPPORT_SHOW_ON_MAP; + } } return operation; } @@ -313,6 +344,10 @@ public class LocalImage extends LocalMediaItem { @Override public int getMediaType() { + if (DrmHelper.isDrmFile(getFilePath())) { + return MEDIA_TYPE_DRM_IMAGE; + } + return MEDIA_TYPE_IMAGE; } diff --git a/src/com/android/gallery3d/data/LocalVideo.java b/src/com/android/gallery3d/data/LocalVideo.java index 4b8774ca4..7fafe97ae 100644 --- a/src/com/android/gallery3d/data/LocalVideo.java +++ b/src/com/android/gallery3d/data/LocalVideo.java @@ -18,6 +18,7 @@ package com.android.gallery3d.data; import android.content.ContentResolver; import android.database.Cursor; +import android.drm.DrmHelper; import android.graphics.Bitmap; import android.graphics.BitmapRegionDecoder; import android.net.Uri; @@ -153,7 +154,7 @@ public class LocalVideo extends LocalMediaItem { @Override public Job<Bitmap> requestImage(int type) { return new LocalVideoRequest(mApplication, getPath(), dateModifiedInSec, - type, filePath); + type, filePath, mimeType); } public static class LocalVideoRequest extends ImageCacheRequest { @@ -166,6 +167,13 @@ public class LocalVideo extends LocalMediaItem { mLocalFilePath = localFilePath; } + LocalVideoRequest(GalleryApp application, Path path, long timeModified, + int type, String localFilePath, String mimeType) { + super(application, path, timeModified, type, + MediaItem.getTargetSize(type), localFilePath, mimeType); + mLocalFilePath = localFilePath; + } + @Override public Bitmap onDecodeOriginal(JobContext jc, int type) { Bitmap bitmap = BitmapUtils.createVideoThumbnail(mLocalFilePath); @@ -182,7 +190,17 @@ public class LocalVideo extends LocalMediaItem { @Override public int getSupportedOperations() { - return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_PLAY | SUPPORT_INFO | SUPPORT_TRIM | SUPPORT_MUTE; + if (DrmHelper.isDrmFile(getFilePath())) { + int operation = SUPPORT_DELETE | SUPPORT_PLAY | SUPPORT_INFO + | SUPPORT_DRM_INFO; + if (DrmHelper.isShareableDrmFile(getFilePath())) { + operation |= SUPPORT_SHARE; + } + return operation; + } + + return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_PLAY | SUPPORT_INFO + | SUPPORT_TRIM | SUPPORT_MUTE; } @Override @@ -211,6 +229,10 @@ public class LocalVideo extends LocalMediaItem { @Override public int getMediaType() { + if (DrmHelper.isDrmFile(getFilePath())) { + return MEDIA_TYPE_DRM_VIDEO; + } + return MEDIA_TYPE_VIDEO; } diff --git a/src/com/android/gallery3d/data/MediaDetails.java b/src/com/android/gallery3d/data/MediaDetails.java index cac524b88..225266029 100644 --- a/src/com/android/gallery3d/data/MediaDetails.java +++ b/src/com/android/gallery3d/data/MediaDetails.java @@ -17,15 +17,11 @@ package com.android.gallery3d.data; import com.android.gallery3d.R; -import com.android.gallery3d.common.Utils; import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.exif.ExifTag; -import com.android.gallery3d.exif.Rational; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; @@ -50,15 +46,16 @@ public class MediaDetails implements Iterable<Entry<Integer, Object>> { public static final int INDEX_SIZE = 10; // for EXIF - public static final int INDEX_MAKE = 100; - public static final int INDEX_MODEL = 101; - public static final int INDEX_FLASH = 102; - public static final int INDEX_FOCAL_LENGTH = 103; - public static final int INDEX_WHITE_BALANCE = 104; - public static final int INDEX_APERTURE = 105; - public static final int INDEX_SHUTTER_SPEED = 106; - public static final int INDEX_EXPOSURE_TIME = 107; - public static final int INDEX_ISO = 108; + public static final int INDEX_DATETIME_ORIGINAL = 100; + public static final int INDEX_MAKE = 101; + public static final int INDEX_MODEL = 102; + public static final int INDEX_FLASH = 103; + public static final int INDEX_FOCAL_LENGTH = 104; + public static final int INDEX_WHITE_BALANCE = 105; + public static final int INDEX_APERTURE = 106; + public static final int INDEX_SHUTTER_SPEED = 107; + public static final int INDEX_EXPOSURE_TIME = 108; + public static final int INDEX_ISO = 109; // Put this last because it may be long. public static final int INDEX_PATH = 200; @@ -148,6 +145,12 @@ public class MediaDetails implements Iterable<Entry<Integer, Object>> { MediaDetails.INDEX_WIDTH); setExifData(details, exif.getTag(ExifInterface.TAG_IMAGE_LENGTH), MediaDetails.INDEX_HEIGHT); + ExifTag recordTag = exif.getTag(ExifInterface.TAG_DATE_TIME_ORIGINAL); + if (recordTag == null) + recordTag = exif.getTag(ExifInterface.TAG_DATE_TIME_DIGITIZED); + if (recordTag == null) + recordTag = exif.getTag(ExifInterface.TAG_DATE_TIME); + setExifData(details, recordTag, MediaDetails.INDEX_DATETIME_ORIGINAL); setExifData(details, exif.getTag(ExifInterface.TAG_MAKE), MediaDetails.INDEX_MAKE); setExifData(details, exif.getTag(ExifInterface.TAG_MODEL), diff --git a/src/com/android/gallery3d/data/MediaItem.java b/src/com/android/gallery3d/data/MediaItem.java index 59ea86551..92ac88dc6 100644 --- a/src/com/android/gallery3d/data/MediaItem.java +++ b/src/com/android/gallery3d/data/MediaItem.java @@ -37,6 +37,7 @@ public abstract class MediaItem extends MediaObject { public static final int IMAGE_ERROR = -1; public static final String MIME_TYPE_JPEG = "image/jpeg"; + public static final String MIME_TYPE_GIF = "image/gif"; private static final int BYTESBUFFE_POOL_SIZE = 4; private static final int BYTESBUFFER_SIZE = 200 * 1024; diff --git a/src/com/android/gallery3d/data/MediaObject.java b/src/com/android/gallery3d/data/MediaObject.java index 530ee306e..6e3867647 100644 --- a/src/com/android/gallery3d/data/MediaObject.java +++ b/src/com/android/gallery3d/data/MediaObject.java @@ -42,12 +42,15 @@ public abstract class MediaObject { public static final int SUPPORT_CAMERA_SHORTCUT = 1 << 15; public static final int SUPPORT_MUTE = 1 << 16; public static final int SUPPORT_PRINT = 1 << 17; + public static final int SUPPORT_DRM_INFO = 1 << 18; public static final int SUPPORT_ALL = 0xffffffff; // These are the bits returned from getMediaType(): public static final int MEDIA_TYPE_UNKNOWN = 1; public static final int MEDIA_TYPE_IMAGE = 2; public static final int MEDIA_TYPE_VIDEO = 4; + public static final int MEDIA_TYPE_DRM_VIDEO = 5; + public static final int MEDIA_TYPE_DRM_IMAGE = 6; public static final int MEDIA_TYPE_ALL = MEDIA_TYPE_IMAGE | MEDIA_TYPE_VIDEO; public static final String MEDIA_TYPE_IMAGE_STRING = "image"; diff --git a/src/com/android/gallery3d/data/MediaSet.java b/src/com/android/gallery3d/data/MediaSet.java index 683aa6b32..4c009c735 100644 --- a/src/com/android/gallery3d/data/MediaSet.java +++ b/src/com/android/gallery3d/data/MediaSet.java @@ -87,6 +87,11 @@ public abstract class MediaSet extends MediaObject { return 0; } + public int getCurrectSize() { + // Dummy method, need to be override in implementation classes + return 0; + } + public MediaSet getSubMediaSet(int index) { throw new IndexOutOfBoundsException(); } diff --git a/src/com/android/gallery3d/data/SecureAlbum.java b/src/com/android/gallery3d/data/SecureAlbum.java index 204f848f8..bb505a50f 100644 --- a/src/com/android/gallery3d/data/SecureAlbum.java +++ b/src/com/android/gallery3d/data/SecureAlbum.java @@ -151,7 +151,7 @@ public class SecureAlbum extends MediaSet implements StitchingChangeListener { private boolean isCameraBucketEmpty(Uri baseUri) { Uri uri = baseUri.buildUpon() .appendQueryParameter("limit", "1").build(); - String[] selection = {String.valueOf(MediaSetUtils.CAMERA_BUCKET_ID)}; + String[] selection = {String.valueOf(MediaSetUtils.getCameraBucketId())}; Cursor cursor = mContext.getContentResolver().query(uri, PROJECTION, "bucket_id = ?", selection, null); if (cursor == null) return true; diff --git a/src/com/android/gallery3d/data/UriImage.java b/src/com/android/gallery3d/data/UriImage.java index b3fe1de03..13176e4aa 100644 --- a/src/com/android/gallery3d/data/UriImage.java +++ b/src/com/android/gallery3d/data/UriImage.java @@ -17,6 +17,7 @@ package com.android.gallery3d.data; import android.content.ContentResolver; +import android.drm.DrmHelper; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory.Options; @@ -58,12 +59,14 @@ public class UriImage extends MediaItem { private PanoramaMetadataSupport mPanoramaMetadata = new PanoramaMetadataSupport(this); private GalleryApp mApplication; + private String mFilePath; public UriImage(GalleryApp application, Path path, Uri uri, String contentType) { super(path, nextVersionNumber()); mUri = uri; mApplication = Utils.checkNotNull(application); mContentType = contentType; + mFilePath = DrmHelper.getFilePath(mApplication.getAndroidContext(), uri); } @Override @@ -171,6 +174,14 @@ public class UriImage extends MediaItem { private class RegionDecoderJob implements Job<BitmapRegionDecoder> { @Override public BitmapRegionDecoder run(JobContext jc) { + if (DrmHelper.isDrmFile(getFilePath())) { + BitmapRegionDecoder decoder = DrmHelper + .createBitmapRegionDecoder(getFilePath(), false); + mWidth = decoder.getWidth(); + mHeight = decoder.getHeight(); + return decoder; + } + if (!prepareInputFile(jc)) return null; BitmapRegionDecoder decoder = DecodeUtils.createBitmapRegionDecoder( jc, mFileDescriptor.getFileDescriptor(), false); @@ -189,6 +200,10 @@ public class UriImage extends MediaItem { @Override public Bitmap run(JobContext jc) { + if (DrmHelper.isDrmFile(getFilePath())) { + return DecodeUtils.ensureGLCompatibleBitmap(DrmHelper.getBitmap(getFilePath())); + } + if (!prepareInputFile(jc)) return null; int targetSize = MediaItem.getTargetSize(mType); Options options = new Options(); @@ -211,10 +226,18 @@ public class UriImage extends MediaItem { @Override public int getSupportedOperations() { - int supported = SUPPORT_PRINT | SUPPORT_SETAS; - if (isSharable()) supported |= SUPPORT_SHARE; - if (BitmapUtils.isSupportedByRegionDecoder(mContentType)) { - supported |= SUPPORT_EDIT | SUPPORT_FULL_IMAGE; + int supported = 0; + if (DrmHelper.isDrmFile(getFilePath())) { + supported |= SUPPORT_DRM_INFO | SUPPORT_FULL_IMAGE; + if (DrmHelper.isShareableDrmFile(getFilePath())) { + supported |= SUPPORT_SHARE; + } + } else { + supported = SUPPORT_PRINT | SUPPORT_SETAS; + if (isSharable()) supported |= SUPPORT_SHARE; + if (BitmapUtils.isSupportedByRegionDecoder(mContentType)) { + supported |= SUPPORT_EDIT | SUPPORT_FULL_IMAGE; + } } return supported; } @@ -239,6 +262,10 @@ public class UriImage extends MediaItem { @Override public int getMediaType() { + if (DrmHelper.isDrmFile(getFilePath())) { + return MEDIA_TYPE_DRM_IMAGE; + } + return MEDIA_TYPE_IMAGE; } @@ -295,4 +322,9 @@ public class UriImage extends MediaItem { public int getRotation() { return mRotation; } + + @Override + public String getFilePath() { + return mFilePath; + } } diff --git a/src/com/android/gallery3d/data/UriSource.java b/src/com/android/gallery3d/data/UriSource.java index f66bacd7b..b4bb16072 100644 --- a/src/com/android/gallery3d/data/UriSource.java +++ b/src/com/android/gallery3d/data/UriSource.java @@ -17,7 +17,9 @@ package com.android.gallery3d.data; import android.content.ContentResolver; +import android.drm.DrmHelper; import android.net.Uri; +import android.text.TextUtils; import android.webkit.MimeTypeMap; import com.android.gallery3d.app.GalleryApp; @@ -73,6 +75,19 @@ class UriSource extends MediaSource { @Override public Path findPathByUri(Uri uri, String type) { String mimeType = getMimeType(uri); + if (DrmHelper.isDrmMimeType(mimeType)) { + String path = DrmHelper.getFilePath( + mApplication.getAndroidContext(), uri); + if (!TextUtils.isEmpty(path)) { + try { + return Path.fromString("/uri/" + + URLEncoder.encode(path, CHARSET_UTF_8) + "/" + + URLEncoder.encode(type, CHARSET_UTF_8)); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } + } // Try to find a most specific type but it has to be started with "image/" if ((type == null) || (IMAGE_TYPE_ANY.equals(type) diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index e627a612b..fe383cef7 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -84,6 +84,7 @@ import com.android.gallery3d.filtershow.editors.EditorColorBorder; import com.android.gallery3d.filtershow.editors.EditorCrop; import com.android.gallery3d.filtershow.editors.EditorDraw; import com.android.gallery3d.filtershow.editors.EditorGrad; +import com.android.gallery3d.filtershow.editors.EditorMakeup; import com.android.gallery3d.filtershow.editors.EditorManager; import com.android.gallery3d.filtershow.editors.EditorMirror; import com.android.gallery3d.filtershow.editors.EditorPanel; @@ -100,6 +101,7 @@ import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation; import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation; import com.android.gallery3d.filtershow.filters.FiltersManager; import com.android.gallery3d.filtershow.filters.ImageFilter; +import com.android.gallery3d.filtershow.filters.SimpleMakeupImageFilter; import com.android.gallery3d.filtershow.history.HistoryItem; import com.android.gallery3d.filtershow.history.HistoryManager; import com.android.gallery3d.filtershow.imageshow.ImageShow; @@ -120,6 +122,7 @@ import com.android.gallery3d.filtershow.ui.ExportDialog; import com.android.gallery3d.filtershow.ui.FramedTextButton; import com.android.gallery3d.util.GalleryUtils; import com.android.photos.data.GalleryBitmapPool; +import com.thundersoft.hz.selfportrait.makeup.engine.MakeupEngine; import java.io.File; import java.io.FileDescriptor; @@ -178,6 +181,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL private CategoryAdapter mCategoryGeometryAdapter = null; private CategoryAdapter mCategoryFiltersAdapter = null; private CategoryAdapter mCategoryVersionsAdapter = null; + private CategoryAdapter mCategoryMakeupAdapter = null; private int mCurrentPanel = MainPanel.LOOKS; private Vector<FilterUserPresetRepresentation> mVersions = new Vector<FilterUserPresetRepresentation>(); @@ -195,6 +199,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL private DialogInterface mCurrentDialog = null; private PopupMenu mCurrentMenu = null; private boolean mLoadingVisible = true; + private boolean mLoadingComplete = false; public ProcessingService getProcessingService() { return mBoundService; @@ -283,6 +288,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL doBindService(); getWindow().setBackgroundDrawable(new ColorDrawable(Color.GRAY)); setContentView(R.layout.filtershow_splashscreen); + mLoadingComplete = false; } public boolean isShowingImageStatePanel() { @@ -351,8 +357,6 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL ActionBar actionBar = getActionBar(); actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); actionBar.setCustomView(R.layout.filtershow_actionbar); - actionBar.setBackgroundDrawable(new ColorDrawable( - getResources().getColor(R.color.background_screen))); mSaveButton = actionBar.getCustomView(); mSaveButton.setOnClickListener(new OnClickListener() { @@ -380,6 +384,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL fillTools(); fillEffects(); fillVersions(); + fillMakeup(); } public void setupStatePanel() { @@ -468,6 +473,25 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL } } + private void fillMakeup() { + if(!SimpleMakeupImageFilter.HAS_TS_MAKEUP) { + return; + } + + FiltersManager filtersManager = FiltersManager.getManager(); + ArrayList<FilterRepresentation> makeups = filtersManager.getMakeup(); + if (mCategoryMakeupAdapter != null) { + mCategoryMakeupAdapter.clear(); + } + mCategoryMakeupAdapter = new CategoryAdapter(this); + for (FilterRepresentation makeup : makeups) { + if (makeup.getTextId() != 0) { + makeup.setName(getString(makeup.getTextId())); + } + mCategoryMakeupAdapter.add(new Action(this, makeup)); + } + } + private void fillTools() { FiltersManager filtersManager = FiltersManager.getManager(); ArrayList<FilterRepresentation> filtersRepresentations = filtersManager.getTools(); @@ -483,7 +507,8 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL } } if (!found) { - FilterRepresentation representation = new FilterDrawRepresentation(); + FilterRepresentation representation = + new FilterDrawRepresentation(getString(R.string.imageDraw)); Action action = new Action(this, representation); action.setIsDoubleAction(true); mCategoryGeometryAdapter.add(action); @@ -528,6 +553,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL mEditorPlaceHolder.addEditor(new EditorMirror()); mEditorPlaceHolder.addEditor(new EditorRotate()); mEditorPlaceHolder.addEditor(new EditorStraighten()); + mEditorPlaceHolder.addEditor(new EditorMakeup()); } private void setDefaultValues() { @@ -558,24 +584,15 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL private void fillBorders() { FiltersManager filtersManager = FiltersManager.getManager(); ArrayList<FilterRepresentation> borders = filtersManager.getBorders(); + mCategoryBordersAdapter = new CategoryAdapter(this); for (int i = 0; i < borders.size(); i++) { FilterRepresentation filter = borders.get(i); - filter.setName(getString(R.string.borders)); + filter.setName(getString(R.string.borders) + "" + i); if (i == 0) { filter.setName(getString(R.string.none)); } - } - - if (mCategoryBordersAdapter != null) { - mCategoryBordersAdapter.clear(); - } - mCategoryBordersAdapter = new CategoryAdapter(this); - for (FilterRepresentation representation : borders) { - if (representation.getTextId() != 0) { - representation.setName(getString(representation.getTextId())); - } - mCategoryBordersAdapter.add(new Action(this, representation, Action.FULL_VIEW)); + mCategoryBordersAdapter.add(new Action(this, filter, Action.FULL_VIEW)); } } @@ -591,6 +608,10 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL return mCategoryBordersAdapter; } + public CategoryAdapter getCategoryMakeupAdapter() { + return mCategoryMakeupAdapter; + } + public CategoryAdapter getCategoryGeometryAdapter() { return mCategoryGeometryAdapter; } @@ -744,16 +765,16 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL MasterImage master = MasterImage.getImage(); Rect originalBounds = master.getOriginalBounds(); if (master.supportsHighRes()) { - int highresPreviewSize = master.getOriginalBitmapLarge().getWidth() * 2; - if (highresPreviewSize > originalBounds.width()) { - highresPreviewSize = originalBounds.width(); - } + int highresPreviewSize = Math.min(MasterImage.MAX_BITMAP_DIM, getScreenImageSize()); + Log.d(LOGTAG, "FilterShowActivity.LoadHighresBitmapTask.doInBackground(): after, highresPreviewSize is " + highresPreviewSize); Rect bounds = new Rect(); Bitmap originalHires = ImageLoader.loadOrientedConstrainedBitmap(master.getUri(), master.getActivity(), highresPreviewSize, master.getOrientation(), bounds); master.setOriginalBounds(bounds); master.setOriginalBitmapHighres(originalHires); + Log.d(LOGTAG, "FilterShowActivity.LoadHighresBitmapTask.doInBackground(): originalHires.WH is (" + originalHires.getWidth() + + ", " + originalHires.getHeight() +"), bounds is " + bounds.toString()); mBoundService.setOriginalBitmapHighres(originalHires); master.warnListeners(); } @@ -766,6 +787,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL if (highresBitmap != null) { float highResPreviewScale = (float) highresBitmap.getWidth() / (float) MasterImage.getImage().getOriginalBounds().width(); + Log.d(LOGTAG, "FilterShowActivity.LoadHighresBitmapTask.onPostExecute(): highResPreviewScale is " + highResPreviewScale); mBoundService.setHighresPreviewScaleFactor(highResPreviewScale); } MasterImage.getImage().warnListeners(); @@ -793,6 +815,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL public LoadBitmapTask() { mBitmapSize = getScreenImageSize(); + Log.d(LOGTAG, "FilterShowActivity.LoadBtimapTask(): mBitmapSize is " + mBitmapSize); } @Override @@ -849,6 +872,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL float previewScale = (float) largeBitmap.getWidth() / (float) MasterImage.getImage().getOriginalBounds().width(); + Log.d(LOGTAG, "FilterShowActivity.LoadBitmapTask.onPostExecute(): previewScale is " + previewScale); mBoundService.setPreviewScaleFactor(previewScale); if (!mShowingTinyPlanet) { mCategoryFiltersAdapter.removeTinyPlanet(); @@ -857,10 +881,14 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL mCategoryBordersAdapter.imageLoaded(); mCategoryGeometryAdapter.imageLoaded(); mCategoryFiltersAdapter.imageLoaded(); + if(mCategoryMakeupAdapter != null) { + mCategoryMakeupAdapter.imageLoaded(); + } mLoadBitmapTask = null; MasterImage.getImage().warnListeners(); loadActions(); + mLoadingComplete = false; if (mOriginalPreset != null) { MasterImage.getImage().setLoadedPreset(mOriginalPreset); @@ -1030,6 +1058,9 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL if (mShareActionProvider != null) { mShareActionProvider.setOnShareTargetSelectedListener(this); } + if(SimpleMakeupImageFilter.HAS_TS_MAKEUP) { + MakeupEngine.getMakeupObj().setContext(getBaseContext()); + } } @Override @@ -1162,6 +1193,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL } public void enableSave(boolean enable) { + mLoadingComplete = true; if (mSaveButton != null) { mSaveButton.setEnabled(enable); } @@ -1329,6 +1361,10 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG); if (currentPanel instanceof MainPanel) { if (!mImageShow.hasModifications()) { + if (!mLoadingComplete) { + Log.v(LOGTAG,"Background processing is ON, rejecting back key event"); + return; + } done(); } else { AlertDialog.Builder builder = new AlertDialog.Builder(this); diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java index 52c296c78..30d535d77 100644 --- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java +++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java @@ -108,7 +108,7 @@ public final class ImageLoader { new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null); if (cursor != null && cursor.moveToNext()) { - int ori = cursor.getInt(0); + int ori = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.ORIENTATION); switch (ori) { case 90: return ORI_ROTATE_90; @@ -147,6 +147,8 @@ public final class ImageLoader { return parseExif(exif); } catch (IOException e) { Log.w(LOGTAG, "Failed to read EXIF orientation", e); + } catch (NullPointerException e) { + Log.w(LOGTAG, "Invalid EXIF data", e); } finally { try { if (is != null) { @@ -576,6 +578,8 @@ public final class ImageLoader { return taglist; } catch (IOException e) { Log.w(LOGTAG, "Failed to read EXIF tags", e); + } catch (NullPointerException e) { + Log.e(LOGTAG, "Failed to read EXIF tags", e); } } return null; diff --git a/src/com/android/gallery3d/filtershow/category/CategoryAdapter.java b/src/com/android/gallery3d/filtershow/category/CategoryAdapter.java index 09f02dd37..50f0e9436 100644 --- a/src/com/android/gallery3d/filtershow/category/CategoryAdapter.java +++ b/src/com/android/gallery3d/filtershow/category/CategoryAdapter.java @@ -79,6 +79,9 @@ public class CategoryAdapter extends ArrayAdapter<Action> { mSelectedPosition = 0; mAddButtonText = getContext().getString(R.string.filtershow_add_button_looks); } +// if (category == MainPanel.MAKEUP) { +// mSelectedPosition = 0; +// } if (category == MainPanel.BORDERS) { mSelectedPosition = 0; } diff --git a/src/com/android/gallery3d/filtershow/category/CategoryPanel.java b/src/com/android/gallery3d/filtershow/category/CategoryPanel.java index fb51bf5ad..66b352ffb 100644 --- a/src/com/android/gallery3d/filtershow/category/CategoryPanel.java +++ b/src/com/android/gallery3d/filtershow/category/CategoryPanel.java @@ -29,7 +29,7 @@ import android.widget.ListView; import android.widget.TextView; import com.android.gallery3d.R; import com.android.gallery3d.filtershow.FilterShowActivity; - +import android.util.Log; public class CategoryPanel extends Fragment implements View.OnClickListener { public static final String FRAGMENT_TAG = "CategoryPanel"; @@ -89,6 +89,13 @@ public class CategoryPanel extends Fragment implements View.OnClickListener { } break; } + case MainPanel.MAKEUP: { + mAdapter = activity.getCategoryMakeupAdapter(); + if (mAdapter != null) { + mAdapter.initializeSelection(MainPanel.MAKEUP); + } + break; + } } updateAddButtonVisibility(); } @@ -148,7 +155,7 @@ public class CategoryPanel extends Fragment implements View.OnClickListener { return; } FilterShowActivity activity = (FilterShowActivity) getActivity(); - if (activity.isShowingImageStatePanel() && mAdapter.showAddButton()) { + if (activity.isShowingImageStatePanel() && mAdapter != null && mAdapter.showAddButton()) { mAddButton.setVisibility(View.VISIBLE); if (mAdapter != null) { mAddButton.setText(mAdapter.getAddButtonText()); diff --git a/src/com/android/gallery3d/filtershow/category/MainPanel.java b/src/com/android/gallery3d/filtershow/category/MainPanel.java index 082bf143a..1dbe42083 100644 --- a/src/com/android/gallery3d/filtershow/category/MainPanel.java +++ b/src/com/android/gallery3d/filtershow/category/MainPanel.java @@ -24,9 +24,10 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.LinearLayout; - +import android.util.Log; import com.android.gallery3d.R; import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.filtershow.filters.SimpleMakeupImageFilter; import com.android.gallery3d.filtershow.imageshow.MasterImage; import com.android.gallery3d.filtershow.state.StatePanel; @@ -39,6 +40,7 @@ public class MainPanel extends Fragment { private ImageButton bordersButton; private ImageButton geometryButton; private ImageButton filtersButton; + private ImageButton makeupButton; public static final String FRAGMENT_TAG = "MainPanel"; public static final int LOOKS = 0; @@ -46,6 +48,7 @@ public class MainPanel extends Fragment { public static final int GEOMETRY = 2; public static final int FILTERS = 3; public static final int VERSIONS = 4; + public static final int MAKEUP = 5; private int mCurrentSelected = -1; private int mPreviousToggleVersions = -1; @@ -72,6 +75,12 @@ public class MainPanel extends Fragment { filtersButton.setSelected(value); break; } + case MAKEUP: { + if(makeupButton != null) { + makeupButton.setSelected(value); + } + break; + } } } @@ -97,6 +106,19 @@ public class MainPanel extends Fragment { bordersButton = (ImageButton) mMainView.findViewById(R.id.borderButton); geometryButton = (ImageButton) mMainView.findViewById(R.id.geometryButton); filtersButton = (ImageButton) mMainView.findViewById(R.id.colorsButton); + if(SimpleMakeupImageFilter.HAS_TS_MAKEUP) { + makeupButton = (ImageButton) mMainView.findViewById(R.id.makeupButton); + makeupButton.setVisibility(View.VISIBLE); + } + + if(makeupButton != null) { + makeupButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showPanel(MAKEUP); + } + }); + } looksButton.setOnClickListener(new View.OnClickListener() { @Override @@ -173,6 +195,19 @@ public class MainPanel extends Fragment { selection(mCurrentSelected, true); } + public void loadCategoryMakeupPanel() { + if (makeupButton == null || mCurrentSelected == MAKEUP) { + return; + } + boolean fromRight = isRightAnimation(MAKEUP); + selection(mCurrentSelected, false); + CategoryPanel categoryPanel = new CategoryPanel(); + categoryPanel.setAdapter(MAKEUP); + setCategoryFragment(categoryPanel, fromRight); + mCurrentSelected = MAKEUP; + selection(mCurrentSelected, true); + } + public void loadCategoryGeometryPanel() { if (mCurrentSelected == GEOMETRY) { return; @@ -239,6 +274,10 @@ public class MainPanel extends Fragment { loadCategoryVersionsPanel(); break; } + case MAKEUP: { + loadCategoryMakeupPanel(); + break; + } } } diff --git a/src/com/android/gallery3d/filtershow/controller/ColorChooser.java b/src/com/android/gallery3d/filtershow/controller/ColorChooser.java index f9f29bccc..82ce80bbf 100644 --- a/src/com/android/gallery3d/filtershow/controller/ColorChooser.java +++ b/src/com/android/gallery3d/filtershow/controller/ColorChooser.java @@ -68,9 +68,16 @@ public class ColorChooser implements Control { Color.colorToHSV(palette[i], hsvo); hsvo[OPACITY_OFFSET] = (0xFF & (palette[i] >> 24)) / (float) 255; button.setTag(hsvo); + + String colorString = "(" + Integer.toHexString(palette[i]) + ")"; + boolean colorSelect = false; + if (parameter.getValueString().equals(colorString)) { + mSelectedButton = i; + colorSelect = true; + } GradientDrawable sd = ((GradientDrawable) button.getBackground()); sd.setColor(palette[i]); - sd.setStroke(3, (mSelectedButton == i) ? mSelected : mTransparent); + sd.setStroke(3, colorSelect? mSelected : mTransparent); final int buttonNo = i; button.setOnClickListener(new View.OnClickListener() { diff --git a/src/com/android/gallery3d/filtershow/controller/StyleChooser.java b/src/com/android/gallery3d/filtershow/controller/StyleChooser.java index f5afec921..dc31401e0 100644 --- a/src/com/android/gallery3d/filtershow/controller/StyleChooser.java +++ b/src/com/android/gallery3d/filtershow/controller/StyleChooser.java @@ -41,7 +41,7 @@ public class StyleChooser implements Control { int n = mParameter.getNumberOfStyles(); mIconButton.clear(); Resources res = context.getResources(); - int dim = res.getDimensionPixelSize(R.dimen.draw_style_icon_dim); + int dim = mTopView.getMeasuredWidth() / n; LayoutParams lp = new LayoutParams(dim, dim); for (int i = 0; i < n; i++) { final ImageButton button = new ImageButton(context); diff --git a/src/com/android/gallery3d/filtershow/crop/CropActivity.java b/src/com/android/gallery3d/filtershow/crop/CropActivity.java index 3a7829681..94c859333 100644 --- a/src/com/android/gallery3d/filtershow/crop/CropActivity.java +++ b/src/com/android/gallery3d/filtershow/crop/CropActivity.java @@ -83,7 +83,7 @@ public class CropActivity extends Activity { * sure the intent stays below 1MB.We should consider just returning a byte * array instead of a Bitmap instance to avoid overhead. */ - public static final int MAX_BMAP_IN_INTENT = 750000; + public static final int MAX_BMAP_IN_INTENT = 520000; // Flags private static final int DO_SET_WALLPAPER = 1; @@ -400,16 +400,8 @@ public class CropActivity extends Activity { mOutputX = outputX; mOutputY = outputY; - if ((flags & DO_EXTRA_OUTPUT) != 0) { - if (mOutUri == null) { - Log.w(LOGTAG, "cannot write file, no output URI given"); - } else { - try { - mOutStream = getContentResolver().openOutputStream(mOutUri); - } catch (FileNotFoundException e) { - Log.w(LOGTAG, "cannot write file: " + mOutUri.toString(), e); - } - } + if ((flags & DO_EXTRA_OUTPUT) != 0 && mOutUri == null) { + Log.w(LOGTAG, "cannot write file, no output URI given"); } if ((flags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0) { @@ -542,7 +534,14 @@ public class CropActivity extends Activity { // Get output compression format CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); - + Utils.closeSilently(mInStream); + if (mOutUri != null) { + try { + mOutStream = getContentResolver().openOutputStream(mOutUri); + } catch (FileNotFoundException e) { + Log.w(LOGTAG, "cannot write file: " + mOutUri.toString(), e); + } + } // If we only need to output to a URI, compress straight to file if (mFlags == DO_EXTRA_OUTPUT) { if (mOutStream == null @@ -604,7 +603,7 @@ public class CropActivity extends Activity { @Override protected void onPostExecute(Boolean result) { Utils.closeSilently(mOutStream); - Utils.closeSilently(mInStream); + // Utils.closeSilently(mInStream); doneBitmapIO(result.booleanValue(), mResultIntent); } diff --git a/src/com/android/gallery3d/filtershow/editors/EditorMakeup.java b/src/com/android/gallery3d/filtershow/editors/EditorMakeup.java new file mode 100644 index 000000000..331d31b57 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/editors/EditorMakeup.java @@ -0,0 +1,44 @@ +/* +* Copyright (C) 2014,2015 Thundersoft Corporation +* All rights Reserved +* +* 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.filtershow.editors; + +import android.view.View; +import android.widget.SeekBar; + +import com.android.gallery3d.R; + +public class EditorMakeup extends BasicEditor { + public static int ID = R.id.editorMakeup; + private final String LOGTAG = "EditorMakeup"; + + public EditorMakeup() { + super(ID, R.layout.filtershow_default_editor, R.id.basicEditor); + } + + @Override + public void setUtilityPanelUI(View actionButton, View editControl) { + super.setUtilityPanelUI(actionButton, editControl); + mSeekBar = (SeekBar) editControl.findViewById(R.id.primarySeekBar); + if (mSeekBar != null) { + mSeekBar.setVisibility(View.INVISIBLE); + } + } + + + +} diff --git a/src/com/android/gallery3d/filtershow/editors/EditorPanel.java b/src/com/android/gallery3d/filtershow/editors/EditorPanel.java index a60b6722c..0581835f4 100644 --- a/src/com/android/gallery3d/filtershow/editors/EditorPanel.java +++ b/src/com/android/gallery3d/filtershow/editors/EditorPanel.java @@ -20,6 +20,7 @@ import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -50,6 +51,7 @@ public class EditorPanel extends Fragment { super.onAttach(activity); FilterShowActivity filterShowActivity = (FilterShowActivity) activity; mEditor = filterShowActivity.getEditor(mEditorID); + Log.d(LOGTAG, "EditorPanle.onAttach(): mEditorID is " + mEditorID + ", mEditor is " + mEditor); } public void cancelCurrentFilter() { diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java index 8350ff356..e93175a92 100644 --- a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java +++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java @@ -36,6 +36,7 @@ public abstract class BaseFiltersManager implements FiltersManagerInterface { protected ArrayList<FilterRepresentation> mBorders = new ArrayList<FilterRepresentation>(); protected ArrayList<FilterRepresentation> mTools = new ArrayList<FilterRepresentation>(); protected ArrayList<FilterRepresentation> mEffects = new ArrayList<FilterRepresentation>(); + protected ArrayList<FilterRepresentation> mMakeup = new ArrayList<FilterRepresentation>(); private static int mImageBorderSize = 4; // in percent protected void init() { @@ -140,6 +141,12 @@ public abstract class BaseFiltersManager implements FiltersManagerInterface { filters.add(ImageFilterFx.class); filters.add(ImageFilterBorder.class); filters.add(ImageFilterColorBorder.class); + if(SimpleMakeupImageFilter.HAS_TS_MAKEUP) { + filters.add(ImageFilterMakeupWhiten.class); + filters.add(ImageFilterMakeupSoften.class); + filters.add(ImageFilterMakeupTrimface.class); + filters.add(ImageFilterMakeupBigeye.class); + } } public ArrayList<FilterRepresentation> getLooks() { @@ -158,8 +165,11 @@ public abstract class BaseFiltersManager implements FiltersManagerInterface { return mEffects; } - public void addBorders(Context context) { + public ArrayList<FilterRepresentation> getMakeup() { + return mMakeup; + } + public void addBorders(Context context) { // Do not localize String[] serializationNames = { "FRAME_4X5", @@ -305,6 +315,15 @@ public abstract class BaseFiltersManager implements FiltersManagerInterface { mEffects.add(getRepresentation(ImageFilterKMeans.class)); } + public void addMakeups(Context context) { + if(SimpleMakeupImageFilter.HAS_TS_MAKEUP) { + mMakeup.add(getRepresentation(ImageFilterMakeupWhiten.class)); + mMakeup.add(getRepresentation(ImageFilterMakeupSoften.class)); + mMakeup.add(getRepresentation(ImageFilterMakeupTrimface.class)); + mMakeup.add(getRepresentation(ImageFilterMakeupBigeye.class)); + } + } + public void addTools(Context context) { int[] textId = { diff --git a/src/com/android/gallery3d/filtershow/filters/FilterCropRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterCropRepresentation.java index ba697d87f..98afc3a08 100644 --- a/src/com/android/gallery3d/filtershow/filters/FilterCropRepresentation.java +++ b/src/com/android/gallery3d/filtershow/filters/FilterCropRepresentation.java @@ -65,6 +65,9 @@ public class FilterCropRepresentation extends FilterRepresentation { return false; } FilterCropRepresentation crop = (FilterCropRepresentation) rep; + if (crop.isNil()) { + return true; + } if (mCrop.bottom != crop.mCrop.bottom || mCrop.left != crop.mCrop.left || mCrop.right != crop.mCrop.right diff --git a/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java index 48d3d9077..5c5e561ca 100644 --- a/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java +++ b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java @@ -157,15 +157,20 @@ public class FilterDrawRepresentation extends FilterRepresentation { private Vector<StrokeData> mDrawing = new Vector<StrokeData>(); private StrokeData mCurrent; // used in the currently drawing style - public FilterDrawRepresentation() { - super("Draw"); + public FilterDrawRepresentation(String name) { + super(name); setFilterClass(ImageFilterDraw.class); - setSerializationName("DRAW"); + setSerializationName(name); setFilterType(FilterRepresentation.TYPE_VIGNETTE); setTextId(R.string.imageDraw); setEditorId(EditorDraw.ID); setOverlayId(R.drawable.filtershow_drawing); setOverlayOnly(true); + setDefaultColor(); + } + + private void setDefaultColor() { + mParamColor.setValue(DEFAULT_MENU_COLOR1); } @Override @@ -185,7 +190,7 @@ public class FilterDrawRepresentation extends FilterRepresentation { @Override public FilterRepresentation copy() { - FilterDrawRepresentation representation = new FilterDrawRepresentation(); + FilterDrawRepresentation representation = new FilterDrawRepresentation(getName()); copyAllParameters(representation); return representation; } diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java index 0fb157d7b..36675b71b 100644 --- a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java +++ b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java @@ -47,6 +47,7 @@ public class FilterRepresentation { public static final byte TYPE_NORMAL = 5; public static final byte TYPE_TINYPLANET = 6; public static final byte TYPE_GEOMETRY = 7; + public static final byte TYPE_MAKEUP = 8; protected static final String NAME_TAG = "Name"; public FilterRepresentation(String name) { diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java index 437137416..1fcd3008c 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java @@ -19,7 +19,7 @@ package com.android.gallery3d.filtershow.filters; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.Matrix; -import android.support.v8.renderscript.Allocation; +import android.renderscript.Allocation; import android.widget.Toast; import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java index a7286f0fa..5f3502272 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java @@ -18,13 +18,15 @@ package com.android.gallery3d.filtershow.filters; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import com.android.gallery3d.data.DecodeUtils; + import java.util.HashMap; +import java.lang.ref.WeakReference; public class ImageFilterBorder extends ImageFilter { private static final float NINEPATCH_ICON_SCALING = 10; @@ -32,7 +34,7 @@ public class ImageFilterBorder extends ImageFilter { private FilterImageBorderRepresentation mParameters = null; private Resources mResources = null; - private HashMap<Integer, Drawable> mDrawables = new HashMap<Integer, Drawable>(); + private HashMap<Integer, WeakReference<Drawable>> mDrawables = new HashMap<Integer, WeakReference<Drawable>>(); public ImageFilterBorder() { mName = "Border"; @@ -57,7 +59,7 @@ public class ImageFilterBorder extends ImageFilter { Rect bounds = new Rect(0, 0, (int) (w * scale1), (int) (h * scale1)); Canvas canvas = new Canvas(bitmap); canvas.scale(scale2, scale2); - Drawable drawable = getDrawable(getParameters().getDrawableResource()); + Drawable drawable = getDrawable(getParameters().getDrawableResource(), w, h); drawable.setBounds(bounds); drawable.draw(canvas); return bitmap; @@ -80,11 +82,12 @@ public class ImageFilterBorder extends ImageFilter { } } - public Drawable getDrawable(int rsc) { - Drawable drawable = mDrawables.get(rsc); + public Drawable getDrawable(int rsc, int reqWidth, int reqHeight) { + Drawable drawable = (mDrawables.get(rsc) != null) ? mDrawables.get(rsc).get() : null; if (drawable == null && mResources != null && rsc != 0) { - drawable = new BitmapDrawable(mResources, BitmapFactory.decodeResource(mResources, rsc)); - mDrawables.put(rsc, drawable); + drawable = new BitmapDrawable(mResources, DecodeUtils.decodeBitmap( + mResources, rsc, reqWidth, reqHeight)); + mDrawables.put(rsc, new WeakReference<Drawable>(drawable)); } return drawable; } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterChanSat.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterChanSat.java index 5d3856ebc..6c48a6a9f 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterChanSat.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterChanSat.java @@ -18,11 +18,11 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; import android.graphics.Matrix; -import android.support.v8.renderscript.Allocation; -import android.support.v8.renderscript.Element; -import android.support.v8.renderscript.RenderScript; -import android.support.v8.renderscript.Script.LaunchOptions; -import android.support.v8.renderscript.Type; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.Script; +import android.renderscript.Type; import com.android.gallery3d.R; import com.android.gallery3d.filtershow.pipeline.FilterEnvironment; @@ -132,7 +132,7 @@ public class ImageFilterChanSat extends ImageFilterRS { int width = in.getType().getX(); int height = in.getType().getY(); - LaunchOptions options = new LaunchOptions(); + Script.LaunchOptions options = new Script.LaunchOptions(); int ty; options.setX(0, width); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java index 8fd5b029e..da7de9379 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java @@ -29,6 +29,7 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import com.android.gallery3d.R; +import com.android.gallery3d.app.GalleryAppImpl; import com.android.gallery3d.app.Log; import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.filters.FilterDrawRepresentation.StrokeData; @@ -47,7 +48,8 @@ public class ImageFilterDraw extends ImageFilter { int mCachedStrokes = -1; int mCurrentStyle = 0; - FilterDrawRepresentation mParameters = new FilterDrawRepresentation(); + FilterDrawRepresentation mParameters = new FilterDrawRepresentation( + GalleryAppImpl.getContext().getString(R.string.imageDraw)); public ImageFilterDraw() { mName = "Image Draw"; @@ -69,7 +71,8 @@ public class ImageFilterDraw extends ImageFilter { @Override public FilterRepresentation getDefaultRepresentation() { - return new FilterDrawRepresentation(); + return new FilterDrawRepresentation( + GalleryAppImpl.getContext().getString(R.string.imageDraw)); } @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGrad.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGrad.java index 0a615afd4..7f10af990 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGrad.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGrad.java @@ -26,11 +26,12 @@ import com.android.gallery3d.filtershow.pipeline.FilterEnvironment; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Matrix; -import android.support.v8.renderscript.Allocation; -import android.support.v8.renderscript.Element; -import android.support.v8.renderscript.RenderScript; -import android.support.v8.renderscript.Script.LaunchOptions; -import android.support.v8.renderscript.Type; + +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.Script; +import android.renderscript.Type; import android.util.Log; import com.android.gallery3d.R; @@ -161,7 +162,7 @@ public class ImageFilterGrad extends ImageFilterRS { int width = in.getType().getX(); int height = in.getType().getY(); - LaunchOptions options = new LaunchOptions(); + Script.LaunchOptions options = new Script.LaunchOptions(); int ty; options.setX(0, width); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterMakeupBigeye.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterMakeupBigeye.java new file mode 100644 index 000000000..64067881e --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterMakeupBigeye.java @@ -0,0 +1,54 @@ +/* +* Copyright (C) 2014,2015 Thundersoft Corporation +* All rights Reserved +* +* 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.filtershow.filters; + +import android.graphics.Bitmap; + +import com.android.gallery3d.R; + +import com.thundersoft.hz.selfportrait.detect.FaceInfo; +import com.thundersoft.hz.selfportrait.makeup.engine.MakeupEngine; + +public class ImageFilterMakeupBigeye extends SimpleMakeupImageFilter { + private static final String SERIALIZATION_NAME = "BIGEYE"; + + public ImageFilterMakeupBigeye() { + mName = "Bigeye"; + } + + public FilterRepresentation getDefaultRepresentation() { + FilterBasicRepresentation representation = + (FilterBasicRepresentation) super.getDefaultRepresentation(); + representation.setName("Bigeye"); + representation.setSerializationName(SERIALIZATION_NAME); + representation.setFilterClass(ImageFilterMakeupBigeye.class); + representation.setTextId(R.string.text_makeup_bigeye); + representation.setOverlayOnly(true); + representation.setOverlayId(R.drawable.ic_ts_makeup_bigeye); + representation.setMinimum(0); + representation.setMaximum(100); + representation.setSupportsPartialRendering(true); + return representation; + } + + protected void doMakeupEffect(Bitmap bitmap, FaceInfo faceInfo, int width, int height, + int value) { + MakeupEngine.doWarpFace(bitmap, bitmap, width, height, faceInfo.eye1, faceInfo.eye2, + faceInfo.mouth, value, 0); + } +} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterMakeupSoften.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterMakeupSoften.java new file mode 100644 index 000000000..8587158c3 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterMakeupSoften.java @@ -0,0 +1,52 @@ +/* +* Copyright (C) 2014,2015 Thundersoft Corporation +* All rights Reserved +* +* 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.filtershow.filters; + +import android.graphics.Bitmap; + +import com.android.gallery3d.R; + +import com.thundersoft.hz.selfportrait.detect.FaceInfo; +import com.thundersoft.hz.selfportrait.makeup.engine.MakeupEngine; + +public class ImageFilterMakeupSoften extends SimpleMakeupImageFilter { + private static final String SERIALIZATION_NAME = "SOFTEN"; + + public ImageFilterMakeupSoften() { + mName = "Soften"; + } + + public FilterRepresentation getDefaultRepresentation() { + FilterBasicRepresentation representation = + (FilterBasicRepresentation) super.getDefaultRepresentation(); + representation.setName("Soften"); + representation.setSerializationName(SERIALIZATION_NAME); + representation.setFilterClass(ImageFilterMakeupSoften.class); + representation.setTextId(R.string.text_makeup_Soften); + representation.setOverlayOnly(true); + representation.setOverlayId(R.drawable.ic_ts_makeup_soften); + representation.setMinimum(0); + representation.setMaximum(100); + representation.setSupportsPartialRendering(true); + return representation; + } + + protected void doMakeupEffect(Bitmap bitmap, FaceInfo faceInfo, int width, int height, int value) { + MakeupEngine.doProcessBeautify(bitmap, bitmap, width, height, faceInfo.face, value, 0); + } +} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterMakeupTrimface.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterMakeupTrimface.java new file mode 100644 index 000000000..4b0499036 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterMakeupTrimface.java @@ -0,0 +1,55 @@ +/* +* Copyright (C) 2014,2015 Thundersoft Corporation +* All rights Reserved +* +* 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.filtershow.filters; + +import android.graphics.Bitmap; + +import com.android.gallery3d.R; + +import com.thundersoft.hz.selfportrait.detect.FaceInfo; +import com.thundersoft.hz.selfportrait.makeup.engine.MakeupEngine; + +public class ImageFilterMakeupTrimface extends SimpleMakeupImageFilter { + private static final String SERIALIZATION_NAME = "TRIMFACE"; + + public ImageFilterMakeupTrimface() { + mName = "Trimface"; + } + + public FilterRepresentation getDefaultRepresentation() { + FilterBasicRepresentation representation = + (FilterBasicRepresentation) super.getDefaultRepresentation(); + representation.setName("Trimface"); + representation.setSerializationName(SERIALIZATION_NAME); + representation.setFilterClass(ImageFilterMakeupTrimface.class); + representation.setTextId(R.string.text_makeup_trimface); + representation.setOverlayOnly(true); + representation.setOverlayId(R.drawable.ic_ts_makeup_trimface); + representation.setMinimum(0); + representation.setMaximum(100); + representation.setSupportsPartialRendering(true); + return representation; + } + + protected void doMakeupEffect(Bitmap bitmap, FaceInfo faceInfo, int width, int height, + int value) { + MakeupEngine.doWarpFace(bitmap, bitmap, width, height, faceInfo.eye1, faceInfo.eye2, + faceInfo.mouth, 0, value); + } + +} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterMakeupWhiten.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterMakeupWhiten.java new file mode 100644 index 000000000..e60be84d7 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterMakeupWhiten.java @@ -0,0 +1,53 @@ +/* +* Copyright (C) 2014,2015 Thundersoft Corporation +* All rights Reserved +* +* 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.filtershow.filters; + +import android.graphics.Bitmap; + +import com.android.gallery3d.R; + +import com.thundersoft.hz.selfportrait.detect.FaceInfo; +import com.thundersoft.hz.selfportrait.makeup.engine.MakeupEngine; + +public class ImageFilterMakeupWhiten extends SimpleMakeupImageFilter { + private static final String SERIALIZATION_NAME = "WHITEN"; + + public ImageFilterMakeupWhiten() { + mName = "Whiten"; + } + + public FilterRepresentation getDefaultRepresentation() { + FilterBasicRepresentation representation = + (FilterBasicRepresentation) super.getDefaultRepresentation(); + representation.setName("Whiten"); + representation.setSerializationName(SERIALIZATION_NAME); + representation.setFilterClass(ImageFilterMakeupWhiten.class); + representation.setTextId(R.string.text_makeup_whiten); + representation.setOverlayOnly(true); + representation.setOverlayId(R.drawable.ic_ts_makeup_whiten); + representation.setMinimum(0); + representation.setMaximum(100); + representation.setSupportsPartialRendering(true); + return representation; + } + + protected void doMakeupEffect(Bitmap bitmap, FaceInfo faceInfo, int width, int height, int value) { + MakeupEngine.doProcessBeautify(bitmap, bitmap, width, height, faceInfo.face, 0, value); + } + +} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java index e94e2a63a..b7c4d80e0 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java @@ -18,7 +18,10 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.support.v8.renderscript.*; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.Type; import android.util.Log; import android.content.res.Resources; import com.android.gallery3d.R; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java index e0b4cf687..279bd1857 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java @@ -24,11 +24,8 @@ import android.graphics.Rect; import com.android.gallery3d.R; import com.android.gallery3d.filtershow.imageshow.MasterImage; import com.android.gallery3d.filtershow.pipeline.FilterEnvironment; -import android.support.v8.renderscript.Allocation; -import android.support.v8.renderscript.Element; -import android.support.v8.renderscript.RenderScript; -import android.support.v8.renderscript.Script.LaunchOptions; -import android.support.v8.renderscript.Type; + +import android.renderscript.RenderScript; import android.util.Log; public class ImageFilterVignette extends ImageFilterRS { diff --git a/src/com/android/gallery3d/filtershow/filters/SimpleMakeupImageFilter.java b/src/com/android/gallery3d/filtershow/filters/SimpleMakeupImageFilter.java new file mode 100644 index 000000000..584074c02 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/SimpleMakeupImageFilter.java @@ -0,0 +1,82 @@ +/* +* Copyright (C) 2014,2015 Thundersoft Corporation +* All rights Reserved +* +* 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.filtershow.filters; + +import android.graphics.Bitmap; +import android.util.Log; + +import com.thundersoft.hz.selfportrait.detect.FaceDetect; +import com.thundersoft.hz.selfportrait.detect.FaceInfo; + +public abstract class SimpleMakeupImageFilter extends SimpleImageFilter { + private static final String LOGTAG = "SimpleMakeupImageFilter"; + protected static final int MAKEUP_INTENSITY = 50; + + public static final boolean HAS_TS_MAKEUP = android.os.SystemProperties.getBoolean("persist.ts.postmakeup", false); + + public SimpleMakeupImageFilter() { + } + + public FilterRepresentation getDefaultRepresentation() { + FilterRepresentation representation = new FilterBasicRepresentation("Default", 0, + MAKEUP_INTENSITY, 100); + representation.setShowParameterValue(true); + return representation; + } + + protected FaceInfo detectFaceInfo(Bitmap bitmap) { + FaceDetect faceDetect = new FaceDetect(); + faceDetect.initialize(); + FaceInfo[] faceInfos = faceDetect.dectectFeatures(bitmap); + faceDetect.uninitialize(); + + Log.v(LOGTAG, "SimpleMakeupImageFilter.detectFaceInfo(): detect faceNum is " + + (faceInfos != null ? faceInfos.length : "NULL")); + if (faceInfos == null || faceInfos.length <= 0) { + return null; + } + + return faceInfos[0]; + } + + @Override + public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) { + if (getParameters() == null) { + return bitmap; + } + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + if(w % 2 != 0 || h % 2 != 0) { + return bitmap; + } + int value = getParameters().getValue(); + applyHelper(bitmap, w, h, value); + return bitmap; + } + + private void applyHelper(Bitmap bitmap, int w, int h, int value) { + FaceInfo faceInfo = detectFaceInfo(bitmap); + if(faceInfo != null) { + doMakeupEffect(bitmap, faceInfo, w, h, value); + } + } + + abstract void doMakeupEffect(Bitmap bitmap, FaceInfo faceInfo, int width, int height, + int value); + +} diff --git a/src/com/android/gallery3d/filtershow/history/HistoryManager.java b/src/com/android/gallery3d/filtershow/history/HistoryManager.java index 569b299cc..9d5065a28 100644 --- a/src/com/android/gallery3d/filtershow/history/HistoryManager.java +++ b/src/com/android/gallery3d/filtershow/history/HistoryManager.java @@ -68,7 +68,7 @@ public class HistoryManager { } public boolean canUndo() { - if (mCurrentPresetPosition == getCount() - 1) { + if (mCurrentPresetPosition >= getCount() - 1) { return false; } return true; diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java index 2022ffd7e..d7c2eb4f8 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java @@ -288,7 +288,7 @@ public class ImageShow extends View implements OnGestureListener, drawImageAndAnimate(canvas, highresPreview); } - drawHighresImage(canvas, fullHighres); +// drawHighresImage(canvas, fullHighres); drawCompareImage(canvas, getGeometryOnlyImage()); canvas.restore(); diff --git a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java index f6b97f11f..5e27f4213 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java +++ b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java @@ -25,6 +25,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.net.Uri; +import android.util.Log; import com.android.gallery3d.exif.ExifTag; import com.android.gallery3d.filtershow.FilterShowActivity; @@ -211,6 +212,9 @@ public class MasterImage implements RenderingRequestCaller { int sh = (int) (sw * (float) mOriginalBitmapLarge.getHeight() / mOriginalBitmapLarge .getWidth()); mOriginalBitmapSmall = Bitmap.createScaledBitmap(mOriginalBitmapLarge, sw, sh, true); + Log.d(LOGTAG, "MasterImage.loadBitmap(): OriginalBitmapLarge.WH is (" + mOriginalBitmapLarge.getWidth() + ", " + + mOriginalBitmapLarge.getHeight() + "), OriginalBitmapSmall.WH is (" + sw + ", " + sh + "), originalBounds is " + + originalBounds.toString()); mZoomOrientation = mOrientation; warnListeners(); return true; @@ -271,6 +275,9 @@ public class MasterImage implements RenderingRequestCaller { public void onHistoryItemClick(int position) { HistoryItem historyItem = mHistory.getItem(position); // We need a copy from the history + if (historyItem == null) { + return; + } ImagePreset newPreset = new ImagePreset(historyItem.getImagePreset()); // don't need to add it to the history setPreset(newPreset, historyItem.getFilterRepresentation(), false); diff --git a/src/com/android/gallery3d/filtershow/pipeline/Buffer.java b/src/com/android/gallery3d/filtershow/pipeline/Buffer.java index c378eb994..a487a5d8d 100644 --- a/src/com/android/gallery3d/filtershow/pipeline/Buffer.java +++ b/src/com/android/gallery3d/filtershow/pipeline/Buffer.java @@ -18,8 +18,8 @@ package com.android.gallery3d.filtershow.pipeline; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.support.v8.renderscript.Allocation; -import android.support.v8.renderscript.RenderScript; +import android.renderscript.Allocation; +import android.renderscript.RenderScript; import android.util.Log; import com.android.gallery3d.filtershow.cache.BitmapCache; import com.android.gallery3d.filtershow.imageshow.MasterImage; diff --git a/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java b/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java index 8ae9a7c7b..06ce9e9df 100644 --- a/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java +++ b/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java @@ -24,8 +24,9 @@ import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; -import android.support.v8.renderscript.Allocation; -import android.support.v8.renderscript.RenderScript; + +import android.renderscript.Allocation; +import android.renderscript.RenderScript; import android.util.Log; import com.android.gallery3d.filtershow.cache.BitmapCache; @@ -175,6 +176,9 @@ public class CachingPipeline implements PipelineInterface { } public void setOriginal(Bitmap bitmap) { + if (mOriginalBitmap != null) { + mOriginalBitmap.recycle(); + } mOriginalBitmap = bitmap; Log.v(LOGTAG,"setOriginal, size " + bitmap.getWidth() + " x " + bitmap.getHeight()); ImagePreset preset = MasterImage.getImage().getPreset(); diff --git a/src/com/android/gallery3d/filtershow/pipeline/FilterEnvironment.java b/src/com/android/gallery3d/filtershow/pipeline/FilterEnvironment.java index ebf83b720..0b84f5203 100644 --- a/src/com/android/gallery3d/filtershow/pipeline/FilterEnvironment.java +++ b/src/com/android/gallery3d/filtershow/pipeline/FilterEnvironment.java @@ -18,7 +18,7 @@ package com.android.gallery3d.filtershow.pipeline; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.support.v8.renderscript.Allocation; +import android.renderscript.Allocation; import com.android.gallery3d.app.Log; import com.android.gallery3d.filtershow.cache.BitmapCache; diff --git a/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java b/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java index 4765a5990..1460ad434 100644 --- a/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java +++ b/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java @@ -18,7 +18,7 @@ package com.android.gallery3d.filtershow.pipeline; import android.graphics.Bitmap; import android.graphics.Rect; -import android.support.v8.renderscript.Allocation; +import android.renderscript.Allocation; import android.util.JsonReader; import android.util.JsonWriter; import android.util.Log; @@ -67,9 +67,11 @@ public class ImagePreset { } public ImagePreset(ImagePreset source) { - for (int i = 0; i < source.mFilters.size(); i++) { - FilterRepresentation sourceRepresentation = source.mFilters.elementAt(i); - mFilters.add(sourceRepresentation.copy()); + if (source != null && source.mFilters != null) { + for (int i = 0; i < source.mFilters.size(); i++) { + FilterRepresentation sourceRepresentation = source.mFilters.elementAt(i); + mFilters.add(sourceRepresentation.copy()); + } } } @@ -237,7 +239,7 @@ public class ImagePreset { FilterRepresentation a = preset.mFilters.elementAt(i); FilterRepresentation b = mFilters.elementAt(i); - if (!a.same(b)) { + if (!a.equals(b)) { return false; } } diff --git a/src/com/android/gallery3d/filtershow/pipeline/PipelineInterface.java b/src/com/android/gallery3d/filtershow/pipeline/PipelineInterface.java index d53768c95..ad59e0c44 100644 --- a/src/com/android/gallery3d/filtershow/pipeline/PipelineInterface.java +++ b/src/com/android/gallery3d/filtershow/pipeline/PipelineInterface.java @@ -18,8 +18,8 @@ package com.android.gallery3d.filtershow.pipeline; import android.content.res.Resources; import android.graphics.Bitmap; -import android.support.v8.renderscript.Allocation; -import android.support.v8.renderscript.RenderScript; +import android.renderscript.Allocation; +import android.renderscript.RenderScript; public interface PipelineInterface { public String getName(); diff --git a/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java b/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java index e5736d43c..e334e8798 100644 --- a/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java +++ b/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java @@ -304,12 +304,14 @@ public class ProcessingService extends Service { filtersManager.addBorders(this); filtersManager.addTools(this); filtersManager.addEffects(); + filtersManager.addMakeups(this); FiltersManager highresFiltersManager = FiltersManager.getHighresManager(); highresFiltersManager.addLooks(this); highresFiltersManager.addBorders(this); highresFiltersManager.addTools(this); highresFiltersManager.addEffects(); +// highresFiltersManager.addMakeups(this); } private void tearDownPipeline() { diff --git a/src/com/android/gallery3d/filtershow/pipeline/UpdatePreviewTask.java b/src/com/android/gallery3d/filtershow/pipeline/UpdatePreviewTask.java index 61ee8eb71..a55abca3d 100644 --- a/src/com/android/gallery3d/filtershow/pipeline/UpdatePreviewTask.java +++ b/src/com/android/gallery3d/filtershow/pipeline/UpdatePreviewTask.java @@ -57,8 +57,11 @@ public class UpdatePreviewTask extends ProcessingTask { SharedBuffer buffer = MasterImage.getImage().getPreviewBuffer(); SharedPreset preset = MasterImage.getImage().getPreviewPreset(); ImagePreset renderingPreset = preset.dequeuePreset(); - if (renderingPreset != null) { + if ( (buffer != null) && (renderingPreset != null)) { mPreviewPipeline.compute(buffer, renderingPreset, 0); + if ( buffer.getProducer() == null) { + return null; + } // set the preset we used in the buffer for later inspection UI-side buffer.getProducer().setPreset(renderingPreset); buffer.getProducer().sync(); diff --git a/src/com/android/gallery3d/filtershow/state/StatePanel.java b/src/com/android/gallery3d/filtershow/state/StatePanel.java index 95c2df991..192400315 100644 --- a/src/com/android/gallery3d/filtershow/state/StatePanel.java +++ b/src/com/android/gallery3d/filtershow/state/StatePanel.java @@ -48,7 +48,9 @@ public class StatePanel extends Fragment { View panel = mMainView.findViewById(R.id.listStates); track = (StatePanelTrack) panel; - track.setAdapter(MasterImage.getImage().getState()); + StateAdapter imageStateAdapter = MasterImage.getImage().getState(); + if (imageStateAdapter == null) return null; + track.setAdapter(imageStateAdapter); mToggleVersionsPanel = (ImageButton) mMainView.findViewById(R.id.toggleVersionsPanel); if (FilterShowHelper.shouldUseVersions()) { if (mToggleVersionsPanel.getVisibility() == View.GONE diff --git a/src/com/android/gallery3d/filtershow/tools/SaveImage.java b/src/com/android/gallery3d/filtershow/tools/SaveImage.java index 17d698f15..e07dd2ce8 100644 --- a/src/com/android/gallery3d/filtershow/tools/SaveImage.java +++ b/src/com/android/gallery3d/filtershow/tools/SaveImage.java @@ -247,7 +247,7 @@ public class SaveImage { if (mimeType == null) { mimeType = ImageLoader.getMimeType(mSelectedImageUri); } - if (mimeType.equals(ImageLoader.JPEG_MIME_TYPE)) { + if (ImageLoader.JPEG_MIME_TYPE.equals(mimeType)) { InputStream inStream = null; try { inStream = mContext.getContentResolver().openInputStream(source); @@ -256,6 +256,8 @@ public class SaveImage { Log.w(LOGTAG, "Cannot find file: " + source, e); } catch (IOException e) { Log.w(LOGTAG, "Cannot read exif for: " + source, e); + } catch (NullPointerException e) { + Log.w(LOGTAG, "Invalid exif data for: " + source, e); } finally { Utils.closeSilently(inStream); } @@ -346,9 +348,9 @@ public class SaveImage { // newSourceUri is then pointing to the new location. // If no file is moved, newSourceUri will be the same as mSourceUri. Uri newSourceUri = mSourceUri; - if (!flatten) { - newSourceUri = moveSrcToAuxIfNeeded(mSourceUri, mDestinationFile); - } + /* + * if (!flatten) { newSourceUri = moveSrcToAuxIfNeeded(mSourceUri, mDestinationFile); } + */ Uri savedUri = mSelectedImageUri; if (mPreviewImage != null) { @@ -380,7 +382,7 @@ public class SaveImage { // After this call, mSelectedImageUri will be actually // pointing at the new file mDestinationFile. savedUri = SaveImage.linkNewFileToUri(mContext, mSelectedImageUri, - mDestinationFile, time, !flatten); + mDestinationFile, time, false); } } if (mCallback != null) { @@ -700,7 +702,7 @@ public class SaveImage { values.put(Images.Media.TITLE, file.getName()); values.put(Images.Media.DISPLAY_NAME, file.getName()); values.put(Images.Media.MIME_TYPE, "image/jpeg"); - values.put(Images.Media.DATE_TAKEN, time); + values.put(Images.Media.DATE_TAKEN, time * 1000); values.put(Images.Media.DATE_MODIFIED, time); values.put(Images.Media.DATE_ADDED, time); values.put(Images.Media.ORIENTATION, 0); diff --git a/src/com/android/gallery3d/filtershow/ui/ExportDialog.java b/src/com/android/gallery3d/filtershow/ui/ExportDialog.java index b42c9f367..001e07589 100644 --- a/src/com/android/gallery3d/filtershow/ui/ExportDialog.java +++ b/src/com/android/gallery3d/filtershow/ui/ExportDialog.java @@ -107,8 +107,16 @@ public class ExportDialog extends DialogFragment implements View.OnClickListener mOriginalBounds = MasterImage.getImage().getOriginalBounds(); ImagePreset preset = MasterImage.getImage().getPreset(); + if (mOriginalBounds == null || preset == null) return null; mOriginalBounds = preset.finalGeometryRect(mOriginalBounds.width(), mOriginalBounds.height()); + if (preset != null) { + mOriginalBounds = preset.finalGeometryRect(mOriginalBounds.width(), + mOriginalBounds.height()); + } + if (mOriginalBounds == null) { + return null; + } mRatio = mOriginalBounds.width() / (float) mOriginalBounds.height(); mWidthText.setText("" + mOriginalBounds.width()); mHeightText.setText("" + mOriginalBounds.height()); diff --git a/src/com/android/gallery3d/gadget/WidgetClickHandler.java b/src/com/android/gallery3d/gadget/WidgetClickHandler.java index e66a2a66f..e5b0a376c 100644 --- a/src/com/android/gallery3d/gadget/WidgetClickHandler.java +++ b/src/com/android/gallery3d/gadget/WidgetClickHandler.java @@ -57,6 +57,8 @@ public class WidgetClickHandler extends Activity { Intent intent; if (isValidDataUri(uri)) { intent = new Intent(Intent.ACTION_VIEW, uri); + // Used for checking whether it is from widget + intent.putExtra(PhotoPage.KEY_IS_FROM_WIDGET, true); if (tediousBack) { intent.putExtra(PhotoPage.KEY_TREAT_BACK_AS_UP, true); } diff --git a/src/com/android/gallery3d/gadget/WidgetConfigure.java b/src/com/android/gallery3d/gadget/WidgetConfigure.java index 2a4c6cfe4..b9a3fec23 100644 --- a/src/com/android/gallery3d/gadget/WidgetConfigure.java +++ b/src/com/android/gallery3d/gadget/WidgetConfigure.java @@ -18,11 +18,16 @@ package com.android.gallery3d.gadget; import android.app.Activity; import android.appwidget.AppWidgetManager; +import android.content.ClipData; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; +import android.provider.MediaStore; +import android.support.v4.content.FileProvider; import android.util.Log; import android.widget.RemoteViews; @@ -38,6 +43,14 @@ import com.android.gallery3d.data.Path; import com.android.gallery3d.filtershow.crop.CropActivity; import com.android.gallery3d.filtershow.crop.CropExtras; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + public class WidgetConfigure extends Activity { @SuppressWarnings("unused") private static final String TAG = "WidgetConfigure"; @@ -61,6 +74,7 @@ public class WidgetConfigure extends Activity { private int mAppWidgetId = -1; private Uri mPickedItem; + private Uri mCropSrc, mCropDst; @Override protected void onCreate(Bundle savedState) { @@ -116,48 +130,142 @@ public class WidgetConfigure extends Activity { } else if (requestCode == REQUEST_GET_PHOTO) { setChoosenPhoto(data); } else if (requestCode == REQUEST_CROP_IMAGE) { - setPhotoWidget(data); + setPhotoWidget(); } else { throw new AssertionError("unknown request: " + requestCode); } } - private void setPhotoWidget(Intent data) { - // Store the cropped photo in our database - Bitmap bitmap = (Bitmap) data.getParcelableExtra("data"); - WidgetDatabaseHelper helper = new WidgetDatabaseHelper(this); + private void setPhotoWidget() { + AsyncTask.execute(new Runnable() { + @Override + public void run() { + Bitmap bitmap = null; + InputStream stream = null; + try { + stream = WidgetConfigure.this.getContentResolver() + .openInputStream(mCropDst); + if (stream != null) { + bitmap = BitmapFactory.decodeStream(stream); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + getContentResolver().delete(mCropSrc, null, null); + getContentResolver().delete(mCropDst, null, null); + } + if (bitmap == null) { + setResult(Activity.RESULT_CANCELED); + finish(); + return; + } + WidgetDatabaseHelper helper = new WidgetDatabaseHelper(WidgetConfigure.this); + try { + helper.setPhoto(mAppWidgetId, mPickedItem, bitmap); + updateWidgetAndFinish(helper.getEntry(mAppWidgetId)); + } finally { + helper.close(); + } + } + }); + } + + private void setChoosenPhoto(final Intent data) { + AsyncTask.execute(new Runnable() { + @Override + public void run() { + Resources res = getResources(); + + float width = res.getDimension(R.dimen.appwidget_width); + float height = res.getDimension(R.dimen.appwidget_height); + + // We try to crop a larger image (by scale factor), but there is still + // a bound on the binder limit. + float scale = Math.min(WIDGET_SCALE_FACTOR, + MAX_WIDGET_SIDE / Math.max(width, height)); + + int widgetWidth = Math.round(width * scale); + int widgetHeight = Math.round(height * scale); + + File cropSrc = new File(getCacheDir(), "crop_source.png"); + File cropDst = new File(getCacheDir(), "crop_dest.png"); + mPickedItem = data.getData(); + if (!copyUriToFile(mPickedItem, cropSrc)) { + setResult(Activity.RESULT_CANCELED); + finish(); + return; + } + + mCropSrc = FileProvider.getUriForFile(WidgetConfigure.this, + "com.android.gallery3d.fileprovider", + new File(cropSrc.getAbsolutePath())); + mCropDst = FileProvider.getUriForFile(WidgetConfigure.this, + "com.android.gallery3d.fileprovider", + new File(cropDst.getAbsolutePath())); + + Intent request = new Intent(CropActivity.CROP_ACTION) + .putExtra(CropExtras.KEY_OUTPUT_X, widgetWidth) + .putExtra(CropExtras.KEY_OUTPUT_Y, widgetHeight) + .putExtra(CropExtras.KEY_ASPECT_X, widgetWidth) + .putExtra(CropExtras.KEY_ASPECT_Y, widgetHeight) + .putExtra(CropExtras.KEY_SCALE_UP_IF_NEEDED, true) + .putExtra(CropExtras.KEY_SCALE, true) + .putExtra(CropExtras.KEY_RETURN_DATA, false) + .setDataAndType(mCropSrc, "image/*") + .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | + Intent.FLAG_GRANT_READ_URI_PERMISSION); + request.putExtra(MediaStore.EXTRA_OUTPUT, mCropDst); + request.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, mCropDst)); + startActivityForResult(request, REQUEST_CROP_IMAGE); + } + }); + } + + public boolean copyUriToFile(Uri inUri, File dst) { + boolean isSuccessful = false; + InputStream in = null; + OutputStream out = null; try { - helper.setPhoto(mAppWidgetId, mPickedItem, bitmap); - updateWidgetAndFinish(helper.getEntry(mAppWidgetId)); + in = getContentResolver().openInputStream(inUri); + out = new FileOutputStream(dst); + + byte[] buf = new byte[1024]; + + try { + for (int len; (len = in.read(buf)) > 0; ) { + out.write(buf, 0, len); + } + isSuccessful = true; + } catch (IOException e) { + // ignore + } + + } catch (FileNotFoundException fnf) { + // ignore } finally { - helper.close(); + if (in != null) { + try { + in.close(); + } catch (IOException e) { + // ignore + } + } + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // ignore + } + } } - } - - private void setChoosenPhoto(Intent data) { - Resources res = getResources(); - - float width = res.getDimension(R.dimen.appwidget_width); - float height = res.getDimension(R.dimen.appwidget_height); - - // We try to crop a larger image (by scale factor), but there is still - // a bound on the binder limit. - float scale = Math.min(WIDGET_SCALE_FACTOR, - MAX_WIDGET_SIDE / Math.max(width, height)); - - int widgetWidth = Math.round(width * scale); - int widgetHeight = Math.round(height * scale); - - mPickedItem = data.getData(); - Intent request = new Intent(CropActivity.CROP_ACTION, mPickedItem) - .putExtra(CropExtras.KEY_OUTPUT_X, widgetWidth) - .putExtra(CropExtras.KEY_OUTPUT_Y, widgetHeight) - .putExtra(CropExtras.KEY_ASPECT_X, widgetWidth) - .putExtra(CropExtras.KEY_ASPECT_Y, widgetHeight) - .putExtra(CropExtras.KEY_SCALE_UP_IF_NEEDED, true) - .putExtra(CropExtras.KEY_SCALE, true) - .putExtra(CropExtras.KEY_RETURN_DATA, true); - startActivityForResult(request, REQUEST_CROP_IMAGE); + return isSuccessful; } private void setChoosenAlbum(Intent data) { diff --git a/src/com/android/gallery3d/gadget/WidgetService.java b/src/com/android/gallery3d/gadget/WidgetService.java index fc54fb6e8..7b16f8b7c 100644 --- a/src/com/android/gallery3d/gadget/WidgetService.java +++ b/src/com/android/gallery3d/gadget/WidgetService.java @@ -19,8 +19,10 @@ package com.android.gallery3d.gadget; import android.annotation.TargetApi; import android.appwidget.AppWidgetManager; import android.content.Intent; +import android.drm.DrmHelper; import android.graphics.Bitmap; import android.net.Uri; +import android.view.View; import android.widget.RemoteViews; import android.widget.RemoteViewsService; @@ -80,7 +82,7 @@ public class WidgetService extends RemoteViewsService { } @Override - public void onDestroy() { + public synchronized void onDestroy() { mSource.close(); mSource = null; } @@ -115,9 +117,37 @@ public class WidgetService extends RemoteViewsService { } @Override - public RemoteViews getViewAt(int position) { + public synchronized RemoteViews getViewAt(int position) { + if (mSource == null) { + // This instance has been destroyed, exit out + return null; + } Bitmap bitmap = mSource.getImage(position); - if (bitmap == null) return getLoadingView(); + + boolean isDrm = false; + if (DrmHelper.isDrmFile(DrmHelper.getFilePath( + mApp.getAndroidContext(), mSource.getContentUri(position)))) { + isDrm = true; + } + + if (isDrm) { + if (bitmap == null) { + RemoteViews rv = new RemoteViews(mApp.getAndroidContext() + .getPackageName(), + R.layout.appwidget_drm_empty_item); + rv.setOnClickFillInIntent( + R.id.appwidget_photo_item, + new Intent().setFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP).setData( + mSource.getContentUri(position))); + return rv; + } + } else { + if (bitmap == null) { + return getLoadingView(); + } + } + RemoteViews views = new RemoteViews( mApp.getAndroidContext().getPackageName(), R.layout.appwidget_photo_item); @@ -125,6 +155,13 @@ public class WidgetService extends RemoteViewsService { views.setOnClickFillInIntent(R.id.appwidget_photo_item, new Intent() .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) .setData(mSource.getContentUri(position))); + + if (isDrm) { + views.setViewVisibility(R.id.drm_icon, View.VISIBLE); + } else { + views.setViewVisibility(R.id.drm_icon, View.GONE); + } + return views; } diff --git a/src/com/android/gallery3d/glrenderer/UploadedTexture.java b/src/com/android/gallery3d/glrenderer/UploadedTexture.java index f41a979b7..546d9f398 100644 --- a/src/com/android/gallery3d/glrenderer/UploadedTexture.java +++ b/src/com/android/gallery3d/glrenderer/UploadedTexture.java @@ -221,8 +221,11 @@ public abstract class UploadedTexture extends BasicTexture { Assert.assertTrue(bWidth <= texWidth && bHeight <= texHeight); - // Upload the bitmap to a new texture. - mId = canvas.getGLId().generateTexture(); + // Null pointer check here is to avoid monkey test failure. + if (canvas.getGLId() != null) { + // Upload the bitmap to a new texture. + mId = canvas.getGLId().generateTexture(); + } canvas.setTextureParameters(this); if (bWidth == texWidth && bHeight == texHeight) { diff --git a/src/com/android/gallery3d/ui/AbstractSlotRenderer.java b/src/com/android/gallery3d/ui/AbstractSlotRenderer.java index 729439dc3..63bcbea5d 100644 --- a/src/com/android/gallery3d/ui/AbstractSlotRenderer.java +++ b/src/com/android/gallery3d/ui/AbstractSlotRenderer.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.Rect; import com.android.gallery3d.R; +import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.glrenderer.FadeOutTexture; import com.android.gallery3d.glrenderer.GLCanvas; import com.android.gallery3d.glrenderer.NinePatchTexture; @@ -33,6 +34,7 @@ public abstract class AbstractSlotRenderer implements SlotView.SlotRenderer { private final ResourceTexture mPanoramaIcon; private final NinePatchTexture mFramePressed; private final NinePatchTexture mFrameSelected; + private final ResourceTexture mDrmIcon; private FadeOutTexture mFramePressedUp; protected AbstractSlotRenderer(Context context) { @@ -41,6 +43,7 @@ public abstract class AbstractSlotRenderer implements SlotView.SlotRenderer { mPanoramaIcon = new ResourceTexture(context, R.drawable.ic_360pano_holo_light); mFramePressed = new NinePatchTexture(context, R.drawable.grid_pressed); mFrameSelected = new NinePatchTexture(context, R.drawable.grid_selected); + mDrmIcon = new ResourceTexture(context, R.drawable.drm_image); } protected void drawContent(GLCanvas canvas, @@ -79,6 +82,19 @@ public abstract class AbstractSlotRenderer implements SlotView.SlotRenderer { mVideoPlayIcon.draw(canvas, (width - s) / 2, (height - s) / 2, s, s); } + protected void drawDrmOverlay(GLCanvas canvas, int width, int height, int Drm_mediaType) { + // Scale the video overlay to the height of the thumbnail and put it on the left side. + if (Drm_mediaType == MediaObject.MEDIA_TYPE_DRM_VIDEO) { + ResourceTexture v = mVideoOverlay; + float scale = (float) height / v.getHeight(); + int w = Math.round(scale * v.getWidth()); + int h = Math.round(scale * v.getHeight()); + v.draw(canvas, 0, 0, w, h); + } + int side = Math.min(width, height) / 6; + mDrmIcon.draw(canvas, (width - side) / 2, (height - side) / 2, side, side); + } + protected void drawPanoramaIcon(GLCanvas canvas, int width, int height) { int iconSize = Math.min(width, height) / 6; mPanoramaIcon.draw(canvas, (width - iconSize) / 2, (height - iconSize) / 2, diff --git a/src/com/android/gallery3d/ui/ActionModeHandler.java b/src/com/android/gallery3d/ui/ActionModeHandler.java index 6b4f10312..411c578a5 100644 --- a/src/com/android/gallery3d/ui/ActionModeHandler.java +++ b/src/com/android/gallery3d/ui/ActionModeHandler.java @@ -272,7 +272,7 @@ public class ActionModeHandler implements Callback, PopupList.OnPopupItemClickLi ArrayList<MediaObject> selected = new ArrayList<MediaObject>(); DataManager manager = mActivity.getDataManager(); for (Path path : unexpandedPaths) { - if (jc.isCancelled()) { + if (jc.isCancelled() || !mSelectionManager.inSelectionMode()) { return null; } selected.add(manager.getMediaObject(path)); diff --git a/src/com/android/gallery3d/ui/AlbumLabelMaker.java b/src/com/android/gallery3d/ui/AlbumLabelMaker.java index da1cac0bd..3ac3bb7fe 100644 --- a/src/com/android/gallery3d/ui/AlbumLabelMaker.java +++ b/src/com/android/gallery3d/ui/AlbumLabelMaker.java @@ -25,6 +25,7 @@ import android.graphics.PorterDuff; import android.graphics.Typeface; import android.text.TextPaint; import android.text.TextUtils; +import android.view.View; import com.android.gallery3d.R; import com.android.gallery3d.data.DataSourceType; @@ -32,6 +33,8 @@ import com.android.photos.data.GalleryBitmapPool; import com.android.gallery3d.util.ThreadPool; import com.android.gallery3d.util.ThreadPool.JobContext; +import java.util.Locale; + public class AlbumLabelMaker { private static final int BORDER_SIZE = 0; @@ -170,30 +173,60 @@ public class AlbumLabelMaker { canvas.translate(BORDER_SIZE, BORDER_SIZE); - // draw title - if (jc.isCancelled()) return null; - int x = s.leftMargin + s.iconSize; - // TODO: is the offset relevant in new reskin? - // int y = s.titleOffset; - int y = (s.labelBackgroundHeight - s.titleFontSize) / 2; - drawText(canvas, x, y, title, labelWidth - s.leftMargin - x - - s.titleRightMargin, mTitlePaint); - - // draw count - if (jc.isCancelled()) return null; - x = labelWidth - s.titleRightMargin; - y = (s.labelBackgroundHeight - s.countFontSize) / 2; - drawText(canvas, x, y, count, - labelWidth - x , mCountPaint); - - // draw the icon - if (icon != null) { + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) {// RTL + // draw title + if (jc.isCancelled()) return null; + int strLength = (int) mTitlePaint.measureText(title); + int x = labelWidth - (s.leftMargin + s.iconSize) - strLength; + // TODO: is the offset relevant in new reskin? + // int y = s.titleOffset; + int y = (s.labelBackgroundHeight - s.titleFontSize) / 2; + drawText(canvas, x, y, title, labelWidth - s.leftMargin - x - + s.titleRightMargin, mTitlePaint); + + // draw count + if (jc.isCancelled()) return null; + x = s.leftMargin + 10;// plus 10 to get a much bigger margin + y = (s.labelBackgroundHeight - s.countFontSize) / 2; + drawText(canvas, x, y, count, + labelWidth - x, mCountPaint); + // draw the icon + if (icon != null) { + if (jc.isCancelled()) return null; + float scale = (float) s.iconSize / icon.getWidth(); + canvas.translate(labelWidth - s.leftMargin - s.iconSize, + (s.labelBackgroundHeight - + Math.round(scale * icon.getHeight())) / 2f); + canvas.scale(scale, scale); + canvas.drawBitmap(icon, 0, 0, null); + } + } else { // LTR + // draw title + if (jc.isCancelled()) return null; + int x = s.leftMargin + s.iconSize; + // TODO: is the offset relevant in new reskin? + // int y = s.titleOffset; + int y = (s.labelBackgroundHeight - s.titleFontSize) / 2; + drawText(canvas, x, y, title, labelWidth - s.leftMargin - x - + s.titleRightMargin, mTitlePaint); + + // draw count if (jc.isCancelled()) return null; - float scale = (float) s.iconSize / icon.getWidth(); - canvas.translate(s.leftMargin, (s.labelBackgroundHeight - - Math.round(scale * icon.getHeight()))/2f); - canvas.scale(scale, scale); - canvas.drawBitmap(icon, 0, 0, null); + x = labelWidth - s.titleRightMargin; + y = (s.labelBackgroundHeight - s.countFontSize) / 2; + drawText(canvas, x, y, count, + labelWidth - x, mCountPaint); + + // draw the icon + if (icon != null) { + if (jc.isCancelled()) return null; + float scale = (float) s.iconSize / icon.getWidth(); + canvas.translate(s.leftMargin, (s.labelBackgroundHeight - + Math.round(scale * icon.getHeight())) / 2f); + canvas.scale(scale, scale); + canvas.drawBitmap(icon, 0, 0, null); + } } return bitmap; diff --git a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java index 5332ef89a..46daf1451 100644 --- a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java +++ b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java @@ -183,7 +183,6 @@ public class AlbumSetSlotRenderer extends AbstractSlotRenderer { ((FadeInTexture) content).isAnimating()) { renderRequestFlags |= SlotView.RENDER_MORE_FRAME; } - return renderRequestFlags; } diff --git a/src/com/android/gallery3d/ui/AlbumSlotRenderer.java b/src/com/android/gallery3d/ui/AlbumSlotRenderer.java index dc6c89b0e..7f97693e3 100644 --- a/src/com/android/gallery3d/ui/AlbumSlotRenderer.java +++ b/src/com/android/gallery3d/ui/AlbumSlotRenderer.java @@ -125,6 +125,9 @@ public class AlbumSlotRenderer extends AbstractSlotRenderer { if (entry.mediaType == MediaObject.MEDIA_TYPE_VIDEO) { drawVideoOverlay(canvas, width, height); + } else if ((entry.mediaType == MediaObject.MEDIA_TYPE_DRM_VIDEO) + || (entry.mediaType == MediaObject.MEDIA_TYPE_DRM_IMAGE)) { + drawDrmOverlay(canvas, width, height, entry.mediaType); } if (entry.isPanorama) { diff --git a/src/com/android/gallery3d/ui/DetailsHelper.java b/src/com/android/gallery3d/ui/DetailsHelper.java index 47296f655..4f610407f 100644 --- a/src/com/android/gallery3d/ui/DetailsHelper.java +++ b/src/com/android/gallery3d/ui/DetailsHelper.java @@ -139,6 +139,8 @@ public class DetailsHelper { return context.getString(R.string.exposure_time); case MediaDetails.INDEX_ISO: return context.getString(R.string.iso); + case MediaDetails.INDEX_DATETIME_ORIGINAL: + return context.getString(R.string.record_time); default: return "Unknown key" + key; } diff --git a/src/com/android/gallery3d/ui/DialogDetailsView.java b/src/com/android/gallery3d/ui/DialogDetailsView.java index 30fd1e18f..3e2af0d40 100644 --- a/src/com/android/gallery3d/ui/DialogDetailsView.java +++ b/src/com/android/gallery3d/ui/DialogDetailsView.java @@ -21,6 +21,7 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnDismissListener; +import android.text.TextUtils; import android.text.format.Formatter; import android.view.LayoutInflater; import android.view.View; @@ -33,14 +34,18 @@ import com.android.gallery3d.R; import com.android.gallery3d.app.AbstractGalleryActivity; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.MediaDetails; +import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.ui.DetailsAddressResolver.AddressResolvingListener; import com.android.gallery3d.ui.DetailsHelper.CloseListener; import com.android.gallery3d.ui.DetailsHelper.DetailsSource; import com.android.gallery3d.ui.DetailsHelper.DetailsViewContainer; import com.android.gallery3d.ui.DetailsHelper.ResolutionResolvingListener; +import java.text.DateFormat; import java.text.DecimalFormat; +import java.text.ParseException; import java.util.ArrayList; +import java.util.Date; import java.util.Locale; import java.util.Map.Entry; @@ -131,6 +136,15 @@ public class DialogDetailsView implements DetailsViewContainer { setDetails(context, details); } + private String exifDateToFormatedDate(String exifDt) { + try { + Date date = ExifInterface.DATETIME_FORMAT.parse(exifDt); + return DateFormat.getDateTimeInstance().format(date); + } catch (ParseException e) { + return exifDt; + } + } + private void setDetails(Context context, MediaDetails details) { boolean resolutionIsValid = true; String path = null; @@ -221,6 +235,9 @@ public class DialogDetailsView implements DetailsViewContainer { case MediaDetails.INDEX_ORIENTATION: value = toLocalInteger(detail.getValue()); break; + case MediaDetails.INDEX_DATETIME_ORIGINAL: + value = exifDateToFormatedDate(detail.getValue().toString()); + break; default: { Object valueObj = detail.getValue(); // This shouldn't happen, log its key to help us diagnose the problem. @@ -236,13 +253,20 @@ public class DialogDetailsView implements DetailsViewContainer { value = String.format("%s: %s %s", DetailsHelper.getDetailsName( context, key), value, context.getString(details.getUnit(key))); } else { - value = String.format("%s: %s", DetailsHelper.getDetailsName( - context, key), value); + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault()) + && (key == MediaDetails.INDEX_PATH)) { + value = String.format("%s : \n%s", + DetailsHelper.getDetailsName(context, key), value); + } else { + value = String.format("%s: %s", DetailsHelper.getDetailsName(context, key), + value); + } } mItems.add(value); - } - if (!resolutionIsValid) { - DetailsHelper.resolveResolution(path, this); + if (!resolutionIsValid) { + DetailsHelper.resolveResolution(path, this); + } } } diff --git a/src/com/android/gallery3d/ui/Knob.java b/src/com/android/gallery3d/ui/Knob.java new file mode 100644 index 000000000..179023e02 --- /dev/null +++ b/src/com/android/gallery3d/ui/Knob.java @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.android.gallery3d.ui; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import java.lang.Math; + +import com.android.gallery3d.R; + +public class Knob extends FrameLayout { + private static final int STROKE_WIDTH = 6; + private static final float TEXT_SIZE = 0.20f; + private static final float TEXT_PADDING = 0.31f; + private static final float LABEL_PADDING = 0.05f; + private static final float LABEL_SIZE = 0.09f; + private static final float LABEL_WIDTH = 0.80f; + private static final float INDICATOR_RADIUS = 0.38f; + + public interface OnKnobChangeListener { + void onValueChanged(Knob knob, int value, boolean fromUser); + boolean onSwitchChanged(Knob knob, boolean on); + } + + private OnKnobChangeListener mOnKnobChangeListener = null; + + private float mProgress = 0.0f; + private int mMax = 100; + private boolean mOn = false; + private boolean mEnabled = false; + + private int mHighlightColor; + private int mLowlightColor; + private int mDisabledColor; + + private final Paint mPaint; + + private final TextView mLabelTV; + private final TextView mProgressTV; + + private final ImageView mKnobOn; + private final ImageView mKnobOff; + + private float mLastX; + private float mLastY; + private boolean mMoved; + + private int mWidth = 0; + private int mIndicatorWidth = 0; + + private RectF mRectF; + + public Knob(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Knob, 0, 0); + + String label; + int foreground; + try { + label = a.getString(R.styleable.Knob_label); + foreground = a.getResourceId(R.styleable.Knob_foreground, R.drawable.knob); + } finally { + a.recycle(); + } + + LayoutInflater li = (LayoutInflater) + context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + li.inflate(R.layout.knob, this, true); + + Resources res = getResources(); + mHighlightColor = res.getColor(R.color.highlight); + mLowlightColor = res.getColor(R.color.lowlight); + mDisabledColor = res.getColor(R.color.disabled_knob); + + ((ImageView) findViewById(R.id.knob_foreground)).setImageResource(foreground); + + mLabelTV = (TextView) findViewById(R.id.knob_label); + mLabelTV.setText(label); + mProgressTV = (TextView) findViewById(R.id.knob_value); + + mKnobOn = (ImageView) findViewById(R.id.knob_toggle_on); + mKnobOff = (ImageView) findViewById(R.id.knob_toggle_off); + + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaint.setColor(mHighlightColor); + mPaint.setStrokeWidth(STROKE_WIDTH); + mPaint.setStrokeCap(Paint.Cap.ROUND); + mPaint.setStyle(Paint.Style.STROKE); + + setWillNotDraw(false); + } + + public Knob(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public Knob(Context context) { + this(context, null); + } + + public void setOnKnobChangeListener(OnKnobChangeListener l) { + mOnKnobChangeListener = l; + } + + public void setValue(int value) { + if (mMax != 0) { + setProgress(((float) value) / mMax); + } + } + + public int getValue() { + return (int) (mProgress * mMax); + } + + public void setProgress(float progress) { + setProgress(progress, false); + } + + private void setProgressText(boolean on) { + if (on) { + mProgressTV.setText((int) (mProgress * 100) + "%"); + } else { + mProgressTV.setText("--%"); + } + } + + private void setProgress(float progress, boolean fromUser) { + if (progress > 1.0f) { + progress = 1.0f; + } + if (progress < 0.0f) { + progress = 0.0f; + } + mProgress = progress; + setProgressText(mOn && mEnabled); + + invalidate(); + + if (mOnKnobChangeListener != null) { + mOnKnobChangeListener.onValueChanged(this, (int) (progress * mMax), fromUser); + } + } + + public void setMax(int max) { + mMax = max; + } + + public float getProgress() { + return mProgress; + } + + private void drawIndicator() { + float r = mWidth * INDICATOR_RADIUS; + ImageView view = mOn ? mKnobOn : mKnobOff; + view.setTranslationX((float) Math.sin(mProgress * 2 * Math.PI) * r - mIndicatorWidth / 2); + view.setTranslationY((float) -Math.cos(mProgress * 2 * Math.PI) * r - mIndicatorWidth / 2); + } + + @Override + public void setEnabled(boolean enabled) { + mEnabled = enabled; + setOn(enabled); + } + + public void setOn(boolean on) { + if (on != mOn) { + mOn = on; + } + on = on && mEnabled; + mLabelTV.setTextColor(on ? mHighlightColor : mDisabledColor); + mProgressTV.setTextColor(on ? mHighlightColor : mDisabledColor); + setProgressText(on); + mPaint.setColor(on ? mHighlightColor : mDisabledColor); + mKnobOn.setVisibility(on ? View.VISIBLE : View.GONE); + mKnobOff.setVisibility(on ? View.GONE : View.VISIBLE); + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + drawIndicator(); + if (mOn && mEnabled) { + canvas.drawArc(mRectF, -90, mProgress * 360, false, mPaint); + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldW, int oldH) { + int size = w > h ? h : w; + mWidth = size; + mIndicatorWidth = mKnobOn.getWidth(); + + int diff; + if (w > h) { + diff = (w - h) / 2; + mRectF = new RectF(STROKE_WIDTH + diff, STROKE_WIDTH, + w - STROKE_WIDTH - diff, h - STROKE_WIDTH); + } else { + diff = (h - w) / 2; + mRectF = new RectF(STROKE_WIDTH, STROKE_WIDTH + diff, + w - STROKE_WIDTH, h - STROKE_WIDTH - diff); + } + + mProgressTV.setTextSize(TypedValue.COMPLEX_UNIT_PX, size * TEXT_SIZE); + mProgressTV.setPadding(0, (int) (size * TEXT_PADDING), 0, 0); + mProgressTV.setVisibility(View.VISIBLE); + mLabelTV.setTextSize(TypedValue.COMPLEX_UNIT_PX, size * LABEL_SIZE); + mLabelTV.setPadding(0, (int) (size * LABEL_PADDING), 0, 0); + mLabelTV.setLayoutParams(new LinearLayout.LayoutParams((int) (w * LABEL_WIDTH), + LayoutParams.WRAP_CONTENT)); + mLabelTV.setVisibility(View.VISIBLE); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return true; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (mOn) { + mLastX = event.getX(); + mLastY = event.getY(); + getParent().requestDisallowInterceptTouchEvent(true); + } + break; + case MotionEvent.ACTION_MOVE: + if (mOn) { + float x = event.getX(); + float y = event.getY(); + float center = mWidth / 2; + if (mMoved || (x - center) * (x - center) + (y - center) * (y - center) + > center * center / 4) { + float delta = getDelta(x, y); + setProgress(mProgress + delta / 360, true); + mMoved = true; + } + mLastX = x; + mLastY = y; + } + break; + case MotionEvent.ACTION_UP: + if (!mMoved) { + if (mOnKnobChangeListener == null + || mOnKnobChangeListener.onSwitchChanged(this, !mOn)) { + if (mEnabled) { + setOn(!mOn); + invalidate(); + } + } + } + mMoved = false; + break; + default: + break; + } + return true; + } + + private float getDelta(float x, float y) { + float angle = angle(x, y); + float oldAngle = angle(mLastX, mLastY); + float delta = angle - oldAngle; + if (delta >= 180.0f) { + delta = -oldAngle; + } else if (delta <= -180.0f) { + delta = 360 - oldAngle; + } + return delta; + } + + private float angle(float x, float y) { + float center = mWidth / 2.0f; + x -= center; + y -= center; + + if (x == 0.0f) { + if (y > 0.0f) { + return 180.0f; + } else { + return 0.0f; + } + } + + float angle = (float) (Math.atan(y / x) / Math.PI * 180.0); + if (x > 0.0f) { + angle += 90; + } else { + angle += 270; + } + return angle; + } +} diff --git a/src/com/android/gallery3d/ui/MenuExecutor.java b/src/com/android/gallery3d/ui/MenuExecutor.java index 1ace71829..9b2c3259c 100644 --- a/src/com/android/gallery3d/ui/MenuExecutor.java +++ b/src/com/android/gallery3d/ui/MenuExecutor.java @@ -24,6 +24,8 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; +import android.drm.DrmHelper; +import android.net.Uri; import android.os.Handler; import android.os.Message; import android.support.v4.print.PrintHelper; @@ -179,7 +181,7 @@ public class MenuExecutor { boolean supportInfo = (supported & MediaObject.SUPPORT_INFO) != 0; boolean supportPrint = (supported & MediaObject.SUPPORT_PRINT) != 0; supportPrint &= PrintHelper.systemSupportsPrint(); - + boolean supportDrmInfo = (supported & MediaObject.SUPPORT_DRM_INFO) != 0; setMenuItemVisible(menu, R.id.action_delete, supportDelete); setMenuItemVisible(menu, R.id.action_rotate_ccw, supportRotate); setMenuItemVisible(menu, R.id.action_rotate_cw, supportRotate); @@ -195,6 +197,7 @@ public class MenuExecutor { // setMenuItemVisible(menu, R.id.action_simple_edit, supportEdit); setMenuItemVisible(menu, R.id.action_details, supportInfo); setMenuItemVisible(menu, R.id.print, supportPrint); + setMenuItemVisible(menu, R.id.action_drm_info, supportDrmInfo); } public static void updateMenuForPanorama(Menu menu, boolean shareAsPanorama360, @@ -254,6 +257,14 @@ public class MenuExecutor { Intent intent = getIntentBySingleSelectedPath(Intent.ACTION_ATTACH_DATA) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.putExtra("mimeType", intent.getType()); + + // DRM files can be set as wallpaper only. Don't show other options + // to set as. + Uri uri = intent.getData(); + if (DrmHelper.isDrmFile(DrmHelper.getFilePath(mActivity, uri))) { + intent.setPackage("com.android.gallery3d"); + } + Activity activity = mActivity; activity.startActivity(Intent.createChooser( intent, activity.getString(R.string.set_as))); @@ -271,6 +282,20 @@ public class MenuExecutor { case R.id.action_show_on_map: title = R.string.show_on_map; break; + case R.id.action_drm_info: + DataManager manager = mActivity.getDataManager(); + Path path = getSingleSelectedPath(); + Uri uri = manager.getContentUri(path); + String filepath = null; + String scheme = uri.getScheme(); + if ("file".equals(scheme)) { + filepath = uri.getPath(); + } else { + filepath = DrmHelper.getFilePath(mActivity, uri); + } + DrmHelper.showDrmInfo(mActivity, filepath); + title = R.string.drm_license_info; + break; default: return; } diff --git a/src/com/android/gallery3d/ui/Paper.java b/src/com/android/gallery3d/ui/Paper.java index b36f5c3a2..6ed5013a2 100644 --- a/src/com/android/gallery3d/ui/Paper.java +++ b/src/com/android/gallery3d/ui/Paper.java @@ -28,53 +28,76 @@ class Paper { @SuppressWarnings("unused") private static final String TAG = "Paper"; private static final int ROTATE_FACTOR = 4; - private EdgeAnimation mAnimationLeft = new EdgeAnimation(); - private EdgeAnimation mAnimationRight = new EdgeAnimation(); + private EdgeAnimation mAnimationBegin = new EdgeAnimation(); + private EdgeAnimation mAnimationEnd = new EdgeAnimation(); private int mWidth; + private int mHeight; private float[] mMatrix = new float[16]; + private final boolean mIsWide; + + public Paper(boolean wide) { + mIsWide = wide; + } + public void overScroll(float distance) { distance /= mWidth; // make it relative to width if (distance < 0) { - mAnimationLeft.onPull(-distance); + mAnimationBegin.onPull(-distance); } else { - mAnimationRight.onPull(distance); + mAnimationEnd.onPull(distance); } } public void edgeReached(float velocity) { velocity /= mWidth; // make it relative to width if (velocity < 0) { - mAnimationRight.onAbsorb(-velocity); + mAnimationEnd.onAbsorb(-velocity); } else { - mAnimationLeft.onAbsorb(velocity); + mAnimationBegin.onAbsorb(velocity); } } public void onRelease() { - mAnimationLeft.onRelease(); - mAnimationRight.onRelease(); + mAnimationBegin.onRelease(); + mAnimationEnd.onRelease(); } public boolean advanceAnimation() { // Note that we use "|" because we want both animations get updated. - return mAnimationLeft.update() | mAnimationRight.update(); + return mAnimationBegin.update() | mAnimationEnd.update(); } public void setSize(int width, int height) { mWidth = width; + mHeight = height; } - public float[] getTransform(Rect rect, float scrollX) { - float left = mAnimationLeft.getValue(); - float right = mAnimationRight.getValue(); - float screenX = rect.centerX() - scrollX; - // We linearly interpolate the value [left, right] for the screenX - // range int [-1/4, 5/4]*mWidth. So if part of the thumbnail is outside + public float[] getTransform(Rect rect, float scroll) { + Log.d(TAG, rect.toString()); + float start = mAnimationBegin.getValue(); + float end = mAnimationEnd.getValue(); + int center = 0; + int screenWidth = mWidth; + int rotateX = 0; + int rotateY = 0; + final boolean wide = mIsWide; + if (wide) { + center = rect.centerX(); + rotateY = 1; + } else { + center = rect.centerY(); + rotateX = 1; + screenWidth = mHeight; + } + float screen = center - scroll; + // We linearly interpolate the value [start, end] for the screen + // range int [-1/4, 5/4]*screenWidth. So if part of the thumbnail is outside // the screen, we still get some transform. - float x = screenX + mWidth / 4; - int range = 3 * mWidth / 2; - float t = ((range - x) * left - x * right) / range; + float x = screen + screenWidth / 4; + int range = 3 * screenWidth / 2; + float t = ((range - x) * start - x * end) / range; + // compress t to the range (-1, 1) by the function // f(t) = (1 / (1 + e^-t) - 0.5) * 2 // then multiply by 90 to make the range (-45, 45) @@ -82,8 +105,9 @@ class Paper { (1 / (1 + (float) Math.exp(-t * ROTATE_FACTOR)) - 0.5f) * 2 * -45; Matrix.setIdentityM(mMatrix, 0); Matrix.translateM(mMatrix, 0, mMatrix, 0, rect.centerX(), rect.centerY(), 0); - Matrix.rotateM(mMatrix, 0, degrees, 0, 1, 0); + Matrix.rotateM(mMatrix, 0, degrees, rotateX, rotateY, 0); Matrix.translateM(mMatrix, 0, mMatrix, 0, -rect.width() / 2, -rect.height() / 2, 0); + return mMatrix; } } diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java index e8c706f05..265a53fc7 100644..100755 --- a/src/com/android/gallery3d/ui/PhotoView.java +++ b/src/com/android/gallery3d/ui/PhotoView.java @@ -18,6 +18,7 @@ package com.android.gallery3d.ui; import android.content.Context; import android.content.res.Configuration; +import android.drm.DrmHelper; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Rect; @@ -92,6 +93,9 @@ public class PhotoView extends GLView { // Returns true if the item is a Video. public boolean isVideo(int offset); + // Returns true if the item is a Gif. + public boolean isGif(int offset); + // Returns true if the item can be deleted. public boolean isDeletable(int offset); @@ -200,6 +204,7 @@ public class PhotoView extends GLView { private EdgeView mEdgeView; private UndoBarView mUndoBar; private Texture mVideoPlayIcon; + private Texture mDrmIcon; private SynchronizedHandler mHandler; @@ -303,6 +308,7 @@ public class PhotoView extends GLView { } }); mVideoPlayIcon = new ResourceTexture(mContext, R.drawable.ic_control_play); + mDrmIcon = new ResourceTexture(mContext, R.drawable.drm_image); for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) { if (i == 0) { mPictures.put(i, new FullPicture()); @@ -593,7 +599,6 @@ public class PhotoView extends GLView { private boolean mIsCamera; private boolean mIsPanorama; private boolean mIsStaticCamera; - private boolean mIsVideo; private boolean mIsDeletable; private int mLoadingState = Model.LOADING_INIT; private Size mSize = new Size(); @@ -606,7 +611,6 @@ public class PhotoView extends GLView { mIsCamera = mModel.isCamera(0); mIsPanorama = mModel.isPanorama(0); mIsStaticCamera = mModel.isStaticCamera(0); - mIsVideo = mModel.isVideo(0); mIsDeletable = mModel.isDeletable(0); mLoadingState = mModel.getLoadingState(0); setScreenNail(mModel.getScreenNail(0)); @@ -731,11 +735,23 @@ public class PhotoView extends GLView { // Draw the play video icon and the message. canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f)); int s = (int) (scale * Math.min(r.width(), r.height()) + 0.5f); - if (mIsVideo) drawVideoPlayIcon(canvas, s); - if (mLoadingState == Model.LOADING_FAIL) { + //Full pic locates at index 0 of the array in PhotoDataAdapter + if (mModel.isVideo(0) || mModel.isGif(0)) { + drawVideoPlayIcon(canvas, s); + } + if (mLoadingState == Model.LOADING_FAIL ) { drawLoadingFailMessage(canvas); } + if (getFilmMode()) { + MediaItem item = mModel.getMediaItem(0); + if (item != null) { + if (DrmHelper.isDrmFile(item.getFilePath())) { + drawDrmIcon(canvas, s); + } + } + } + // Draw a debug indicator showing which picture has focus (index == // 0). //canvas.fillRect(-10, -10, 20, 20, 0x80FF00FF); @@ -774,7 +790,6 @@ public class PhotoView extends GLView { private boolean mIsCamera; private boolean mIsPanorama; private boolean mIsStaticCamera; - private boolean mIsVideo; private boolean mIsDeletable; private int mLoadingState = Model.LOADING_INIT; private Size mSize = new Size(); @@ -788,7 +803,6 @@ public class PhotoView extends GLView { mIsCamera = mModel.isCamera(mIndex); mIsPanorama = mModel.isPanorama(mIndex); mIsStaticCamera = mModel.isStaticCamera(mIndex); - mIsVideo = mModel.isVideo(mIndex); mIsDeletable = mModel.isDeletable(mIndex); mLoadingState = mModel.getLoadingState(mIndex); setScreenNail(mModel.getScreenNail(mIndex)); @@ -852,10 +866,21 @@ public class PhotoView extends GLView { invalidate(); } int s = Math.min(drawW, drawH); - if (mIsVideo) drawVideoPlayIcon(canvas, s); - if (mLoadingState == Model.LOADING_FAIL) { + if (mModel.isVideo(mIndex) || mModel.isGif(mIndex)) { + drawVideoPlayIcon(canvas, s); + } + + if (mLoadingState == Model.LOADING_FAIL ) { drawLoadingFailMessage(canvas); } + + MediaItem item = mModel.getMediaItem(mIndex); + if (item != null) { + if (DrmHelper.isDrmFile(item.getFilePath())) { + drawDrmIcon(canvas, s); + } + } + canvas.restore(); } @@ -922,6 +947,13 @@ public class PhotoView extends GLView { mVideoPlayIcon.draw(canvas, -s / 2, -s / 2, s, s); } + // Draw the Drm lock icon (in the place where the spinner was) + private void drawDrmIcon(GLCanvas canvas, int side) { + int s = side / ICON_RATIO; + // Draw the Drm lock icon at the center + mDrmIcon.draw(canvas, -s / 2, -s / 2, s, s); + } + // Draw the "no thumbnail" message private void drawLoadingFailMessage(GLCanvas canvas) { StringTexture m = mNoThumbnailText; @@ -1128,6 +1160,7 @@ public class PhotoView extends GLView { } private void deleteAfterAnimation(int duration) { + if (mHandler.hasMessages(MSG_DELETE_ANIMATION_DONE)) return; MediaItem item = mModel.getMediaItem(mTouchBoxIndex); if (item == null) return; mListener.onCommitDeleteImage(); @@ -1854,4 +1887,5 @@ public class PhotoView extends GLView { } return effect; } + } diff --git a/src/com/android/gallery3d/ui/SlotView.java b/src/com/android/gallery3d/ui/SlotView.java index bd0ffdc15..475d93a67 100644 --- a/src/com/android/gallery3d/ui/SlotView.java +++ b/src/com/android/gallery3d/ui/SlotView.java @@ -18,20 +18,24 @@ package com.android.gallery3d.ui; import android.graphics.Rect; import android.os.Handler; +import android.text.TextUtils; import android.view.GestureDetector; import android.view.MotionEvent; +import android.view.View; import android.view.animation.DecelerateInterpolator; +import com.android.gallery3d.R; import com.android.gallery3d.anim.Animation; import com.android.gallery3d.app.AbstractGalleryActivity; import com.android.gallery3d.common.Utils; import com.android.gallery3d.glrenderer.GLCanvas; +import java.util.Locale; + public class SlotView extends GLView { @SuppressWarnings("unused") private static final String TAG = "SlotView"; - private static final boolean WIDE = true; private static final int INDEX_NONE = -1; public static final int RENDER_MORE_PASS = 1; @@ -62,7 +66,7 @@ public class SlotView extends GLView { private final GestureDetector mGestureDetector; private final ScrollerHelper mScroller; - private final Paper mPaper = new Paper(); + private final Paper mPaper; private Listener mListener; private UserInteractionListener mUIListener; @@ -88,10 +92,17 @@ public class SlotView extends GLView { // to prevent allocating memory private final Rect mTempRect = new Rect(); + // Flag to check whether it is come from Photo Page. + private boolean isFromPhotoPage = false; + + private final boolean mIsWide; + public SlotView(AbstractGalleryActivity activity, Spec spec) { + mIsWide = activity.getResources().getBoolean(R.bool.config_scroll_horizontal); mGestureDetector = new GestureDetector(activity, new MyGestureListener()); mScroller = new ScrollerHelper(activity); mHandler = new SynchronizedHandler(activity.getGLRoot()); + mPaper = new Paper(mIsWide); setSlotSpec(spec); } @@ -109,7 +120,7 @@ public class SlotView extends GLView { return; } Rect rect = mLayout.getSlotRect(index, mTempRect); - int position = WIDE + int position = mIsWide ? (rect.left + rect.right - getWidth()) / 2 : (rect.top + rect.bottom - getHeight()) / 2; setScrollPosition(position); @@ -117,11 +128,11 @@ public class SlotView extends GLView { public void makeSlotVisible(int index) { Rect rect = mLayout.getSlotRect(index, mTempRect); - int visibleBegin = WIDE ? mScrollX : mScrollY; - int visibleLength = WIDE ? getWidth() : getHeight(); + int visibleBegin = mIsWide ? mScrollX : mScrollY; + int visibleLength = mIsWide ? getWidth() : getHeight(); int visibleEnd = visibleBegin + visibleLength; - int slotBegin = WIDE ? rect.left : rect.top; - int slotEnd = WIDE ? rect.right : rect.bottom; + int slotBegin = mIsWide ? rect.left : rect.top; + int slotEnd = mIsWide ? rect.right : rect.bottom; int position = visibleBegin; if (visibleLength < slotEnd - slotBegin) { @@ -135,7 +146,20 @@ public class SlotView extends GLView { setScrollPosition(position); } + /** + * Set the flag which used for check whether it is come from Photo Page. + */ + public void setIsFromPhotoPage(boolean flag) { + isFromPhotoPage = flag; + } + public void setScrollPosition(int position) { + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault()) + && position == 0 && !isFromPhotoPage) { + // If RTL and not from Photo Page, set position to max. + position = mLayout.getScrollLimit(); + } position = Utils.clamp(position, 0, mLayout.getScrollLimit()); mScroller.setPosition(position); updateScrollPosition(position, false); @@ -179,8 +203,8 @@ public class SlotView extends GLView { } private void updateScrollPosition(int position, boolean force) { - if (!force && (WIDE ? position == mScrollX : position == mScrollY)) return; - if (WIDE) { + if (!force && (mIsWide ? position == mScrollX : position == mScrollY)) return; + if (mIsWide) { mScrollX = position; } else { mScrollY = position; @@ -315,7 +339,7 @@ public class SlotView extends GLView { canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX); Rect rect = mLayout.getSlotRect(index, mTempRect); if (paperActive) { - canvas.multiplyMatrix(mPaper.getTransform(rect, mScrollX), 0); + canvas.multiplyMatrix(mPaper.getTransform(rect, (mIsWide ? mScrollX : mScrollY)), 0); } else { canvas.translate(rect.left, rect.top, 0); } @@ -389,6 +413,8 @@ public class SlotView extends GLView { public int rowsLand = -1; public int rowsPort = -1; + public int colsLand = -1; + public int colsPort = -1; public int slotGap = -1; } @@ -434,9 +460,17 @@ public class SlotView extends GLView { public Rect getSlotRect(int index, Rect rect) { int col, row; - if (WIDE) { - col = index / mUnitCount; - row = index - col * mUnitCount; + if (mIsWide) { + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { + // If RTL, recalculate the columns and rows. + int count = ((mSlotCount + mUnitCount - 1) / mUnitCount); + col = count - index / mUnitCount - 1; + row = index % mUnitCount; + } else { + col = index / mUnitCount; + row = index - col * mUnitCount; + } } else { row = index / mUnitCount; col = index - row * mUnitCount; @@ -498,10 +532,17 @@ public class SlotView extends GLView { mSlotWidth = mSpec.slotWidth; mSlotHeight = mSpec.slotHeight; } else { - int rows = (mWidth > mHeight) ? mSpec.rowsLand : mSpec.rowsPort; - mSlotGap = mSpec.slotGap; - mSlotHeight = Math.max(1, (mHeight - (rows - 1) * mSlotGap) / rows); - mSlotWidth = mSlotHeight - mSpec.slotHeightAdditional; + if (mIsWide) { + int rows = (mWidth > mHeight) ? mSpec.rowsLand : mSpec.rowsPort; + mSlotGap = mSpec.slotGap; + mSlotHeight = Math.max(1, (mHeight - (rows - 1) * mSlotGap) / rows); + mSlotWidth = mSlotHeight - mSpec.slotHeightAdditional; + } else { + int cols = (mWidth > mHeight) ? mSpec.colsLand : mSpec.colsPort; + mSlotGap = mSpec.slotGap; + mSlotHeight = Math.max(1, (mWidth - (cols - 1) * mSlotGap) / cols); + mSlotWidth = mSlotHeight - mSpec.slotHeightAdditional; + } } if (mRenderer != null) { @@ -509,7 +550,7 @@ public class SlotView extends GLView { } int[] padding = new int[2]; - if (WIDE) { + if (mIsWide) { initLayoutParameters(mWidth, mHeight, mSlotWidth, mSlotHeight, padding); mVerticalPadding.startAnimateTo(padding[0]); mHorizontalPadding.startAnimateTo(padding[1]); @@ -530,7 +571,14 @@ public class SlotView extends GLView { private void updateVisibleSlotRange() { int position = mScrollPosition; - if (WIDE) { + if (mIsWide) { + if (View.LAYOUT_DIRECTION_RTL == TextUtils + .getLayoutDirectionFromLocale(Locale.getDefault())) { + // If RTL, recalculate the position. + position = mContentLength > mWidth ? (mContentLength - position - mWidth) + : position; + position = Math.max(0, position); + } int startCol = position / (mSlotWidth + mSlotGap); int start = Math.max(0, mUnitCount * startCol); int endCol = (position + mWidth + mSlotWidth + mSlotGap - 1) / @@ -575,9 +623,8 @@ public class SlotView extends GLView { } public int getSlotIndexByPosition(float x, float y) { - int absoluteX = Math.round(x) + (WIDE ? mScrollPosition : 0); - int absoluteY = Math.round(y) + (WIDE ? 0 : mScrollPosition); - + int absoluteX = Math.round(x) + (mIsWide ? mScrollPosition : 0); + int absoluteY = Math.round(y) + (mIsWide ? 0 : mScrollPosition); absoluteX -= mHorizontalPadding.get(); absoluteY -= mVerticalPadding.get(); @@ -588,11 +635,11 @@ public class SlotView extends GLView { int columnIdx = absoluteX / (mSlotWidth + mSlotGap); int rowIdx = absoluteY / (mSlotHeight + mSlotGap); - if (!WIDE && columnIdx >= mUnitCount) { + if (!mIsWide && columnIdx >= mUnitCount) { return INDEX_NONE; } - if (WIDE && rowIdx >= mUnitCount) { + if (mIsWide && rowIdx >= mUnitCount) { return INDEX_NONE; } @@ -604,7 +651,7 @@ public class SlotView extends GLView { return INDEX_NONE; } - int index = WIDE + int index = mIsWide ? (columnIdx * mUnitCount + rowIdx) : (rowIdx * mUnitCount + columnIdx); @@ -612,7 +659,7 @@ public class SlotView extends GLView { } public int getScrollLimit() { - int limit = WIDE ? mContentLength - mWidth : mContentLength - mHeight; + int limit = mIsWide ? mContentLength - mWidth : mContentLength - mHeight; return limit <= 0 ? 0 : limit; } @@ -660,7 +707,7 @@ public class SlotView extends GLView { cancelDown(false); int scrollLimit = mLayout.getScrollLimit(); if (scrollLimit == 0) return false; - float velocity = WIDE ? velocityX : velocityY; + float velocity = mIsWide ? velocityX : velocityY; mScroller.fling((int) -velocity, 0, scrollLimit); if (mUIListener != null) mUIListener.onUserInteractionBegin(); invalidate(); @@ -671,7 +718,7 @@ public class SlotView extends GLView { public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { cancelDown(false); - float distance = WIDE ? distanceX : distanceY; + float distance = mIsWide ? distanceX : distanceY; int overDistance = mScroller.startScroll( Math.round(distance), 0, mLayout.getScrollLimit()); if (mOverscrollEffect == OVERSCROLL_3D && overDistance != 0) { @@ -718,7 +765,7 @@ public class SlotView extends GLView { mStartIndex = INDEX_NONE; } // Reset the scroll position to avoid scrolling over the updated limit. - setScrollPosition(WIDE ? mScrollX : mScrollY); + setScrollPosition(mIsWide ? mScrollX : mScrollY); return changed; } @@ -785,4 +832,11 @@ public class SlotView extends GLView { if (progress == 1f) mEnabled = false; } } + + /** + * Get the SlotView's max scroll value. + */ + public int getScrollLimit() { + return mLayout.getScrollLimit(); + } } diff --git a/src/com/android/gallery3d/util/GIFView.java b/src/com/android/gallery3d/util/GIFView.java new file mode 100755 index 000000000..c80625b41 --- /dev/null +++ b/src/com/android/gallery3d/util/GIFView.java @@ -0,0 +1,234 @@ +package com.android.gallery3d.util; + +import com.android.gallery3d.R; + +import android.content.Context; +import android.content.ContentResolver; +import android.content.res.AssetManager; +import android.database.Cursor; +import android.drm.DrmHelper; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.widget.ImageView; +import android.widget.Toast; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.IOException; + +public class GIFView extends ImageView implements GifAction { + + private static final String TAG = "GIFView"; + private static final float SCALE_LIMIT = 4; + private static final long FRAME_DELAY = 200; //milliseconds + + private GifDecoder mGifDecoder = null; + private Bitmap mCurrentImage = null; + private DrawThread mDrawThread = null; + + private Uri mUri; + private Context mContext; + + public GIFView(Context context) { + super(context); + mContext = context; + } + + public boolean setDrawable(Uri uri) { + if (null == uri) { + return false; + } + mUri = uri; + + // Let decode the GIF image from byte stream instead of file stream + String filepath = DrmHelper.getFilePath(mContext, mUri); + if (DrmHelper.isDrmFile(filepath)) { + byte[] bytes = DrmHelper.getDrmImageBytes(filepath); + DrmHelper.manageDrmLicense(mContext, this.getHandler(), filepath, + "image/gif"); + if (bytes == null) { + return false; + } + startDecode(bytes); + return true; + } + + InputStream is = getInputStream(uri); + if (is == null || (getFileSize (is) == 0)) { + return false; + } + startDecode(is); + return true; + } + + private int getFileSize (InputStream is) { + if(is == null) return 0; + + int size = 0; + try { + if (is instanceof FileInputStream) { + FileInputStream f = (FileInputStream) is; + size = (int) f.getChannel().size(); + } else { + while (-1 != is.read()) { + size++; + } + } + + } catch (IOException e) { + Log.e(TAG, "catch exception:" + e); + } + + return size; + + } + + private InputStream getInputStream (Uri uri) { + ContentResolver cr = mContext.getContentResolver(); + InputStream input = null; + try { + input = cr.openInputStream(uri); + } catch (IOException e) { + Log.e(TAG, "catch exception:" + e); + } + return input; + } + + private void startDecode(InputStream is) { + freeGifDecoder(); + mGifDecoder = new GifDecoder(is, this); + mGifDecoder.start(); + } + + private void startDecode(byte[] bytes) { + freeGifDecoder(); + mGifDecoder = new GifDecoder(bytes, this); + mGifDecoder.start(); + } + + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mGifDecoder == null) { + return; + } + + if (mCurrentImage == null) { + mCurrentImage = mGifDecoder.getImage(); + } + if (mCurrentImage == null) { + // if this gif can not be displayed, just try to show it as jpg by parsing mUri + setImageURI(mUri); + return; + } + setImageURI(null); + int saveCount = canvas.getSaveCount(); + canvas.save(); + canvas.translate(getPaddingLeft(), getPaddingTop()); + Rect sRect = null; + Rect dRect = null; + + int imageHeight = mCurrentImage.getHeight(); + int imageWidth = mCurrentImage.getWidth(); + + int displayHeight = ViewGifImage.mDM.heightPixels; + int displayWidth = ViewGifImage.mDM.widthPixels; + + int width, height; + if (imageWidth >= displayWidth || imageHeight >= displayHeight) { + // scale-down the image + if (imageWidth * displayHeight > displayWidth * imageHeight) { + width = displayWidth; + height = (imageHeight * width) / imageWidth; + } else { + height = displayHeight; + width = (imageWidth * height) / imageHeight; + } + } else { + // scale-up the image + float scale = Math.min(SCALE_LIMIT, Math.min(displayWidth / (float) imageWidth, + displayHeight / (float) imageHeight)); + width = (int) (imageWidth * scale); + height = (int) (imageHeight * scale); + } + dRect = new Rect((displayWidth - width) / 2, (displayHeight - height) / 2, + (displayWidth + width) / 2, (displayHeight + height) / 2); + canvas.drawBitmap(mCurrentImage, sRect, dRect, null); + canvas.restoreToCount(saveCount); + } + + public void parseOk(boolean parseStatus, int frameIndex) { + if (parseStatus) { + //indicates the start of a new GIF + if (mGifDecoder != null && frameIndex == -1 + && mGifDecoder.getFrameCount() > 1) { + if (mDrawThread != null) { + mDrawThread = null; + } + mDrawThread = new DrawThread(); + mDrawThread.start(); + } + } else { + Log.e(TAG, "parse error"); + } + } + + private Handler mRedrawHandler = new Handler() { + public void handleMessage(Message msg) { + invalidate(); + } + }; + + private class DrawThread extends Thread { + public void run() { + if (mGifDecoder == null) { + return; + } + + while (true) { + if (!isShown() || mRedrawHandler == null) { + break; + } + if (mGifDecoder == null) { + return; + } + GifFrame frame = mGifDecoder.next(); + mCurrentImage = frame.mImage; + + Message msg = mRedrawHandler.obtainMessage(); + mRedrawHandler.sendMessage(msg); + try { + Thread.sleep(getDelay(frame)); + } catch (InterruptedException e) { + Log.e(TAG, "catch exception:" + e); + } + } + } + + } + + private long getDelay (GifFrame frame) { + //in milliseconds + return frame.mDelayInMs == 0 ? FRAME_DELAY : frame.mDelayInMs; + } + + private void freeGifDecoder () { + if (mGifDecoder != null) { + mGifDecoder.free(); + mGifDecoder = null; + } + + } + + public void freeMemory() { + if (mDrawThread != null) { + mDrawThread = null; + } + freeGifDecoder(); + } +} diff --git a/src/com/android/gallery3d/util/GalleryUtils.java b/src/com/android/gallery3d/util/GalleryUtils.java index 8fb926c0b..8e4ebb714 100644 --- a/src/com/android/gallery3d/util/GalleryUtils.java +++ b/src/com/android/gallery3d/util/GalleryUtils.java @@ -17,6 +17,7 @@ package com.android.gallery3d.util; import android.annotation.TargetApi; +import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; @@ -35,6 +36,7 @@ import android.provider.MediaStore; import android.util.DisplayMetrics; import android.util.Log; import android.view.WindowManager; +import android.widget.Toast; import com.android.gallery3d.R; import com.android.gallery3d.app.GalleryActivity; @@ -275,7 +277,7 @@ public class GalleryUtils { return String.format(Locale.ENGLISH, format, latitude, longitude); } - public static void showOnMap(Context context, double latitude, double longitude) { + public static void showOnMap(final Context context, double latitude, double longitude) { try { // We don't use "geo:latitude,longitude" because it only centers // the MapView to the specified location, but we need a marker @@ -292,8 +294,21 @@ public class GalleryUtils { // Use the "geo intent" if no GMM is installed Log.e(TAG, "GMM activity not found!", e); String url = formatLatitudeLongitude("geo:%f,%f", latitude, longitude); - Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - context.startActivity(mapsIntent); + try { + Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + context.startActivity(mapsIntent); + } catch (ActivityNotFoundException ex) { + Log.e(TAG, "Map view activity not found! url = " + url, ex); + ((Activity)context).runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(context, + context.getString(R.string.map_activity_not_found_err), + Toast.LENGTH_SHORT).show(); + } + }); + + } } } diff --git a/src/com/android/gallery3d/util/GifAction.java b/src/com/android/gallery3d/util/GifAction.java new file mode 100644 index 000000000..88e3cdee0 --- /dev/null +++ b/src/com/android/gallery3d/util/GifAction.java @@ -0,0 +1,5 @@ +package com.android.gallery3d.util; + +public interface GifAction { + public void parseOk(boolean parseStatus, int frameIndex); +} diff --git a/src/com/android/gallery3d/util/GifDecoder.java b/src/com/android/gallery3d/util/GifDecoder.java new file mode 100755 index 000000000..4235fc5a5 --- /dev/null +++ b/src/com/android/gallery3d/util/GifDecoder.java @@ -0,0 +1,723 @@ +package com.android.gallery3d.util; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +public class GifDecoder extends Thread { + + public static final int STATUS_PARSING = 0; + public static final int STATUS_FORMAT_ERROR = 1; + public static final int STATUS_OPEN_ERROR = 2; + public static final int STATUS_FINISH = -1; + + private InputStream mIS; + private int mStatus; + + public int mWidth; // full image width + public int mHeight; // full image height + private boolean mGctFlag; // global color table used + private int mGctSize; // size of global color table + private int mLoopCount = 1; // iterations; 0 = repeat forever + + private int[] mGct; // global color table + private int[] mLct; // local color table + private int[] mAct; // active color table + + private int mBgIndex; // background color index + private int mBgColor; // background color + private int mLastBgColor; // previous bg color + private int mPixelAspect; // pixel aspect ratio + + private boolean mLctFlag; // local color table flag + private boolean mInterlace; // interlace flag + private int mLctSize; // local color table size + + private int mIx, mIy, mIw, mIh; // current image rectangle + private int mLrx, mLry, mLrw, mLrh; + private Bitmap mImage; // current frame + private Bitmap mLastImage; // previous frame + private GifFrame mCurrentFrame = null; + + private boolean mIsShow = false; + + private byte[] mBlock = new byte[256]; // current data block + private int mBlockSize = 0; // block size + private int mDispose = 0; + private int mLastDispose = 0; + private boolean mTransparency = false; // use transparent color + private int mDelay = 0; // delay in milliseconds + private int mTransIndex; // transparent color index + + // max decoder pixel stack size + private static final int MaxStackSize = 4096; + + // LZW decoder working arrays + private short[] mPrefix; + private byte[] mSuffix; + private byte[] mPixelStack; + private byte[] mPixels; + + private GifFrame mGifFrame; // frames read from current file + private int mFrameCount; + + private GifAction mGifAction = null; + + private byte[] mGifData = null; + + public GifDecoder(byte[] data, GifAction act) { + mGifData = data; + mGifAction = act; + } + + public GifDecoder(InputStream is, GifAction act) { + mIS = is; + mGifAction = act; + } + + public void run() { + if (mIS != null) { + readStream(); + } else if (mGifData != null) { + readByte(); + } + } + + public void free() { + freeFrame(); + freeIS(); + freeImage(); + } + + public int getStatus() { + return mStatus; + } + + public boolean parseOk() { + return mStatus == STATUS_FINISH; + } + + public int getDelay(int n) { + mDelay = -1; + if ((n >= 0) && (n < mFrameCount)) { + GifFrame f = getFrame(n); + if (f != null) { + mDelay = f.mDelayInMs; + } + } + return mDelay; + } + + public int[] getDelays() { + GifFrame f = mGifFrame; + int[] d = new int[mFrameCount]; + int i = 0; + while (f != null && i < mFrameCount) { + d[i] = f.mDelayInMs; + f = f.mNextFrame; + i++; + } + return d; + } + + public int getFrameCount() { + return mFrameCount; + } + + public Bitmap getImage() { + return getFrameImage(0); + } + + public int getLoopCount() { + return mLoopCount; + } + + private void setPixels() { + int[] dest = new int[mWidth * mHeight]; + // fill in starting image contents based on last image's dispose code + if (mLastDispose > 0) { + if (mLastDispose == 3) { + // use image before last + int n = mFrameCount - 2; + if (n > 0) { + mLastImage = getPreUndisposedImage(n - 1); + } else { + mLastImage = null; + } + } + if (mLastImage != null) { + mLastImage.getPixels(dest, 0, mWidth, 0, 0, mWidth, mHeight); + // copy pixels + if (mLastDispose == 2) { + // fill last image rect area with background color + int c = 0; + if (!mTransparency) { + c = mLastBgColor; + } + for (int i = 0; i < mLrh; i++) { + int n1 = (mLry + i) * mWidth + mLrx; + int n2 = n1 + mLrw; + for (int k = n1; k < n2; k++) { + dest[k] = c; + } + } + } + } + } + + // copy each source line to the appropriate place in the destination + int pass = 1; + int inc = 8; + int iline = 0; + for (int i = 0; i < mIh; i++) { + int line = i; + if (mInterlace) { + if (iline >= mIh) { + pass++; + switch (pass) { + case 2: + iline = 4; + break; + case 3: + iline = 2; + inc = 4; + break; + case 4: + iline = 1; + inc = 2; + } + } + line = iline; + iline += inc; + } + line += mIy; + if (line < mHeight) { + int k = line * mWidth; + int dx = k + mIx; // start of line in dest + int dlim = dx + mIw; // end of dest line + if ((k + mWidth) < dlim) { + dlim = k + mWidth; // past dest edge + } + int sx = i * mIw; // start of line in source + while (dx < dlim) { + // map color and insert in destination + int index = ((int) mPixels[sx++]) & 0xff; + int c = mAct[index]; + if (c != 0) { + dest[dx] = c; + } + dx++; + } + } + } + mImage = Bitmap.createBitmap(dest, mWidth, mHeight, Config.ARGB_4444); + } + + public Bitmap getFrameImage(int n) { + GifFrame frame = getFrame(n); + if (frame == null) { + return null; + } else { + return frame.mImage; + } + } + + public GifFrame getCurrentFrame() { + return mCurrentFrame; + } + + public GifFrame getFrame(int n) { + GifFrame frame = mGifFrame; + int i = 0; + while (frame != null) { + if (i == n) { + return frame; + } else { + frame = frame.mNextFrame; + } + i++; + } + return null; + } + + private Bitmap getPreUndisposedImage(int n) { + Bitmap preUndisposedImage = null; + GifFrame frame = mGifFrame; + int i = 0; + while (frame != null && i <= n) { + if (frame.mDispose == 1) { + preUndisposedImage = frame.mImage; + } else { + frame = frame.mNextFrame; + } + i++; + } + return preUndisposedImage; + } + + public void reset() { + mCurrentFrame = mGifFrame; + } + + public GifFrame next() { + if (mIsShow == false) { + mIsShow = true; + return mGifFrame; + } else { + if (mStatus == STATUS_PARSING) { + if (mCurrentFrame.mNextFrame != null) { + mCurrentFrame = mCurrentFrame.mNextFrame; + } + } else { + mCurrentFrame = mCurrentFrame.mNextFrame; + if (mCurrentFrame == null) { + mCurrentFrame = mGifFrame; + } + } + return mCurrentFrame; + } + } + + private int readByte() { + mIS = new ByteArrayInputStream(mGifData); + mGifData = null; + return readStream(); + } + + private int readStream() { + init(); + if (mIS != null) { + readHeader(); + if (!err()) { + readContents(); + if (mFrameCount < 0) { + mStatus = STATUS_FORMAT_ERROR; + mGifAction.parseOk(false, -1); + } else { + mStatus = STATUS_FINISH; + mGifAction.parseOk(true, -1); + } + } + try { + mIS.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + mStatus = STATUS_OPEN_ERROR; + mGifAction.parseOk(false, -1); + } + return mStatus; + } + + private void decodeImageData() { + int NullCode = -1; + int npix = mIw * mIh; + int available, clear, code_mask, code_size, end_of_information, in_code, old_code, + bits, code, count, i, datum, data_size, first, top, bi, pi; + + if ((mPixels == null) || (mPixels.length < npix)) { + mPixels = new byte[npix]; // allocate new pixel array + } + if (mPrefix == null) { + mPrefix = new short[MaxStackSize]; + } + if (mSuffix == null) { + mSuffix = new byte[MaxStackSize]; + } + if (mPixelStack == null) { + mPixelStack = new byte[MaxStackSize + 1]; + } + // Initialize GIF data stream decoder. + data_size = read(); + clear = 1 << data_size; + end_of_information = clear + 1; + available = clear + 2; + old_code = NullCode; + code_size = data_size + 1; + code_mask = (1 << code_size) - 1; + for (code = 0; code < clear; code++) { + mPrefix[code] = 0; + mSuffix[code] = (byte) code; + } + + // Decode GIF pixel stream. + datum = bits = count = first = top = pi = bi = 0; + for (i = 0; i < npix;) { + if (top == 0) { + if (bits < code_size) { + // Load bytes until there are enough bits for a code. + if (count == 0) { + // Read a new data block. + count = readBlock(); + if (count <= 0) { + break; + } + bi = 0; + } + datum += (((int) mBlock[bi]) & 0xff) << bits; + bits += 8; + bi++; + count--; + continue; + } + // Get the next code. + code = datum & code_mask; + datum >>= code_size; + bits -= code_size; + + // Interpret the code + if ((code > available) || (code == end_of_information)) { + break; + } + if (code == clear) { + // Reset decoder. + code_size = data_size + 1; + code_mask = (1 << code_size) - 1; + available = clear + 2; + old_code = NullCode; + continue; + } + if (old_code == NullCode) { + mPixelStack[top++] = mSuffix[code]; + old_code = code; + first = code; + continue; + } + in_code = code; + if (code == available) { + mPixelStack[top++] = (byte) first; + code = old_code; + } + while (code > clear) { + mPixelStack[top++] = mSuffix[code]; + code = mPrefix[code]; + } + first = ((int) mSuffix[code]) & 0xff; + // Add a new string to the string table, + if (available >= MaxStackSize) { + break; + } + mPixelStack[top++] = (byte) first; + mPrefix[available] = (short) old_code; + mSuffix[available] = (byte) first; + available++; + if (((available & code_mask) == 0) + && (available < MaxStackSize)) { + code_size++; + code_mask += available; + } + old_code = in_code; + } + + // Pop a pixel off the pixel stack. + top--; + mPixels[pi++] = mPixelStack[top]; + i++; + } + for (i = pi; i < npix; i++) { + mPixels[i] = 0; // clear missing pixels + } + } + + private boolean err() { + return mStatus != STATUS_PARSING; + } + + private void init() { + mStatus = STATUS_PARSING; + mFrameCount = 0; + mGifFrame = null; + mGct = null; + mLct = null; + } + + private int read() { + int curByte = 0; + try { + curByte = mIS.read(); + } catch (Exception e) { + mStatus = STATUS_FORMAT_ERROR; + } + return curByte; + } + + private int readBlock() { + mBlockSize = read(); + int n = 0; + if (mBlockSize > 0) { + try { + int count = 0; + while (n < mBlockSize) { + count = mIS.read(mBlock, n, mBlockSize - n); + if (count == -1) { + break; + } + n += count; + } + } catch (Exception e) { + e.printStackTrace(); + } + if (n < mBlockSize) { + mStatus = STATUS_FORMAT_ERROR; + } + } + return n; + } + + private int[] readColorTable(int ncolors) { + int nbytes = 3 * ncolors; + int[] tab = null; + byte[] c = new byte[nbytes]; + int n = 0; + try { + n = mIS.read(c); + } catch (Exception e) { + e.printStackTrace(); + } + if (n < nbytes) { + mStatus = STATUS_FORMAT_ERROR; + } else { + tab = new int[256]; // max size to avoid bounds checks + int i = 0; + int j = 0; + while (i < ncolors) { + int r = ((int) c[j++]) & 0xff; + int g = ((int) c[j++]) & 0xff; + int b = ((int) c[j++]) & 0xff; + tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b; + } + } + return tab; + } + + private void readContents() { + // read GIF file content blocks + boolean done = false; + while (!(done || err())) { + int code = read(); + switch (code) { + case 0x2C: // image separator + readImage(); + break; + case 0x21: // extension + code = read(); + switch (code) { + case 0xf9: // graphics control extension + readGraphicControlExt(); + break; + case 0xff: // application extension + readBlock(); + String app = ""; + for (int i = 0; i < 11; i++) { + app += (char) mBlock[i]; + } + if (app.equals("NETSCAPE2.0")) { + readNetscapeExt(); + } else { + skip(); // don't care + } + break; + default: // uninteresting extension + skip(); + } + break; + case 0x3b: // terminator + done = true; + break; + case 0x00: // bad byte, but keep going and see what happens + break; + default: + mStatus = STATUS_FORMAT_ERROR; + } + } + } + + private void readGraphicControlExt() { + read(); // block size + int packed = read(); // packed fields + mDispose = (packed & 0x1c) >> 2; // disposal method + if (mDispose == 0) { + mDispose = 1; // elect to keep old image if discretionary + } + mTransparency = (packed & 1) != 0; + mDelay = readShort() * 10; // delay in milliseconds + mTransIndex = read(); // transparent color index + read(); // block terminator + } + + private void readHeader() { + String id = ""; + for (int i = 0; i < 6; i++) { + id += (char) read(); + } + if (!id.startsWith("GIF")) { + mStatus = STATUS_FORMAT_ERROR; + return; + } + readLSD(); + if (mGctFlag && !err()) { + mGct = readColorTable(mGctSize); + mBgColor = mGct[mBgIndex]; + } + } + + private void readImage() { + mIx = readShort(); // (sub)image position & size + mIy = readShort(); + mIw = readShort(); + mIh = readShort(); + int packed = read(); + mLctFlag = (packed & 0x80) != 0; // 1 - local color table flag + mInterlace = (packed & 0x40) != 0; // 2 - interlace flag + // 3 - sort flag + // 4-5 - reserved + mLctSize = 2 << (packed & 7); // 6-8 - local color table size + if (mLctFlag) { + mLct = readColorTable(mLctSize); // read table + mAct = mLct; // make local table active + } else { + mAct = mGct; // make global table active + if (mBgIndex == mTransIndex) { + mBgColor = 0; + } + } + int save = 0; + if (mTransparency) { + save = mAct[mTransIndex]; + mAct[mTransIndex] = 0; // set transparent color if specified + } + if (mAct == null) { + mStatus = STATUS_FORMAT_ERROR; // no color table defined + } + if (err()) { + return; + } + try { + decodeImageData(); // decode pixel data + skip(); + if (err()) { + return; + } + mFrameCount++; + // create new image to receive frame data + mImage = Bitmap.createBitmap(mWidth, mHeight, Config.ARGB_4444); + // createImage(mWidth, mHeight); + setPixels(); // transfer pixel data to image + if (mGifFrame == null) { + mGifFrame = new GifFrame(mImage, mDelay, mDispose); + mCurrentFrame = mGifFrame; + } else { + GifFrame f = mGifFrame; + while (f.mNextFrame != null) { + f = f.mNextFrame; + } + f.mNextFrame = new GifFrame(mImage, mDelay, mDispose); + } + // frames.addElement(new GifFrame(image, delay)); // add image to + // frame + // list + if (mTransparency) { + mAct[mTransIndex] = save; + } + resetFrame(); + mGifAction.parseOk(true, mFrameCount); + } catch (OutOfMemoryError e) { + Log.e("GifDecoder", ">>> log : " + e.toString()); + e.printStackTrace(); + } + } + + private void readLSD() { + // logical screen size + mWidth = readShort(); + mHeight = readShort(); + // packed fields + int packed = read(); + mGctFlag = (packed & 0x80) != 0; // 1 : global color table flag + // 2-4 : color resolution + // 5 : gct sort flag + mGctSize = 2 << (packed & 7); // 6-8 : gct size + mBgIndex = read(); // background color index + mPixelAspect = read(); // pixel aspect ratio + } + + private void readNetscapeExt() { + do { + readBlock(); + if (mBlock[0] == 1) { + // loop count sub-block + int b1 = ((int) mBlock[1]) & 0xff; + int b2 = ((int) mBlock[2]) & 0xff; + mLoopCount = (b2 << 8) | b1; + } + } while ((mBlockSize > 0) && !err()); + } + + private int readShort() { + // read 16-bit value, LSB first + return read() | (read() << 8); + } + + private void resetFrame() { + mLastDispose = mDispose; + mLrx = mIx; + mLry = mIy; + mLrw = mIw; + mLrh = mIh; + mLastImage = mImage; + mLastBgColor = mBgColor; + mDispose = 0; + mTransparency = false; + mDelay = 0; + mLct = null; + } + + /** + * Skips variable length blocks up to and including next zero length block. + */ + private void skip() { + do { + readBlock(); + } while ((mBlockSize > 0) && !err()); + } + + private void freeFrame() { + GifFrame fg = mGifFrame; + while (fg != null) { + if (fg.mImage != null) { + fg.mImage.recycle(); + } + fg.mImage = null; + fg = null; + mGifFrame = mGifFrame.mNextFrame; + fg = mGifFrame; + } + } + + private void freeIS() { + if (mIS != null) { + try { + mIS.close(); + } catch (Exception ex) { + ex.printStackTrace(); + } + mIS = null; + } + mGifData = null; + } + + private void freeImage() { + if (mImage != null) { + mImage.recycle(); + mImage = null; + } + if (mLastImage != null) { + mLastImage.recycle(); + mLastImage = null; + } + } +} diff --git a/src/com/android/gallery3d/util/GifFrame.java b/src/com/android/gallery3d/util/GifFrame.java new file mode 100755 index 000000000..87d58a40d --- /dev/null +++ b/src/com/android/gallery3d/util/GifFrame.java @@ -0,0 +1,17 @@ +package com.android.gallery3d.util; + +import android.graphics.Bitmap; + +public class GifFrame { + + public Bitmap mImage; + public int mDelayInMs; //in milliseconds + public int mDispose; + public GifFrame mNextFrame = null; + + public GifFrame(Bitmap bitmap, int delay, int dispose) { + mImage = bitmap; + mDelayInMs = delay; + mDispose = dispose; + } +} diff --git a/src/com/android/gallery3d/util/MediaSetUtils.java b/src/com/android/gallery3d/util/MediaSetUtils.java index 043800561..35a4dff04 100644 --- a/src/com/android/gallery3d/util/MediaSetUtils.java +++ b/src/com/android/gallery3d/util/MediaSetUtils.java @@ -28,9 +28,12 @@ import java.util.Comparator; public class MediaSetUtils { public static final Comparator<MediaSet> NAME_COMPARATOR = new NameComparator(); - public static final int CAMERA_BUCKET_ID = GalleryUtils.getBucketId( - Environment.getExternalStorageDirectory().toString() + "/" - + BucketNames.CAMERA); + private static String mRoot = Environment.getExternalStorageDirectory().toString(); + + public static void setRoot(String root) { + mRoot = root; + } + public static final int DOWNLOAD_BUCKET_ID = GalleryUtils.getBucketId( Environment.getExternalStorageDirectory().toString() + "/" + BucketNames.DOWNLOAD); @@ -44,14 +47,14 @@ public class MediaSetUtils { Environment.getExternalStorageDirectory().toString() + "/" + BucketNames.SCREENSHOTS); - private static final Path[] CAMERA_PATHS = { - Path.fromString("/local/all/" + CAMERA_BUCKET_ID), - Path.fromString("/local/image/" + CAMERA_BUCKET_ID), - Path.fromString("/local/video/" + CAMERA_BUCKET_ID)}; + public static int getCameraBucketId() { + return GalleryUtils.getBucketId(mRoot + "/" + BucketNames.CAMERA); + } public static boolean isCameraSource(Path path) { - return CAMERA_PATHS[0] == path || CAMERA_PATHS[1] == path - || CAMERA_PATHS[2] == path; + return path.equalsIgnoreCase("/local/all/" + getCameraBucketId()) + || path.equalsIgnoreCase("/local/image/" + getCameraBucketId()) + || path.equalsIgnoreCase("/local/video/" + getCameraBucketId()); } // Sort MediaSets by name diff --git a/src/com/android/gallery3d/util/ViewGifImage.java b/src/com/android/gallery3d/util/ViewGifImage.java new file mode 100755 index 000000000..cdd509280 --- /dev/null +++ b/src/com/android/gallery3d/util/ViewGifImage.java @@ -0,0 +1,67 @@ +package com.android.gallery3d.util; + +import com.android.gallery3d.R; + +import android.app.Activity; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.DisplayMetrics; +import android.view.ViewGroup.LayoutParams; +import android.widget.ImageView; +import android.widget.LinearLayout; + +public class ViewGifImage extends Activity { + private static final String TAG = "ViewGifImage"; + public static final String VIEW_GIF_ACTION = "com.android.gallery3d.VIEW_GIF"; + + public static DisplayMetrics mDM; + + private ImageView mGifView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.view_gif_image); + mDM = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(mDM); + if (getIntent().getAction() != null + && getIntent().getAction().equals(VIEW_GIF_ACTION)) { + Uri gifUri = getIntent().getData(); + showGifPicture(gifUri); + } + } + + @Override + protected void onStop() { + super.onStop(); + finish(); + } + + @Override + protected void onDestroy() { + if (mGifView != null && mGifView instanceof GIFView) { + ((GIFView) mGifView).freeMemory(); + mGifView = null; + } + super.onDestroy(); + } + + private void showGifPicture(Uri uri) { + mGifView = new GIFView(this); + ((LinearLayout) findViewById(R.id.image_absoluteLayout)).addView(mGifView, + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + if (((GIFView) mGifView).setDrawable(uri)) return; + + finish(); + + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + getWindowManager().getDefaultDisplay().getMetrics(mDM); + super.onConfigurationChanged(newConfig); + } +} diff --git a/src/com/thundersoft/hz/selfportrait/detect/FaceDetect.java b/src/com/thundersoft/hz/selfportrait/detect/FaceDetect.java new file mode 100644 index 000000000..a5c7fb043 --- /dev/null +++ b/src/com/thundersoft/hz/selfportrait/detect/FaceDetect.java @@ -0,0 +1,79 @@ +/* +* Copyright (C) 2014,2015 Thundersoft Corporation +* All rights Reserved +* +* 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.thundersoft.hz.selfportrait.detect; + +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.util.Log; + +public class FaceDetect { + private static final String TAG = "FaceDetect"; + + private int mHandle = 0; + + static { + try { + System.loadLibrary("ts_detected_face_jni"); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + Log.e(TAG, "ts_detected_face_jni library not found!"); + } + } + + /** + * initialize method,MUST called at first time. + */ + public void initialize() { + mHandle = native_create(); + } + + /** + * uninitialize method,MUST called at last time. + */ + + public void uninitialize() { + native_destroy(mHandle); + } + + /** + * dectectFeatures method,MUST called after initialize method and before + * uninitialize method. + * + * @param bmp, Android Bitmap instance,MUST not null. + * @return FaceInfo array if success, otherwise return null. + */ + public FaceInfo[] dectectFeatures(Bitmap bmp) { + int count = native_detect(mHandle, bmp); + if (count < 1) { + return null; + } + FaceInfo[] res = new FaceInfo[count]; + for (int i = 0; i < count; i++) { + FaceInfo face = new FaceInfo(); + native_face_info(mHandle, i, face.face, face.eye1, face.eye2, face.mouth); + res[i] = face; + } + return res; + } + + private static native int native_create(); + private static native void native_destroy(int handle); + private static native int native_detect(int handle, Bitmap bmp); + private static native int native_face_info(int handle, int index, Rect face, Rect eye1, + Rect eye2, Rect mouth); +} diff --git a/src/com/thundersoft/hz/selfportrait/detect/FaceInfo.java b/src/com/thundersoft/hz/selfportrait/detect/FaceInfo.java new file mode 100644 index 000000000..eee51f71b --- /dev/null +++ b/src/com/thundersoft/hz/selfportrait/detect/FaceInfo.java @@ -0,0 +1,39 @@ +/* +* Copyright (C) 2014,2015 Thundersoft Corporation +* All rights Reserved +* +* 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.thundersoft.hz.selfportrait.detect; + +import android.graphics.Rect; + +public class FaceInfo { + /** + * face rectangle + */ + public Rect face = new Rect(); + /** + * left eye rectangle + */ + public Rect eye1 = new Rect(); + /** + * right eye rectangle + */ + public Rect eye2 = new Rect(); + /** + * mount rectangle + */ + public Rect mouth = new Rect(); +} diff --git a/src/com/thundersoft/hz/selfportrait/makeup/engine/MakeupEngine.java b/src/com/thundersoft/hz/selfportrait/makeup/engine/MakeupEngine.java new file mode 100644 index 000000000..8d58bcfef --- /dev/null +++ b/src/com/thundersoft/hz/selfportrait/makeup/engine/MakeupEngine.java @@ -0,0 +1,87 @@ +/* +* Copyright (C) 2014,2015 Thundersoft Corporation +* All rights Reserved +* +* 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.thundersoft.hz.selfportrait.makeup.engine; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.util.Log; + +public class MakeupEngine { + static { + try { + System.loadLibrary("ts_face_beautify_jni"); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + Log.e(MakeupEngine.class.getName(), "ts_face_beautify_jni library not found!"); + } + } + + private static MakeupEngine mInstance; + + private MakeupEngine() { + + } + + public static MakeupEngine getMakeupObj() { + if(mInstance == null) { + mInstance = new MakeupEngine(); + } + + return mInstance; + } + + private Context mContext; + + public void setContext(Context context) { + mContext = context; + } + + public Context getContext() { + return mContext; + } + + /** + * FUNCTION: doProcessBeautify + * Do process face region clean and whiten. + * @param inBitmap, the Bitmap instance which have face region, MUST not null. + * @param outBitmap, the result of process, MUST not null. + * @param frameWidth,frameHeight, the size of inBitmap. + * @param faceRect, the face region in inBitmap. + * @param cleanLevel, the level of clean.(0-100) + * @param whiteLevel, the level of white.(0-100) + */ + public static native boolean doProcessBeautify(Bitmap inBitmap, Bitmap outBitmap, int frameWidth, int frameHeight, + Rect faceRect, int cleanLevel, int beautyLevel); + + /** + * FUNCTION: doWarpFace + * Do process face region warp and big eye. + * @param inBitmap, the Bitmap instance which have face region, MUST not null. + * @param outBitmap, the result of process, MUST not null. + * @param frameWidth, the size of inBitmap. + * @param frameHeight, the size of inBitmap. + * @param leftEye, the left eye rectangle + * @param rightEye, the right eye rectangle + * @param mouth, the mouth rectangle + * @param bigEyeLevel, the level of big eye.(0-100) + * @param trimFaceLevel, the level of trim face.(0-100) + */ + public static native boolean doWarpFace(Bitmap inBitmap, Bitmap outBitmap, int frameWidth, int frameHeight, + Rect leftEye, Rect rightEye, Rect mouth, int bigEyeLevel, int trimFaceLevel); +} diff --git a/src/org/codeaurora/gallery3d/ext/ActivityHooker.java b/src/org/codeaurora/gallery3d/ext/ActivityHooker.java new file mode 100644 index 000000000..65761ff23 --- /dev/null +++ b/src/org/codeaurora/gallery3d/ext/ActivityHooker.java @@ -0,0 +1,96 @@ +package org.codeaurora.gallery3d.ext; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; + +/** + * Default implemention class of IActivityHooker. + */ +public class ActivityHooker implements IActivityHooker { + + private static final int MENU_MAX_NUMBER = 100; + private static int sMenuId = 1; + private int mMenuId; + private static Object sMenuLock = new Object(); + private Activity mContext; + private Intent mIntent; + + public ActivityHooker() { + synchronized (sMenuLock) { + sMenuId++; + mMenuId = sMenuId * MENU_MAX_NUMBER; + } + } + + @Override + public int getMenuActivityId(int id) { + return mMenuId + id; + }; + + @Override + public int getMenuOriginalId(int id) { + return id - mMenuId; + } + + @Override + public void init(Activity context, Intent intent) { + mContext = context; + mIntent = intent; + } + + @Override + public Activity getContext() { + return mContext; + } + + @Override + public Intent getIntent() { + return mIntent; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + } + + @Override + public void onStart() { + } + + @Override + public void onResume() { + } + + @Override + public void onPause() { + } + + @Override + public void onStop() { + } + + @Override + public void onDestroy() { + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + return false; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return false; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + return false; + } + + @Override + public void setParameter(String key, Object value) { + } +} diff --git a/src/org/codeaurora/gallery3d/ext/ActivityHookerGroup.java b/src/org/codeaurora/gallery3d/ext/ActivityHookerGroup.java new file mode 100644 index 000000000..4bf8616e7 --- /dev/null +++ b/src/org/codeaurora/gallery3d/ext/ActivityHookerGroup.java @@ -0,0 +1,150 @@ +package org.codeaurora.gallery3d.ext; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; + +import java.util.ArrayList; + +/** + * The composite pattern class. It will deliver every action to its leaf + * hookers. + */ +public class ActivityHookerGroup extends ActivityHooker { + private ArrayList<IActivityHooker> mHooks = new ArrayList<IActivityHooker>(); + + /** + * Add hooker to current group. + * + * @param hooker + * @return + */ + public boolean addHooker(IActivityHooker hooker) { + return mHooks.add(hooker); + } + + /** + * Remove hooker from current group. + * + * @param hooker + * @return + */ + public boolean removeHooker(IActivityHooker hooker) { + return mHooks.remove(hooker); + } + + /** + * Get hooker of requested location. + * + * @param index + * @return + */ + public IActivityHooker getHooker(int index) { + return mHooks.get(index); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + for (IActivityHooker hook : mHooks) { + hook.onCreate(savedInstanceState); + } + } + + @Override + public void onStart() { + super.onStart(); + for (IActivityHooker hook : mHooks) { + hook.onStart(); + } + } + + @Override + public void onResume() { + super.onResume(); + for (IActivityHooker hook : mHooks) { + hook.onResume(); + } + } + + @Override + public void onPause() { + super.onPause(); + for (IActivityHooker hook : mHooks) { + hook.onPause(); + } + } + + @Override + public void onStop() { + super.onStop(); + for (IActivityHooker hook : mHooks) { + hook.onStop(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + for (IActivityHooker hook : mHooks) { + hook.onDestroy(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + boolean handle = false; + for (IActivityHooker hook : mHooks) { + boolean one = hook.onCreateOptionsMenu(menu); + if (!handle) { + handle = one; + } + } + return handle; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + boolean handle = false; + for (IActivityHooker hook : mHooks) { + boolean one = hook.onPrepareOptionsMenu(menu); + if (!handle) { + handle = one; + } + } + return handle; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + super.onOptionsItemSelected(item); + boolean handle = false; + for (IActivityHooker hook : mHooks) { + boolean one = hook.onOptionsItemSelected(item); + if (!handle) { + handle = one; + } + } + return handle; + } + + @Override + public void setParameter(String key, Object value) { + super.setParameter(key, value); + for (IActivityHooker hook : mHooks) { + hook.setParameter(key, value); + } + } + + @Override + public void init(Activity context, Intent intent) { + super.init(context, intent); + for (IActivityHooker hook : mHooks) { + hook.init(context, intent); + } + } +} diff --git a/src/org/codeaurora/gallery3d/ext/IActivityHooker.java b/src/org/codeaurora/gallery3d/ext/IActivityHooker.java new file mode 100644 index 000000000..a83799626 --- /dev/null +++ b/src/org/codeaurora/gallery3d/ext/IActivityHooker.java @@ -0,0 +1,128 @@ +package org.codeaurora.gallery3d.ext; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; + +/** + * Activity action hooker class. Host app's activity will call this hooker's + * functions in its lifecycle. For example: + * HostActivity.onCreate()-->hooker.onCreate(). But void init(Activity context, + * Intent intent) will be called before other functions. <br/> + * IActivityHooker objects may show menus, but we should give a unique menu id + * to every menus. Hooker can call getMenuActivityId(int) to get a global unique + * menu id to be used in menu.add(), and can call getMenuOriginalId(int) to get + * the original menu id. the example: class Hooker implements IActivityHooker { + * private static final int MENU_EXAMPLE = 1; + * + * @Override public boolean onCreateOptionsMenu(Menu menu) { + * super.onCreateOptionsMenu(menu); menu.add(0, + * getMenuActivityId(MENU_EXAMPLE), 0, android.R.string.ok); return + * true; } + * @Override public boolean onOptionsItemSelected(MenuItem item) { + * switch(getMenuOriginalId(item.getItemId())) { case MENU_EXAMPLE: + * //do something return true; default: return false; } } } + */ +public interface IActivityHooker { + /** + * Will be called in Host Activity.onCreate(Bundle savedInstanceState) + * @param savedInstanceState + */ + void onCreate(Bundle savedInstanceState); + /** + * Will be called in Host Activity.onStart() + */ + void onStart(); + /** + * Will be called in Host Activity.onStop() + */ + void onStop(); + /** + * Will be called in Host Activity.onPause() + */ + void onPause(); + /** + * Will be called in Host Activity.onResume() + */ + void onResume(); + /** + * Will be called in Host Activity.onDestroy() + */ + void onDestroy(); + /** + * Will be called in Host Activity.onCreateOptionsMenu(Menu menu) + * @param menu + * @return + */ + /** + * Will be called in Host Activity.onCreateOptionsMenu(Menu menu) + * + * @param menu + * @return + */ + boolean onCreateOptionsMenu(Menu menu); + + /** + * Will be called in Host Activity.onPrepareOptionsMenu(Menu menu) + * + * @param menu + * @return + */ + boolean onPrepareOptionsMenu(Menu menu); + + /** + * Will be called in Host Activity.onOptionsItemSelected(MenuItem item) + * + * @param item + * @return + */ + boolean onOptionsItemSelected(MenuItem item); + + /** + * Should be called before any other functions. + * + * @param context + * @param intent + */ + void init(Activity context, Intent intent); + + /** + * @return return activity set by init(Activity context, Intent intent) + */ + Activity getContext(); + + /** + * @return return intent set by init(Activity context, Intent intent) + */ + Intent getIntent(); + + /** + * IActivityHooker objects may show menus, but we should give a unique menu + * id to every menus. Hooker can call this function to get a global unique + * menu id to be used in menu.add() + * + * @param id + * @return + */ + int getMenuActivityId(int id); + + /** + * When onOptionsItemSelected is called, we can get menu's id from + * parameter. You can get the original menu id by calling this function. + * + * @param id + * @return + */ + int getMenuOriginalId(int id); + + /** + * Host activity will call this function to set parameter to hooker + * activity. + * + * @param key + * @param value + */ + void setParameter(String key, Object value); +} diff --git a/src/org/codeaurora/gallery3d/ext/IContrllerOverlayExt.java b/src/org/codeaurora/gallery3d/ext/IContrllerOverlayExt.java new file mode 100644 index 000000000..da50cdffc --- /dev/null +++ b/src/org/codeaurora/gallery3d/ext/IContrllerOverlayExt.java @@ -0,0 +1,51 @@ +package org.codeaurora.gallery3d.ext; +/** + * Controller overlay extension interface. + */ +public interface IContrllerOverlayExt { + /** + * Show buffering state. + * @param fullBuffer + * @param percent + */ + void showBuffering(boolean fullBuffer, int percent); + /** + * Clear buffering state. + */ + void clearBuffering(); + /** + * Show re-connecting state. + * @param times + */ + void showReconnecting(int times); + /** + * Show re-connecting error for connecting fail error. + */ + void showReconnectingError(); + /** + * Show playing info or not. + * @param liveStreaming true means showing playing info, otherwise doesn't show playing info. + */ + void setPlayingInfo(boolean liveStreaming); + /** + * Indicates whether current video can be paused or not. + * @param canPause + */ + void setCanPause(boolean canPause); + /** + * Indicates whether thumb can be scrubbed or not. + * @param enable + */ + void setCanScrubbing(boolean enable); + /** + * Always show bottmon panel or not. + * @param alwaysShow + * @param foreShow + */ + void setBottomPanel(boolean alwaysShow, boolean foreShow); + /** + * Is playing end or not. + * @return + */ + boolean isPlayingEnd(); +} diff --git a/src/org/codeaurora/gallery3d/ext/IMovieItem.java b/src/org/codeaurora/gallery3d/ext/IMovieItem.java new file mode 100644 index 000000000..dece4e803 --- /dev/null +++ b/src/org/codeaurora/gallery3d/ext/IMovieItem.java @@ -0,0 +1,66 @@ +package org.codeaurora.gallery3d.ext; + +import android.net.Uri; + +/** + * Movie info class + */ +public interface IMovieItem { + /** + * @return movie Uri, it's may be not the original Uri. + */ + Uri getUri(); + + /** + * @return MIME type of video + */ + String getMimeType(); + + /** + * @return title of video + */ + String getTitle(); + + /** + * @return whether error occured or not. + */ + boolean getError(); + + /** + * set title of video + * + * @param title + */ + void setTitle(String title); + + /** + * set video Uri + * + * @param uri + */ + void setUri(Uri uri); + + /** + * Set MIME type of video + * + * @param mimeType + */ + void setMimeType(String mimeType); + + /** + * Set error occured flag + */ + void setError(); + + /** + * @return return original Uri of video. + */ + Uri getOriginalUri(); + + /** + * Set video original Uri. + * + * @param uri + */ + void setOriginalUri(Uri uri); +} diff --git a/src/org/codeaurora/gallery3d/ext/IMovieList.java b/src/org/codeaurora/gallery3d/ext/IMovieList.java new file mode 100644 index 000000000..404d24c41 --- /dev/null +++ b/src/org/codeaurora/gallery3d/ext/IMovieList.java @@ -0,0 +1,46 @@ +package org.codeaurora.gallery3d.ext; +/** + * Movie list extension interface + */ +public interface IMovieList { + /** + * Add movie item to list. + * @param item + */ + void add(IMovieItem item); + /** + * Get the item index of list + * @param item + * @return + */ + int index(IMovieItem item); + /** + * + * @return list size + */ + int size(); + /** + * + * @param item + * @return next item of current item + */ + IMovieItem getNext(IMovieItem item); + /** + * + * @param item + * @return previous item of current item + */ + IMovieItem getPrevious(IMovieItem item); + /** + * Is first item in list + * @param item + * @return + */ + boolean isFirst(IMovieItem item); + /** + * Is last item in list. + * @param item + * @return + */ + boolean isLast(IMovieItem item); +}
\ No newline at end of file diff --git a/src/org/codeaurora/gallery3d/ext/IMovieListLoader.java b/src/org/codeaurora/gallery3d/ext/IMovieListLoader.java new file mode 100644 index 000000000..fe5999858 --- /dev/null +++ b/src/org/codeaurora/gallery3d/ext/IMovieListLoader.java @@ -0,0 +1,51 @@ +package org.codeaurora.gallery3d.ext; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; + +public interface IMovieListLoader { + /** + * Load all video list or not.[boolean] + * "yes" means load all videos in all storages. + * "false" means load videos located in current video's folder. + */ + String EXTRA_ALL_VIDEO_FOLDER = "org.codeaurora.intent.extra.ALL_VIDEO_FOLDER"; + /** + * Video list order by column name.[String] + */ + String EXTRA_ORDERBY = "org.codeaurora.intent.extra.VIDEO_LIST_ORDERBY"; + /** + * Enable video list or not.[boolean] + */ + String EXTRA_ENABLE_VIDEO_LIST = "org.codeaurora.intent.extra.ENABLE_VIDEO_LIST"; + /** + * Loader listener interface + */ + public interface LoaderListener { + /** + * Will be called after movie list loaded. + * @param movieList + */ + void onListLoaded(IMovieList movieList); + } + /** + * Build the movie list from current item. + * @param context + * @param intent + * @param l + * @param item + */ + void fillVideoList(Activity context, Intent intent, LoaderListener l, IMovieItem item); + /** + * enable video list or not. + * @param intent + * @return + */ + boolean isEnabledVideoList(Intent intent); + /** + * Cancel current loading process. + */ + void cancelList(); + +} diff --git a/src/org/codeaurora/gallery3d/ext/IMoviePlayer.java b/src/org/codeaurora/gallery3d/ext/IMoviePlayer.java new file mode 100644 index 000000000..32d400b0d --- /dev/null +++ b/src/org/codeaurora/gallery3d/ext/IMoviePlayer.java @@ -0,0 +1,42 @@ +package org.codeaurora.gallery3d.ext; + +public interface IMoviePlayer { + + /** + * add new bookmark Uri. + */ + void addBookmark(); + + /** + * Loop current video. + * + * @param loop + */ + void setLoop(boolean loop); + + /** + * Loop current video or not + * + * @return + */ + boolean getLoop(); + + /** + * Can stop current video or not. + * + * @return + */ + boolean canStop(); + + /** + * Stop current video. + */ + void stopVideo(); + + /** + * start current item and stop playing video. + * + * @param item + */ + void startNextVideo(IMovieItem item); +} diff --git a/src/org/codeaurora/gallery3d/ext/MovieItem.java b/src/org/codeaurora/gallery3d/ext/MovieItem.java new file mode 100644 index 000000000..56afdda4b --- /dev/null +++ b/src/org/codeaurora/gallery3d/ext/MovieItem.java @@ -0,0 +1,115 @@ +package org.codeaurora.gallery3d.ext; + +import android.net.Uri; +import android.provider.MediaStore; + +public class MovieItem implements IMovieItem { + private static final String TAG = "MovieItem"; + private static final boolean LOG = false; + + private Uri mUri; + private String mMimeType; + private String mTitle; + private boolean mError; + // private int mStereoType; + private Uri mOriginal; + + private static final int STREO_TYPE_2D = 1; + + public MovieItem(Uri uri, String mimeType, String title, int stereoType) { + mUri = uri; + mMimeType = mimeType; + mTitle = title; + // mStereoType = stereoType; + mOriginal = uri; + } + + public MovieItem(String uri, String mimeType, String title, int stereoType) { + this(Uri.parse(uri), mimeType, title, stereoType); + } + + public MovieItem(Uri uri, String mimeType, String title) { + this(uri, mimeType, title, STREO_TYPE_2D); + } + + public MovieItem(String uri, String mimeType, String title) { + this(Uri.parse(uri), mimeType, title); + } + + @Override + public Uri getUri() { + return mUri; + } + + @Override + public String getMimeType() { + return mMimeType; + } + + @Override + public String getTitle() { + return mTitle; + } + + @Override + public boolean getError() { + return mError; + } + + // @Override + // public int getStereoType() { + // return mStereoType; + // } + + public void setTitle(String title) { + mTitle = title; + } + + @Override + public void setUri(Uri uri) { + mUri = uri; + } + + @Override + public void setMimeType(String mimeType) { + mMimeType = mimeType; + } + + // @Override + // public void setStereoType(int stereoType) { + // mStereoType = stereoType; + // } + + @Override + public void setError() { + mError = true; + } + + @Override + public Uri getOriginalUri() { + return mOriginal; + } + + @Override + public void setOriginalUri(Uri uri) { + mOriginal = uri; + } + + @Override + public String toString() { + return new StringBuilder().append("MovieItem(uri=") + .append(mUri) + .append(", mime=") + .append(mMimeType) + .append(", title=") + .append(mTitle) + .append(", error=") + .append(mError) + // .append(", support3D=") + // .append(mStereoType) + .append(", mOriginal=") + .append(mOriginal) + .append(")") + .toString(); + } +} diff --git a/src/org/codeaurora/gallery3d/ext/MovieList.java b/src/org/codeaurora/gallery3d/ext/MovieList.java new file mode 100644 index 000000000..ecb7f0db3 --- /dev/null +++ b/src/org/codeaurora/gallery3d/ext/MovieList.java @@ -0,0 +1,72 @@ +package org.codeaurora.gallery3d.ext; + +import android.util.Log; + +import java.util.ArrayList; + +public class MovieList implements IMovieList { + private static final String TAG = "MovieList"; + private static final boolean LOG = false; + + private final ArrayList<IMovieItem> mItems = new ArrayList<IMovieItem>(); + private static final int UNKNOWN = -1; + + @Override + public void add(IMovieItem item) { + if (LOG) { + Log.v(TAG, "add(" + item + ")"); + } + mItems.add(item); + } + + @Override + public int index(IMovieItem item) { + int find = UNKNOWN; + int size = mItems.size(); + for (int i = 0; i < size; i++) { + if (item == mItems.get(i)) { + find = i; + break; + } + } + if (LOG) { + Log.v(TAG, "index(" + item + ") return " + find); + } + return find; + } + + @Override + public int size() { + return mItems.size(); + } + + @Override + public IMovieItem getNext(IMovieItem item) { + IMovieItem next = null; + int find = index(item); + if (find >= 0 && find < size() - 1) { + next = mItems.get(++find); + } + return next; + } + + @Override + public IMovieItem getPrevious(IMovieItem item) { + IMovieItem prev = null; + int find = index(item); + if (find > 0 && find < size()) { + prev = mItems.get(--find); + } + return prev; + } + + @Override + public boolean isFirst(IMovieItem item) { + return getPrevious(item) == null; + } + + @Override + public boolean isLast(IMovieItem item) { + return getNext(item) == null; + } +} diff --git a/src/org/codeaurora/gallery3d/ext/MovieListLoader.java b/src/org/codeaurora/gallery3d/ext/MovieListLoader.java new file mode 100644 index 000000000..237d7e138 --- /dev/null +++ b/src/org/codeaurora/gallery3d/ext/MovieListLoader.java @@ -0,0 +1,274 @@ +package org.codeaurora.gallery3d.ext; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; +import android.net.Uri; +import android.os.AsyncTask; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.util.Log; + +import java.io.File; +import java.util.ArrayList; + +/** + * Movie list loader class. It will load videos from MediaProvider database. + * If MoviePlayer starting activity doesn't set any thing, default OrderBy will be used. + * Default OrderBy: MediaStore.Video.Media.DATE_TAKEN + " DESC, " + MediaStore.Video.Media._ID + " DESC "; + */ +public class MovieListLoader implements IMovieListLoader { + private static final String TAG = "MovieListLoader"; + private static final boolean LOG = false; + + private MovieListFetcherTask mListTask; + + @Override + public void fillVideoList(Activity activity, Intent intent, final LoaderListener l, + IMovieItem currentMovieItem) { + + // determine if a video playlist has been passed in through the intent + // if a playlist does exist, use that + ArrayList<Uri> uris = intent.getParcelableArrayListExtra("EXTRA_FILE_LIST"); + if (uris != null) { + final MovieList movieList = new MovieList(); + ContentResolver cr = activity.getContentResolver(); + + for(Uri uri : uris) { + // add currentMovieItem in its proper place in the video playlist + // 'Next' and 'Previous' functionality in MovieListHooker is dependent on reference + // matching currentMovieItem + if (currentMovieItem.getOriginalUri().equals(uri)) { + movieList.add(currentMovieItem); + continue; + } + + File videoFile = new File(uri.getPath()); + movieList.add(new MovieItem(uri, cr.getType(uri), videoFile.getName())); + } + + // notify callback on main thread + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + l.onListLoaded(movieList); + } + }); + + return; + } + + // proceed with creating a playlist if one isn't found + boolean fetechAll = false; + if (intent.hasExtra(EXTRA_ALL_VIDEO_FOLDER)) { + fetechAll = intent.getBooleanExtra(EXTRA_ALL_VIDEO_FOLDER, false); + } + //default order by + String orderBy = MediaStore.Video.Media.DATE_TAKEN + " DESC, " + MediaStore.Video.Media._ID + " DESC "; + if (intent.hasExtra(EXTRA_ORDERBY)) { + orderBy = intent.getStringExtra(EXTRA_ORDERBY); + } + cancelList(); + mListTask = new MovieListFetcherTask(activity, fetechAll, l, orderBy); + mListTask.execute(currentMovieItem); + if (LOG) { + Log.v(TAG, "fillVideoList() fetechAll=" + fetechAll + ", orderBy=" + orderBy); + } + } + + @Override + public boolean isEnabledVideoList(Intent intent) { + boolean enable = true; + if (intent != null && intent.hasExtra(EXTRA_ENABLE_VIDEO_LIST)) { + enable = intent.getBooleanExtra(EXTRA_ENABLE_VIDEO_LIST, true); + } + if (LOG) { + Log.v(TAG, "isEnabledVideoList() return " + enable); + } + return enable; + } + + @Override + public void cancelList() { + if (mListTask != null) { + mListTask.cancel(true); + } + } + + private class MovieListFetcherTask extends AsyncTask<IMovieItem, Void, IMovieList> { + private static final String TAG = "MovieListFetcherTask"; + private static final boolean LOG = false; + + // TODO comments by sunlei +// public static final String COLUMN_STEREO_TYPE = MediaStore.Video.Media.STEREO_TYPE; +// public static final String COLUMN_STEREO_TYPE = "STEREO_TYPE"; + + private final ContentResolver mCr; + private final LoaderListener mFetecherListener; + private final boolean mFetechAll; + private final String mOrderBy; + + public MovieListFetcherTask(Context context, boolean fetechAll, LoaderListener l, String orderBy) { + mCr = context.getContentResolver(); + mFetecherListener = l; + mFetechAll = fetechAll; + mOrderBy = orderBy; + if (LOG) { + Log.v(TAG, "MovieListFetcherTask() fetechAll=" + fetechAll + ", orderBy=" + orderBy); + } + } + + @Override + protected void onPostExecute(IMovieList params) { + if (LOG) { + Log.v(TAG, "onPostExecute() isCancelled()=" + isCancelled()); + } + if (isCancelled()) { + return; + } + if (mFetecherListener != null) { + mFetecherListener.onListLoaded(params); + } + } + + @Override + protected IMovieList doInBackground(IMovieItem... params) { + if (LOG) { + Log.v(TAG, "doInBackground() begin"); + } + if (params[0] == null) { + return null; + } + IMovieList movieList = null; + Uri uri = params[0].getUri(); + String mime = params[0].getMimeType(); + if (mFetechAll) { //get all list + if (MovieUtils.isLocalFile(uri, mime)) { + String uristr = String.valueOf(uri); + if (uristr.toLowerCase().startsWith("content://media")) { + //from gallery, gallery3D, videoplayer + long curId = Long.parseLong(uri.getPathSegments().get(3)); + movieList = fillUriList(null, null, curId, params[0]); + } else if (uristr.toLowerCase().startsWith("file://")) { + long curId = getCursorId(uri); + movieList = fillUriList(null, null, curId, params[0]); + } + } + } else { //get current list + if (MovieUtils.isLocalFile(uri, mime)) { + String uristr = String.valueOf(uri); + if (uristr.toLowerCase().startsWith("content://media")) { + Cursor cursor = mCr.query(uri, + new String[]{MediaStore.Video.Media.BUCKET_ID}, + null, null, null); + long bucketId = -1; + if (cursor != null) { + if (cursor.moveToFirst()) { + bucketId = cursor.getLong(0); + } + cursor.close(); + } + try { + long curId = Long.parseLong(uri.getPathSegments().get(3)); + movieList = fillUriList(MediaStore.Video.Media.BUCKET_ID + "=? ", + new String[]{String.valueOf(bucketId)}, curId, params[0]); + } catch (Exception e) { + Log.e(TAG, "Exception while creating movie list. " + e); + return null; + } + } else if (uristr.toLowerCase().startsWith("file://")) { + String data = Uri.decode(uri.toString()); + data = data.replaceAll("'", "''"); + String where = "_data LIKE '%" + data.replaceFirst("file:///", "") + "'"; + Cursor cursor = mCr.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + new String[]{"_id", MediaStore.Video.Media.BUCKET_ID}, + where, null, null); + long bucketId = -1; + long curId = -1; + if (cursor != null) { + if (cursor.moveToFirst()) { + curId = cursor.getLong(0); + bucketId = cursor.getLong(1); + } + cursor.close(); + } + movieList = fillUriList(MediaStore.Video.Media.BUCKET_ID + "=? ", + new String[]{String.valueOf(bucketId)}, curId, params[0]); + } + } + } + if (LOG) { + Log.v(TAG, "doInBackground() done return " + movieList); + } + return movieList; + } + + private IMovieList fillUriList(String where, String[] whereArgs, long curId, IMovieItem current) { + IMovieList movieList = null; + Cursor cursor = null; + try { + cursor = mCr.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + new String[]{"_id", "mime_type", OpenableColumns.DISPLAY_NAME}, + where, + whereArgs, + mOrderBy); + boolean find = false; + if (cursor != null && cursor.getCount() > 0) { + movieList = new MovieList(); + while (cursor.moveToNext()) { + long id = cursor.getLong(0); + if (!find && id == curId) { + find = true; + movieList.add(current); + continue; + } + Uri uri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id); + String mimeType = cursor.getString(1); + String title = cursor.getString(2); + + movieList.add(new MovieItem(uri, mimeType, title)); + } + } + } catch (final SQLiteException e) { + e.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + if (LOG) { + Log.v(TAG, "fillUriList() cursor=" + cursor + ", return " + movieList); + } + return movieList; + } + + private long getCursorId(Uri uri) { + long curId = -1; + Cursor cursor = null; + String data = Uri.decode(uri.toString()); + data = data.replaceAll("'", "''"); + String where = "_data LIKE '%" + data.replaceFirst("file:///", "") + "'"; + try { + cursor = mCr.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + new String[] { + "_id" + }, where, null, null); + + if (cursor != null && cursor.moveToFirst()) { + curId = cursor.getLong(0); + } + } catch (final SQLiteException e) { + e.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return curId; + } + } +} diff --git a/src/org/codeaurora/gallery3d/ext/MovieUtils.java b/src/org/codeaurora/gallery3d/ext/MovieUtils.java new file mode 100644 index 000000000..4bc70a39f --- /dev/null +++ b/src/org/codeaurora/gallery3d/ext/MovieUtils.java @@ -0,0 +1,98 @@ +package org.codeaurora.gallery3d.ext; + +import android.net.Uri; +import android.util.Log; + +import java.util.Locale; + +/** + * Util class for Movie functions. * + */ +public class MovieUtils { + private static final String TAG = "MovieUtils"; + private static final boolean LOG = false; + + private MovieUtils() { + } + + /** + * Whether current video(Uri) is RTSP streaming or not. + * + * @param uri + * @param mimeType + * @return + */ + public static boolean isRtspStreaming(Uri uri, String mimeType) { + boolean rtsp = false; + if (uri != null) { + if ("rtsp".equalsIgnoreCase(uri.getScheme())) { + rtsp = true; + } + } + if (LOG) { + Log.v(TAG, "isRtspStreaming(" + uri + ", " + mimeType + ") return " + rtsp); + } + return rtsp; + } + + /** + * Whether current video(Uri) is HTTP streaming or not. + * + * @param uri + * @param mimeType + * @return + */ + public static boolean isHttpStreaming(Uri uri, String mimeType) { + boolean http = false; + if (uri != null) { + if ("http".equalsIgnoreCase(uri.getScheme())) { + http = true; + } else if ("https".equalsIgnoreCase(uri.getScheme())) { + http = true; + } + } + if (LOG) { + Log.v(TAG, "isHttpStreaming(" + uri + ", " + mimeType + ") return " + http); + } + return http; + } + + /** + * Whether current video(Uri) is live streaming or not. + * + * @param uri + * @param mimeType + * @return + */ + public static boolean isSdpStreaming(Uri uri, String mimeType) { + boolean sdp = false; + if (uri != null) { + if ("application/sdp".equals(mimeType)) { + sdp = true; + } else if (uri.toString().toLowerCase(Locale.ENGLISH).endsWith(".sdp")) { + sdp = true; + } + } + if (LOG) { + Log.v(TAG, "isSdpStreaming(" + uri + ", " + mimeType + ") return " + sdp); + } + return sdp; + } + + /** + * Whether current video(Uri) is local file or not. + * + * @param uri + * @param mimeType + * @return + */ + public static boolean isLocalFile(Uri uri, String mimeType) { + boolean local = (!isSdpStreaming(uri, mimeType) + && !isRtspStreaming(uri, mimeType) + && !isHttpStreaming(uri, mimeType)); + if (LOG) { + Log.v(TAG, "isLocalFile(" + uri + ", " + mimeType + ") return " + local); + } + return local; + } +} diff --git a/src/org/codeaurora/gallery3d/video/BookmarkActivity.java b/src/org/codeaurora/gallery3d/video/BookmarkActivity.java new file mode 100644 index 000000000..e4662b4eb --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/BookmarkActivity.java @@ -0,0 +1,244 @@ +package org.codeaurora.gallery3d.video; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; +import android.widget.TextView; + +import com.android.gallery3d.R; +import com.android.gallery3d.app.MovieActivity; + +public class BookmarkActivity extends Activity implements OnItemClickListener { + private static final String TAG = "BookmarkActivity"; + private static final boolean LOG = false; + + private BookmarkEnhance mBookmark; + private BookmarkAdapter mAdapter; + private Cursor mCursor; + private ListView mListView; + private TextView mEmptyView; + + private static final int MENU_DELETE_ALL = 1; + private static final int MENU_DELETE_ONE = 2; + private static final int MENU_EDIT = 3; + + public static final String KEY_LOGO_BITMAP = "logo-bitmap"; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.bookmark); + + Bitmap logo = getIntent().getParcelableExtra(KEY_LOGO_BITMAP); + if (logo != null) { + getActionBar().setLogo(new BitmapDrawable(getResources(), logo)); + } + + mListView = (ListView) findViewById(android.R.id.list); + mEmptyView = (TextView) findViewById(android.R.id.empty); + + mBookmark = new BookmarkEnhance(this); + mCursor = mBookmark.query(); + mAdapter = new BookmarkAdapter(this, R.layout.bookmark_item, null, new String[] {}, + new int[] {}); + mListView.setEmptyView(mEmptyView); + mListView.setAdapter(mAdapter); + mAdapter.changeCursor(mCursor); + + mListView.setOnItemClickListener(this); + registerForContextMenu(mListView); + } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void onDestroy() { + if (mAdapter != null) { + mAdapter.changeCursor(null); + } + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + menu.add(0, MENU_DELETE_ALL, 0, R.string.delete_all) + .setIcon(android.R.drawable.ic_menu_delete); + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case MENU_DELETE_ALL: + mBookmark.deleteAll(); + return true; + default: + break; + } + return super.onOptionsItemSelected(item); + } + + private class BookmarkAdapter extends SimpleCursorAdapter { + + public BookmarkAdapter(final Context context, final int layout, final Cursor c, + final String[] from, final int[] to) { + super(context, layout, c, from, to); + } + + @Override + public View newView(final Context context, final Cursor cursor, final ViewGroup parent) { + final View view = super.newView(context, cursor, parent); + final ViewHolder holder = new ViewHolder(); + holder.mTitleView = (TextView) view.findViewById(R.id.title); + holder.mDataView = (TextView) view.findViewById(R.id.data); + view.setTag(holder); + return view; + } + + @Override + public void bindView(final View view, final Context context, final Cursor cursor) { + final ViewHolder holder = (ViewHolder) view.getTag(); + holder.mId = cursor.getLong(BookmarkEnhance.INDEX_ID); + holder.mTitle = cursor.getString(BookmarkEnhance.INDEX_TITLE); + holder.mData = cursor.getString(BookmarkEnhance.INDEX_DATA); + holder.mMimetype = cursor.getString(BookmarkEnhance.INDEX_MIME_TYPE); + holder.mTitleView.setText(holder.mTitle); + holder.mDataView.setText(holder.mData); + } + + @Override + public void changeCursor(final Cursor c) { + super.changeCursor(c); + } + + } + + private class ViewHolder { + long mId; + String mTitle; + String mData; + String mMimetype; + TextView mTitleView; + TextView mDataView; + } + + @Override + public void onItemClick(final AdapterView<?> parent, final View view, final int position, + final long id) { + final Object o = view.getTag(); + if (o instanceof ViewHolder) { + final ViewHolder holder = (ViewHolder) o; + finish(); + final Intent intent = new Intent(this, MovieActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + String mime = "video/*"; + if (!(holder.mMimetype == null || "".equals(holder.mMimetype.trim()))) { + mime = holder.mMimetype; + } + intent.setDataAndType(Uri.parse(holder.mData), mime); + startActivity(intent); + } + if (LOG) { + Log.v(TAG, "onItemClick(" + position + ", " + id + ")"); + } + } + + @Override + public void onCreateContextMenu(final ContextMenu menu, final View v, + final ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + menu.add(0, MENU_DELETE_ONE, 0, R.string.delete); + menu.add(0, MENU_EDIT, 0, R.string.edit); + } + + @Override + public boolean onContextItemSelected(final MenuItem item) { + final AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + switch (item.getItemId()) { + case MENU_DELETE_ONE: + mBookmark.delete(info.id); + return true; + case MENU_EDIT: + final Object obj = info.targetView.getTag(); + if (obj instanceof ViewHolder) { + showEditDialog((ViewHolder) obj); + } else { + Log.w(TAG, "wrong context item info " + info); + } + return true; + default: + return super.onContextItemSelected(item); + } + } + + private void showEditDialog(final ViewHolder holder) { + if (LOG) { + Log.v(TAG, "showEditDialog(" + holder + ")"); + } + if (holder == null) { + return; + } + final LayoutInflater inflater = LayoutInflater.from(this); + final View v = inflater.inflate(R.layout.bookmark_edit_dialog, null); + final EditText titleView = (EditText) v.findViewById(R.id.title); + final EditText dataView = (EditText) v.findViewById(R.id.data); + titleView.setText(holder.mTitle); + dataView.setText(holder.mData); + + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.edit); + builder.setView(v); + builder.setIcon(R.drawable.ic_menu_display_bookmark); + builder.setPositiveButton(android.R.string.ok, new OnClickListener() { + + @Override + public void onClick(final DialogInterface dialog, final int which) { + mBookmark.update(holder.mId, titleView.getText().toString(), + dataView.getText().toString(), 0); + } + + }); + builder.setNegativeButton(android.R.string.cancel, null); + final AlertDialog dialog = builder.create(); + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + dialog.setInverseBackgroundForced(true); + dialog.show(); + } +} diff --git a/src/org/codeaurora/gallery3d/video/BookmarkEnhance.java b/src/org/codeaurora/gallery3d/video/BookmarkEnhance.java new file mode 100644 index 000000000..cf607ecc4 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/BookmarkEnhance.java @@ -0,0 +1,138 @@ +package org.codeaurora.gallery3d.video; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; + +import com.android.gallery3d.R; + +public class BookmarkEnhance { + private static final String TAG = "BookmarkEnhance"; + private static final boolean LOG = false; + + private static final Uri BOOKMARK_URI = Uri.parse("content://media/internal/bookmark"); + + public static final String COLUMN_ID = "_id"; + public static final String COLUMN_DATA = "_data"; + public static final String COLUMN_TITLE = "_display_name"; + public static final String COLUMN_ADD_DATE = "date_added"; + public static final String COLUMN_MEDIA_TYPE = "mime_type"; + private static final String COLUMN_POSITION = "position"; + private static final String COLUMN_MIME_TYPE = "media_type"; + + private static final String NULL_HOCK = COLUMN_POSITION; + public static final String ORDER_COLUMN = COLUMN_ADD_DATE + " ASC "; + private static final String VIDEO_STREAMING_MEDIA_TYPE = "streaming"; + + public static final int INDEX_ID = 0; + public static final int INDEX_DATA = 1; + public static final int INDEX_TITLE = 2; + public static final int INDEX_ADD_DATE = 3; + public static final int INDEX_MIME_TYPE = 4; + private static final int INDEX_POSITION = 5; + private static final int INDEX_MEDIA_TYPE = 6; + + public static final String[] PROJECTION = new String[] { + COLUMN_ID, + COLUMN_DATA, + COLUMN_TITLE, + COLUMN_ADD_DATE, + COLUMN_MIME_TYPE, + }; + + private final Context mContext; + private final ContentResolver mCr; + + public BookmarkEnhance(final Context context) { + mContext = context; + mCr = context.getContentResolver(); + } + + public Uri insert(final String title, final String uri, final String mimeType, + final long position) { + final ContentValues values = new ContentValues(); + final String mytitle = (title == null ? mContext.getString(R.string.default_title) : title); + values.put(COLUMN_TITLE, mytitle); + values.put(COLUMN_DATA, uri); + values.put(COLUMN_POSITION, position); + values.put(COLUMN_ADD_DATE, System.currentTimeMillis()); + values.put(COLUMN_MEDIA_TYPE, VIDEO_STREAMING_MEDIA_TYPE); + values.put(COLUMN_MIME_TYPE, mimeType); + final Uri insertUri = mCr.insert(BOOKMARK_URI, values); + if (LOG) { + Log.v(TAG, "insert(" + title + "," + uri + ", " + position + ") return " + + insertUri); + } + return insertUri; + } + + public int delete(final long id) { + final Uri uri = ContentUris.withAppendedId(BOOKMARK_URI, id); + final int count = mCr.delete(uri, null, null); + if (LOG) { + Log.v(TAG, "delete(" + id + ") return " + count); + } + return count; + } + + public int deleteAll() { + final int count = mCr.delete(BOOKMARK_URI, COLUMN_MEDIA_TYPE + "=? ", new String[] { + VIDEO_STREAMING_MEDIA_TYPE + }); + if (LOG) { + Log.v(TAG, "deleteAll() return " + count); + } + return count; + } + + public boolean exists(final String uri) { + final Cursor cursor = mCr.query(BOOKMARK_URI, + PROJECTION, + COLUMN_DATA + "=? and " + COLUMN_MEDIA_TYPE + "=? ", + new String[] { + uri, VIDEO_STREAMING_MEDIA_TYPE + }, + null + ); + boolean exist = false; + if (cursor != null) { + exist = cursor.moveToFirst(); + cursor.close(); + } + if (LOG) { + Log.v(TAG, "exists(" + uri + ") return " + exist); + } + return exist; + } + + public Cursor query() { + final Cursor cursor = mCr.query(BOOKMARK_URI, + PROJECTION, + COLUMN_MEDIA_TYPE + "='" + VIDEO_STREAMING_MEDIA_TYPE + "' ", + null, + ORDER_COLUMN + ); + if (LOG) { + Log.v(TAG, "query() return cursor=" + (cursor == null ? -1 : cursor.getCount())); + } + return cursor; + } + + public int update(final long id, final String title, final String uri, final int position) { + final ContentValues values = new ContentValues(); + values.put(COLUMN_TITLE, title); + values.put(COLUMN_DATA, uri); + values.put(COLUMN_POSITION, position); + final Uri updateUri = ContentUris.withAppendedId(BOOKMARK_URI, id); + final int count = mCr.update(updateUri, values, null, null); + if (LOG) { + Log.v(TAG, "update(" + id + ", " + title + ", " + uri + ", " + position + ")" + + " return " + count); + } + return count; + } +} diff --git a/src/org/codeaurora/gallery3d/video/BookmarkHooker.java b/src/org/codeaurora/gallery3d/video/BookmarkHooker.java new file mode 100644 index 000000000..015fc3c41 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/BookmarkHooker.java @@ -0,0 +1,76 @@ +package org.codeaurora.gallery3d.video; + +import android.content.Intent; +import android.view.Menu; +import android.view.MenuItem; + +import com.android.gallery3d.R; +import org.codeaurora.gallery3d.ext.MovieUtils; + +public class BookmarkHooker extends MovieHooker { + private static final String TAG = "BookmarkHooker"; + private static final boolean LOG = false; + + private static final String ACTION_BOOKMARK = "org.codeaurora.bookmark.VIEW"; + private static final int MENU_BOOKMARK_ADD = 1; + private static final int MENU_BOOKMARK_DISPLAY = 2; + private MenuItem mMenuBookmarks; + private MenuItem mMenuBookmarkAdd; + + public static final String KEY_LOGO_BITMAP = "logo-bitmap"; + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + mMenuBookmarkAdd = menu.add(0, getMenuActivityId(MENU_BOOKMARK_ADD), 0, + R.string.bookmark_add); + mMenuBookmarks = menu.add(0, getMenuActivityId(MENU_BOOKMARK_DISPLAY), 0, + R.string.bookmark_display); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(final Menu menu) { + super.onPrepareOptionsMenu(menu); + if (MovieUtils.isLocalFile(getMovieItem().getUri(), getMovieItem().getMimeType())) { + if (mMenuBookmarkAdd != null) { + mMenuBookmarkAdd.setVisible(false); + } + if (mMenuBookmarks != null) { + mMenuBookmarks.setVisible(false); + } + } else { + if (mMenuBookmarkAdd != null) { + mMenuBookmarkAdd.setVisible(true); + } + if (mMenuBookmarks != null) { + mMenuBookmarks.setVisible(true); + } + } + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + super.onOptionsItemSelected(item); + switch (getMenuOriginalId(item.getItemId())) { + case MENU_BOOKMARK_ADD: + getPlayer().addBookmark(); + return true; + case MENU_BOOKMARK_DISPLAY: + gotoBookmark(); + return true; + default: + return false; + } + } + + private void gotoBookmark() { + final Intent intent = new Intent(ACTION_BOOKMARK); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); + intent.putExtra(KEY_LOGO_BITMAP, getIntent().getParcelableExtra(KEY_LOGO_BITMAP)); + getContext().startActivity(intent); + } +} diff --git a/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java b/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java new file mode 100755 index 000000000..c637c295a --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java @@ -0,0 +1,1049 @@ +package org.codeaurora.gallery3d.video; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnBufferingUpdateListener; +import android.media.MediaPlayer.OnVideoSizeChangedListener; +import android.media.Metadata; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; +import android.media.MediaPlayer.OnInfoListener; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.MediaController; +import android.widget.MediaController.MediaPlayerControl; + +import org.codeaurora.gallery3d.video.ScreenModeManager.ScreenModeListener; + +import java.io.IOException; +import java.util.Map; + +/** + * Displays a video file. The VideoView class + * can load images from various sources (such as resources or content + * providers), takes care of computing its measurement from the video so that + * it can be used in any layout manager, and provides various display options + * such as scaling and tinting. + */ +public class CodeauroraVideoView extends SurfaceView implements MediaPlayerControl, ScreenModeListener{ + private static final boolean LOG = false; + private String TAG = "CodeauroraVideoView"; + // settable by the client + private Uri mUri; + private Map<String, String> mHeaders; + + // all possible internal states + private static final int STATE_ERROR = -1; + private static final int STATE_IDLE = 0; + private static final int STATE_PREPARING = 1; + private static final int STATE_PREPARED = 2; + private static final int STATE_PLAYING = 3; + private static final int STATE_PAUSED = 4; + private static final int STATE_PLAYBACK_COMPLETED = 5; + private static final int STATE_SUSPENDED = 6; + private static final int MSG_LAYOUT_READY = 1; + + // mCurrentState is a VideoView object's current state. + // mTargetState is the state that a method caller intends to reach. + // For instance, regardless the VideoView object's current state, + // calling pause() intends to bring the object to a target state + // of STATE_PAUSED. + private int mCurrentState = STATE_IDLE; + private int mTargetState = STATE_IDLE; + + // All the stuff we need for playing and showing a video + private SurfaceHolder mSurfaceHolder = null; + private MediaPlayer mMediaPlayer = null; + private int mAudioSession; + private int mVideoWidth; + private int mVideoHeight; + private int mSurfaceWidth; + private int mSurfaceHeight; + private int mDuration; + private MediaController mMediaController; + private OnCompletionListener mOnCompletionListener; + private MediaPlayer.OnPreparedListener mOnPreparedListener; + private MediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener; + private MediaPlayer.OnVideoSizeChangedListener mVideoSizeListener; + private MediaPlayer.OnPreparedListener mPreparedListener; + private ScreenModeManager mScreenManager; + private int mCurrentBufferPercentage; + private OnErrorListener mOnErrorListener; + private OnInfoListener mOnInfoListener; + private int mSeekWhenPrepared; // recording the seek position while preparing + private boolean mCanPause; + private boolean mCanSeekBack; + private boolean mCanSeekForward; + private boolean mCanSeek; + private boolean mHasGotPreparedCallBack = false; + private boolean mNeedWaitLayout = false; + private boolean mHasGotMetaData = false; + private boolean mOnResumed; + private boolean mIsShowDialog = false; + + private final Handler mHandler = new Handler() { + public void handleMessage(final Message msg) { + if (LOG) { + Log.v(TAG, "handleMessage() to do prepare. msg=" + msg); + } + switch (msg.what) { + case MSG_LAYOUT_READY: + if (mMediaPlayer == null || mUri == null) { + Log.w(TAG, "Cannot prepare play! mMediaPlayer=" + mMediaPlayer + + ", mUri=" + mUri); + return; + } + doPreparedIfReady(mMediaPlayer); + break; + default: + Log.w(TAG, "Unhandled message " + msg); + break; + } + } + }; + + public CodeauroraVideoView(Context context) { + super(context); + initVideoView(); + initialize(); + } + + public CodeauroraVideoView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + initVideoView(); + initialize(); + } + + public CodeauroraVideoView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initVideoView(); + initialize(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = 0; + int height = 0; + int screenMode = ScreenModeManager.SCREENMODE_BIGSCREEN; + if (mScreenManager != null) { + screenMode = mScreenManager.getScreenMode(); + } + switch (screenMode) { + case ScreenModeManager.SCREENMODE_BIGSCREEN: + width = getDefaultSize(mVideoWidth, widthMeasureSpec); + height = getDefaultSize(mVideoHeight, heightMeasureSpec); + if (mVideoWidth > 0 && mVideoHeight > 0) { + if (mVideoWidth * height > width * mVideoHeight) { + height = width * mVideoHeight / mVideoWidth; + } else if (mVideoWidth * height < width * mVideoHeight) { + width = height * mVideoWidth / mVideoHeight; + } + } + break; + case ScreenModeManager.SCREENMODE_FULLSCREEN: + width = getDefaultSize(mVideoWidth, widthMeasureSpec); + height = getDefaultSize(mVideoHeight, heightMeasureSpec); + break; + case ScreenModeManager.SCREENMODE_CROPSCREEN: + width = getDefaultSize(mVideoWidth, widthMeasureSpec); + height = getDefaultSize(mVideoHeight, heightMeasureSpec); + if (mVideoWidth > 0 && mVideoHeight > 0) { + if (mVideoWidth * height > width * mVideoHeight) { + width = height * mVideoWidth / mVideoHeight; + } else if (mVideoWidth * height < width * mVideoHeight) { + height = width * mVideoHeight / mVideoWidth; + } + } + break; + default: + Log.w(TAG, "wrong screen mode : " + screenMode); + break; + } + if (LOG) { + Log.v(TAG, "onMeasure() set size: " + width + 'x' + height); + Log.v(TAG, "onMeasure() video size: " + mVideoWidth + 'x' + mVideoHeight); + Log.v(TAG, "onMeasure() mNeedWaitLayout=" + mNeedWaitLayout); + } + setMeasuredDimension(width, height); + if (mNeedWaitLayout) { // when OnMeasure ok, start video. + mNeedWaitLayout = false; + mHandler.sendEmptyMessage(MSG_LAYOUT_READY); + } + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(CodeauroraVideoView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(CodeauroraVideoView.class.getName()); + } + + public int resolveAdjustedSize(int desiredSize, int measureSpec) { + return getDefaultSize(desiredSize, measureSpec); + } + + private void initVideoView() { + mVideoWidth = 0; + mVideoHeight = 0; + getHolder().addCallback(mSHCallback); + getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; + } + + private void initialize() { + mPreparedListener = new MediaPlayer.OnPreparedListener() { + public void onPrepared(final MediaPlayer mp) { + if (LOG) { + Log.v(TAG, "mPreparedListener.onPrepared(" + mp + ")"); + } + //Here we can get meta data from mediaplayer. + // Get the capabilities of the player for this stream + final Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL, + MediaPlayer.BYPASS_METADATA_FILTER); + if (data != null) { + mCanPause = !data.has(Metadata.PAUSE_AVAILABLE) + || data.getBoolean(Metadata.PAUSE_AVAILABLE); + mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE) + || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE); + mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE) + || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE); + mCanSeek = !data.has(Metadata.SEEK_AVAILABLE) + || data.getBoolean(Metadata.SEEK_AVAILABLE); + } else { + mCanPause = true; + mCanSeekBack = true; + mCanSeekForward = true; + mCanSeek = true; + Log.w(TAG, "Metadata is null!"); + } + if (LOG) { + Log.v(TAG, "mPreparedListener.onPrepared() mCanPause=" + mCanPause); + } + mHasGotPreparedCallBack = true; + doPreparedIfReady(mMediaPlayer); + } + }; + + mErrorListener = new MediaPlayer.OnErrorListener() { + public boolean onError(final MediaPlayer mp, final int frameworkErr, final int implErr) { + Log.d(TAG, "Error: " + frameworkErr + "," + implErr); + //record error position and duration + //here disturb the original logic + mSeekWhenPrepared = getCurrentPosition(); + if (LOG) { + Log.v(TAG, "onError() mSeekWhenPrepared=" + mSeekWhenPrepared + ", mDuration=" + mDuration); + } + //for old version Streaming server, getduration is not valid. + mDuration = Math.abs(mDuration); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + if (mMediaController != null) { + mMediaController.hide(); + } + + /* If an error handler has been supplied, use it and finish. */ + if (mOnErrorListener != null) { + if (mOnErrorListener.onError(mMediaPlayer, frameworkErr, implErr)) { + return true; + } + } + + /* Otherwise, pop up an error dialog so the user knows that + * something bad has happened. Only try and pop up the dialog + * if we're attached to a window. When we're going away and no + * longer have a window, don't bother showing the user an error. + */ + if (getWindowToken() != null) { + final Resources r = mContext.getResources(); + int messageId; + + if (frameworkErr == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { + messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback; + } else { + messageId = com.android.internal.R.string.VideoView_error_text_unknown; + } + new AlertDialog.Builder(mContext) + .setMessage(messageId) + .setPositiveButton(com.android.internal.R.string.VideoView_error_button, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + /* If we get here, there is no onError listener, so + * at least inform them that the video is over. + */ + if (mOnCompletionListener != null) { + mOnCompletionListener.onCompletion(mMediaPlayer); + } + } + }) + .setCancelable(false) + .show(); + } + return true; + } + }; + + mBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { + public void onBufferingUpdate(final MediaPlayer mp, final int percent) { + mCurrentBufferPercentage = percent; + if (mOnBufferingUpdateListener != null) { + mOnBufferingUpdateListener.onBufferingUpdate(mp, percent); + } + if (LOG) { + Log.v(TAG, "onBufferingUpdate() Buffering percent: " + percent); + Log.v(TAG, "onBufferingUpdate() mTargetState=" + mTargetState); + Log.v(TAG, "onBufferingUpdate() mCurrentState=" + mCurrentState); + } + } + }; + + mSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() { + public void onVideoSizeChanged(final MediaPlayer mp, final int width, final int height) { + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + if (LOG) { + Log.v(TAG, "OnVideoSizeChagned(" + width + "," + height + ")"); + Log.v(TAG, "OnVideoSizeChagned(" + mVideoWidth + "," + mVideoHeight + ")"); + Log.v(TAG, "OnVideoSizeChagned() mCurrentState=" + mCurrentState); + } + if (mVideoWidth != 0 && mVideoHeight != 0) { + getHolder().setFixedSize(mVideoWidth, mVideoHeight); + if (mCurrentState == STATE_PREPARING) { + mNeedWaitLayout = true; + } + } + if (mVideoSizeListener != null) { + mVideoSizeListener.onVideoSizeChanged(mp, width, height); + } + CodeauroraVideoView.this.requestLayout(); + } + }; + + getHolder().removeCallback(mSHCallback); + mSHCallback = new SurfaceHolder.Callback() { + public void surfaceChanged(final SurfaceHolder holder, final int format, + final int w, final int h) { + if (LOG) { + Log.v(TAG, "surfaceChanged(" + holder + ", " + format + + ", " + w + ", " + h + ")"); + Log.v(TAG, "surfaceChanged() mMediaPlayer=" + mMediaPlayer + + ", mTargetState=" + mTargetState + + ", mVideoWidth=" + mVideoWidth + + ", mVideoHeight=" + mVideoHeight); + } + mSurfaceWidth = w; + mSurfaceHeight = h; + final boolean isValidState = (mTargetState == STATE_PLAYING); + final boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h); + if (mMediaPlayer != null && isValidState && hasValidSize) { + if (mSeekWhenPrepared != 0) { + seekTo(mSeekWhenPrepared); + } + Log.v(TAG, "surfaceChanged() start()"); + start(); + } + } + + public void surfaceCreated(final SurfaceHolder holder) { + if (LOG) { + Log.v(TAG, "surfaceCreated(" + holder + ")"); + } + if (mCurrentState == STATE_SUSPENDED) { + mSurfaceHolder = holder; + mMediaPlayer.setDisplay(mSurfaceHolder); + if (mMediaPlayer.resume()) { + mCurrentState = STATE_PREPARED; + if (mSeekWhenPrepared != 0) { + seekTo(mSeekWhenPrepared); + } + if (mTargetState == STATE_PLAYING) { + start(); + } + return; + } else { + release(false); + } + } + mSurfaceHolder = holder; + openVideo(); + } + + public void surfaceDestroyed(final SurfaceHolder holder) { + // after we return from this we can't use the surface any more + if (LOG) { + Log.v(TAG, "surfaceDestroyed(" + holder + ")"); + } + mSurfaceHolder = null; + if (mMediaController != null) { + mMediaController.hide(); + } + if (isHTTPStreaming(mUri) && mCurrentState == STATE_SUSPENDED) { + // Don't call release() while run suspend operation + return; + } + release(true); + } + }; + getHolder().addCallback(mSHCallback); + } + + public void setVideoPath(String path) { + setVideoURI(Uri.parse(path)); + } + + public void setVideoURI(Uri uri) { + setVideoURI(uri, null); + } + + /** + * @hide + */ + public void setVideoURI(Uri uri, Map<String, String> headers) { + Log.d(TAG,"setVideoURI uri = " + uri); + mDuration = -1; + setResumed(true); + mUri = uri; + mHeaders = headers; + mSeekWhenPrepared = 0; + openVideo(); + requestLayout(); + invalidate(); + } + + public void stopPlayback() { + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; + } + } + + private void openVideo() { + clearVideoInfo(); + if (mUri == null || mSurfaceHolder == null) { + // not ready for playback just yet, will try again later + return; + } + + // we shouldn't clear the target state, because somebody might have + // called start() previously + release(false); + if ("".equalsIgnoreCase(String.valueOf(mUri))) { + Log.w(TAG, "Unable to open content: " + mUri); + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } + try { + mMediaPlayer = new MediaPlayer(); + if (mAudioSession != 0) { + mMediaPlayer.setAudioSessionId(mAudioSession); + } else { + mAudioSession = mMediaPlayer.getAudioSessionId(); + } + mMediaPlayer.setOnPreparedListener(mPreparedListener); + mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); + mMediaPlayer.setOnCompletionListener(mCompletionListener); + mMediaPlayer.setOnErrorListener(mErrorListener); + mMediaPlayer.setOnInfoListener(mOnInfoListener); + mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); + mCurrentBufferPercentage = 0; + mMediaPlayer.setDataSource(mContext, mUri, mHeaders); + mMediaPlayer.setDisplay(mSurfaceHolder); + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mMediaPlayer.setScreenOnWhilePlaying(true); + mMediaPlayer.prepareAsync(); + // we don't set the target state here either, but preserve the + // target state that was there before. + mCurrentState = STATE_PREPARING; + attachMediaController(); + } catch (IOException ex) { + Log.w(TAG, "Unable to open content: " + mUri, ex); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } catch (IllegalArgumentException ex) { + Log.w(TAG, "Unable to open content: " + mUri, ex); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } + } + + public void setMediaController(MediaController controller) { + if (mMediaController != null) { + mMediaController.hide(); + } + mMediaController = controller; + attachMediaController(); + } + + private void attachMediaController() { + if (mMediaPlayer != null && mMediaController != null) { + mMediaController.setMediaPlayer(this); + View anchorView = this.getParent() instanceof View ? + (View)this.getParent() : this; + mMediaController.setAnchorView(anchorView); + mMediaController.setEnabled(isInPlaybackState()); + } + } + + private boolean isHTTPStreaming(Uri mUri) { + if (mUri != null){ + String scheme = mUri.toString(); + if (scheme.startsWith("http://") || scheme.startsWith("https://")) { + if (scheme.endsWith(".m3u8") || scheme.endsWith(".m3u") + || scheme.contains("m3u8") || scheme.endsWith(".mpd")) { + // HLS or DASH streaming source + return false; + } + // HTTP streaming + return true; + } + } + return false; + } + + MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = + new MediaPlayer.OnVideoSizeChangedListener() { + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + if (mVideoWidth != 0 && mVideoHeight != 0) { + getHolder().setFixedSize(mVideoWidth, mVideoHeight); + requestLayout(); + } + } + }; + + private MediaPlayer.OnCompletionListener mCompletionListener = + new MediaPlayer.OnCompletionListener() { + public void onCompletion(MediaPlayer mp) { + mCurrentState = STATE_PLAYBACK_COMPLETED; + mTargetState = STATE_PLAYBACK_COMPLETED; + if (mMediaController != null) { + mMediaController.hide(); + } + if (mOnCompletionListener != null) { + mOnCompletionListener.onCompletion(mMediaPlayer); + } + } + }; + + private MediaPlayer.OnErrorListener mErrorListener; + + private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = + new MediaPlayer.OnBufferingUpdateListener() { + public void onBufferingUpdate(MediaPlayer mp, int percent) { + mCurrentBufferPercentage = percent; + } + }; + + /** + * Register a callback to be invoked when the media file + * is loaded and ready to go. + * + * @param l The callback that will be run + */ + public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) { + mOnPreparedListener = l; + } + + /** + * Register a callback to be invoked when the end of a media file + * has been reached during playback. + * + * @param l The callback that will be run + */ + public void setOnCompletionListener(OnCompletionListener l) { + mOnCompletionListener = l; + } + + /** + * Register a callback to be invoked when an error occurs + * during playback or setup. If no listener is specified, + * or if the listener returned false, VideoView will inform + * the user of any errors. + * + * @param l The callback that will be run + */ + public void setOnErrorListener(OnErrorListener l) { + mOnErrorListener = l; + } + + /** + * Register a callback to be invoked when an informational event + * occurs during playback or setup. + * + * @param l The callback that will be run + */ + public void setOnInfoListener(OnInfoListener l) { + mOnInfoListener = l; + } + + SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() { + public void surfaceChanged(SurfaceHolder holder, int format, + int w, int h) { + mSurfaceWidth = w; + mSurfaceHeight = h; + boolean isValidState = (mTargetState == STATE_PLAYING); + boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h); + if (mMediaPlayer != null && isValidState && hasValidSize) { + if (mSeekWhenPrepared != 0) { + seekTo(mSeekWhenPrepared); + } + start(); + } + } + + public void surfaceCreated(SurfaceHolder holder) { + if (LOG) { + Log.v(TAG, "surfaceCreated(" + holder + ")"); + } + if (mCurrentState == STATE_SUSPENDED) { + mSurfaceHolder = holder; + mMediaPlayer.setDisplay(mSurfaceHolder); + if (mMediaPlayer.resume()) { + mCurrentState = STATE_PREPARED; + if (mSeekWhenPrepared != 0) { + seekTo(mSeekWhenPrepared); + } + if (mTargetState == STATE_PLAYING) { + start(); + } + return; + } else { + release(false); + } + } + mSurfaceHolder = holder; + openVideo(); + } + + public void surfaceDestroyed(SurfaceHolder holder) { + // after we return from this we can't use the surface any more + mSurfaceHolder = null; + if (mMediaController != null) mMediaController.hide(); + if (isHTTPStreaming(mUri) && mCurrentState == STATE_SUSPENDED) { + // Don't call release() while run suspend operation + return; + } + release(true); + } + }; + + /* + * release the media player in any state + */ + private void release(boolean cleartargetstate) { + if (mMediaPlayer != null) { + mMediaPlayer.reset(); + mMediaPlayer.release(); + mMediaPlayer = null; + mCurrentState = STATE_IDLE; + if (cleartargetstate) { + mTargetState = STATE_IDLE; + } + } + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (isInPlaybackState() && mMediaController != null) { + toggleMediaControlsVisiblity(); + } + return false; + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + if (isInPlaybackState() && mMediaController != null) { + toggleMediaControlsVisiblity(); + } + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + final boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && + keyCode != KeyEvent.KEYCODE_VOLUME_UP && + keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && + keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && + keyCode != KeyEvent.KEYCODE_MENU && + keyCode != KeyEvent.KEYCODE_CALL && + keyCode != KeyEvent.KEYCODE_ENDCALL && + keyCode != KeyEvent.KEYCODE_CAMERA; + if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) { + if (event.getRepeatCount() == 0 && (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || + keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)) { + if (mMediaPlayer.isPlaying()) { + pause(); + mMediaController.show(); + } else { + start(); + mMediaController.hide(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { + if (!mMediaPlayer.isPlaying()) { + start(); + mMediaController.hide(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP + || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { + if (mMediaPlayer.isPlaying()) { + pause(); + mMediaController.show(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD || + keyCode == KeyEvent.KEYCODE_MEDIA_NEXT || + keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS || + keyCode == KeyEvent.KEYCODE_MEDIA_REWIND || + keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || + keyCode == KeyEvent.KEYCODE_HEADSETHOOK) { + // consume media action, so if video view if front, + // other media player will not play any sounds. + return true; + } else { + toggleMediaControlsVisiblity(); + } + } + return super.onKeyDown(keyCode, event); + } + + private void toggleMediaControlsVisiblity() { + if (mMediaController.isShowing()) { + mMediaController.hide(); + } else { + mMediaController.show(); + } + } + + public void setDialogShowState(boolean isDialogShow) { + mIsShowDialog = isDialogShow; + } + + @Override + public void start() { + if (mIsShowDialog) return; + if (isInPlaybackState()) { + mMediaPlayer.start(); + mCurrentState = STATE_PLAYING; + } + mTargetState = STATE_PLAYING; + } + + @Override + public void pause() { + if (isInPlaybackState()) { + if (mMediaPlayer.isPlaying()) { + mMediaPlayer.pause(); + mCurrentState = STATE_PAUSED; + } + } + mTargetState = STATE_PAUSED; + } + + public void suspend() { + // HTTP streaming will call mMediaPlayer->suspend(), others will call release() + if (isHTTPStreaming(mUri) && mCurrentState != STATE_PREPARING) { + if (mMediaPlayer != null) { + if (mMediaPlayer.suspend()) { + mTargetState = mCurrentState; + mCurrentState = STATE_SUSPENDED; + return; + } + } + } + release(false); + } + + public void resume() { + // HTTP streaming (with suspended status) will call mMediaPlayer->resume(), + // others will call openVideo() + if (mCurrentState == STATE_SUSPENDED) { + if (mSurfaceHolder != null) { + // The surface hasn't been destroyed + if (mMediaPlayer.resume()) { + mCurrentState = STATE_PREPARED; + if (mSeekWhenPrepared !=0) { + seekTo(mSeekWhenPrepared); + } + if (mTargetState == STATE_PLAYING) { + start(); + } + return; + } else { + // resume failed, so call release() before openVideo() + release(false); + } + } else { + // The surface has been destroyed, resume operation will be done + // after surface created + return; + } + } + openVideo(); + } + + @Override + public int getDuration() { + final boolean inPlaybackState = isInPlaybackState(); + if (LOG) { + Log.v(TAG, "getDuration() mDuration=" + mDuration + ", inPlaybackState=" + + inPlaybackState); + } + if (inPlaybackState) { + if (mDuration > 0) { + return mDuration; + } + // in case the duration is zero or smaller than zero for streaming + // video + int tempDuration = mMediaPlayer.getDuration(); + if (tempDuration <= 0) { + return mDuration; + } else { + mDuration = tempDuration; + } + + return mDuration; + } + return mDuration; + } + + @Override + public int getCurrentPosition() { + int position = 0; + if (mSeekWhenPrepared > 0) { + // if connecting error before seek, + // we should remember this position for retry + position = mSeekWhenPrepared; + // /M: if player not started, getCurrentPosition() will lead to NE. + } else if (isInPlaybackState()) { + position = mMediaPlayer.getCurrentPosition(); + } + if (LOG) { + Log.v(TAG, "getCurrentPosition() return " + position + + ", mSeekWhenPrepared=" + mSeekWhenPrepared); + } + return position; + } + + @Override + public void seekTo(int msec) { + if (isInPlaybackState()) { + mMediaPlayer.seekTo(msec); + mSeekWhenPrepared = 0; + } else { + mSeekWhenPrepared = msec; + } + } + + @Override + public boolean isPlaying() { + return isInPlaybackState() && mMediaPlayer.isPlaying(); + } + + @Override + public int getBufferPercentage() { + if (mMediaPlayer != null) { + return mCurrentBufferPercentage; + } + return 0; + } + + private boolean isInPlaybackState() { + return (mMediaPlayer != null && + mCurrentState != STATE_ERROR && + mCurrentState != STATE_IDLE && + mCurrentState != STATE_PREPARING && + mCurrentState != STATE_SUSPENDED); + } + + @Override + public boolean canPause() { + return mCanPause; + } + + @Override + public boolean canSeekBackward() { + return mCanSeekBack; + } + + @Override + public boolean canSeekForward() { + return mCanSeekForward; + } + + public boolean canSeek() { + return mCanSeek; + } + + @Override + public int getAudioSessionId() { + if (mAudioSession == 0) { + MediaPlayer foo = new MediaPlayer(); + mAudioSession = foo.getAudioSessionId(); + foo.release(); + } + return mAudioSession; + } + + // for duration displayed + public void setDuration(final int duration) { + if (LOG) { + Log.v(TAG, "setDuration(" + duration + ")"); + } + mDuration = (duration > 0 ? -duration : duration); + } + + public void setVideoURI(final Uri uri, final Map<String, String> headers, + final boolean hasGotMetaData) { + if (LOG) { + Log.v(TAG, "setVideoURI(" + uri + ", " + headers + ")"); + } + // clear the flags + mHasGotMetaData = hasGotMetaData; + setVideoURI(uri, headers); + } + + private void doPreparedIfReady(final MediaPlayer mp) { + if (LOG) { + Log.v(TAG, "doPreparedIfReady() mHasGotPreparedCallBack=" + mHasGotPreparedCallBack + + ", mHasGotMetaData=" + mHasGotMetaData + ", mNeedWaitLayout=" + + mNeedWaitLayout + + ", mCurrentState=" + mCurrentState); + } + if (mHasGotPreparedCallBack && mHasGotMetaData && !mNeedWaitLayout) { + doPrepared(mp); + } + } + + private void doPrepared(final MediaPlayer mp) { + if (LOG) { + Log.v(TAG, "doPrepared(" + mp + ") start"); + } + mCurrentState = STATE_PREPARED; + if (mOnPreparedListener != null) { + mOnPreparedListener.onPrepared(mMediaPlayer); + } + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + + // mSeekWhenPrepared may be changed after seekTo() + final int seekToPosition = mSeekWhenPrepared; + if (seekToPosition != 0) { + seekTo(seekToPosition); + } + if (mVideoWidth != 0 && mVideoHeight != 0) { + getHolder().setFixedSize(mVideoWidth, mVideoHeight); + } + + if (mTargetState == STATE_PLAYING) { + start(); + } + if (LOG) { + Log.v(TAG, "doPrepared() end video size: " + mVideoWidth + "," + mVideoHeight + + ", mTargetState=" + mTargetState + ", mCurrentState=" + mCurrentState); + } + } + + /** + * surfaceCreate will invoke openVideo after the activity stoped. Here set + * this flag to avoid openVideo after the activity stoped. + * + * @param resume + */ + public void setResumed(final boolean resume) { + if (LOG) { + Log.v(TAG, "setResumed(" + resume + ") mUri=" + mUri + ", mOnResumed=" + mOnResumed); + } + mOnResumed = resume; + } + + private void clearVideoInfo() { + if (LOG) { + Log.v(TAG, "clearVideoInfo()"); + } + mHasGotPreparedCallBack = false; + mNeedWaitLayout = false; + } + + public void clearSeek() { + if (LOG) { + Log.v(TAG, "clearSeek() mSeekWhenPrepared=" + mSeekWhenPrepared); + } + mSeekWhenPrepared = 0; + } + + public void clearDuration() { + if (LOG) { + Log.v(TAG, "clearDuration() mDuration=" + mDuration); + } + mDuration = -1; + } + + public boolean isTargetPlaying() { + if (LOG) { + Log.v(TAG, "isTargetPlaying() mTargetState=" + mTargetState); + } + return mTargetState == STATE_PLAYING; + } + + public void setScreenModeManager(final ScreenModeManager manager) { + mScreenManager = manager; + if (mScreenManager != null) { + mScreenManager.addListener(this); + } + if (LOG) { + Log.v(TAG, "setScreenModeManager(" + manager + ")"); + } + } + + @Override + public void onScreenModeChanged(final int newMode) { + this.requestLayout(); + } + + public void setOnVideoSizeChangedListener(final OnVideoSizeChangedListener l) { + mVideoSizeListener = l; + if (LOG) { + Log.i(TAG, "setOnVideoSizeChangedListener(" + l + ")"); + } + } + + public void setOnBufferingUpdateListener(final OnBufferingUpdateListener l) { + mOnBufferingUpdateListener = l; + if (LOG) { + Log.v(TAG, "setOnBufferingUpdateListener(" + l + ")"); + } + } +} diff --git a/src/org/codeaurora/gallery3d/video/DmReceiver.java b/src/org/codeaurora/gallery3d/video/DmReceiver.java new file mode 100644 index 000000000..616ce33d6 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/DmReceiver.java @@ -0,0 +1,65 @@ +package org.codeaurora.gallery3d.video; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.net.Uri; +import android.os.PowerManager; +import android.preference.PreferenceManager; +import android.provider.Settings.System; +import android.util.Log; + +public class DmReceiver extends BroadcastReceiver { + private static final String TAG = "DmReceiver"; + public static final String WRITE_SETTING_ACTION = "streaming.action.WRITE_SETTINGS"; + public static final String BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; + + private SharedPreferences mPref; + static final int STREAMING_CONNPROFILE_IO_HANDLER_TYPE = 1; + static final int STREAMING_MAX_UDP_PORT_IO_HANDLER_TYPE = 3; + static final int STREAMING_MIN_UDP_PORT_IO_HANDLER_TYPE = 4; + + @Override + public void onReceive(Context context, Intent intent) { + if (mPref == null) { + mPref = PreferenceManager.getDefaultSharedPreferences(context); + } + if (BOOT_COMPLETED.equals(intent.getAction())) { + String rtpMaxport = mPref.getString(SettingsActivity.PREFERENCE_RTP_MAXPORT, "65535"); + String rtpMinport = mPref.getString(SettingsActivity.PREFERENCE_RTP_MINPORT, "8192"); + String apn = mPref.getString(SettingsActivity.PREFERENCE_APN, "CMWAP"); + System.putString(context.getContentResolver(), + "streaming_max_udp_port", rtpMaxport); + System.putString(context.getContentResolver(), + "streaming_min_udp_port", rtpMinport); + System.putString(context.getContentResolver(), "apn", apn); + } else if (WRITE_SETTING_ACTION.equals(intent.getAction())) { + int valueType = intent.getIntExtra("type", 0); + String value = intent.getStringExtra("value"); + if (valueType == STREAMING_MAX_UDP_PORT_IO_HANDLER_TYPE) { + mPref.edit().putString(SettingsActivity.PREFERENCE_RTP_MAXPORT, + value).commit(); + System.putString(context.getContentResolver(), + "streaming_max_udp_port", value); + } else if (valueType == STREAMING_MIN_UDP_PORT_IO_HANDLER_TYPE) { + mPref.edit().putString(SettingsActivity.PREFERENCE_RTP_MINPORT, + value).commit(); + System.putString(context.getContentResolver(), + "streaming_min_udp_port", value); + } else if (valueType == STREAMING_CONNPROFILE_IO_HANDLER_TYPE) { + mPref.edit().putString(SettingsActivity.PREFERENCE_APN, + value).commit(); + System.putString(context.getContentResolver(), + "apn", value); + } + } + } +} diff --git a/src/org/codeaurora/gallery3d/video/ExtensionHelper.java b/src/org/codeaurora/gallery3d/video/ExtensionHelper.java new file mode 100755 index 000000000..b5156100b --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/ExtensionHelper.java @@ -0,0 +1,72 @@ +/* +Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.codeaurora.gallery3d.video; + +import android.content.Context; + +import com.android.gallery3d.app.MovieActivity; +import com.android.gallery3d.R; + +import org.codeaurora.gallery3d.ext.ActivityHookerGroup; +import org.codeaurora.gallery3d.ext.IActivityHooker; + +import java.util.ArrayList; +import java.util.List; + +public class ExtensionHelper { + + public static IActivityHooker getHooker(final Context context) { + + final ActivityHookerGroup group = new ActivityHookerGroup(); + boolean loop = context.getResources().getBoolean(R.bool.loop); + boolean stereo = context.getResources().getBoolean(R.bool.stereo); + boolean streaming = context.getResources().getBoolean(R.bool.streaming); + boolean playlist = context.getResources().getBoolean(R.bool.playlist); + boolean speaker = context.getResources().getBoolean(R.bool.speaker); + + if (loop == true) { + group.addHooker(new LoopVideoHooker()); // add it for common feature. + } + if (stereo == true) { + group.addHooker(new StereoAudioHooker()); // add it for common feature. + } + if (streaming == true) { + group.addHooker(new StreamingHooker()); + group.addHooker(new BookmarkHooker()); + } + if (playlist == true) { + group.addHooker(new MovieListHooker()); // add it for common feature. + group.addHooker(new StepOptionSettingsHooker()); + } + if (speaker == true) { + group.addHooker(new SpeakerHooker()); + } + return group; + } +} diff --git a/src/org/codeaurora/gallery3d/video/IControllerRewindAndForward.java b/src/org/codeaurora/gallery3d/video/IControllerRewindAndForward.java new file mode 100644 index 000000000..1fc7f704d --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/IControllerRewindAndForward.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.codeaurora.gallery3d.video; + +import com.android.gallery3d.app.ControllerOverlay; +import com.android.gallery3d.app.ControllerOverlay.Listener; + +///M: for CU rewind and forward +public interface IControllerRewindAndForward extends ControllerOverlay { + + interface IRewindAndForwardListener extends Listener { + void onStopVideo(); + void onRewind(); + void onForward(); + } + + boolean getPlayPauseEanbled(); + boolean getTimeBarEanbled(); + void setIListener(IRewindAndForwardListener listener); + void showControllerButtonsView(boolean canStop, boolean canRewind, boolean canForward); +} diff --git a/src/org/codeaurora/gallery3d/video/LoopVideoHooker.java b/src/org/codeaurora/gallery3d/video/LoopVideoHooker.java new file mode 100644 index 000000000..004254856 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/LoopVideoHooker.java @@ -0,0 +1,60 @@ +package org.codeaurora.gallery3d.video; + +import android.view.Menu; +import android.view.MenuItem; + +import com.android.gallery3d.R; +import org.codeaurora.gallery3d.ext.MovieUtils; + +public class LoopVideoHooker extends MovieHooker { + + private static final String TAG = "LoopVideoHooker"; + private static final boolean LOG = false; + private static final int MENU_LOOP = 1; + + private MenuItem mMenuLoopButton; + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + mMenuLoopButton = menu.add(0, getMenuActivityId(MENU_LOOP), 0, R.string.loop); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(final Menu menu) { + super.onPrepareOptionsMenu(menu); + updateLoop(); + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + super.onOptionsItemSelected(item); + switch (getMenuOriginalId(item.getItemId())) { + case MENU_LOOP: + getPlayer().setLoop(!getPlayer().getLoop()); + return true; + default: + return false; + } + } + + private void updateLoop() { + if (mMenuLoopButton != null) { + if (MovieUtils.isLocalFile(getMovieItem().getUri(), getMovieItem().getMimeType())) { + mMenuLoopButton.setVisible(true); + } else { + mMenuLoopButton.setVisible(false); + } + final boolean newLoop = getPlayer().getLoop(); + if (newLoop) { + mMenuLoopButton.setTitle(R.string.single); + mMenuLoopButton.setIcon(R.drawable.ic_menu_unloop); + } else { + mMenuLoopButton.setTitle(R.string.loop); + mMenuLoopButton.setIcon(R.drawable.ic_menu_loop); + } + } + } +} diff --git a/src/org/codeaurora/gallery3d/video/MovieHooker.java b/src/org/codeaurora/gallery3d/video/MovieHooker.java new file mode 100644 index 000000000..a859d44a3 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/MovieHooker.java @@ -0,0 +1,44 @@ +package org.codeaurora.gallery3d.video; + +import android.util.Log; + +import org.codeaurora.gallery3d.ext.ActivityHooker; +import org.codeaurora.gallery3d.ext.IMovieItem; +import org.codeaurora.gallery3d.ext.IMoviePlayer; + +public class MovieHooker extends ActivityHooker { + + private static final String TAG = "MovieHooker"; + private static final boolean LOG = false; + private IMovieItem mMovieItem; + private IMoviePlayer mPlayer; + + @Override + public void setParameter(final String key, final Object value) { + super.setParameter(key, value); + if (LOG) { + Log.v(TAG, "setParameter(" + key + ", " + value + ")"); + } + if (value instanceof IMovieItem) { + mMovieItem = (IMovieItem) value; + onMovieItemChanged(mMovieItem); + } else if (value instanceof IMoviePlayer) { + mPlayer = (IMoviePlayer) value; + onMoviePlayerChanged(mPlayer); + } + } + + public IMovieItem getMovieItem() { + return mMovieItem; + } + + public IMoviePlayer getPlayer() { + return mPlayer; + } + + public void onMovieItemChanged(final IMovieItem item) { + } + + public void onMoviePlayerChanged(final IMoviePlayer player) { + } +} diff --git a/src/org/codeaurora/gallery3d/video/MovieListHooker.java b/src/org/codeaurora/gallery3d/video/MovieListHooker.java new file mode 100644 index 000000000..ee360ac78 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/MovieListHooker.java @@ -0,0 +1,116 @@ +package org.codeaurora.gallery3d.video; + +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; + +import com.android.gallery3d.R; +import org.codeaurora.gallery3d.ext.IMovieItem; +import org.codeaurora.gallery3d.ext.IMovieList; +import org.codeaurora.gallery3d.ext.IMovieListLoader; +import org.codeaurora.gallery3d.ext.IMovieListLoader.LoaderListener; +import org.codeaurora.gallery3d.ext.MovieListLoader; + +public class MovieListHooker extends MovieHooker implements LoaderListener { + private static final String TAG = "MovieListHooker"; + private static final boolean LOG = false; + + private static final int MENU_NEXT = 1; + private static final int MENU_PREVIOUS = 2; + + private MenuItem mMenuNext; + private MenuItem mMenuPrevious; + + private IMovieListLoader mMovieLoader; + private IMovieList mMovieList; + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mMovieLoader = new MovieListLoader(); + mMovieLoader.fillVideoList(getContext(), getIntent(), this, getMovieItem()); + } + @Override + public void onDestroy() { + super.onDestroy(); + mMovieLoader.cancelList(); + } + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + if (mMovieList != null) { //list should be filled + if (mMovieLoader != null && mMovieLoader.isEnabledVideoList(getIntent())) { + mMenuPrevious = menu.add(0, getMenuActivityId(MENU_PREVIOUS), 0, R.string.previous); + mMenuNext = menu.add(0, getMenuActivityId(MENU_NEXT), 0, R.string.next); + } + } + return true; + } + @Override + public boolean onPrepareOptionsMenu(final Menu menu) { + super.onPrepareOptionsMenu(menu); + updatePrevNext(); + return true; + } + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + super.onOptionsItemSelected(item); + switch(getMenuOriginalId(item.getItemId())) { + case MENU_PREVIOUS: + if (mMovieList == null) { + return false; + } + getPlayer().startNextVideo(mMovieList.getPrevious(getMovieItem())); + return true; + case MENU_NEXT: + if (mMovieList == null) { + return false; + } + getPlayer().startNextVideo(mMovieList.getNext(getMovieItem())); + return true; + default: + return false; + } + } + + @Override + public void onMovieItemChanged(final IMovieItem item) { + super.onMovieItemChanged(item); + updatePrevNext(); + } + + private void updatePrevNext() { + if (LOG) { + Log.v(TAG, "updatePrevNext()"); + } + if (mMovieList != null && mMenuPrevious != null && mMenuNext != null) { + if (mMovieList.isFirst(getMovieItem()) && mMovieList.isLast(getMovieItem())) { //only one movie + mMenuNext.setVisible(false); + mMenuPrevious.setVisible(false); + } else { + mMenuNext.setVisible(true); + mMenuPrevious.setVisible(true); + } + if (mMovieList.isFirst(getMovieItem())) { + mMenuPrevious.setEnabled(false); + } else { + mMenuPrevious.setEnabled(true); + } + if (mMovieList.isLast(getMovieItem())) { + mMenuNext.setEnabled(false); + } else { + mMenuNext.setEnabled(true); + } + } + } + + @Override + public void onListLoaded(final IMovieList movieList) { + mMovieList = movieList; + getContext().invalidateOptionsMenu(); + if (LOG) { + Log.v(TAG, "onListLoaded() " + (mMovieList != null ? mMovieList.size() : "null")); + } + } +} diff --git a/src/org/codeaurora/gallery3d/video/MovieTitleHelper.java b/src/org/codeaurora/gallery3d/video/MovieTitleHelper.java new file mode 100644 index 000000000..4f23e81b8 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/MovieTitleHelper.java @@ -0,0 +1,108 @@ +package org.codeaurora.gallery3d.video; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; +import android.net.Uri; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.util.Log; + + +import java.io.File; + +public class MovieTitleHelper { + private static final String TAG = "MovieTitleHelper"; + private static final boolean LOG = false; + + public static String getTitleFromMediaData(final Context context, final Uri uri) { + String title = null; + Cursor cursor = null; + try { + String data = Uri.decode(uri.toString()); + data = data.replaceAll("'", "''"); + final String where = "_data LIKE '%" + data.replaceFirst("file:///", "") + "'"; + cursor = context.getContentResolver().query( + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + new String[] { + OpenableColumns.DISPLAY_NAME + }, where, null, null); + if (LOG) { + Log.v( + TAG, + "setInfoFromMediaData() cursor=" + + (cursor == null ? "null" : cursor.getCount())); + } + if (cursor != null && cursor.moveToFirst()) { + title = cursor.getString(0); + } + } catch (final SQLiteException ex) { + ex.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + if (LOG) { + Log.v(TAG, "setInfoFromMediaData() return " + title); + } + return title; + } + + public static String getTitleFromDisplayName(final Context context, final Uri uri) { + String title = null; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, + new String[] { + OpenableColumns.DISPLAY_NAME + }, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + title = cursor.getString(0); + } + } catch (final SQLiteException ex) { + ex.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + if (LOG) { + Log.v(TAG, "getTitleFromDisplayName() return " + title); + } + return title; + } + + public static String getTitleFromUri(final Uri uri) { + final String title = Uri.decode(uri.getLastPathSegment()); + if (LOG) { + Log.v(TAG, "getTitleFromUri() return " + title); + } + return title; + } + + public static String getTitleFromData(final Context context, final Uri uri) { + String title = null; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, + new String[] { + "_data" + }, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + final File file = new File(cursor.getString(0)); + title = file.getName(); + } + } catch (final SQLiteException ex) { + ex.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + if (LOG) { + Log.v(TAG, "getTitleFromData() return " + title); + } + return title; + } +} diff --git a/src/org/codeaurora/gallery3d/video/ScreenModeManager.java b/src/org/codeaurora/gallery3d/video/ScreenModeManager.java new file mode 100644 index 000000000..a1c04c69f --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/ScreenModeManager.java @@ -0,0 +1,117 @@ +package org.codeaurora.gallery3d.video; + +import android.util.Log; + +import java.util.ArrayList; + +public class ScreenModeManager { + private static final String TAG = "ScreenModeManager"; + private static final boolean LOG = false; + //support screen mode. + public static final int SCREENMODE_BIGSCREEN = 1; + public static final int SCREENMODE_FULLSCREEN = 2; + public static final int SCREENMODE_CROPSCREEN = 4; + public static final int SCREENMODE_ALL = 7; + + private int mScreenMode = SCREENMODE_BIGSCREEN; + private int mScreenModes = SCREENMODE_ALL; + + /** + * Enable specified screen mode list. + * The screen mode's value determines the order of being shown. + * <br>you can enable three screen modes by setting screenModes = + * {@link #SCREENMODE_BIGSCREEN} | + * {@link #SCREENMODE_FULLSCREEN} | + * {@link #SCREENMODE_CROPSCREEN} or + * just enable two screen modes by setting screenModes = + * {@link #SCREENMODE_BIGSCREEN} | + * {@link #SCREENMODE_CROPSCREEN}. + * <br>If current screen mode is the last one of the ordered list, + * then the next screen mode will be the first one of the ordered list. + * @param screenModes enabled screen mode list. + */ + public void setScreenModes(final int screenModes) { + mScreenModes = (SCREENMODE_BIGSCREEN & screenModes) + | (SCREENMODE_FULLSCREEN & screenModes) + | (SCREENMODE_CROPSCREEN & screenModes); + if ((screenModes & SCREENMODE_ALL) == 0) { + mScreenModes = SCREENMODE_ALL; + Log.w(TAG, "wrong screenModes=" + screenModes + ". use default value " + SCREENMODE_ALL); + } + if (LOG) { + Log.v(TAG, "enableScreenMode(" + screenModes + ") mScreenModes=" + mScreenModes); + } + } + + /** + * Get the all screen modes of media controller. + * <br><b>Note:</b> it is not the video's current screen mode. + * @return the current screen modes. + */ + public int getScreenModes() { + return mScreenModes; + } + + public void setScreenMode(final int curScreenMode) { + if (LOG) { + Log.v(TAG, "setScreenMode(" + curScreenMode + ")"); + } + mScreenMode = curScreenMode; + for (final ScreenModeListener listener : mListeners) { + listener.onScreenModeChanged(curScreenMode); + } + } + + public int getScreenMode() { + if (LOG) { + Log.v(TAG, "getScreenMode() return " + mScreenMode); + } + return mScreenMode; + } + + public int getNextScreenMode() { + int mode = getScreenMode(); + mode <<= 1; + if ((mode & mScreenModes) == 0) { + //not exist, find the right one + if (mode > mScreenModes) { + mode = 1; + } + while ((mode & mScreenModes) == 0) { + mode <<= 1; + if (mode > mScreenModes) { + throw new RuntimeException("wrong screen mode = " + mScreenModes); + } + } + } + if (LOG) { + Log.v(TAG, "getNextScreenMode() = " + mode); + } + return mode; + } + + private final ArrayList<ScreenModeListener> mListeners = new ArrayList<ScreenModeListener>(); + public void addListener(final ScreenModeListener l) { + if (!mListeners.contains(l)) { + mListeners.add(l); + } + if (LOG) { + Log.v(TAG, "addListener(" + l + ")"); + } + } + + public void removeListener(final ScreenModeListener l) { + mListeners.remove(l); + if (LOG) { + Log.v(TAG, "removeListener(" + l + ")"); + } + } + + public void clear() { + mListeners.clear(); + } + + public interface ScreenModeListener { + void onScreenModeChanged(int newMode); + } +} diff --git a/src/org/codeaurora/gallery3d/video/SettingsActivity.java b/src/org/codeaurora/gallery3d/video/SettingsActivity.java new file mode 100755 index 000000000..3d7fac573 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/SettingsActivity.java @@ -0,0 +1,308 @@ +package org.codeaurora.gallery3d.video; + +import android.app.ActionBar; +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.SystemProperties; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; +import android.preference.RingtonePreference; +import android.preference.PreferenceScreen; +import android.provider.ContactsContract; +import android.provider.Settings; +import android.provider.Settings.System; +import android.provider.Telephony; +import android.telephony.TelephonyManager; +import android.text.method.DigitsKeyListener; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MenuItem; +import com.android.gallery3d.R; + +import java.util.ArrayList; + +public class SettingsActivity extends PreferenceActivity { + + private static final String LOG_TAG = "SettingsActivity"; + + public static final String PREFERENCE_RTP_MINPORT = "rtp_min_port"; + public static final String PREFERENCE_RTP_MAXPORT = "rtp_max_port"; + private static final String PREFERENCE_KEEP_ALIVE_INTERVAL_SECOND = "keep_alive_interval_second"; + private static final String PREFERENCE_CACHE_MIN_SIZE = "cache_min_size"; + private static final String PREFERENCE_CACHE_MAX_SIZE = "cache_max_size"; + public static final String PREFERENCE_BUFFER_SIZE = "buffer_size"; + public static final String PREFERENCE_APN = "apn"; + private static final String PACKAGE_NAME = "com.android.settings"; + + private static final int DEFAULT_RTP_MINPORT = 8192; + private static final int DEFAULT_RTP_MAXPORT = 65535; + private static final int DEFAULT_CACHE_MIN_SIZE = 4 * 1024 * 1024; + private static final int DEFAULT_CACHE_MAX_SIZE = 20 * 1024 * 1024; + private static final int DEFAULT_KEEP_ALIVE_INTERVAL_SECOND = 15; + + private static final int RTP_MIN_PORT = 1; + private static final int RTP_MAX_PORT = 2; + private static final int BUFFER_SIZE = 3; + + private SharedPreferences mPref; + private EditTextPreference mRtpMinPort; + private EditTextPreference mRtpMaxPort; + private EditTextPreference mBufferSize; + private PreferenceScreen mApn; + + private static final int SELECT_APN = 1; + public static final String PREFERRED_APN_URI = "content://telephony/carriers/preferapn"; + private static final Uri PREFERAPN_URI = Uri.parse(PREFERRED_APN_URI); + private static final int COLUMN_ID_INDEX = 0; + private static final int NAME_INDEX = 1; + private static final String RTP_PORTS_PROPERTY_NAME = "persist.env.media.rtp-ports"; + private static final String CACHE_PROPERTY_NAME = "persist.env.media.cache-params"; + + private boolean mUseNvOperatorForEhrpd = SystemProperties.getBoolean( + "persist.radio.use_nv_for_ehrpd", false); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.rtsp_settings_preferences); + + mPref = getPreferenceScreen().getSharedPreferences(); + mRtpMinPort = (EditTextPreference) findPreference(PREFERENCE_RTP_MINPORT); + mRtpMaxPort = (EditTextPreference) findPreference(PREFERENCE_RTP_MAXPORT); + mBufferSize = (EditTextPreference) findPreference(PREFERENCE_BUFFER_SIZE); + mApn = (PreferenceScreen) findPreference(PREFERENCE_APN); + + setPreferenceListener(RTP_MIN_PORT, mRtpMinPort); + setPreferenceListener(RTP_MAX_PORT, mRtpMaxPort); + setPreferenceListener(BUFFER_SIZE, mBufferSize); + setApnListener(); + + ActionBar ab = getActionBar(); + ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + ab.setDisplayHomeAsUpEnabled(true); + ab.setTitle(R.string.setting); + } + + private String getApnKey() { + // to find default key + String key = null; + Cursor cursor = getContentResolver().query(PREFERAPN_URI, new String[] { + "_id" + }, null, null, Telephony.Carriers.DEFAULT_SORT_ORDER); + if (cursor.getCount() > 0 && cursor.moveToFirst()) { + key = cursor.getString(COLUMN_ID_INDEX); + Log.v("settingActivty", "default apn key = " + key); + } + cursor.close(); + return key; + } + + private String getApnName(String key) { + String name = null; + // to find default proxy + String where = getOperatorNumericSelection(); + + Cursor cursor = getContentResolver().query(Telephony.Carriers.CONTENT_URI, new String[] { + "_id", "name", "apn", "type" + }, where, null, Telephony.Carriers.DEFAULT_SORT_ORDER); + + while (cursor != null && cursor.moveToNext()) { + String curKey = cursor.getString(cursor.getColumnIndex("_id")); + String curName = cursor.getString(cursor.getColumnIndex("name")); + if (curKey.equals(key)) { + Log.d("rtsp", "getDefaultApnName, find, key=" + curKey + ",curName=" + curName); + name = curName; + break; + } + } + + if (cursor != null) { + cursor.close(); + } + return name; + + } + + private String getDefaultApnName() { + return getApnName(getApnKey()); + } + + private String getSelectedApnKey() { + String key = null; + + Cursor cursor = getContentResolver().query(PREFERAPN_URI, new String[] { + "_id", "name" + }, null, null, null); + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + key = cursor.getString(NAME_INDEX); + } + cursor.close(); + + Log.w("rtsp", "getSelectedApnKey key = " + key); + if (null == key) + return new String("null"); + return key; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == SELECT_APN) { + setResult(resultCode); + finish(); + Log.w(LOG_TAG, "onActivityResult requestCode = " + requestCode); + } + + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // TODO Auto-generated method stub + if (item.getItemId() == android.R.id.home) { + finish(); + } + return true; + } + + private String getOperatorNumericSelection() { + String[] mccmncs = getOperatorNumeric(); + String where; + where = (mccmncs[0] != null) ? "numeric=\"" + mccmncs[0] + "\"" : ""; + where += (mccmncs[1] != null) ? " or numeric=\"" + mccmncs[1] + "\"" : ""; + Log.d(LOG_TAG, "getOperatorNumericSelection: " + where); + return where; + } + + private String[] getOperatorNumeric() { + ArrayList<String> result = new ArrayList<String>(); + String mccMncFromSim = null; + if (mUseNvOperatorForEhrpd) { + String mccMncForEhrpd = SystemProperties.get("ro.cdma.home.operator.numeric", null); + if (mccMncForEhrpd != null && mccMncForEhrpd.length() > 0) { + result.add(mccMncForEhrpd); + } + } + + mccMncFromSim = TelephonyManager.getDefault().getSimOperator(); + + if (mccMncFromSim != null && mccMncFromSim.length() > 0) { + result.add(mccMncFromSim); + } + return result.toArray(new String[2]); + } + + private void enableRtpPortSetting() { + final String rtpMinPortStr = mPref.getString(PREFERENCE_RTP_MINPORT, + Integer.toString(DEFAULT_RTP_MINPORT)); + final String rtpMaxPortStr = mPref.getString(PREFERENCE_RTP_MAXPORT, + Integer.toString(DEFAULT_RTP_MAXPORT)); + // System property format: "rtpMinPort/rtpMaxPort" + final String propertyValue = rtpMinPortStr + "/" + rtpMaxPortStr; + Log.v(LOG_TAG, "set system property " + RTP_PORTS_PROPERTY_NAME + " : " + propertyValue); + SystemProperties.set(RTP_PORTS_PROPERTY_NAME, propertyValue); + } + + private void enableBufferSetting() { + final String bufferSizeStr = mPref.getString(PREFERENCE_BUFFER_SIZE, + Integer.toString(DEFAULT_CACHE_MAX_SIZE)); + final int cacheMaxSize; + final String ACTION_NAME = "org.codeaurora.gallery3d.video.STREAMING_SETTINGS_ENABLER"; + try { + cacheMaxSize = Integer.valueOf(bufferSizeStr); + } catch (NumberFormatException e) { + Log.e(LOG_TAG, "Failed to parse cache max size"); + return; + } + // System property format: "minCacheSizeKB/maxCacheSizeKB/keepAliveIntervalSeconds" + final String propertyValue = (DEFAULT_CACHE_MIN_SIZE / 1024) + "/" + + (cacheMaxSize / 1024) + "/" + DEFAULT_KEEP_ALIVE_INTERVAL_SECOND; + Log.v(LOG_TAG, "set system property " + CACHE_PROPERTY_NAME + " : " + propertyValue); + SystemProperties.set(CACHE_PROPERTY_NAME, propertyValue); + } + + private void setPreferenceListener(final int which, final EditTextPreference etp) { + + final String DIGITS_ACCEPTABLE = "0123456789"; + String summaryStr = ""; + String preferStr = ""; + + switch (which) { + case RTP_MIN_PORT: + preferStr = mPref.getString(PREFERENCE_RTP_MINPORT, + Integer.toString(DEFAULT_RTP_MINPORT)); + summaryStr = "streaming_min_udp_port"; + break; + case RTP_MAX_PORT: + preferStr = mPref.getString(PREFERENCE_RTP_MAXPORT, + Integer.toString(DEFAULT_RTP_MAXPORT)); + summaryStr = "streaming_max_udp_port"; + break; + case BUFFER_SIZE: + preferStr = mPref.getString(PREFERENCE_BUFFER_SIZE, + Integer.toString(DEFAULT_CACHE_MAX_SIZE)); + break; + default: + return; + + } + + final String summaryString = summaryStr; + etp.getEditText().setKeyListener(DigitsKeyListener.getInstance(DIGITS_ACCEPTABLE)); + etp.setSummary(preferStr); + etp.setText(preferStr); + etp.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + final String summary = newValue.toString(); + final int value; + try { + value = Integer.valueOf(summary); + } catch (NumberFormatException e) { + Log.e(LOG_TAG, "NumberFormatException"); + return false; + } + etp.setSummary(summary); + etp.setText(summary); + Log.d(LOG_TAG, "z66/z82 summary = " + summary); + if(which == RTP_MIN_PORT || which == RTP_MAX_PORT) { + System.putString(getContentResolver(), summaryString, summary); + enableRtpPortSetting(); + } else { + enableBufferSetting(); + } + return true; + } + }); + + } + + private void setApnListener() { + final String SUBSCRIPTION_KEY = "subscription"; + mApn.setSummary(getDefaultApnName()); + mApn.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(Settings.ACTION_APN_SETTINGS); + int subscription = 0; + try { + subscription = Settings.Global.getInt(SettingsActivity.this.getContentResolver(), + Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION); + } catch (Exception e) { + Log.d("SettingActivity", "Can't get subscription for Exception: " + e); + } finally { + intent.putExtra(SUBSCRIPTION_KEY, subscription); + } + startActivityForResult(intent, SELECT_APN); + return true; + } + }); + } +} diff --git a/src/org/codeaurora/gallery3d/video/SpeakerHooker.java b/src/org/codeaurora/gallery3d/video/SpeakerHooker.java new file mode 100644 index 000000000..9bf534872 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/SpeakerHooker.java @@ -0,0 +1,210 @@ +/* +Copyright (c) 2014, The Linux Foundation. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package org.codeaurora.gallery3d.video; + +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.os.Bundle; + +import com.android.gallery3d.R; + +public class SpeakerHooker extends MovieHooker { + + private static final int MENU_SPEAKER = 1; + + private AudioManager mAudioManager; + + private MenuItem mMenuSpeakerButton; + + private boolean mIsHeadsetOn = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initAudioManager(); + } + + private void initAudioManager() { + if (mAudioManager == null) { + mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + } + } + + @Override + public void onDestroy() { + turnSpeakerOff(); + super.onDestroy(); + } + + @Override + public void onResume() { + super.onResume(); + registerHeadSetReceiver(); + } + + private void registerHeadSetReceiver() { + 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); + getContext().registerReceiver(mHeadSetReceiver, intentFilter); + } + + @Override + public void onPause() { + unregisterHeadSetReceiver(); + super.onPause(); + } + + private void unregisterHeadSetReceiver() { + try { + getContext().unregisterReceiver(mHeadSetReceiver); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private final BroadcastReceiver mHeadSetReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (mAudioManager == null) { + initAudioManager(); + } + if (action.equals(Intent.ACTION_HEADSET_PLUG)) { + mIsHeadsetOn = (intent.getIntExtra("state", 0) == 1) + || mAudioManager.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) + || mAudioManager.isWiredHeadsetOn(); + } + } else if (action.equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { + mIsHeadsetOn = false; + } + updateSpeakerButton(); + if (!mIsHeadsetOn) { + turnSpeakerOff(); + } + } + }; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + mMenuSpeakerButton = menu.add(0, getMenuActivityId(MENU_SPEAKER), 0, R.string.speaker_on); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + super.onOptionsItemSelected(item); + switch (getMenuOriginalId(item.getItemId())) { + case MENU_SPEAKER: + changeSpeakerState(); + return true; + default: + return false; + } + } + + private void changeSpeakerState() { + if (isSpeakerOn()) { + turnSpeakerOff(); + } else { + if (mIsHeadsetOn) { + turnSpeakerOn(); + } else { + Toast.makeText(getContext(), getContext().getString(R.string.speaker_need_headset), + Toast.LENGTH_SHORT).show(); + } + } + updateSpeakerButton(); + } + + private void turnSpeakerOn() { + if (mAudioManager == null) { + initAudioManager(); + } + mAudioManager.setSpeakerphoneOn(true); + AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, + AudioSystem.FORCE_SPEAKER); + } + + private void turnSpeakerOff() { + if (mAudioManager == null) { + initAudioManager(); + } + AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, + AudioSystem.FORCE_NONE); + mAudioManager.setSpeakerphoneOn(false); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + updateSpeakerButton(); + return true; + } + + private void updateSpeakerButton() { + if (mMenuSpeakerButton != null) { + if (isSpeakerOn()) { + mMenuSpeakerButton.setTitle(R.string.speaker_off); + } else { + mMenuSpeakerButton.setTitle(R.string.speaker_on); + } + } + } + + private boolean isSpeakerOn() { + boolean isSpeakerOn = false; + if (mAudioManager == null) { + initAudioManager(); + } + isSpeakerOn = mAudioManager.isSpeakerphoneOn(); + return isSpeakerOn; + } + +} diff --git a/src/org/codeaurora/gallery3d/video/StepOptionDialogFragment.java b/src/org/codeaurora/gallery3d/video/StepOptionDialogFragment.java new file mode 100644 index 000000000..50bd8a669 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/StepOptionDialogFragment.java @@ -0,0 +1,83 @@ +package org.codeaurora.gallery3d.video; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.os.Bundle; + +/** M: use DialogFragment to show Dialog */ +public class StepOptionDialogFragment extends DialogFragment implements + DialogInterface.OnClickListener{ + + private static final String KEY_ITEM_ARRAY = "itemArray"; + private static final String KEY_TITLE = "title"; + private static final String KEY_DEFAULT_SELECT = "nowSelect"; + private DialogInterface.OnClickListener mClickListener = null; + + /** + * M: create a instance of SelectDialogFragment + * + * @param itemArrayID + * the resource id array of strings that show in list + * @param sufffixArray + * the suffix array at the right of list item + * @param titleID + * the resource id of title string + * @param nowSelect + * the current select item index + * @return the instance of SelectDialogFragment + */ + public static StepOptionDialogFragment newInstance(int[] itemArrayID, + int titleID, int nowSelect) { + StepOptionDialogFragment frag = new StepOptionDialogFragment(); + Bundle args = new Bundle(); + args.putIntArray(KEY_ITEM_ARRAY, itemArrayID); + args.putInt(KEY_TITLE, titleID); + args.putInt(KEY_DEFAULT_SELECT, nowSelect); + frag.setArguments(args); + return frag; + } + + @Override + /** + * M: create a select dialog + */ + public Dialog onCreateDialog(Bundle savedInstanceState) { + Bundle args = getArguments(); + final String title = getString(args.getInt(KEY_TITLE)); + final int[] itemArrayID = args.getIntArray(KEY_ITEM_ARRAY); + int arraySize = itemArrayID.length; + CharSequence[] itemArray = new CharSequence[arraySize]; + for (int i = 0; i < arraySize; i++) { + itemArray[i] = getString(itemArrayID[i]); + } + + AlertDialog.Builder builder = null; + int nowSelect = args.getInt(KEY_DEFAULT_SELECT); + builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(title).setSingleChoiceItems(itemArray, nowSelect, this) + .setNegativeButton(getString(android.R.string.cancel), null); + return builder.create(); + } + + @Override + /** + * M: the process of select an item + */ + public void onClick(DialogInterface arg0, int arg1) { + if (null != mClickListener) { + mClickListener.onClick(arg0, arg1); + } + } + + /** + * M: set listener of click items + * + * @param listener + * the listener to be set + */ + public void setOnClickListener(DialogInterface.OnClickListener listener) { + mClickListener = listener; + } +}
\ No newline at end of file diff --git a/src/org/codeaurora/gallery3d/video/StepOptionSettingsHooker.java b/src/org/codeaurora/gallery3d/video/StepOptionSettingsHooker.java new file mode 100644 index 000000000..eff8057bd --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/StepOptionSettingsHooker.java @@ -0,0 +1,41 @@ +package org.codeaurora.gallery3d.video; + +import android.content.Intent; +import android.view.Menu; +import android.view.MenuItem; + +import com.android.gallery3d.R; +import com.android.gallery3d.app.MovieActivity; +import org.codeaurora.gallery3d.ext.ActivityHooker; +import org.codeaurora.gallery3d.video.VideoSettingsActivity; + +public class StepOptionSettingsHooker extends ActivityHooker { + private static final int MENU_STEP_OPTION_SETTING = 1; + private MenuItem mMenuStepOption; + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + mMenuStepOption = menu.add(0, getMenuActivityId(MENU_STEP_OPTION_SETTING), 0, R.string.settings); + return true; + } + @Override + public boolean onPrepareOptionsMenu(final Menu menu) { + super.onPrepareOptionsMenu(menu); + return true; + } + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + super.onOptionsItemSelected(item); + switch(getMenuOriginalId(item.getItemId())) { + case MENU_STEP_OPTION_SETTING: + //start activity + Intent mIntent = new Intent(); + mIntent.setClass(getContext(), VideoSettingsActivity.class); + getContext().startActivity(mIntent); + return true; + default: + return false; + } + } +}
\ No newline at end of file diff --git a/src/org/codeaurora/gallery3d/video/StereoAudioHooker.java b/src/org/codeaurora/gallery3d/video/StereoAudioHooker.java new file mode 100755 index 000000000..cbf2f357a --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/StereoAudioHooker.java @@ -0,0 +1,118 @@ +package org.codeaurora.gallery3d.video; + +import android.content.Context; +import android.media.AudioManager; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; + +import com.android.gallery3d.R; + +public class StereoAudioHooker extends MovieHooker { + private static final String TAG = "StereoAudioHooker"; + private static final boolean LOG = false; + + private static final int MENU_STEREO_AUDIO = 1; + private MenuItem mMenuStereoAudio; + + private static final String KEY_STEREO = "EnableStereoOutput"; + private boolean mSystemStereoAudio; + private boolean mCurrentStereoAudio; + private boolean mIsInitedStereoAudio; + private AudioManager mAudioManager; + + @Override + public void onStart() { + super.onStart(); + enableStereoAudio(); + } + + @Override + public void onStop() { + super.onStop(); + restoreStereoAudio(); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + mMenuStereoAudio = menu.add(0, getMenuActivityId(MENU_STEREO_AUDIO), 0, + R.string.single_track); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(final Menu menu) { + super.onPrepareOptionsMenu(menu); + updateStereoAudioIcon(); + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + super.onOptionsItemSelected(item); + if(getMenuOriginalId(item.getItemId()) == MENU_STEREO_AUDIO) { + mCurrentStereoAudio = !mCurrentStereoAudio; + setStereoAudio(mCurrentStereoAudio); + return true; + } + return false; + } + + private boolean getStereoAudio() { + boolean isstereo = false; + if (mAudioManager == null) { + mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + } + final String stereo = mAudioManager.getParameters(KEY_STEREO); + final String key = KEY_STEREO + "=1"; + if (stereo != null && stereo.indexOf(key) > -1) { + isstereo = true; + } else { + isstereo = false; + } + if (LOG) { + Log.v(TAG, "getStereoAudio() isstereo=" + isstereo + ", stereo=" + stereo + + ", key=" + key); + } + return isstereo; + } + + private void setStereoAudio(final boolean flag) { + final String value = KEY_STEREO + "=" + (flag ? "1" : "0"); + if (mAudioManager == null) { + mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + } + mAudioManager.setParameters(value); + if (LOG) { + Log.v(TAG, "setStereoAudio(" + flag + ") value=" + value); + } + } + + private void updateStereoAudioIcon() { + if (mMenuStereoAudio != null) { + mMenuStereoAudio.setTitle(mCurrentStereoAudio?R.string.single_track:R.string.stereo); + mMenuStereoAudio.setIcon(mCurrentStereoAudio?R.drawable.ic_menu_single_track:R.drawable.ic_menu_stereo); + } + } + + private void enableStereoAudio() { + if (LOG) { + Log.v(TAG, "enableStereoAudio() mIsInitedStereoAudio=" + mIsInitedStereoAudio + + ", mCurrentStereoAudio=" + mCurrentStereoAudio); + } + mSystemStereoAudio = getStereoAudio(); + if (!mIsInitedStereoAudio) { + mCurrentStereoAudio = mSystemStereoAudio; + mIsInitedStereoAudio = true; + } else { + // restore old stereo type + setStereoAudio(mCurrentStereoAudio); + } + updateStereoAudioIcon(); + } + + private void restoreStereoAudio() { + setStereoAudio(mSystemStereoAudio); + } +} diff --git a/src/org/codeaurora/gallery3d/video/StreamingHooker.java b/src/org/codeaurora/gallery3d/video/StreamingHooker.java new file mode 100755 index 000000000..55735f44c --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/StreamingHooker.java @@ -0,0 +1,86 @@ +package org.codeaurora.gallery3d.video; + +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.net.Uri; +import android.provider.Browser; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +import com.android.gallery3d.R; +import org.codeaurora.gallery3d.ext.MovieUtils; + +public class StreamingHooker extends MovieHooker { + private static final String TAG = "StreamingHooker"; + private static final boolean LOG = false; + + private static final String ACTION_STREAMING = "org.codeaurora.settings.streaming"; + private static final int MENU_INPUT_URL = 1; + private static final int MENU_SETTINGS = 2; + private static final int MENU_DETAIL = 3; + + public static final String KEY_LOGO_BITMAP = "logo-bitmap"; + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + // when in rtsp streaming type, generally it only has one uri. + menu.add(0, getMenuActivityId(MENU_INPUT_URL), 0, R.string.input_url); + menu.add(0, getMenuActivityId(MENU_SETTINGS), 0, R.string.streaming_settings); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(final Menu menu) { + super.onPrepareOptionsMenu(menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + super.onOptionsItemSelected(item); + switch (getMenuOriginalId(item.getItemId())) { + case MENU_INPUT_URL: + gotoInputUrl(); + return true; + case MENU_SETTINGS: + gotoSettings(); + return true; + default: + return false; + } + } + + private void gotoInputUrl() { + final String APN_NAME = getClass().getName(); + final String URI_STR = "about:blank"; + final String EXTRA_NAME = "inputUrl"; + + final Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(URI_STR)); + intent.putExtra(EXTRA_NAME, true); + intent.putExtra(Browser.EXTRA_APPLICATION_ID, APN_NAME); + + try { + getContext().startActivity(intent); + } catch (ActivityNotFoundException e) { + Toast.makeText(getContext(), + R.string.fail_to_load, Toast.LENGTH_LONG).show(); + } + + if (LOG) { + Log.v(TAG, "gotoInputUrl() appName=" + APN_NAME); + } + } + + private void gotoSettings() { + final Intent intent = new Intent(ACTION_STREAMING); + getContext().startActivity(intent); + if (LOG) { + Log.v(TAG, "gotoInputUrl()"); + } + } +} diff --git a/src/org/codeaurora/gallery3d/video/VideoSettingsActivity.java b/src/org/codeaurora/gallery3d/video/VideoSettingsActivity.java new file mode 100644 index 000000000..32ccfe70f --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/VideoSettingsActivity.java @@ -0,0 +1,182 @@ +package org.codeaurora.gallery3d.video; + +import android.app.ListActivity; + +import android.app.ActionBar; +import android.app.Activity; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.SimpleAdapter; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.android.gallery3d.R; + +public class VideoSettingsActivity extends ListActivity { + private String OPTION_NAME = "option_name"; + private String OPTION_DESC = "option_desc"; + private String DIALOG_TAG_SELECT_STEP_OPTION = "step_option_dialog"; + private static int[] sStepOptionArray = null; + private static final int STEP_OPTION_THREE_SECOND = 0; + private static final int STEP_OPTION_SIX_SECOND = 1; + private static final String SELECTED_STEP_OPTION = "selected_step_option"; + private static final String VIDEO_PLAYER_DATA = "video_player_data"; + private int mSelectedStepOption = -1; + private SharedPreferences mPrefs = null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ActionBar actionBar = getActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setTitle(getResources().getString(R.string.settings)); + setContentView(R.layout.setting_list); + ArrayList<HashMap<String, Object>> arrlist = new ArrayList<HashMap<String, Object>>(1); + HashMap<String, Object> map = new HashMap<String, Object>(); + map.put(OPTION_NAME, getString(R.string.setp_option_name)); + map.put(OPTION_DESC, getString(R.string.step_option_desc)); + arrlist.add(map); + SimpleAdapter adapter = new SimpleAdapter(this, arrlist, android.R.layout.simple_expandable_list_item_2, + new String[] { OPTION_NAME, OPTION_DESC }, new int[] { + android.R.id.text1, android.R.id.text2}); + setListAdapter(adapter); + restoreStepOptionSettings(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + storeStepOptionSettings(); + super.onSaveInstanceState(outState); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + restoreDialogFragment(); + restoreStepOptionSettings(); + } + + + + @Override + protected void onDestroy() { + // TODO Auto-generated method stub + storeStepOptionSettings(); + super.onDestroy(); + } + + @Override + protected void onListItemClick(ListView arg0, View arg1, int arg2, long arg3) { + switch (arg2) { + case 0: + DialogFragment newFragment = null; + FragmentManager fragmentManager = getFragmentManager(); + removeOldFragmentByTag(DIALOG_TAG_SELECT_STEP_OPTION); + newFragment = StepOptionDialogFragment.newInstance(getStepOptionIDArray(), + R.string.setp_option_name, mSelectedStepOption); + ((StepOptionDialogFragment) newFragment).setOnClickListener(mStepOptionSelectedListener); + newFragment.show(fragmentManager, DIALOG_TAG_SELECT_STEP_OPTION); + break; + default: + break; + } + } + + private int[] getStepOptionIDArray() { + int[] stepOptionIDArray = new int[2]; + stepOptionIDArray[STEP_OPTION_THREE_SECOND] = R.string.setp_option_three_second; + stepOptionIDArray[STEP_OPTION_SIX_SECOND] = R.string.setp_option_six_second; + sStepOptionArray = new int[2]; + sStepOptionArray[0] = STEP_OPTION_THREE_SECOND; + sStepOptionArray[1] = STEP_OPTION_SIX_SECOND; + return stepOptionIDArray; + } + + private DialogInterface.OnClickListener mStepOptionSelectedListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichItemSelect) { + setSelectedStepOption(whichItemSelect); + dialog.dismiss(); + } + }; + + public void setSelectedStepOption(int which) { + mSelectedStepOption = getSelectedStepOption(which); + } + + static int getSelectedStepOption(int which) { + return sStepOptionArray[which]; + } + + /** + * remove old DialogFragment + * + * @param tag + * the tag of DialogFragment to be removed + */ + private void removeOldFragmentByTag(String tag) { + FragmentManager fragmentManager = getFragmentManager(); + DialogFragment oldFragment = (DialogFragment) fragmentManager.findFragmentByTag(tag); + if (null != oldFragment) { + oldFragment.dismissAllowingStateLoss(); + } + } + + private void restoreDialogFragment() { + FragmentManager fragmentManager = getFragmentManager(); + Fragment fragment = fragmentManager.findFragmentByTag(DIALOG_TAG_SELECT_STEP_OPTION); + if (null != fragment) { + ((StepOptionDialogFragment) fragment).setOnClickListener(mStepOptionSelectedListener); + } + } + + private void storeStepOptionSettings() { + if (null == mPrefs) { + mPrefs = getSharedPreferences(VIDEO_PLAYER_DATA, 0); + } + SharedPreferences.Editor ed = mPrefs.edit(); + ed.clear(); + ed.putInt(SELECTED_STEP_OPTION, mSelectedStepOption); + ed.commit(); + } + + private void restoreStepOptionSettings() { + if (null == mPrefs) { + mPrefs = getSharedPreferences(VIDEO_PLAYER_DATA, 0); + } + mSelectedStepOption = mPrefs.getInt(SELECTED_STEP_OPTION, STEP_OPTION_THREE_SECOND); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + // The user clicked on the Messaging icon in the action bar. Take them back from + // wherever they came from + finish(); + return true; + } + return false; + } +} |