edu.stanford.junction.android.AndroidJunctionMaker.java Source code

Java tutorial

Introduction

Here is the source code for edu.stanford.junction.android.AndroidJunctionMaker.java

Source

/*
 * Copyright (C) 2010 Stanford University
 *
 * 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 edu.stanford.junction.android;

import java.net.URI;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

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

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.net.Uri;

import edu.stanford.junction.Junction;
import edu.stanford.junction.JunctionException;
import edu.stanford.junction.JunctionMaker;
import edu.stanford.junction.SwitchboardConfig;
import edu.stanford.junction.api.activity.ActivityScript;
import edu.stanford.junction.api.activity.Cast;
import edu.stanford.junction.api.activity.JunctionActor;
import edu.stanford.junction.provider.JunctionProvider;
import edu.stanford.junction.provider.bluetooth.BluetoothSwitchboardConfig;
import edu.stanford.junction.provider.xmpp.XMPPSwitchboardConfig;

// TODO:
// Class hierarchy is badly broken.
// Split out platform methods and impl methods.
// For now, extending XMPP.JunctionMaker.
public class AndroidJunctionMaker extends JunctionMaker {

    public static final int JUNCTION_VERSION = 1;

    public static class Intents {
        public static final String ACTION_JOIN = "junction.intent.action.JOIN";
        public static final String ACTION_CAST = "junction.intent.action.CAST";

        public static final String EXTRA_CAST_ROLES = "castingRoles";
        public static final String EXTRA_CAST_DIRECTORS = "castingDirectors";
        public static final String EXTRA_CAST_PACKAGE = "joiningPackage";
        public static final String EXTRA_CAST_OR_JOIN = "castOrJoin";
        public static final String EXTRA_ACTIVITY_SESSION_URI = "invitationURI";
        public static final String EXTRA_ACTIVITY_SCRIPT = "activityScript";
        public static final String EXTRA_JUNCTION_VERSION = "junctionVersion";

        public static final String PACKAGE_DIRECTOR = "edu.stanford.prpl.junction.applaunch";

        public static final int ALLOW_CAST = 1;
        public static final int ALLOW_JOIN = 2;
    }

    private static String JX_LAUNCHER_NAME = "Activity Director";
    private static String JX_LAUNCHER_URL = "http://prpl.stanford.edu/android/JunctionAppLauncher.apk";
    private static String JX_LAUNCHER_PACKAGE = Intents.PACKAGE_DIRECTOR;

    public static final URI CASTING_DIRECTOR = JunctionMaker.CASTING_DIRECTOR;

    public static AndroidJunctionMaker getInstance(SwitchboardConfig config) {
        AndroidJunctionMaker maker = new AndroidJunctionMaker();
        maker.mProvider = maker.getProvider(config);
        maker.mProvider.setJunctionMaker(maker);
        return maker;
    }

    private AndroidJunctionMaker() {

    }

    public URI getInvitationForActivity(Activity activity) {
        try {
            return new URI(activity.getIntent().getExtras().getString("invitationURI"));
        } catch (Exception e) {
            Log.e("junction", "could not get invitation URI", e);
            return null;
        }
    }

    /**
     * Returns an Intent than can be used to join a Junction activity.
     * If you want a specific class to handle this intent,
     * you can specify the package/class on the returned
     * Intent before using it to start an activity.
     * 
     * @param junctionInvitation
     * @return
     */
    public static Intent getIntentForActivityJoin(URI junctionInvitation) {
        Intent launchIntent = new Intent(Intents.ACTION_JOIN);
        launchIntent.putExtra("junctionVersion", 1);
        //launchIntent.putExtra("activityDescriptor", invitation.toString());
        // TODO: keep URI?
        launchIntent.putExtra(Intents.EXTRA_ACTIVITY_SESSION_URI, junctionInvitation.toString());

        return launchIntent;
    }

    /**
     * Sends an intent to complete the casting of an activity.
     * 
     * The result will be an Intent issued, of action ACTION_JOIN.
     * The Intent will be populated with casting information, and should
     * be handled in your android.app.Activity:
     * 
     * if AndroidJunctionMaker.isJoinable(this) {
     *      maker.newJunction(this,mActor);
     * }
     * 
     * @param context
     * @param script
     * @param support
     */
    public static void castActivity(Context context, ActivityScript script, Cast support) {
        castActivityHelper(context, script, support, Intents.ALLOW_CAST);
    }

    /**
     * Casts or joins an activity, as
     * decided by the Activity Director.
     * If the Director detects the user is launching
     * to a generic target (Eg a director's activity)
     * then it will start a new activity.
     * If the user pairs with an existing activity,
     * then the user is joined to that session with 
     * no casting done.
     * 
     * @param context
     * @param script
     * @param support
     */
    public static void castOrJoinActivity(Context context, ActivityScript script, Cast support) {
        castActivityHelper(context, script, support, Intents.ALLOW_CAST | Intents.ALLOW_JOIN);
    }

    private static void castActivityHelper(Context context, ActivityScript script, Cast support, int castOrJoin) {
        Intent castingIntent = new Intent(Intents.ACTION_CAST);

        if (support != null && support.size() > 0) {
            int size = support.size();
            String[] castingRoles = new String[size];
            String[] castingDirectors = new String[size];

            for (int i = 0; i < size; i++) {
                castingRoles[i] = support.getRole(i);
                castingDirectors[i] = support.getDirector(i).toString();
            }

            castingIntent.putExtra(Intents.EXTRA_CAST_ROLES, castingRoles);
            castingIntent.putExtra(Intents.EXTRA_CAST_DIRECTORS, castingDirectors);
        }

        castingIntent.putExtra(Intents.EXTRA_CAST_PACKAGE, context.getPackageName());
        castingIntent.putExtra(Intents.EXTRA_ACTIVITY_SCRIPT, script.getJSON().toString());
        castingIntent.putExtra(Intents.EXTRA_CAST_OR_JOIN, castOrJoin);
        context.startActivity(castingIntent);
    }

    /**
     * Junction creator from a bundle passed from
     * a Junction Activity Director.
     * 
     * The bundle may contain the URI of an existing activity session
     * or an activity script for creating a new session. It may also
     * contain casting information.
     * 
     * @param bundle
     * @param actor
     * @return
     */
    public Junction newJunction(Bundle bundle, JunctionActor actor) throws JunctionException {
        if (bundle == null || !bundle.containsKey(Intents.EXTRA_JUNCTION_VERSION)) {
            Log.d("junction", "Could not launch from bundle (" + bundle + ")");
            return null;
        }

        try {
            if (bundle.containsKey(Intents.EXTRA_ACTIVITY_SESSION_URI)) {
                // TODO: pass both activity script and uri if available?
                // TODO: still support casting?
                Log.d("junction",
                        "joining existing session " + bundle.getString(Intents.EXTRA_ACTIVITY_SESSION_URI));
                URI uri = new URI(bundle.getString(Intents.EXTRA_ACTIVITY_SESSION_URI));
                Junction jx = newJunction(uri, actor);
                return jx;
            } else {
                JSONObject desc = new JSONObject(bundle.getString(Intents.EXTRA_ACTIVITY_SCRIPT));
                ActivityScript activityDesc = new ActivityScript(desc);

                Junction jx;
                if (bundle.containsKey(Intents.EXTRA_CAST_ROLES)) {
                    Log.d("junction", "casting roles");
                    String[] aroles = bundle.getStringArray(AndroidJunctionMaker.Intents.EXTRA_CAST_ROLES);
                    String[] adirectors = bundle.getStringArray(AndroidJunctionMaker.Intents.EXTRA_CAST_DIRECTORS);

                    List<String> roles = Arrays.asList(aroles);
                    List<URI> directors = new LinkedList<URI>();
                    for (int i = 0; i < adirectors.length; i++) {
                        directors.add(new URI(adirectors[i]));
                    }
                    Log.d("junction", "going to request casting for " + directors.size() + " roles");
                    Cast support = new Cast(roles, directors);
                    jx = newJunction(activityDesc, actor, support);
                } else {
                    jx = newJunction(activityDesc, actor);
                }
                return jx;
            }
        } catch (JunctionException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace(System.err);
            throw new JunctionException(e);
        }
    }

    public static boolean isJoinable(Intent intent) {
        if (intent == null || intent.getExtras() == null)
            return false;
        return intent.getExtras().containsKey("junctionVersion");
    }

    public static boolean isJoinable(Activity a) {
        return (isJoinable(a.getIntent()));
    }

    public static Junction newJunction(Activity a, JunctionActor actor) throws JunctionException {
        return newJunction(a.getIntent(), actor);
    }

    public static Junction newJunction(Intent intent, JunctionActor actor) throws JunctionException {
        Bundle bundle = intent.getExtras();
        if (bundle == null || !bundle.containsKey(Intents.EXTRA_ACTIVITY_SESSION_URI)) {
            throw new JunctionException("No session uri found.");
        }

        URI uri = URI.create(bundle.getString(Intents.EXTRA_ACTIVITY_SESSION_URI));
        SwitchboardConfig cfg = AndroidJunctionMaker.getDefaultSwitchboardConfig(uri);
        AndroidJunctionMaker maker = AndroidJunctionMaker.getInstance(cfg);

        ActivityScript script = null;
        if (bundle.containsKey(Intents.EXTRA_ACTIVITY_SCRIPT)) {
            try {
                JSONObject json = new JSONObject(bundle.getString(Intents.EXTRA_ACTIVITY_SCRIPT));
                script = new ActivityScript(json);
            } catch (JSONException e) {
                throw new JunctionException("Bad activity script in Intent.", e);
            }
        }

        return maker.newJunction(uri, script, actor);
    }

    /**
     * Finds a pre-existing Junction activity by scanning for a QR code.
     * @param context
     */
    public static void findActivityByScan(final Activity activity) {
        WaitForInternetCallback callback = new WaitForInternetCallback(activity) {
            @Override
            public void onConnectionFailure() {
                activity.finish();
            }

            @Override
            public void onConnectionSuccess() {
                Intent intent = new Intent("junction.intent.action.join.SCAN");
                intent.putExtra("package", activity.getPackageName());
                IntentLauncher.launch(activity, intent, "edu.stanford.prpl.junction.applaunch",
                        "http://prpl.stanford.edu/android/JunctionAppLauncher.apk", "Activity Director");
            }
        };

        try {
            WaitForInternet.setCallback(callback);
        } catch (SecurityException e) {
            Log.w("junction",
                    "Could not check network state. If you'd like this functionality, please add the permission: android.permission.ACCESS_NETWORK_STATE",
                    e);
            callback.onConnectionSuccess();
        }
    }

    /**
     * Launch the Activity Director to join an existing
     * Junction activity by some user-specified method.
     * 
     * @param context
     */
    public static void joinActivity(Context context) {
        Intent intent = new Intent("junction.intent.action.join.ANY");
        intent.putExtra("package", context.getPackageName());
        IntentLauncher.launch(context, intent, "edu.stanford.prpl.junction.applaunch",
                "http://prpl.stanford.edu/android/JunctionAppLauncher.apk", "Activity Director");
    }

    /**
     * Invites another actor by some unspecified means.
     * @param context
     * @param Junction
     * @param role
     */
    public void inviteActor(Context context, Junction junction, String role) {
        Intent intent = new Intent("junction.intent.action.invite.ANY");
        intent.putExtra("package", context.getPackageName());
        intent.putExtra("uri", junction.getInvitationURI(role).toString());
        //intent.putExtra("activityDescriptor", junction.getActivityDescription().getJSON());

        IntentLauncher.launch(context, intent, JX_LAUNCHER_PACKAGE, JX_LAUNCHER_URL, JX_LAUNCHER_NAME);
    }

    /**
     * Invites another actor by some unspecified means.
     * @param context
     * @param Junction
     * @param role
     */
    public void inviteActor(Context context, URI invitation) {
        Intent intent = new Intent("junction.intent.action.invite.ANY");
        intent.putExtra("package", context.getPackageName());
        intent.putExtra("uri", invitation.toString());
        //intent.putExtra("activityDescriptor", junction.getActivityDescription().getJSON());

        IntentLauncher.launch(context, intent, JX_LAUNCHER_PACKAGE, JX_LAUNCHER_URL, JX_LAUNCHER_NAME);
    }

    /**
     * Invites an actor to an activity by presenting a QR code on screen. 
     * @param context
     * @param junction
     * @param role
     */
    public void inviteActorByQR(Context context, Junction junction, String role) {
        Intent intent = new Intent("junction.intent.action.invite.QR");
        intent.putExtra("package", context.getPackageName());
        intent.putExtra("uri", junction.getInvitationURI(role).toString());
        //intent.putExtra("activityDescriptor", junction.getActivityDescription().getJSON());

        IntentLauncher.launch(context, intent, JX_LAUNCHER_PACKAGE, JX_LAUNCHER_URL, JX_LAUNCHER_NAME);
    }

    /**
     * Scan for a Listening service and send it a 'join activity' request.
     * @param context
     * @param junction
     * @param role
     */
    public void inviteActorByScan(Context context, Junction junction, String role) {
        Intent intent = new Intent("junction.intent.action.invite.SCAN");
        intent.putExtra("package", context.getPackageName());
        intent.putExtra("uri", junction.getInvitationURI(role).toString());
        //intent.putExtra("activityDescription", junction.getActivityDescription().getJSON().toString());
        //intent.putExtra("role",role);

        IntentLauncher.launch(context, intent, JX_LAUNCHER_PACKAGE, JX_LAUNCHER_URL, JX_LAUNCHER_NAME);
    }

    /**
     * Send an invitation to join an activity by text message.
     * @param context
     * @param junction
     * @param role
     */
    public void inviteActorBySMS(Context context, Junction junction, String role) {
        Intent intent = new Intent("junction.intent.action.invite.TEXT");
        String uri = junction.getInvitationURI(role).toString();
        intent.putExtra("invitation", uri);

        IntentLauncher.launch(context, intent, JX_LAUNCHER_PACKAGE, JX_LAUNCHER_URL, JX_LAUNCHER_NAME);
    }

    /**
     * Send an invitation to join an activity by text message.
     * @param context
     * @param junction
     * @param role
     * @param phoneNumber
     */
    public void inviteActorBySMS(Context context, Junction junction, String role, String phoneNumber) {
        Intent intent = new Intent("junction.intent.action.invite.TEXT");
        String uri = junction.getInvitationURI(role).toString();
        intent.putExtra("invitation", uri);
        intent.putExtra("phoneNumber", phoneNumber);

        IntentLauncher.launch(context, intent, JX_LAUNCHER_PACKAGE, JX_LAUNCHER_URL, JX_LAUNCHER_NAME);
    }

    @Override
    protected JunctionProvider getProvider(SwitchboardConfig switchboardConfig) {
        if (switchboardConfig instanceof BluetoothSwitchboardConfig) {
            return new edu.stanford.junction.provider.bluetooth.JunctionProvider(
                    (BluetoothSwitchboardConfig) switchboardConfig);
        }
        return super.getProvider(switchboardConfig);
    }

    public static SwitchboardConfig getDefaultSwitchboardConfig(URI uri) {
        String fragment = uri.getFragment();
        if (fragment != null) {
            if (fragment.equalsIgnoreCase("bt")) {
                return new BluetoothSwitchboardConfig();
            }
        }
        return JunctionMaker.getDefaultSwitchboardConfig(uri);
    }

    /**
     * Binds a {@link JunctionActor} to a session URI, using the default
     * switchboard for that URI.
     */
    public static Junction bind(URI uri, JunctionActor actor) throws JunctionException {
        return AndroidJunctionMaker.getInstance(AndroidJunctionMaker.getDefaultSwitchboardConfig(uri))
                .newJunction(uri, actor);
    }

    /**
      * Binds a {@link JunctionActor} to a session URI, using the default
      * switchboard for that URI.
      */
    public static Junction bind(Uri uri, JunctionActor actor) throws JunctionException {
        URI uriCaps = URI.create(uri.toString());
        return AndroidJunctionMaker.getInstance(AndroidJunctionMaker.getDefaultSwitchboardConfig(uriCaps))
                .newJunction(uriCaps, actor);
    }

    /**
     * Binds a {@link JunctionActor} to a randomly generated sesssion, using the default
     * switchboard.
     */
    public static Junction bind(JunctionActor actor) throws JunctionException {
        // default
        SwitchboardConfig config = new XMPPSwitchboardConfig("prpl.stanford.edu");
        JunctionMaker maker = JunctionMaker.getInstance(config);
        return maker.newJunction(maker.generateSessionUri(), actor);
    }
}