00001 package com.github.rosjava.android_remocons.headless_launcher;
00002
00003 import android.app.Activity;
00004 import android.content.Context;
00005 import android.content.Intent;
00006 import android.net.wifi.WifiManager;
00007 import android.nfc.NfcAdapter;
00008 import android.os.AsyncTask;
00009 import android.os.Bundle;
00010 import android.os.Vibrator;
00011 import android.util.Log;
00012 import android.widget.Toast;
00013
00014 import com.github.rosjava.android_remocons.common_tools.master.ConcertChecker;
00015 import com.github.rosjava.android_remocons.common_tools.master.MasterDescription;
00016 import com.github.rosjava.android_remocons.common_tools.master.MasterId;
00017 import com.github.rosjava.android_remocons.common_tools.master.RoconDescription;
00018 import com.github.rosjava.android_remocons.common_tools.nfc.NfcManager;
00019 import com.github.rosjava.android_remocons.common_tools.rocon.AppLauncher;
00020 import com.github.rosjava.android_remocons.common_tools.rocon.AppsManager;
00021 import com.github.rosjava.android_remocons.common_tools.system.Util;
00022 import com.github.rosjava.android_remocons.common_tools.system.WifiChecker;
00023
00024 import org.ros.exception.RemoteException;
00025 import org.ros.node.service.ServiceResponseListener;
00026 import org.yaml.snakeyaml.Yaml;
00027
00028 import java.util.Map;
00029 import java.util.concurrent.ExecutionException;
00030 import java.util.concurrent.TimeUnit;
00031 import java.util.concurrent.TimeoutException;
00032
00033 import rocon_interaction_msgs.GetInteractionResponse;
00034 import rocon_interaction_msgs.Interaction;
00035 import rocon_interaction_msgs.RequestInteractionResponse;
00036
00037 import static com.github.rosjava.android_remocons.common_tools.rocon.Constants.NFC_APP_HASH_FIELD_LENGTH;
00038 import static com.github.rosjava.android_remocons.common_tools.rocon.Constants.NFC_MASTER_HOST_FIELD_LENGTH;
00039 import static com.github.rosjava.android_remocons.common_tools.rocon.Constants.NFC_MASTER_PORT_FIELD_LENGTH;
00040 import static com.github.rosjava.android_remocons.common_tools.rocon.Constants.NFC_PASSWORD_FIELD_LENGTH;
00041 import static com.github.rosjava.android_remocons.common_tools.rocon.Constants.NFC_PAYLOAD_LENGTH;
00042 import static com.github.rosjava.android_remocons.common_tools.rocon.Constants.NFC_SSID_FIELD_LENGTH;
00043
00048 public class NfcLauncherActivity extends Activity {
00049
00050 private enum Step {
00051 STARTUP,
00052 CONNECT_TO_SSID,
00053 VALIDATE_CONCERT,
00054 GET_NFC_APP_INFO,
00055 REQUEST_PERMIT,
00056 LAUNCH_APP,
00057 ABORT_LAUNCH;
00058
00059 public Step next() {
00060 return this.ordinal() < Step.values().length - 1
00061 ? Step.values()[this.ordinal() + 1]
00062 : null;
00063 }
00064 }
00065
00066 private Step launchStep = Step.STARTUP;
00067 private Toast lastToast;
00068 private Vibrator vibrator;
00069 private NfcManager nfcManager;
00070 private String errorString;
00071
00072 private String ssid;
00073 private String password;
00074 private String masterHost;
00075 private short masterPort;
00076 private int appHash;
00077 private short extraData;
00078 private MasterId masterId;
00079 private Interaction app;
00080 private RoconDescription concert;
00081
00082
00083 @Override
00084 public void onCreate(Bundle savedInstanceState) {
00085 super.onCreate(savedInstanceState);
00086
00087 try {
00088 vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
00089 vibrator.vibrate(500);
00090 toast(getString(R.string.app_name) + " started", Toast.LENGTH_SHORT);
00091
00092 Intent intent = getIntent();
00093 String action = intent.getAction();
00094 Log.d("NfcLaunch", action + " action started");
00095
00096 nfcManager = new NfcManager(this);
00097 nfcManager.onNewIntent(intent);
00098
00099 if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action) == false)
00100 {
00101
00102 throw new Exception("Not started by NDEF_DISCOVERED action; this activity is only intended to run that way");
00103 }
00104
00105
00106 parseNFCData();
00107
00108 Log.i("NfcLaunch", "NFC tag read");
00109 toast("NFC tag read", Toast.LENGTH_SHORT);
00110
00111
00112 connectToSSID();
00113
00114 Log.i("NfcLaunch", "Connected to " + ssid);
00115 toast("Connected to " + ssid, Toast.LENGTH_LONG);
00116
00117
00118 checkConcert();
00119
00120 Log.i("NfcLaunch", "Concert " + masterId.getMasterUri() + " up and running");
00121 toast("Concert " + masterId.getMasterUri() + " up and running", Toast.LENGTH_SHORT);
00122
00123
00124 getAppConfig();
00125
00126 Log.i("NfcLaunch", app.getDisplayName() + " configuration received from concert");
00127 toast(app.getDisplayName() + " configuration received from concert", Toast.LENGTH_SHORT);
00128
00129
00130 getUsePermit();
00131
00132 Log.i("NfcLaunch", app.getDisplayName() + " ready to launch!");
00133 toast(app.getDisplayName() + " ready to launch!", Toast.LENGTH_SHORT);
00134
00135
00136 launchApp();
00137
00138
00139 finish();
00140 }
00141 catch (Exception e) {
00142
00143 Log.e("NfcLaunch", "ERROR:" + e.getMessage());
00144 toast(e.getMessage(), Toast.LENGTH_LONG);
00145 finish();
00146 }
00147 }
00148
00149 @Override
00150 protected void onResume() {
00151 super.onResume();
00152 }
00153
00154 @Override
00155 protected void onPause() {
00156 super.onPause();
00157 }
00158
00159 private void parseNFCData() throws Exception {
00160 byte[] payload = nfcManager.getPayload();
00161 if (payload.length != NFC_PAYLOAD_LENGTH + 3)
00162 {
00163 throw new Exception("Payload doesn't match expected length: "
00164 + payload.length +" != " + NFC_PAYLOAD_LENGTH);
00165 }
00166
00167 int offset = 3;
00168 ssid = Util.toString(payload, offset, NFC_SSID_FIELD_LENGTH).trim();
00169 offset += NFC_SSID_FIELD_LENGTH;
00170 password = Util.toString(payload, offset, NFC_PASSWORD_FIELD_LENGTH).trim();
00171 offset += NFC_PASSWORD_FIELD_LENGTH;
00172 masterHost = Util.toString(payload, offset, NFC_MASTER_HOST_FIELD_LENGTH).trim();
00173 offset += NFC_MASTER_HOST_FIELD_LENGTH;
00174 masterPort = Util.toShort(payload, offset);
00175 offset += NFC_MASTER_PORT_FIELD_LENGTH;
00176 appHash = Util.toInteger(payload, offset);
00177 offset += NFC_APP_HASH_FIELD_LENGTH;
00178 extraData = Util.toShort(payload, offset);
00179
00180 launchStep = launchStep.next();
00181 }
00182
00183
00184
00185
00186 private void connectToSSID() throws Exception {
00187 String masterUri = "http://" + masterHost + ":" + masterPort;
00188 String encryption = "WPA2";
00189 masterId = new MasterId(masterUri, ssid, encryption, password);
00190 errorString = "";
00191 final WifiChecker wc = new WifiChecker(
00192 new WifiChecker.SuccessHandler() {
00193 public void handleSuccess() {
00194 launchStep = launchStep.next();
00195 }
00196 },
00197 new WifiChecker.FailureHandler() {
00198 public void handleFailure(String reason){
00199 launchStep = Step.ABORT_LAUNCH;
00200 errorString = reason;
00201 }
00202 },
00203 new WifiChecker.ReconnectionHandler() {
00204 public boolean doReconnection(String from, String to) {
00205
00206 Log.i("NfcLaunch", "Switching from " + from + " to " + to);
00207 toast("Switching from " + from + " to " + to, Toast.LENGTH_SHORT);
00208 return true;
00209 }
00210 }
00211 );
00212 toast("Connecting to " + ssid + "...", Toast.LENGTH_SHORT);
00213 wc.beginChecking(masterId, (WifiManager) getSystemService(WIFI_SERVICE));
00214
00215 if (waitFor(Step.VALIDATE_CONCERT, 15) == false) {
00216 if(errorString.length() == 0) {
00217 throw new Exception("Cannot connect to " + ssid + ": Aborting app launch");
00218 }
00219 else{
00220 throw new Exception("Cannot connect to " + ssid + ": "+errorString);
00221 }
00222 }
00223 }
00224
00225 private void checkConcert() throws Exception {
00226 final ConcertChecker cc = new ConcertChecker(
00227 new ConcertChecker.ConcertDescriptionReceiver() {
00228 @Override
00229 public void receive(RoconDescription roconDescription) {
00230 concert = roconDescription;
00231 if ( concert.getConnectionStatus() == MasterDescription.UNAVAILABLE ) {
00232
00233 Log.e("NfcLaunch", "Concert is unavailable: busy serving another remote controller");
00234 launchStep = Step.ABORT_LAUNCH;
00235 } else {
00236 launchStep = launchStep.next();
00237 }
00238 }
00239 },
00240 new ConcertChecker.FailureHandler() {
00241 public void handleFailure(String reason) {
00242 Log.e("NfcLaunch", "Cannot contact ROS master: " + reason);
00243 launchStep = Step.ABORT_LAUNCH;
00244 }
00245 }
00246 );
00247 toast("Validating " + masterId.getMasterUri() + "...", Toast.LENGTH_SHORT);
00248 cc.beginChecking(masterId);
00249
00250 if (waitFor(Step.GET_NFC_APP_INFO, 15) == false) {
00251 throw new Exception("Cannot connect to " + masterId.getMasterUri() + ". Aborting app launch");
00252 }
00253 }
00254
00255 private void getAppConfig() throws Exception {
00256 AppsManager am = new AppsManager(new AppsManager.FailureHandler() {
00257 public void handleFailure(String reason) {
00258 Log.e("NfcLaunch", "Cannot get app info: " + reason);
00259 launchStep = Step.ABORT_LAUNCH;
00260 }
00261 });
00262
00263 am.setupAppInfoService(new ServiceResponseListener<GetInteractionResponse>() {
00264 @Override
00265 public void onSuccess(GetInteractionResponse getInteractionResponse) {
00266 if (getInteractionResponse.getResult() == true) {
00267 app = getInteractionResponse.getInteraction();
00268 launchStep = launchStep.next();
00269 } else {
00270 Log.i("NfcLaunch", "App with hash " + appHash + " not found in concert");
00271 launchStep = Step.ABORT_LAUNCH;
00272 }
00273 }
00274
00275 @Override
00276 public void onFailure(RemoteException e) {
00277 Log.e("NfcLaunch", "Get app info failed. " + e.getMessage());
00278 launchStep = Step.ABORT_LAUNCH;
00279 }
00280 });
00281
00282
00283 am.init(concert.getInteractionsNamespace());
00284 am.getAppInfo(masterId, appHash);
00285
00286 toast("Requesting app info for hash " + appHash + "...", Toast.LENGTH_SHORT);
00287
00288 if (waitFor(Step.REQUEST_PERMIT, 15) == false) {
00289 am.shutdown();
00290 throw new Exception("Cannot get app info for hash " + appHash + ". Aborting app launch");
00291 }
00292
00293
00294
00295
00296 Yaml param_yaml = new Yaml();
00297 Map<String, String> params = (Map<String, String>) param_yaml.load(app.getParameters());
00298
00299 if (params != null && params.size() > 0){
00300 params.put("extra_data",String.valueOf(extraData));
00301 Yaml yaml = new Yaml();
00302 app.setParameters(yaml.dump(params));
00303 }
00304 }
00305
00306 private void getUsePermit() throws Exception {
00307 AppsManager am = new AppsManager(new AppsManager.FailureHandler() {
00308 public void handleFailure(String reason) {
00309 Log.e("NfcLaunch", "Cannot request app use: " + reason);
00310 launchStep = Step.ABORT_LAUNCH;
00311 }
00312 });
00313
00314
00315 am.setupRequestService(new ServiceResponseListener<rocon_interaction_msgs.RequestInteractionResponse>() {
00316 @Override
00317 public void onSuccess(RequestInteractionResponse requestInteractionResponse) {
00318 if (requestInteractionResponse.getResult() == true) {
00319 launchStep = launchStep.next();
00320 } else {
00321 Log.i("NfcLaunch", "Concert deny app use. " + requestInteractionResponse.getMessage());
00322 toast("Concert deny app use. " + requestInteractionResponse.getMessage(), Toast.LENGTH_LONG);
00323 launchStep = Step.ABORT_LAUNCH;
00324 }
00325 }
00326
00327 @Override
00328 public void onFailure(RemoteException e) {
00329 Log.e("NfcLaunch", "Request app use failed. " + e.getMessage());
00330 launchStep = Step.ABORT_LAUNCH;
00331 }
00332 });
00333
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
00345
00346
00347
00348
00349
00350
00351
00352 am.init(concert.getInteractionsNamespace());
00353 am.requestAppUse(masterId, app.getRole(), app);
00354 toast("Requesting permit to use " + app.getDisplayName() + "...", Toast.LENGTH_SHORT);
00355
00356 if (waitFor(Step.LAUNCH_APP, 15) == false) {
00357 am.shutdown();
00358 throw new Exception("Cannot get permission to use " + app.getDisplayName() + ". Aborting app launch");
00359 }
00360 }
00361
00362 private void launchApp() throws Exception {
00363
00364 AppLauncher.Result result = AppLauncher.launch(this, concert, app);
00365
00366 if (result == AppLauncher.Result.SUCCESS) {
00367 Log.i("NfcLaunch", app.getDisplayName() + " successfully launched");
00368 toast(app.getDisplayName() + " successfully launched; have fun!", Toast.LENGTH_SHORT);
00369 }
00370 else if (result == AppLauncher.Result.NOT_INSTALLED) {
00371
00372
00373 Log.i("NfcLaunch", "Showing not-installed dialog.");
00374 final String installPackage =app.getName().substring(0, app.getName().lastIndexOf("."));
00375
00376 Bundle bundle = new Bundle();
00377 bundle.putString("InstallPackageName", installPackage);
00378 bundle.putString("MainContext", "This concert app requires a client user interface app, "
00379 + "but the applicable app is not installed.\n"
00380 + "Would you like to install the app from the market place?");
00381
00382 Intent popup= new Intent(getApplicationContext(), AlertDialogActivity.class);
00383 popup.putExtras(bundle);
00384 NfcLauncherActivity.this.startActivity(popup);
00385 }
00386
00387 else {
00388
00389
00390 throw new Exception("Launch " + app.getDisplayName() + " failed. " + result.message);
00391 }
00392 }
00393
00394 private boolean waitFor(final Step step, final int timeout) {
00395 AsyncTask<Void, Void, Boolean> asyncTask = new AsyncTask<Void, Void, Boolean>() {
00396 @Override
00397 protected Boolean doInBackground(Void... params) {
00398 while ((launchStep != step) && (launchStep != Step.ABORT_LAUNCH)) {
00399 try { Thread.sleep(200); }
00400 catch (InterruptedException e) { return false; }
00401 }
00402 return (launchStep == step);
00403 }
00404 }.execute();
00405 try {
00406 return asyncTask.get(timeout, TimeUnit.SECONDS);
00407 } catch (InterruptedException e) {
00408 Log.e("NfcLaunch", "Async task interrupted. " + e.getMessage());
00409 return false;
00410 } catch (ExecutionException e) {
00411 Log.e("NfcLaunch", "Async task execution error. " + e.getMessage());
00412 return false;
00413 } catch (TimeoutException e) {
00414 Log.e("NfcLaunch", "Async task timeout (" + timeout + " s). " + e.getMessage());
00415 return false;
00416 }
00417 }
00418
00419 private void toast(final String message, final int length) {
00420 runOnUiThread(new Runnable() {
00421 @Override
00422 public void run() {
00423
00424 if ((lastToast != null) && (lastToast.getDuration() == Toast.LENGTH_SHORT))
00425 lastToast.cancel();
00426 lastToast = Toast.makeText(getBaseContext(), message, length);
00427 lastToast.show();
00428 }
00429 });
00430 }
00431 }