/*
* 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.provider;
import java.util.Date;
import java.util.List;
import mobisocial.musubi.feed.iface.DbEntryHandler;
import mobisocial.musubi.feed.iface.FeedRenderer;
import mobisocial.musubi.model.DbRelation;
import mobisocial.musubi.model.MApp;
import mobisocial.musubi.model.MDevice;
import mobisocial.musubi.model.MFact;
import mobisocial.musubi.model.MFeed;
import mobisocial.musubi.model.MFeedApp;
import mobisocial.musubi.model.MFeedMember;
import mobisocial.musubi.model.MIdentity;
import mobisocial.musubi.model.MObject;
import mobisocial.musubi.model.SKFeedMembers;
import mobisocial.musubi.model.SKIdentities;
import mobisocial.musubi.model.SKObjects;
import mobisocial.musubi.model.helpers.DatabaseFile;
import mobisocial.musubi.model.helpers.DatabaseManager;
import mobisocial.musubi.model.helpers.DeviceManager;
import mobisocial.musubi.model.helpers.SQLClauseHelper;
import mobisocial.musubi.obj.ObjHelpers;
import mobisocial.musubi.objects.AppObj;
import mobisocial.musubi.objects.PictureObj;
import mobisocial.musubi.service.MusubiService;
import mobisocial.musubi.util.UriImage;
import mobisocial.socialkit.Obj;
import mobisocial.socialkit.musubi.DbObj;
import org.json.JSONException;
import org.json.JSONObject;
import org.mobisocial.corral.ContentCorral;
import org.mobisocial.corral.CorralDownloadClient;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.util.Log;
/**
* Manages Musubi's social database, providing access to third-party
* applications with access control.
*/
public class MusubiContentProvider extends ContentProvider {
static final String TAG = "MusubiContentProvider";
public static final String SUPER_APP_ID = "mobisocial.musubi";
public static final String UNKNOWN_APP_ID = "mobisocial.unknown.app";
public static final String AUTHORITY = "org.musubi.db";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
static final boolean DBG = false;
private DatabaseManager mDatabaseManager;
private static MusubiContentProvider sInstance;
private DbInsertionThread mDbInsertionThread;
static MusubiContentProvider getInstance() {
return sInstance; // constructed by framework
}
private DatabaseManager getDatabaseManager() {
if (mDatabaseManager == null) {
mDatabaseManager = new DatabaseManager(getContext());
}
return mDatabaseManager;
}
public MusubiContentProvider() {
sInstance = this;
}
public DbInsertionThread getDbInsertionThread() {
if (mDbInsertionThread == null) {
mDbInsertionThread = new DbInsertionThread();
mDbInsertionThread.start();
}
return mDbInsertionThread;
}
/**
* Types that can be queried in this Content Provider.
*
* Other "pseudo-providers":
* subfeeds-- use the parent_id field of objects
* feed_members-- we auto-join on feeds._id if a query includes a feed_id parameter
*/
public enum Provided {
OBJECTS, OBJS_ID, FEEDS, FEEDS_ID, IDENTITIES, IDENTITIES_ID, FACTS, FEED_MEMBERS_ID;
@Override
public String toString() {
switch (this) {
case OBJECTS:
case OBJS_ID:
return MObject.TABLE;
case FEEDS:
case FEEDS_ID:
return MFeed.TABLE;
case IDENTITIES:
case IDENTITIES_ID:
return MIdentity.TABLE;
case FACTS:
return MFact.TABLE;
case FEED_MEMBERS_ID:
return MFeedMember.TABLE;
default: return null;
}
}
};
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, "objects", Provided.OBJECTS.ordinal());
sUriMatcher.addURI(AUTHORITY, "objects/#", Provided.OBJS_ID.ordinal());
sUriMatcher.addURI(AUTHORITY, "feeds", Provided.FEEDS.ordinal());
sUriMatcher.addURI(AUTHORITY, "feeds/#", Provided.FEEDS_ID.ordinal()); // allow negative id
sUriMatcher.addURI(AUTHORITY, "identities", Provided.IDENTITIES.ordinal());
sUriMatcher.addURI(AUTHORITY, "identities/#", Provided.IDENTITIES_ID.ordinal());
sUriMatcher.addURI(AUTHORITY, "feed_members/#", Provided.FEED_MEMBERS_ID.ordinal());
sUriMatcher.addURI(AUTHORITY, "facts", Provided.FACTS.ordinal());
}
public static Uri createUri(String encodedPath) {
return CONTENT_URI.buildUpon().appendEncodedPath(encodedPath).build();
}
public static Uri uriForDir(Provided type) {
String path = type.toString();
if (path == null) {
throw new IllegalArgumentException("Can't get dir for provided type " + type);
}
return CONTENT_URI.buildUpon().appendEncodedPath(path).build();
}
public static Uri uriForItem(Provided type, long id) {
String dir = type.toString();
if (dir == null) {
throw new IllegalArgumentException("Can't look up item for provided type " + type);
}
return CONTENT_URI.buildUpon().appendEncodedPath(dir).appendEncodedPath(Long.toString(id))
.build();
}
@Override
public String getType(Uri uri) {
int match = sUriMatcher.match(uri);
if (match == UriMatcher.NO_MATCH) {
return null;
}
return getType(Provided.values()[match]);
}
public static String getType(Provided provided) {
switch (provided) {
case OBJECTS:
return "vnd.android.cursor.dir/vnd.mobisocial.obj";
case OBJS_ID:
return "vnd.android.cursor.item/vnd.mobisocial.obj";
case FEEDS:
return "vnd.android.cursor.dir/vnd.mobisocial.feed";
case FEEDS_ID:
return "vnd.android.cursor.item/vnd.mobisocial.feed";
case IDENTITIES:
return "vnd.android.cursor.dir/vnd.mobisocial.identity";
case IDENTITIES_ID:
return "vnd.android.cursor.item/vnd.mobisocial.identity";
case FEED_MEMBERS_ID:
return "vnd.android.cursor.item/vnd.mobisocial.membership";
case FACTS:
return "vnd.android.cursor.dir/vnd.mobisocial.fact";
default:
throw new IllegalStateException("Unmatched-but-known content type");
}
}
@Override
public boolean onCreate() {
// restoreDatabase();
Log.i(TAG, "Creating MusubiContentProvider");
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
ContentResolver resolver = getContext().getContentResolver();
final String realAppId = getCallingActivityId();
if (realAppId == null) {
Log.d(TAG, "No AppId for calling activity. Ignoring query.");
return null;
}
if (DBG) {
Log.d(TAG, "Processing query: " + selection + " on " + uri + " from appId " + realAppId);
}
int match = sUriMatcher.match(uri);
if (match == UriMatcher.NO_MATCH) {
Log.e(TAG, "Unmatched uri " + uri);
return null;
}
selection = selectionFromUri(uri, selection);
Cursor result = null;
SQLiteDatabase db = getDatabaseManager().getDatabase();
switch (Provided.values()[match]) {
case FEEDS:
if (!isSuperApp(realAppId)) {
return null;
}
result = db.query(MFeed.TABLE, projection, selection, selectionArgs,
null, null, sortOrder, null);
break;
case FEEDS_ID:
throw new IllegalArgumentException("You probably want /objects?feed_id=<id>");
case IDENTITIES:
if (projection == null) {
projection = SKIdentities.getViewColumns();
}
if (!isSuperApp(realAppId)) {
MApp app = getDatabaseManager().getAppManager().ensureApp(realAppId);
String idSubSelection = "SELECT " + MObject.COL_IDENTITY_ID + " FROM "
+ MObject.TABLE + " WHERE " + MObject.COL_APP_ID + " = " + app.id_;
selection = SQLClauseHelper.andClauses(selection, SKIdentities.COL_ID +
" in (" + idSubSelection + ")");
}
result = new IdentityCursorWrapper(getContext(), db.query(SKIdentities.TABLE, projection, selection,
selectionArgs, null, null, sortOrder, null));
break;
case IDENTITIES_ID:
if (projection == null) {
projection = SKIdentities.getViewColumns();
}
Long idId = Long.parseLong(uri.getLastPathSegment());
if (!appAllowedForIdentity(realAppId, idId)) {
return new MatrixCursor(projection);
}
selection = SQLClauseHelper.andClauses(selection, SKIdentities.COL_ID + "= ?");
selectionArgs = SQLClauseHelper.andArguments(selectionArgs, idId);
// IdentityCursorWrapper requires identity_id to be the last column.
projection = SQLClauseHelper.andArguments(projection, SKIdentities.COL_ID);
result = new IdentityCursorWrapper(getContext(), db.query(SKIdentities.TABLE, projection, selection,
selectionArgs, null, null, sortOrder, null));
break;
case OBJECTS:
// Currently, apps are only allowed to see data from their own authority.
if (!isSuperApp(realAppId)) {
selection = SQLClauseHelper.andClauses(selection, MObject.COL_APP_ID + " = ?");
selectionArgs = SQLClauseHelper.andArguments(selectionArgs, realAppId);
}
result = db.query(SKObjects.TABLE, projection, selection,
selectionArgs, null, null, sortOrder);
break;
case OBJS_ID:
if (!isSuperApp(realAppId)) {
selection = SQLClauseHelper.andClauses(selection, MObject.COL_APP_ID + " = ?");
selectionArgs = SQLClauseHelper.andArguments(selectionArgs, realAppId);
}
// objects by database id
Long objId = Long.parseLong(uri.getLastPathSegment());
selection = SQLClauseHelper.andClauses(selection, MObject.COL_ID + " = ?");
selectionArgs = SQLClauseHelper.andArguments(selectionArgs, new String[] {
Long.toString(objId)
});
result = db.query(MObject.TABLE, projection, selection,
selectionArgs, null, null, sortOrder);
break;
case FEED_MEMBERS_ID:
if (projection == null) {
projection = SKFeedMembers.getViewColumns();
}
Long feedId = Long.parseLong(uri.getLastPathSegment());
if (!appAllowedForFeed(realAppId, feedId)) {
Log.w(TAG, "access denied");
return new MatrixCursor(projection);
}
// IdentityCursorWrapper requires identity_id to be the last column.
projection = SQLClauseHelper.andArguments(projection, SKFeedMembers.COL_IDENTITY_ID);
// Restrict query to the given feed id
selection = SQLClauseHelper.andClauses(selection,
SKFeedMembers.TABLE + "." + SKFeedMembers.COL_FEED_ID + " = ?");
selectionArgs = SQLClauseHelper.andArguments(selectionArgs,
new String[] { Long.toString(feedId) });
// Wrap the cursor so we can return a "safe" name and thumbnail
result = new IdentityCursorWrapper(getContext(), db.query(SKFeedMembers.TABLE,
projection, selection, selectionArgs, null, null, sortOrder));
break;
case FACTS:
if (!SUPER_APP_ID.equals(realAppId)) {
String selection2 = MFact.COL_APP_ID + " in "
+ SQLClauseHelper.appOrUnknown(realAppId);
selection = SQLClauseHelper.andClauses(selection, selection2);
}
result = db.query(MFact.TABLE, projection, selection,
selectionArgs, null, null, sortOrder);
break;
}
if (result != null) {
result.setNotificationUri(resolver, uri);
} else {
Log.w(TAG, "Unrecognized query: " + uri);
}
return result;
}
/**
* Inserts a message locally that has been received from some agent,
* typically from a remote device.
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
if (DBG)
Log.i(TAG, "Insert called on " + uri + ", " + values);
final String appId = getCallingActivityId();
if (appId == null) {
Log.d(TAG, "No AppId for calling activity. Ignoring query.");
return null;
}
int match = sUriMatcher.match(uri);
if (Provided.values()[match] != Provided.OBJECTS) {
Log.e(TAG, "Unsupported insert.");
return null;
}
Uri result = insertObjWithContentValues(appId, values);
return result;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
final String appId = getCallingActivityId();
if (appId == null) {
Log.d(TAG, "No AppId for calling activity. Ignoring query.");
return 0;
}
if (!isSuperApp(appId)) {
throw new RuntimeException("Operation not yet supported");
}
int match = sUriMatcher.match(uri);
if (match == UriMatcher.NO_MATCH) {
Log.e(TAG, "Unmatched uri " + uri);
return -1;
}
return 0;
}
String selectionFromUri(Uri uri, String base) {
if (uri.getQuery() == null) return base;
return SQLClauseHelper.andClauses(base, uri.getQuery().replace("&", " AND "));
}
/**
* Modifies the given values to reflect the relations
*/
private static void prepareObjRelations(SQLiteDatabase db, ContentValues values) throws DbInsertionError {
Long parentId = values.getAsLong(DbObj.COL_PARENT_ID);
if (parentId == null) {
return;
}
String table = DbObj.TABLE;
String[] columns = new String[] {
DbObj.COL_UNIVERSAL_HASH
};
String selection = DbObj.COL_ID + " = ?";
String[] selectionArgs = new String[] {
Long.toString(parentId)
};
String groupBy = null, having = null, orderBy = null;
Cursor c = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
if (!c.moveToFirst()) {
throw new DbInsertionError("Could not find parent obj " + parentId);
}
byte[] parentObjHash = c.getBlob(0);
if (parentObjHash == null) {
throw new DbInsertionError("Parent hash not available");
}
try {
JSONObject json;
if (values.containsKey(DbObj.COL_JSON)) {
json = new JSONObject(values.getAsString(MObject.COL_JSON));
} else {
json = new JSONObject();
}
json.put(ObjHelpers.TARGET_HASH, ObjHelpers.hashToString(parentObjHash));
json.put(ObjHelpers.TARGET_RELATION, DbRelation.RELATION_PARENT);
values.put(DbObj.COL_JSON, json.toString());
} catch (JSONException e) {
throw new DbInsertionError("Error parsing json", e);
}
}
/**
* Validates the content values and returns a row for insertion.
*/
private MObject rowForContentValues(String realAppId, ContentValues values) throws DbInsertionError {
long timestamp = new Date().getTime();
Long feedId = values.getAsLong(DbObj.COL_FEED_ID);
if (feedId == null) {
throw new DbInsertionError("Feed id required");
}
if (!appAllowedForFeed(realAppId, feedId)) {
throw new DbInsertionError("App not allowed in feed");
}
Long identityId = getDatabaseManager().getFeedManager().getOwnedIdentityForFeed(feedId);
if (identityId == null) {
throw new DbInsertionError("No owned id for this feed.");
}
DeviceManager dm = getDatabaseManager().getDeviceManager();
long deviceName = dm.getLocalDeviceName();
MDevice device = dm.getDeviceForName(identityId, deviceName);
if (device == null) {
throw new DbInsertionError("No device found for id " + identityId);
}
String type = values.getAsString(MObject.COL_TYPE);
if (type == null) {
throw new DbInsertionError("Type is required");
}
Long parentId = null;
if (values.containsKey(DbObj.COL_PARENT_ID)) {
parentId = values.getAsLong(DbObj.COL_PARENT_ID);
if (parentId == null) {
throw new DbInsertionError("Bad format for parent_id");
}
}
String jsonSrc = null;
JSONObject json = null;
if (values.containsKey(DbObj.COL_JSON)) {
jsonSrc = values.getAsString(DbObj.COL_JSON);
if (jsonSrc.length() > DatabaseFile.SIZE_LIMIT) {
throw new DbInsertionError("Message json size too large for sending");
}
try {
json = new JSONObject(jsonSrc);
} catch (JSONException e) {
throw new DbInsertionError("Could not parse json", e);
}
}
String appId = realAppId;
if (isSuperApp(realAppId)) {
if (json != null && json.has(AppObj.CLAIMED_APP_ID)) {
appId = json.optString(AppObj.CLAIMED_APP_ID);
json.remove(AppObj.CLAIMED_APP_ID);
if (appId == null || appId.length() == 0) {
Log.e(TAG, "Bad app listed. Reverting to real app id.");
appId = realAppId;
}
} else if (values.containsKey(ObjHelpers.CALLER_APP_ID)) {
appId = values.getAsString(ObjHelpers.CALLER_APP_ID);
}
}
Integer intKey = null;
if (values.containsKey(DbObj.COL_INT_KEY)) {
intKey = values.getAsInteger(DbObj.COL_INT_KEY);
if (intKey == null) {
throw new DbInsertionError("Bad format for int field");
}
}
String name = null;
if (values.containsKey(DbObj.COL_STRING_KEY)) {
name = values.getAsString(DbObj.COL_STRING_KEY);
if (name == null) {
throw new DbInsertionError("Bad format for name field");
}
if (name.length() > DatabaseFile.SIZE_LIMIT) {
throw new DbInsertionError("Message name too large for sending");
}
}
byte[] raw = null;
if (values.containsKey(DbObj.COL_RAW)) {
raw = values.getAsByteArray(DbObj.COL_RAW);
if (raw == null) {
throw new DbInsertionError("Bad format for raw field");
}
// XXX this seems like a horrible place for this.
//scale down pictures that come in too large
//this also stored them in the corral
//if its already in the corral, don't rescale because we already
//compressed this, and we don't really want another copy
if(type.equals(PictureObj.TYPE) && json != null && !json.has("localUri")) {
byte[] new_raw = handleDownscalePicture(raw, json);
if(raw != new_raw) {
jsonSrc = json.toString();
}
raw = new_raw;
}
if (raw.length > DatabaseFile.SIZE_LIMIT) {
throw new DbInsertionError("Messasge raw size too large for sending");
}
}
boolean renderable = false;
DbEntryHandler h = ObjHelpers.forType(type);
if (h instanceof FeedRenderer) {
renderable = true;
} else {
if (json != null && json.has(Obj.FIELD_HTML)) {
renderable = true;
}
}
MApp app = getDatabaseManager().getAppManager().ensureApp(appId);
MObject o = new MObject();
o.feedId_ = feedId;
o.identityId_ = identityId;
o.deviceId_ = device.id_;
o.parentId_ = parentId;
o.appId_ = app.id_;
o.timestamp_ = timestamp;
o.type_ = type;
o.stringKey_ = name;
o.json_ = jsonSrc;
o.raw_ = raw;
o.intKey_ = intKey;
o.lastModifiedTimestamp_ = timestamp;
o.processed_ = false;
o.renderable_ = renderable;
return o;
}
public static void insertInBackground(Obj obj, Uri feedUri, String assumedAppId) {
final ContentValues values = DbObj.toContentValues(feedUri, null, obj);
if (assumedAppId != null) {
values.put(ObjHelpers.CALLER_APP_ID, assumedAppId);
}
Handler insertHandler = getInstance().getDbInsertionThread().getHandler();
Message msg = insertHandler.obtainMessage();
msg.what = DbInsertionThread.INSERT;
msg.obj = values;
insertHandler.sendMessage(msg);
}
private byte[] handleDownscalePicture(byte[] raw, JSONObject json) {
//TODO: corral storage directly from within the web app
Uri corralUri = ContentCorral.storeContent(getContext(), raw, CorralDownloadClient.typeForBytes(raw, PictureObj.TYPE));
if(corralUri == null)
return raw;
try {
byte[] new_raw = new UriImage(getContext(), corralUri).getResizedImageData(PictureObj.MAX_IMAGE_WIDTH, PictureObj.MAX_IMAGE_HEIGHT, PictureObj.MAX_IMAGE_SIZE);
json.put("localUri", corralUri);
return new_raw;
} catch (Throwable t) {
return raw;
}
}
String getCallingActivityId() {
int pid = Binder.getCallingPid();
if (pid == Process.myPid()) {
return SUPER_APP_ID;
}
ActivityManager am = (ActivityManager) getContext().getSystemService(
Activity.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> lstAppInfo = am.getRunningAppProcesses();
for (ActivityManager.RunningAppProcessInfo ai : lstAppInfo) {
if (ai.pid == pid) {
return ai.processName;
}
}
Log.d(TAG, "Missing app id for pid " + pid);
Log.d(TAG, "Local pid " + Process.myPid());
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
final String appId = getCallingActivityId();
if (appId == null) {
Log.d(TAG, "No AppId for calling activity. Ignoring query.");
return 0;
}
int match = sUriMatcher.match(uri);
if (match == UriMatcher.NO_MATCH) {
Log.d(TAG, "Unmatched uri " + uri);
return -1;
}
switch (Provided.values()[match]) {
case OBJS_ID:
return -111;
default:
return 0;
}
}
public static boolean isSuperApp(String appId) {
return SUPER_APP_ID.equals(appId);
}
boolean appAllowedForIdentity(String appId, long identityId) {
/**
* SELECT
* FROM objects, apps, feed_members
* WHERE
* fm.identity_id = ?
* and objects.feed_id = fm.feed_Id
* and objects.app_id = apps._id
* and apps.app_id = ?
*/
if (SUPER_APP_ID.equals(appId)) {
return true;
}
StringBuilder sql = new StringBuilder(100)
.append("SELECT ").append(MApp.TABLE).append(".").append(MApp.COL_APP_ID)
.append(" FROM ").append(MFeedMember.TABLE).append(",")
.append(MObject.TABLE).append(",").append(MApp.TABLE)
.append(" WHERE ").append(MFeedMember.TABLE).append(".").append(MFeedMember.COL_FEED_ID)
.append(" = ").append(MObject.TABLE).append(".").append(MObject.COL_FEED_ID)
.append(" AND ").append(MFeedMember.TABLE).append(".").append(MFeedMember.COL_IDENTITY_ID)
.append(" = ? AND ").append(MObject.TABLE).append(".").append(MObject.COL_APP_ID)
.append(" = ").append(MApp.TABLE).append(".").append(MApp.COL_ID)
.append(" AND ").append(MApp.TABLE).append(".").append(MApp.COL_APP_ID).append("=?")
.append(" LIMIT 1");
SQLiteDatabase db = getDatabaseManager().getDatabase();
String[] selectionArgs = new String[] { Long.toString(identityId), appId };
Cursor c = db.rawQuery(sql.toString(), selectionArgs);
try {
return c.moveToFirst();
} finally {
c.close();
}
}
boolean appAllowedForFeed(String appId, long feedId) {
if (SUPER_APP_ID.equals(appId)) {
return true;
}
StringBuilder sql = new StringBuilder(100)
.append("SELECT ").append(MFeedApp.COL_ID).append(" FROM ").append(MFeedApp.TABLE)
.append(" WHERE ").append(MFeedApp.COL_FEED_ID).append("=? AND ")
.append(MFeedApp.COL_APP_ID).append("=? LIMIT 1");
SQLiteDatabase db = getDatabaseManager().getDatabase();
MApp app = getDatabaseManager().getAppManager().ensureApp(appId);
String[] selectionArgs = new String[] { Long.toString(feedId), Long.toString(app.id_) };
Cursor c = db.rawQuery(sql.toString(), selectionArgs);
try {
return c.moveToFirst();
} finally {
c.close();
}
}
static class DbInsertionError extends Exception {
private static final long serialVersionUID = 1972051851209225969L;
public DbInsertionError(String msg) {
super(msg);
}
public DbInsertionError(String msg, Throwable cause) {
super(msg, cause);
}
}
Uri insertObjWithContentValues(String parentAppId, ContentValues values) {
MObject object;
try {
prepareObjRelations(getDatabaseManager().getDatabase(), values);
object = rowForContentValues(parentAppId, values);
} catch (DbInsertionError e) {
if (DBG) Log.e(TAG, "DbInsertionError", e);
return null;
}
try {
getDatabaseManager().getObjectManager().insertObject(object);
} catch (Throwable e) {
Log.e(TAG, "Error inserting object", e);
return null;
}
Uri result = uriForItem(Provided.OBJS_ID, object.id_);
ContentResolver resolver = getContext().getContentResolver();
if (result != null) {
if (DBG) Log.d(TAG, "just inserted " + values.getAsString(MObject.COL_JSON));
resolver.notifyChange(MusubiService.PLAIN_OBJ_READY, null);
resolver.notifyChange(result, null);
}
return result;
}
class DbInsertionThread extends Thread {
public static final int INSERT = 0;
private Handler mHandler;
public DbInsertionThread() {
setName("DbInsertionThread");
}
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case INSERT:
ContentValues cv = (ContentValues)msg.obj;
insertObjWithContentValues(SUPER_APP_ID, cv);
}
}
};
synchronized (this) {
notify();
}
Looper.loop();
}
public Handler getHandler() {
if (mHandler == null) {
synchronized (this) {
while (mHandler == null) {
try {
wait();
} catch (InterruptedException e) {}
}
}
}
return mHandler;
}
}
}