Improve TransportController demo.

This is an actually realistic implementation of a
video player.  It's still delibrately simple, but it's
not outright broken.  We now have our own media controller
since that is the only way to get something that actually
works, we use system UI flags to hide nav/status bar when
playing, we use the new TransportController APIs to go
into a formal playback state where we take audio focus.

Change-Id: I19ac171483637b8ee94c9c07aea39c11748cbc46
diff --git a/samples/Support4Demos/res/layout/media_controller.xml b/samples/Support4Demos/res/layout/media_controller.xml
new file mode 100644
index 0000000..b5e58b1
--- /dev/null
+++ b/samples/Support4Demos/res/layout/media_controller.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="#CC000000"
+    android:orientation="vertical"
+    android:layoutDirection="ltr">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:paddingTop="4dip"
+        android:orientation="horizontal">
+
+        <ImageButton android:id="@+id/prev" style="@android:style/MediaButton.Previous" />
+        <ImageButton android:id="@+id/rew" style="@android:style/MediaButton.Rew" />
+        <ImageButton android:id="@+id/pause" style="@android:style/MediaButton.Play" />
+        <ImageButton android:id="@+id/ffwd" style="@android:style/MediaButton.Ffwd" />
+        <ImageButton android:id="@+id/next" style="@android:style/MediaButton.Next" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <TextView android:id="@+id/time_current"
+            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:textColor="?android:attr/textColorSecondary" />
+
+        <SeekBar
+            android:id="@+id/mediacontroller_progress"
+            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/time"
+            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:textColor="?android:attr/textColorSecondary" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/samples/Support4Demos/res/layout/videoview.xml b/samples/Support4Demos/res/layout/videoview.xml
index ff636b9..abc1f7e 100644
--- a/samples/Support4Demos/res/layout/videoview.xml
+++ b/samples/Support4Demos/res/layout/videoview.xml
@@ -1,14 +1,39 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_width="match_parent" android:layout_height="match_parent"
     >
