/* * 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.objects; import java.util.Date; import java.util.List; import mobisocial.crypto.IBHashedIdentity; import mobisocial.musubi.App; import mobisocial.musubi.R; import mobisocial.musubi.feed.iface.Activator; import mobisocial.musubi.feed.iface.DbEntryHandler; import mobisocial.musubi.feed.iface.FeedRenderer; import mobisocial.musubi.model.MFeed; import mobisocial.musubi.model.MIdentity; import mobisocial.musubi.model.MObject; import mobisocial.musubi.model.helpers.IdentitiesManager; import mobisocial.musubi.model.helpers.ObjectManager; import mobisocial.musubi.obj.ObjHelpers; import mobisocial.musubi.provider.MusubiContentProvider; import mobisocial.musubi.service.WebRenderService; import mobisocial.musubi.ui.fragments.FeedListFragment.FeedSummary; import mobisocial.musubi.ui.widget.DbObjCursorAdapter.DbObjCursor; import mobisocial.musubi.util.Util; import mobisocial.socialkit.Obj; import mobisocial.socialkit.musubi.DbObj; 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.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.database.sqlite.SQLiteOpenHelper; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Typeface; import android.net.Uri; import android.util.Base64; import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; /** * A snapshot of an application's state. */ public class AppStateObj extends DbEntryHandler implements FeedRenderer, Activator { private static final String TAG = "AppStateObj"; private static final boolean DBG = false; public static final String TYPE = "appstate"; public static final String ARG = "arg"; public static final String STATE = "state"; public static final String THUMB_RAW_JPG = "__b64jpgthumb"; public static final String THUMB_TEXT = "__text"; public static final String PACKAGE_NAME = "packageName"; public static final String OBJ_INTENT_ACTION = "intentAction"; public static final int MAX_HEIGHT = 225; @Override public String getType() { return TYPE; } public static Obj from(String appId, String type, JSONObject json, byte[] raw, Integer intKey, String strKey) { if (type == null) { type = TYPE; } if (json == null) { json = new JSONObject(); } try { json.put(AppObj.ANDROID_PACKAGE_NAME, appId); } catch (JSONException e) { throw new IllegalStateException("Bad json libary", e); } return new MemObj(type, json, raw, intKey, strKey); } @Override public boolean processObject(Context context, MFeed feed, MIdentity sender, MObject object) { JSONObject json; try { json = new JSONObject(object.json_); } catch (JSONException e) { Log.e(TAG, "Bad app object json", e); return false; } SQLiteOpenHelper helper = App.getDatabaseSource(context); ObjectManager om = new ObjectManager(helper); if (json.has(ObjHelpers.TARGET_HASH)) { String hashA = json.optString(ObjHelpers.TARGET_HASH); byte[] uHash; try { uHash = Util.convertToByteArray(hashA); } catch (Exception e) { Log.e(TAG, "Couldn't convert universal hash"); return true; } long idA = om.getObjectIdForHash(uHash); if (idA == -1) { Log.e(TAG, "No objId found for hash " + hashA); return true; } MObject parent = om.getObjectForId(idA); object.lastModifiedTimestamp_ = new Date().getTime(); om.updateObject(parent); } return true; } @Override public View createView(Context context, ViewGroup frame) { return new LinearLayout(context); } @Override public void render(final Context context, final View view, DbObjCursor obj, boolean allowInteractions) { ViewGroup frame = (LinearLayout)view; frame.removeAllViews(); JSONObject content = obj.getJson(); boolean rendered = false; AppState ref = new AppState(content); byte[] raw = ref.getThumbnailPicture(); if (content.has(THUMB_RAW_JPG) && raw != null) { rendered = true; ImageView imageView = new ImageView(context); imageView.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); //TODO: this is complete BS, pass in thing has a local id which is a perfect hash BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; options.inInputShareable = true; Bitmap b = BitmapFactory.decodeByteArray(raw, 0, raw.length, options); imageView.setImageBitmap(b); frame.addView(imageView); } String thumbnail = ref.getThumbnailText(); if (thumbnail != null) { rendered = true; TextView valueTV = new TextView(context); valueTV.setText(thumbnail); valueTV.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); valueTV.setGravity(Gravity.TOP | Gravity.LEFT); frame.addView(valueTV); } thumbnail = ref.getThumbnailHtml(); if (thumbnail != null) { rendered = true; renderHtml(context, frame, thumbnail); } if (!rendered) { String appName = content.optString(PACKAGE_NAME); if (appName.contains(".")) { appName = appName.substring(appName.lastIndexOf(".") + 1); } String text = "Welcome to " + appName + "!"; TextView valueTV = new TextView(context); valueTV.setText(text); valueTV.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); valueTV.setGravity(Gravity.TOP | Gravity.LEFT); frame.addView(valueTV); } } public static Intent getLaunchIntent(Context context, DbObj obj) { JSONObject content = obj.getJson(); if (DBG) Log.d(TAG, "Getting launch intent for " + content); Uri appFeed = obj.getContainingFeed().getUri(); String appId = obj.getAppId(); // TODO: Hack for deprecated launch method if (appId.equals(MusubiContentProvider.SUPER_APP_ID)) { appId = content.optString(PACKAGE_NAME); } if (DBG) Log.d(TAG, "Preparing launch of " + appId + " on " + appFeed); Intent launch = new Intent(); if (content.has(OBJ_INTENT_ACTION)) { launch.setAction(content.optString(OBJ_INTENT_ACTION)); } else { launch.setAction(Intent.ACTION_MAIN); } launch.addCategory(Intent.CATEGORY_LAUNCHER); launch.putExtra(AppObj.EXTRA_FEED_URI, appFeed); // TODO: optimize! List<ResolveInfo> resolved = context.getPackageManager().queryIntentActivities(launch, 0); for (ResolveInfo r : resolved) { ActivityInfo activity = r.activityInfo; if (activity.packageName.equals(appId)) { launch.setClassName(activity.packageName, activity.name); launch.putExtra("mobisocial.db.PACKAGE", activity.packageName); return launch; } } Intent market = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appId)); return market; } public interface Callback { public void onAppSelected(String pkg, String arg, Intent localLaunch); } @Override public boolean doNotification(Context context, DbObj obj) { JSONObject json = obj.getJson(); if (json == null) { return true; } if (json.has("notify")) { return json.optBoolean("notify"); } if (!json.has("membership")) { return true; } try { IdentitiesManager im = new IdentitiesManager(App.getDatabaseSource(context)); JSONArray arr = json.getJSONArray("membership"); for (int i = 0; i < arr.length(); i++) { try { String personId = arr.getString(i); byte[] personBytes = Util.convertToByteArray(personId); IBHashedIdentity hid = new IBHashedIdentity(personBytes); MIdentity maybeMe = im.getIdentityForIBHashedIdentity(hid); if (maybeMe != null && maybeMe.owned_) { return true; } } catch (Exception e) {} } } catch (JSONException e) {} return false; } @Override public void activate(Context context, DbObj obj) { if (DBG) Log.d(TAG, "activating appstate " + obj.getAppId() + "; hash=" + obj.getHash()); Intent launch = getLaunchIntent(context, obj); if (!(context instanceof Activity)) { launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } context.startActivity(launch); } public static void renderHtml(Context context, ViewGroup frame, String html) { Resources res = context.getResources(); int targetWidth, targetHeight; if (res.getBoolean(R.bool.is_tablet)) { targetWidth = (int)(context.getResources().getDisplayMetrics().widthPixels * 0.333f); targetHeight = (int)(context.getResources().getDisplayMetrics().heightPixels * 0.4f); } else { targetWidth = (int)(context.getResources().getDisplayMetrics().widthPixels * 0.5f); targetHeight = (int)(context.getResources().getDisplayMetrics().heightPixels * 0.333f); } ImageView imageView = WebRenderService.newLazyImageWeb(context, html, targetWidth, targetHeight); frame.addView(imageView); } static class AppState extends MemObj { public static final String EXTRA_APPLICATION_PACKAGE = "mobisocial.db.PACKAGE"; public static final String EXTRA_APPLICATION_STATE = "mobisocial.db.STATE"; public static final String EXTRA_APPLICATION_IMG = "mobisocial.db.THUMBNAIL_IMAGE"; public static final String EXTRA_APPLICATION_TEXT = "mobisocial.db.THUMBNAIL_TEXT"; public AppState(JSONObject json) { super(AppStateObj.TYPE, json); } public String pkg() { return getJson().optString("packageName"); } public String getThumbnailText() { if (getJson().has(AppStateObj.THUMB_TEXT)) { return getJson().optString(AppStateObj.THUMB_TEXT); } return null; } public String getThumbnailHtml() { if (getJson().has(Obj.FIELD_HTML)) { return getJson().optString(Obj.FIELD_HTML); } return null; } public byte[] getThumbnailPicture() { if (getJson().has(AppStateObj.THUMB_RAW_JPG)) { return Base64.decode(getJson().optString(AppStateObj.THUMB_RAW_JPG), Base64.DEFAULT); } return null; } } @Override public void getSummaryText(Context context, TextView view, FeedSummary summary) { JSONObject obj = summary.getJson(); String text = " did something in an app!"; if (obj.has(THUMB_TEXT)) { text = " did something in " + obj.optString(THUMB_TEXT + "!"); } String appName = obj.optString(PACKAGE_NAME); if (appName.contains(".")) { text = " did something in " + appName.substring(appName.lastIndexOf(".") + 1) +"!"; } view.setTypeface(null, Typeface.ITALIC); view.setText(summary.getSender() + text); } }