MediaSync: add OnErrorListener.

Bug: 19666434
Change-Id: I34983d6101cc0f93ee74bdcec5d272808842a5c2
diff --git a/api/current.txt b/api/current.txt
index fee81f5c..1e0138c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16392,10 +16392,13 @@
     method public final void release();
     method public void setAudioTrack(android.media.AudioTrack);
     method public void setCallback(android.media.MediaSync.Callback, android.os.Handler);
+    method public void setOnErrorListener(android.media.MediaSync.OnErrorListener, android.os.Handler);
     method public void setPlaybackRate(float, int);
     method public void setPlaybackSettings(android.media.PlaybackSettings);
     method public void setSurface(android.view.Surface);
     method public void setSyncSettings(android.media.SyncSettings);
+    field public static final int MEDIASYNC_ERROR_AUDIOTRACK_FAIL = 1; // 0x1
+    field public static final int MEDIASYNC_ERROR_SURFACE_FAIL = 2; // 0x2
     field public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0; // 0x0
     field public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2; // 0x2
     field public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1; // 0x1
@@ -16406,6 +16409,10 @@
     method public abstract void onReturnAudioBuffer(android.media.MediaSync, java.nio.ByteBuffer, int);
   }
 
+  public static abstract interface MediaSync.OnErrorListener {
+    method public abstract void onError(android.media.MediaSync, int, int);
+  }
+
   public class MediaSyncEvent {
     method public static android.media.MediaSyncEvent createEvent(int) throws java.lang.IllegalArgumentException;
     method public int getAudioSessionId();
@@ -16429,6 +16436,14 @@
     method public abstract void onAudioDeviceConnection();
   }
 
+  public abstract interface OnAudioRecordRoutingListener {
+    method public abstract void onAudioRecordRouting(android.media.AudioRecord);
+  }
+
+  public abstract interface OnAudioTrackRoutingListener {
+    method public abstract void onAudioTrackRouting(android.media.AudioTrack);
+  }
+
   public final class PlaybackSettings {
     ctor public PlaybackSettings();
     method public android.media.PlaybackSettings allowDefaults();
@@ -16447,14 +16462,6 @@
     field public static final int AUDIO_STRETCH_MODE_VOICE = 1; // 0x1
   }
 
-  public abstract interface OnAudioRecordRoutingListener {
-    method public abstract void onAudioRecordRouting(android.media.AudioRecord);
-  }
-
-  public abstract interface OnAudioTrackRoutingListener {
-    method public abstract void onAudioTrackRouting(android.media.AudioTrack);
-  }
-
   public final class Rating implements android.os.Parcelable {
     method public int describeContents();
     method public float getPercentRating();
diff --git a/api/system-current.txt b/api/system-current.txt
index 137bb19..7911fd0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -17608,10 +17608,13 @@
     method public final void release();
     method public void setAudioTrack(android.media.AudioTrack);
     method public void setCallback(android.media.MediaSync.Callback, android.os.Handler);
+    method public void setOnErrorListener(android.media.MediaSync.OnErrorListener, android.os.Handler);
     method public void setPlaybackRate(float, int);
     method public void setPlaybackSettings(android.media.PlaybackSettings);
     method public void setSurface(android.view.Surface);
     method public void setSyncSettings(android.media.SyncSettings);
+    field public static final int MEDIASYNC_ERROR_AUDIOTRACK_FAIL = 1; // 0x1
+    field public static final int MEDIASYNC_ERROR_SURFACE_FAIL = 2; // 0x2
     field public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0; // 0x0
     field public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2; // 0x2
     field public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1; // 0x1
@@ -17622,6 +17625,10 @@
     method public abstract void onReturnAudioBuffer(android.media.MediaSync, java.nio.ByteBuffer, int);
   }
 
+  public static abstract interface MediaSync.OnErrorListener {
+    method public abstract void onError(android.media.MediaSync, int, int);
+  }
+
   public class MediaSyncEvent {
     method public static android.media.MediaSyncEvent createEvent(int) throws java.lang.IllegalArgumentException;
     method public int getAudioSessionId();
@@ -17645,6 +17652,14 @@
     method public abstract void onAudioDeviceConnection();
   }
 
