Bug 1055562 - Crash in java.lang.IllegalStateException: Callback has already been executed. r=wesj, a=lsblakk
authorRandall Barker <rbarker@mozilla.com>
Mon, 13 Oct 2014 13:48:00 +0200
changeset 225928 3c9ba9327aa9
parent 225927 fdb8b52bea5c
child 225929 38b0e08b93b7
push id4072
push userryanvm@gmail.com
push date2014-11-05 16:49 +0000
treeherdermozilla-beta@57c47cb49c03 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswesj, lsblakk
bugs1055562
milestone34.0
Bug 1055562 - Crash in java.lang.IllegalStateException: Callback has already been executed. r=wesj, a=lsblakk
mobile/android/base/ChromeCast.java
--- a/mobile/android/base/ChromeCast.java
+++ b/mobile/android/base/ChromeCast.java
@@ -43,16 +43,35 @@ class ChromeCast implements GeckoMediaPl
     private final RouteInfo route;
     private GoogleApiClient apiClient;
     private RemoteMediaPlayer remoteMediaPlayer;
     private boolean canMirror;
     private String mSessionId;
     private MirrorChannel mMirrorChannel;
     private boolean mApplicationStarted = false;
 
+    // EventCallback which is actually a GeckoEventCallback is sometimes being invoked more
+    // than once. That causes the IllegalStateException to be thrown. To prevent a crash,
+    // catch the exception and report it as an error to the log.
+    private static void sendSuccess(final EventCallback callback, final String msg) {
+        try {
+            callback.sendSuccess(msg);
+        } catch (final IllegalStateException e) {
+            Log.e(LOGTAG, "Attempting to invoke callback.sendSuccess more than once.", e);
+        }
+    }
+
+    private static void sendError(final EventCallback callback, final String msg) {
+        try {
+            callback.sendError(msg);
+        } catch (final IllegalStateException e) {
+            Log.e(LOGTAG, "Attempting to invoke callback.sendError more than once.", e);
+        }
+    }
+
     // Callback to start playback of a url on a remote device
     private class VideoPlayCallback implements ResultCallback<ApplicationConnectionResult>,
                                                RemoteMediaPlayer.OnStatusUpdatedListener,
                                                RemoteMediaPlayer.OnMetadataUpdatedListener {
         private final String url;
         private final String type;
         private final String title;
         private final EventCallback callback;
@@ -96,51 +115,51 @@ class ChromeCast implements GeckoMediaPl
                 try {
                     Cast.CastApi.setMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace(), remoteMediaPlayer);
                 } catch (IOException e) {
                     debug("Exception while creating media channel", e);
                 }
 
                 startPlayback();
             } else {
-                callback.sendError(status.toString());
+                sendError(callback, status.toString());
             }
         }
 
         private void startPlayback() {
             MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
             mediaMetadata.putString(MediaMetadata.KEY_TITLE, title);
             MediaInfo mediaInfo = new MediaInfo.Builder(url)
                                                .setContentType(type)
                                                .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
                                                .setMetadata(mediaMetadata)
                                                .build();
             try {
                 remoteMediaPlayer.load(apiClient, mediaInfo, true).setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
                     @Override
                     public void onResult(MediaChannelResult result) {
                         if (result.getStatus().isSuccess()) {
-                            callback.sendSuccess(null);
+                            sendSuccess(callback, null);
                             debug("Media loaded successfully");
                             return;
                         }
 
                         debug("Media load failed " + result.getStatus());
-                        callback.sendError(result.getStatus().toString());
+                        sendError(callback, result.getStatus().toString());
                     }
                 });
 
                 return;
             } catch (IllegalStateException e) {
                 debug("Problem occurred with media during loading", e);
             } catch (Exception e) {
                 debug("Problem opening media during loading", e);
             }
 
