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