-    
-    <VideoView 
-        android:id="@+id/surface_view" 
+    <view class="com.example.android.supportv4.media.TransportControllerActivity$Content"
+        android:id="@+id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_gravity="center"
-    />
-    
+        />
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fitsSystemWindows="true"
+        android:animateLayoutChanges="true"
+        >
+        <com.example.android.supportv4.media.MediaController
+                android:id="@+id/media_controller"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="bottom">
+        </com.example.android.supportv4.media.MediaController>
+    </FrameLayout>
 </FrameLayout>
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/MediaController.java b/samples/Support4Demos/src/com/example/android/supportv4/media/MediaController.java
new file mode 100644
index 0000000..e7f9e08
--- /dev/null
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/MediaController.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.supportv4.media;
+
+import com.example.android.supportv4.R;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import java.util.Formatter;
+import java.util.Locale;
+
+/**
+ * Helper for implementing media controls in an application.
+ * Use instead of the very useful android.widget.MediaController.
+ * This version is embedded inside of an application's layout.
+ */
+public class MediaController extends FrameLayout {
+
+    private MediaPlayerControl  mPlayer;
+    private Context mContext;
+    private ProgressBar mProgress;
+    private TextView mEndTime, mCurrentTime;
+    private boolean             mDragging;
+    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 MediaController(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mContext = context;
+        mUseFastForward = true;
+        mFromXml = true;
+        LayoutInflater inflate = (LayoutInflater)
+                mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflate.inflate(R.layout.media_controller, this, true);
+        initControllerView();
+    }
+
+    public MediaController(Context context, boolean useFastForward) {
+        super(context);
+        mContext = context;
+        mUseFastForward = useFastForward;
+    }
+
+    public MediaController(Context context) {
+        this(context, true);
+    }
+
+    public void setMediaPlayer(MediaPlayerControl player) {
+        mPlayer = player;
+        updatePausePlay();
+    }
+
+    private void initControllerView() {
+        mPauseButton = (ImageButton) findViewById(R.id.pause);
+        if (mPauseButton != null) {
+            mPauseButton.requestFocus();
+            mPauseButton.setOnClickListener(mPauseListener);
+        }
+
+        mFfwdButton = (ImageButton) findViewById(R.id.ffwd);
+        if (mFfwdButton != null) {
+            mFfwdButton.setOnClickListener(mFfwdListener);
+            if (!mFromXml) {
+                mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
+            }
+        }
+
+        mRewButton = (ImageButton) findViewById(R.id.rew);
+        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) findViewById(R.id.next);
+        if (mNextButton != null && !mFromXml && !mListenersSet) {
+            mNextButton.setVisibility(View.GONE);
+        }
+        mPrevButton = (ImageButton) findViewById(R.id.prev);
+        if (mPrevButton != null && !mFromXml && !mListenersSet) {
+            mPrevButton.setVisibility(View.GONE);
+        }
+
+        mProgress = (ProgressBar) findViewById(R.id.mediacontroller_progress);
+        if (mProgress != null) {
+            if (mProgress instanceof SeekBar) {
+                SeekBar seeker = (SeekBar) mProgress;
+                seeker.setOnSeekBarChangeListener(mSeekListener);
+            }
+            mProgress.setMax(1000);
+        }
+
+        mEndTime = (TextView) findViewById(R.id.time);
+        mCurrentTime = (TextView) findViewById(R.id.time_current);
+        mFormatBuilder = new StringBuilder();
+        mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
+
+        installPrevNextListeners();
+    }
+
+    /**
+     * 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.
+        }
+    }
+
+    public void refresh() {
+        updateProgress();
+        disableUnsupportedButtons();
+        updatePausePlay();
+    }
+
+    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();
+        }
+    }
+
+    public int updateProgress() {
+        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;
+    }
+
+    private View.OnClickListener mPauseListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            doPauseResume();
+        }
+    };
+
+    private void updatePausePlay() {
+        if (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 SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
+        public void onStartTrackingTouch(SeekBar bar) {
+            mDragging = true;
+        }
+
+        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;
+            updateProgress();
+            updatePausePlay();
+        }
+    };
+
+    @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(MediaController.class.getName());
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.setClassName(MediaController.class.getName());
+    }
+
+    private View.OnClickListener mRewListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            int pos = mPlayer.getCurrentPosition();
+            pos -= 5000; // milliseconds
+            mPlayer.seekTo(pos);
+            updateProgress();
+        }
+    };
+
+    private View.OnClickListener mFfwdListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            int pos = mPlayer.getCurrentPosition();
+            pos += 15000; // milliseconds
+            mPlayer.seekTo(pos);
+            updateProgress();
+        }
+    };
+
+    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;
+
+        installPrevNextListeners();
+
+        if (mNextButton != null && !mFromXml) {
+            mNextButton.setVisibility(View.VISIBLE);
+        }
+        if (mPrevButton != null && !mFromXml) {
+            mPrevButton.setVisibility(View.VISIBLE);
+        }
+    }
+
+    public interface MediaPlayerControl {
+        void    start();
+        void    pause();
+        int     getDuration();
+        int     getCurrentPosition();
+        void    seekTo(int pos);
+        boolean isPlaying();
+        int     getBufferPercentage();
+        boolean canPause();
+        boolean canSeekBackward();
+        boolean canSeekForward();
+    }
+}
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/TransportControllerActivity.java b/samples/Support4Demos/src/com/example/android/supportv4/media/TransportControllerActivity.java
index e6d28a7..a4e0951 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/media/TransportControllerActivity.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/TransportControllerActivity.java
@@ -16,13 +16,19 @@
 
 package com.example.android.supportv4.media;
 
-import android.view.KeyEvent;
 import com.example.android.supportv4.R;
 
+import android.app.ActionBar;
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+
 import android.app.Activity;
 import android.net.Uri;
 import android.os.Bundle;
-import android.widget.MediaController;
 import android.widget.VideoView;
 
 import android.support.v4.media.TransportController;
@@ -33,33 +39,266 @@
      * TODO: Set the path variable to a streaming video URL or a local media
      * file path.
      */
-    private VideoView mVideoView;
+    private Content mContent;
     private TransportController mTransportController;
+    private MediaController mMediaController;
 
     /**
      * Handle media buttons to start/stop video playback.  Real implementations
      * will probably handle more buttons, like skip and fast-forward.
      */
