--- /dev/null
+<?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"/>
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>
<style name="DialogPickerTheme" parent="Theme.Gallery">
</style>
<bool name="picker_is_dialog">false</bool>
+ <color name="darker_transparent">#C1000000</color>
</resources>
--- /dev/null
+/*
+ * 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();
+
+}
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) {
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;
--- /dev/null
+/*
+ * 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);
+ }
+
+}
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;
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";
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
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);
if (bookmark != null) {
showResumeDialog(movieActivity, bookmark);
} else {
- mVideoView.start();
+ startVideo();
}
}
}
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) {
@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();
// 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() {
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() {
--- /dev/null
+/*
+ * 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();
+ }
+ }
+
+}