Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / ContentVideoView.java
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.content.browser;
6
7 import android.app.Activity;
8 import android.app.AlertDialog;
9 import android.content.Context;
10 import android.content.ContextWrapper;
11 import android.content.DialogInterface;
12 import android.graphics.Point;
13 import android.provider.Settings;
14 import android.util.Log;
15 import android.view.Display;
16 import android.view.Gravity;
17 import android.view.KeyEvent;
18 import android.view.Surface;
19 import android.view.SurfaceHolder;
20 import android.view.SurfaceView;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.view.WindowManager;
24 import android.widget.FrameLayout;
25 import android.widget.LinearLayout;
26 import android.widget.ProgressBar;
27 import android.widget.TextView;
28
29 import org.chromium.base.CalledByNative;
30 import org.chromium.base.JNINamespace;
31 import org.chromium.base.ThreadUtils;
32
33 /**
34  * This class implements accelerated fullscreen video playback using surface view.
35  */
36 @JNINamespace("content")
37 public class ContentVideoView extends FrameLayout
38         implements SurfaceHolder.Callback {
39
40     private static final String TAG = "ContentVideoView";
41
42     /* Do not change these values without updating their counterparts
43      * in include/media/mediaplayer.h!
44      */
45     private static final int MEDIA_NOP = 0; // interface test message
46     private static final int MEDIA_PREPARED = 1;
47     private static final int MEDIA_PLAYBACK_COMPLETE = 2;
48     private static final int MEDIA_BUFFERING_UPDATE = 3;
49     private static final int MEDIA_SEEK_COMPLETE = 4;
50     private static final int MEDIA_SET_VIDEO_SIZE = 5;
51     private static final int MEDIA_ERROR = 100;
52     private static final int MEDIA_INFO = 200;
53
54     /**
55      * Keep these error codes in sync with the code we defined in
56      * MediaPlayerListener.java.
57      */
58     public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 2;
59     public static final int MEDIA_ERROR_INVALID_CODE = 3;
60
61     // all possible internal states
62     private static final int STATE_ERROR              = -1;
63     private static final int STATE_IDLE               = 0;
64     private static final int STATE_PLAYING            = 1;
65     private static final int STATE_PAUSED             = 2;
66     private static final int STATE_PLAYBACK_COMPLETED = 3;
67
68     private SurfaceHolder mSurfaceHolder;
69     private int mVideoWidth;
70     private int mVideoHeight;
71     private int mDuration;
72
73     // Native pointer to C++ ContentVideoView object.
74     private long mNativeContentVideoView;
75
76     // webkit should have prepared the media
77     private int mCurrentState = STATE_IDLE;
78
79     // Strings for displaying media player errors
80     private String mPlaybackErrorText;
81     private String mUnknownErrorText;
82     private String mErrorButton;
83     private String mErrorTitle;
84     private String mVideoLoadingText;
85
86     // This view will contain the video.
87     private VideoSurfaceView mVideoSurfaceView;
88
89     // Progress view when the video is loading.
90     private View mProgressView;
91
92     private final ContentVideoViewClient mClient;
93
94     private boolean mInitialOrientation;
95     private boolean mPossibleAccidentalChange;
96     private boolean mUmaRecorded;
97     private long mOrientationChangedTime;
98     private long mPlaybackStartTime;
99
100     private class VideoSurfaceView extends SurfaceView {
101
102         public VideoSurfaceView(Context context) {
103             super(context);
104         }
105
106         @Override
107         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
108             // set the default surface view size to (1, 1) so that it won't block
109             // the infobar. (0, 0) is not a valid size for surface view.
110             int width = 1;
111             int height = 1;
112             if (mVideoWidth > 0 && mVideoHeight > 0) {
113                 width = getDefaultSize(mVideoWidth, widthMeasureSpec);
114                 height = getDefaultSize(mVideoHeight, heightMeasureSpec);
115                 if (mVideoWidth * height  > width * mVideoHeight) {
116                     height = width * mVideoHeight / mVideoWidth;
117                 } else if (mVideoWidth * height  < width * mVideoHeight) {
118                     width = height * mVideoWidth / mVideoHeight;
119                 }
120             }
121             if (mUmaRecorded) {
122                 // If we have never switched orientation, record the orientation
123                 // time.
124                 if (mPlaybackStartTime == mOrientationChangedTime) {
125                     if (isOrientationPortrait() != mInitialOrientation) {
126                         mOrientationChangedTime = System.currentTimeMillis();
127                     }
128                 } else {
129                     // if user quickly switched the orientation back and force, don't
130                     // count it in UMA.
131                     if (!mPossibleAccidentalChange
132                             && isOrientationPortrait() == mInitialOrientation
133                             && System.currentTimeMillis() - mOrientationChangedTime < 5000) {
134                         mPossibleAccidentalChange = true;
135                     }
136                 }
137             }
138             setMeasuredDimension(width, height);
139         }
140     }
141
142     private static class ProgressView extends LinearLayout {
143
144         private final ProgressBar mProgressBar;
145         private final TextView mTextView;
146
147         public ProgressView(Context context, String videoLoadingText) {
148             super(context);
149             setOrientation(LinearLayout.VERTICAL);
150             setLayoutParams(new LinearLayout.LayoutParams(
151                     LinearLayout.LayoutParams.WRAP_CONTENT,
152                     LinearLayout.LayoutParams.WRAP_CONTENT));
153             mProgressBar = new ProgressBar(context, null, android.R.attr.progressBarStyleLarge);
154             mTextView = new TextView(context);
155             mTextView.setText(videoLoadingText);
156             addView(mProgressBar);
157             addView(mTextView);
158         }
159     }
160
161     private final Runnable mExitFullscreenRunnable = new Runnable() {
162         @Override
163         public void run() {
164             exitFullscreen(true);
165         }
166     };
167
168     protected ContentVideoView(Context context, long nativeContentVideoView,
169             ContentVideoViewClient client) {
170         super(context);
171         mNativeContentVideoView = nativeContentVideoView;
172         mClient = client;
173         mUmaRecorded = false;
174         mPossibleAccidentalChange = false;
175         initResources(context);
176         mVideoSurfaceView = new VideoSurfaceView(context);
177         showContentVideoView();
178         setVisibility(View.VISIBLE);
179     }
180
181     protected ContentVideoViewClient getContentVideoViewClient() {
182         return mClient;
183     }
184
185     private void initResources(Context context) {
186         if (mPlaybackErrorText != null) return;
187         mPlaybackErrorText = context.getString(
188                 org.chromium.content.R.string.media_player_error_text_invalid_progressive_playback);
189         mUnknownErrorText = context.getString(
190                 org.chromium.content.R.string.media_player_error_text_unknown);
191         mErrorButton = context.getString(
192                 org.chromium.content.R.string.media_player_error_button);
193         mErrorTitle = context.getString(
194                 org.chromium.content.R.string.media_player_error_title);
195         mVideoLoadingText = context.getString(
196                 org.chromium.content.R.string.media_player_loading_video);
197     }
198
199     protected void showContentVideoView() {
200         mVideoSurfaceView.getHolder().addCallback(this);
201         this.addView(mVideoSurfaceView, new FrameLayout.LayoutParams(
202                 ViewGroup.LayoutParams.WRAP_CONTENT,
203                 ViewGroup.LayoutParams.WRAP_CONTENT,
204                 Gravity.CENTER));
205
206         mProgressView = mClient.getVideoLoadingProgressView();
207         if (mProgressView == null) {
208             mProgressView = new ProgressView(getContext(), mVideoLoadingText);
209         }
210         this.addView(mProgressView, new FrameLayout.LayoutParams(
211                 ViewGroup.LayoutParams.WRAP_CONTENT,
212                 ViewGroup.LayoutParams.WRAP_CONTENT,
213                 Gravity.CENTER));
214     }
215
216     protected SurfaceView getSurfaceView() {
217         return mVideoSurfaceView;
218     }
219
220     @CalledByNative
221     public void onMediaPlayerError(int errorType) {
222         Log.d(TAG, "OnMediaPlayerError: " + errorType);
223         if (mCurrentState == STATE_ERROR || mCurrentState == STATE_PLAYBACK_COMPLETED) {
224             return;
225         }
226
227         // Ignore some invalid error codes.
228         if (errorType == MEDIA_ERROR_INVALID_CODE) {
229             return;
230         }
231
232         mCurrentState = STATE_ERROR;
233
234         if (!isActivityContext(getContext())) {
235             Log.w(TAG, "Unable to show alert dialog because it requires an activity context");
236             return;
237         }
238
239         /* Pop up an error dialog so the user knows that
240          * something bad has happened. Only try and pop up the dialog
241          * if we're attached to a window. When we're going away and no
242          * longer have a window, don't bother showing the user an error.
243          *
244          * TODO(qinmin): We need to review whether this Dialog is OK with
245          * the rest of the browser UI elements.
246          */
247         if (getWindowToken() != null) {
248             String message;
249
250             if (errorType == MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
251                 message = mPlaybackErrorText;
252             } else {
253                 message = mUnknownErrorText;
254             }
255
256             try {
257                 new AlertDialog.Builder(getContext())
258                     .setTitle(mErrorTitle)
259                     .setMessage(message)
260                     .setPositiveButton(mErrorButton,
261                             new DialogInterface.OnClickListener() {
262                                 @Override
263                                 public void onClick(DialogInterface dialog, int whichButton) {
264                                     // Inform that the video is over.
265                                     onCompletion();
266                                 }
267                             })
268                     .setCancelable(false)
269                     .show();
270             } catch (RuntimeException e) {
271                 Log.e(TAG, "Cannot show the alert dialog, error message: " + message, e);
272             }
273         }
274     }
275
276     @CalledByNative
277     private void onVideoSizeChanged(int width, int height) {
278         mVideoWidth = width;
279         mVideoHeight = height;
280         // This will trigger the SurfaceView.onMeasure() call.
281         mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
282     }
283
284     @CalledByNative
285     protected void onBufferingUpdate(int percent) {
286     }
287
288     @CalledByNative
289     private void onPlaybackComplete() {
290         onCompletion();
291     }
292
293     @CalledByNative
294     protected void onUpdateMediaMetadata(
295             int videoWidth,
296             int videoHeight,
297             int duration,
298             boolean canPause,
299             boolean canSeekBack,
300             boolean canSeekForward) {
301         mDuration = duration;
302         mProgressView.setVisibility(View.GONE);
303         mCurrentState = isPlaying() ? STATE_PLAYING : STATE_PAUSED;
304         onVideoSizeChanged(videoWidth, videoHeight);
305         if (mUmaRecorded) return;
306         try {
307             if (Settings.System.getInt(getContext().getContentResolver(),
308                     Settings.System.ACCELEROMETER_ROTATION) == 0) {
309                 return;
310             }
311         } catch (Settings.SettingNotFoundException e) {
312             return;
313         }
314         mInitialOrientation = isOrientationPortrait();
315         mUmaRecorded = true;
316         mPlaybackStartTime = System.currentTimeMillis();
317         mOrientationChangedTime = mPlaybackStartTime;
318         nativeRecordFullscreenPlayback(
319                 mNativeContentVideoView, videoHeight > videoWidth, mInitialOrientation);
320     }
321
322     @Override
323     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
324     }
325
326     @Override
327     public void surfaceCreated(SurfaceHolder holder) {
328         mSurfaceHolder = holder;
329         openVideo();
330     }
331
332     @Override
333     public void surfaceDestroyed(SurfaceHolder holder) {
334         if (mNativeContentVideoView != 0) {
335             nativeSetSurface(mNativeContentVideoView, null);
336         }
337         mSurfaceHolder = null;
338         post(mExitFullscreenRunnable);
339     }
340
341     @CalledByNative
342     protected void openVideo() {
343         if (mSurfaceHolder != null) {
344             mCurrentState = STATE_IDLE;
345             if (mNativeContentVideoView != 0) {
346                 nativeRequestMediaMetadata(mNativeContentVideoView);
347                 nativeSetSurface(mNativeContentVideoView,
348                         mSurfaceHolder.getSurface());
349             }
350         }
351     }
352
353     protected void onCompletion() {
354         mCurrentState = STATE_PLAYBACK_COMPLETED;
355     }
356
357
358     protected boolean isInPlaybackState() {
359         return (mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE);
360     }
361
362     protected void start() {
363         if (isInPlaybackState()) {
364             if (mNativeContentVideoView != 0) {
365                 nativePlay(mNativeContentVideoView);
366             }
367             mCurrentState = STATE_PLAYING;
368         }
369     }
370
371     protected void pause() {
372         if (isInPlaybackState()) {
373             if (isPlaying()) {
374                 if (mNativeContentVideoView != 0) {
375                     nativePause(mNativeContentVideoView);
376                 }
377                 mCurrentState = STATE_PAUSED;
378             }
379         }
380     }
381
382     // cache duration as mDuration for faster access
383     protected int getDuration() {
384         if (isInPlaybackState()) {
385             if (mDuration > 0) {
386                 return mDuration;
387             }
388             if (mNativeContentVideoView != 0) {
389                 mDuration = nativeGetDurationInMilliSeconds(mNativeContentVideoView);
390             } else {
391                 mDuration = 0;
392             }
393             return mDuration;
394         }
395         mDuration = -1;
396         return mDuration;
397     }
398
399     protected int getCurrentPosition() {
400         if (isInPlaybackState() && mNativeContentVideoView != 0) {
401             return nativeGetCurrentPosition(mNativeContentVideoView);
402         }
403         return 0;
404     }
405
406     protected void seekTo(int msec) {
407         if (mNativeContentVideoView != 0) {
408             nativeSeekTo(mNativeContentVideoView, msec);
409         }
410     }
411
412     public boolean isPlaying() {
413         return mNativeContentVideoView != 0 && nativeIsPlaying(mNativeContentVideoView);
414     }
415
416     @CalledByNative
417     private static ContentVideoView createContentVideoView(
418             ContentViewCore contentViewCore, long nativeContentVideoView) {
419         ThreadUtils.assertOnUiThread();
420         Context context = contentViewCore.getContext();
421         ContentVideoViewClient client = contentViewCore.getContentVideoViewClient();
422         ContentVideoView videoView = new ContentVideoView(context, nativeContentVideoView, client);
423         client.enterFullscreenVideo(videoView);
424         return videoView;
425     }
426
427     private static boolean isActivityContext(Context context) {
428         // Only retrieve the base context if the supplied context is a ContextWrapper but not
429         // an Activity, given that Activity is already a subclass of ContextWrapper.
430         if (context instanceof ContextWrapper && !(context instanceof Activity)) {
431             context = ((ContextWrapper) context).getBaseContext();
432             return isActivityContext(context);
433         }
434         return context instanceof Activity;
435     }
436
437     public void removeSurfaceView() {
438         removeView(mVideoSurfaceView);
439         removeView(mProgressView);
440         mVideoSurfaceView = null;
441         mProgressView = null;
442     }
443
444     public void exitFullscreen(boolean relaseMediaPlayer) {
445         destroyContentVideoView(false);
446         if (mNativeContentVideoView != 0) {
447             if (mUmaRecorded && !mPossibleAccidentalChange) {
448                 long currentTime = System.currentTimeMillis();
449                 long timeBeforeOrientationChange = mOrientationChangedTime - mPlaybackStartTime;
450                 long timeAfterOrientationChange = currentTime - mOrientationChangedTime;
451                 if (timeBeforeOrientationChange == 0) {
452                     timeBeforeOrientationChange = timeAfterOrientationChange;
453                     timeAfterOrientationChange = 0;
454                 }
455                 nativeRecordExitFullscreenPlayback(mNativeContentVideoView, mInitialOrientation,
456                         timeBeforeOrientationChange, timeAfterOrientationChange);
457             }
458             nativeExitFullscreen(mNativeContentVideoView, relaseMediaPlayer);
459             mNativeContentVideoView = 0;
460         }
461     }
462
463     @CalledByNative
464     private void onExitFullscreen() {
465         exitFullscreen(false);
466     }
467
468     /**
469      * This method shall only be called by native and exitFullscreen,
470      * To exit fullscreen, use exitFullscreen in Java.
471      */
472     @CalledByNative
473     protected void destroyContentVideoView(boolean nativeViewDestroyed) {
474         if (mVideoSurfaceView != null) {
475             removeSurfaceView();
476             setVisibility(View.GONE);
477
478             // To prevent re-entrance, call this after removeSurfaceView.
479             mClient.exitFullscreenVideo();
480         }
481         if (nativeViewDestroyed) {
482             mNativeContentVideoView = 0;
483         }
484     }
485
486     public static ContentVideoView getContentVideoView() {
487         return nativeGetSingletonJavaContentVideoView();
488     }
489
490     @Override
491     public boolean onKeyUp(int keyCode, KeyEvent event) {
492         if (keyCode == KeyEvent.KEYCODE_BACK) {
493             exitFullscreen(false);
494             return true;
495         }
496         return super.onKeyUp(keyCode, event);
497     }
498
499     private boolean isOrientationPortrait() {
500         Context context = getContext();
501         WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
502         Display display = manager.getDefaultDisplay();
503         Point outputSize = new Point(0, 0);
504         display.getSize(outputSize);
505         return outputSize.x <= outputSize.y;
506     }
507
508     private static native ContentVideoView nativeGetSingletonJavaContentVideoView();
509     private native void nativeExitFullscreen(long nativeContentVideoView,
510             boolean relaseMediaPlayer);
511     private native int nativeGetCurrentPosition(long nativeContentVideoView);
512     private native int nativeGetDurationInMilliSeconds(long nativeContentVideoView);
513     private native void nativeRequestMediaMetadata(long nativeContentVideoView);
514     private native int nativeGetVideoWidth(long nativeContentVideoView);
515     private native int nativeGetVideoHeight(long nativeContentVideoView);
516     private native boolean nativeIsPlaying(long nativeContentVideoView);
517     private native void nativePause(long nativeContentVideoView);
518     private native void nativePlay(long nativeContentVideoView);
519     private native void nativeSeekTo(long nativeContentVideoView, int msec);
520     private native void nativeSetSurface(long nativeContentVideoView, Surface surface);
521     private native void nativeRecordFullscreenPlayback(
522             long nativeContentVideoView, boolean isVideoPortrait, boolean isOrientationPortrait);
523     private native void nativeRecordExitFullscreenPlayback(
524             long nativeContentVideoView, boolean isOrientationPortrait,
525             long playbackDurationBeforeOrientationChange,
526             long playbackDurationAfterOrientationChange);
527 }