Forráskód Böngészése

Create our own media control - basic operation

David A. Velasco 12 éve
szülő
commit
d68b324653

+ 33 - 23
res/layout/file_preview.xml

@@ -19,36 +19,46 @@
   
 -->
 
-<!--
-     ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/fdScrollView"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:background="@color/owncloud_white" 
-    android:gravity="center_horizontal"
--->
-
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/top"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:background="@color/owncloud_white"
     android:gravity="center"
     tools:context=".ui.fragment.FilePreviewFragment" >
 
-    <ImageView
-        android:id="@+id/image_preview"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_margin="16dp"
-        android:contentDescription="@string/preview_image_description"
-        android:src="@drawable/owncloud_logo" />
+    <FrameLayout 
+        android:id="@+id/visual_area"
+   	    android:layout_width="match_parent"
+   	    android:layout_height="wrap_content"
+   	    android:layout_alignParentTop="true"
+        >
     
-   	<VideoView  
-   	    android:id="@+id/video_preview"
-		android:layout_width="wrap_content"
-		android:layout_height="wrap_content"  
-		android:layout_gravity="center" />
+	    <ImageView
+	        android:id="@+id/image_preview"
+	        android:layout_width="wrap_content"
+	        android:layout_height="wrap_content"
+	        android:layout_margin="16dp"
+	        android:layout_gravity="center"
+	        android:contentDescription="@string/preview_image_description"
+	        android:src="@drawable/owncloud_logo" />
+	    
+	   	<VideoView  
+	   	    android:id="@+id/video_preview"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"  
+			android:layout_gravity="center" 
+			android:visibility="gone"
+			/>
+	   	
+   	</FrameLayout>
+   	
+   	<com.owncloud.android.media.MediaControlView 
+   	    android:id="@+id/media_controller"
+   	    android:layout_width="match_parent"
+   	    android:layout_height="wrap_content"
+   	    android:layout_below="@id/visual_area"
+   	    />
 
 </RelativeLayout>

+ 104 - 0
res/layout/media_control.xml

@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ownCloud Android client application
+
+  Copyright (C) 2012-2013  ownCloud Inc.
+  
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:paddingTop="4dip"
+        android:orientation="horizontal"
+        >
+
+        <ImageButton 
+            android:id="@+id/previousBtn" 
+            style="@android:style/MediaButton.Previous" 
+            android:contentDescription="@string/media_previous_description"
+            />
+        <ImageButton 
+            android:id="@+id/rewindBtn" 
+            style="@android:style/MediaButton.Rew" 
+            android:contentDescription="@string/media_rewind_description"
+            />
+        <ImageButton 
+            android:id="@+id/playBtn" 
+            style="@android:style/MediaButton.Play" 
+            android:contentDescription="@string/media_play_pause_description"
+            />
+        <ImageButton 
+            android:id="@+id/forwardBtn" 
+            style="@android:style/MediaButton.Ffwd" 
+            android:contentDescription="@string/media_forward_description"
+            />
+        <ImageButton 
+            android:id="@+id/nextBtn" 
+            style="@android:style/MediaButton.Next" 
+            android:contentDescription="@string/media_next_description"
+            />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <TextView 
+            android:id="@+id/currentTimeText"
+            android:textSize="14sp"
+            android:textStyle="bold"
+            android:paddingTop="4dip"
+            android:paddingStart="4dip"
+            android:layout_gravity="center_horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingEnd="4dip"
+            android:text="@string/placeholder_media_time"
+            />
+
+        <SeekBar
+            android:id="@+id/progressBar"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="0dip"
+            android:layout_weight="1"
+            android:layout_height="32dip"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentEnd="true" />
+
+        <TextView android:id="@+id/totalTimeText"
+            android:textSize="14sp"
+            android:textStyle="bold"
+            android:paddingTop="4dip"
+            android:paddingEnd="4dip"
+            android:layout_gravity="center_horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingStart="4dip"
+            android:text="@string/placeholder_media_time"
+            />
+        
+    </LinearLayout>
+
+</LinearLayout>

+ 8 - 2
res/values/strings.xml

@@ -89,7 +89,7 @@
     <string name="common_exit">Leave %1$s</string>
     <string name="common_error">Error</string>
     <string name="common_loading">Loading &#8230;</string>
