OSDN Git Service

Fix 5393669: New Player Control UI.
authorChih-Chung Chang <chihchung@google.com>
Fri, 14 Oct 2011 08:09:09 +0000 (16:09 +0800)
committerChih-Chung Chang <chihchung@google.com>
Tue, 18 Oct 2011 04:11:13 +0000 (12:11 +0800)
Change-Id: I1aa310eee3b7715dbefea6b2f24e6f32481db49c

33 files changed:
res/anim/player_out.xml [new file with mode: 0644]
res/drawable-hdpi/bg_vidcontrol.png [new file with mode: 0644]
res/drawable-hdpi/ic_vidcontrol_pause.png [new file with mode: 0644]
res/drawable-hdpi/ic_vidcontrol_play.png [new file with mode: 0644]
res/drawable-hdpi/ic_vidcontrol_reload.png [new file with mode: 0644]
res/drawable-hdpi/scrubber_knob.png [new file with mode: 0644]
res/drawable-mdpi/bg_vidcontrol.png [new file with mode: 0644]
res/drawable-mdpi/ic_vidcontrol_pause.png [new file with mode: 0644]
res/drawable-mdpi/ic_vidcontrol_play.png [new file with mode: 0644]
res/drawable-mdpi/ic_vidcontrol_reload.png [new file with mode: 0644]
res/drawable-mdpi/scrubber_knob.png [new file with mode: 0644]
res/drawable-sw600dp-mdpi/bg_vidcontrol.png [new file with mode: 0644]
res/drawable-sw600dp-mdpi/ic_vidcontrol_pause.png [new file with mode: 0644]
res/drawable-sw600dp-mdpi/ic_vidcontrol_play.png [new file with mode: 0644]
res/drawable-sw600dp-mdpi/ic_vidcontrol_reload.png [new file with mode: 0644]
res/drawable-sw600dp-mdpi/scrubber_knob.png [new file with mode: 0644]
res/drawable-sw600dp/bg_vidcontrol.png [new file with mode: 0644]
res/drawable-sw600dp/ic_vidcontrol_pause.png [new file with mode: 0644]
res/drawable-sw600dp/ic_vidcontrol_play.png [new file with mode: 0644]
res/drawable-sw600dp/ic_vidcontrol_reload.png [new file with mode: 0644]
res/drawable-sw600dp/scrubber_knob.png [new file with mode: 0644]
res/drawable-xhdpi/bg_vidcontrol.png [new file with mode: 0644]
res/drawable-xhdpi/ic_vidcontrol_pause.png [new file with mode: 0644]
res/drawable-xhdpi/ic_vidcontrol_play.png [new file with mode: 0644]
res/drawable-xhdpi/ic_vidcontrol_reload.png [new file with mode: 0644]
res/drawable-xhdpi/scrubber_knob.png [new file with mode: 0644]
res/layout/movie_view.xml
res/values/styles.xml
src/com/android/gallery3d/app/ControllerOverlay.java [new file with mode: 0644]
src/com/android/gallery3d/app/MovieActivity.java
src/com/android/gallery3d/app/MovieControllerOverlay.java [new file with mode: 0644]
src/com/android/gallery3d/app/MoviePlayer.java
src/com/android/gallery3d/app/TimeBar.java [new file with mode: 0644]

