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