blob: 13a77f4721fbc11eb2917ed43ebe949e22e01f9c [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 Linf9a0a432011-08-17 22:07:43 +080019import android.app.AlertDialog;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.DialogInterface.OnCancelListener;
24import android.content.DialogInterface.OnClickListener;
25import android.content.Intent;
26import android.content.IntentFilter;
Owen Lin00bb8e42012-05-23 15:47:36 -070027import android.graphics.Color;
Owen Linf9a0a432011-08-17 22:07:43 +080028import android.media.AudioManager;
29import android.media.MediaPlayer;
30import android.net.Uri;
Owen Lin540e4692011-09-14 19:49:03 +080031import android.os.Bundle;
Owen Linf9a0a432011-08-17 22:07:43 +080032import android.os.Handler;
Chih-Chung Changfc8c5032011-11-29 14:21:11 +080033import android.view.KeyEvent;
Chih-Chung Chang209a9162011-10-14 16:09:09 +080034import android.view.MotionEvent;
Owen Linf9a0a432011-08-17 22:07:43 +080035import android.view.View;
Chih-Chung Chang209a9162011-10-14 16:09:09 +080036import android.view.ViewGroup;
Owen Linf9a0a432011-08-17 22:07:43 +080037import android.widget.VideoView;
38
Owen Linaea077a2011-11-11 12:01:09 +080039import com.android.gallery3d.R;
40import com.android.gallery3d.common.BlobCache;
41import com.android.gallery3d.util.CacheManager;
42import com.android.gallery3d.util.GalleryUtils;
43
Owen Linf9a0a432011-08-17 22:07:43 +080044import java.io.ByteArrayInputStream;
45import java.io.ByteArrayOutputStream;
46import java.io.DataInputStream;
47import java.io.DataOutputStream;
48
49public class MoviePlayer implements
Chih-Chung Chang209a9162011-10-14 16:09:09 +080050 MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener,
51 ControllerOverlay.Listener {
Owen Linf9a0a432011-08-17 22:07:43 +080052 @SuppressWarnings("unused")
53 private static final String TAG = "MoviePlayer";
54
Owen Lin540e4692011-09-14 19:49:03 +080055 private static final String KEY_VIDEO_POSITION = "video-position";
56 private static final String KEY_RESUMEABLE_TIME = "resumeable-timeout";
57
Owen Linf9a0a432011-08-17 22:07:43 +080058 // Copied from MediaPlaybackService in the Music Player app.
59 private static final String SERVICECMD = "com.android.music.musicservicecommand";
60 private static final String CMDNAME = "command";
61 private static final String CMDPAUSE = "pause";
62
Owen Lin00bb8e42012-05-23 15:47:36 -070063 private static final long BLACK_TIMEOUT = 500;
64
Owen Lin540e4692011-09-14 19:49:03 +080065 // If we resume the acitivty with in RESUMEABLE_TIMEOUT, we will keep playing.
66 // Otherwise, we pause the player.
67 private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins
68
Owen Linf9a0a432011-08-17 22:07:43 +080069 private Context mContext;
70 private final VideoView mVideoView;
Owen Lin00bb8e42012-05-23 15:47:36 -070071 private final View mRootView;
Owen Linf9a0a432011-08-17 22:07:43 +080072 private final Bookmarker mBookmarker;
73 private final Uri mUri;
74 private final Handler mHandler = new Handler();
75 private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver;
Owen Lin00bb8e42012-05-23 15:47:36 -070076 private final MovieControllerOverlay mController;
Owen Linf9a0a432011-08-17 22:07:43 +080077
Owen Lin540e4692011-09-14 19:49:03 +080078 private long mResumeableTime = Long.MAX_VALUE;
79 private int mVideoPosition = 0;
80 private boolean mHasPaused = false;
Ray Chen24525c22012-05-22 17:34:29 +080081 private int mLastSystemUiVis = 0;
Owen Linf9a0a432011-08-17 22:07:43 +080082
Chih-Chung Chang209a9162011-10-14 16:09:09 +080083 // If the time bar is being dragged.
84 private boolean mDragging;
85
86 // If the time bar is visible.
87 private boolean mShowing;
88
Ray Chen392a2922012-05-14 17:19:56 +080089 // Control when system UI can be shown
90 private boolean mAllowShowingSystemUI;
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() {
104 @Override
105 public void run() {
106 mRootView.setBackground(null);
107 }
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
Chih-Chung Chang81760122011-09-27 16:50:16 +0800154 // When the user touches the screen or uses some hard key, the framework
155 // will change system ui visibility from invisible to visible. We show
Ray Chen392a2922012-05-14 17:19:56 +0800156 // the media control and enable system UI (e.g. ActionBar) to be visible at this point
Chih-Chung Chang81760122011-09-27 16:50:16 +0800157 mVideoView.setOnSystemUiVisibilityChangeListener(
158 new View.OnSystemUiVisibilityChangeListener() {
Owen Lin00bb8e42012-05-23 15:47:36 -0700159 @Override
Chih-Chung Chang81760122011-09-27 16:50:16 +0800160 public void onSystemUiVisibilityChange(int visibility) {
Ray Chen24525c22012-05-22 17:34:29 +0800161 int diff = mLastSystemUiVis ^ visibility;
162 mLastSystemUiVis = visibility;
163 if ((diff & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
164 && (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
Ray Chen392a2922012-05-14 17:19:56 +0800165 mAllowShowingSystemUI = true;
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800166 mController.show();
Owen Lin00bb8e42012-05-23 15:47:36 -0700167
168 // We need to set the background to clear ghosting images
169 // when ActionBar slides in. However, if we keep the background,
170 // there will be one additional layer in HW composer, which is bad
171 // to battery. As a solution, we remove the background when we
172 // hide the action bar
173 mHandler.removeCallbacks(mRemoveBackground);
174 mRootView.setBackgroundColor(Color.BLACK);
175 } else {
176 mHandler.removeCallbacks(mRemoveBackground);
177
178 // Wait for the slide out animation, one second should be enough
179 mHandler.postDelayed(mRemoveBackground, 1000);
Chih-Chung Chang81760122011-09-27 16:50:16 +0800180 }
181 }
182 });
183
Ray Chen392a2922012-05-14 17:19:56 +0800184 // Hide system UI by default
185 showSystemUi(false);
186
Owen Linf9a0a432011-08-17 22:07:43 +0800187 mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
188 mAudioBecomingNoisyReceiver.register();
189
Owen Linf9a0a432011-08-17 22:07:43 +0800190 Intent i = new Intent(SERVICECMD);
191 i.putExtra(CMDNAME, CMDPAUSE);
192 movieActivity.sendBroadcast(i);
193
Owen Lin540e4692011-09-14 19:49:03 +0800194 if (savedInstance != null) { // this is a resumed activity
195 mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0);
196 mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE);
Owen Linf9a0a432011-08-17 22:07:43 +0800197 mVideoView.start();
Owen Lin540e4692011-09-14 19:49:03 +0800198 mVideoView.suspend();
199 mHasPaused = true;
200 } else {
201 final Integer bookmark = mBookmarker.getBookmark(mUri);
202 if (bookmark != null) {
203 showResumeDialog(movieActivity, bookmark);
204 } else {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800205 startVideo();
Owen Lin540e4692011-09-14 19:49:03 +0800206 }
Owen Linf9a0a432011-08-17 22:07:43 +0800207 }
208 }
209
Chih-Chung Chang81760122011-09-27 16:50:16 +0800210 private void showSystemUi(boolean visible) {
Ray Chen24525c22012-05-22 17:34:29 +0800211 int flag = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
212 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
213 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
214 if (!visible) {
215 flag |= View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN
216 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
Ray Chen392a2922012-05-14 17:19:56 +0800217 }
Ray Chen24525c22012-05-22 17:34:29 +0800218 mVideoView.setSystemUiVisibility(flag);
Chih-Chung Chang81760122011-09-27 16:50:16 +0800219 }
220
Owen Lin540e4692011-09-14 19:49:03 +0800221 public void onSaveInstanceState(Bundle outState) {
222 outState.putInt(KEY_VIDEO_POSITION, mVideoPosition);
223 outState.putLong(KEY_RESUMEABLE_TIME, mResumeableTime);
224 }
225
Owen Linf9a0a432011-08-17 22:07:43 +0800226 private void showResumeDialog(Context context, final int bookmark) {
227 AlertDialog.Builder builder = new AlertDialog.Builder(context);
228 builder.setTitle(R.string.resume_playing_title);
229 builder.setMessage(String.format(
230 context.getString(R.string.resume_playing_message),
231 GalleryUtils.formatDuration(context, bookmark / 1000)));
232 builder.setOnCancelListener(new OnCancelListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800233 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800234 public void onCancel(DialogInterface dialog) {
235 onCompletion();
236 }
237 });
238 builder.setPositiveButton(
239 R.string.resume_playing_resume, new OnClickListener() {
Owen Lin540e4692011-09-14 19:49:03 +0800240 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800241 public void onClick(DialogInterface dialog, int which) {
242 mVideoView.seekTo(bookmark);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800243 startVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800244 }
245 });
246 builder.setNegativeButton(
247 R.string.resume_playing_restart, 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) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800250 startVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800251 }
252 });
253 builder.show();
254 }
255
256 public void onPause() {
Owen Linf9a0a432011-08-17 22:07:43 +0800257 mHasPaused = true;
Owen Lin540e4692011-09-14 19:49:03 +0800258 mHandler.removeCallbacksAndMessages(null);
259 mVideoPosition = mVideoView.getCurrentPosition();
260 mBookmarker.setBookmark(mUri, mVideoPosition, mVideoView.getDuration());
261 mVideoView.suspend();
262 mResumeableTime = System.currentTimeMillis() + RESUMEABLE_TIMEOUT;
Owen Linf9a0a432011-08-17 22:07:43 +0800263 }
264
265 public void onResume() {
266 if (mHasPaused) {
Owen Lin540e4692011-09-14 19:49:03 +0800267 mVideoView.seekTo(mVideoPosition);
268 mVideoView.resume();
269
270 // If we have slept for too long, pause the play
271 if (System.currentTimeMillis() > mResumeableTime) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800272 pauseVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800273 }
274 }
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800275 mHandler.post(mProgressChecker);
Owen Linf9a0a432011-08-17 22:07:43 +0800276 }
277
278 public void onDestroy() {
279 mVideoView.stopPlayback();
280 mAudioBecomingNoisyReceiver.unregister();
281 }
282
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800283 // This updates the time bar display (if necessary). It is called every
284 // second by mProgressChecker and also from places where the time bar needs
285 // to be updated immediately.
286 private int setProgress() {
287 if (mDragging || !mShowing) {
288 return 0;
289 }
290 int position = mVideoView.getCurrentPosition();
291 int duration = mVideoView.getDuration();
292 mController.setTimes(position, duration);
293 return position;
294 }
295
296 private void startVideo() {
297 // For streams that we expect to be slow to start up, show a
298 // progress spinner until playback starts.
299 String scheme = mUri.getScheme();
300 if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) {
301 mController.showLoading();
302 mHandler.removeCallbacks(mPlayingChecker);
303 mHandler.postDelayed(mPlayingChecker, 250);
304 } else {
305 mController.showPlaying();
Owen Lin00bb8e42012-05-23 15:47:36 -0700306 mController.hide();
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800307 }
308
309 mVideoView.start();
310 setProgress();
311 }
312
313 private void playVideo() {
314 mVideoView.start();
315 mController.showPlaying();
316 setProgress();
317 }
318
319 private void pauseVideo() {
320 mVideoView.pause();
321 mController.showPaused();
322 }
323
324 // Below are notifications from VideoView
325 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800326 public boolean onError(MediaPlayer player, int arg1, int arg2) {
327 mHandler.removeCallbacksAndMessages(null);
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800328 // VideoView will show an error dialog if we return false, so no need
329 // to show more message.
330 mController.showErrorMessage("");
Owen Linf9a0a432011-08-17 22:07:43 +0800331 return false;
332 }
333
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800334 @Override
Owen Linf9a0a432011-08-17 22:07:43 +0800335 public void onCompletion(MediaPlayer mp) {
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800336 mController.showEnded();
Owen Linf9a0a432011-08-17 22:07:43 +0800337 onCompletion();
338 }
339
340 public void onCompletion() {
341 }
342
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800343 // Below are notifications from ControllerOverlay
344 @Override
345 public void onPlayPause() {
346 if (mVideoView.isPlaying()) {
347 pauseVideo();
348 } else {
349 playVideo();
350 }
351 }
352
353 @Override
354 public void onSeekStart() {
355 mDragging = true;
356 }
357
358 @Override
359 public void onSeekMove(int time) {
360 mVideoView.seekTo(time);
361 }
362
363 @Override
364 public void onSeekEnd(int time) {
365 mDragging = false;
366 mVideoView.seekTo(time);
367 setProgress();
368 }
369
370 @Override
371 public void onShown() {
372 mShowing = true;
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800373 setProgress();
Ray Chen392a2922012-05-14 17:19:56 +0800374
375 // System UI is invisible by default until the flag is set by user interaction
376 // See VideoView's onSystemUiVisibilityChange listener for details.
377 if (mAllowShowingSystemUI) {
378 showSystemUi(true);
379 }
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800380 }
381
382 @Override
383 public void onHidden() {
384 mShowing = false;
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800385 showSystemUi(false);
386 }
387
388 @Override
389 public void onReplay() {
390 startVideo();
391 }
392
Chih-Chung Changfc8c5032011-11-29 14:21:11 +0800393 // Below are key events passed from MovieActivity.
394 public boolean onKeyDown(int keyCode, KeyEvent event) {
395
396 // Some headsets will fire off 7-10 events on a single click
397 if (event.getRepeatCount() > 0) {
398 return isMediaKey(keyCode);
399 }
400
401 switch (keyCode) {
402 case KeyEvent.KEYCODE_HEADSETHOOK:
403 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
404 if (mVideoView.isPlaying()) {
405 pauseVideo();
406 } else {
407 playVideo();
408 }
409 return true;
410 case KeyEvent.KEYCODE_MEDIA_PAUSE:
411 if (mVideoView.isPlaying()) {
412 pauseVideo();
413 }
414 return true;
415 case KeyEvent.KEYCODE_MEDIA_PLAY:
416 if (!mVideoView.isPlaying()) {
417 playVideo();
418 }
419 return true;
420 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
421 case KeyEvent.KEYCODE_MEDIA_NEXT:
422 // TODO: Handle next / previous accordingly, for now we're
423 // just consuming the events.
424 return true;
425 }
426 return false;
427 }
428
429 public boolean onKeyUp(int keyCode, KeyEvent event) {
430 return isMediaKey(keyCode);
431 }
432
433 private static boolean isMediaKey(int keyCode) {
434 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK
435 || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS
436 || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT
437 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
438 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY
439 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE;
440 }
441
Chih-Chung Chang209a9162011-10-14 16:09:09 +0800442 // We want to pause when the headset is unplugged.
Owen Linf9a0a432011-08-17 22:07:43 +0800443 private class AudioBecomingNoisyReceiver extends BroadcastReceiver {
444
445 public void register() {
446 mContext.registerReceiver(this,
447 new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
448 }
449
450 public void unregister() {
451 mContext.unregisterReceiver(this);
452 }
453
454 @Override
455 public void onReceive(Context context, Intent intent) {
Owen Linaea077a2011-11-11 12:01:09 +0800456 if (mVideoView.isPlaying()) pauseVideo();
Owen Linf9a0a432011-08-17 22:07:43 +0800457 }
458 }
459}
460
461class Bookmarker {
462 private static final String TAG = "Bookmarker";
463
464 private static final String BOOKMARK_CACHE_FILE = "bookmark";
465 private static final int BOOKMARK_CACHE_MAX_ENTRIES = 100;
466 private static final int BOOKMARK_CACHE_MAX_BYTES = 10 * 1024;
467 private static final int BOOKMARK_CACHE_VERSION = 1;
468
469 private static final int HALF_MINUTE = 30 * 1000;
470 private static final int TWO_MINUTES = 4 * HALF_MINUTE;
471
472 private final Context mContext;
473
474 public Bookmarker(Context context) {
475 mContext = context;
476 }
477
478 public void setBookmark(Uri uri, int bookmark, int duration) {
479 try {
480 BlobCache cache = CacheManager.getCache(mContext,
481 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
482 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
483
484 ByteArrayOutputStream bos = new ByteArrayOutputStream();
485 DataOutputStream dos = new DataOutputStream(bos);
486 dos.writeUTF(uri.toString());
487 dos.writeInt(bookmark);
488 dos.writeInt(duration);
489 dos.flush();
490 cache.insert(uri.hashCode(), bos.toByteArray());
491 } catch (Throwable t) {
492 Log.w(TAG, "setBookmark failed", t);
493 }
494 }
495
496 public Integer getBookmark(Uri uri) {
497 try {
498 BlobCache cache = CacheManager.getCache(mContext,
499 BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
500 BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
501
502 byte[] data = cache.lookup(uri.hashCode());
503 if (data == null) return null;
504
505 DataInputStream dis = new DataInputStream(
506 new ByteArrayInputStream(data));
507
508 String uriString = dis.readUTF(dis);
509 int bookmark = dis.readInt();
510 int duration = dis.readInt();
511
512 if (!uriString.equals(uri.toString())) {
513 return null;
514 }
515
516 if ((bookmark < HALF_MINUTE) || (duration < TWO_MINUTES)
517 || (bookmark > (duration - HALF_MINUTE))) {
518 return null;
519 }
520 return Integer.valueOf(bookmark);
521 } catch (Throwable t) {
522 Log.w(TAG, "getBookmark failed", t);
523 }
524 return null;
525 }
526}