+  public abstract interface OnAudioRecordRoutingListener {
+    method public abstract void onAudioRecordRouting(android.media.AudioRecord);
+  }
+
+  public abstract interface OnAudioTrackRoutingListener {
+    method public abstract void onAudioTrackRouting(android.media.AudioTrack);
+  }
+
   public final class PlaybackSettings {
     ctor public PlaybackSettings();
     method public android.media.PlaybackSettings allowDefaults();
@@ -17663,14 +17678,6 @@
     field public static final int AUDIO_STRETCH_MODE_VOICE = 1; // 0x1
   }
 
-  public abstract interface OnAudioRecordRoutingListener {
-    method public abstract void onAudioRecordRouting(android.media.AudioRecord);
-  }
-
-  public abstract interface OnAudioTrackRoutingListener {
-    method public abstract void onAudioTrackRouting(android.media.AudioTrack);
-  }
-
   public final class Rating implements android.os.Parcelable {
     method public int describeContents();
     method public float getPercentRating();
diff --git a/media/java/android/media/MediaSync.java b/media/java/android/media/MediaSync.java
index 3b4f8e5..55c65f6 100644
--- a/media/java/android/media/MediaSync.java
+++ b/media/java/android/media/MediaSync.java
@@ -129,6 +129,37 @@
                 @NonNull MediaSync sync, @NonNull ByteBuffer audioBuffer, int bufferIndex);
     }
 
+    /** Audio track failed.
+     * @see android.media.MediaSync.OnErrorListener
+     */
+    public static final int MEDIASYNC_ERROR_AUDIOTRACK_FAIL = 1;
+
+    /** The surface failed to handle video buffers.
+     * @see android.media.MediaSync.OnErrorListener
+     */
+    public static final int MEDIASYNC_ERROR_SURFACE_FAIL = 2;
+
+    /**
+     * Interface definition of a callback to be invoked when there
+     * has been an error during an asynchronous operation (other errors
+     * will throw exceptions at method call time).
+     */
+    public interface OnErrorListener {
+        /**
+         * Called to indicate an error.
+         *
+         * @param sync The MediaSync the error pertains to
+         * @param what The type of error that has occurred:
+         * <ul>
+         * <li>{@link #MEDIASYNC_ERROR_AUDIOTRACK_FAIL}
+         * <li>{@link #MEDIASYNC_ERROR_SURFACE_FAIL}
+         * </ul>
+         * @param extra an extra code, specific to the error. Typically
+         * implementation dependent.
+         */
+        void onError(@NonNull MediaSync sync, int what, int extra);
+    }
+
     private static final String TAG = "MediaSync";
 
     private static final int EVENT_CALLBACK = 1;
@@ -155,6 +186,10 @@
     private Handler mCallbackHandler = null;
     private MediaSync.Callback mCallback = null;
 
+    private final Object mOnErrorListenerLock = new Object();
+    private Handler mOnErrorListenerHandler = null;
+    private MediaSync.OnErrorListener mOnErrorListener = null;
+
     private Thread mAudioThread = null;
     // Created on mAudioThread when mAudioThread is started. When used on user thread, they should
     // be guarded by checking mAudioThread.
@@ -235,6 +270,39 @@
     }
 
     /**
+     * Sets an asynchronous callback for error events.
+     * <p>
+     * This method can be called multiple times to update a previously set listener. If the
+     * handler is changed, undelivered notifications scheduled for the old handler may be dropped.
+     * <p>
+     * <b>Do not call this inside callback.</b>
+     *
+     * @param listener The callback that will run. Use {@code null} to stop receiving callbacks.
+     * @param handler The Handler that will run the callback. Use {@code null} to use MediaSync's
+     *     internal handler if it exists.
+     */
+    public void setOnErrorListener(@Nullable /* MediaSync. */ OnErrorListener listener,
+            @Nullable Handler handler) {
+        synchronized(mOnErrorListenerLock) {
+            if (handler != null) {
+                mOnErrorListenerHandler = handler;
+            } else {
+                Looper looper;
+                if ((looper = Looper.myLooper()) == null) {
+                    looper = Looper.getMainLooper();
+                }
+                if (looper == null) {
+                    mOnErrorListenerHandler = null;
+                } else {
+                    mOnErrorListenerHandler = new Handler(looper);
+                }
+            }
+
+            mOnErrorListener = listener;
+        }
+    }
+
+    /**
      * Sets the output surface for MediaSync.
      * <p>
      * Currently, this is only supported in the Initialized state.