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 package com.github.rosjava.android_remocons.robot_remocon;
00036
00037 import android.app.Activity;
00038 import android.app.AlertDialog;
00039 import android.app.Dialog;
00040 import android.content.ContentValues;
00041 import android.content.Context;
00042 import android.content.DialogInterface;
00043 import android.content.Intent;
00044 import android.database.Cursor;
00045 import android.net.Uri;
00046 import android.os.Bundle;
00047 import android.text.Spannable;
00048 import android.text.Spannable.Factory;
00049 import android.text.style.ForegroundColorSpan;
00050 import android.text.style.RelativeSizeSpan;
00051 import android.util.Log;
00052 import android.util.SparseBooleanArray;
00053 import android.view.KeyEvent;
00054 import android.view.LayoutInflater;
00055 import android.view.Menu;
00056 import android.view.MenuInflater;
00057 import android.view.MenuItem;
00058 import android.view.View;
00059 import android.widget.AdapterView;
00060 import android.widget.AdapterView.OnItemClickListener;
00061 import android.widget.Button;
00062 import android.widget.EditText;
00063 import android.widget.ListView;
00064 import android.widget.TextView;
00065 import android.widget.Toast;
00066
00067 import com.github.rosjava.android_apps.application_management.RobotDescription;
00068 import com.github.rosjava.android_apps.application_management.RobotId;
00069 import com.github.rosjava.android_apps.application_management.RobotsContentProvider;
00070 import com.github.rosjava.android_remocons.robot_remocon.zeroconf.MasterSearcher;
00071 import com.github.rosjava.zeroconf_jmdns_suite.jmdns.DiscoveredService;
00072 import com.google.zxing.IntentIntegrator;
00073 import com.google.zxing.IntentResult;
00074
00075 import org.yaml.snakeyaml.Yaml;
00076
00077 import java.util.ArrayList;
00078 import java.util.HashMap;
00079 import java.util.Iterator;
00080 import java.util.List;
00081 import java.util.Map;
00082 import java.util.Timer;
00083 import java.util.TimerTask;
00084
00089 public class RobotMasterChooser extends Activity {
00090
00091 private static final int ADD_URI_DIALOG_ID = 0;
00092 private static final int ADD_DELETION_DIALOG_ID = 1;
00093 private static final int ADD_SEARCH_ROBOT_DIALOG_ID = 2;
00094
00095 private static final int QR_CODE_SCAN_REQUEST_CODE = 101;
00096 private static final int NFC_TAG_SCAN_REQUEST_CODE = 102;
00097
00098 private List<RobotDescription> robots;
00099 private boolean[] selections;
00100 private MasterSearcher masterSearcher;
00101 private ListView listView;
00102
00103 public RobotMasterChooser() {
00104 robots = new ArrayList<RobotDescription>();
00105 }
00106
00107 private void readRobotList() {
00108 String str = null;
00109 Cursor c = getContentResolver().query(
00110 RobotsContentProvider.CONTENT_URI, null, null, null, null);
00111 if (c == null) {
00112 robots = new ArrayList<RobotDescription>();
00113 Log.e("RobotRemocon", "robot master chooser provider failed!!!");
00114 return;
00115 }
00116 if (c.getCount() > 0) {
00117 c.moveToFirst();
00118 str = c.getString(c
00119 .getColumnIndex(RobotsContentProvider.TABLE_COLUMN));
00120 Log.i("RobotRemocon", "robot master chooser found a robot: " + str);
00121 }
00122 if (str != null) {
00123 Yaml yaml = new Yaml();
00124 robots = (List<RobotDescription>) yaml.load(str);
00125 } else {
00126 robots = new ArrayList<RobotDescription>();
00127 }
00128 }
00129
00130 public void writeRobotList() {
00131 Log.i("RobotRemocon", "robot master chooser saving robot...");
00132 Yaml yaml = new Yaml();
00133 String txt = null;
00134 final List<RobotDescription> robot = robots;
00135 if (robot != null) {
00136 txt = yaml.dump(robot);
00137 }
00138 ContentValues cv = new ContentValues();
00139 cv.put(RobotsContentProvider.TABLE_COLUMN, txt);
00140 Uri newEmp = getContentResolver().insert(
00141 RobotsContentProvider.CONTENT_URI, cv);
00142 if (newEmp != RobotsContentProvider.CONTENT_URI) {
00143 Log.e("RobotRemocon", "robot master chooser could not save robot, non-equal URI's");
00144 }
00145 }
00146
00147 private void refresh() {
00148 readRobotList();
00149 updateListView();
00150 }
00151
00152 private void updateListView() {
00153 setContentView(R.layout.robot_master_chooser);
00154 ListView listview = (ListView) findViewById(R.id.master_list);
00155 listview.setAdapter(new MasterAdapter(this, robots));
00156 registerForContextMenu(listview);
00157
00158 listview.setOnItemClickListener(new OnItemClickListener() {
00159 @Override
00160 public void onItemClick(AdapterView<?> parent, View v,
00161 int position, long id) {
00162 choose(position);
00163 }
00164 });
00165 }
00166
00175 private void choose(int position) {
00176 RobotDescription robot = robots.get(position);
00177 if (robot == null || robot.getConnectionStatus() == null
00178 || robot.getConnectionStatus().equals(robot.ERROR)) {
00179 AlertDialog d = new AlertDialog.Builder(RobotMasterChooser.this)
00180 .setTitle("Error!")
00181 .setCancelable(false)
00182 .setMessage("Failed: Cannot contact robot")
00183 .setNeutralButton("OK",
00184 new DialogInterface.OnClickListener() {
00185 public void onClick(DialogInterface dialog,
00186 int which) {
00187 }
00188 }).create();
00189 d.show();
00190 } else if ( robot.getConnectionStatus().equals(robot.UNAVAILABLE) ) {
00191 AlertDialog d = new AlertDialog.Builder(RobotMasterChooser.this)
00192 .setTitle("Robot Unavailable!")
00193 .setCancelable(false)
00194 .setMessage("Currently busy serving another.")
00195 .setNeutralButton("OK",
00196 new DialogInterface.OnClickListener() {
00197 public void onClick(DialogInterface dialog,
00198 int which) {
00199 }
00200 }).create();
00201 d.show();
00202 } else {
00203 Intent resultIntent = new Intent();
00204 resultIntent
00205 .putExtra(RobotDescription.UNIQUE_KEY, robots.get(position));
00206 setResult(RESULT_OK, resultIntent);
00207 finish();
00208 }
00209 }
00210
00211 private void addMaster(RobotId robotId) {
00212 addMaster(robotId, false);
00213 }
00214
00215 private void addMaster(RobotId robotId, boolean connectToDuplicates) {
00216 Log.i("MasterChooserActivity", "adding master to the robot master chooser [" + robotId.toString() + "]");
00217 if (robotId == null || robotId.getMasterUri() == null) {
00218 } else {
00219 for (int i = 0; i < robots.toArray().length; i++) {
00220 RobotDescription robot = robots.get(i);
00221 if (robot.getRobotId().equals(robotId)) {
00222 if (connectToDuplicates) {
00223 choose(i);
00224 return;
00225 } else {
00226 Toast.makeText(this, "That robot is already listed.",
00227 Toast.LENGTH_SHORT).show();
00228 return;
00229 }
00230 }
00231 }
00232 Log.i("MasterChooserActivity", "creating robot description: "
00233 + robotId.toString());
00234 robots.add(RobotDescription.createUnknown(robotId));
00235 Log.i("MasterChooserActivity", "description created");
00236 onRobotsChanged();
00237 }
00238 }
00239
00240 private void onRobotsChanged() {
00241 writeRobotList();
00242 updateListView();
00243 }
00244
00245 private void deleteAllRobots() {
00246 robots.clear();
00247 onRobotsChanged();
00248 }
00249
00250 private void deleteSelectedRobots(boolean[] array) {
00251 int j = 0;
00252 for (int i = 0; i < array.length; i++) {
00253 if (array[i]) {
00254 robots.remove(j);
00255 } else {
00256 j++;
00257 }
00258 }
00259 onRobotsChanged();
00260 }
00261
00262 private void deleteUnresponsiveRobots() {
00263 Iterator<RobotDescription> iter = robots.iterator();
00264 while (iter.hasNext()) {
00265 RobotDescription robot = iter.next();
00266 if (robot == null || robot.getConnectionStatus() == null
00267 || robot.getConnectionStatus().equals(robot.ERROR)) {
00268 Log.i("RobotRemocon", "robot master chooser removing robot with connection status '"
00269 + robot.getConnectionStatus() + "'");
00270 iter.remove();
00271 }
00272 }
00273 onRobotsChanged();
00274 }
00275
00276 @Override
00277 protected void onCreate(Bundle savedInstanceState) {
00278 super.onCreate(savedInstanceState);
00279 readRobotList();
00280 updateListView();
00281
00282 }
00283
00284 @Override
00285 public void onActivityResult(int requestCode, int resultCode, Intent intent) {
00286
00287
00288
00289 if (resultCode == RESULT_CANCELED) {
00290 Toast.makeText(this, "Cancelled", Toast.LENGTH_SHORT).show();
00291 return;
00292 }
00293
00294 String scanned_data = null;
00295
00296 if (requestCode == QR_CODE_SCAN_REQUEST_CODE) {
00297 IntentResult scanResult = IntentIntegrator.parseActivityResult(
00298 requestCode, resultCode, intent);
00299 if (scanResult != null && scanResult.getContents() != null) {
00300 scanned_data = scanResult.getContents().toString();
00301 }
00302 }
00303 else if (requestCode == NFC_TAG_SCAN_REQUEST_CODE && resultCode == RESULT_OK) {
00304 if (intent.hasExtra("tag_data")) {
00305 scanned_data = intent.getExtras().getString("tag_data");
00306 }
00307 }
00308 else {
00309 Log.w("RobotRemocon", "Unknown activity request code: " + requestCode);
00310 return;
00311 }
00312
00313 if (scanned_data == null) {
00314 Toast.makeText(this, "Scan failed", Toast.LENGTH_SHORT).show();
00315 }
00316 else {
00317 try {
00318 Yaml yaml = new Yaml();
00319 Map<String, Object> data = (Map<String, Object>) yaml.load(scanned_data);
00320 Log.d("RobotRemocon", "RobotMasterChooser OBJECT: " + data.toString());
00321 addMaster(new RobotId(data), false);
00322 } catch (Exception e) {
00323 Toast.makeText(this,
00324 "Invalid robot description: " + e.getMessage(),
00325 Toast.LENGTH_SHORT).show();
00326 }
00327 }
00328 }
00329
00330 @Override
00331 protected Dialog onCreateDialog(int id) {
00332 readRobotList();
00333 final Dialog dialog;
00334 Button button;
00335 AlertDialog.Builder builder;
00336 switch (id) {
00337 case ADD_URI_DIALOG_ID:
00338 dialog = new Dialog(this);
00339 dialog.setContentView(R.layout.add_uri_dialog);
00340 dialog.setTitle("Add a robot");
00341 dialog.setOnKeyListener(new DialogKeyListener());
00342 EditText uriField = (EditText) dialog.findViewById(R.id.uri_editor);
00343 EditText controlUriField = (EditText) dialog
00344 .findViewById(R.id.control_uri_editor);
00345 uriField.setText("http://localhost:11311/",
00346 TextView.BufferType.EDITABLE);
00347
00348
00349 button = (Button) dialog.findViewById(R.id.enter_button);
00350 button.setOnClickListener(new View.OnClickListener() {
00351 public void onClick(View v) {
00352 enterRobotInfo(dialog);
00353 removeDialog(ADD_URI_DIALOG_ID);
00354 }
00355 });
00356 button = (Button) dialog.findViewById(R.id.qr_code_button);
00357 button.setOnClickListener(new View.OnClickListener() {
00358 @Override
00359 public void onClick(View v) {
00360 scanQRCodeClicked(v);
00361 }
00362 });
00363 button = (Button) dialog.findViewById(R.id.nfc_tag_button);
00364 button.setOnClickListener(new View.OnClickListener() {
00365 @Override
00366 public void onClick(View v) {
00367 scanNFCTagClicked(v);
00368 }
00369 });
00370 button = (Button) dialog.findViewById(R.id.search_master_button);
00371 button.setOnClickListener(new View.OnClickListener() {
00372 @Override
00373 public void onClick(View v) {
00374 searchRobotClicked(v);
00375 }
00376 });
00377
00378 button = (Button) dialog.findViewById(R.id.cancel_button);
00379 button.setOnClickListener(new View.OnClickListener() {
00380 @Override
00381 public void onClick(View v) {
00382 removeDialog(ADD_URI_DIALOG_ID);
00383 }
00384 });
00385 break;
00386 case ADD_DELETION_DIALOG_ID:
00387 builder = new AlertDialog.Builder(this);
00388 String newline = System.getProperty("line.separator");
00389 if (robots.size() > 0) {
00390 selections = new boolean[robots.size()];
00391 Spannable[] robot_names = new Spannable[robots.size()];
00392 Spannable name;
00393 for (int i = 0; i < robots.size(); i++) {
00394 name = Factory.getInstance().newSpannable(
00395 robots.get(i).getRobotName() + newline
00396 + robots.get(i).getRobotId());
00397 name.setSpan(new ForegroundColorSpan(0xff888888), robots
00398 .get(i).getRobotName().length(), name.length(),
00399 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
00400 name.setSpan(new RelativeSizeSpan(0.8f), robots.get(i)
00401 .getRobotName().length(), name.length(),
00402 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
00403 robot_names[i] = name;
00404 }
00405 builder.setTitle("Delete a robot");
00406 builder.setMultiChoiceItems(robot_names, selections,
00407 new DialogSelectionClickHandler());
00408 builder.setPositiveButton("Delete Selections",
00409 new DeletionDialogButtonClickHandler());
00410 builder.setNegativeButton("Cancel",
00411 new DeletionDialogButtonClickHandler());
00412 dialog = builder.create();
00413 } else {
00414 builder.setTitle("No robots to delete.");
00415 dialog = builder.create();
00416 final Timer t = new Timer();
00417 t.schedule(new TimerTask() {
00418 public void run() {
00419 removeDialog(ADD_DELETION_DIALOG_ID);
00420 }
00421 }, 2 * 1000);
00422 }
00423 break;
00424 case ADD_SEARCH_ROBOT_DIALOG_ID:
00425 builder = new AlertDialog.Builder(this);
00426 builder.setTitle("Scanning on the local network...");
00427 LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
00428 listView = (ListView) layoutInflater.inflate(
00429 R.layout.zeroconf_master_list, null);
00430 masterSearcher = new MasterSearcher(this, listView);
00431 builder.setView(listView);
00432 builder.setPositiveButton("Select",
00433 new SearchRobotDialogButtonClickHandler());
00434 builder.setNegativeButton("Cancel",
00435 new SearchRobotDialogButtonClickHandler());
00436 dialog = builder.create();
00437 dialog.setOnKeyListener(new DialogKeyListener());
00438
00439 break;
00440 default:
00441 dialog = null;
00442 }
00443 return dialog;
00444 }
00445
00446 public class DialogSelectionClickHandler implements
00447 DialogInterface.OnMultiChoiceClickListener {
00448 public void onClick(DialogInterface dialog, int clicked,
00449 boolean selected) {
00450 return;
00451 }
00452 }
00453
00454 public class DeletionDialogButtonClickHandler implements
00455 DialogInterface.OnClickListener {
00456 public void onClick(DialogInterface dialog, int clicked) {
00457 switch (clicked) {
00458 case DialogInterface.BUTTON_POSITIVE:
00459 deleteSelectedRobots(selections);
00460 removeDialog(ADD_DELETION_DIALOG_ID);
00461 break;
00462 case DialogInterface.BUTTON_NEGATIVE:
00463 removeDialog(ADD_DELETION_DIALOG_ID);
00464 break;
00465 }
00466 }
00467 }
00468
00469 public class SearchRobotDialogButtonClickHandler implements
00470 DialogInterface.OnClickListener {
00471 public void onClick(DialogInterface dialog, int clicked) {
00472 switch (clicked) {
00473 case DialogInterface.BUTTON_POSITIVE:
00474 SparseBooleanArray positions = listView
00475 .getCheckedItemPositions();
00476
00477 for (int i = 0; i < positions.size(); i++) {
00478 if (positions.valueAt(i)) {
00479 enterRobotInfo((DiscoveredService) listView.getAdapter()
00480 .getItem(positions.keyAt(i)));
00481 }
00482 }
00483 removeDialog(ADD_DELETION_DIALOG_ID);
00484 break;
00485 case DialogInterface.BUTTON_NEGATIVE:
00486 removeDialog(ADD_DELETION_DIALOG_ID);
00487 break;
00488 }
00489 }
00490 }
00491
00492 public void enterRobotInfo(DiscoveredService discovered_service) {
00493
00494
00495
00496
00497
00498 String newMasterUri = null;
00499 if ( discovered_service.ipv4_addresses.size() != 0 ) {
00500 newMasterUri = "http://" + discovered_service.ipv4_addresses.get(0) + ":"
00501 + discovered_service.port + "/";
00502 }
00503 if (newMasterUri != null && newMasterUri.length() > 0) {
00504 android.util.Log.i("RobotRemocon", newMasterUri);
00505 Map<String, Object> data = new HashMap<String, Object>();
00506 data.put("URL", newMasterUri);
00507 try {
00508 addMaster(new RobotId(data));
00509 } catch (Exception e) {
00510 Toast.makeText(RobotMasterChooser.this, "Invalid Parameters.",
00511 Toast.LENGTH_SHORT).show();
00512 }
00513 } else {
00514 Toast.makeText(RobotMasterChooser.this, "No valid resolvable master URI.",
00515 Toast.LENGTH_SHORT).show();
00516 }
00517 }
00518
00519 public void enterRobotInfo(Dialog dialog) {
00520 EditText uriField = (EditText) dialog.findViewById(R.id.uri_editor);
00521 String newMasterUri = uriField.getText().toString();
00522 EditText controlUriField = (EditText) dialog
00523 .findViewById(R.id.control_uri_editor);
00524 String newControlUri = controlUriField.getText().toString();
00525 EditText wifiNameField = (EditText) dialog
00526 .findViewById(R.id.wifi_name_editor);
00527 String newWifiName = wifiNameField.getText().toString();
00528 EditText wifiPasswordField = (EditText) dialog
00529 .findViewById(R.id.wifi_password_editor);
00530 String newWifiPassword = wifiPasswordField.getText().toString();
00531 if (newMasterUri != null && newMasterUri.length() > 0) {
00532 Map<String, Object> data = new HashMap<String, Object>();
00533 data.put("URL", newMasterUri);
00534 if (newControlUri != null && newControlUri.length() > 0) {
00535 data.put("CURL", newControlUri);
00536 }
00537 if (newWifiName != null && newWifiName.length() > 0) {
00538 data.put("WIFI", newWifiName);
00539 }
00540 if (newWifiPassword != null && newWifiPassword.length() > 0) {
00541 data.put("WIFIPW", newWifiPassword);
00542 }
00543 try {
00544 addMaster(new RobotId(data));
00545 } catch (Exception e) {
00546 Toast.makeText(RobotMasterChooser.this, "Invalid Parameters.",
00547 Toast.LENGTH_SHORT).show();
00548 }
00549 } else {
00550 Toast.makeText(RobotMasterChooser.this, "Must specify Master URI.",
00551 Toast.LENGTH_SHORT).show();
00552 }
00553 }
00554
00555 public class DialogKeyListener implements DialogInterface.OnKeyListener {
00556 @Override
00557 public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
00558 if (event.getAction() == KeyEvent.ACTION_DOWN
00559 && keyCode == KeyEvent.KEYCODE_ENTER) {
00560 Dialog dlg = (Dialog) dialog;
00561 enterRobotInfo(dlg);
00562 removeDialog(ADD_URI_DIALOG_ID);
00563 return true;
00564 }
00565 return false;
00566 }
00567 }
00568
00569 public void addRobotClicked(View view) {
00570 showDialog(ADD_URI_DIALOG_ID);
00571 }
00572
00573 public void refreshClicked(View view) {
00574 refresh();
00575 }
00576
00577 public void scanQRCodeClicked(View view) {
00578 dismissDialog(ADD_URI_DIALOG_ID);
00579 IntentIntegrator.initiateScan(this, IntentIntegrator.DEFAULT_TITLE,
00580 IntentIntegrator.DEFAULT_MESSAGE, IntentIntegrator.DEFAULT_YES,
00581 IntentIntegrator.DEFAULT_NO, IntentIntegrator.QR_CODE_TYPES);
00582 }
00583
00584 public void scanNFCTagClicked(View view) {
00585 dismissDialog(ADD_URI_DIALOG_ID);
00586 Intent i = new Intent(this,
00587 com.github.rosjava.android_remocons.robot_remocon.nfc.ForegroundDispatch.class);
00588
00589 startActivityForResult(i, NFC_TAG_SCAN_REQUEST_CODE);
00590 }
00591
00592 public void searchRobotClicked(View view) {
00593 removeDialog(ADD_URI_DIALOG_ID);
00594 showDialog(ADD_SEARCH_ROBOT_DIALOG_ID);
00595
00596 }
00597
00598 @Override
00599 public boolean onCreateOptionsMenu(Menu menu) {
00600 MenuInflater inflater = getMenuInflater();
00601 inflater.inflate(R.menu.robot_master_chooser_option_menu, menu);
00602 return true;
00603 }
00604
00605 @Override
00606 public boolean onOptionsItemSelected(MenuItem item) {
00607 int id = item.getItemId();
00608 if (id == R.id.add_robot) {
00609 showDialog(ADD_URI_DIALOG_ID);
00610 return true;
00611 } else if (id == R.id.delete_selected) {
00612 showDialog(ADD_DELETION_DIALOG_ID);
00613 return true;
00614 } else if (id == R.id.delete_unresponsive) {
00615 deleteUnresponsiveRobots();
00616 return true;
00617 } else if (id == R.id.delete_all) {
00618 deleteAllRobots();
00619 return true;
00620 } else if (id == R.id.kill) {
00621 Intent intent = new Intent();
00622 setResult(RESULT_CANCELED, intent);
00623 finish();
00624 return true;
00625 } else {
00626 return super.onOptionsItemSelected(item);
00627 }
00628 }
00629 }