mobisocial.musubi.objects.IntroductionObj.java Source code

Java tutorial

Introduction

Here is the source code for mobisocial.musubi.objects.IntroductionObj.java

Source

/*
 * 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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import mobisocial.crypto.IBHashedIdentity;
import mobisocial.crypto.IBHashedIdentity.Authority;
import mobisocial.musubi.App;
import mobisocial.musubi.MembersActivity;
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.FeedManager;
import mobisocial.musubi.model.helpers.IdentitiesManager;
import mobisocial.musubi.model.helpers.ObjectManager;
import mobisocial.musubi.provider.MusubiContentProvider;
import mobisocial.musubi.provider.MusubiContentProvider.Provided;
import mobisocial.musubi.service.MusubiService;
import mobisocial.musubi.ui.fragments.FeedListFragment.FeedSummary;
import mobisocial.musubi.ui.util.UiUtil;
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.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.net.Uri;
import android.support.v4.widget.CursorAdapter;
import android.util.Base64;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.AbsListView.LayoutParams;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
 * An object that provides minimal information about introduced participants to a group.
 * This ensures the minimum latency in a person being able to tell who is participating
 * in a feed.  Other approaches, such as sending profile to people we discover (which we still do),
 * have one round-trip of latency.
 *
 */
public class IntroductionObj extends DbEntryHandler implements FeedRenderer, Activator {

    public static final String TYPE = "introduction";
    public static final String IDENTITIES = "identities";
    public static final String ID_AUTHORITY = "authority";
    public static final String ID_PRINCIPAL = "principal";
    public static final String ID_PRINCIPAL_HASH = "hash";
    public static final String ID_NAME = "name";

    @Override
    public String getType() {
        return TYPE;
    }

    public static MemObj from(Collection<MIdentity> identities, boolean tellPrincipals) {
        return new MemObj(TYPE, json(identities, tellPrincipals));
    }

    static JSONObject json(Collection<MIdentity> identities, boolean tellPrincipals) {
        JSONArray array = new JSONArray();
        JSONObject obj = new JSONObject();
        try {
            for (MIdentity id : identities) {
                JSONObject identity = new JSONObject();
                identity.put(ID_AUTHORITY, id.type_.ordinal());
                identity.put(ID_PRINCIPAL_HASH, Base64.encodeToString(id.principalHash_, Base64.DEFAULT));
                identity.put(ID_PRINCIPAL, id.principal_);
                if (tellPrincipals)
                    identity.put(ID_PRINCIPAL, id.principal_);
                identity.put(ID_NAME, UiUtil.safeNameForIdentity(id));
                array.put(identity);
            }
            obj.put(IDENTITIES, array);
        } catch (JSONException e) {
        }
        return obj;
    }

