NfcLauncherActivity.java
Go to the documentation of this file.
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                 // This should not append unless we are debugging
00102                 throw new Exception("Not started by NDEF_DISCOVERED action; this activity is only intended to run that way");
00103             }
00104 
00105             //** Step 1. Parsing NFC Data
00106             parseNFCData();
00107 
00108             Log.i("NfcLaunch", "NFC tag read");
00109             toast("NFC tag read", Toast.LENGTH_SHORT);
00110 
00111             //** Step 2. Connect to SSID
00112             connectToSSID();
00113 
00114             Log.i("NfcLaunch", "Connected to " + ssid);
00115             toast("Connected to " + ssid, Toast.LENGTH_LONG);
00116 
00117                 //** Step 3. Validate the concert: check for specific topics on masterUri
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             //** Step 4. Retrieve app basic info given its app hash
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             //** Step 5. Request permission to use the app
00130             getUsePermit();
00131 
00132             Log.i("NfcLaunch", app.getDisplayName() + " ready to launch!");
00133             toast(app.getDisplayName() + " ready to launch!", Toast.LENGTH_SHORT);
00134 
00135             //** Step 6. Launch the app!
00136             launchApp();
00137 
00138             //** Terminate this app
00139             finish();
00140         }
00141         catch (Exception e) {
00142             // TODO make and "error sound"
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) // 1 byte for status and 2 lang bytes
00162         {
00163             throw new Exception("Payload doesn't match expected length: "
00164                     + payload.length +" != " + NFC_PAYLOAD_LENGTH);
00165         }
00166 
00167         int offset = 3; // skip 1 byte for status and 2 lang bytes
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";    // not needed
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                         // Switch to the SSID on tag; this is the place if we want to ask for permit
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                             // Check that it's not busy
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         // Add the extra data integer we got from the NFC tag as a new parameter for the app
00294         // Useful when we want to tailor app behavior depending to the tag that launched it
00295         //String params = app.getParameters();
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 //        am.setupRequestService(new ServiceResponseListener<concert_msgs.RequestInteractionResponse>() {
00335 //            @Override
00336 //            public void onSuccess(concert_msgs.RequestInteractionResponse response) {
00337 //                if (response.getResult() == true) {
00338 //                    launchStep = launchStep.next();
00339 //                } else {
00340 //                    Log.i("NfcLaunch", "Concert deny app use. " + response.getMessage());
00341 //                    toast("Concert deny app use. " + response.getMessage(), Toast.LENGTH_LONG);
00342 //                    launchStep = Step.ABORT_LAUNCH;
00343 //                }
00344 //            }
00345 //
00346 //            @Override
00347 //            public void onFailure(RemoteException e) {
00348 //                Log.e("NfcLaunch", "Request app use failed. " + e.getMessage());
00349 //                launchStep = Step.ABORT_LAUNCH;
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         //AppLauncher.Result result = AppLauncher.launch(this, concert, app);
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             // App not installed; ask for going to play store to download the missing app
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             // I could also show an "app not-installed" dialog and ask for going to play store to download the
00389             // missing app, but... this would stop to be a headless launcher! But maybe is a good idea, anyway
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); // returns false on ABORT_LAUNCH or InterruptedException
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                 // We overwrite only short duration toast, as the long ones are normally important
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 }


android_remocons
Author(s): Daniel Stonier, Kazuto Murase
autogenerated on Sat Jun 8 2019 19:32:24