Home | History | Annotate | Download | only in apprtc
      1 /*
      2  * libjingle
      3  * Copyright 2013, Google Inc.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are met:
      7  *
      8  *  1. Redistributions of source code must retain the above copyright notice,
      9  *     this list of conditions and the following disclaimer.
     10  *  2. Redistributions in binary form must reproduce the above copyright notice,
     11  *     this list of conditions and the following disclaimer in the documentation
     12  *     and/or other materials provided with the distribution.
     13  *  3. The name of the author may not be used to endorse or promote products
     14  *     derived from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
     19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 package org.appspot.apprtc;
     29 
     30 import android.annotation.SuppressLint;
     31 import android.app.Activity;
     32 import android.util.Log;
     33 import android.webkit.ConsoleMessage;
     34 import android.webkit.JavascriptInterface;
     35 import android.webkit.WebChromeClient;
     36 import android.webkit.WebView;
     37 import android.webkit.WebViewClient;
     38 
     39 /**
     40  * Java-land version of Google AppEngine's JavaScript Channel API:
     41  * https://developers.google.com/appengine/docs/python/channel/javascript
     42  *
     43  * Requires a hosted HTML page that opens the desired channel and dispatches JS
     44  * on{Open,Message,Close,Error}() events to a global object named
     45  * "androidMessageHandler".
     46  */
     47 public class GAEChannelClient {
     48   private static final String TAG = "GAEChannelClient";
     49   private WebView webView;
     50   private final ProxyingMessageHandler proxyingMessageHandler;
     51 
     52   /**
     53    * Callback interface for messages delivered on the Google AppEngine channel.
     54    *
     55    * Methods are guaranteed to be invoked on the UI thread of |activity| passed
     56    * to GAEChannelClient's constructor.
     57    */
     58   public interface MessageHandler {
     59     public void onOpen();
     60     public void onMessage(String data);
     61     public void onClose();
     62     public void onError(int code, String description);
     63   }
     64 
     65   /** Asynchronously open an AppEngine channel. */
     66   @SuppressLint("SetJavaScriptEnabled")
     67   public GAEChannelClient(
     68       Activity activity, String token, MessageHandler handler) {
     69     webView = new WebView(activity);
     70     webView.getSettings().setJavaScriptEnabled(true);
     71     webView.setWebChromeClient(new WebChromeClient() {  // Purely for debugging.
     72         public boolean onConsoleMessage (ConsoleMessage msg) {
     73           Log.d(TAG, "console: " + msg.message() + " at " +
     74               msg.sourceId() + ":" + msg.lineNumber());
     75           return false;
     76         }
     77       });
     78     webView.setWebViewClient(new WebViewClient() {  // Purely for debugging.
     79         public void onReceivedError(
     80             WebView view, int errorCode, String description,
     81             String failingUrl) {
     82           Log.e(TAG, "JS error: " + errorCode + " in " + failingUrl +
     83               ", desc: " + description);
     84         }
     85       });
     86     proxyingMessageHandler =
     87         new ProxyingMessageHandler(activity, handler, token);
     88     webView.addJavascriptInterface(
     89         proxyingMessageHandler, "androidMessageHandler");
     90     webView.loadUrl("file:///android_asset/channel.html");
     91   }
     92 
     93   /** Close the connection to the AppEngine channel. */
     94   public void close() {
     95     if (webView == null) {
     96       return;
     97     }
     98     proxyingMessageHandler.disconnect();
     99     webView.removeJavascriptInterface("androidMessageHandler");
    100     webView.loadUrl("about:blank");
    101     webView = null;
    102   }
    103 
    104   // Helper class for proxying callbacks from the Java<->JS interaction
    105   // (private, background) thread to the Activity's UI thread.
    106   private static class ProxyingMessageHandler {
    107     private final Activity activity;
    108     private final MessageHandler handler;
    109     private final boolean[] disconnected = { false };
    110     private final String token;
    111 
    112     public
    113      ProxyingMessageHandler(Activity activity, MessageHandler handler,
    114                             String token) {
    115       this.activity = activity;
    116       this.handler = handler;
    117       this.token = token;
    118     }
    119 
    120     public void disconnect() {
    121       disconnected[0] = true;
    122     }
    123 
    124     private boolean disconnected() {
    125       return disconnected[0];
    126     }
    127 
    128     @JavascriptInterface public String getToken() {
    129       return token;
    130     }
    131 
    132     @JavascriptInterface public void onOpen() {
    133       activity.runOnUiThread(new Runnable() {
    134           public void run() {
    135             if (!disconnected()) {
    136               handler.onOpen();
    137             }
    138           }
    139         });
    140     }
    141 
    142     @JavascriptInterface public void onMessage(final String data) {
    143       activity.runOnUiThread(new Runnable() {
    144           public void run() {
    145             if (!disconnected()) {
    146               handler.onMessage(data);
    147             }
    148           }
    149         });
    150     }
    151 
    152     @JavascriptInterface public void onClose() {
    153       activity.runOnUiThread(new Runnable() {
    154           public void run() {
    155             if (!disconnected()) {
    156               handler.onClose();
    157             }
    158           }
    159         });
    160     }
    161 
    162     @JavascriptInterface public void onError(
    163         final int code, final String description) {
    164       activity.runOnUiThread(new Runnable() {
    165           public void run() {
    166             if (!disconnected()) {
    167               handler.onError(code, description);
    168             }
    169           }
    170         });
    171     }
    172   }
    173 }
    174