-    <string name="common_error_unknown">Unkown error</string>
+    <string name="common_error_unknown">Unknown error</string>
     <string name="about_title">About</string>
     
     <string name="delete_account">Delete account</string>
@@ -162,7 +162,12 @@
     <string name="media_err_security_ex">Security error trying to play %1$s</string>
 	<string name="media_err_io_ex">Input error trying to play %1$s</string>
 	<string name="media_err_unexpected">Unexpected error tryung to playe %1$s</string>
-            
+	<string name="media_previous_description">Previous track button</string>
+	<string name="media_rewind_description">Rewind button</string>
+	<string name="media_play_pause_description">Play or pause button</string>
+	<string name="media_forward_description">Fast forward button</string>
+	<string name="media_next_description">Next track button</string>
+	
     <string-array name="prefs_trackmydevice_intervall_keys">
     	<item>15 Minutes</item>
     	<item>30 Minutes</item>
@@ -267,6 +272,7 @@
     <string name="placeholder_filetype">PNG Image</string>
     <string name="placeholder_filesize">389 KB</string>
     <string name="placeholder_timestamp">2012/05/18 12:23 PM</string>
+    <string name="placeholder_media_time">12:23:45</string>
     
     <string name="instant_upload_on_wifi">Upload pictures via WiFi only</string>
 	<string name="instant_upload_path">/InstantUpload</string>

+ 688 - 0
src/com/owncloud/android/media/MediaControlView.java