-    public class PlayerControlCallbacks extends TransportController.Callbacks {
+    TransportController.Callbacks mTransportCallbacks = new TransportController.Callbacks() {
         public boolean onMediaButtonDown(int keyCode, KeyEvent event) {
             switch (keyCode) {
                 case TransportController.KEYCODE_MEDIA_PLAY:
-                    mVideoView.start();
+                    mMediaPlayerControl.start();
                     return true;
                 case TransportController.KEYCODE_MEDIA_PAUSE:
                 case KeyEvent.KEYCODE_MEDIA_STOP:
-                    mVideoView.pause();
+                    mMediaPlayerControl.pause();
                     return true;
                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
                 case KeyEvent.KEYCODE_HEADSETHOOK:
-                    if (mVideoView.isPlaying()) {
-                        mVideoView.pause();
+                    if (mContent.isPlaying()) {
+                        mMediaPlayerControl.pause();
                     } else {
-                        mVideoView.start();
+                        mMediaPlayerControl.start();
                     }
             }
             return true;
         }
+    };
+
+    /**
+     * Handle actions from on-screen media controls.  Most of these are simple re-directs
+     * to the VideoView; some we need to capture to update our state.
+     */
+    MediaController.MediaPlayerControl mMediaPlayerControl
+            = new MediaController.MediaPlayerControl() {
+
+        @Override
+        public void start() {
+            mTransportController.startPlaying();
+            mContent.start();
+        }
+
+        @Override
+        public void pause() {
+            mTransportController.pausePlaying();
+            mContent.pause();
+        }
+
+        @Override
+        public int getDuration() {
+            return mContent.getDuration();
+        }
+
+        @Override
+        public int getCurrentPosition() {
+            return mContent.getCurrentPosition();
+        }
+
+        @Override
+        public void seekTo(int pos) {
+            mContent.seekTo(pos);
+        }
+
+        @Override
+        public boolean isPlaying() {
+            return mContent.isPlaying();
+        }
+
+        @Override
+        public int getBufferPercentage() {
+            return mContent.getBufferPercentage();
+        }
+
+        @Override
+        public boolean canPause() {
+            return mContent.canPause();
+        }
+
+        @Override
+        public boolean canSeekBackward() {
+            return mContent.canSeekBackward();
+        }
+
+        @Override
+        public boolean canSeekForward() {
+            return mContent.canSeekForward();
+        }
+    };
+
+    /**
+     * This is the actual video player.  It is the top-level content of
+     * the activity's view hierarchy, going under the status bar and nav
+     * bar areas.
+     */
+    public static class Content extends VideoView implements
+            View.OnSystemUiVisibilityChangeListener, View.OnClickListener,
+            ActionBar.OnMenuVisibilityListener, MediaPlayer.OnPreparedListener,
+            MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
+        Activity mActivity;
+        TransportController mTransportController;
+        MediaController mMediaController;
+        boolean mAddedMenuListener;
+        boolean mMenusOpen;
+        boolean mPaused;
+        boolean mNavVisible;
+        int mLastSystemUiVis;
+
+        Runnable mNavHider = new Runnable() {
+            @Override public void run() {
+                setNavVisibility(false);
+            }
+        };
+
+        Runnable mProgressUpdater = new Runnable() {
+            @Override public void run() {
+                mMediaController.updateProgress();
+                getHandler().postDelayed(this, 1000);
+            }
+        };
+
+        public Content(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            setOnSystemUiVisibilityChangeListener(this);
+            setOnClickListener(this);
+            setOnPreparedListener(this);
+            setOnCompletionListener(this);
+            setOnErrorListener(this);
+        }
+
+        public void init(Activity activity, TransportController transportController,
+                MediaController mediaController) {
+            // This called by the containing activity to supply the surrounding
+            // state of the video player that it will interact with.
+            mActivity = activity;
+            mTransportController = transportController;
+            mMediaController = mediaController;
+            pause();
+        }
+
+        @Override protected void onAttachedToWindow() {
+            super.onAttachedToWindow();
+            if (mActivity != null) {
+                mAddedMenuListener = true;
+                mActivity.getActionBar().addOnMenuVisibilityListener(this);
+            }
+        }
+
+        @Override protected void onDetachedFromWindow() {
+            super.onDetachedFromWindow();
+            if (mAddedMenuListener) {
+                mActivity.getActionBar().removeOnMenuVisibilityListener(this);
+            }
+            mNavVisible = false;
+        }
+
+        @Override public void onSystemUiVisibilityChange(int visibility) {
+            // Detect when we go out of nav-hidden mode, to clear our state
+            // back to having the full UI chrome up.  Only do this when
+            // the state is changing and nav is no longer hidden.
+            int diff = mLastSystemUiVis ^ visibility;
+            mLastSystemUiVis = visibility;
+            if ((diff&SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
+                    && (visibility&SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
+                setNavVisibility(true);
+            }
+        }
+
+        @Override protected void onWindowVisibilityChanged(int visibility) {
+            super.onWindowVisibilityChanged(visibility);
+
+            // When we become visible or invisible, play is paused.
+            pause();
+        }
+
+        @Override public void onClick(View v) {
+            // Clicking anywhere makes the navigation visible.
+            setNavVisibility(true);
+        }
+
+        @Override public void onMenuVisibilityChanged(boolean isVisible) {
+            mMenusOpen = isVisible;
+            setNavVisibility(true);
+        }
+
+        @Override
+        public void onPrepared(MediaPlayer mp) {
+            mMediaController.setEnabled(true);
+        }
+
+        @Override
+        public void onCompletion(MediaPlayer mp) {
+            mTransportController.pausePlaying();
+            pause();
+        }
+
+        @Override
+        public boolean onError(MediaPlayer mp, int what, int extra) {
+            mTransportController.pausePlaying();
+            pause();
+            return false;
+        }
+
+        @Override public void start() {
+            super.start();
+            mPaused = false;
+            setKeepScreenOn(true);
+            setNavVisibility(true);
+            mMediaController.refresh();
+            scheduleProgressUpdater();
+        }
+
+        @Override public void pause() {
+            super.pause();
+            mPaused = true;
+            setKeepScreenOn(false);
+            setNavVisibility(true);
+            mMediaController.refresh();
+            scheduleProgressUpdater();
+        }
+
+        void scheduleProgressUpdater() {
+            Handler h = getHandler();
+            if (h != null) {
+                if (mNavVisible && !mPaused) {
+                    h.removeCallbacks(mProgressUpdater);
+                    h.post(mProgressUpdater);
+                } else {
+                    h.removeCallbacks(mProgressUpdater);
+                }
+            }
+        }
+
+        void setNavVisibility(boolean visible) {
+            int newVis = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                    | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                    | SYSTEM_UI_FLAG_LAYOUT_STABLE;
+            if (!visible) {
+                newVis |= SYSTEM_UI_FLAG_LOW_PROFILE | SYSTEM_UI_FLAG_FULLSCREEN
+                        | SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+            }
+
+            // If we are now visible, schedule a timer for us to go invisible.
+            if (visible) {
+                Handler h = getHandler();
+                if (h != null) {
+                    h.removeCallbacks(mNavHider);
+                    if (!mMenusOpen && !mPaused) {
+                        // If the menus are open or play is paused, we will not auto-hide.
+                        h.postDelayed(mNavHider, 3000);
+                    }
+                }
+            }
+
+            // Set the new desired visibility.
+            setSystemUiVisibility(newVis);
+            mNavVisible = visible;
+            mMediaController.setVisibility(visible ? VISIBLE : INVISIBLE);
+            scheduleProgressUpdater();
+        }
     }
 
     @Override
@@ -68,27 +307,31 @@
         setContentView(R.layout.videoview);
 
         // Find the video player in our UI.
-        mVideoView = (VideoView) findViewById(R.id.surface_view);
+        mContent = (Content) findViewById(R.id.content);
 
-        // Create transport controller to control video; use the standard
-        // control callbacks that knows how to talk to a MediaPlayerControl.
-        mTransportController = new TransportController(this, new PlayerControlCallbacks());
+        // Create and initialize the media control UI.
+        mMediaController = (MediaController) findViewById(R.id.media_controller);
+        mMediaController.setMediaPlayer(mMediaPlayerControl);
+
+        // Create transport controller to control video, giving the callback
+        // interface to receive actions from.
+        mTransportController = new TransportController(this, mTransportCallbacks);
 
         // We're just playing a built-in demo video.
-        mVideoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() +
+        mContent.init(this, mTransportController, mMediaController);
+        mContent.setVideoURI(Uri.parse("android.resource://" + getPackageName() +
                 "/" + R.raw.videoviewdemo));
-        mVideoView.setMediaController(new MediaController(this));
-        mVideoView.requestFocus();
     }
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        if (super.dispatchKeyEvent(event)) {
+        // We first dispatch keys to the transport controller -- we want it
+        // to get to consume any media keys rather than letting whoever has focus
+        // in the view hierarchy to potentially eat it.
+        if (mTransportController.dispatchKeyEvent(event)) {
             return true;
         }
 
-        // If the UI didn't handle the key, give the transport controller
-        // a crack at it.
-        return mTransportController.dispatchKeyEvent(event);
+        return super.dispatchKeyEvent(event);
     }
 }