diff --git a/res/anim/player_out.xml b/res/anim/player_out.xml
new file mode 100644 (file)
index 0000000..b6d90d2
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:anim/linear_interpolator"
+    android:fromAlpha="1.0"
+    android:toAlpha="0.0"
+    android:duration="500"/>
diff --git a/res/drawable-hdpi/bg_vidcontrol.png b/res/drawable-hdpi/bg_vidcontrol.png
new file mode 100644 (file)
index 0000000..dfe2da1
Binary files /dev/null and b/res/drawable-hdpi/bg_vidcontrol.png differ
diff --git a/res/drawable-hdpi/ic_vidcontrol_pause.png b/res/drawable-hdpi/ic_vidcontrol_pause.png
new file mode 100644 (file)
index 0000000..3d1a8bf
Binary files /dev/null and b/res/drawable-hdpi/ic_vidcontrol_pause.png differ
diff --git a/res/drawable-hdpi/ic_vidcontrol_play.png b/res/drawable-hdpi/ic_vidcontrol_play.png
new file mode 100644 (file)
index 0000000..cc02166
Binary files /dev/null and b/res/drawable-hdpi/ic_vidcontrol_play.png differ
diff --git a/res/drawable-hdpi/ic_vidcontrol_reload.png b/res/drawable-hdpi/ic_vidcontrol_reload.png
new file mode 100644 (file)
index 0000000..bf8d529
Binary files /dev/null and b/res/drawable-hdpi/ic_vidcontrol_reload.png differ
diff --git a/res/drawable-hdpi/scrubber_knob.png b/res/drawable-hdpi/scrubber_knob.png
new file mode 100644 (file)
index 0000000..426e3da
Binary files /dev/null and b/res/drawable-hdpi/scrubber_knob.png differ
diff --git a/res/drawable-mdpi/bg_vidcontrol.png b/res/drawable-mdpi/bg_vidcontrol.png
new file mode 100644 (file)
index 0000000..5a5ce55
Binary files /dev/null and b/res/drawable-mdpi/bg_vidcontrol.png differ
diff --git a/res/drawable-mdpi/ic_vidcontrol_pause.png b/res/drawable-mdpi/ic_vidcontrol_pause.png
new file mode 100644 (file)
index 0000000..9319079
Binary files /dev/null and b/res/drawable-mdpi/ic_vidcontrol_pause.png differ
diff --git a/res/drawable-mdpi/ic_vidcontrol_play.png b/res/drawable-mdpi/ic_vidcontrol_play.png
new file mode 100644 (file)
index 0000000..8d7cd83
Binary files /dev/null and b/res/drawable-mdpi/ic_vidcontrol_play.png differ
diff --git a/res/drawable-mdpi/ic_vidcontrol_reload.png b/res/drawable-mdpi/ic_vidcontrol_reload.png
new file mode 100644 (file)
index 0000000..51b1f48
Binary files /dev/null and b/res/drawable-mdpi/ic_vidcontrol_reload.png differ
diff --git a/res/drawable-mdpi/scrubber_knob.png b/res/drawable-mdpi/scrubber_knob.png
new file mode 100644 (file)
index 0000000..9205d9c
Binary files /dev/null and b/res/drawable-mdpi/scrubber_knob.png differ
diff --git a/res/drawable-sw600dp-mdpi/bg_vidcontrol.png b/res/drawable-sw600dp-mdpi/bg_vidcontrol.png
new file mode 100644 (file)
index 0000000..dfe2da1
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/bg_vidcontrol.png differ
diff --git a/res/drawable-sw600dp-mdpi/ic_vidcontrol_pause.png b/res/drawable-sw600dp-mdpi/ic_vidcontrol_pause.png
new file mode 100644 (file)
index 0000000..3d1a8bf
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/ic_vidcontrol_pause.png differ
diff --git a/res/drawable-sw600dp-mdpi/ic_vidcontrol_play.png b/res/drawable-sw600dp-mdpi/ic_vidcontrol_play.png
new file mode 100644 (file)
index 0000000..cc02166
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/ic_vidcontrol_play.png differ
diff --git a/res/drawable-sw600dp-mdpi/ic_vidcontrol_reload.png b/res/drawable-sw600dp-mdpi/ic_vidcontrol_reload.png
new file mode 100644 (file)
index 0000000..bf8d529
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/ic_vidcontrol_reload.png differ
diff --git a/res/drawable-sw600dp-mdpi/scrubber_knob.png b/res/drawable-sw600dp-mdpi/scrubber_knob.png
new file mode 100644 (file)
index 0000000..426e3da
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/scrubber_knob.png differ
diff --git a/res/drawable-sw600dp/bg_vidcontrol.png b/res/drawable-sw600dp/bg_vidcontrol.png
new file mode 100644 (file)
index 0000000..0eb8148
Binary files /dev/null and b/res/drawable-sw600dp/bg_vidcontrol.png differ
diff --git a/res/drawable-sw600dp/ic_vidcontrol_pause.png b/res/drawable-sw600dp/ic_vidcontrol_pause.png
new file mode 100644 (file)
index 0000000..4d274c0
Binary files /dev/null and b/res/drawable-sw600dp/ic_vidcontrol_pause.png differ
diff --git a/res/drawable-sw600dp/ic_vidcontrol_play.png b/res/drawable-sw600dp/ic_vidcontrol_play.png
new file mode 100644 (file)
index 0000000..6f97a64
Binary files /dev/null and b/res/drawable-sw600dp/ic_vidcontrol_play.png differ
diff --git a/res/drawable-sw600dp/ic_vidcontrol_reload.png b/res/drawable-sw600dp/ic_vidcontrol_reload.png
new file mode 100644 (file)
index 0000000..2aaf491
Binary files /dev/null and b/res/drawable-sw600dp/ic_vidcontrol_reload.png differ
diff --git a/res/drawable-sw600dp/scrubber_knob.png b/res/drawable-sw600dp/scrubber_knob.png
new file mode 100644 (file)
index 0000000..4e415a2
Binary files /dev/null and b/res/drawable-sw600dp/scrubber_knob.png differ
diff --git a/res/drawable-xhdpi/bg_vidcontrol.png b/res/drawable-xhdpi/bg_vidcontrol.png
new file mode 100644 (file)
index 0000000..0eb8148
Binary files /dev/null and b/res/drawable-xhdpi/bg_vidcontrol.png differ
diff --git a/res/drawable-xhdpi/ic_vidcontrol_pause.png b/res/drawable-xhdpi/ic_vidcontrol_pause.png
new file mode 100644 (file)
index 0000000..4d274c0
Binary files /dev/null and b/res/drawable-xhdpi/ic_vidcontrol_pause.png differ
diff --git a/res/drawable-xhdpi/ic_vidcontrol_play.png b/res/drawable-xhdpi/ic_vidcontrol_play.png
new file mode 100644 (file)
index 0000000..6f97a64
Binary files /dev/null and b/res/drawable-xhdpi/ic_vidcontrol_play.png differ
diff --git a/res/drawable-xhdpi/ic_vidcontrol_reload.png b/res/drawable-xhdpi/ic_vidcontrol_reload.png
new file mode 100644 (file)
index 0000000..2aaf491
Binary files /dev/null and b/res/drawable-xhdpi/ic_vidcontrol_reload.png differ
diff --git a/res/drawable-xhdpi/scrubber_knob.png b/res/drawable-xhdpi/scrubber_knob.png
new file mode 100644 (file)
index 0000000..4e415a2
Binary files /dev/null and b/res/drawable-xhdpi/scrubber_knob.png differ
index 6d6b28d..bd0415c 100644 (file)
             android:layout_height="match_parent"
             android:layout_centerInParent="true" />
 
