/*
* 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.List;
import mobisocial.musubi.BJDNotImplementedException;
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.DbObjCache;
import mobisocial.musubi.obj.ObjHelpers;
import mobisocial.musubi.ui.fragments.FeedListFragment.FeedSummary;
import mobisocial.musubi.ui.widget.DbObjCursorAdapter.DbObjCursor;
import mobisocial.musubi.util.CommonLayouts;
import mobisocial.musubi.webapp.WebAppActivity;
import mobisocial.socialkit.musubi.DbObj;
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.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
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;
/**
* Entry point for application sessions. Typically, an AppObj is created by
* Musubi, but "owned" by a third-party application. That application can
* attach application data
*
*/
public class AppObj extends DbEntryHandler implements Activator, FeedRenderer {
private static final String TAG = "musubi-appObj";
private static final boolean DBG = false;
public static final String TYPE = "app";
public static final String ANDROID_PACKAGE_NAME = "__apkg";
public static final String CLAIMED_APP_ID = "__ai";
public static final String APP_NAME = "__an";
public static final String ANDROID_CLASS_NAME = "android_cls";
public static final String ANDROID_ACTION = "android_action";
public static final String EXTRA_OBJ_URI = "mobisocial.db.OBJ_URI";
public static final String EXTRA_FEED_URI = Musubi.EXTRA_FEED_URI;
public static final String EXTRA_APPLICATION_ARGUMENT = "android.intent.extra.APPLICATION_ARGUMENT";
public static final String WEB_URL = "web_url";
private static final String SORT_ORDER =
DbObj.COL_INT_KEY + " desc, " + DbObj.COL_IDENTITY_ID + " desc LIMIT 1";
@Override
public String getType() {
return TYPE;
}
public static long[] contactsFromPicker(Intent pickerResult) {
return pickerResult.getLongArrayExtra("contacts");
}
public static MemObj withDetails(Context context, String action,
ResolveInfo resolveInfo, long[] contactIds) {
String pkgName = resolveInfo.activityInfo.packageName;
String className = resolveInfo.activityInfo.name;
/**
* TODO:
*
* Identity Firewall Goes Here.
* Membership details can be randomized in one of many ways.
* The app (scrabble) may see games a set of gamers play together.
* The app may always see random ids
* The app may always see stable ids
*
* Can also permute the cursor and member order.
*/
JSONArray participantIds = new JSONArray();
BJDNotImplementedException.except(BJDNotImplementedException.MSG_LOCAL_PERSON_ID);
String personId = null;
participantIds.put(personId);
for (long id : contactIds) {
Log.d(TAG, "Launching apps is broken");
}
JSONObject json = new JSONObject();
try {
json.put(Multiplayer.OBJ_MEMBERSHIP, (participantIds));
json.put(ANDROID_ACTION, action);
json.put(ANDROID_PACKAGE_NAME, pkgName);
json.put(ANDROID_CLASS_NAME, className);
} catch (JSONException e) {
Log.d(TAG, "What? Impossible!", e);
}
return new MemObj(TYPE, json);
}
public static MemObj forApp(Context context, ResolveInfo info, String action) {
/**
* TODO:
*
* Identity Firewall Goes Here.
* Membership details can be randomized in one of many ways.
* The app (scrabble) may see games a set of gamers play together.
* The app may always see random ids
* The app may always see stable ids
*
* Can also permute the cursor and member order.
*/
JSONObject json = new JSONObject();
try {
json.put(ANDROID_ACTION, action);
json.put(ANDROID_PACKAGE_NAME, info.activityInfo.packageName);
json.put(ANDROID_CLASS_NAME, info.activityInfo.name);
} catch (JSONException e) {
Log.d(TAG, "What? Impossible!", e);
}
return new MemObj(TYPE, json);
}
@Override
public void activate(Context context, DbObj obj) {
if (!(obj instanceof DbObj)) {
Log.w(TAG, "Obj not ready yet!");
return;
}
if (DBG) {
JSONObject content = obj.getJson();
Log.d(TAG, "activating app " + content + " from " + obj.getHash());
}
Intent launch = getLaunchIntent(context, (DbObj)obj);
if (!(context instanceof Activity)) {
launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(launch);
}
public static Intent getLaunchIntent(Context context, DbObj obj) {
JSONObject content = obj.getJson();
if (content.has(ANDROID_PACKAGE_NAME)) {
Uri appFeed = obj.getContainingFeed().getUri();
String action = content.optString(ANDROID_ACTION);
String pkgName = content.optString(ANDROID_PACKAGE_NAME);
String className = content.optString(ANDROID_CLASS_NAME);
Intent launch = new Intent(action);
launch.setClassName(pkgName, className);
launch.addCategory(Intent.CATEGORY_LAUNCHER);
// TODO: feed for related objs, not parent feed
launch.putExtra(EXTRA_FEED_URI, appFeed);
launch.putExtra(EXTRA_OBJ_URI, obj.getUri());
List<ResolveInfo> resolved = context.getPackageManager().queryIntentActivities(launch, 0);
if (resolved.size() > 0) {
return launch;
}
Intent market = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + pkgName));
return market;
} else if (content.has(WEB_URL)) {
Intent app = new Intent(Intent.ACTION_VIEW);
app.setClass(context, WebAppActivity.class);
app.putExtra(WebAppActivity.EXTRA_APP_URI, Uri.parse(content.optString(WEB_URL)));
app.putExtra(WebAppActivity.EXTRA_APP_ID, obj.getAppId());
app.setData(obj.getUri());
return app;
}
return null;
}
@Override
public View createView(Context context, ViewGroup frame) {
// XXX must be synchronized with ObjHelpers.renderGeneric()
return new LinearLayout(context);
}
@Override
public void render(final Context context, final View view, DbObjCursor obj, boolean allowInteractions) throws Exception {
// XXX must be synchronized with ObjHelpers.renderGeneric()
LinearLayout frame = (LinearLayout) view;
frame.removeAllViews();
PackageManager pm = context.getPackageManager();
Drawable icon = null;
String appName;
if (obj.getJson() != null && obj.getJson().has(ANDROID_PACKAGE_NAME)) {
appName = obj.getJson().optString(ANDROID_PACKAGE_NAME);
} else {
appName = "Unknown";
}
String pkg = obj.appId;
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setPackage(pkg);
List<ResolveInfo> infos = pm.queryIntentActivities(intent, 0);
if (infos != null && infos.size() > 0) {
ResolveInfo info = infos.get(0);
if (info.activityInfo.labelRes != 0) {
appName = info.activityInfo.loadLabel(pm).toString();
icon = info.loadIcon(pm);
} else {
appName = info.activityInfo.name;
}
} else {
appName = obj.getJson().optString(ANDROID_PACKAGE_NAME);
if (appName.contains(".")) {
appName = appName.substring(appName.lastIndexOf(".") + 1);
}
}
// TODO: Safer reference to containing view
if (icon != null) {
View parentView = (View)frame.getParent().getParent().getParent();
ImageView avatar = (ImageView)parentView.findViewById(R.id.icon);
avatar.setImageDrawable(icon);
TextView label = (TextView)parentView.findViewById(R.id.name_text);
label.setText(appName);
}
DbObjCursor childObj = getLatestChild(obj);
if (childObj != null) {
ObjHelpers.getFeedRenderer(childObj.getType()).render(context, frame, childObj,
allowInteractions);
} else {
String text;
if (icon != null) {
ImageView iv = new ImageView(context);
iv.setImageDrawable(icon);
iv.setAdjustViewBounds(true);
iv.setMaxWidth(60);
iv.setMaxHeight(60);
iv.setLayoutParams(CommonLayouts.WRAPPED);
frame.addView(iv);
text = appName;
} else {
text = "New application: " + appName + ".";
}
// TODO: Show Market icon or app icon.
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);
}
}
/**
* @see FeedModifiedObjHandler
*/
private DbObjCursor getLatestChild(DbObjCursor parent) {
SQLiteDatabase db = parent.getDatabaseManager().getDatabase();
Cursor c = null;
String[] columns = new String[] { DbObjCache.LATEST_OBJ };
String selection = DbObjCache.PARENT_OBJ + " = ?";
String[] selectionArgs = new String[] { Long.toString(parent.objId) };
String groupBy = null;
String having = null;
String orderBy = null;
c = db.query(DbObjCache.TABLE, columns, selection, selectionArgs,
groupBy, having, orderBy);
try {
if (c.moveToFirst()) {
long id = c.getLong(0);
return new DbObjCursor(parent.getDatabaseManager(), id);
}
} finally {
c.close();
}
return null;
}
@Override
public void getSummaryText(Context context, TextView view, FeedSummary summary) {
JSONObject obj = summary.getJson();
String appName;
if (obj.has(ANDROID_PACKAGE_NAME)) {
appName = obj.optString(ANDROID_PACKAGE_NAME);
} else {
appName = "Unknown";
}
view.setTypeface(null, Typeface.ITALIC);
view.setText(summary.getSender() + "started a new session for " + appName + "!");
}
}