@@ -0,0 +1,688 @@
+/* ownCloud Android client application
+ * 
+ *   Copyright (C) 2012-2013  ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.media;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.MediaController.MediaPlayerControl;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import java.util.Formatter;
+import java.util.Locale;
+
+import com.owncloud.android.R;
+
+/**
+ * View containing controls for a {@link MediaPlayer}. 
+ * 
+ * Holds buttons "play / pause", "rewind", "fast forward" 
+ * and a progress slider. 
+ * 
+ * It synchronizes itself with the state of the 
+ * {@link MediaPlayer}.
+ * 
+ * @author David A. Velasco
+ */
+
+public class MediaControlView extends FrameLayout implements /* OnLayoutChangeListener, */ OnTouchListener {
+
+    private static final String TAG = MediaControlView.class.getSimpleName();
+
+    
+    private MediaPlayerControl  mPlayer;
+    private Context             mContext;
+    private View                mAnchor;
+    private View                mRoot;
+    private WindowManager       mWindowManager;
+    //private Window              mWindow;
+    private View                mDecor;
+    private WindowManager.LayoutParams mDecorLayoutParams;
+    private ProgressBar         mProgress;
+    private TextView            mEndTime, mCurrentTime;
+    private boolean             mShowing;
+    private boolean             mDragging;
+    private static final int    sDefaultTimeout = 3000;
+    private static final int    FADE_OUT = 1;
+    private static final int    SHOW_PROGRESS = 2;
+    private boolean             mUseFastForward;
+    private boolean             mFromXml;
+    private boolean             mListenersSet;
+    private View.OnClickListener mNextListener, mPrevListener;
+    StringBuilder               mFormatBuilder;
+    Formatter                   mFormatter;
+    private ImageButton         mPauseButton;
+    private ImageButton         mFfwdButton;
+    private ImageButton         mRewButton;
+    private ImageButton         mNextButton;
+    private ImageButton         mPrevButton;
+    
+    public MediaControlView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        //mRoot = this;           // TODO review if this is adequate
+        mContext = context;
+        mUseFastForward = true;
+        mFromXml = true;
+        
+        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        
+        FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT
+        );
+        //removeAllViews();
+        LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mRoot = inflate.inflate(R.layout.media_control, null);
+        initControllerView(mRoot);
+        addView(mRoot, frameParams);
+        
+        setFocusable(true);
+        setFocusableInTouchMode(true);
+        setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+        requestFocus();
+    }
+
+    @Override
+    public void onFinishInflate() {
+        if (mRoot != null)
+            initControllerView(mRoot);
+    }
+
+    /* TODO REMOVE
+    public MediaControlView(Context context, boolean useFastForward) {
+        super(context);
+        mContext = context;
+        mUseFastForward = useFastForward;
+        initFloatingWindowLayout();
+        //initFloatingWindow();
+    }
+    */
+
+    /* TODO REMOVE
+    public MediaControlView(Context context) {
+        this(context, true);
+    }
+    */
+    
+    /* T
+    private void initFloatingWindow() {
+        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        mWindow = PolicyManager.makeNewWindow(mContext);
+        mWindow.setWindowManager(mWindowManager, null, null);
+        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+        mDecor = mWindow.getDecorView();
+        mDecor.setOnTouchListener(mTouchListener);
+        mWindow.setContentView(this);
+        mWindow.setBackgroundDrawableResource(android.R.color.transparent);
+        
+        // While the media controller is up, the volume control keys should
+        // affect the media stream type
+        mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+        setFocusable(true);
+        setFocusableInTouchMode(true);
+        setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+        requestFocus();
+    }
+    */
+
+    /*
+    // Allocate and initialize the static parts of mDecorLayoutParams. Must
+    // also call updateFloatingWindowLayout() to fill in the dynamic parts
+    // (y and width) before mDecorLayoutParams can be used.
+    private void initFloatingWindowLayout() {
+        mDecorLayoutParams = new WindowManager.LayoutParams();
+        WindowManager.LayoutParams p = mDecorLayoutParams;
+        p.gravity = Gravity.TOP;
+        p.height = LayoutParams.WRAP_CONTENT;
+        p.x = 0;
+        p.format = PixelFormat.TRANSLUCENT;
+        p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+        p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+        p.token = null;
+        p.windowAnimations = 0; // android.R.style.DropDownAnimationDown;
+    }
+    */
+
+    // Update the dynamic parts of mDecorLayoutParams
+    // Must be called with mAnchor != NULL.
+    private void updateFloatingWindowLayout() {
+        int [] anchorPos = new int[2];
+        mAnchor.getLocationOnScreen(anchorPos);
+
+        WindowManager.LayoutParams p = mDecorLayoutParams;
+        p.width = mAnchor.getWidth();
+        p.y = anchorPos[1] + mAnchor.getHeight();
+    }
+
+    /*
+    // This is called whenever mAnchor's layout bound changes
+    public void onLayoutChange(View v, int left, int top, int right,
+            int bottom, int oldLeft, int oldTop, int oldRight,
+            int oldBottom) {
+        //updateFloatingWindowLayout();
+        if (mShowing) {
+            mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams);
+        }
+    }
+    */
+    
+    public boolean onTouch(View v, MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            if (mShowing) {
+                hide();
+            }
+        }
+            return false;
+    }
+    
+    
+    public void setMediaPlayer(MediaPlayerControl player) {
+        mPlayer = player;
+        updatePausePlay();
+    }
+
+    /*
+    /**
+     * Set the view that acts as the anchor for the control view.
+     * This can for example be a VideoView, or your Activity's main view.
+     * @param view The view to which to anchor the controller when it is visible.
+     *-/
+    public void setAnchorView(View view) {
+        if (mAnchor != null) {
+            mAnchor.removeOnLayoutChangeListener(this);
+        }
+        mAnchor = view;
+        if (mAnchor != null) {
+            mAnchor.addOnLayoutChangeListener(this);
+        }
+
+        FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT
+        );
+
+        removeAllViews();
+        View v = makeControllerView();
+        addView(v, frameParams);
+    }
+    */
+    
+    /*
+    /**
+     * Create the view that holds the widgets that control playback.
+     * Derived classes can override this to create their own.
+     * @return The controller view.
+     * @hide This doesn't work as advertised
+     *-/
+    protected View makeControllerView() {
+        LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mRoot = inflate.inflate(R.layout.media_control, null);
+
+        initControllerView(mRoot);
+
+        return mRoot;
+    }
+    */
+
+    private void initControllerView(View v) {
+        mPauseButton = (ImageButton) v.findViewById(R.id.playBtn);
+        if (mPauseButton != null) {
+            mPauseButton.requestFocus();
+            mPauseButton.setOnClickListener(mPauseListener);
+        }
+
+        mFfwdButton = (ImageButton) v.findViewById(R.id.forwardBtn);
+        if (mFfwdButton != null) {
+            mFfwdButton.setOnClickListener(mFfwdListener);
+            if (!mFromXml) {
+                mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
+            }
+        }
+
+        mRewButton = (ImageButton) v.findViewById(R.id.rewindBtn);
+        if (mRewButton != null) {
+            mRewButton.setOnClickListener(mRewListener);
+            if (!mFromXml) {
+                mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
+            }
+        }
+
+        // By default these are hidden. They will be enabled when setPrevNextListeners() is called 
+        mNextButton = (ImageButton) v.findViewById(R.id.nextBtn);
+        if (mNextButton != null && !mFromXml && !mListenersSet) {
+            mNextButton.setVisibility(View.GONE);
+        }
+        mPrevButton = (ImageButton) v.findViewById(R.id.previousBtn);
+        if (mPrevButton != null && !mFromXml && !mListenersSet) {
+            mPrevButton.setVisibility(View.GONE);
+        }
+
+        mProgress = (ProgressBar) v.findViewById(R.id.progressBar);
+        if (mProgress != null) {
+            if (mProgress instanceof SeekBar) {
+                SeekBar seeker = (SeekBar) mProgress;
+                seeker.setOnSeekBarChangeListener(mSeekListener);
+            }
+            mProgress.setMax(1000);
+        }
+
+        mEndTime = (TextView) v.findViewById(R.id.totalTimeText);
+        mCurrentTime = (TextView) v.findViewById(R.id.currentTimeText);
+        mFormatBuilder = new StringBuilder();
+        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
+
+        installPrevNextListeners();
+    }
+
+    /**
+     * Show the controller on screen. It will go away
+     * automatically after 3 seconds of inactivity.
+     */
+    public void show() {
+        show(sDefaultTimeout);
+    }
+
+    /**
+     * Disable pause or seek buttons if the stream cannot be paused or seeked.
+     * This requires the control interface to be a MediaPlayerControlExt
+     */
+    private void disableUnsupportedButtons() {
+        try {
+            if (mPauseButton != null && !mPlayer.canPause()) {
+                mPauseButton.setEnabled(false);
+            }
+            if (mRewButton != null && !mPlayer.canSeekBackward()) {
+                mRewButton.setEnabled(false);
+            }
+            if (mFfwdButton != null && !mPlayer.canSeekForward()) {
+                mFfwdButton.setEnabled(false);
+            }
+        } catch (IncompatibleClassChangeError ex) {
+            // We were given an old version of the interface, that doesn't have
+            // the canPause/canSeekXYZ methods. This is OK, it just means we
+            // assume the media can be paused and seeked, and so we don't disable
+            // the buttons.
+        }
+    }
+    
+    /**
+     * Show the controller on screen. It will go away
+     * automatically after 'timeout' milliseconds of inactivity.
+     * @param timeout The timeout in milliseconds. Use 0 to show
+     * the controller until hide() is called.
+     */
+    public void show(int timeout) {
+        if (!mShowing && mAnchor != null) {
+            setProgress();
+            if (mPauseButton != null) {
+                mPauseButton.requestFocus();
+            }
+            disableUnsupportedButtons();
+            //updateFloatingWindowLayout();
+            mWindowManager.addView(mDecor, mDecorLayoutParams);
+            mShowing = true;
+        }
+        updatePausePlay();
+        
+        // cause the progress bar to be updated even if mShowing
+        // was already true.  This happens, for example, if we're
+        // paused with the progress bar showing the user hits play.
+        mHandler.sendEmptyMessage(SHOW_PROGRESS);
+
+        Message msg = mHandler.obtainMessage(FADE_OUT);
+        if (timeout != 0) {
+            mHandler.removeMessages(FADE_OUT);
+            mHandler.sendMessageDelayed(msg, timeout);
+        }
+    }
+    
+    public boolean isShowing() {
+        return mShowing;
+    }
+
+    /**
+     * Remove the controller from the screen.
+     */
+    public void hide() {
+        /*
+        if (mAnchor == null)
+            return;
+
+        if (mShowing) {
+            try {
+                mHandler.removeMessages(SHOW_PROGRESS);
+                mWindowManager.removeView(mDecor);
+            } catch (IllegalArgumentException ex) {
+                Log.w(TAG, "already removed");
+            }
+            mShowing = false;
+        }
+        */
+    }
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            int pos;
+            switch (msg.what) {
+                case FADE_OUT:
+                    hide();
+                    break;
+                case SHOW_PROGRESS:
+                    pos = setProgress();
+                    if (!mDragging && mShowing && mPlayer.isPlaying()) {
+                        msg = obtainMessage(SHOW_PROGRESS);
+                        sendMessageDelayed(msg, 1000 - (pos % 1000));
+                    }
+                    break;
+            }
+        }
+    };
+
+    private String stringForTime(int timeMs) {
+        int totalSeconds = timeMs / 1000;
+
+        int seconds = totalSeconds % 60;
+        int minutes = (totalSeconds / 60) % 60;
+        int hours   = totalSeconds / 3600;
+
+        mFormatBuilder.setLength(0);
+        if (hours > 0) {
+            return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
+        } else {
+            return mFormatter.format("%02d:%02d", minutes, seconds).toString();
+        }
+    }
+
+    private int setProgress() {
+        if (mPlayer == null || mDragging) {
+            return 0;
+        }
+        int position = mPlayer.getCurrentPosition();
+        int duration = mPlayer.getDuration();
+        if (mProgress != null) {
+            if (duration > 0) {
+                // use long to avoid overflow
+                long pos = 1000L * position / duration;
+                mProgress.setProgress( (int) pos);
+            }
+            int percent = mPlayer.getBufferPercentage();
+            mProgress.setSecondaryProgress(percent * 10);
+        }
+
+        if (mEndTime != null)
+            mEndTime.setText(stringForTime(duration));
+        if (mCurrentTime != null)
+            mCurrentTime.setText(stringForTime(position));
+
+        return position;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        show(sDefaultTimeout);
+        return true;
+    }
+
+    @Override
+    public boolean onTrackballEvent(MotionEvent ev) {
+        show(sDefaultTimeout);
+        return false;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        int keyCode = event.getKeyCode();
+        final boolean uniqueDown = event.getRepeatCount() == 0
+                && event.getAction() == KeyEvent.ACTION_DOWN;
+        if (keyCode ==  KeyEvent.KEYCODE_HEADSETHOOK
+                || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+                || keyCode == KeyEvent.KEYCODE_SPACE) {
+            if (uniqueDown) {
+                doPauseResume();
+                show(sDefaultTimeout);
+                if (mPauseButton != null) {
+                    mPauseButton.requestFocus();
+                }
+            }
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
+            if (uniqueDown && !mPlayer.isPlaying()) {
+                mPlayer.start();
+                updatePausePlay();
+                show(sDefaultTimeout);
+            }
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
+                || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
+            if (uniqueDown && mPlayer.isPlaying()) {
+                mPlayer.pause();
+                updatePausePlay();
+                show(sDefaultTimeout);
+            }
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+                || keyCode == KeyEvent.KEYCODE_VOLUME_UP
+                || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE
+                || keyCode == KeyEvent.KEYCODE_CAMERA) {
+            // don't show the controls for volume adjustment
+            return super.dispatchKeyEvent(event);
+        } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
+            if (uniqueDown) {
+                hide();
+            }
+            return true;
+        }
+
+        show(sDefaultTimeout);
+        return super.dispatchKeyEvent(event);
+    }
+
+    private View.OnClickListener mPauseListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            doPauseResume();
+            show(sDefaultTimeout);
+        }
+    };
+
+    private void updatePausePlay() {
+        if (mRoot == null || mPauseButton == null)
+            return;
+
+        if (mPlayer.isPlaying()) {
+            mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
+        } else {
+            mPauseButton.setImageResource(android.R.drawable.ic_media_play);
+        }
+    }
+
+    private void doPauseResume() {
+        if (mPlayer.isPlaying()) {
+            mPlayer.pause();
+        } else {
+            mPlayer.start();
+        }
+        updatePausePlay();
+    }
+
+    // There are two scenarios that can trigger the seekbar listener to trigger:
+    //
+    // The first is the user using the touchpad to adjust the posititon of the
+    // seekbar's thumb. In this case onStartTrackingTouch is called followed by
+    // a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
+    // We're setting the field "mDragging" to true for the duration of the dragging
+    // session to avoid jumps in the position in case of ongoing playback.
+    //
+    // The second scenario involves the user operating the scroll ball, in this
+    // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
+    // we will simply apply the updated position without suspending regular updates.
+    private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
+        public void onStartTrackingTouch(SeekBar bar) {
+            show(3600000);
+
+            mDragging = true;
+
+            // By removing these pending progress messages we make sure
+            // that a) we won't update the progress while the user adjusts
+            // the seekbar and b) once the user is done dragging the thumb
+            // we will post one of these messages to the queue again and
+            // this ensures that there will be exactly one message queued up.
+            mHandler.removeMessages(SHOW_PROGRESS);
+        }
+
+        public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
+            if (!fromuser) {
+                // We're not interested in programmatically generated changes to
+                // the progress bar's position.
+                return;
+            }
+
+            long duration = mPlayer.getDuration();
+            long newposition = (duration * progress) / 1000L;
+            mPlayer.seekTo( (int) newposition);
+            if (mCurrentTime != null)
+                mCurrentTime.setText(stringForTime( (int) newposition));
+        }
+
+        public void onStopTrackingTouch(SeekBar bar) {
+            mDragging = false;
+            setProgress();
+            updatePausePlay();
+            show(sDefaultTimeout);
+
+            // Ensure that progress is properly updated in the future,
+            // the call to show() does not guarantee this because it is a
+            // no-op if we are already showing.
+            mHandler.sendEmptyMessage(SHOW_PROGRESS);
+        }
+    };
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        if (mPauseButton != null) {
+            mPauseButton.setEnabled(enabled);
+        }
+        if (mFfwdButton != null) {
+            mFfwdButton.setEnabled(enabled);
+        }
+        if (mRewButton != null) {
+            mRewButton.setEnabled(enabled);
+        }
+        if (mNextButton != null) {
+            mNextButton.setEnabled(enabled && mNextListener != null);
+        }
+        if (mPrevButton != null) {
+            mPrevButton.setEnabled(enabled && mPrevListener != null);
+        }
+        if (mProgress != null) {
+            mProgress.setEnabled(enabled);
+        }
+        disableUnsupportedButtons();
+        super.setEnabled(enabled);
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        event.setClassName(MediaControlView.class.getName());
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.setClassName(MediaControlView.class.getName());
+    }
+
+    private View.OnClickListener mRewListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            int pos = mPlayer.getCurrentPosition();
+            pos -= 5000; // milliseconds
+            mPlayer.seekTo(pos);
+            setProgress();
+
+            show(sDefaultTimeout);
+        }
+    };
+
+    private View.OnClickListener mFfwdListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            int pos = mPlayer.getCurrentPosition();
+            pos += 15000; // milliseconds
+            mPlayer.seekTo(pos);
+            setProgress();
+
+            show(sDefaultTimeout);
+        }
+    };
+
+    private void installPrevNextListeners() {
+        if (mNextButton != null) {
+            mNextButton.setOnClickListener(mNextListener);
+            mNextButton.setEnabled(mNextListener != null);
+        }
+
+        if (mPrevButton != null) {
+            mPrevButton.setOnClickListener(mPrevListener);
+            mPrevButton.setEnabled(mPrevListener != null);
+        }
+    }
+
+    public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {
+        mNextListener = next;
+        mPrevListener = prev;
+        mListenersSet = true;
+
+        if (mRoot != null) {
+            installPrevNextListeners();
+            
+            if (mNextButton != null && !mFromXml) {
+                mNextButton.setVisibility(View.VISIBLE);
+            }
+            if (mPrevButton != null && !mFromXml) {
+                mPrevButton.setVisibility(View.VISIBLE);
+            }
+        }
+    }
+
+}