-            callback.sendError("");
+            sendError(callback, "");
         }
     }
 
     public ChromeCast(Context context, RouteInfo route) {
         int status =  GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
         if (status != ConnectionResult.SUCCESS) {
             throw new IllegalStateException("Play services are required for Chromecast support (go status code " + status + ")");
         }
@@ -191,17 +210,17 @@ class ChromeCast implements GeckoMediaPl
         apiClient = new GoogleApiClient.Builder(context)
             .addApi(Cast.API, apiOptionsBuilder.build())
             .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                 @Override
                 public void onConnected(Bundle connectionHint) {
                     // Sometimes apiClient is null here. See bug 1061032
                     if (apiClient != null && !apiClient.isConnected()) {
                         debug("Connection failed");
-                        callback.sendError("Not connected");
+                        sendError(callback, "Not connected");
                         return;
                     }
 
                     // Launch the media player app and launch this url once its loaded
                     try {
                         Cast.CastApi.launchApplication(apiClient, CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID, true)
                                     .setResultCallback(new VideoPlayCallback(url, type, title, callback));
                     } catch (Exception e) {
@@ -215,38 +234,38 @@ class ChromeCast implements GeckoMediaPl
                 }
         }).build();
 
         apiClient.connect();
     }
 
     public void start(final EventCallback callback) {
         // Nothing to be done here
-        callback.sendSuccess(null);
+        sendSuccess(callback, null);
     }
 
     public void stop(final EventCallback callback) {
         // Nothing to be done here
-        callback.sendSuccess(null);
+        sendSuccess(callback, null);
     }
 
     public boolean verifySession(final EventCallback callback) {
         String msg = null;
         if (apiClient == null || !apiClient.isConnected()) {
             msg = "Not connected";
         }
 
         if (mSessionId == null) {
             msg = "No session";
         }
 
         if (msg != null) {
             debug(msg);
             if (callback != null) {
-                callback.sendError(msg);
+                sendError(callback, msg);
             }
             return false;
         }
 
         return true;
     }
 
     public void play(final EventCallback callback) {
@@ -256,49 +275,49 @@ class ChromeCast implements GeckoMediaPl
 
         try {
             remoteMediaPlayer.play(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
                 @Override
                 public void onResult(MediaChannelResult result) {
                     Status status = result.getStatus();
                     if (!status.isSuccess()) {
                         debug("Unable to play: " + status.getStatusCode());
-                        callback.sendError(status.toString());
+                        sendError(callback, status.toString());
                     } else {
-                        callback.sendSuccess(null);
+                        sendSuccess(callback, null);
                     }
                 }
             });
         } catch(IllegalStateException ex) {
             // The media player may throw if the session has been killed. For now, we're just catching this here.
-            callback.sendError("Error playing");
+            sendError(callback, "Error playing");
         }
     }
 
     public void pause(final EventCallback callback) {
         if (!verifySession(callback)) {
             return;
         }
 
         try {
             remoteMediaPlayer.pause(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
                 @Override
                 public void onResult(MediaChannelResult result) {
                     Status status = result.getStatus();
                     if (!status.isSuccess()) {
                         debug("Unable to pause: " + status.getStatusCode());
-                        callback.sendError(status.toString());
+                        sendError(callback, status.toString());
                     } else {
-                        callback.sendSuccess(null);
+                        sendSuccess(callback, null);
                     }
                 }
             });
         } catch(IllegalStateException ex) {
             // The media player may throw if the session has been killed. For now, we're just catching this here.
-            callback.sendError("Error pausing");
+            sendError(callback, "Error pausing");
         }
     }
 
     public void end(final EventCallback callback) {
         if (!verifySession(callback)) {
             return;
         }
 
@@ -310,33 +329,33 @@ class ChromeCast implements GeckoMediaPl
                         try {
                             Cast.CastApi.removeMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace());
                             remoteMediaPlayer = null;
                             mSessionId = null;
                             apiClient.disconnect();
                             apiClient = null;
 
                             if (callback != null) {
-                                callback.sendSuccess(null);
+                                sendSuccess(callback, null);
                             }
 
                             return;
                         } catch(Exception ex) {
                             debug("Error ending", ex);
                         }
                     }
 
                     if (callback != null) {
-                        callback.sendError(result.getStatus().toString());
+                        sendError(callback, result.getStatus().toString());
                     }
                 }
             });
         } catch(IllegalStateException ex) {
             // The media player may throw if the session has been killed. For now, we're just catching this here.
-            callback.sendError("Error stopping");
+            sendError(callback, "Error stopping");
         }
     }
 
     class MirrorChannel implements MessageReceivedCallback {
         /**
          * @return custom namespace
          */
         public String getNamespace() {
@@ -364,17 +383,16 @@ class ChromeCast implements GeckoMediaPl
                                            });
                 } catch (Exception e) {
                     Log.e(LOGTAG, "Exception while sending message", e);
                 }
             }
         }
     }
     private class MirrorCallback implements ResultCallback<ApplicationConnectionResult> {
-
         final EventCallback callback;
         MirrorCallback(final EventCallback callback) {
             this.callback = callback;
         }
 
 
         public void onResult(ApplicationConnectionResult result) {
             Status status = result.getStatus();
@@ -388,24 +406,24 @@ class ChromeCast implements GeckoMediaPl
                 // Create the custom message
                 // channel
                 mMirrorChannel = new MirrorChannel();
                 try {
                     Cast.CastApi.setMessageReceivedCallbacks(apiClient,
                                                              mMirrorChannel
                                                              .getNamespace(),
                                                              mMirrorChannel);
-                    callback.sendSuccess(null);
+                    sendSuccess(callback, null);
                 } catch (IOException e) {
                     Log.e(LOGTAG, "Exception while creating channel", e);
                 }
 
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Casting:Mirror", route.getId()));
             } else {
-                callback.sendError(status.toString());
+                sendError(callback, status.toString());
             }
         }
     }
 
     public void message(String msg, final EventCallback callback) {
         if (mMirrorChannel != null) {
             mMirrorChannel.sendMessage(msg);
         }