edu.stanford.mobisocial.dungbeetle.DungBeetleContentProvider.java Source code

Java tutorial

Introduction

Here is the source code for edu.stanford.mobisocial.dungbeetle.DungBeetleContentProvider.java

Source

/*
 * 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 java.security.interfaces.RSAPublicKey;
import java.util.List;

import mobisocial.socialkit.musubi.DbObj;
import mobisocial.socialkit.musubi.RSACrypto;

import org.json.JSONException;
import org.json.JSONObject;

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.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Binder;
import android.util.Log;
import edu.stanford.mobisocial.dungbeetle.feed.DbObjects;
import edu.stanford.mobisocial.dungbeetle.feed.objects.AppObj;
import edu.stanford.mobisocial.dungbeetle.feed.objects.DeleteObj;
import edu.stanford.mobisocial.dungbeetle.feed.objects.InviteToGroupObj;
import edu.stanford.mobisocial.dungbeetle.group_providers.GroupProviders;
import edu.stanford.mobisocial.dungbeetle.model.Contact;
import edu.stanford.mobisocial.dungbeetle.model.DbObject;
import edu.stanford.mobisocial.dungbeetle.model.DbRelation;
import edu.stanford.mobisocial.dungbeetle.model.Feed;
import edu.stanford.mobisocial.dungbeetle.model.Group;
import edu.stanford.mobisocial.dungbeetle.model.GroupMember;
import edu.stanford.mobisocial.dungbeetle.model.Subscriber;
import edu.stanford.mobisocial.dungbeetle.util.Maybe;

/**
 * Manages Musubi's social database, providing access to third-party
 * applications with access control.
 */
public class DungBeetleContentProvider extends ContentProvider {
    public static final String AUTHORITY = "org.mobisocial.db";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
    static final String TAG = "DungBeetleContentProvider";
    static final boolean DBG = true;
    public static final String SUPER_APP_ID = "edu.stanford.mobisocial.dungbeetle";
    private DBHelper mHelper;
    private IdentityProvider mIdent;

