Merge "Add the HTML5Audio class, to support the audio tag. The corresponding webkit C++ CL is https://android-git.corp.google.com/g/#change,41405"
diff --git a/core/java/android/webkit/HTML5Audio.java b/core/java/android/webkit/HTML5Audio.java
new file mode 100644
index 0000000..d292881
--- /dev/null
+++ b/core/java/android/webkit/HTML5Audio.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnBufferingUpdateListener;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.media.MediaPlayer.OnSeekCompleteListener;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * <p>HTML5 support class for Audio.
+ */
+class HTML5Audio extends Handler
+                 implements MediaPlayer.OnBufferingUpdateListener,
+                            MediaPlayer.OnCompletionListener,
+                            MediaPlayer.OnErrorListener,
+                            MediaPlayer.OnPreparedListener,
+                            MediaPlayer.OnSeekCompleteListener {
+    // Logging tag.
+    private static final String LOGTAG = "HTML5Audio";
+
+    private MediaPlayer mMediaPlayer;
+
+    // The C++ MediaPlayerPrivateAndroid object.
+    private int mNativePointer;
+
+    private static int IDLE        =  0;
+    private static int INITIALIZED =  1;
+    private static int PREPARED    =  2;
+    private static int STARTED     =  4;
+    private static int COMPLETE    =  5;
+    private static int PAUSED      =  6;
+    private static int STOPPED     = -2;
+    private static int ERROR       = -1;
+
+    private int mState = IDLE;
+
+    private String mUrl;
+    private boolean mAskToPlay = false;
+
+    // Timer thread -> UI thread
+    private static final int TIMEUPDATE = 100;
+
+    // The spec says the timer should fire every 250 ms or less.
+    private static final int TIMEUPDATE_PERIOD = 250;  // ms
+    // The timer for timeupate events.
+    // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
+    private Timer mTimer;
+    private final class TimeupdateTask extends TimerTask {
+        public void run() {
+            HTML5Audio.this.obtainMessage(TIMEUPDATE).sendToTarget();
+        }
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case TIMEUPDATE: {
+                try {
+                    if (mState != ERROR && mMediaPlayer.isPlaying()) {
+                        int position = mMediaPlayer.getCurrentPosition();
+                        nativeOnTimeupdate(position, mNativePointer);
+                    }
+                } catch (IllegalStateException e) {
+                    mState = ERROR;
+                }
+            }
+        }
+    }
+
+    // event listeners for MediaPlayer
+    // Those are called from the same thread we created the MediaPlayer
+    // (i.e. the webviewcore thread here)
+
+    // MediaPlayer.OnBufferingUpdateListener
+    public void onBufferingUpdate(MediaPlayer mp, int percent) {
+        nativeOnBuffering(percent, mNativePointer);
+    }
+
+    // MediaPlayer.OnCompletionListener;
+    public void onCompletion(MediaPlayer mp) {
+        resetMediaPlayer();
+        mState = IDLE;
+        nativeOnEnded(mNativePointer);
+    }
+
+    // MediaPlayer.OnErrorListener
+    public boolean onError(MediaPlayer mp, int what, int extra) {
+        mState = ERROR;
+        resetMediaPlayer();
+        mState = IDLE;
+        return false;
+    }
+
+    // MediaPlayer.OnPreparedListener
+    public void onPrepared(MediaPlayer mp) {
+        mState = PREPARED;
+        if (mTimer != null) {
+            mTimer.schedule(new TimeupdateTask(),
+                            TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);
+        }
+        nativeOnPrepared(mp.getDuration(), 0, 0, mNativePointer);
+        if (mAskToPlay) {
+            mAskToPlay = false;
+            play();
+        }
+    }
+
+    // MediaPlayer.OnSeekCompleteListener
+    public void onSeekComplete(MediaPlayer mp) {
+        nativeOnTimeupdate(mp.getCurrentPosition(), mNativePointer);
+    }
+
+
+    /**
+     * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
+     */
+    public HTML5Audio(int nativePtr) {
+        // Save the native ptr
+        mNativePointer = nativePtr;
+        resetMediaPlayer();
+    }
+
+    private void resetMediaPlayer() {
+        if (mMediaPlayer == null) {
+            mMediaPlayer = new MediaPlayer();
+        } else {
+            mMediaPlayer.reset();
+        }
+        mMediaPlayer.setOnBufferingUpdateListener(this);
+        mMediaPlayer.setOnCompletionListener(this);
+        mMediaPlayer.setOnErrorListener(this);
+        mMediaPlayer.setOnPreparedListener(this);
+        mMediaPlayer.setOnSeekCompleteListener(this);
+
+        if (mTimer != null) {
+            mTimer.cancel();
+        }
+        mTimer = new Timer();
+        mState = IDLE;
+    }
+
+    private void setDataSource(String url) {
+        mUrl = url;
+        try {
+            if (mState != IDLE) {
+                resetMediaPlayer();
+            }
+            mMediaPlayer.setDataSource(url);
+            mState = INITIALIZED;
+            mMediaPlayer.prepareAsync();
+        } catch (IOException e) {
+            Log.e(LOGTAG, "couldn't load the resource: " + url + " exc: " + e);
+            resetMediaPlayer();
+        }
+    }
+
+    private void play() {
+        if ((mState == ERROR || mState == IDLE) && mUrl != null) {
+            resetMediaPlayer();
+            setDataSource(mUrl);
+            mAskToPlay = true;
+        }
+
+        if (mState >= PREPARED) {
+            mMediaPlayer.start();
+            mState = STARTED;
+        }
+    }
+
+    private void pause() {
+        if (mState == STARTED) {
+            if (mTimer != null) {
+                mTimer.purge();
+            }
+            mMediaPlayer.pause();
+            mState = PAUSED;
+        }
+    }
+
+    private void seek(int msec) {
+        if (mState >= PREPARED) {
+            mMediaPlayer.seekTo(msec);
+        }
+    }
+
+    private void teardown() {
+        mMediaPlayer.release();
+        mState = ERROR;
+        mNativePointer = 0;
+    }
+
+    private float getMaxTimeSeekable() {
+        return mMediaPlayer.getDuration() / 1000.0f;
+    }
+
+    private native void nativeOnBuffering(int percent, int nativePointer);
+    private native void nativeOnEnded(int nativePointer);
+    private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
+    private native void nativeOnTimeupdate(int position, int nativePointer);
+}