    @Override
    public View createView(Context context, ViewGroup frame) {
        LinearLayout wrap = new LinearLayout(context);
        wrap.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
        wrap.setOrientation(LinearLayout.VERTICAL);
        wrap.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
        wrap.setEnabled(false);
        wrap.setFocusableInTouchMode(false);
        wrap.setFocusable(false);
        wrap.setClickable(false);

        TextView title = new TextView(context);
        title.setText(R.string.introduced);
        title.setTypeface(null, Typeface.BOLD);
        title.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        wrap.addView(title);

        Gallery intro = new Gallery(context);
        intro.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT));
        hackGalleryInit(context, intro);
        wrap.addView(intro);
        return wrap;
    }

    @Override
    public void render(Context context, final View frame, DbObjCursor obj, final boolean allowInteractions) {
        Gallery intro = (Gallery) ((ViewGroup) frame).getChildAt(1);

        // TODO: LoaderManager requires access to a SupportActivity.
        intro.setAdapter(FacesAdapter.forObj(context, obj));
        intro.setSpacing(1);
        intro.setOnItemClickListener(mIdentityClickListener);
    }

    void hackGalleryInit(Context context, Gallery gallery) {
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        MarginLayoutParams mlp = (MarginLayoutParams) gallery.getLayoutParams();
        mlp.setMargins(-(metrics.widthPixels / 2), mlp.topMargin, mlp.rightMargin, mlp.bottomMargin);
    }

    @Override
    public boolean processObject(Context context, MFeed feed, MIdentity sender, MObject object) {
        boolean anyChanged = false;
        SQLiteOpenHelper databaseSource = App.getDatabaseSource(context);
        IdentitiesManager identitiesManager = new IdentitiesManager(databaseSource);

        if (object.json_ == null) {
            Log.w(TAG, "bad introduction format");
            return false;
        }
        JSONObject json;
        try {
            json = new JSONObject(object.json_);
        } catch (JSONException e) {
            Log.e(TAG, "Bad json in database", e);
            return false;
        }
        JSONArray array;
        try {
            array = json.getJSONArray(IDENTITIES);
        } catch (JSONException e) {
            Log.e(TAG, "json identity array missing", e);
            return false;
        }
        // TODO: use getIdentitiesForObj
        for (int i = 0; i < array.length(); ++i) {
            JSONObject identity;
            try {
                identity = array.getJSONObject(i);
            } catch (JSONException e) {
                Log.e(TAG, "identity entry in introduction access error", e);
                continue;
            }
            int authority = -1;
            String principalHashString = null;
            try {
                authority = identity.getInt(ID_AUTHORITY);
                principalHashString = identity.getString(ID_PRINCIPAL_HASH);
            } catch (JSONException e) {
                Log.e(TAG, "identity entry in introduction missing key fields", e);
                continue;
            }
            String principal = null;
            try {
                principal = identity.getString(ID_PRINCIPAL);
            } catch (JSONException e) {
            }
            String name = null;
            try {
                name = identity.getString(ID_NAME);
            } catch (JSONException e) {
            }
            if (name == null && principal == null) {
                //not much of an introduction
                continue;
            }

            byte[] principalHash = Base64.decode(principalHashString, Base64.DEFAULT);
            IBHashedIdentity hid = new IBHashedIdentity(Authority.values()[authority], principalHash, 0);
            MIdentity ident = identitiesManager.getIdentityForIBHashedIdentity(hid);
            if (ident == null) {
                //this introduction has to be sent to both participants, so the low leve
                //will already have added the identity
                Log.e(TAG, "identity introduction for totally unseen identities");
                continue;
            }
            if (ident.owned_) {
                //we won't have a received profile version, so owned keeps us from self updating
                continue;
            }
            //TODO: rely on  deferred handling for gray list participants
            //TODO: check that the person is actually in the feed
            boolean changed = false;
            if (principal != null && ident.principal_ == null) {
                if (!Arrays.equals(Util.sha256(principal.getBytes()), principalHash)) {
                    Log.e(TAG, "received mismatched principal and principal hash");
                    continue;
                }
                changed = true;
                ident.principal_ = principal;
            }
            if (name != null && ident.receivedProfileVersion_ == 0) {
                changed = true;
                //each time someone introduces us, we'll just accept the new name
                //as long as we never got a real profile.
                ident.musubiName_ = name;
            }
            if (changed) {
                identitiesManager.updateIdentity(ident);
                anyChanged = true;
            }
        }
        if (anyChanged) {
            context.getContentResolver().notifyChange(MusubiService.PRIMARY_CONTENT_CHANGED, null);
        }
        return true;
    }

    @Override
    public void activate(Context context, DbObj obj) {
        SQLiteOpenHelper databaseSource = App.getDatabaseSource(context);
        ObjectManager objectManger = new ObjectManager(databaseSource);
        FeedManager feedManager = new FeedManager(databaseSource);
        MObject object = objectManger.getObjectForId(obj.getLocalId());
        if (!(context instanceof Activity)) {
            return;
        }
        if (object == null) {
            return;
        }
        MFeed feed = feedManager.lookupFeed(object.feedId_);
        if (feed == null) {
            return;
        }
        Intent members = new Intent(context, MembersActivity.class);
        members.putExtra(MembersActivity.INTENT_EXTRA_FEED_URI,
                MusubiContentProvider.uriForItem(Provided.FEEDS, feed.id_));
        context.startActivity(members);
    }

    static class FacesAdapter extends CursorAdapter {
        static IdentitiesManager sIdentitiesManager;

        // TODO: No access to LoaderManager.
        private FacesAdapter(Context context, Cursor c) {
            super(context, c);
        }

        public static CursorAdapter forObj(Context context, Obj obj) {
            MatrixCursor recruits = new MatrixCursor(new String[] { MIdentity.COL_ID });

            sIdentitiesManager = new IdentitiesManager(App.getDatabaseSource(context));
            List<IBHashedIdentity> ids = getIdentitiesForObj(obj);
            for (IBHashedIdentity id : ids) {
                MIdentity ident = sIdentitiesManager.getIdentityForIBHashedIdentity(id);
                if (ident != null) {
                    recruits.addRow(new Object[] { ident.id_ });
                }
            }

            return new FacesAdapter(context, recruits);
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            ImageView face = new ImageView(context);
            int size = (int) context.getResources().getDisplayMetrics().density * 80;
            Gallery.LayoutParams spec = new Gallery.LayoutParams(size, size);
            face.setLayoutParams(spec);
            face.setScaleType(ScaleType.FIT_XY);
            face.setPadding(6, 6, 6, 6);
            face.setBackgroundResource(android.R.drawable.picture_frame);
            return face;
        }

        @Override
        public void bindView(View view, Context context, Cursor cursor) {
            long id = cursor.getLong(0);
            view.setTag(id);
            MIdentity stub = new MIdentity();
            stub.id_ = id;
            Bitmap bm = UiUtil.safeGetContactThumbnail(context, sIdentitiesManager, stub);
            if (bm != null) {
                ((ImageView) view).setImageBitmap(bm);
            } else {
                Log.w(TAG, "safe thumbnail lookup not safe");
            }
        }
    }

    /**
     * Extracts the list of identities from an introductionObj.
     */
    static List<IBHashedIdentity> getIdentitiesForObj(Obj obj) {
        ArrayList<IBHashedIdentity> ids = new ArrayList<IBHashedIdentity>();

        JSONObject json = obj.getJson();
        if (json == null) {
            return ids;
        }
        JSONArray array;
        try {
            array = json.getJSONArray(IDENTITIES);
        } catch (JSONException e) {
            return ids;
        }

        for (int i = 0; i < array.length(); ++i) {
            JSONObject identity;
            try {
                identity = array.getJSONObject(i);
            } catch (JSONException e) {
                Log.e(TAG, "identity entry in introduction access error", e);
                continue;
            }
            int authority = -1;
            String principalHashString = null;
            try {
                authority = identity.getInt(ID_AUTHORITY);
                principalHashString = identity.getString(ID_PRINCIPAL_HASH);
            } catch (JSONException e) {
                Log.e(TAG, "identity entry in introduction missing key fields", e);
                continue;
            }
            String principal = null;
            try {
                principal = identity.getString(ID_PRINCIPAL);
            } catch (JSONException e) {
            }
            String name = null;
            try {
                name = identity.getString(ID_NAME);
            } catch (JSONException e) {
            }
            if (name == null && principal == null) {
                //not much of an introduction
                continue;
            }

            byte[] principalHash = Base64.decode(principalHashString, Base64.DEFAULT);
            ids.add(new IBHashedIdentity(Authority.values()[authority], principalHash, 0));
        }
        return ids;
    }

    OnItemClickListener mIdentityClickListener = new OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> av, View v, int pos, long id) {
            long identityId = (Long) v.getTag();
            Uri identityUri = MusubiContentProvider.uriForItem(Provided.IDENTITIES_ID, identityId);
            String identityType = MusubiContentProvider.getType(Provided.IDENTITIES_ID);
            Intent view = new Intent(Intent.ACTION_VIEW);
            view.setDataAndType(identityUri, identityType);
            v.getContext().startActivity(view);
        }
    };

    @Override
    public void getSummaryText(Context context, TextView view, FeedSummary summary) {
        view.setTypeface(null, Typeface.ITALIC);
        view.setText(summary.getSender() + " introduced new people to the feed.");
    }
}