+ 37 - 60
src/com/owncloud/android/ui/preview/PreviewMediaFragment.java

@@ -57,6 +57,7 @@ import com.actionbarsherlock.view.MenuInflater;
 import com.actionbarsherlock.view.MenuItem;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.media.MediaControlView;
 import com.owncloud.android.media.MediaService;
 import com.owncloud.android.media.MediaServiceBinder;
 import com.owncloud.android.network.OwnCloudClientUtils;
@@ -104,7 +105,8 @@ public class PreviewMediaFragment extends SherlockFragment implements
     private RemoteOperation mLastRemoteOperation;
     
     private MediaServiceBinder mMediaServiceBinder = null;
-    private MediaController mMediaController = null;
+    //private MediaController mMediaController = null;
+    private MediaControlView mMediaController = null;
     private MediaServiceConnection mMediaServiceConnection = null;
     private VideoHelper mVideoHelper;
     
@@ -167,6 +169,8 @@ public class PreviewMediaFragment extends SherlockFragment implements
         mImagePreview.setOnTouchListener(this);
         mVideoPreview = (VideoView)mView.findViewById(R.id.video_preview);
         
+        mMediaController = (MediaControlView)mView.findViewById(R.id.media_controller);
+        
         //updateFileDetails(false);
         return mView;
     }
