00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037 package com.github.rosjava.android_remocons.rocon_remocon;
00038
00039 import android.app.AlertDialog;
00040 import android.content.DialogInterface;
00041 import android.content.Intent;
00042 import android.net.Uri;
00043 import android.net.wifi.WifiManager;
00044 import android.os.AsyncTask;
00045 import android.os.Bundle;
00046 import android.util.Log;
00047 import android.view.Menu;
00048 import android.view.MenuItem;
00049 import android.view.View;
00050 import android.view.Window;
00051 import android.view.WindowManager;
00052 import android.widget.AdapterView;
00053 import android.widget.AdapterView.OnItemClickListener;
00054 import android.widget.Button;
00055 import android.widget.GridView;
00056 import android.widget.TextView;
00057 import android.widget.Toast;
00058
00059 import com.github.robotics_in_concert.rocon_rosjava_core.rocon_interactions.InteractionMode;
00060 import com.github.rosjava.android_remocons.common_tools.master.ConcertChecker;
00061 import com.github.rosjava.android_remocons.common_tools.master.MasterId;
00062 import com.github.rosjava.android_remocons.common_tools.master.RoconDescription;
00063 import com.github.rosjava.android_remocons.common_tools.rocon.AppLauncher;
00064 import com.github.rosjava.android_remocons.common_tools.rocon.Constants;
00065 import com.github.rosjava.android_remocons.common_tools.rocon.InteractionsManager;
00066 import com.github.rosjava.android_remocons.common_tools.system.WifiChecker;
00067 import com.github.rosjava.android_remocons.rocon_remocon.dialogs.AlertDialogWrapper;
00068 import com.github.rosjava.android_remocons.rocon_remocon.dialogs.LaunchInteractionDialog;
00069 import com.github.rosjava.android_remocons.rocon_remocon.dialogs.ProgressDialogWrapper;
00070 import com.google.common.base.Preconditions;
00071
00072 import org.ros.android.RosActivity;
00073 import org.ros.exception.RemoteException;
00074 import org.ros.exception.RosRuntimeException;
00075 import org.ros.node.NodeConfiguration;
00076 import org.ros.node.NodeMainExecutor;
00077 import org.ros.node.service.ServiceResponseListener;
00078
00079 import java.io.IOException;
00080 import java.net.URI;
00081 import java.net.URISyntaxException;
00082 import java.util.ArrayList;
00083 import java.util.List;
00084
00085 import rocon_interaction_msgs.GetInteractionsResponse;
00086 import rocon_interaction_msgs.Interaction;
00087
00096 public class Remocon extends RosActivity {
00097
00098
00099 private static final int CONCERT_MASTER_CHOOSER_REQUEST_CODE = 1;
00100
00101 private Interaction selectedInteraction;
00102 private String concertAppName = null;
00103 private String defaultConcertAppName = null;
00104 private RoconDescription roconDescription;
00105 private NodeConfiguration nodeConfiguration;
00106 private ArrayList<Interaction> availableAppsCache;
00107 private TextView concertNameView;
00108 private Button leaveConcertButton;
00109 private LaunchInteractionDialog launchInteractionDialog;
00110 private ProgressDialogWrapper progressDialog;
00111 private AlertDialogWrapper wifiDialog;
00112 private AlertDialogWrapper evictDialog;
00113 private AlertDialogWrapper errorDialog;
00114 private InteractionsManager interactionsManager;
00115 private StatusPublisher statusPublisher;
00116 private PairSubscriber pairSubscriber;
00117 private boolean alreadyClicked = false;
00118 private boolean validatedConcert;
00119 private long availableAppsCacheTime;
00120
00121
00122
00123
00124
00125 private boolean fromApplication = false;
00126 private boolean fromNfcLauncher = false;
00127
00128 public Remocon() {
00129 super("Remocon", "Remocon");
00130 availableAppsCacheTime = 0;
00131 availableAppsCache = new ArrayList<Interaction>();
00132 statusPublisher = StatusPublisher.getInstance();
00133 pairSubscriber= PairSubscriber.getInstance();
00134 pairSubscriber.setAppHash(0);
00135 }
00136
00137 @Override
00138 protected void init() {
00139 Log.d("Remocon", "init()");
00140 if (!fromApplication && !fromNfcLauncher) {
00141 super.init();
00142 } else {
00143 Log.i("Remocon", "init() - returned from closing interaction, or started by Nfc launcher");
00144
00145 if (getIntent().hasExtra(RoconDescription.UNIQUE_KEY)) {
00146 Log.w("Remocon", "init() - successfully retrieved concert description key and moving to init(Intent)");
00147 init(getIntent());
00148 Log.i("Remocon", "Successfully retrieved concert description from the intent");
00149 } else {
00150 Log.e("Remocon", "Closing interaction didn't return the concert description");
00151
00152 }
00153 }
00154 }
00155
00156 @Override
00157 protected void onStart() {
00158 super.onResume();
00159 if ( getIntent().getExtras() != null ) {
00160 Log.i("Remocon", "onStart: " + Constants.ACTIVITY_SWITCHER_ID + "." + InteractionMode.CONCERT + "_app_name");
00161 concertAppName = getIntent().getStringExtra(Constants.ACTIVITY_SWITCHER_ID + "." + InteractionMode.CONCERT + "_app_name");
00162 if (concertAppName == null){
00163 fromApplication = false;
00164 fromApplication = false;
00165 }
00166 else{
00167 if (concertAppName.equals("AppChooser")) {
00168 Log.i("Remocon", "got intent from a closing remocon application");
00169 statusPublisher.update(false, 0, null);
00170 fromApplication = true;
00171 }
00172 else if (concertAppName.equals("NfcLauncher")) {
00173 Log.i("Remocon", "got intent from an Nfc launched application");
00174 fromNfcLauncher = true;
00175 }
00176 }
00177 }
00178 super.onStart();
00179 }
00180
00182 @Override
00183 public void onCreate(Bundle savedInstanceState) {
00184 super.onCreate(savedInstanceState);
00185 Log.i("[Remocon]", "Oncreate");
00186 requestWindowFeature(Window.FEATURE_NO_TITLE);
00187 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
00188 WindowManager.LayoutParams.FLAG_FULLSCREEN);
00189 setContentView(R.layout.rocon_remocon);
00190 concertNameView = (TextView) findViewById(R.id.concert_name_view);
00191
00192 interactionsManager = new InteractionsManager(
00193 new InteractionsManager.FailureHandler() {
00194 public void handleFailure(String reason) {
00195 Log.e("Remocon", "Failure on interactions manager: " + reason);
00196 }
00197 }
00198 );
00199 interactionsManager.setupGetInteractionsService(new ServiceResponseListener<GetInteractionsResponse>() {
00200 @Override
00201 public void onSuccess(rocon_interaction_msgs.GetInteractionsResponse response) {
00202 List<Interaction> apps = response.getInteractions();
00203 if (apps.size() > 0) {
00204 availableAppsCache = (ArrayList<Interaction>) apps;
00205 Log.i("Remocon", "Interaction Publication: " + availableAppsCache.size() + " apps");
00206 runOnUiThread(new Runnable() {
00207 @Override
00208 public void run() {
00209 updateAppList(availableAppsCache, roconDescription.getMasterName(), roconDescription.getCurrentRole());
00210 progressDialog.dismiss();
00211 }
00212 });
00213 } else {
00214
00215 Log.w("Remocon", "No interactions available for the '" + roconDescription.getCurrentRole() + "' role.");
00216 }
00217 availableAppsCacheTime = System.currentTimeMillis();
00218 }
00219
00220 @Override
00221 public void onFailure(RemoteException e) {
00222 progressDialog.dismiss();
00223 Log.e("Remocon", "retrieve interactions for the role '"
00224 + roconDescription.getCurrentRole() + "' failed: " + e.getMessage());
00225 }
00226 });
00227 interactionsManager.setupRequestService(new ServiceResponseListener<rocon_interaction_msgs.RequestInteractionResponse>() {
00228 @Override
00229 public void onSuccess(rocon_interaction_msgs.RequestInteractionResponse response) {
00230 Preconditions.checkNotNull(selectedInteraction);
00231
00232 final boolean allowed = response.getResult();
00233 final String reason = response.getMessage();
00234
00235 boolean ret_launcher_dialog = false;
00236 progressDialog.dismiss();
00237
00238 if(AppLauncher.checkAppType(selectedInteraction.getName()) == AppLauncher.AppType.NOTHING){
00239 pairSubscriber.setAppHash(selectedInteraction.getHash());
00240 ret_launcher_dialog = true;
00241 }
00242 else{
00243 launchInteractionDialog.setup(selectedInteraction, allowed, reason);
00244 if(allowed){
00245 pairSubscriber.setAppHash(selectedInteraction.getHash());
00246 }
00247 else{
00248 pairSubscriber.setAppHash(0);
00249 }
00250 ret_launcher_dialog = launchInteractionDialog.show();
00251 }
00252
00253 if (ret_launcher_dialog) {
00254 Log.i("Remocon", "Selected Launch button");
00255 runOnUiThread(new Runnable() {
00256 @Override
00257 public void run() {
00258
00259 AppLauncher.Result result =
00260 AppLauncher.launch(Remocon.this, roconDescription, selectedInteraction);
00261 if (result == AppLauncher.Result.SUCCESS) {
00262
00263
00264
00265 }
00266 else if (result == AppLauncher.Result.NOTHING){
00267
00268 }
00269 else if (result == AppLauncher.Result.NOT_INSTALLED) {
00270
00271 statusPublisher.update(false, 0, null);
00272 Log.i("Remocon", "Showing not-installed dialog.");
00273 final String installPackage = selectedInteraction.getName().substring(0, selectedInteraction.getName().lastIndexOf("."));
00274 selectedInteraction = null;
00275
00276 AlertDialog.Builder dialog = new AlertDialog.Builder(Remocon.this);
00277 dialog.setIcon(R.drawable.playstore_icon_small);
00278 dialog.setTitle("Android app not installed.");
00279 dialog.setMessage("This interaction requires an android application to be installed.\n"
00280 + "Would you like to install '" + installPackage + "' from the market place?");
00281 dialog.setPositiveButton("Yes", new DialogInterface.OnClickListener()
00282 {
00283 @Override
00284 public void onClick(DialogInterface dlog, int i) {
00285 Uri uri = Uri.parse("market://details?id=" + installPackage);
00286 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
00287 Remocon.this.startActivity(intent);
00288 }
00289 });
00290 dialog.setNegativeButton("No", new DialogInterface.OnClickListener()
00291 {
00292 @Override
00293 public void onClick(DialogInterface dlog, int i) {
00294 dlog.dismiss();
00295 }
00296 });
00297 dialog.show();
00298 }
00299 else {
00300 AlertDialog.Builder dialog = new AlertDialog.Builder(Remocon.this);
00301 dialog.setIcon(R.drawable.failure_small);
00302 dialog.setTitle("Cannot start app");
00303 dialog.setMessage(result.message);
00304 dialog.setPositiveButton("Accept", new DialogInterface.OnClickListener() {
00305 @Override
00306 public void onClick(DialogInterface dlog, int i) {
00307
00308 }
00309 });
00310 dialog.show();
00311 }
00312
00313 };
00314 });
00315 }
00316 else {
00317 Log.i("[Remocon]","User select cancel");
00318 statusPublisher.update(false, 0, null);
00319 }
00320 }
00321
00322 @Override
00323 public void onFailure(RemoteException e) {
00324 progressDialog.dismiss();
00325 Log.e("Remocon", "Retrieve rapps for role "
00326 + roconDescription.getCurrentRole() + " failed: " + e.getMessage());
00327 }
00328 });
00329
00330 pairSubscriber.setAppHash(0);
00331 }
00332
00345 @Override
00346 protected void init(final NodeMainExecutor nodeMainExecutor) {
00347 try {
00348 java.net.Socket socket = new java.net.Socket(getMasterUri().getHost(), getMasterUri().getPort());
00349 java.net.InetAddress local_network_address = socket.getLocalAddress();
00350 socket.close();
00351 NodeConfiguration nodeConfiguration =
00352 NodeConfiguration.newPublic(local_network_address.getHostAddress(), getMasterUri());
00353 interactionsManager.init(roconDescription.getInteractionsNamespace());
00354 interactionsManager.getAppsForRole(roconDescription.getMasterId(), roconDescription.getCurrentRole());
00355 interactionsManager.setRemoconName(statusPublisher.REMOCON_FULL_NAME);
00356 progressDialog.show("Getting apps...",
00357 "Retrieving interactions for the '" + roconDescription.getCurrentRole() + "' role");
00358
00359 if (! statusPublisher.isInitialized()) {
00360
00361 nodeMainExecutorService.execute(statusPublisher, nodeConfiguration.setNodeName(StatusPublisher.NODE_NAME));
00362 }
00363
00364 pairSubscriber.setAppHash(0);
00365
00366 if (! pairSubscriber.isInitialized()) {
00367
00368 nodeMainExecutorService.execute(pairSubscriber, nodeConfiguration.setNodeName(pairSubscriber.NODE_NAME));
00369 }
00370 } catch (IOException e) {
00371
00372 }
00373 }
00374
00388 void init(Intent intent) {
00389 URI uri;
00390 try {
00391 roconDescription = (RoconDescription) intent.getSerializableExtra(RoconDescription.UNIQUE_KEY);
00392 validatedConcert = false;
00393 validateConcert(roconDescription.getMasterId());
00394
00395 uri = new URI(roconDescription.getMasterId().getMasterUri());
00396 Log.i("Remocon", "init(Intent) - master uri is " + uri.toString());
00397 } catch (ClassCastException e) {
00398 Log.e("Remocon", "Cannot get concert description from intent. " + e.getMessage());
00399 throw new RosRuntimeException(e);
00400 } catch (URISyntaxException e) {
00401 throw new RosRuntimeException(e);
00402 }
00403 nodeMainExecutorService.setMasterUri(uri);
00404
00405
00406
00407
00408
00409
00410 if (roconDescription.getCurrentRole() == null) {
00411 chooseRole();
00412 }
00413 else {
00414 new AsyncTask<Void, Void, Void>() {
00415 @Override
00416 protected Void doInBackground(Void... params) {
00417 while (!validatedConcert) {
00418
00419 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
00420 }
00421 Log.i("Remocon", "init(Intent) passing control back to init(nodeMainExecutorService)");
00422 Remocon.this.init(nodeMainExecutorService);
00423 return null;
00424 }
00425 }.execute();
00426 }
00427 }
00428
00429 private void chooseRole() {
00430 Log.i("Remocon", "concert chosen; show choose user role dialog");
00431 roconDescription.setCurrentRole(-1);
00432
00433 AlertDialog.Builder builder = new AlertDialog.Builder(Remocon.this);
00434 builder.setTitle("Choose your role");
00435 builder.setSingleChoiceItems(roconDescription.getUserRoles(), -1,
00436 new DialogInterface.OnClickListener() {
00437 @Override
00438 public void onClick(DialogInterface dialog, int selectedRole) {
00439 roconDescription.setCurrentRole(selectedRole);
00440 String role = roconDescription.getCurrentRole();
00441 Toast.makeText(Remocon.this, role + " selected", Toast.LENGTH_SHORT).show();
00442 dialog.dismiss();
00443
00444 new AsyncTask<Void, Void, Void>() {
00445 @Override
00446 protected Void doInBackground(Void... params) {
00447 while (!validatedConcert) {
00448
00449 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
00450 }
00451 Remocon.this.init(nodeMainExecutorService);
00452 return null;
00453 }
00454 }.execute();
00455 }
00456 });
00457 AlertDialog alert = builder.create();
00458 alert.show();
00459 }
00460
00470 @Override
00471 public void onActivityResult(int requestCode, int resultCode, Intent data) {
00472 Log.i("Remocon", "onActivityResult [" + requestCode + "][" + resultCode + "]");
00473 if (resultCode == RESULT_CANCELED) {
00474 Log.i("Remocon", "activityResult...RESULT_CANCELLED");
00475 finish();
00476 } else if (resultCode == RESULT_OK) {
00477 Log.i("Remocon", "activityResult...RESULT_OK");
00478 if (requestCode == CONCERT_MASTER_CHOOSER_REQUEST_CODE) {
00479 init(data);
00480 } else {
00481
00482 nodeMainExecutorService.shutdown();
00483 finish();
00484 }
00485 } else {
00486 Log.w("Remocon", "activityResult...??? [" + resultCode + "]");
00487 }
00488 }
00489
00497 @Override
00498 public void startMasterChooser() {
00499 if (!fromApplication && !fromNfcLauncher) {
00500 super.startActivityForResult(new Intent(this,
00501 MasterChooser.class),
00502 CONCERT_MASTER_CHOOSER_REQUEST_CODE);
00503 }
00504 }
00505
00506 public void validateConcert(final MasterId id) {
00507
00508 launchInteractionDialog = new LaunchInteractionDialog(this);
00509 wifiDialog = new AlertDialogWrapper(this, new AlertDialog.Builder(this)
00510 .setTitle("Change Wifi?").setCancelable(false), "Yes", "No");
00511 evictDialog = new AlertDialogWrapper(this,
00512 new AlertDialog.Builder(this).setTitle("Evict User?")
00513 .setCancelable(false), "Yes", "No");
00514 errorDialog = new AlertDialogWrapper(this,
00515 new AlertDialog.Builder(this).setTitle("Could Not Connect")
00516 .setCancelable(false), "Ok");
00517 progressDialog = new ProgressDialogWrapper(this);
00518 final AlertDialogWrapper wifiDialog = new AlertDialogWrapper(this,
00519 new AlertDialog.Builder(this).setTitle("Change Wifi?")
00520 .setCancelable(false), "Yes", "No");
00521
00522
00523 final ConcertChecker cc = new ConcertChecker(
00524 new ConcertChecker.ConcertDescriptionReceiver() {
00525 public void receive(RoconDescription concertDescription) {
00526 progressDialog.dismiss();
00527 if(!fromApplication) {
00528
00529 if ( concertDescription.getConnectionStatus() == RoconDescription.UNAVAILABLE ) {
00530 errorDialog.show("Concert is unavailable : busy serving another remote controller.");
00531 errorDialog.dismiss();
00532 startMasterChooser();
00533 } else {
00534 validatedConcert = true;
00535 }
00536 } else {
00537
00538
00539
00540 validatedConcert = true;
00541 }
00542 }
00543 }, new ConcertChecker.FailureHandler() {
00544 public void handleFailure(String reason) {
00545 final String reason2 = reason;
00546
00547 progressDialog.dismiss();
00548 errorDialog.show("Cannot contact ROS master: " + reason2);
00549 errorDialog.dismiss();
00550
00551 finish();
00552 }
00553 });
00554
00555
00556 final WifiChecker wc = new WifiChecker(
00557 new WifiChecker.SuccessHandler() {
00558 public void handleSuccess() {
00559 progressDialog.show("Checking...", "Starting connection process");
00560 cc.beginChecking(id);
00561 }
00562 }, new WifiChecker.FailureHandler() {
00563 public void handleFailure(String reason) {
00564 final String reason2 = reason;
00565 progressDialog.dismiss();
00566 errorDialog.show("Cannot connect to concert WiFi: " + reason2);
00567 errorDialog.dismiss();
00568 finish();
00569 }
00570 }, new WifiChecker.ReconnectionHandler() {
00571 public boolean doReconnection(String from, String to) {
00572 progressDialog.dismiss();
00573 if (from == null) {
00574 wifiDialog.setMessage("To interact with this master, you must connect to " + to
00575 + "\nDo you want to connect to " + to + "?");
00576 } else {
00577 wifiDialog.setMessage("To interact with this master, you must switch wifi networks"
00578 + "\nDo you want to switch from " + from + " to " + to + "?");
00579 }
00580
00581 progressDialog.show("Checking...", "Switching wifi networks");
00582 return wifiDialog.show();
00583 }
00584 });
00585 progressDialog.show("Connecting...", "Checking wifi connection");
00586 wc.beginChecking(id, (WifiManager) getSystemService(WIFI_SERVICE));
00587 }
00588
00589 protected void updateAppList(final ArrayList<Interaction> apps, final String master_name, final String role) {
00590 Log.d("Remocon", "updating app list gridview");
00591 selectedInteraction = null;
00592 concertNameView.setText(master_name + " - " + role);
00593 GridView gridview = (GridView) findViewById(R.id.gridview);
00594 AppAdapter appAdapter = new AppAdapter(Remocon.this, apps);
00595 gridview.setAdapter(appAdapter);
00596 registerForContextMenu(gridview);
00597 gridview.setOnItemClickListener(new OnItemClickListener() {
00598 @Override
00599 public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
00600 selectedInteraction = apps.get(position);
00601 progressDialog.show("Requesting app...", "Requesting permission to use "
00602 + selectedInteraction.getDisplayName());
00603 interactionsManager.requestAppUse(roconDescription.getMasterId(), role, selectedInteraction);
00604 statusPublisher.update(true, selectedInteraction.getHash(), selectedInteraction.getName());
00605
00606 }
00607 });
00608 Log.d("Remocon", "app list gridview updated");
00609 }
00610
00614 public void changeRoleClicked(View view) {
00615 chooseRole();
00616 }
00617
00623 public void leaveConcertClicked(View view) {
00624 availableAppsCache.clear();
00625 startActivityForResult(new Intent(this, MasterChooser.class),
00626 CONCERT_MASTER_CHOOSER_REQUEST_CODE);
00627
00628 nodeMainExecutorService.shutdownNodeMain(statusPublisher);
00629 nodeMainExecutorService.shutdownNodeMain(pairSubscriber);
00630
00631 interactionsManager.shutdown();
00632 }
00633
00634 @Override
00635 public boolean onCreateOptionsMenu(Menu menu) {
00636 menu.add(0, 0, 0, R.string.exit);
00637 return super.onCreateOptionsMenu(menu);
00638 }
00639
00640 @Override
00641 public boolean onOptionsItemSelected(MenuItem item) {
00642 super.onOptionsItemSelected(item);
00643 switch (item.getItemId()) {
00644 case 0:
00645 finish();
00646 break;
00647 }
00648 return true;
00649 }
00650
00651 @Override
00652 public void onBackPressed() {
00653 Log.i("Remocon", "Press Back Button");
00654 leaveConcertClicked(null);
00655 }
00656
00657 }