-    <LinearLayout android:id="@+id/progress_indicator"
-            android:orientation="vertical"
-            android:layout_centerInParent="true"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content">
-
-        <ProgressBar android:id="@android:id/progress"
-                style="?android:attr/progressBarStyleLarge"
-                android:layout_gravity="center"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" />
-
-        <TextView android:paddingTop="5dip"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:text="@string/loading_video" android:textSize="14sp"
-                android:textColor="#ffffffff" />
-    </LinearLayout>
-
 </RelativeLayout>
index df9fea3..b26c728 100644 (file)
@@ -35,4 +35,5 @@
     <style name="DialogPickerTheme" parent="Theme.Gallery">
     </style>
     <bool name="picker_is_dialog">false</bool>
+    <color name="darker_transparent">#C1000000</color>
 </resources>
diff --git a/src/com/android/gallery3d/app/ControllerOverlay.java b/src/com/android/gallery3d/app/ControllerOverlay.java
new file mode 100644 (file)
index 0000000..847d9e6
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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 com.android.gallery3d.app;
+
+import android.view.View;
+
+public interface ControllerOverlay {
+
+  interface Listener {
+    void onPlayPause();
+    void onSeekStart();
+    void onSeekMove(int time);
+    void onSeekEnd(int time);
+    void onShown();
+    void onHidden();
+    void onReplay();
+  }
+
+  void setListener(Listener listener);
+
+  void setCanReplay(boolean canReplay);
+
+  /**
+   * @return The overlay view that should be added to the player.
+   */
+  View getView();
+
+  void show();
+
+  void showPlaying();
+
+  void showPaused();
+
+  void showEnded();
+
+  void showLoading();
+
+  void showErrorMessage(String message);
+
+  void hide();
+
+  void setTimes(int currentTime, int totalTime);
+
+  void resetTime();
+
+}
index ef836fe..1950549 100644 (file)
@@ -53,7 +53,10 @@ public class MovieActivity extends Activity {
         View rootView = findViewById(R.id.root);
         Intent intent = getIntent();
         initializeActionBar(intent);
-        mPlayer = new MoviePlayer(rootView, this, intent.getData(), savedInstanceState) {
+        mFinishOnCompletion = intent.getBooleanExtra(
+                MediaStore.EXTRA_FINISH_ON_COMPLETION, true);
+        mPlayer = new MoviePlayer(rootView, this, intent.getData(), savedInstanceState,
+                !mFinishOnCompletion) {
             @Override
             public void onCompletion() {
                 if (mFinishOnCompletion) {
@@ -69,7 +72,6 @@ public class MovieActivity extends Activity {
                 setRequestedOrientation(orientation);
             }
         }
-        mFinishOnCompletion = intent.getBooleanExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, true);
         Window win = getWindow();
         WindowManager.LayoutParams winParams = win.getAttributes();
         winParams.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF;
diff --git a/src/com/android/gallery3d/app/MovieControllerOverlay.java b/src/com/android/gallery3d/app/MovieControllerOverlay.java
new file mode 100644 (file)
index 0000000..752213e
--- /dev/null
@@ -0,0 +1,390 @@
+/*
+ * 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 com.android.gallery3d.app;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.R;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationUtils;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+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;
+
+/**
+ * The playback controller for the Movie Player.
+ */
+public class MovieControllerOverlay extends FrameLayout implements
+    ControllerOverlay,
+    OnClickListener,
+    AnimationListener,
+    TimeBar.Listener {
+
+  private enum State {
+    PLAYING,
+    PAUSED,
+    ENDED,
+    ERROR,
+    LOADING
+  }
+
+  private static final float ERROR_MESSAGE_RELATIVE_PADDING = 1.0f / 6;
+
+  private Listener listener;
+
+  private final View background;
+  private final TimeBar timeBar;
+
+  private View mainView;
+  private final LinearLayout loadingView;
+  private final TextView errorView;
+  private final ImageView playPauseReplayView;
+
+  private final Handler handler;
+  private final Runnable startHidingRunnable;
+  private final Animation hideAnimation;
+
+  private State state;
+
+  private boolean hidden;
+
+  private boolean canReplay = true;
+
+  public MovieControllerOverlay(Context context) {
+    super(context);
+
+    state = State.LOADING;
+
+    LayoutParams wrapContent =
+        new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+    LayoutParams matchParent =
+        new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+
+    LayoutInflater inflater = LayoutInflater.from(context);
+
+    background = new View(context);
+    background.setBackgroundColor(context.getResources().getColor(R.color.darker_transparent));
+    addView(background, matchParent);
+
+    timeBar = new TimeBar(context, this);
+    addView(timeBar, wrapContent);
+
+    loadingView = new LinearLayout(context);
+    loadingView.setOrientation(LinearLayout.VERTICAL);
+    loadingView.setGravity(Gravity.CENTER_HORIZONTAL);
+    ProgressBar spinner = new ProgressBar(context);
+    spinner.setIndeterminate(true);
+    loadingView.addView(spinner, wrapContent);
+    addView(loadingView, wrapContent);
+
+    playPauseReplayView = new ImageView(context);
+    playPauseReplayView.setImageResource(R.drawable.ic_vidcontrol_play);
+    playPauseReplayView.setBackgroundResource(R.drawable.bg_vidcontrol);
+    playPauseReplayView.setScaleType(ScaleType.CENTER);
+    playPauseReplayView.setFocusable(true);
+    playPauseReplayView.setClickable(true);
+    playPauseReplayView.setOnClickListener(this);
+    addView(playPauseReplayView, wrapContent);
+
+    errorView = new TextView(context);
+    errorView.setGravity(Gravity.CENTER);
+    errorView.setBackgroundColor(0xCC000000);
+    errorView.setTextColor(0xFFFFFFFF);
+    addView(errorView, matchParent);
+
+    handler = new Handler();
+    startHidingRunnable = new Runnable() {
+      public void run() {
+        startHiding();
+      }
+    };
+
+    hideAnimation = AnimationUtils.loadAnimation(context, R.anim.player_out);
+    hideAnimation.setAnimationListener(this);
+
+    RelativeLayout.LayoutParams params =
+        new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+    setLayoutParams(params);
+    hide();
+  }
+
+  public void setListener(Listener listener) {
+    this.listener = listener;
+  }
+
+  public void setCanReplay(boolean canReplay) {
+    this.canReplay = canReplay;
+  }
+
+  public View getView() {
+    return this;
+  }
+
+  public void showPlaying() {
+    state = State.PLAYING;
+    showMainView(playPauseReplayView);
+  }
+
+  public void showPaused() {
+    state = State.PAUSED;
+    showMainView(playPauseReplayView);
+  }
+
+  public void showEnded() {
+    state = State.ENDED;
+    showMainView(playPauseReplayView);
+  }
+
+  public void showLoading() {
+    state = State.LOADING;
+    showMainView(loadingView);
+  }
+
+  public void showErrorMessage(String message) {
+    state = State.ERROR;
+    int padding = (int) (getMeasuredWidth() * ERROR_MESSAGE_RELATIVE_PADDING);
+    errorView.setPadding(padding, 10, padding, 10);
+    errorView.setText(message);
+    showMainView(errorView);
+  }
+
+  public void resetTime() {
+    timeBar.resetTime();
+  }
+
+  public void setTimes(int currentTime, int totalTime) {
+    timeBar.setTime(currentTime, totalTime);
+  }
+
+  public void hide() {
+    boolean wasHidden = hidden;
+    hidden = true;
+    playPauseReplayView.setVisibility(View.INVISIBLE);
+    loadingView.setVisibility(View.INVISIBLE);
+    background.setVisibility(View.INVISIBLE);
+    timeBar.setVisibility(View.INVISIBLE);
+    setVisibility(View.INVISIBLE);
+    setFocusable(true);
+    requestFocus();
+    if (listener != null && wasHidden != hidden) {
+      listener.onHidden();
+    }
+  }
+
+  private void showMainView(View view) {
+    mainView = view;
+    errorView.setVisibility(mainView == errorView ? View.VISIBLE : View.INVISIBLE);
+    loadingView.setVisibility(mainView == loadingView ? View.VISIBLE : View.INVISIBLE);
+    playPauseReplayView.setVisibility(
+        mainView == playPauseReplayView ? View.VISIBLE : View.INVISIBLE);
+    show();
+  }
+
+  public void show() {
+    boolean wasHidden = hidden;
+    hidden = false;
+    updateViews();
+    setVisibility(View.VISIBLE);
+    setFocusable(false);
+    if (listener != null && wasHidden != hidden) {
+      listener.onShown();
+    }
+    maybeStartHiding();
+  }
+
+  private void maybeStartHiding() {
+    cancelHiding();
+    if (state == State.PLAYING) {
+      handler.postDelayed(startHidingRunnable, 2500);
+    }
+  }
+
+  private void startHiding() {
+    startHideAnimation(timeBar);
+    startHideAnimation(playPauseReplayView);
+  }
+
+  private void startHideAnimation(View view) {
+    if (view.getVisibility() == View.VISIBLE) {
+      view.startAnimation(hideAnimation);
+    }
+  }
+
+  private void cancelHiding() {
+    handler.removeCallbacks(startHidingRunnable);
+    background.setAnimation(null);
+    timeBar.setAnimation(null);
+    playPauseReplayView.setAnimation(null);
+  }
+
+  public void onAnimationStart(Animation animation) {
+    // Do nothing.
+  }
+
+  public void onAnimationRepeat(Animation animation) {
+    // Do nothing.
+  }
+
+  public void onAnimationEnd(Animation animation) {
+    hide();
+  }
+
+  public void onClick(View view) {
+    if (listener != null) {
+      if (view == playPauseReplayView) {
+        if (state == State.ENDED) {
+          if (canReplay) {
+              listener.onReplay();
+          }
+        } else if (state == State.PAUSED || state == State.PLAYING) {
+          listener.onPlayPause();
+        }
+      }
+    }
+  }
+
+  @Override
+  public boolean onKeyDown(int keyCode, KeyEvent event) {
+    if (hidden) {
+      show();
+    }
+    return super.onKeyDown(keyCode, event);
+  }
+
+  @Override
+  public boolean onTouchEvent(MotionEvent event) {
+    if (super.onTouchEvent(event)) {
+      return true;
+    }
+
+    if (hidden) {
+      show();
+      return true;
+    }
+    switch (event.getAction()) {
+      case MotionEvent.ACTION_DOWN:
+        cancelHiding();
+        if (state == State.PLAYING || state == State.PAUSED) {
+          listener.onPlayPause();
+        }
+        break;
+      case MotionEvent.ACTION_UP:
+        maybeStartHiding();
+        break;
+    }
+    return true;
+  }
+
+  @Override
+  protected void onLayout(boolean changed, int l, int t, int r, int b) {
+    int bw;
+    int bh;
+    int y;
+    int h = b - t;
+    int w = r - l;
+    boolean error = errorView.getVisibility() == View.VISIBLE;
+
+    bw = timeBar.getBarHeight();
+    bh = bw;
+    y = b - bh;
+
+    background.layout(l, y, r, b);
+
+    timeBar.layout(l, b - timeBar.getPreferredHeight(), r, b);
+    // Needed, otherwise the framework will not re-layout in case only the padding is changed
+    timeBar.requestLayout();
+
+    // play pause / next / previous buttons
+    int cx = l + w / 2; // center x
+    int playbackButtonsCenterline = t + h / 2;
+    bw = playPauseReplayView.getMeasuredWidth();
+    bh = playPauseReplayView.getMeasuredHeight();
+    playPauseReplayView.layout(
+        cx - bw / 2, playbackButtonsCenterline - bh / 2, cx + bw / 2,
+        playbackButtonsCenterline + bh / 2);
+
+    // Space available on each side of the error message for the next and previous buttons
+    int errorMessagePadding = (int) (w * ERROR_MESSAGE_RELATIVE_PADDING);
+
+    if (mainView != null) {
+      layoutCenteredView(mainView, l, t, r, b);
+    }
+  }
+
+  private 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;
+    int ct = (b - t - ch) / 2;
+    view.layout(cl, ct, cl + cw, ct + ch);
+  }
+
+  @Override
+  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    measureChildren(widthMeasureSpec, heightMeasureSpec);
+  }
+
+  private void updateViews() {
+    if (hidden) {
+      return;
+    }
+    background.setVisibility(View.VISIBLE);
+    timeBar.setVisibility(View.VISIBLE);
+    playPauseReplayView.setImageResource(
+        state == State.PAUSED ? R.drawable.ic_vidcontrol_play :
+          state == State.PLAYING ? R.drawable.ic_vidcontrol_pause :
+            R.drawable.ic_vidcontrol_reload);
+    playPauseReplayView.setVisibility(
+        (state != State.LOADING && state != State.ERROR &&
+                !(state == State.ENDED && !canReplay))
+        ? View.VISIBLE : View.GONE);
+    requestLayout();
+  }
+
+  // TimeBar listener
+
+  public void onScrubbingStart() {
+    cancelHiding();
+    listener.onSeekStart();
+  }
+
+  public void onScrubbingMove(int time) {
+    cancelHiding();
+    listener.onSeekMove(time);
+  }
+
+  public void onScrubbingEnd(int time) {
+    maybeStartHiding();
+    listener.onSeekEnd(time);
+  }
+
+}
index f37bc7d..07fe71a 100644 (file)
@@ -36,7 +36,9 @@ import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.MediaController;
 import android.widget.VideoView;
 
@@ -46,7 +48,8 @@ import java.io.DataInputStream;
 import java.io.DataOutputStream;
 
 public class MoviePlayer implements
-        MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
+        MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener,
+        ControllerOverlay.Listener {
     @SuppressWarnings("unused")
     private static final String TAG = "MoviePlayer";
 
@@ -64,84 +67,64 @@ public class MoviePlayer implements
 
     private Context mContext;
     private final VideoView mVideoView;
-    private final View mProgressView;
     private final Bookmarker mBookmarker;
     private final Uri mUri;
     private final Handler mHandler = new Handler();
     private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver;
     private final ActionBar mActionBar;
-    private final MediaController mMediaController;
+    private final ControllerOverlay mController;
 
     private long mResumeableTime = Long.MAX_VALUE;
     private int mVideoPosition = 0;
     private boolean mHasPaused = false;
 
+    // If the time bar is being dragged.
+    private boolean mDragging;
+
+    // If the time bar is visible.
+    private boolean mShowing;
+
     private final Runnable mPlayingChecker = new Runnable() {
         @Override
         public void run() {
             if (mVideoView.isPlaying()) {
-                mProgressView.setVisibility(View.GONE);
+                mController.showPlaying();
             } else {
                 mHandler.postDelayed(mPlayingChecker, 250);
             }
         }
     };
 
+    private final Runnable mProgressChecker = new Runnable() {
+        @Override
+        public void run() {
+            int pos = setProgress();
+            mHandler.postDelayed(mProgressChecker, 1000 - (pos % 1000));
+        }
+    };
+
     public MoviePlayer(View rootView, final MovieActivity movieActivity, Uri videoUri,
-            Bundle savedInstance) {
+            Bundle savedInstance, boolean canReplay) {
         mContext = movieActivity.getApplicationContext();
         mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
-        mProgressView = rootView.findViewById(R.id.progress_indicator);
         mBookmarker = new Bookmarker(movieActivity);
         mActionBar = movieActivity.getActionBar();
         mUri = videoUri;
 
-        // 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)) {
-            mHandler.postDelayed(mPlayingChecker, 250);
-        } else {
-            mProgressView.setVisibility(View.GONE);
-        }
+        mController = new MovieControllerOverlay(mContext);
+        ((ViewGroup)rootView).addView(mController.getView());
+        mController.setListener(this);
+        mController.setCanReplay(canReplay);
 
         mVideoView.setOnErrorListener(this);
         mVideoView.setOnCompletionListener(this);
         mVideoView.setVideoURI(mUri);
-
-        mMediaController = new MediaController(movieActivity) {
-            @Override
-            public void show() {
-                showSystemUi(true);
-                mActionBar.show();
-                super.show();
+        mVideoView.setOnTouchListener(new View.OnTouchListener() {
+            public boolean onTouch(View v, MotionEvent event) {
+                mController.show();
+                return true;
             }
-
-            @Override
-            public void hide() {
-                super.hide();
-                mActionBar.hide();
-                showSystemUi(false);
-            }
-
-            // We intercept the "back" key events here, so hide() won't be
-            // called for ACTION_DOWN events of the "back" key (The code is in
-            // MediaController). Otherwise after system bar is hidden, we
-            // will not receive the ACTION_UP events of the "back" key.
-            @Override
-            public boolean dispatchKeyEvent(KeyEvent event) {
-                int keyCode = event.getKeyCode();
-                if (keyCode == KeyEvent.KEYCODE_BACK) {
-                    if (event.getAction() == KeyEvent.ACTION_UP) {
-                        movieActivity.onBackPressed();
-                    }
-                    return true;
-                }
-                return super.dispatchKeyEvent(event);
-            }
-        };
-
-        mMediaController.show();
+        });
 
         // When the user touches the screen or uses some hard key, the framework
         // will change system ui visibility from invisible to visible. We show
@@ -150,19 +133,14 @@ public class MoviePlayer implements
                 new View.OnSystemUiVisibilityChangeListener() {
             public void onSystemUiVisibilityChange(int visibility) {
                 if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
-                    mMediaController.show();
+                    mController.show();
                 }
             }
         });
 
-        mVideoView.setMediaController(mMediaController);
-
         mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
         mAudioBecomingNoisyReceiver.register();
 
-        // make the video view handle keys for seeking and pausing
-        mVideoView.requestFocus();
-
         Intent i = new Intent(SERVICECMD);
         i.putExtra(CMDNAME, CMDPAUSE);
         movieActivity.sendBroadcast(i);
@@ -178,7 +156,7 @@ public class MoviePlayer implements
             if (bookmark != null) {
                 showResumeDialog(movieActivity, bookmark);
             } else {
-                mVideoView.start();
+                startVideo();
             }
         }
     }