@@ -329,10 +333,14 @@ public class PreviewMediaFragment extends SherlockFragment implements
         mVideoPreview.setVideoPath(mFile.getStoragePath()); 
 
         // create and prepare control panel for the user
-        mMediaController = new MediaController(getActivity());
-        mMediaController.setMediaPlayer(mVideoPreview);
-        mMediaController.setAnchorView(mVideoPreview);
-        mVideoPreview.setMediaController(mMediaController);
+        //mMediaController = new MediaController(getActivity());
+        if (mMediaController != null) {
+            mMediaController.setMediaPlayer(mVideoPreview);
+            //mMediaController.setAnchorView(mVideoPreview);
+            //mVideoPreview.setMediaController(mMediaController);
+        } else {
+            Toast.makeText(getActivity(), "No media controller to play video", Toast.LENGTH_SHORT).show();
+        }
     }
     
 
@@ -349,7 +357,7 @@ public class PreviewMediaFragment extends SherlockFragment implements
         public void onPrepared(MediaPlayer vp) {
             mVideoPreview.seekTo(mSavedPlaybackPosition);
             mVideoPreview.start();
-            mMediaController.show(MediaService.MEDIA_CONTROL_SHORT_LIFE);  
+            //mMediaController.show(MediaService.MEDIA_CONTROL_SHORT_LIFE);  
         }
         
         
