Java tutorial
/* * Copyright (C) 2011 The Stanford MobiSocial Laboratory * * This file is part of Musubi, a mobile social network. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package edu.stanford.mobisocial.dungbeetle; import mobisocial.socialkit.Obj; import mobisocial.socialkit.musubi.DbFeed; import mobisocial.socialkit.musubi.DbObj; import mobisocial.socialkit.musubi.DbUser; import mobisocial.socialkit.musubi.Musubi; import mobisocial.socialkit.musubi.multiplayer.Multiplayer; import mobisocial.socialkit.obj.MemObj; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.KeyEvent; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; import edu.stanford.mobisocial.dungbeetle.feed.iface.Activator; import edu.stanford.mobisocial.dungbeetle.feed.objects.AppObj; import edu.stanford.mobisocial.dungbeetle.model.Contact; import edu.stanford.mobisocial.dungbeetle.ui.MusubiBaseActivity; import edu.stanford.mobisocial.dungbeetle.util.ActivityCallout; import edu.stanford.mobisocial.dungbeetle.util.Maybe; import edu.stanford.mobisocial.dungbeetle.util.Maybe.NoValError; /** * A web-based 'app store' for finding new Musubi apps. Also * contains a partial implementaion of the SocialKit-JS library. */ public class AppFinderActivity extends MusubiBaseActivity { private static final String EXTRA_CURRENT_PAGE = "page"; private static final String MUSUBI_JS = "Musubi_android_platform"; private String mCurrentPage; private SocialKitJavascript mSocialKitJavascript; private Uri mFeedUri; WebView mWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mFeedUri = (Uri) getIntent().getParcelableExtra(Musubi.EXTRA_FEED_URI); setContentView(R.layout.appcorral); if (savedInstanceState != null) { mCurrentPage = savedInstanceState.getString(EXTRA_CURRENT_PAGE); } else if (getIntent().getData() != null) { mCurrentPage = getIntent().getDataString(); } else { mCurrentPage = "http://musubi.us/apps"; } WebViewClient webViewClient = new AppStoreWebViewClient(); mWebView = (WebView) findViewById(R.id.webview); mWebView.getSettings().setJavaScriptEnabled(true); //mWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); mWebView.setWebViewClient(webViewClient); mSocialKitJavascript = new SocialKitJavascript(this, mFeedUri); mWebView.addJavascriptInterface(mSocialKitJavascript, MUSUBI_JS); mWebView.loadUrl(mCurrentPage); } @Override public boolean onKeyDown(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 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.onKeyDown(keyCode, event); } class AppStoreWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Uri uri = Uri.parse(url); String scheme = uri.getScheme(); if (scheme.startsWith("http")) { if (!uri.getPath().endsWith(".apk")) { return false; } } if (scheme.startsWith("socialkit")) { String appUrl = uri.buildUpon().scheme("http").build().toString(); MembersSelectedCallout callout = new MembersSelectedCallout(AppFinderActivity.this, mFeedUri, appUrl); doActivityForResult(callout); return true; } // Otherwise, the link is not for a page on my site, so launch another Activity that handles URLs Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(intent); return true; } @Override public void onPageFinished(WebView view, String url) { if (DBG) Log.d(TAG, "Page loaded, injecting musubi SocialKit bridge for " + url); mCurrentPage = url; //mWebView.loadUrl("javascript:document.write(\"<script>" + js + "</script>\")"); // Launch musubi app DbFeed dbFeed = mSocialKitJavascript.mDbFeed; SocialKitJavascript.Feed feed = mSocialKitJavascript.new Feed(dbFeed); SocialKitJavascript.User user = mSocialKitJavascript.new User( App.instance().getMusubi().userForLocalDevice(dbFeed.getUri())); String initSocialKit = new StringBuilder("javascript:").append("Musubi._launch(") .append(user.toJson() + ", " + feed.toJson() + ",'someappid', false)").toString(); Log.d(TAG, "Android calling " + initSocialKit); mWebView.loadUrl(initSocialKit); } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { if (DBG) { Log.d(TAG, "socialkit.js error: " + errorCode + ", " + description); } } } static class SocialKitJavascript { private static final String TAG = "socialkit.js"; final Context mContext; final Musubi mMusubi; final DbFeed mDbFeed; SocialKitJavascript(Activity context, Uri feedUri) { mContext = context; mMusubi = Musubi.getInstance(context); mDbFeed = mMusubi.getFeed(feedUri); } public String _messagesForFeed(String feedName, String callback) { Log.d(TAG, "message for " + feedName); return null; } public void _postObjToFeed(String obj, String feedName) { DbFeed feed = App.instance().getMusubi() .getFeed(edu.stanford.mobisocial.dungbeetle.model.Feed.uriForName(feedName)); //feed.postObj(obj); Log.d(TAG, "posting " + obj); } public void _setConfig(String config) { Log.d(TAG, "config " + config); } public void _log(String text) { Log.d(TAG, text); } public void showToast(String toast) { Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show(); } public Feed getFeed() { return new Feed(mDbFeed); } public boolean isDeveloperModeEnabled() { return MusubiBaseActivity.isDeveloperModeEnabled(mContext); } /** * JSON representations of common Musubi classes. */ abstract class SocialKitApiConversion<JavaType> implements Jsonable { private final JavaType mNativeType; public SocialKitApiConversion(JavaType nativeType) { mNativeType = nativeType; } public JavaType getNative() { return mNativeType; } } class User extends SocialKitApiConversion<DbUser> { public User(DbUser user) { super(user); } @Override public JSONObject toJson() { DbUser user = getNative(); JSONObject o = new JSONObject(); try { o.put("name", user.getName()); o.put("id", user.getLocalId()); o.put("personId", user.getId()); } catch (JSONException e) { } return o; } } class Obj extends SocialKitApiConversion<DbObj> { public Obj(DbObj obj) { super(obj); } @Override public JSONObject toJson() { DbObj obj = getNative(); JSONObject json = obj.getJson(); try { json.put("type", obj.getType()); json.put("data", json); } catch (JSONException e) { } return json; } } class Feed extends SocialKitApiConversion<DbFeed> { public Feed(DbFeed feed) { super(feed); } @Override public JSONObject toJson() { DbFeed feed = getNative(); JSONObject o = new JSONObject(); try { o.put("name", feed.getUri().getLastPathSegment()); o.put("uri", feed.getUri().toString()); o.put("session", feed.getUri().getLastPathSegment()); o.put("key", "what is a key?"); JSONArray m = new JSONArray(); for (DbUser u : feed.getRemoteUsers()) { m.put(new User(u).toJson()); } o.put("members", m); } catch (JSONException e) { } return o; } } interface Jsonable { public JSONObject toJson(); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(EXTRA_CURRENT_PAGE, mCurrentPage); } /** * When a webapp is launched, we create an Obj of type "app" * representing the app session. The obj's json has a field * web_url identifying the webapp code, and a "membership" * list of participants. This class manages the picker for * selecting contacts. Once the user has selected participants, * the obj is created and sent to the current feed, and the app is * launched. */ private class MembersSelectedCallout implements ActivityCallout { private final Context mContext; private final Uri mFeedUri; private final String mAppUrl; public MembersSelectedCallout(Context context, Uri feedUri, String appUrl) { mFeedUri = feedUri; mContext = context; mAppUrl = appUrl; } @Override public Intent getStartIntent() { Intent i = new Intent(PickContactsActivity.INTENT_ACTION_PICK_CONTACTS); i.putExtra(PickContactsActivity.INTENT_EXTRA_PARENT_FEED, mFeedUri); return i; } @Override public void handleResult(int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK) { // Create and share new application instance Obj obj = objForPickerResult(mAppUrl, data); Uri objUri = Helpers.sendToFeed(mContext, obj, mFeedUri); mContext.getContentResolver().registerContentObserver(objUri, false, new ObjObserver(mContext, new AppObj(), objUri)); } else { Log.i(TAG, "No members selected."); } } private class ObjObserver extends ContentObserver { private final Uri mUri; private final Context mContext; private final Activator mActivator; public ObjObserver(Context context, Activator activator, Uri uri) { super(new Handler(context.getMainLooper())); mUri = uri; mContext = context; mActivator = activator; } @Override public void onChange(boolean selfChange) { mContext.getContentResolver().unregisterContentObserver(this); Long objId = Long.parseLong(mUri.getLastPathSegment()); DbObj obj = App.instance().getMusubi().objForId(objId); Log.d(TAG, "Launching app obj " + obj + " for " + objId); mActivator.activate(mContext, obj); } } public Obj objForPickerResult(String appUrl, Intent data) { long[] contactIds = data.getLongArrayExtra("contacts"); JSONArray participantIds = new JSONArray(); participantIds.put(App.instance().getLocalPersonId()); for (long id : contactIds) { Maybe<Contact> annoyingContact = Contact.forId(AppFinderActivity.this, id); try { Contact contact = annoyingContact.get(); participantIds.put(contact.personId); } catch (NoValError e) { participantIds.put(Contact.UNKNOWN); } } JSONObject json = new JSONObject(); try { json.put(Multiplayer.OBJ_MEMBERSHIP, participantIds); json.put(AppObj.WEB_URL, appUrl); } catch (JSONException e) { Log.e(TAG, "Error setting up json"); } return new MemObj("app", json); } } }