@@ -186,7 +164,6 @@ public class MoviePlayer implements
     private void showSystemUi(boolean visible) {
         int flag = visible ? 0 : View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
         mVideoView.setSystemUiVisibility(flag);
-        mMediaController.setSystemUiVisibility(flag);
     }
 
     public void onSaveInstanceState(Bundle outState) {
@@ -211,14 +188,14 @@ public class MoviePlayer implements
             @Override
             public void onClick(DialogInterface dialog, int which) {
                 mVideoView.seekTo(bookmark);
-                mVideoView.start();
+                startVideo();
             }
         });
         builder.setNegativeButton(
                 R.string.resume_playing_restart, new OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int which) {
-                mVideoView.start();
+                startVideo();
             }
         });
         builder.show();
@@ -240,10 +217,10 @@ public class MoviePlayer implements
 
             // If we have slept for too long, pause the play
             if (System.currentTimeMillis() > mResumeableTime) {
-                mMediaController.show();
-                mVideoView.pause();
+                pauseVideo();
             }
         }
+        mHandler.post(mProgressChecker);
     }
 
     public void onDestroy() {
@@ -251,19 +228,113 @@ public class MoviePlayer implements
         mAudioBecomingNoisyReceiver.unregister();
     }
 
+    // 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) {
+            return 0;
+        }
+        int position = mVideoView.getCurrentPosition();
+        int duration = mVideoView.getDuration();
+        mController.setTimes(position, duration);
+        return position;
+    }
+
+    private void startVideo() {
+        // 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)) {
+            mController.showLoading();
+            mHandler.removeCallbacks(mPlayingChecker);
+            mHandler.postDelayed(mPlayingChecker, 250);
+        } else {
+            mController.showPlaying();
+        }
+
+        mVideoView.start();
+        setProgress();
+    }
+
+    private void playVideo() {
+        mVideoView.start();
+        mController.showPlaying();
+        setProgress();
+    }
+
+    private void pauseVideo() {
+        mVideoView.pause();
+        mController.showPaused();
+    }
+
+    // Below are notifications from VideoView
+    @Override
     public boolean onError(MediaPlayer player, int arg1, int arg2) {
         mHandler.removeCallbacksAndMessages(null);
-        mProgressView.setVisibility(View.GONE);
+        // VideoView will show an error dialog if we return false, so no need
+        // to show more message.
+        mController.showErrorMessage("");
         return false;
     }
 