@@ -377,9 +385,11 @@ public class PreviewMediaFragment extends SherlockFragment implements
         public boolean onError(MediaPlayer mp, int what, int extra) {
             Log.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra);
             
+            /*
             if (mMediaController != null) {
                 mMediaController.hide();
             }
+            */
             
             if (mVideoPreview.getWindowToken() != null) {
                 String message = MediaService.getMessageForMediaError(getActivity(), what, extra);
@@ -404,34 +414,12 @@ public class PreviewMediaFragment extends SherlockFragment implements
     @Override
     public void onResume() {
         super.onResume();
-        /*
-        mDownloadFinishReceiver = new DownloadFinishReceiver();
-        IntentFilter filter = new IntentFilter(
-                FileDownloader.DOWNLOAD_FINISH_MESSAGE);
-        getActivity().registerReceiver(mDownloadFinishReceiver, filter);
-        
-        mUploadFinishReceiver = new UploadFinishReceiver();
-        filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);
-        getActivity().registerReceiver(mUploadFinishReceiver, filter);
-        */
-
     }
 
 
     @Override
     public void onPause() {
         super.onPause();
-        /*
-        if (mVideoPreview.getVisibility() == View.VISIBLE) {
-            mSavedPlaybackPosition = mVideoPreview.getCurrentPosition();
-        }*/
-        /*
-        getActivity().unregisterReceiver(mDownloadFinishReceiver);
-        mDownloadFinishReceiver = null;
-        
-        getActivity().unregisterReceiver(mUploadFinishReceiver);
-        mUploadFinishReceiver = null;
-        */
     }
 
 
@@ -441,15 +429,17 @@ public class PreviewMediaFragment extends SherlockFragment implements
         if (mMediaServiceConnection != null) {
             Log.d(TAG, "Unbinding from MediaService ...");
             if (mMediaServiceBinder != null && mMediaController != null) {
-                mMediaServiceBinder.unregisterMediaController(mMediaController);
+                //mMediaServiceBinder.unregisterMediaController(mMediaController);
             }
             getActivity().unbindService(mMediaServiceConnection);
             mMediaServiceConnection = null;
             mMediaServiceBinder = null;
+            /*
             if (mMediaController != null) {
                 mMediaController.hide();
                 mMediaController = null;
             }
+            */
         }
     }
     
