blob: c0f0ce73935eb29cacc0fcd3cafad1db4ba8f0c4 [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 Lin7a5e1e72012-06-20 16:54:24 +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
Owen Linf9a0a432011-08-17 22:07:43 +080061 // Copied from MediaPlaybackService in the Music Player app.
62 private static final String SERVICECMD = "com.android.music.musicservicecommand";
63 private static final String CMDNAME = "command";
64 private static final String CMDPAUSE = "pause";
65
Owen Lin00bb8e42012-05-23 15:47:36 -070066 private static final long BLACK_TIMEOUT = 500;
67
Owen Lin540e4692011-09-14 19:49:03 +080068 // If we resume the acitivty with in RESUMEABLE_TIMEOUT, we will keep playing.
69 // Otherwise, we pause the player.
70 private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins
71
Owen Linf9a0a432011-08-17 22:07:43 +080072 private Context mContext;
73 private final VideoView mVideoView;
Owen Lin00bb8e42012-05-23 15:47:36 -070074 private final View mRootView;
Owen Linf9a0a432011-08-17 22:07:43 +080075 private final Bookmarker mBookmarker;
76 private final Uri mUri;
77 private final Handler mHandler = new Handler();
78 private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver;
Owen Lin00bb8e42012-05-23 15:47:36 -070079 private final MovieControllerOverlay mController;
Owen Linf9a0a432011-08-17 22:07:43 +080080
Owen Lin540e4692011-09-14 19:49:03 +080081 private long mResumeableTime = Long.MAX_VALUE;
82 private int mVideoPosition = 0;
83 private boolean mHasPaused = false;
Ray Chen24525c22012-05-22 17:34:29 +080084 private int mLastSystemUiVis = 0;
Owen Linf9a0a432011-08-17 22:07:43 +080085
Chih-Chung Chang209a9162011-10-14 16:09:09 +080086 // If the time bar is being dragged.
87 private boolean mDragging;
88
89 // If the time bar is visible.
90 private boolean mShowing;
91
Owen Linf9a0a432011-08-17 22:07:43 +080092 private final Runnable mPlayingChecker = new Runnable() {
Owen Lin540e4692011-09-14 19:49:03 +080093 @Override
Owen Linf9a0a432011-08-17 22:07:43 +080094 public void run() {
95 if (mVideoView.isPlaying()) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +080096 mController.showPlaying();
Owen Linf9a0a432011-08-17 22:07:43 +080097 } else {
98 mHandler.postDelayed(mPlayingChecker, 250);
99 }
100 }
101 };
102
Owen Lin00bb8e42012-05-23 15:47:36 -0700103 private final Runnable mRemoveBackground = new Runnable() {
Wu-cheng Li22a36332012-06-21 16:45:55 +0800104 @SuppressWarnings("deprecation")
Owen Lin00bb8e42012-05-23 15:47:36 -0700105 @Override
106 public void run() {
Wu-cheng Li22a36332012-06-21 16:45:55 +0800107 mRootView.setBackgroundDrawable(null);
Owen Lin00bb8e42012-05-23 15:47:36 -0700108 }
109 };
110
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800111 private final Runnable mProgressChecker = new Runnable() {
112 @Override
113 public void run() {
114 int pos = setProgress();
115 mHandler.postDelayed(mProgressChecker, 1000 - (pos % 1000));
116 }
117 };
118
Owen Lin00bb8e42012-05-23 15:47:36 -0700119 public MoviePlayer(View rootView, final MovieActivity movieActivity,
120 Uri videoUri, Bundle savedInstance, boolean canReplay) {
Owen Linf9a0a432011-08-17 22:07:43 +0800121 mContext = movieActivity.getApplicationContext();
Owen Lin00bb8e42012-05-23 15:47:36 -0700122 mRootView = rootView;
Owen Linf9a0a432011-08-17 22:07:43 +0800123 mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
Owen Linf9a0a432011-08-17 22:07:43 +0800124 mBookmarker = new Bookmarker(movieActivity);
Owen Linf9a0a432011-08-17 22:07:43 +0800125 mUri = videoUri;
126
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800127 mController = new MovieControllerOverlay(mContext);
128 ((ViewGroup)rootView).addView(mController.getView());
129 mController.setListener(this);
130 mController.setCanReplay(canReplay);
Owen Linf9a0a432011-08-17 22:07:43 +0800131
132 mVideoView.setOnErrorListener(this);
133 mVideoView.setOnCompletionListener(this);
134 mVideoView.setVideoURI(mUri);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800135 mVideoView.setOnTouchListener(new View.OnTouchListener() {
Owen Lin00bb8e42012-05-23 15:47:36 -0700136 @Override
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800137 public boolean onTouch(View v, MotionEvent event) {
138 mController.show();
139 return true;
Owen Linf9a0a432011-08-17 22:07:43 +0800140 }
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800141 });
Chih-Chung Chang81760122011-09-27 16:50:16 +0800142
Owen Lin00bb8e42012-05-23 15:47:36 -0700143 // The SurfaceView is transparent before drawing the first frame.
144 // This makes the UI flashing when open a video. (black -> old screen
145 // -> video) However, we have no way to know the timing of the first
146 // frame. So, we hide the VideoView for a while to make sure the
147 // video has been drawn on it.
148 mVideoView.postDelayed(new Runnable() {
149 @Override
150 public void run() {
151 mVideoView.setVisibility(View.VISIBLE);
152 }
153 }, BLACK_TIMEOUT);
154
Owen Lin7a5e1e72012-06-20 16:54:24 +0800155 setOnSystemUiVisibilityChangeListener();
156 // Hide system UI by default
157 showSystemUi(false);
158
159 mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
160 mAudioBecomingNoisyReceiver.register();
161
162 Intent i = new Intent(SERVICECMD);
163 i.putExtra(CMDNAME, CMDPAUSE);
164 movieActivity.sendBroadcast(i);
165
166 if (savedInstance != null) { // this is a resumed activity
167 mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0);
168 mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE);
169 mVideoView.start();
170 mVideoView.suspend();
171 mHasPaused = true;
172 } else {
173 final Integer bookmark = mBookmarker.getBookmark(mUri);
174 if (bookmark != null) {
175 showResumeDialog(movieActivity, bookmark);
176 } else {
177 startVideo();
178 }
179 }
180 }
181
182 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
183 private void setOnSystemUiVisibilityChangeListener() {
184 if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION) return;
185
Chih-Chung Chang81760122011-09-27 16:50:16 +0800186 // When the user touches the screen or uses some hard key, the framework
187 // will change system ui visibility from invisible to visible. We show
Ray Chen392a2922012-05-14 17:19:56 +0800188 // the media control and enable system UI (e.g. ActionBar) to be visible at this point
Chih-Chung Chang81760122011-09-27 16:50:16 +0800189 mVideoView.setOnSystemUiVisibilityChangeListener(
190 new View.OnSystemUiVisibilityChangeListener() {
Owen Lin00bb8e42012-05-23 15:47:36 -0700191 @Override
Chih-Chung Chang81760122011-09-27 16:50:16 +0800192 public void onSystemUiVisibilityChange(int visibility) {
Ray Chen24525c22012-05-22 17:34:29 +0800193 int diff = mLastSystemUiVis ^ visibility;
194 mLastSystemUiVis = visibility;
195 if ((diff & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
196 && (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800197 mController.show();
Owen Lin00bb8e42012-05-23 15:47:36 -0700198
199 // We need to set the background to clear ghosting images
200 // when ActionBar slides in. However, if we keep the background,
201 // there will be one additional layer in HW composer, which is bad
202 // to battery. As a solution, we remove the background when we
203 // hide the action bar
204 mHandler.removeCallbacks(mRemoveBackground);
205 mRootView.setBackgroundColor(Color.BLACK);
206 } else {
207 mHandler.removeCallbacks(mRemoveBackground);
208
209 // Wait for the slide out animation, one second should be enough
210 mHandler.postDelayed(mRemoveBackground, 1000);
Chih-Chung Chang81760122011-09-27 16:50:16 +0800211 }
212 }
213 });
Owen Linf9a0a432011-08-17 22:07:43 +0800214 }
215
Owen Lin7a5e1e72012-06-20 16:54:24 +0800216 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
Chih-Chung Chang81760122011-09-27 16:50:16 +0800217 private void showSystemUi(boolean visible) {
Owen Lin7a5e1e72012-06-20 16:54:24 +0800218 if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) return;
219
Ray Chen24525c22012-05-22 17:34:29 +0800220 int flag = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
221 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Owen Lin7a5e1e72012-06-20 16:54:24 +0800222 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
Ray Chen24525c22012-05-22 17:34:29 +0800223 if (!visible) {
224 flag |= View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN
225 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
Ray Chen392a2922012-05-14 17:19:56 +0800226 }
Ray Chen24525c22012-05-22 17:34:29 +0800227 mVideoView.setSystemUiVisibility(flag);
Chih-Chung Chang81760122011-09-27 16:50:16 +0800228 }
229
Owen Lin540e4692011-09-14 19:49:03 +0800230 public void onSaveInstanceState(Bundle outState) {
231 outState.putInt(KEY_VIDEO_POSITION, mVideoPosition);
232 outState.putLong(KEY_RESUMEABLE_TIME, mResumeableTime);
233 }
234
Owen Linf9a0a432011-08-17 22:07:43 +0800235 private void showResumeDialog(Context context, final int bookmark) {
236 AlertDialog.Builder builder = new AlertDialog.Builder(context);
237 builder.setTitle(R.string.resume_playing_title);
238 builder.setMessage(String.format(
239 context.getString(R.string.resume_playing_message),
240 GalleryUtils.formatDuration(context, bookmark / 1000)));
241 builder.setOnCancelListener(new OnCancelListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800242 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800243 public void onCancel(DialogInterface dialog) {
244 onCompletion();
245 }
246 });
247 builder.setPositiveButton(
248 R.string.resume_playing_resume, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800249 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800250 public void onClick(DialogInterface dialog, int which) {
251 mVideoView.seekTo(bookmark);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800252 startVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800253 }
254 });
255 builder.setNegativeButton(
256 R.string.resume_playing_restart, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800257 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800258 public void onClick(DialogInterface dialog, int which) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800259 startVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800260 }
261 });
262 builder.show();
263 }
264
265 public void onPause() {
Owen Linf9a0a432011-08-17 22:07:43 +0800266 mHasPaused = true;
Owen Lin540e4692011-09-14 19:49:03 +0800267 mHandler.removeCallbacksAndMessages(null);
268 mVideoPosition = mVideoView.getCurrentPosition();
269 mBookmarker.setBookmark(mUri, mVideoPosition, mVideoView.getDuration());
270 mVideoView.suspend();
271 mResumeableTime = System.currentTimeMillis() + RESUMEABLE_TIMEOUT;
Owen Linf9a0a432011-08-17 22:07:43 +0800272 }
273
274 public void onResume() {
275 if (mHasPaused) {
Owen Lin540e4692011-09-14 19:49:03 +0800276 mVideoView.seekTo(mVideoPosition);
277 mVideoView.resume();
278
279 // If we have slept for too long, pause the play
280 if (System.currentTimeMillis() > mResumeableTime) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800281 pauseVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800282 }
283 }
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800284 mHandler.post(mProgressChecker);
Owen Linf9a0a432011-08-17 22:07:43 +0800285 }
286
287 public void onDestroy() {
288 mVideoView.stopPlayback();
289 mAudioBecomingNoisyReceiver.unregister();
290 }
291
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800292 // This updates the time bar display (if necessary). It is called every
293 // second by mProgressChecker and also from places where the time bar needs
294 // to be updated immediately.
295 private int setProgress() {
296 if (mDragging || !mShowing) {
297 return 0;
298 }
299 int position = mVideoView.getCurrentPosition();
300 int duration = mVideoView.getDuration();
301 mController.setTimes(position, duration);
302 return position;
303 }
304
305 private void startVideo() {
306 // For streams that we expect to be slow to start up, show a
307 // progress spinner until playback starts.
308 String scheme = mUri.getScheme();
309 if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) {
310 mController.showLoading();
311 mHandler.removeCallbacks(mPlayingChecker);
312 mHandler.postDelayed(mPlayingChecker, 250);
313 } else {
314 mController.showPlaying();
Owen Lin00bb8e42012-05-23 15:47:36 -0700315 mController.hide();
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800316 }
317
318 mVideoView.start();
319 setProgress();
320 }
321
322 private void playVideo() {
323 mVideoView.start();
324 mController.showPlaying();
325 setProgress();
326 }
327
328 private void pauseVideo() {
329 mVideoView.pause();
330 mController.showPaused();
331 }
332
333 // Below are notifications from VideoView
334 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800335 public boolean onError(MediaPlayer player, int arg1, int arg2) {
336 mHandler.removeCallbacksAndMessages(null);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800337 // VideoView will show an error dialog if we return false, so no need
338 // to show more message.
339 mController.showErrorMessage("");
Owen Linf9a0a432011-08-17 22:07:43 +0800340 return false;
341 }
342
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800343 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800344 public void onCompletion(MediaPlayer mp) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800345 mController.showEnded();
Owen Linf9a0a432011-08-17 22:07:43 +0800346 onCompletion();
347 }
348
349 public void onCompletion() {
350 }
351
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800352 // Below are notifications from ControllerOverlay
353 @Override
354 public void onPlayPause() {
355 if (mVideoView.isPlaying()) {
356 pauseVideo();
357 } else {
358 playVideo();
359 }
360 }
361
362 @Override
363 public void onSeekStart() {
364 mDragging = true;
365 }
366
367 @Override
368 public void onSeekMove(int time) {
369 mVideoView.seekTo(time);
370 }
371
372 @Override
373 public void onSeekEnd(int time) {
374 mDragging = false;
375 mVideoView.seekTo(time);
376 setProgress();
377 }
378
379 @Override
380 public void onShown() {
381 mShowing = true;
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800382 setProgress();
Owen Lin0b4e9092012-06-19 14:35:06 +0800383 showSystemUi(true);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800384 }
385
386 @Override
387 public void onHidden() {
388 mShowing = false;
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800389 showSystemUi(false);
390 }
391
392 @Override
393 public void onReplay() {
394 startVideo();
395 }
396
Chih-Chung Changfc8c5032011-11-29 14:21:11 +0800397 // Below are key events passed from MovieActivity.
398 public boolean onKeyDown(int keyCode, KeyEvent event) {
399
400 // Some headsets will fire off 7-10 events on a single click
401 if (event.getRepeatCount() > 0) {
402 return isMediaKey(keyCode);
403 }
404
405 switch (keyCode) {
406 case KeyEvent.KEYCODE_HEADSETHOOK:
407 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
408 if (mVideoView.isPlaying()) {
409 pauseVideo();
410 } else {
411 playVideo();
412 }
413 return true;
414 case KeyEvent.KEYCODE_MEDIA_PAUSE:
415 if (mVideoView.isPlaying()) {
416 pauseVideo();
417 }
418 return true;
419 case KeyEvent.KEYCODE_MEDIA_PLAY:
420 if (!mVideoView.isPlaying()) {
421 playVideo();
422 }
423 return true;
424 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
425 case KeyEvent.KEYCODE_MEDIA_NEXT:
426 // TODO: Handle next / previous accordingly, for now we're
427 // just consuming the events.
428 return true;
429 }
430 return false;
431 }
432
433 public boolean onKeyUp(int keyCode, KeyEvent event) {
434 return isMediaKey(keyCode);
435 }
436
437 private static boolean isMediaKey(int keyCode) {
438 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
439 || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS
440 || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT
441 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
442 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY
443 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE;
444 }
445
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800446 // We want to pause when the headset is unplugged.
Owen Linf9a0a432011-08-17 22:07:43 +0800447 private class AudioBecomingNoisyReceiver extends BroadcastReceiver {
448
449 public void register() {
450 mContext.registerReceiver(this,
451 new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
452 }
453
454 public void unregister() {
455 mContext.unregisterReceiver(this);
456 }
457
458 @Override
459 public void onReceive(Context context, Intent intent) {
Owen Linaea077a2011-11-11 12:01:09 +0800460 if (mVideoView.isPlaying()) pauseVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800461 }
462 }
463}
464
465class Bookmarker {
466 private static final String TAG = "Bookmarker";
467
468 private static final String BOOKMARK_CACHE_FILE = "bookmark";
469 private static final int BOOKMARK_CACHE_MAX_ENTRIES = 100;
470 private static final int BOOKMARK_CACHE_MAX_BYTES = 10 * 1024;
471 private static final int BOOKMARK_CACHE_VERSION = 1;
472
473 private static final int HALF_MINUTE = 30 * 1000;
474 private static final int TWO_MINUTES = 4 * HALF_MINUTE;
475
476 private final Context mContext;
477
478 public Bookmarker(Context context) {
479 mContext = context;
480 }
481
482 public void setBookmark(Uri uri, int bookmark, int duration) {
483 try {
484 BlobCache cache = CacheManager.getCache(mContext,
485 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
486 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
487
488 ByteArrayOutputStream bos = new ByteArrayOutputStream();
489 DataOutputStream dos = new DataOutputStream(bos);
490 dos.writeUTF(uri.toString());
491 dos.writeInt(bookmark);
492 dos.writeInt(duration);
493 dos.flush();
494 cache.insert(uri.hashCode(), bos.toByteArray());
495 } catch (Throwable t) {
496 Log.w(TAG, "setBookmark failed", t);
497 }
498 }
499
500 public Integer getBookmark(Uri uri) {
501 try {
502 BlobCache cache = CacheManager.getCache(mContext,
503 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
504 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
505
506 byte[] data = cache.lookup(uri.hashCode());
507 if (data == null) return null;
508
509 DataInputStream dis = new DataInputStream(
510 new ByteArrayInputStream(data));
511
Owen Lin7a5e1e72012-06-20 16:54:24 +0800512 String uriString = DataInputStream.readUTF(dis);
Owen Linf9a0a432011-08-17 22:07:43 +0800513 int bookmark = dis.readInt();
514 int duration = dis.readInt();
515
516 if (!uriString.equals(uri.toString())) {
517 return null;
518 }
519
520 if ((bookmark < HALF_MINUTE) || (duration < TWO_MINUTES)
521 || (bookmark > (duration - HALF_MINUTE))) {
522 return null;
523 }
524 return Integer.valueOf(bookmark);
525 } catch (Throwable t) {
526 Log.w(TAG, "getBookmark failed", t);
527 }
528 return null;
529 }
530}