/* * 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.net.URLDecoder; import mobisocial.musubi.App; import mobisocial.musubi.model.MApp; import mobisocial.musubi.model.MObject; import mobisocial.musubi.model.helpers.AppManager; import mobisocial.musubi.model.helpers.SQLClauseHelper; import mobisocial.musubi.objects.AppObj; import mobisocial.musubi.provider.MusubiContentProvider; import mobisocial.musubi.provider.MusubiContentProvider.Provided; import mobisocial.musubi.ui.MusubiBaseActivity; import mobisocial.socialkit.Obj; import mobisocial.socialkit.musubi.DbFeed; import mobisocial.socialkit.musubi.DbIdentity; import mobisocial.socialkit.musubi.DbObj; import mobisocial.socialkit.musubi.Musubi; import mobisocial.socialkit.obj.MemObj; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.mobisocial.corral.ContentCorral; import android.app.Activity; import android.database.Cursor; import android.net.Uri; import android.util.Base64; import android.util.Log; import android.widget.Toast; /** * Creates bindings in Javascript for the local Musubi database with * access granted to the specified app. */ class SocialKitJavascript { static final String MUSUBI_JS_VAR = "Musubi_android_platform"; private static final String TAG = "socialkit.js"; final Activity mContext; final Musubi mMusubi; /** * TODO: Manage apps through AppRegistry. */ String mLoadedUrl; String mAppTitle; String mAppId; String mAppToken; private SocialKitJavascript(Activity context, String appId) { mContext = context; mMusubi = App.getMusubi(context); mAppId = appId; } public static SocialKitJavascript bindAccess(Activity context, String appId, long objId) { SocialKitJavascript skjs = new SocialKitJavascript(context, appId); skjs.mAppToken = ContentCorral.registerForAccessToken(appId, objId); return skjs; } public void unbind() { ContentCorral.unregisterAppToken(mAppToken); } public void setLoadedUrl(String url) { mLoadedUrl = url; } public String _queryFeed(String feedId, String query, String sortOrder) { Log.d(TAG, "querying " + feedId + ", " + query); Uri uri = MusubiContentProvider.uriForDir(Provided.OBJECTS); String[] projection = new String[] { DbObj.COL_ID }; String selection = MObject.COL_FEED_ID + "=? AND " + MObject.COL_APP_ID + "=?"; selection = SQLClauseHelper.andClauses(selection, query); String[] selectionArgs = new String[] { feedId, mAppId }; if (!isDefined(sortOrder)) { sortOrder = null; } Cursor c = mContext.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder); JSONArray results = new JSONArray(); try { while (c.moveToNext()) { results.put(new SKDbObj(mMusubi.objForId(c.getLong(0))).toJson()); } return results.toString(); } finally { c.close(); } } public String _querySubfeed(String objId, String query, String sortOrder) { Log.d(TAG, "querying subfeed " + objId + ", " + query); Uri uri = MusubiContentProvider.uriForDir(Provided.OBJECTS); String[] projection = new String[] { DbObj.COL_ID }; String selection = MObject.COL_PARENT_ID + "=? AND " + MObject.COL_APP_ID + "=?"; selection = SQLClauseHelper.andClauses(selection, query); String[] selectionArgs = new String[] { objId, mAppId }; if (!isDefined(sortOrder)) { sortOrder = null; } Cursor c = mContext.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder); JSONArray results = new JSONArray(); try { while (c.moveToNext()) { results.put(new SKDbObj(mMusubi.objForId(c.getLong(0))).toJson()); } return results.toString(); } finally { c.close(); } } private boolean isDefined(String jsString) { return (jsString != null && jsString.length() > 0 && !jsString.equals("undefined")); } public String _urlForRaw(long objId) { return Uri.parse("http://127.0.0.1:" + ContentCorral.SERVER_PORT).buildUpon() .appendPath("raw").appendPath("" + objId) .appendQueryParameter("ticket", mAppToken).toString(); } //TODO: delete me, I am redundant public void _back() { _quit(); } public void _postObjToFeed(String objJson, String feedIdString) { long feedId; JSONObject browserJson; try { feedId = Long.parseLong(feedIdString); } catch (NumberFormatException e) { Log.e(TAG, "bad feedId for postObj", e); return; } try { browserJson = new JSONObject(objJson); } catch (JSONException e) { Log.e(TAG, "Bad json from web"); return; } DbFeed feed = mMusubi.getFeed(DbFeed.uriForId(feedId)); try { Obj javaObj = objFromJson(mAppId, browserJson); feed.postObj(javaObj); } catch (Exception e) { Log.e(TAG, "error posting obj to feed: " + objJson, e); } } public void _postObjToSubfeed(String objJson, String feedIdString, String parentIdString) { long feedId; long parentId; JSONObject browserJson; try { feedId = Long.parseLong(feedIdString); parentId = Long.parseLong(parentIdString); } catch (NumberFormatException e) { Log.e(TAG, "bad feedId for postObj", e); return; } try { browserJson = new JSONObject(objJson); } catch (JSONException e) { Log.e(TAG, "Bad json from web"); return; } DbObj parentObj = mMusubi.objForId(parentId); if (parentObj == null || parentObj.getContainingFeed().getLocalId() != feedId) { Log.e(TAG, "Failed to post to parent obj " + parentObj); return; } try { Obj javaObj = objFromJson(mAppId, browserJson); parentObj.getSubfeed().postObj(javaObj); } catch (Exception e) { Log.e(TAG, "error posting obj to feed: " + objJson, e); } } public void _quit() { mContext.finish(); } public void _setConfig(String config) { Log.d(TAG, "config " + config); try { JSONObject obj = new JSONObject(config); if (obj.has("title")) { mAppTitle = obj.getString("title"); } } catch (JSONException e) { // don't sweat it. } } public void _log(String text) { Log.d(TAG, text); } public void showToast(String toast) { Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show(); } public boolean isDeveloperModeEnabled() { return MusubiBaseActivity.isDeveloperModeEnabled(mContext); } Obj objFromJson(String appId, JSONObject source) throws JSONException { String type = source.getString(SKObj.FIELD_TYPE); JSONObject json = null; byte[] raw = null; Integer intKey = null; String stringKey = null; if (source.has(SKObj.FIELD_JSON)) { json = source.getJSONObject(SKObj.FIELD_JSON); } else if (source.has("data")) { Log.w(TAG, "old SocialKitJS version detected"); json = source.getJSONObject("data"); } else { json = new JSONObject(); } try { json.put(AppObj.CLAIMED_APP_ID, appId); MApp app = new AppManager(App.getDatabaseSource(mContext)).lookupAppByAppId(appId); if (app != null && app.name_ != null) { json.put(AppObj.APP_NAME, app.name_); } } catch (JSONException e) { throw new IllegalStateException("Bad json libary", e); } if (source.has(SKObj.FIELD_RAW_DATA_URL)) { DataUri data = new DataUri(source.getString(SKObj.FIELD_RAW_DATA_URL)); raw = data.data; } if (source.has(SKObj.FIELD_INT_KEY)) { intKey = source.getInt(SKObj.FIELD_INT_KEY); } if (source.has(SKObj.FIELD_STRING_KEY)) { stringKey = source.getString(SKObj.FIELD_STRING_KEY); } return new MemObj(type, json, raw, intKey, stringKey); } /** * JSON representations of common Musubi classes. */ abstract class SocialKitApiConversion<JavaType> implements Jsonable { private final JavaType mNativeType; public SocialKitApiConversion(JavaType nativeType) { mNativeType = nativeType; } public JavaType getJavaImplementation() { return mNativeType; } } class SKUser extends SocialKitApiConversion<DbIdentity> { public SKUser(DbIdentity user) { super(user); } @Override public JSONObject toJson() { DbIdentity user = getJavaImplementation(); 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; } } /** * Don't confuse this with SKDbObj * */ class SKObj extends SocialKitApiConversion<MemObj> { static final String FIELD_RAW_DATA_URL = "raw_data_url"; static final String FIELD_JSON = "json"; static final String FIELD_TYPE = "type"; static final String FIELD_INT_KEY = "intKey"; static final String FIELD_STRING_KEY = "stringKey"; public SKObj(MemObj obj) { super(obj); } @Override public JSONObject toJson() { MemObj obj = getJavaImplementation(); JSONObject json = new JSONObject(); try { json.put(FIELD_TYPE, obj.getType()); json.put(FIELD_JSON, obj.getJson()); if (obj.getRaw() != null) { json.put(FIELD_RAW_DATA_URL, null);//TODO } json.put(FIELD_INT_KEY, obj.getIntKey()); json.put(FIELD_STRING_KEY, obj.getStringKey()); } catch (JSONException e) {} return json; } } /** * Don't confuse this with SKObj * */ class SKDbObj extends SocialKitApiConversion<DbObj> { static final String FIELD_OBJ_ID = "objId"; static final String FIELD_SENDER = "sender"; static final String FIELD_HASH = "hash"; static final String FIELD_RAW_DATA_URL = "raw_data_url"; static final String FIELD_JSON = "json"; static final String FIELD_TYPE = "type"; static final String FIELD_INT_KEY = "intKey"; static final String FIELD_STRING_KEY = "stringKey"; static final String FIELD_FEED_ID = "session"; public SKDbObj(DbObj obj) { super(obj); } @Override public JSONObject toJson() { DbObj obj = getJavaImplementation(); JSONObject json = new JSONObject(); try { //json.put("data", obj.getJson()); json.put(FIELD_HASH, obj.getUniversalHashString()); json.put(FIELD_OBJ_ID, obj.getLocalId()); json.put(FIELD_SENDER, new SKUser(obj.getSender()).toJson()); json.put(FIELD_TYPE, obj.getType()); json.put(FIELD_JSON, obj.getJson()); json.put(FIELD_FEED_ID, obj.getContainingFeed().getLocalId()); if (obj.getRaw() != null) { json.put(FIELD_RAW_DATA_URL, null);//TODO } json.put(FIELD_INT_KEY, obj.getIntKey()); json.put(FIELD_STRING_KEY, obj.getStringKey()); } catch (JSONException e) {} return json; } } class SKFeed extends SocialKitApiConversion<DbFeed> { public SKFeed(DbFeed feed) { super(feed); } @Override public JSONObject toJson() { DbFeed feed = getJavaImplementation(); JSONObject o = new JSONObject(); try { o.put("name", feed.getUri().getLastPathSegment()); o.put("uri", feed.getUri().toString()); o.put("session", feed.getUri().getLastPathSegment()); JSONArray m = new JSONArray(); for (DbIdentity u : feed.getMembers()) { m.put(new SKUser(u).toJson()); } o.put("members", m); } catch (JSONException e) {} return o; } } interface Jsonable { public JSONObject toJson(); } /** * A parsed "data:base64;f00"-style uri. */ public static class DataUri { public final String mimeType; public final String parameters; public final byte[] data; public DataUri(String dataUri) throws IllegalArgumentException { if (dataUri == null) { throw new NullPointerException(); } if (!dataUri.startsWith("data:")) { throw new IllegalArgumentException("Not a data uri"); } int commaIndex = dataUri.indexOf(','); if (commaIndex == -1) { throw new IllegalArgumentException("DataUri has no data"); } String mediaType = dataUri.substring(5, commaIndex); String content = dataUri.substring(commaIndex + 1); if (mediaType.length() == 0) { mediaType = "text/plain;charset=US-ASCII"; } if (mediaType.endsWith(";base64")) { mediaType = mediaType.substring(0, mediaType.length() - 7); data = Base64.decode(content, Base64.DEFAULT); // strange, URL_SAFE not compatible with html library. } else { data = URLDecoder.decode(content).getBytes(); } int colon = mediaType.indexOf(";"); if (colon != -1) { mimeType = mediaType.substring(0, colon); parameters = mediaType.substring(colon + 1); } else { mimeType = mediaType; parameters = null; } } } }