@@ -477,11 +467,13 @@ public class PreviewMediaFragment extends SherlockFragment implements
 
     
     private void toggleMediaController(int time) {
+        /*
         if (mMediaController.isShowing()) {
             mMediaController.hide();
         } else {
             mMediaController.show(time);
         }
+        */
     }
 
 
@@ -495,7 +487,7 @@ public class PreviewMediaFragment extends SherlockFragment implements
                 mMediaServiceBinder.start();
             }
             if (!mMediaController.isShowing() && isVisible()) {
-                mMediaController.show(MediaService.MEDIA_CONTROL_PERMANENT);
+                //mMediaController.show(MediaService.MEDIA_CONTROL_PERMANENT);
                 // TODO - fix strange bug; steps to trigger :
                 // 1. remove the "isVisible()" control
                 // 2. start the app and preview an audio file
@@ -528,9 +520,11 @@ public class PreviewMediaFragment extends SherlockFragment implements
                 Log.d(TAG, "Media service connected");
                 mMediaServiceBinder = (MediaServiceBinder) service;
                 if (mMediaServiceBinder != null) {
+                    /*
                     if (mMediaController == null) {
                         mMediaController = new MediaController(getSherlockActivity());
                     }
+                    */
                     prepareMediaController();
                     playAudio();    // do not wait for the touch of nobody to play audio
                     
@@ -543,10 +537,14 @@ public class PreviewMediaFragment extends SherlockFragment implements
         }
         
         private void prepareMediaController() {
-            mMediaServiceBinder.registerMediaController(mMediaController);
-            mMediaController.setMediaPlayer(mMediaServiceBinder);
-            mMediaController.setAnchorView(getView());
-            mMediaController.setEnabled(mMediaServiceBinder.isInPlaybackState());
+            //mMediaServiceBinder.registerMediaController(mMediaController);
+            if (mMediaController != null) {
+                mMediaController.setMediaPlayer(mMediaServiceBinder);
+                //mMediaController.setAnchorView(getView());
+                mMediaController.setEnabled(mMediaServiceBinder.isInPlaybackState());
+            } else {
+                Toast.makeText(getActivity(), "No media controller to prepare when connected to media service", Toast.LENGTH_SHORT).show();
+            }
         }
 
         @Override
@@ -554,9 +552,11 @@ public class PreviewMediaFragment extends SherlockFragment implements
             if (component.equals(new ComponentName(getActivity(), MediaService.class))) {
                 Log.e(TAG, "Media service suddenly disconnected");
                 if (mMediaController != null) {
-                    mMediaController.hide();
+                    //mMediaController.hide();
                     mMediaController.setMediaPlayer(null);
-                    mMediaController = null;
+                    //mMediaController = null;
+                } else {
+                    Toast.makeText(getActivity(), "No media controller to release when disconnected from media service", Toast.LENGTH_SHORT).show();
                 }
                 mMediaServiceBinder = null;
                 mMediaServiceConnection = null;
@@ -710,29 +710,6 @@ public class PreviewMediaFragment extends SherlockFragment implements
     */
     
 
-    /**
-     * Interface to implement by any Activity that includes some instance of FileDetailFragment
-     * 
-     * @author David A. Velasco
-     */
-    public interface ContainerActivity extends TransferServiceGetter {
-
-        /**
-         * Callback method invoked when the detail fragment wants to notice its container 
-         * activity about a relevant state the file shown by the fragment.
-         * 
-         * Added to notify to FileDisplayActivity about the need of refresh the files list. 
-         * 
-         * Currently called when:
-         *  - a download is started;
-         *  - a rename is completed;
-         *  - a deletion is completed;
-         *  - the 'inSync' flag is changed;
-         */
-        public void onFileStateChanged();
-        
-    }
-
     /**
      * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewMediaFragment} to be previewed.
      * 
@@ -775,7 +752,7 @@ public class PreviewMediaFragment extends SherlockFragment implements
 
     private void stopPreview(boolean stopAudio) {
         if (mMediaController != null) {
-            mMediaController.hide();
+            //mMediaController.hide();
         }
         if (mFile.isAudio() && stopAudio) {
             mMediaServiceBinder.pause();