    private static final int FEEDS = 1;
    private static final int FEEDS_ID = 2;
    private static final int OBJS = 3;
    private static final int OBJS_ID = 4;

    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        sUriMatcher.addURI(AUTHORITY, "feeds", FEEDS);
        sUriMatcher.addURI(AUTHORITY, "feeds/*", FEEDS_ID);
        sUriMatcher.addURI(AUTHORITY, "obj", OBJS);
        sUriMatcher.addURI(AUTHORITY, "obj/*", OBJS_ID);
    }

    public DungBeetleContentProvider() {
    }

    @Override
    protected void finalize() throws Throwable {
        mHelper.close();
    }

    @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;
        }
        String appSelection = DbObject.APP_ID + "= ?";
        String[] appSelectionArgs = new String[] { appId };
        selection = DBHelper.andClauses(selection, appSelection);
        selectionArgs = DBHelper.andArguments(selectionArgs, appSelectionArgs);
        String[] projection = new String[] { DbObject.HASH };

        int count = 0;
        Cursor c = mHelper.getReadableDatabase().query(DbObject.TABLE, projection, selection, selectionArgs, null,
                null, null);
        if (c != null && c.moveToFirst()) {
            count = c.getCount();
            long[] hashes = new long[count];
            int i = 0;
            do {
                hashes[i++] = c.getLong(0);
            } while (c.moveToNext());
            Helpers.sendToFeed(getContext(), DeleteObj.from(hashes, true), uri);
        }
        return count;
    }

    @Override
    public String getType(Uri uri) {
        int match = sUriMatcher.match(uri);
        if (match == UriMatcher.NO_MATCH) {
            return null;
        }
        switch (match) {
        case OBJS:
            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";
        }
        throw new IllegalStateException("Unmatched-but-known content type");
    }

    private Uri uriWithId(Uri uri, long id) {
        Uri.Builder b = uri.buildUpon();
        b.appendPath(String.valueOf(id));
        return b.build();
    }

    /**
     * Inserts a message locally that has been received from some agent,
     * typically from a remote device.
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        ContentResolver resolver = getContext().getContentResolver();
        if (DBG)
            Log.i(TAG, "Inserting at uri: " + uri + ", " + values);

        final String appId = getCallingActivityId();
        if (appId == null) {
            Log.d(TAG, "No AppId for calling activity. Ignoring query.");
            return null;
        }

        List<String> segs = uri.getPathSegments();
        if (match(uri, "feeds", "me")) {
            if (!appId.equals(SUPER_APP_ID)) {
                return null;
            }

            long objId = mHelper.addToFeed(appId, "friend", values);
            Uri objUri = DbObject.uriForObj(objId);
            resolver.notifyChange(Feed.uriForName("me"), null);
            resolver.notifyChange(Feed.uriForName("friend"), null);
            resolver.notifyChange(objUri, null);
            return objUri;
        } else if (match(uri, "feeds", ".+")) {
            String feedName = segs.get(1);
            String type = values.getAsString(DbObject.TYPE);
            try {
                JSONObject json = new JSONObject(values.getAsString(DbObject.JSON));
                String objHash = null;
                if (feedName.contains(":")) {
                    String[] parts = feedName.split(":");
                    feedName = parts[0];
                    objHash = parts[1];
                }
                if (objHash != null) {
                    json.put(DbObjects.TARGET_HASH, Long.parseLong(objHash));
                    json.put(DbObjects.TARGET_RELATION, DbRelation.RELATION_PARENT);
                    values.put(DbObject.JSON, json.toString());
                }

                String appAuthority = appId;
                if (SUPER_APP_ID.equals(appId)) {
                    if (AppObj.TYPE.equals(type)) {
                        if (json.has(AppObj.ANDROID_PACKAGE_NAME)) {
                            appAuthority = json.getString(AppObj.ANDROID_PACKAGE_NAME);
                        }
                    }
                }

                long objId = mHelper.addToFeed(appAuthority, feedName, values);
                Uri objUri = DbObject.uriForObj(objId);
                resolver.notifyChange(objUri, null);
                notifyDependencies(mHelper, resolver, segs.get(1));
                if (DBG)
                    Log.d(TAG, "just inserted " + values.getAsString(DbObject.JSON));
                return objUri;
            } catch (JSONException e) {
                return null;
            }
        } else if (match(uri, "out")) {
            try {
                JSONObject obj = new JSONObject(values.getAsString("json"));
                long objId = mHelper.addToOutgoing(appId, values.getAsString(DbObject.DESTINATION),
                        values.getAsString(DbObject.TYPE), obj);
                resolver.notifyChange(Uri.parse(CONTENT_URI + "/out"), null);
                return DbObject.uriForObj(objId);
            } catch (JSONException e) {
                return null;
            }
        } else if (match(uri, "contacts")) {
            if (!appId.equals(SUPER_APP_ID)) {
                return null;
            }
            long id = mHelper.insertContact(values);
            resolver.notifyChange(Uri.parse(CONTENT_URI + "/contacts"), null);
            return uriWithId(uri, id);
        } else if (match(uri, "subscribers")) {
            // Question: Should this be restricted?
            // if(!appId.equals(SUPER_APP_ID)) return null;
            long id = mHelper.insertSubscriber(values);
            resolver.notifyChange(Uri.parse(CONTENT_URI + "/subscribers"), null);
            return uriWithId(uri, id);
        } else if (match(uri, "groups")) {
            if (!appId.equals(SUPER_APP_ID))
                return null;
            long id = mHelper.insertGroup(values);
            getContext().getContentResolver().notifyChange(Uri.parse(CONTENT_URI + "/groups"), null);
            return uriWithId(uri, id);
        } else if (match(uri, "group_members")) {
            if (!appId.equals(SUPER_APP_ID)) {
                return null;
            }
            long id = mHelper.insertGroupMember(values);
            getContext().getContentResolver().notifyChange(Uri.parse(CONTENT_URI + "/group_members"), null);
            getContext().getContentResolver().notifyChange(Uri.parse(CONTENT_URI + "/group_contacts"), null);
            return uriWithId(uri, id);
        }

        else if (match(uri, "group_invitations")) {
            if (!appId.equals(SUPER_APP_ID)) {
                return null;
            }
            String groupName = values.getAsString(InviteToGroupObj.GROUP_NAME);
            Uri dynUpdateUri = Uri.parse(values.getAsString(InviteToGroupObj.DYN_UPDATE_URI));
            long gid = values.getAsLong("groupId");
            SQLiteDatabase db = mHelper.getWritableDatabase();
            mHelper.addToOutgoing(db, appId, values.getAsString(InviteToGroupObj.PARTICIPANTS),
                    InviteToGroupObj.TYPE, InviteToGroupObj.json(groupName, dynUpdateUri));
            getContext().getContentResolver().notifyChange(Uri.parse(CONTENT_URI + "/out"), null);
            return uriWithId(uri, gid);
        }

        else if (match(uri, "dynamic_groups")) {
            if (!appId.equals(SUPER_APP_ID)) {
                return null;
            }
            Uri gUri = Uri.parse(values.getAsString("uri"));
            GroupProviders.GroupProvider gp = GroupProviders.forUri(gUri);
            String feedName = gp.feedName(gUri);
            Maybe<Group> mg = mHelper.groupByFeedName(feedName);
            long id = -1;
            try {
                Group g = mg.get();
                id = g.id;
            } catch (Maybe.NoValError e) {
                ContentValues cv = new ContentValues();
                cv.put(Group.NAME, gp.groupName(gUri));
                cv.put(Group.FEED_NAME, feedName);
                cv.put(Group.DYN_UPDATE_URI, gUri.toString());

                String table = DbObject.TABLE;
                String[] columns = new String[] { DbObject.FEED_NAME };
                String selection = DbObject.CHILD_FEED_NAME + " = ?";
                String[] selectionArgs = new String[] { feedName };
                Cursor parent = mHelper.getReadableDatabase().query(table, columns, selection, selectionArgs, null,
                        null, null);
                try {
                    if (parent.moveToFirst()) {
                        String parentName = parent.getString(0);
                        table = Group.TABLE;
                        columns = new String[] { Group._ID };
                        selection = Group.FEED_NAME + " = ?";
                        selectionArgs = new String[] { parentName };

                        Cursor parent2 = mHelper.getReadableDatabase().query(table, columns, selection,
                                selectionArgs, null, null, null);
                        try {
                            if (parent2.moveToFirst()) {
                                cv.put(Group.PARENT_FEED_ID, parent2.getLong(0));
                            } else {
                                Log.e(TAG, "Parent feed found but no id for " + parentName);
                            }
                        } finally {
                            parent2.close();
                        }
                    } else {
                        Log.w(TAG, "No parent feed for " + feedName);
                    }
                } finally {
                    parent.close();
                }
                id = mHelper.insertGroup(cv);
                getContext().getContentResolver().notifyChange(Uri.parse(CONTENT_URI + "/dynamic_groups"), null);
                getContext().getContentResolver().notifyChange(Uri.parse(CONTENT_URI + "/groups"), null);
            }
            return uriWithId(uri, id);
        }

        else if (match(uri, "dynamic_group_member")) {
            if (!appId.equals(SUPER_APP_ID)) {
                return null;
            }
            SQLiteDatabase db = mHelper.getWritableDatabase();
            db.beginTransaction();
            try {
                ContentValues cv = new ContentValues();
                String pubKeyStr = values.getAsString(Contact.PUBLIC_KEY);
                RSAPublicKey k = RSACrypto.publicKeyFromString(pubKeyStr);
                String personId = mIdent.personIdForPublicKey(k);
                if (!personId.equals(mIdent.userPersonId())) {
                    cv.put(Contact.PUBLIC_KEY, values.getAsString(Contact.PUBLIC_KEY));
                    cv.put(Contact.NAME, values.getAsString(Contact.NAME));
                    cv.put(Contact.EMAIL, values.getAsString(Contact.EMAIL));
                    if (values.getAsString(Contact.PICTURE) != null) {
                        cv.put(Contact.PICTURE, values.getAsByteArray(Contact.PICTURE));
                    }

                    long cid = -1;
                    Contact contact = mHelper.contactForPersonId(personId).otherwise(Contact.NA());
                    if (contact.id > -1) {
                        cid = contact.id;
                    } else {
                        cid = mHelper.insertContact(db, cv);
                    }

                    if (cid > -1) {

                        ContentValues gv = new ContentValues();
                        gv.put(GroupMember.GLOBAL_CONTACT_ID, values.getAsString(GroupMember.GLOBAL_CONTACT_ID));
                        gv.put(GroupMember.GROUP_ID, values.getAsLong(GroupMember.GROUP_ID));
                        gv.put(GroupMember.CONTACT_ID, cid);
                        mHelper.insertGroupMember(db, gv);
                        getContext().getContentResolver().notifyChange(Uri.parse(CONTENT_URI + "/group_members"),
                                null);
                        getContext().getContentResolver().notifyChange(Uri.parse(CONTENT_URI + "/contacts"), null);
                        getContext().getContentResolver().notifyChange(Uri.parse(CONTENT_URI + "/group_contacts"),
                                null);

                        // Add subscription to this private group feed
                        ContentValues sv = new ContentValues();
                        sv = new ContentValues();
                        sv.put(Subscriber.CONTACT_ID, cid);
                        sv.put(Subscriber.FEED_NAME, values.getAsString(Group.FEED_NAME));
                        mHelper.insertSubscriber(db, sv);

                        ContentValues xv = new ContentValues();
                        xv.put(Subscriber.CONTACT_ID, cid);
                        xv.put(Subscriber.FEED_NAME, "friend");
                        mHelper.insertSubscriber(db, xv);

                        getContext().getContentResolver().notifyChange(Uri.parse(CONTENT_URI + "/subscribers"),
                                null);

                        db.setTransactionSuccessful();
                    }
                    return uriWithId(uri, cid);
                } else {
                    Log.i(TAG, "Omitting self.");
                    return uriWithId(uri, Contact.MY_ID);
                }
            } finally {
                db.endTransaction();
            }
        } else {
            Log.e(TAG, "Failed to insert into " + uri);
            return null;
        }
    }

    /*private void restoreDatabase() {
    File data = Environment.getDataDirectory();
    String newDBPath = "/data/edu.stanford.mobisocial.dungbeetle/databases/"+DBHelper.DB_NAME+".new";
    File newDB = new File(data, newDBPath);
    if(newDB.exists()){
        
        String currentDBPath = "/data/edu.stanford.mobisocial.dungbeetle/databases/"+DBHelper.DB_NAME;
        File currentDB = new File(data, currentDBPath);
        currentDB.delete();
        currentDB = new File(data, currentDBPath);
        newDB.renameTo(currentDB);
            
        Log.w(TAG, "backup exists");
    }
    else {
    //database does't exist yet.
        Log.w(TAG, "backup does not exist");
    }
        
        
    }*/

    @Override
    public boolean onCreate() {
        //restoreDatabase();
        Log.i(TAG, "Creating DungBeetleContentProvider");
        mHelper = new DBHelper(getContext());
        mIdent = new DBIdentityProvider(mHelper);
        boolean ok = mHelper.getWritableDatabase() == null;
        if (!ok) {
            return false;
        }
        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: " + uri + " from appId " + realAppId);

        int match = sUriMatcher.match(uri);
        switch (match) {
        case UriMatcher.NO_MATCH:
            break;
        case FEEDS:
            Cursor c = mHelper.queryFeedList(realAppId, projection, selection, selectionArgs, sortOrder);
            c.setNotificationUri(resolver, Feed.feedListUri());
            return c;
        case FEEDS_ID:
            List<String> segs = uri.getPathSegments();
            switch (Feed.typeOf(uri)) {
            case APP:
                String queriedAppId = segs.get(1);
                queriedAppId = queriedAppId.substring(queriedAppId.lastIndexOf('^') + 1);
                int pos = queriedAppId.lastIndexOf(':');
                if (pos > 0) {
                    queriedAppId = queriedAppId.substring(0, pos);
                }
                if (!realAppId.equals(queriedAppId)) {
                    Log.w(TAG, "Illegal data access.");
                    return null;
                }
                String table = DbObj.TABLE;
                String select = DbObj.COL_APP_ID + " = ?";
                String[] selectArgs = new String[] { realAppId };
                String[] columns = projection;
                String groupBy = null;
                String having = null;
                String orderBy = null;
                select = DBHelper.andClauses(select, selection);
                selectArgs = DBHelper.andArguments(selectArgs, selectionArgs);
                c = mHelper.getReadableDatabase().query(table, columns, select, selectArgs, groupBy, having,
                        orderBy);
                c.setNotificationUri(resolver, uri);
                return c;
            default:
                boolean isMe = segs.get(1).equals("me");
                String feedName = isMe ? "friend" : segs.get(1);
                if (Feed.FEED_NAME_GLOBAL.equals(feedName)) {
                    feedName = null;
                }
                select = isMe ? DBHelper.andClauses(selection, DbObject.CONTACT_ID + "=" + Contact.MY_ID)
                        : selection;
                c = mHelper.queryFeed(realAppId, feedName, projection, select, selectionArgs, sortOrder);
                c.setNotificationUri(resolver, uri);
                return c;
            }
        case OBJS:
            if (!SUPER_APP_ID.equals(realAppId)) {
                String selection2 = DbObj.COL_APP_ID + " = ?";
                String[] selectionArgs2 = new String[] { realAppId };
                selection = DBHelper.andClauses(selection, selection2);
                selectionArgs = DBHelper.andArguments(selectionArgs, selectionArgs2);
            }
            return mHelper.getReadableDatabase().query(DbObject.TABLE, projection, selection, selectionArgs, null,
                    null, sortOrder);
        case OBJS_ID:
            if (!SUPER_APP_ID.equals(realAppId)) {
                return null;
            }
            // objects by database id
            String objId = uri.getLastPathSegment();
            selectionArgs = DBHelper.andArguments(selectionArgs, new String[] { objId });
            selection = DBHelper.andClauses(selection, DbObject._ID + " = ?");
            return mHelper.getReadableDatabase().query(DbObject.TABLE, projection, selection, selectionArgs, null,
                    null, sortOrder);
        }

        ///////////////////////////////////////////////////////
        // TODO: Remove code from here down, add to UriMatcher
        List<String> segs = uri.getPathSegments();
        if (match(uri, "feeds", ".+", "head")) {
            boolean isMe = segs.get(1).equals("me");
            String feedName = isMe ? "friend" : segs.get(1);
            String select = isMe ? DBHelper.andClauses(selection, DbObject.CONTACT_ID + "=" + Contact.MY_ID)
                    : selection;
            Cursor c = mHelper.queryFeedLatest(realAppId, feedName, projection, select, selectionArgs, sortOrder);
            c.setNotificationUri(resolver, Uri.parse(CONTENT_URI + "/feeds/" + feedName));
            if (isMe)
                c.setNotificationUri(resolver, Uri.parse(CONTENT_URI + "/feeds/me"));
            return c;
        } else if (match(uri, "groups_membership", ".+")) {
            if (!realAppId.equals(SUPER_APP_ID))
                return null;
            Long contactId = Long.valueOf(segs.get(1));
            Cursor c = mHelper.queryGroupsMembership(contactId);
            c.setNotificationUri(resolver, uri);
            return c;
        } else if (match(uri, "group_contacts", ".+")) {
            if (!realAppId.equals(SUPER_APP_ID))
                return null;
            Long group_id = Long.valueOf(segs.get(1));
            Cursor c = mHelper.queryGroupContacts(group_id);
            c.setNotificationUri(resolver, uri);
            return c;
        } else if (match(uri, "local_user", ".+")) {
            // currently available to any local app with a feed id.
            String feed_name = uri.getLastPathSegment();
            Cursor c = mHelper.queryLocalUser(realAppId, feed_name);
            c.setNotificationUri(resolver, uri);
            return c;
        } else if (match(uri, "members", ".+")) {
            // TODO: This is a hack so we can us SocialKit
            // to get the sender of a mass message.
            if (match(uri, "members", "friend")) {
                if (!realAppId.equals(SUPER_APP_ID))
                    return null;
                return mHelper.getReadableDatabase().query(Contact.TABLE, projection, selection, selectionArgs,
                        null, null, sortOrder);
            }

            switch (Feed.typeOf(uri)) {
            case FRIEND:
                String personId = Feed.friendIdForFeed(uri);
                if (personId == null) {
                    Log.w(TAG, "no  person id in person feed");
                    return null;
                }
                selection = DBHelper.andClauses(selection, Contact.PERSON_ID + " = ?");
                selectionArgs = DBHelper.andArguments(selectionArgs, new String[] { personId });
                return mHelper.getReadableDatabase().query(Contact.TABLE, projection, selection, selectionArgs,
                        null, null, sortOrder);
            case GROUP:
            case APP:
                String feedName = segs.get(1);
                if (feedName == null || Feed.FEED_NAME_GLOBAL.equals(feedName)) {
                    if (!SUPER_APP_ID.equals(realAppId)) {
                        return null;
                    }
                }
                Cursor c = mHelper.queryFeedMembers(projection, selection, selectionArgs, uri, realAppId);
                c.setNotificationUri(resolver, uri);
                return c;
            default:
                return null;
            }
        } else if (match(uri, "groups")) {
            if (!realAppId.equals(SUPER_APP_ID))
                return null;
            Cursor c = mHelper.queryGroups();
            c.setNotificationUri(resolver, Uri.parse(CONTENT_URI + "/groups"));
            return c;
        } else if (match(uri, "contacts") || match(uri, "subscribers") || match(uri, "group_members")) {

            if (!realAppId.equals(SUPER_APP_ID))
                return null;

            Cursor c = mHelper.getReadableDatabase().query(segs.get(0), projection, selection, selectionArgs, null,
                    null, sortOrder);
            c.setNotificationUri(resolver, Uri.parse(CONTENT_URI + "/" + segs.get(0)));
            return c;
        } else if (match(uri, "users")) {
            if (!realAppId.equals(SUPER_APP_ID))
                return null;
            Cursor c = mHelper.getReadableDatabase().query(Contact.TABLE, projection, selection, selectionArgs,
                    null, null, sortOrder);
            return c;
        } else {
            Log.d(TAG, "Unrecognized query: " + uri);
            return null;
        }
    }

    @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 (!appId.equals(SUPER_APP_ID))
            return 0;
        List<String> segs = uri.getPathSegments();

        // TODO: If uri is a feed:
        //String appRestriction = DbObject.APP_ID + "='" + appId + "'";
        //selection = DBHelper.andClauses(selection, appRestriction);

        if (DBG)
            Log.d(TAG, "Updating uri " + uri + " with " + values);
        int count = mHelper.getWritableDatabase().update(segs.get(0), values, selection, selectionArgs);
        if (count > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return count;
    }

    // For unit tests
    public DBHelper getDatabaseHelper() {
        return mHelper;
    }

    // Helper for matching on url paths
    private boolean match(Uri uri, String... regexes) {
        List<String> segs = uri.getPathSegments();
        if (segs.size() == regexes.length) {
            for (int i = 0; i < regexes.length; i++) {
                if (!segs.get(i).matches(regexes[i])) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    private String getCallingActivityId() {
        int pid = Binder.getCallingPid();
        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;
            }
        }
        return null;
    }

    static void notifyDependencies(DBHelper helper, ContentResolver resolver, String feedName) {
        Uri feedUri = Feed.uriForName(feedName);
        if (DBG)
            Log.d(TAG, "notifying dependencies of  " + feedUri);
        resolver.notifyChange(feedUri, null);
        resolver.notifyChange(Feed.uriForName(Feed.FEED_NAME_GLOBAL), null);
        if (feedName.contains(":")) {
            feedName = feedName.split(":")[0];
            resolver.notifyChange(Feed.uriForName(feedName), null);
        }
        Cursor c = helper.getFeedDependencies(feedName);
        try {
            while (c.moveToNext()) {
                Uri uri = Feed.uriForName(c.getString(0));
                resolver.notifyChange(uri, null);
            }
        } finally {
            c.close();
        }
    }

    public DBHelper getDBHelper() {
        mHelper.addRef();
        return mHelper;
    }
}