mobisocial.musubi.webapp.WebAppActivity.java Source code

Java tutorial

Introduction

Here is the source code for mobisocial.musubi.webapp.WebAppActivity.java

Source

/*
 * Copyright 2012 The Stanford MobiSocial Laboratory
 *
 * 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 mobisocial.musubi.webapp;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import mobisocial.musubi.App;
import mobisocial.musubi.R;
import mobisocial.musubi.ui.MusubiBaseActivity;
import mobisocial.socialkit.musubi.DbFeed;
import mobisocial.socialkit.musubi.DbObj;
import mobisocial.socialkit.musubi.Musubi;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.mobisocial.corral.ContentCorral;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.KeyEvent;
import android.webkit.ConsoleMessage;
import android.webkit.ConsoleMessage.MessageLevel;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.LinearLayout;
import android.widget.Toast;

/**
 * Runs a webapp by injecting SocialKit-JS in a webview.
 * 
 * SocialKitJS is bound to the application given in the extra EXTRA_APP_ID,
 * which must be set by Musubi in a trusted way-- this activity cannot be
 * safely exported.
 * 
 * {@see AppObj}
 */
public class WebAppActivity extends MusubiBaseActivity {
    public static final String EXTRA_APP_NAME = "name";
    public static final String EXTRA_APP_ID = "appid";
    public static final String EXTRA_APP_URI = "appurl";
    private static final String EXTRA_CURRENT_PAGE = "page";
    private String mCurrentPage;
    private String mAppId;
    private Uri mObjUri;
    private Uri mFeedUri;
    private DbObj mArgumentData;
    private DbFeed mArgumentFeed;
    private String mArgumentName;
    WebView mWebView;
    private Musubi mMusubi;
    private boolean mDestroyed = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.appcorral);

        mMusubi = App.getMusubi(this);
        getSupportActionBar().hide();

        mAppId = getIntent().getStringExtra(EXTRA_APP_ID);
        if (mAppId == null) {
            toast("Must set app id for socialKitJS binding.");
            finish();
            return;
        }

        mArgumentName = getIntent().getStringExtra(EXTRA_APP_NAME);
        if (mArgumentName == null) {
            mArgumentName = "Application";
        }

        mObjUri = getIntent().getData();
        if (mObjUri != null) {
            mArgumentData = mMusubi.objForUri(mObjUri);
        }

        mFeedUri = (Uri) getIntent().getParcelableExtra(Musubi.EXTRA_FEED_URI);
        if (mFeedUri != null) {
            mArgumentFeed = mMusubi.getFeed(mFeedUri);
        }

        if (savedInstanceState != null) {
            mCurrentPage = savedInstanceState.getString(EXTRA_CURRENT_PAGE);
        } else {
            Uri appUrl = getIntent().getParcelableExtra(EXTRA_APP_URI);
            if (appUrl != null) {
                mCurrentPage = appUrl.toString();
            }
        }

        if (mCurrentPage == null) {
            Log.w(TAG, "No WebApp specified, bailing.");
            finish();
            return;
        }

        mWebView = (WebView) findViewById(R.id.webview);
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
        WebAppWebViewClient webapp = new WebAppWebViewClient(this, mWebView, mAppId);
        mWebView.setWebViewClient(webapp);
        mWebView.addJavascriptInterface(webapp.mSocialKitJavascript, SocialKitJavascript.MUSUBI_JS_VAR);
        mWebView.setWebChromeClient(new WebAppWebChromeClient(webapp));

        new DataFromLocalhostTask(webapp).execute();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

    @Override
    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
        //provide an override to escape
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            finish();
        }
        return super.onKeyLongPress(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        // Check if the key event was the BACK key and if there's history
        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
            mWebView.goBack();
            return true;
        }
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            mWebView.loadUrl("javascript:globalAppContext.back()");
            Log.w(TAG, "pressed back");
            return true;
        }
        // If it wasn't the BACK key or there's no web page history, bubble up to the default
        // system behavior (probably exit the activity)
        return super.onKeyUp(keyCode, event);
    }

    class WebAppWebChromeClient extends WebChromeClient {
        private WebAppWebViewClient mWebViewClient;

        public WebAppWebChromeClient(WebAppWebViewClient webViewClient) {
            mWebViewClient = webViewClient;
        }

        @Override
        public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
            mWebViewClient.mSocialKitJavascript.unbind();
            return false;
        }

        @Override
        public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
            if (consoleMessage.messageLevel() == MessageLevel.ERROR) {
                //if there is an error in this web app for whatever reason
                //including it not handling our callbacks correctly
                //then go back
                String msg = "Long Press BACK to EXIT.\n" + "This application had an error: "
                        + consoleMessage.sourceId() + ":" + consoleMessage.lineNumber() + ":"
                        + consoleMessage.message();
                Toast.makeText(WebAppActivity.this, msg, Toast.LENGTH_LONG).show();
            }
            return false;
        }
    }

    class WebAppWebViewClient extends WebViewClient {
        private SocialKitJavascript mSocialKitJavascript;
        private AlertDialog mAlertDialog;
        private ProgressDialog mProgressDialog;

        public WebAppWebViewClient(Activity context, WebView webView, String appId) {
            long objId = 0, feedId = 0;
            try {
                objId = Long.parseLong(mObjUri.getLastPathSegment());
            } catch (Throwable t) {
            }

            mSocialKitJavascript = SocialKitJavascript.bindAccess(context, appId, objId);
            mAlertDialog = new AlertDialog.Builder(WebAppActivity.this).create();

            mProgressDialog = new ProgressDialog(WebAppActivity.this);
            mProgressDialog.setTitle(mArgumentName);
            mProgressDialog.setMessage("Loading...");
            mProgressDialog.setCancelable(true);
            mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialog) {
                    finish();
                }
            });
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            if (DBG)
                Log.d(TAG, "Page loaded, injecting musubi SocialKit bridge for " + url);
            mCurrentPage = url;

            // Launch musubi app
            SocialKitJavascript.SKUser user = null;
            SocialKitJavascript.SKDbObj obj = null;
            SocialKitJavascript.SKFeed feed = null;
            if (mArgumentData != null) {
                user = mSocialKitJavascript.new SKUser(
                        mMusubi.userForLocalDevice(mArgumentData.getContainingFeed().getUri()));
                obj = mSocialKitJavascript.new SKDbObj(mArgumentData);
                feed = mSocialKitJavascript.new SKFeed(mArgumentData.getContainingFeed());
            } else if (mArgumentFeed != null) {
                obj = null;
                feed = mSocialKitJavascript.new SKFeed(mArgumentFeed);
                user = mSocialKitJavascript.new SKUser(mMusubi.userForLocalDevice(mFeedUri));
            }

            String appId;
            String objJson;
            if (obj == null) {
                appId = url;
                objJson = "false";
            } else {
                appId = mArgumentData.getAppId();
                objJson = obj.toJson().toString();
            }
            String initSocialKit = new StringBuilder("javascript:").append("Musubi._launch(")
                    .append(user.toJson() + ", " + feed.toJson() + ", '" + appId + "', " + objJson + ")")
                    .toString();
            Log.d(TAG, "Android calling " + initSocialKit);
            mWebView.loadUrl(initSocialKit);
            ProgressDialog d = mProgressDialog;
            if (d != null && d.isShowing()) {
                d.dismiss();
            }
        }

        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
            if (DBG) {
                Log.d(TAG, "socialkit.js error: " + errorCode + ", " + description);
            }
            mAlertDialog.setTitle("Connectivity Problem");
            mAlertDialog.setMessage("There was a problem loading " + mArgumentName
                    + ". Please make sure that you have connectivity.");
            mAlertDialog.setButton("OK", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    WebAppActivity.this.finish();
                    return;
                }
            });
            mAlertDialog.show();
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
            mSocialKitJavascript.setLoadedUrl(url);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //this works around a memory leak with the webview that occurs at least on all <= 2.3.7
        LinearLayout web_view_parent = (LinearLayout) findViewById(R.id.db1_root);
        web_view_parent.removeAllViews();
        mDestroyed = true;
        mWebView.destroy();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(EXTRA_CURRENT_PAGE, mCurrentPage);
    }

    /**
     * Serves content to a WebView from localhost rather than the original host.
     * This allows the webview to interact with content from the local device
     * while avoiding issues involving the same origin policy.
     *
     */
    class DataFromLocalhostTask extends AsyncTask<Void, Void, Void> {
        WebAppWebViewClient webapp;
        String data = null;
        String baseUrl = null;
        String mCachedPage;

        public DataFromLocalhostTask(WebAppWebViewClient webapp) {
            this.webapp = webapp;
        }

        @Override
        protected void onPreExecute() {
            webapp.mProgressDialog.show();
        }

        @Override
        protected Void doInBackground(Void... params) {
            Uri appUri = Uri.parse(mCurrentPage);
            Uri cached = ContentCorral.getWebappCacheUrl(appUri);
            if (cached != null) {
                mCachedPage = cached.toString();
            } else {
                mCachedPage = ContentCorral.cacheWebApp(appUri, "tmp").toString();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            if (mDestroyed)
                return;
            mWebView.loadUrl(mCachedPage);
        }
    }
}