Java tutorial
/* * 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); } }