diff --git a/robot_remocon/src/main/AndroidManifest.xml b/robot_remocon/src/main/AndroidManifest.xml index 82fa3c7..6e37f45 100644 --- a/robot_remocon/src/main/AndroidManifest.xml +++ b/robot_remocon/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ + + + + + + + + + + + + + + diff --git a/robot_remocon/src/main/java/com/github/rosjava/android_remocons/robot_remocon/RobotMasterChooser.java b/robot_remocon/src/main/java/com/github/rosjava/android_remocons/robot_remocon/RobotMasterChooser.java index dd9b00f..dfee79d 100644 --- a/robot_remocon/src/main/java/com/github/rosjava/android_remocons/robot_remocon/RobotMasterChooser.java +++ b/robot_remocon/src/main/java/com/github/rosjava/android_remocons/robot_remocon/RobotMasterChooser.java @@ -34,16 +34,6 @@ package com.github.rosjava.android_remocons.robot_remocon; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; - -import com.github.rosjava.zeroconf_jmdns_suite.jmdns.DiscoveredService; - import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -74,16 +64,24 @@ import android.widget.TextView; import android.widget.Toast; -import com.google.zxing.IntentIntegrator; -import com.google.zxing.IntentResult; - import com.github.rosjava.android_apps.application_management.RobotDescription; import com.github.rosjava.android_apps.application_management.RobotId; import com.github.rosjava.android_apps.application_management.RobotsContentProvider; import com.github.rosjava.android_remocons.robot_remocon.zeroconf.MasterSearcher; +import com.github.rosjava.zeroconf_jmdns_suite.jmdns.DiscoveredService; +import com.google.zxing.IntentIntegrator; +import com.google.zxing.IntentResult; import org.yaml.snakeyaml.Yaml; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + /** * @author hersh@willowgarage.com * @author murase@jsk.imi.i.u-tokyo.ac.jp (Kazuto Murase) @@ -94,6 +92,9 @@ public class RobotMasterChooser extends Activity { private static final int ADD_DELETION_DIALOG_ID = 1; private static final int ADD_SEARCH_ROBOT_DIALOG_ID = 2; + private static final int QR_CODE_SCAN_REQUEST_CODE = 101; + private static final int NFC_TAG_SCAN_REQUEST_CODE = 102; + private List robots; private boolean[] selections; private MasterSearcher masterSearcher; @@ -282,25 +283,49 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { - IntentResult scanResult = IntentIntegrator.parseActivityResult( - requestCode, resultCode, intent); - if (scanResult != null && scanResult.getContents() != null) { - Yaml yaml = new Yaml(); - Map data = (Map) yaml - .load(scanResult.getContents().toString()); - Log.d("RobotRemocon", "RobotMasterChooser OBJECT: " + data.toString()); - try { - addMaster(new RobotId(data), false); - } catch (Exception e) { - Toast.makeText(this, - "Invalid robot description: " + e.getMessage(), - Toast.LENGTH_SHORT).show(); - } - } else { - Toast.makeText(this, "Scan failed", Toast.LENGTH_SHORT).show(); - } + // Sub-activity to gather robot connection data completed: can be QR code or NFC tag scan + // TODO: cannot unify both calls? - } + if (resultCode == RESULT_CANCELED) { + Toast.makeText(this, "Cancelled", Toast.LENGTH_SHORT).show(); + return; + } + + String scanned_data = null; + + if (requestCode == QR_CODE_SCAN_REQUEST_CODE) { + IntentResult scanResult = IntentIntegrator.parseActivityResult( + requestCode, resultCode, intent); + if (scanResult != null && scanResult.getContents() != null) { + scanned_data = scanResult.getContents().toString(); + } + } + else if (requestCode == NFC_TAG_SCAN_REQUEST_CODE && resultCode == RESULT_OK) { + if (intent.hasExtra("tag_data")) { + scanned_data = intent.getExtras().getString("tag_data"); + } + } + else { + Log.w("RobotRemocon", "Unknown activity request code: " + requestCode); + return; + } + + if (scanned_data == null) { + Toast.makeText(this, "Scan failed", Toast.LENGTH_SHORT).show(); + } + else { + try { + Yaml yaml = new Yaml(); + Map data = (Map) yaml.load(scanned_data); + Log.d("RobotRemocon", "RobotMasterChooser OBJECT: " + data.toString()); + addMaster(new RobotId(data), false); + } catch (Exception e) { + Toast.makeText(this, + "Invalid robot description: " + e.getMessage(), + Toast.LENGTH_SHORT).show(); + } + } + } @Override protected Dialog onCreateDialog(int id) { @@ -328,13 +353,20 @@ public void onClick(View v) { removeDialog(ADD_URI_DIALOG_ID); } }); - button = (Button) dialog.findViewById(R.id.scan_robot_button); - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - scanRobotClicked(v); - } - }); + button = (Button) dialog.findViewById(R.id.qr_code_button); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + scanQRCodeClicked(v); + } + }); + button = (Button) dialog.findViewById(R.id.nfc_tag_button); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + scanNFCTagClicked(v); + } + }); button = (Button) dialog.findViewById(R.id.search_master_button); button.setOnClickListener(new View.OnClickListener() { @Override @@ -542,12 +574,20 @@ public void refreshClicked(View view) { refresh(); } - public void scanRobotClicked(View view) { - dismissDialog(ADD_URI_DIALOG_ID); - IntentIntegrator.initiateScan(this, IntentIntegrator.DEFAULT_TITLE, - IntentIntegrator.DEFAULT_MESSAGE, IntentIntegrator.DEFAULT_YES, - IntentIntegrator.DEFAULT_NO, IntentIntegrator.QR_CODE_TYPES); - } + public void scanQRCodeClicked(View view) { + dismissDialog(ADD_URI_DIALOG_ID); + IntentIntegrator.initiateScan(this, IntentIntegrator.DEFAULT_TITLE, + IntentIntegrator.DEFAULT_MESSAGE, IntentIntegrator.DEFAULT_YES, + IntentIntegrator.DEFAULT_NO, IntentIntegrator.QR_CODE_TYPES); + } + + public void scanNFCTagClicked(View view) { + dismissDialog(ADD_URI_DIALOG_ID); + Intent i = new Intent(this, + com.github.rosjava.android_remocons.robot_remocon.nfc.ForegroundDispatch.class); + // Set the request code so we can identify the callback via this code + startActivityForResult(i, NFC_TAG_SCAN_REQUEST_CODE); + } public void searchRobotClicked(View view) { removeDialog(ADD_URI_DIALOG_ID); diff --git a/robot_remocon/src/main/java/com/github/rosjava/android_remocons/robot_remocon/nfc/ForegroundDispatch.java b/robot_remocon/src/main/java/com/github/rosjava/android_remocons/robot_remocon/nfc/ForegroundDispatch.java new file mode 100644 index 0000000..e6f69c2 --- /dev/null +++ b/robot_remocon/src/main/java/com/github/rosjava/android_remocons/robot_remocon/nfc/ForegroundDispatch.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.github.rosjava.android_remocons.robot_remocon.nfc; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentFilter.MalformedMimeTypeException; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.NfcAdapter; +import android.nfc.tech.*; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.Log; +import android.widget.TextView; + +import com.github.rosjava.android_remocons.robot_remocon.R; + +/** + * An example of how to use the NFC foreground dispatch APIs. This will intercept any MIME data + * based NDEF dispatch as well as all dispatched for NfcF tags. + */ +public class ForegroundDispatch extends Activity { + private NfcAdapter mAdapter; + private PendingIntent mPendingIntent; + private IntentFilter[] mFilters; + private String[][] mTechLists; + private TextView mText; + private int mCount = 0; + private String tag_data = null; + + @Override + public void onCreate(Bundle savedState) { + super.onCreate(savedState); + + setContentView(R.layout.nfc_tag_scan); + mText = (TextView) findViewById(R.id.text); + mText.setText("Scan a NFC tag"); + + mAdapter = NfcAdapter.getDefaultAdapter(this); + + // Create a generic PendingIntent that will be deliver to this activity. The NFC stack + // will fill in the intent with the details of the discovered tag before delivering to + // this activity. + mPendingIntent = PendingIntent.getActivity(this, 0, + new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); + + // Setup an intent filter for all MIME based dispatches + IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED); + try { + ndef.addDataType("text/plain"); + } catch (MalformedMimeTypeException e) { + throw new RuntimeException("fail", e); + } + mFilters = new IntentFilter[] { + ndef, + }; + + // Setup a tech list for all NfcF tags + mTechLists = new String[][] { new String[] { NfcF.class.getName() }, + new String[] { NfcA.class.getName(), MifareClassic.class.getName() }, + new String[] { NfcA.class.getName(), MifareUltralight.class.getName() } }; + } + + @Override + public void onResume() { + super.onResume(); + if (mAdapter != null) mAdapter.enableForegroundDispatch(this, mPendingIntent, mFilters, + mTechLists); + } + + @Override + public void onNewIntent(Intent intent) { + Log.i("Foreground dispatch", "Discovered tag with intent: " + intent); + mText.setText("Discovered tag " + ++mCount + " with intent: " + intent); + + if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) { + Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); + if ((rawMsgs != null) && (rawMsgs.length > 0)) { + NdefMessage msg = (NdefMessage) rawMsgs[0]; + NdefRecord[] records = msg.getRecords(); + if ((records != null) && (records.length > 0)) { + String payload = new String(records[0].getPayload()); + tag_data = payload.substring(3); // Remove data type (1 byte) and language code (2 bytes) + mText.append("\n\n\n" + tag_data); + finish(); // TODO add ok/cancel buttons instead + } + } + +// if (payload2[0] != NdefRecord.TNF_MIME_MEDIA) + } + + mText.setText("Unrecognized NFC tag format"); + } + + @Override + public void onPause() { + super.onPause(); + if (mAdapter != null) mAdapter.disableForegroundDispatch(this); + } + + @Override + public void finish() { + // Prepare data in tent + Intent data = new Intent(); + if (tag_data != null) { + // Activity finished ok, return the data + data.putExtra("tag_data", tag_data); + setResult(RESULT_OK, data); + } + else { + setResult(RESULT_CANCELED, data); + } + + super.finish(); + } +} diff --git a/robot_remocon/src/main/res/drawable-hdpi/nfc_tag.png b/robot_remocon/src/main/res/drawable-hdpi/nfc_tag.png new file mode 100644 index 0000000..d5c7674 Binary files /dev/null and b/robot_remocon/src/main/res/drawable-hdpi/nfc_tag.png differ diff --git a/robot_remocon/src/main/res/drawable-ldpi/nfc_tag.png b/robot_remocon/src/main/res/drawable-ldpi/nfc_tag.png new file mode 100644 index 0000000..d5c7674 Binary files /dev/null and b/robot_remocon/src/main/res/drawable-ldpi/nfc_tag.png differ diff --git a/robot_remocon/src/main/res/drawable-mdpi/nfc_tag.png b/robot_remocon/src/main/res/drawable-mdpi/nfc_tag.png new file mode 100644 index 0000000..d5c7674 Binary files /dev/null and b/robot_remocon/src/main/res/drawable-mdpi/nfc_tag.png differ diff --git a/robot_remocon/src/main/res/layout-land/add_uri_dialog.xml b/robot_remocon/src/main/res/layout-land/add_uri_dialog.xml index ef2602d..5878055 100644 --- a/robot_remocon/src/main/res/layout-land/add_uri_dialog.xml +++ b/robot_remocon/src/main/res/layout-land/add_uri_dialog.xml @@ -4,7 +4,8 @@ android:layout_height="wrap_content"> + android:layout_height="fill_parent" + android:fillViewport="true"> -