Merge "Unhide new AAC-ELD audio record type." into jb-dev
diff --git a/api/current.txt b/api/current.txt
index f6db5b1..2ddf096 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11076,15 +11076,20 @@
     method public boolean hasCacheReachedEndOfStream();
     method public int readSampleData(java.nio.ByteBuffer, int);
     method public final void release();
-    method public void seekTo(long);
+    method public void seekTo(long, int);
     method public void selectTrack(int);
     method public final void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException;
     method public final void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>);
     method public final void setDataSource(java.lang.String);
     method public final void setDataSource(java.io.FileDescriptor);
     method public final void setDataSource(java.io.FileDescriptor, long, long);
+    method public void unselectTrack(int);
     field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2
     field public static final int SAMPLE_FLAG_SYNC = 1; // 0x1
+    field public static final int SEEK_TO_CLOSEST = 3; // 0x3
+    field public static final int SEEK_TO_CLOSEST_SYNC = 2; // 0x2
+    field public static final int SEEK_TO_NEXT_SYNC = 1; // 0x1
+    field public static final int SEEK_TO_PREVIOUS_SYNC = 0; // 0x0
   }
 
   public class MediaMetadataRetriever {
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 7d41e64..018785b 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -5411,8 +5411,9 @@
 
             /**
              * The phone number's E164 representation. This value can be omitted in which
-             * case the provider will try to automatically infer it. If present, {@link #NUMBER}
-             * has to be set as well (it will be ignored otherwise).
+             * case the provider will try to automatically infer it.  (It'll be left null if the
+             * provider fails to infer.)
+             * If present, {@link #NUMBER} has to be set as well (it will be ignored otherwise).
              * <P>Type: TEXT</P>
              */
             public static final String NORMALIZED_NUMBER = DATA4;
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 05b3e64..4d6094d 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -2598,7 +2598,8 @@
      */
     @Override
     public boolean isPrivateBrowsingEnabled() {
-        return getSettings().isPrivateBrowsingEnabled();
+        WebSettingsClassic settings = getSettings();
+        return (settings != null) ? settings.isPrivateBrowsingEnabled() : false;
     }
 
     private void startPrivateBrowsing() {
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index a91b16e..98e45fb 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -457,7 +457,16 @@
         public void parse() {
             Editable editable = (Editable) mTextView.getText();
             // Iterate over the newly added text and schedule new SpellCheckSpans
-            final int start = editable.getSpanStart(mRange);
+            final int start;
+            if (mIsSentenceSpellCheckSupported) {
+                // TODO: Find the start position of the sentence.
+                // Set span with the context
+                start =  Math.max(
+                        0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
+            } else {
+                start = editable.getSpanStart(mRange);
+            }
+
             final int end = editable.getSpanEnd(mRange);
 
             int wordIteratorWindowEnd = Math.min(end, start + WORD_ITERATOR_INTERVAL);
@@ -512,9 +521,7 @@
                     return;
                 }
                 // TODO: Find the start position of the sentence.
-                // Set span with the context
-                final int spellCheckStart =  Math.max(
-                        0, Math.min(wordStart, regionEnd - MIN_SENTENCE_LENGTH));
+                final int spellCheckStart =  wordStart;
                 if (regionEnd <= spellCheckStart) {
                     return;
                 }
diff --git a/data/fonts/DroidNaskh-Regular-Shift.ttf b/data/fonts/DroidNaskh-Regular-Shift.ttf
index bb9c70c..de475ca 100644
--- a/data/fonts/DroidNaskh-Regular-Shift.ttf
+++ b/data/fonts/DroidNaskh-Regular-Shift.ttf
Binary files differ
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 41d5c32..012e095 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -47,7 +47,6 @@
 
     private final Context mContext;
     private long mVolumeKeyUpTime;
-    private int  mVolumeControlStream = -1;
     private final boolean mUseMasterVolume;
     private static String TAG = "AudioManager";
 
@@ -304,13 +303,6 @@
     public static final int FLAG_VIBRATE = 1 << 4;
 
     /**
-     * forces use of specified stream
-     * @hide
-     */
-    public static final int FLAG_FORCE_STREAM = 1 << 5;
-
-
-    /**
      * Ringer mode that will be silent and will not vibrate. (This overrides the
      * vibrate setting.)
      *
@@ -458,10 +450,6 @@
                                     : ADJUST_LOWER,
                             flags);
                 } else {
-                    if (mVolumeControlStream != -1) {
-                        stream = mVolumeControlStream;
-                        flags |= FLAG_FORCE_STREAM;
-                    }
                     adjustSuggestedStreamVolume(
                             keyCode == KeyEvent.KEYCODE_VOLUME_UP
                                     ? ADJUST_RAISE
@@ -500,10 +488,6 @@
                     }
                 } else {
                     int flags = FLAG_PLAY_SOUND;
-                    if (mVolumeControlStream != -1) {
-                        stream = mVolumeControlStream;
-                        flags |= FLAG_FORCE_STREAM;
-                    }
                     adjustSuggestedStreamVolume(
                             ADJUST_SAME,
                             stream,
@@ -943,7 +927,12 @@
      * @hide
      */
     public void forceVolumeControlStream(int streamType) {
-        mVolumeControlStream = streamType;
+        IAudioService service = getService();
+        try {
+            service.forceVolumeControlStream(streamType, mICallBack);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in forceVolumeControlStream", e);
+        }
     }
 
     /**
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 48d3712..dcf72cc 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -149,6 +149,7 @@
     private int mMode;
     // protects mRingerMode
     private final Object mSettingsLock = new Object();
+
     private boolean mMediaServerOk;
 
     private SoundPool mSoundPool;
@@ -343,6 +344,14 @@
     // Keyguard manager proxy
     private KeyguardManager mKeyguardManager;
 
+    // mVolumeControlStream is set by VolumePanel to temporarily force the stream type which volume
+    // is controlled by Vol keys.
+    private int  mVolumeControlStream = -1;
+    private final Object mForceControlStreamLock = new Object();
+    // VolumePanel is currently the only client of forceVolumeControlStream() and runs in system
+    // server process so in theory it is not necessary to monitor the client death.
+    // However it is good to be ready for future evolutions.
+    private ForceControlStreamClient mForceControlStreamClient = null;
 
     ///////////////////////////////////////////////////////////////////////////
     // Construction
@@ -538,8 +547,8 @@
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
 
         int streamType;
-        if ((flags & AudioManager.FLAG_FORCE_STREAM) != 0) {
-            streamType = suggestedStreamType;
+        if (mVolumeControlStream != -1) {
+            streamType = mVolumeControlStream;
         } else {
             streamType = getActiveStreamType(suggestedStreamType);
         }
@@ -682,6 +691,57 @@
         sendVolumeUpdate(streamType, oldIndex, index, flags);
     }
 
+    /** @see AudioManager#forceVolumeControlStream(int) */
+    public void forceVolumeControlStream(int streamType, IBinder cb) {
+        synchronized(mForceControlStreamLock) {
+            mVolumeControlStream = streamType;
+            if (mVolumeControlStream == -1) {
+                if (mForceControlStreamClient != null) {
+                    mForceControlStreamClient.release();
+                    mForceControlStreamClient = null;
+                }
+            } else {
+                mForceControlStreamClient = new ForceControlStreamClient(cb);
+            }
+        }
+    }
+
+    private class ForceControlStreamClient implements IBinder.DeathRecipient {
+        private IBinder mCb; // To be notified of client's death
+
+        ForceControlStreamClient(IBinder cb) {
+            if (cb != null) {
+                try {
+                    cb.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    // Client has died!
+                    Log.w(TAG, "ForceControlStreamClient() could not link to "+cb+" binder death");
+                    cb = null;
+                }
+            }
+            mCb = cb;
+        }
+
+        public void binderDied() {
+            synchronized(mForceControlStreamLock) {
+                Log.w(TAG, "SCO client died");
+                if (mForceControlStreamClient != this) {
+                    Log.w(TAG, "unregistered control stream client died");
+                } else {
+                    mForceControlStreamClient = null;
+                    mVolumeControlStream = -1;
+                }
+            }
+        }
+
+        public void release() {
+            if (mCb != null) {
+                mCb.unlinkToDeath(this, 0);
+                mCb = null;
+            }
+        }
+    }
+
     private int findVolumeDelta(int direction, int volume) {
         int delta = 0;
         if (direction == AudioManager.ADJUST_RAISE) {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index b775095..0311c59 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -115,4 +115,6 @@
     void startBluetoothSco(IBinder cb);
 
     void stopBluetoothSco(IBinder cb);
+
+    void forceVolumeControlStream(int streamType, IBinder cb);
 }
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 9fdb81f..5fe58a8 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -191,17 +191,33 @@
 
     /** Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and
      *  {@link #getSampleTime} only retrieve information for the subset of tracks
-     *  selected by the call below.
-     *  Selecting the same track multiple times has no effect, the track
+     *  selected.
+     *  Selecting the same track multiple times has no effect, the track is
      *  only selected once.
-     *  Media data will be returned in the order of their timestamps.
     */
     public native void selectTrack(int index);
 
-    /** All selected tracks seek near the requested time. The next sample
-     *  returned for each selected track will be a sync sample.
+    /** Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and
+     *  {@link #getSampleTime} only retrieve information for the subset of tracks
+     *  selected.
     */
-    public native void seekTo(long timeUs);
+    public native void unselectTrack(int index);
+
+    /** If possible, seek to a sync sample at or before the specified time */
+    public static final int SEEK_TO_PREVIOUS_SYNC       = 0;
+    /** If possible, seek to a sync sample at or after the specified time */
+    public static final int SEEK_TO_NEXT_SYNC           = 1;
+    /** If possible, seek to the sync sample closest to the specified time */
+    public static final int SEEK_TO_CLOSEST_SYNC        = 2;
+    /** If possible, seek to a sample closest to the specified time, which may
+      * NOT be a sync sample!
+      */
+    public static final int SEEK_TO_CLOSEST             = 3;
+
+    /** All selected tracks seek near the requested time according to the
+      * specified mode.
+      */
+    public native void seekTo(long timeUs, int mode);
 
     /** Advance to the next sample. Returns false if no more sample data
      *  is available (end of stream).
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 0518331..9e1920c 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -96,8 +96,13 @@
     return mImpl->selectTrack(index);
 }
 
-status_t JMediaExtractor::seekTo(int64_t timeUs) {
-    return mImpl->seekTo(timeUs);
+status_t JMediaExtractor::unselectTrack(size_t index) {
+    return mImpl->unselectTrack(index);
+}
+
+status_t JMediaExtractor::seekTo(
+        int64_t timeUs, MediaSource::ReadOptions::SeekMode mode) {
+    return mImpl->seekTo(timeUs, mode);
 }
 
 status_t JMediaExtractor::advance() {
@@ -281,8 +286,8 @@
     }
 }
 
-static void android_media_MediaExtractor_seekTo(
-        JNIEnv *env, jobject thiz, jlong timeUs) {
+static void android_media_MediaExtractor_unselectTrack(
+        JNIEnv *env, jobject thiz, jint index) {
     sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
 
     if (extractor == NULL) {
@@ -290,7 +295,30 @@
         return;
     }
 
-    extractor->seekTo(timeUs);
+    status_t err = extractor->unselectTrack(index);
+
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+}
+
+static void android_media_MediaExtractor_seekTo(
+        JNIEnv *env, jobject thiz, jlong timeUs, jint mode) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    if (mode < MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC
+            || mode > MediaSource::ReadOptions::SEEK_CLOSEST) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return;
+    }
+
+    extractor->seekTo(timeUs, (MediaSource::ReadOptions::SeekMode)mode);
 }
 
 static jboolean android_media_MediaExtractor_advance(
@@ -648,7 +676,10 @@
 
     { "selectTrack", "(I)V", (void *)android_media_MediaExtractor_selectTrack },
 
-    { "seekTo", "(J)V", (void *)android_media_MediaExtractor_seekTo },
+    { "unselectTrack", "(I)V",
+        (void *)android_media_MediaExtractor_unselectTrack },
+
+    { "seekTo", "(JI)V", (void *)android_media_MediaExtractor_seekTo },
 
     { "advance", "()Z", (void *)android_media_MediaExtractor_advance },
 
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
index ef0c48b..2d4627e 100644
--- a/media/jni/android_media_MediaExtractor.h
+++ b/media/jni/android_media_MediaExtractor.h
@@ -18,6 +18,7 @@
 #define _ANDROID_MEDIA_MEDIAEXTRACTOR_H_
 
 #include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/MediaSource.h>
 #include <utils/Errors.h>
 #include <utils/KeyedVector.h>
 #include <utils/RefBase.h>
@@ -43,8 +44,9 @@
     status_t getTrackFormat(size_t index, jobject *format) const;
 
     status_t selectTrack(size_t index);
+    status_t unselectTrack(size_t index);
 
-    status_t seekTo(int64_t timeUs);
+    status_t seekTo(int64_t timeUs, MediaSource::ReadOptions::SeekMode mode);
 
     status_t advance();
     status_t readSampleData(jobject byteBuf, size_t offset, size_t *sampleSize);
diff --git a/tests/WebViewTests/src/com/android/webviewtests/JavaBridgeBasicsTest.java b/tests/WebViewTests/src/com/android/webviewtests/JavaBridgeBasicsTest.java
index 07d7c34..1ecccf6 100644
--- a/tests/WebViewTests/src/com/android/webviewtests/JavaBridgeBasicsTest.java
+++ b/tests/WebViewTests/src/com/android/webviewtests/JavaBridgeBasicsTest.java
@@ -186,6 +186,13 @@
         assertRaisesException("testController.foo()");
     }
 
+    public void testUncaughtJavaExceptionRaisesJavaException() throws Throwable {
+        injectObjectAndReload(new Object() {
+            public void method() { throw new RuntimeException("foo"); }
+        }, "testObject");
+        assertRaisesException("testObject.method()");
+    }
+
     // Note that this requires that we can pass a JavaScript string to Java.
     public void testTypeOfStaticMethod() throws Throwable {
         injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
@@ -394,7 +401,6 @@
         assertEquals("", mTestController.waitForStringValue());
     }
 
-    // java.lang.reflect only allows access to public methods and fields. See b/6386557.
     public void testReflectPublicMethod() throws Throwable {
         injectObjectAndReload(new Object() {
             public String method() { return "foo"; }
@@ -404,7 +410,6 @@
                 ".toString()"));
     }
 
-    // java.lang.reflect only allows access to public methods and fields. See b/6386557.
     public void testReflectPublicField() throws Throwable {
         injectObjectAndReload(new Object() {
             public String field = "foo";
@@ -412,4 +417,26 @@
         assertEquals("foo", executeJavaScriptAndGetStringResult(
                 "testObject.getClass().getField('field').get(testObject).toString()"));
     }
+
+    public void testReflectPrivateMethodRaisesException() throws Throwable {
+        injectObjectAndReload(new Object() {
+            private void method() {};
+        }, "testObject");
+        assertRaisesException("testObject.getClass().getMethod('method', null)");
+        // getDeclaredMethod() is able to access a private method, but invoke()
+        // throws a Java exception.
+        assertRaisesException(
+                "testObject.getClass().getDeclaredMethod('method', null).invoke(testObject, null)");
+    }
+
+    public void testReflectPrivateFieldRaisesException() throws Throwable {
+        injectObjectAndReload(new Object() {
+            private int field;
+        }, "testObject");
+        assertRaisesException("testObject.getClass().getField('field')");
+        // getDeclaredField() is able to access a private field, but getInt()
+        // throws a Java exception.
+        assertRaisesException(
+                "testObject.getClass().getDeclaredField('field').getInt(testObject)");
+    }
 }