blob: 0cf361c714879042dcc51f2422ecd318db3da627 [file] [log] [blame]
Owen Linf9a0a432011-08-17 22:07:43 +08001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.gallery3d.app;
18
Owen Lin7a5e1e72012-06-20 16:54:24 +080019import android.annotation.TargetApi;
Owen Linf9a0a432011-08-17 22:07:43 +080020import android.app.AlertDialog;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.content.DialogInterface.OnCancelListener;
25import android.content.DialogInterface.OnClickListener;
26import android.content.Intent;
27import android.content.IntentFilter;
Owen Lin00bb8e42012-05-23 15:47:36 -070028import android.graphics.Color;
Owen Linf9a0a432011-08-17 22:07:43 +080029import android.media.AudioManager;
30import android.media.MediaPlayer;
31import android.net.Uri;
Owen Lindf4763c2012-06-28 17:12:20 +080032import android.os.Build;
Owen Lin540e4692011-09-14 19:49:03 +080033import android.os.Bundle;
Owen Linf9a0a432011-08-17 22:07:43 +080034import android.os.Handler;
Chih-Chung Changfc8c5032011-11-29 14:21:11 +080035import android.view.KeyEvent;
Chih-Chung Chang209a9162011-10-14 16:09:09 +080036import android.view.MotionEvent;
Owen Linf9a0a432011-08-17 22:07:43 +080037import android.view.View;
Chih-Chung Chang209a9162011-10-14 16:09:09 +080038import android.view.ViewGroup;
Owen Linf9a0a432011-08-17 22:07:43 +080039import android.widget.VideoView;
40
Owen Linaea077a2011-11-11 12:01:09 +080041import com.android.gallery3d.R;
Owen Lin7a5e1e72012-06-20 16:54:24 +080042import com.android.gallery3d.common.ApiHelper;
Owen Linaea077a2011-11-11 12:01:09 +080043import com.android.gallery3d.common.BlobCache;
44import com.android.gallery3d.util.CacheManager;
45import com.android.gallery3d.util.GalleryUtils;
46
Owen Linf9a0a432011-08-17 22:07:43 +080047import java.io.ByteArrayInputStream;
48import java.io.ByteArrayOutputStream;
49import java.io.DataInputStream;
50import java.io.DataOutputStream;
51
52public class MoviePlayer implements
Chih-Chung Chang209a9162011-10-14 16:09:09 +080053 MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener,
54 ControllerOverlay.Listener {
Owen Linf9a0a432011-08-17 22:07:43 +080055 @SuppressWarnings("unused")
56 private static final String TAG = "MoviePlayer";
57
Owen Lin540e4692011-09-14 19:49:03 +080058 private static final String KEY_VIDEO_POSITION = "video-position";
59 private static final String KEY_RESUMEABLE_TIME = "resumeable-timeout";
60
Chih-Chung Chang67721732012-07-03 12:49:45 +080061 // These are constants in KeyEvent, appearing on API level 11.
62 private static final int KEYCODE_MEDIA_PLAY = 126;
63 private static final int KEYCODE_MEDIA_PAUSE = 127;
64
Owen Linf9a0a432011-08-17 22:07:43 +080065 // Copied from MediaPlaybackService in the Music Player app.
66 private static final String SERVICECMD = "com.android.music.musicservicecommand";
67 private static final String CMDNAME = "command";
68 private static final String CMDPAUSE = "pause";
69
Owen Lin00bb8e42012-05-23 15:47:36 -070070 private static final long BLACK_TIMEOUT = 500;
71
Owen Lin540e4692011-09-14 19:49:03 +080072 // If we resume the acitivty with in RESUMEABLE_TIMEOUT, we will keep playing.
73 // Otherwise, we pause the player.
74 private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins
75
Owen Linf9a0a432011-08-17 22:07:43 +080076 private Context mContext;
77 private final VideoView mVideoView;
Owen Lin00bb8e42012-05-23 15:47:36 -070078 private final View mRootView;
Owen Linf9a0a432011-08-17 22:07:43 +080079 private final Bookmarker mBookmarker;
80 private final Uri mUri;
81 private final Handler mHandler = new Handler();
82 private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver;
Owen Lin00bb8e42012-05-23 15:47:36 -070083 private final MovieControllerOverlay mController;
Owen Linf9a0a432011-08-17 22:07:43 +080084
Owen Lin540e4692011-09-14 19:49:03 +080085 private long mResumeableTime = Long.MAX_VALUE;
86 private int mVideoPosition = 0;
87 private boolean mHasPaused = false;
Ray Chen24525c22012-05-22 17:34:29 +080088 private int mLastSystemUiVis = 0;
Owen Linf9a0a432011-08-17 22:07:43 +080089
Chih-Chung Chang209a9162011-10-14 16:09:09 +080090 // If the time bar is being dragged.
91 private boolean mDragging;
92
93 // If the time bar is visible.
94 private boolean mShowing;
95
Owen Linf9a0a432011-08-17 22:07:43 +080096 private final Runnable mPlayingChecker = new Runnable() {
Owen Lin540e4692011-09-14 19:49:03 +080097 @Override
Owen Linf9a0a432011-08-17 22:07:43 +080098 public void run() {
99 if (mVideoView.isPlaying()) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800100 mController.showPlaying();
Owen Linf9a0a432011-08-17 22:07:43 +0800101 } else {
102 mHandler.postDelayed(mPlayingChecker, 250);
103 }
104 }
105 };
106
Owen Lin00bb8e42012-05-23 15:47:36 -0700107 private final Runnable mRemoveBackground = new Runnable() {
Wu-cheng Li22a36332012-06-21 16:45:55 +0800108 @SuppressWarnings("deprecation")
Owen Lin00bb8e42012-05-23 15:47:36 -0700109 @Override
110 public void run() {
Wu-cheng Li22a36332012-06-21 16:45:55 +0800111 mRootView.setBackgroundDrawable(null);
Owen Lin00bb8e42012-05-23 15:47:36 -0700112 }
113 };
114
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800115 private final Runnable mProgressChecker = new Runnable() {
116 @Override
117 public void run() {
118 int pos = setProgress();
119 mHandler.postDelayed(mProgressChecker, 1000 - (pos % 1000));
120 }
121 };
122
Owen Lin00bb8e42012-05-23 15:47:36 -0700123 public MoviePlayer(View rootView, final MovieActivity movieActivity,
124 Uri videoUri, Bundle savedInstance, boolean canReplay) {
Owen Linf9a0a432011-08-17 22:07:43 +0800125 mContext = movieActivity.getApplicationContext();
Owen Lin00bb8e42012-05-23 15:47:36 -0700126 mRootView = rootView;
Owen Linf9a0a432011-08-17 22:07:43 +0800127 mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
Owen Linf9a0a432011-08-17 22:07:43 +0800128 mBookmarker = new Bookmarker(movieActivity);
Owen Linf9a0a432011-08-17 22:07:43 +0800129 mUri = videoUri;
130
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800131 mController = new MovieControllerOverlay(mContext);
132 ((ViewGroup)rootView).addView(mController.getView());
133 mController.setListener(this);
134 mController.setCanReplay(canReplay);
Owen Linf9a0a432011-08-17 22:07:43 +0800135
136 mVideoView.setOnErrorListener(this);
137 mVideoView.setOnCompletionListener(this);
138 mVideoView.setVideoURI(mUri);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800139 mVideoView.setOnTouchListener(new View.OnTouchListener() {
Owen Lin00bb8e42012-05-23 15:47:36 -0700140 @Override
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800141 public boolean onTouch(View v, MotionEvent event) {
142 mController.show();
143 return true;
Owen Linf9a0a432011-08-17 22:07:43 +0800144 }
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800145 });
Chih-Chung Chang81760122011-09-27 16:50:16 +0800146
Owen Lin00bb8e42012-05-23 15:47:36 -0700147 // The SurfaceView is transparent before drawing the first frame.
148 // This makes the UI flashing when open a video. (black -> old screen
149 // -> video) However, we have no way to know the timing of the first
150 // frame. So, we hide the VideoView for a while to make sure the
151 // video has been drawn on it.
152 mVideoView.postDelayed(new Runnable() {
153 @Override
154 public void run() {
155 mVideoView.setVisibility(View.VISIBLE);
156 }
157 }, BLACK_TIMEOUT);
158
Owen Lin7a5e1e72012-06-20 16:54:24 +0800159 setOnSystemUiVisibilityChangeListener();
160 // Hide system UI by default
161 showSystemUi(false);
162
163 mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
164 mAudioBecomingNoisyReceiver.register();
165
166 Intent i = new Intent(SERVICECMD);
167 i.putExtra(CMDNAME, CMDPAUSE);
168 movieActivity.sendBroadcast(i);
169
170 if (savedInstance != null) { // this is a resumed activity
171 mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0);
172 mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE);
173 mVideoView.start();
174 mVideoView.suspend();
175 mHasPaused = true;
176 } else {
177 final Integer bookmark = mBookmarker.getBookmark(mUri);
178 if (bookmark != null) {
179 showResumeDialog(movieActivity, bookmark);
180 } else {
181 startVideo();
182 }
183 }
184 }
185
Owen Lindf4763c2012-06-28 17:12:20 +0800186 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
Owen Lin7a5e1e72012-06-20 16:54:24 +0800187 private void setOnSystemUiVisibilityChangeListener() {
188 if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION) return;
189
Chih-Chung Chang81760122011-09-27 16:50:16 +0800190 // When the user touches the screen or uses some hard key, the framework
191 // will change system ui visibility from invisible to visible. We show
Ray Chen392a2922012-05-14 17:19:56 +0800192 // the media control and enable system UI (e.g. ActionBar) to be visible at this point
Chih-Chung Chang81760122011-09-27 16:50:16 +0800193 mVideoView.setOnSystemUiVisibilityChangeListener(
194 new View.OnSystemUiVisibilityChangeListener() {
Owen Lin00bb8e42012-05-23 15:47:36 -0700195 @Override
Chih-Chung Chang81760122011-09-27 16:50:16 +0800196 public void onSystemUiVisibilityChange(int visibility) {
Ray Chen24525c22012-05-22 17:34:29 +0800197 int diff = mLastSystemUiVis ^ visibility;
198 mLastSystemUiVis = visibility;
199 if ((diff & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
200 && (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800201 mController.show();
Owen Lin00bb8e42012-05-23 15:47:36 -0700202
203 // We need to set the background to clear ghosting images
204 // when ActionBar slides in. However, if we keep the background,
205 // there will be one additional layer in HW composer, which is bad
206 // to battery. As a solution, we remove the background when we
207 // hide the action bar
208 mHandler.removeCallbacks(mRemoveBackground);
209 mRootView.setBackgroundColor(Color.BLACK);
210 } else {
211 mHandler.removeCallbacks(mRemoveBackground);
212
213 // Wait for the slide out animation, one second should be enough
214 mHandler.postDelayed(mRemoveBackground, 1000);
Chih-Chung Chang81760122011-09-27 16:50:16 +0800215 }
216 }
217 });
Owen Linf9a0a432011-08-17 22:07:43 +0800218 }
219
Owen Lin28cb4162012-08-29 11:53:10 +0800220 @SuppressWarnings("deprecation")
Owen Lindf4763c2012-06-28 17:12:20 +0800221 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
Chih-Chung Chang81760122011-09-27 16:50:16 +0800222 private void showSystemUi(boolean visible) {
Owen Lin7a5e1e72012-06-20 16:54:24 +0800223 if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) return;
224
Ray Chen24525c22012-05-22 17:34:29 +0800225 int flag = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
226 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Owen Lin7a5e1e72012-06-20 16:54:24 +0800227 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
Ray Chen24525c22012-05-22 17:34:29 +0800228 if (!visible) {
Owen Lin28cb4162012-08-29 11:53:10 +0800229 // We used the deprecated "STATUS_BAR_HIDDEN" for unbundling
The Android Open Source Projectca7d9bf2012-06-29 08:19:39 -0700230 flag |= View.STATUS_BAR_HIDDEN | View.SYSTEM_UI_FLAG_FULLSCREEN
Ray Chen24525c22012-05-22 17:34:29 +0800231 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
Ray Chen392a2922012-05-14 17:19:56 +0800232 }
Ray Chen24525c22012-05-22 17:34:29 +0800233 mVideoView.setSystemUiVisibility(flag);
Chih-Chung Chang81760122011-09-27 16:50:16 +0800234 }
235
Owen Lin540e4692011-09-14 19:49:03 +0800236 public void onSaveInstanceState(Bundle outState) {
237 outState.putInt(KEY_VIDEO_POSITION, mVideoPosition);
238 outState.putLong(KEY_RESUMEABLE_TIME, mResumeableTime);
239 }
240
Owen Linf9a0a432011-08-17 22:07:43 +0800241 private void showResumeDialog(Context context, final int bookmark) {
242 AlertDialog.Builder builder = new AlertDialog.Builder(context);
243 builder.setTitle(R.string.resume_playing_title);
244 builder.setMessage(String.format(
245 context.getString(R.string.resume_playing_message),
246 GalleryUtils.formatDuration(context, bookmark / 1000)));
247 builder.setOnCancelListener(new OnCancelListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800248 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800249 public void onCancel(DialogInterface dialog) {
250 onCompletion();
251 }
252 });
253 builder.setPositiveButton(
254 R.string.resume_playing_resume, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800255 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800256 public void onClick(DialogInterface dialog, int which) {
257 mVideoView.seekTo(bookmark);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800258 startVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800259 }
260 });
261 builder.setNegativeButton(
262 R.string.resume_playing_restart, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800263 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800264 public void onClick(DialogInterface dialog, int which) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800265 startVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800266 }
267 });
268 builder.show();
269 }
270
271 public void onPause() {
Owen Linf9a0a432011-08-17 22:07:43 +0800272 mHasPaused = true;
Owen Lin540e4692011-09-14 19:49:03 +0800273 mHandler.removeCallbacksAndMessages(null);
274 mVideoPosition = mVideoView.getCurrentPosition();
275 mBookmarker.setBookmark(mUri, mVideoPosition, mVideoView.getDuration());
276 mVideoView.suspend();
277 mResumeableTime = System.currentTimeMillis() + RESUMEABLE_TIMEOUT;
Owen Linf9a0a432011-08-17 22:07:43 +0800278 }
279
280 public void onResume() {
281 if (mHasPaused) {
Owen Lin540e4692011-09-14 19:49:03 +0800282 mVideoView.seekTo(mVideoPosition);
283 mVideoView.resume();
284
285 // If we have slept for too long, pause the play
286 if (System.currentTimeMillis() > mResumeableTime) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800287 pauseVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800288 }
289 }
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800290 mHandler.post(mProgressChecker);
Owen Linf9a0a432011-08-17 22:07:43 +0800291 }
292
293 public void onDestroy() {
294 mVideoView.stopPlayback();
295 mAudioBecomingNoisyReceiver.unregister();
296 }
297
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800298 // This updates the time bar display (if necessary). It is called every
299 // second by mProgressChecker and also from places where the time bar needs
300 // to be updated immediately.
301 private int setProgress() {
302 if (mDragging || !mShowing) {
303 return 0;
304 }
305 int position = mVideoView.getCurrentPosition();
306 int duration = mVideoView.getDuration();
307 mController.setTimes(position, duration);
308 return position;
309 }
310
311 private void startVideo() {
312 // For streams that we expect to be slow to start up, show a
313 // progress spinner until playback starts.
314 String scheme = mUri.getScheme();
315 if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) {
316 mController.showLoading();
317 mHandler.removeCallbacks(mPlayingChecker);
318 mHandler.postDelayed(mPlayingChecker, 250);
319 } else {
320 mController.showPlaying();
Owen Lin00bb8e42012-05-23 15:47:36 -0700321 mController.hide();
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800322 }
323
324 mVideoView.start();
325 setProgress();
326 }
327
328 private void playVideo() {
329 mVideoView.start();
330 mController.showPlaying();
331 setProgress();
332 }
333
334 private void pauseVideo() {
335 mVideoView.pause();
336 mController.showPaused();
337 }
338
339 // Below are notifications from VideoView
340 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800341 public boolean onError(MediaPlayer player, int arg1, int arg2) {
342 mHandler.removeCallbacksAndMessages(null);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800343 // VideoView will show an error dialog if we return false, so no need
344 // to show more message.
345 mController.showErrorMessage("");
Owen Linf9a0a432011-08-17 22:07:43 +0800346 return false;
347 }
348
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800349 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800350 public void onCompletion(MediaPlayer mp) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800351 mController.showEnded();
Owen Linf9a0a432011-08-17 22:07:43 +0800352 onCompletion();
353 }
354
355 public void onCompletion() {
356 }
357
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800358 // Below are notifications from ControllerOverlay
359 @Override
360 public void onPlayPause() {
361 if (mVideoView.isPlaying()) {
362 pauseVideo();
363 } else {
364 playVideo();
365 }
366 }
367
368 @Override
369 public void onSeekStart() {
370 mDragging = true;
371 }
372
373 @Override
374 public void onSeekMove(int time) {
375 mVideoView.seekTo(time);
376 }
377
378 @Override
379 public void onSeekEnd(int time) {
380 mDragging = false;
381 mVideoView.seekTo(time);
382 setProgress();
383 }
384
385 @Override
386 public void onShown() {
387 mShowing = true;
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800388 setProgress();
Owen Lin0b4e9092012-06-19 14:35:06 +0800389 showSystemUi(true);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800390 }
391
392 @Override
393 public void onHidden() {
394 mShowing = false;
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800395 showSystemUi(false);
396 }
397
398 @Override
399 public void onReplay() {
400 startVideo();
401 }
402
Chih-Chung Changfc8c5032011-11-29 14:21:11 +0800403 // Below are key events passed from MovieActivity.
404 public boolean onKeyDown(int keyCode, KeyEvent event) {
405
406 // Some headsets will fire off 7-10 events on a single click
407 if (event.getRepeatCount() > 0) {
408 return isMediaKey(keyCode);
409 }
410
411 switch (keyCode) {
412 case KeyEvent.KEYCODE_HEADSETHOOK:
413 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
414 if (mVideoView.isPlaying()) {
415 pauseVideo();
416 } else {
417 playVideo();
418 }
419 return true;
Chih-Chung Chang67721732012-07-03 12:49:45 +0800420 case KEYCODE_MEDIA_PAUSE:
Chih-Chung Changfc8c5032011-11-29 14:21:11 +0800421 if (mVideoView.isPlaying()) {
422 pauseVideo();
423 }
424 return true;
Chih-Chung Chang67721732012-07-03 12:49:45 +0800425 case KEYCODE_MEDIA_PLAY:
Chih-Chung Changfc8c5032011-11-29 14:21:11 +0800426 if (!mVideoView.isPlaying()) {
427 playVideo();
428 }
429 return true;
430 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
431 case KeyEvent.KEYCODE_MEDIA_NEXT:
432 // TODO: Handle next / previous accordingly, for now we're
433 // just consuming the events.
434 return true;
435 }
436 return false;
437 }
438
439 public boolean onKeyUp(int keyCode, KeyEvent event) {
440 return isMediaKey(keyCode);
441 }
442
443 private static boolean isMediaKey(int keyCode) {
444 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
445 || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS
446 || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT
447 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
448 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY
449 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE;
450 }
451
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800452 // We want to pause when the headset is unplugged.
Owen Linf9a0a432011-08-17 22:07:43 +0800453 private class AudioBecomingNoisyReceiver extends BroadcastReceiver {
454
455 public void register() {
456 mContext.registerReceiver(this,
457 new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
458 }
459
460 public void unregister() {
461 mContext.unregisterReceiver(this);
462 }
463
464 @Override
465 public void onReceive(Context context, Intent intent) {
Owen Linaea077a2011-11-11 12:01:09 +0800466 if (mVideoView.isPlaying()) pauseVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800467 }
468 }
469}
470
471class Bookmarker {
472 private static final String TAG = "Bookmarker";
473
474 private static final String BOOKMARK_CACHE_FILE = "bookmark";
475 private static final int BOOKMARK_CACHE_MAX_ENTRIES = 100;
476 private static final int BOOKMARK_CACHE_MAX_BYTES = 10 * 1024;
477 private static final int BOOKMARK_CACHE_VERSION = 1;
478
479 private static final int HALF_MINUTE = 30 * 1000;
480 private static final int TWO_MINUTES = 4 * HALF_MINUTE;
481
482 private final Context mContext;
483
484 public Bookmarker(Context context) {
485 mContext = context;
486 }
487
488 public void setBookmark(Uri uri, int bookmark, int duration) {
489 try {
490 BlobCache cache = CacheManager.getCache(mContext,
491 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
492 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
493
494 ByteArrayOutputStream bos = new ByteArrayOutputStream();
495 DataOutputStream dos = new DataOutputStream(bos);
496 dos.writeUTF(uri.toString());
497 dos.writeInt(bookmark);
498 dos.writeInt(duration);
499 dos.flush();
500 cache.insert(uri.hashCode(), bos.toByteArray());
501 } catch (Throwable t) {
502 Log.w(TAG, "setBookmark failed", t);
503 }
504 }
505
506 public Integer getBookmark(Uri uri) {
507 try {
508 BlobCache cache = CacheManager.getCache(mContext,
509 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
510 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
511
512 byte[] data = cache.lookup(uri.hashCode());
513 if (data == null) return null;
514
515 DataInputStream dis = new DataInputStream(
516 new ByteArrayInputStream(data));
517
Owen Lin7a5e1e72012-06-20 16:54:24 +0800518 String uriString = DataInputStream.readUTF(dis);
Owen Linf9a0a432011-08-17 22:07:43 +0800519 int bookmark = dis.readInt();
520 int duration = dis.readInt();
521
522 if (!uriString.equals(uri.toString())) {
523 return null;
524 }
525
526 if ((bookmark < HALF_MINUTE) || (duration < TWO_MINUTES)
527 || (bookmark > (duration - HALF_MINUTE))) {
528 return null;
529 }
530 return Integer.valueOf(bookmark);
531 } catch (Throwable t) {
532 Log.w(TAG, "getBookmark failed", t);
533 }
534 return null;
535 }
536}