+    @Override
     public void onCompletion(MediaPlayer mp) {
+        mController.showEnded();
         onCompletion();
     }
 
     public void onCompletion() {
     }
 
+    // Below are notifications from ControllerOverlay
+    @Override
+    public void onPlayPause() {
+        if (mVideoView.isPlaying()) {
+            pauseVideo();
+        } else {
+            playVideo();
+        }
+    }
+
+    @Override
+    public void onSeekStart() {
+        mDragging = true;
+    }
+
+    @Override
+    public void onSeekMove(int time) {
+        mVideoView.seekTo(time);
+    }
+
+    @Override
+    public void onSeekEnd(int time) {
+        mDragging = false;
+        mVideoView.seekTo(time);
+        setProgress();
+    }
+
+    @Override
+    public void onShown() {
+        mShowing = true;
+        mActionBar.show();
+        showSystemUi(true);
+        setProgress();
+    }
+
+    @Override
+    public void onHidden() {
+        mShowing = false;
+        mActionBar.hide();
+        showSystemUi(false);
+    }
+
+    @Override
+    public void onReplay() {
+        startVideo();
+    }
+
+    // We want to pause when the headset is unplugged.
     private class AudioBecomingNoisyReceiver extends BroadcastReceiver {
 
         public void register() {
diff --git a/src/com/android/gallery3d/app/TimeBar.java b/src/com/android/gallery3d/app/TimeBar.java
new file mode 100644 (file)
index 0000000..0ac8efb
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * 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 com.android.gallery3d.app;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.R;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * The time bar view, which includes the current and total time, the progress bar,
+ * and the scrubber.
+ */
+public class TimeBar extends View {
+
+  public interface Listener {
+    void onScrubbingStart();
+    void onScrubbingMove(int time);
+    void onScrubbingEnd(int time);
+  }
+
+  // Padding around the scrubber to increase its touch target
+  private static final int SCRUBBER_PADDING_IN_DP = 10;
+
+  // The total padding, top plus bottom
+  private static final int V_PADDING_IN_DP = 30;
+
+  private static final int TEXT_SIZE_IN_DP = 14;
+
+  private final Listener listener;
+
+  // the bars we use for displaying the progress
+  private final Rect progressBar;
+  private final Rect playedBar;
+
+  private final Paint progressPaint;
+  private final Paint playedPaint;
+  private final Paint timeTextPaint;
+
+  private final Bitmap scrubber;
+  private final int scrubberPadding; // adds some touch tolerance around the scrubber
+
+  private int scrubberLeft;
+  private int scrubberTop;
+  private int scrubberCorrection;
+  private boolean scrubbing;
+  private boolean showTimes;
+  private boolean showScrubber;
+
+  private int totalTime;
+  private int currentTime;
+
+  private final Rect timeBounds;
+
+  private int vPaddingInPx;
+
+  public TimeBar(Context context, Listener listener) {
+    super(context);
+    this.listener = Utils.checkNotNull(listener);
+
+    showTimes = true;
+    showScrubber = true;
+
+    progressBar = new Rect();
+    playedBar = new Rect();
+
+    progressPaint = new Paint();
+    progressPaint.setColor(0xFF808080);
+    playedPaint = new Paint();
+    playedPaint.setColor(0xFFFFFFFF);
+
+    DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+    float textSizeInPx = metrics.density * TEXT_SIZE_IN_DP;
+    timeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    timeTextPaint.setColor(0xFFCECECE);
+    timeTextPaint.setTextSize(textSizeInPx);
+    timeTextPaint.setTextAlign(Paint.Align.CENTER);
+
+    timeBounds = new Rect();
+    timeTextPaint.getTextBounds("0:00:00", 0, 7, timeBounds);
+
+    scrubber = BitmapFactory.decodeResource(getResources(), R.drawable.scrubber_knob);
+    scrubberPadding = (int) (metrics.density * SCRUBBER_PADDING_IN_DP);
+
+    vPaddingInPx = (int) (metrics.density * V_PADDING_IN_DP);
+  }
+
+  private void update() {
+    playedBar.set(progressBar);
+
+    if (totalTime > 0) {
+      playedBar.right =
+          playedBar.left + (int) ((progressBar.width() * (long) currentTime) / totalTime);
+    } else {
+      playedBar.right = progressBar.left;
+    }
+
+    if (!scrubbing) {
+      scrubberLeft = playedBar.right - scrubber.getWidth() / 2;
+    }
+    invalidate();
+  }
+
+  /**
+   * @return the preferred height of this view, including invisible padding
+   */
+  public int getPreferredHeight() {
+    return timeBounds.height() + vPaddingInPx + scrubberPadding;
+  }
+
+  /**
+   * @return the height of the time bar, excluding invisible padding
+   */
+  public int getBarHeight() {
+    return timeBounds.height() + vPaddingInPx;
+  }
+
+  public void setTime(int currentTime, int totalTime) {
+    if (this.currentTime == currentTime && this.totalTime == totalTime) {
+        return;
+    }
+    this.currentTime = currentTime;
+    this.totalTime = totalTime;
+    update();
+  }
+
+  public void setShowTimes(boolean showTimes) {
+    this.showTimes = showTimes;
+    requestLayout();
+  }
+
+  public void resetTime() {
+    setTime(0, 0);
+  }
+
+  public void setShowScrubber(boolean showScrubber) {
+    this.showScrubber = showScrubber;
+    if (!showScrubber && scrubbing) {
+      listener.onScrubbingEnd(getScrubberTime());
+      scrubbing = false;
+    }
+    requestLayout();
+  }
+
+  private boolean inScrubber(float x, float y) {
+    int scrubberRight = scrubberLeft + scrubber.getWidth();
+    int scrubberBottom = scrubberTop + scrubber.getHeight();
+    return scrubberLeft - scrubberPadding < x && x < scrubberRight + scrubberPadding
+        && scrubberTop - scrubberPadding < y && y < scrubberBottom + scrubberPadding;
+  }
+
+  private void clampScrubber() {
+    int half = scrubber.getWidth() / 2;
+    int max = progressBar.right - half;
+    int min = progressBar.left - half;
+    scrubberLeft = Math.min(max, Math.max(min, scrubberLeft));
+  }
+
+  private int getScrubberTime() {
+    return (int) ((long) (scrubberLeft + scrubber.getWidth() / 2 - progressBar.left)
+        * totalTime / progressBar.width());
+  }
+
+  @Override
+  protected void onLayout(boolean changed, int l, int t, int r, int b) {
+    int w = r - l;
+    int h = b - t;
+    if (!showTimes && !showScrubber) {
+      progressBar.set(0, 0, w, h);
+    } else {
+      int margin = scrubber.getWidth() / 3;
+      if (showTimes) {
+        margin += timeBounds.width();
+      }
+      int progressY = (h + scrubberPadding) / 2;
+      scrubberTop = progressY - scrubber.getHeight() / 2 + 1;
+      progressBar.set(
+          getPaddingLeft() + margin, progressY,
+          w - getPaddingRight() - margin, progressY + 4);
+    }
+    update();
+  }
+
+  @Override
+  public void draw(Canvas canvas) {
+    super.draw(canvas);
+
+    // draw progress bars
+    canvas.drawRect(progressBar, progressPaint);
+    canvas.drawRect(playedBar, playedPaint);
+
+    // draw scrubber and timers
+    if (showScrubber) {
+      canvas.drawBitmap(scrubber, scrubberLeft, scrubberTop, null);
+    }
+    if (showTimes) {
+      canvas.drawText(
+          stringForTime(currentTime),
+          timeBounds.width() / 2 + getPaddingLeft(),
+          timeBounds.height() + vPaddingInPx / 2 + scrubberPadding + 1,
+          timeTextPaint);
+      canvas.drawText(
+          stringForTime(totalTime),
+          getWidth() - getPaddingRight() - timeBounds.width() / 2,
+          timeBounds.height() + vPaddingInPx / 2 + scrubberPadding + 1,
+          timeTextPaint);
+    }
+  }
+
+  @Override
+  public boolean onTouchEvent(MotionEvent event) {
+
+    if (showScrubber) {
+      int x = (int) event.getX();
+      int y = (int) event.getY();
+
+      switch (event.getAction()) {
+        case MotionEvent.ACTION_DOWN:
+          if (inScrubber(x, y)) {
+            scrubbing = true;
+            scrubberCorrection = x - scrubberLeft;
+            listener.onScrubbingStart();
+            return true;
+          }
+          break;
+        case MotionEvent.ACTION_MOVE:
+          if (scrubbing) {
+            scrubberLeft = x - scrubberCorrection;
+            clampScrubber();
+            currentTime = getScrubberTime();
+            listener.onScrubbingMove(currentTime);
+            invalidate();
+            return true;
+          }
+          break;
+        case MotionEvent.ACTION_UP:
+          if (scrubbing) {
+            listener.onScrubbingEnd(getScrubberTime());
+            scrubbing = false;
+            return true;
+          }
+          break;
+      }
+    }
+    return false;
+  }
+
+  private String stringForTime(long millis) {
+    int totalSeconds = (int) millis / 1000;
+    int seconds = totalSeconds % 60;
+    int minutes = (totalSeconds / 60) % 60;
+    int hours = totalSeconds / 3600;
+    if (hours > 0) {
+      return String.format("%d:%02d:%02d", hours, minutes, seconds).toString();
+    } else {
+      return String.format("%02d:%02d", minutes, seconds).toString();
+    }
+  }
+
+}