blob: 4f72c13baeb888202b3d2bd7953b727b119640f1 [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 Lin540e4692011-09-14 19:49:03 +080032import android.os.Bundle;
Owen Linf9a0a432011-08-17 22:07:43 +080033import android.os.Handler;
Chih-Chung Changfc8c5032011-11-29 14:21:11 +080034import android.view.KeyEvent;
Chih-Chung Chang209a9162011-10-14 16:09:09 +080035import android.view.MotionEvent;
Owen Linf9a0a432011-08-17 22:07:43 +080036import android.view.View;
Chih-Chung Chang209a9162011-10-14 16:09:09 +080037import android.view.ViewGroup;
Owen Linf9a0a432011-08-17 22:07:43 +080038import android.widget.VideoView;
39
Owen Linaea077a2011-11-11 12:01:09 +080040import com.android.gallery3d.R;
Owen Lin7a5e1e72012-06-20 16:54:24 +080041import com.android.gallery3d.common.ApiHelper;
Owen Linaea077a2011-11-11 12:01:09 +080042import com.android.gallery3d.common.BlobCache;
43import com.android.gallery3d.util.CacheManager;
44import com.android.gallery3d.util.GalleryUtils;
45
Owen Linf9a0a432011-08-17 22:07:43 +080046import java.io.ByteArrayInputStream;
47import java.io.ByteArrayOutputStream;
48import java.io.DataInputStream;
49import java.io.DataOutputStream;
50
51public class MoviePlayer implements
Chih-Chung Chang209a9162011-10-14 16:09:09 +080052 MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener,
53 ControllerOverlay.Listener {
Owen Linf9a0a432011-08-17 22:07:43 +080054 @SuppressWarnings("unused")
55 private static final String TAG = "MoviePlayer";
56
Owen Lin540e4692011-09-14 19:49:03 +080057 private static final String KEY_VIDEO_POSITION = "video-position";
58 private static final String KEY_RESUMEABLE_TIME = "resumeable-timeout";
59
Owen Linf9a0a432011-08-17 22:07:43 +080060 // Copied from MediaPlaybackService in the Music Player app.
61 private static final String SERVICECMD = "com.android.music.musicservicecommand";
62 private static final String CMDNAME = "command";
63 private static final String CMDPAUSE = "pause";
64
Owen Lin00bb8e42012-05-23 15:47:36 -070065 private static final long BLACK_TIMEOUT = 500;
66
Owen Lin540e4692011-09-14 19:49:03 +080067 // If we resume the acitivty with in RESUMEABLE_TIMEOUT, we will keep playing.
68 // Otherwise, we pause the player.
69 private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins
70
Owen Linf9a0a432011-08-17 22:07:43 +080071 private Context mContext;
72 private final VideoView mVideoView;
Owen Lin00bb8e42012-05-23 15:47:36 -070073 private final View mRootView;
Owen Linf9a0a432011-08-17 22:07:43 +080074 private final Bookmarker mBookmarker;
75 private final Uri mUri;
76 private final Handler mHandler = new Handler();
77 private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver;
Owen Lin00bb8e42012-05-23 15:47:36 -070078 private final MovieControllerOverlay mController;
Owen Linf9a0a432011-08-17 22:07:43 +080079
Owen Lin540e4692011-09-14 19:49:03 +080080 private long mResumeableTime = Long.MAX_VALUE;
81 private int mVideoPosition = 0;
82 private boolean mHasPaused = false;
Ray Chen24525c22012-05-22 17:34:29 +080083 private int mLastSystemUiVis = 0;
Owen Linf9a0a432011-08-17 22:07:43 +080084
Chih-Chung Chang209a9162011-10-14 16:09:09 +080085 // If the time bar is being dragged.
86 private boolean mDragging;
87
88 // If the time bar is visible.
89 private boolean mShowing;
90
Owen Linf9a0a432011-08-17 22:07:43 +080091 private final Runnable mPlayingChecker = new Runnable() {
Owen Lin540e4692011-09-14 19:49:03 +080092 @Override
Owen Linf9a0a432011-08-17 22:07:43 +080093 public void run() {
94 if (mVideoView.isPlaying()) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +080095 mController.showPlaying();
Owen Linf9a0a432011-08-17 22:07:43 +080096 } else {
97 mHandler.postDelayed(mPlayingChecker, 250);
98 }
99 }
100 };
101
Owen Lin00bb8e42012-05-23 15:47:36 -0700102 private final Runnable mRemoveBackground = new Runnable() {
Wu-cheng Li22a36332012-06-21 16:45:55 +0800103 @SuppressWarnings("deprecation")
Owen Lin00bb8e42012-05-23 15:47:36 -0700104 @Override
105 public void run() {
Wu-cheng Li22a36332012-06-21 16:45:55 +0800106 mRootView.setBackgroundDrawable(null);
Owen Lin00bb8e42012-05-23 15:47:36 -0700107 }
108 };
109
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800110 private final Runnable mProgressChecker = new Runnable() {
111 @Override
112 public void run() {
113 int pos = setProgress();
114 mHandler.postDelayed(mProgressChecker, 1000 - (pos % 1000));
115 }
116 };
117
Owen Lin00bb8e42012-05-23 15:47:36 -0700118 public MoviePlayer(View rootView, final MovieActivity movieActivity,
119 Uri videoUri, Bundle savedInstance, boolean canReplay) {
Owen Linf9a0a432011-08-17 22:07:43 +0800120 mContext = movieActivity.getApplicationContext();
Owen Lin00bb8e42012-05-23 15:47:36 -0700121 mRootView = rootView;
Owen Linf9a0a432011-08-17 22:07:43 +0800122 mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
Owen Linf9a0a432011-08-17 22:07:43 +0800123 mBookmarker = new Bookmarker(movieActivity);
Owen Linf9a0a432011-08-17 22:07:43 +0800124 mUri = videoUri;
125
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800126 mController = new MovieControllerOverlay(mContext);
127 ((ViewGroup)rootView).addView(mController.getView());
128 mController.setListener(this);
129 mController.setCanReplay(canReplay);
Owen Linf9a0a432011-08-17 22:07:43 +0800130
131 mVideoView.setOnErrorListener(this);
132 mVideoView.setOnCompletionListener(this);
133 mVideoView.setVideoURI(mUri);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800134 mVideoView.setOnTouchListener(new View.OnTouchListener() {
Owen Lin00bb8e42012-05-23 15:47:36 -0700135 @Override
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800136 public boolean onTouch(View v, MotionEvent event) {
137 mController.show();
138 return true;
Owen Linf9a0a432011-08-17 22:07:43 +0800139 }
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800140 });
Chih-Chung Chang81760122011-09-27 16:50:16 +0800141
Owen Lin00bb8e42012-05-23 15:47:36 -0700142 // The SurfaceView is transparent before drawing the first frame.
143 // This makes the UI flashing when open a video. (black -> old screen
144 // -> video) However, we have no way to know the timing of the first
145 // frame. So, we hide the VideoView for a while to make sure the
146 // video has been drawn on it.
147 mVideoView.postDelayed(new Runnable() {
148 @Override
149 public void run() {
150 mVideoView.setVisibility(View.VISIBLE);
151 }
152 }, BLACK_TIMEOUT);
153
Owen Lin7a5e1e72012-06-20 16:54:24 +0800154 setOnSystemUiVisibilityChangeListener();
155 // Hide system UI by default
156 showSystemUi(false);
157
158 mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
159 mAudioBecomingNoisyReceiver.register();
160
161 Intent i = new Intent(SERVICECMD);
162 i.putExtra(CMDNAME, CMDPAUSE);
163 movieActivity.sendBroadcast(i);
164
165 if (savedInstance != null) { // this is a resumed activity
166 mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0);
167 mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE);
168 mVideoView.start();
169 mVideoView.suspend();
170 mHasPaused = true;
171 } else {
172 final Integer bookmark = mBookmarker.getBookmark(mUri);
173 if (bookmark != null) {
174 showResumeDialog(movieActivity, bookmark);
175 } else {
176 startVideo();
177 }
178 }
179 }
180
Owen Lin3a457252012-06-28 11:10:02 +0800181 @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
Owen Lin7a5e1e72012-06-20 16:54:24 +0800182 private void setOnSystemUiVisibilityChangeListener() {
183 if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION) return;
184
Chih-Chung Chang81760122011-09-27 16:50:16 +0800185 // When the user touches the screen or uses some hard key, the framework
186 // will change system ui visibility from invisible to visible. We show
Ray Chen392a2922012-05-14 17:19:56 +0800187 // the media control and enable system UI (e.g. ActionBar) to be visible at this point
Chih-Chung Chang81760122011-09-27 16:50:16 +0800188 mVideoView.setOnSystemUiVisibilityChangeListener(
189 new View.OnSystemUiVisibilityChangeListener() {
Owen Lin00bb8e42012-05-23 15:47:36 -0700190 @Override
Chih-Chung Chang81760122011-09-27 16:50:16 +0800191 public void onSystemUiVisibilityChange(int visibility) {
Ray Chen24525c22012-05-22 17:34:29 +0800192 int diff = mLastSystemUiVis ^ visibility;
193 mLastSystemUiVis = visibility;
194 if ((diff & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
195 && (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800196 mController.show();
Owen Lin00bb8e42012-05-23 15:47:36 -0700197
198 // We need to set the background to clear ghosting images
199 // when ActionBar slides in. However, if we keep the background,
200 // there will be one additional layer in HW composer, which is bad
201 // to battery. As a solution, we remove the background when we
202 // hide the action bar
203 mHandler.removeCallbacks(mRemoveBackground);
204 mRootView.setBackgroundColor(Color.BLACK);
205 } else {
206 mHandler.removeCallbacks(mRemoveBackground);
207
208 // Wait for the slide out animation, one second should be enough
209 mHandler.postDelayed(mRemoveBackground, 1000);
Chih-Chung Chang81760122011-09-27 16:50:16 +0800210 }
211 }
212 });
Owen Linf9a0a432011-08-17 22:07:43 +0800213 }
214
Owen Lin3a457252012-06-28 11:10:02 +0800215 @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
Chih-Chung Chang81760122011-09-27 16:50:16 +0800216 private void showSystemUi(boolean visible) {
Owen Lin7a5e1e72012-06-20 16:54:24 +0800217 if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) return;
218
Ray Chen24525c22012-05-22 17:34:29 +0800219 int flag = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
220 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Owen Lin7a5e1e72012-06-20 16:54:24 +0800221 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
Ray Chen24525c22012-05-22 17:34:29 +0800222 if (!visible) {
The Android Open Source Projectca7d9bf2012-06-29 08:19:39 -0700223 flag |= View.STATUS_BAR_HIDDEN | View.SYSTEM_UI_FLAG_FULLSCREEN
Ray Chen24525c22012-05-22 17:34:29 +0800224 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
Ray Chen392a2922012-05-14 17:19:56 +0800225 }
Ray Chen24525c22012-05-22 17:34:29 +0800226 mVideoView.setSystemUiVisibility(flag);
Chih-Chung Chang81760122011-09-27 16:50:16 +0800227 }
228
Owen Lin540e4692011-09-14 19:49:03 +0800229 public void onSaveInstanceState(Bundle outState) {
230 outState.putInt(KEY_VIDEO_POSITION, mVideoPosition);
231 outState.putLong(KEY_RESUMEABLE_TIME, mResumeableTime);
232 }
233
Owen Linf9a0a432011-08-17 22:07:43 +0800234 private void showResumeDialog(Context context, final int bookmark) {
235 AlertDialog.Builder builder = new AlertDialog.Builder(context);
236 builder.setTitle(R.string.resume_playing_title);
237 builder.setMessage(String.format(
238 context.getString(R.string.resume_playing_message),
239 GalleryUtils.formatDuration(context, bookmark / 1000)));
240 builder.setOnCancelListener(new OnCancelListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800241 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800242 public void onCancel(DialogInterface dialog) {
243 onCompletion();
244 }
245 });
246 builder.setPositiveButton(
247 R.string.resume_playing_resume, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800248 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800249 public void onClick(DialogInterface dialog, int which) {
250 mVideoView.seekTo(bookmark);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800251 startVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800252 }
253 });
254 builder.setNegativeButton(
255 R.string.resume_playing_restart, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800256 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800257 public void onClick(DialogInterface dialog, int which) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800258 startVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800259 }
260 });
261 builder.show();
262 }
263
264 public void onPause() {
Owen Linf9a0a432011-08-17 22:07:43 +0800265 mHasPaused = true;
Owen Lin540e4692011-09-14 19:49:03 +0800266 mHandler.removeCallbacksAndMessages(null);
267 mVideoPosition = mVideoView.getCurrentPosition();
268 mBookmarker.setBookmark(mUri, mVideoPosition, mVideoView.getDuration());
269 mVideoView.suspend();
270 mResumeableTime = System.currentTimeMillis() + RESUMEABLE_TIMEOUT;
Owen Linf9a0a432011-08-17 22:07:43 +0800271 }
272
273 public void onResume() {
274 if (mHasPaused) {
Owen Lin540e4692011-09-14 19:49:03 +0800275 mVideoView.seekTo(mVideoPosition);
276 mVideoView.resume();
277
278 // If we have slept for too long, pause the play
279 if (System.currentTimeMillis() > mResumeableTime) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800280 pauseVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800281 }
282 }
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800283 mHandler.post(mProgressChecker);
Owen Linf9a0a432011-08-17 22:07:43 +0800284 }
285
286 public void onDestroy() {
287 mVideoView.stopPlayback();
288 mAudioBecomingNoisyReceiver.unregister();
289 }
290
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800291 // This updates the time bar display (if necessary). It is called every
292 // second by mProgressChecker and also from places where the time bar needs
293 // to be updated immediately.
294 private int setProgress() {
295 if (mDragging || !mShowing) {
296 return 0;
297 }
298 int position = mVideoView.getCurrentPosition();
299 int duration = mVideoView.getDuration();
300 mController.setTimes(position, duration);
301 return position;
302 }
303
304 private void startVideo() {
305 // For streams that we expect to be slow to start up, show a
306 // progress spinner until playback starts.
307 String scheme = mUri.getScheme();
308 if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) {
309 mController.showLoading();
310 mHandler.removeCallbacks(mPlayingChecker);
311 mHandler.postDelayed(mPlayingChecker, 250);
312 } else {
313 mController.showPlaying();
Owen Lin00bb8e42012-05-23 15:47:36 -0700314 mController.hide();
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800315 }
316
317 mVideoView.start();
318 setProgress();
319 }
320
321 private void playVideo() {
322 mVideoView.start();
323 mController.showPlaying();
324 setProgress();
325 }
326
327 private void pauseVideo() {
328 mVideoView.pause();
329 mController.showPaused();
330 }
331
332 // Below are notifications from VideoView
333 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800334 public boolean onError(MediaPlayer player, int arg1, int arg2) {
335 mHandler.removeCallbacksAndMessages(null);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800336 // VideoView will show an error dialog if we return false, so no need
337 // to show more message.
338 mController.showErrorMessage("");
Owen Linf9a0a432011-08-17 22:07:43 +0800339 return false;
340 }
341
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800342 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800343 public void onCompletion(MediaPlayer mp) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800344 mController.showEnded();
Owen Linf9a0a432011-08-17 22:07:43 +0800345 onCompletion();
346 }
347
348 public void onCompletion() {
349 }
350
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800351 // Below are notifications from ControllerOverlay
352 @Override
353 public void onPlayPause() {
354 if (mVideoView.isPlaying()) {
355 pauseVideo();
356 } else {
357 playVideo();
358 }
359 }
360
361 @Override
362 public void onSeekStart() {
363 mDragging = true;
364 }
365
366 @Override
367 public void onSeekMove(int time) {
368 mVideoView.seekTo(time);
369 }
370
371 @Override
372 public void onSeekEnd(int time) {
373 mDragging = false;
374 mVideoView.seekTo(time);
375 setProgress();
376 }
377
378 @Override
379 public void onShown() {
380 mShowing = true;
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800381 setProgress();
Owen Lin0b4e9092012-06-19 14:35:06 +0800382 showSystemUi(true);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800383 }
384
385 @Override
386 public void onHidden() {
387 mShowing = false;
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800388 showSystemUi(false);
389 }
390
391 @Override
392 public void onReplay() {
393 startVideo();
394 }
395
Chih-Chung Changfc8c5032011-11-29 14:21:11 +0800396 // Below are key events passed from MovieActivity.
397 public boolean onKeyDown(int keyCode, KeyEvent event) {
398
399 // Some headsets will fire off 7-10 events on a single click
400 if (event.getRepeatCount() > 0) {
401 return isMediaKey(keyCode);
402 }
403
404 switch (keyCode) {
405 case KeyEvent.KEYCODE_HEADSETHOOK:
406 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
407 if (mVideoView.isPlaying()) {
408 pauseVideo();
409 } else {
410 playVideo();
411 }
412 return true;
413 case KeyEvent.KEYCODE_MEDIA_PAUSE:
414 if (mVideoView.isPlaying()) {
415 pauseVideo();
416 }
417 return true;
418 case KeyEvent.KEYCODE_MEDIA_PLAY:
419 if (!mVideoView.isPlaying()) {
420 playVideo();
421 }
422 return true;
423 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
424 case KeyEvent.KEYCODE_MEDIA_NEXT:
425 // TODO: Handle next / previous accordingly, for now we're
426 // just consuming the events.
427 return true;
428 }
429 return false;
430 }
431
432 public boolean onKeyUp(int keyCode, KeyEvent event) {
433 return isMediaKey(keyCode);
434 }
435
436 private static boolean isMediaKey(int keyCode) {
437 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
438 || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS
439 || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT
440 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
441 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY
442 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE;
443 }
444
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800445 // We want to pause when the headset is unplugged.
Owen Linf9a0a432011-08-17 22:07:43 +0800446 private class AudioBecomingNoisyReceiver extends BroadcastReceiver {
447
448 public void register() {
449 mContext.registerReceiver(this,
450 new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
451 }
452
453 public void unregister() {
454 mContext.unregisterReceiver(this);
455 }
456
457 @Override
458 public void onReceive(Context context, Intent intent) {
Owen Linaea077a2011-11-11 12:01:09 +0800459 if (mVideoView.isPlaying()) pauseVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800460 }
461 }
462}
463
464class Bookmarker {
465 private static final String TAG = "Bookmarker";
466
467 private static final String BOOKMARK_CACHE_FILE = "bookmark";
468 private static final int BOOKMARK_CACHE_MAX_ENTRIES = 100;
469 private static final int BOOKMARK_CACHE_MAX_BYTES = 10 * 1024;
470 private static final int BOOKMARK_CACHE_VERSION = 1;
471
472 private static final int HALF_MINUTE = 30 * 1000;
473 private static final int TWO_MINUTES = 4 * HALF_MINUTE;
474
475 private final Context mContext;
476
477 public Bookmarker(Context context) {
478 mContext = context;
479 }
480
481 public void setBookmark(Uri uri, int bookmark, int duration) {
482 try {
483 BlobCache cache = CacheManager.getCache(mContext,
484 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
485 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
486
487 ByteArrayOutputStream bos = new ByteArrayOutputStream();
488 DataOutputStream dos = new DataOutputStream(bos);
489 dos.writeUTF(uri.toString());
490 dos.writeInt(bookmark);
491 dos.writeInt(duration);
492 dos.flush();
493 cache.insert(uri.hashCode(), bos.toByteArray());
494 } catch (Throwable t) {
495 Log.w(TAG, "setBookmark failed", t);
496 }
497 }
498
499 public Integer getBookmark(Uri uri) {
500 try {
501 BlobCache cache = CacheManager.getCache(mContext,
502 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
503 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
504
505 byte[] data = cache.lookup(uri.hashCode());
506 if (data == null) return null;
507
508 DataInputStream dis = new DataInputStream(
509 new ByteArrayInputStream(data));
510
Owen Lin7a5e1e72012-06-20 16:54:24 +0800511 String uriString = DataInputStream.readUTF(dis);
Owen Linf9a0a432011-08-17 22:07:43 +0800512 int bookmark = dis.readInt();
513 int duration = dis.readInt();
514
515 if (!uriString.equals(uri.toString())) {
516 return null;
517 }
518
519 if ((bookmark < HALF_MINUTE) || (duration < TWO_MINUTES)
520 || (bookmark > (duration - HALF_MINUTE))) {
521 return null;
522 }
523 return Integer.valueOf(bookmark);
524 } catch (Throwable t) {
525 Log.w(TAG, "getBookmark failed", t);
526 }
527 return null;
528 }
529}