452 lines
14 KiB
Java
452 lines
14 KiB
Java
/*
|
|
* Copyright (C) 2013 Miguel Angel Astor Romero
|
|
*
|
|
* 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 ve.ucv.ciens.ccg.nxtcam;
|
|
|
|
import java.io.IOException;
|
|
import java.net.DatagramPacket;
|
|
import java.net.InetAddress;
|
|
import java.net.MulticastSocket;
|
|
import java.net.Socket;
|
|
|
|
import ve.ucv.ciens.ccg.nxtcam.dialogs.ConnectRobotDialog;
|
|
import ve.ucv.ciens.ccg.nxtcam.dialogs.ConnectRobotDialog.ConnectRobotDialogListener;
|
|
import ve.ucv.ciens.ccg.nxtcam.dialogs.WifiOnDialog;
|
|
import ve.ucv.ciens.ccg.nxtcam.dialogs.WifiOnDialog.WifiOnDialogListener;
|
|
import ve.ucv.ciens.ccg.nxtcam.network.BTCommunicator;
|
|
import ve.ucv.ciens.ccg.nxtcam.utils.Logger;
|
|
import ve.ucv.ciens.ccg.nxtcam.utils.ProjectConstants;
|
|
import android.app.Activity;
|
|
import android.app.DialogFragment;
|
|
import android.app.ProgressDialog;
|
|
import android.bluetooth.BluetoothAdapter;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.net.wifi.WifiManager;
|
|
import android.net.wifi.WifiManager.MulticastLock;
|
|
import android.os.AsyncTask;
|
|
import android.os.Bundle;
|
|
import android.view.Menu;
|
|
import android.view.View;
|
|
import android.widget.Button;
|
|
import android.widget.Toast;
|
|
|
|
/**
|
|
* Entry point por the NxtCAM application.
|
|
*
|
|
* This activity shows a splashscreen and handles the search for the controller device
|
|
* via a simle ad hoc UDP based service discovery method. Basically, it just listens for
|
|
* multicast packets sent by the controller device on the multicast address defined in
|
|
* ProjectConstants.java. When the packet is received the next activity of the application
|
|
* is launched with the ip address found. The service discovery process continues until a
|
|
* datagram carrying the string "NxtAR server here!" is received.
|
|
*
|
|
* @author miky
|
|
*/
|
|
public class MainActivity extends Activity implements WifiOnDialogListener, ConnectRobotDialogListener{
|
|
// Cosntant fields.
|
|
private final String TAG = "NXTCAM_MAIN";
|
|
private final String CLASS_NAME = MainActivity.class.getSimpleName();
|
|
private static final int REQUEST_ENABLE_BT = 1;
|
|
private static final int REQUEST_CAM_ACTIVITY = 2;
|
|
|
|
// Gui components
|
|
private Button startButton;
|
|
private Button connectButton;
|
|
private ProgressDialog progressDialog;
|
|
|
|
// Resources.
|
|
private BTCommunicator btManager;
|
|
private WifiManager wifiManager;
|
|
|
|
// Variables.
|
|
private boolean wifiOnByMe;
|
|
private boolean btOnByMe;
|
|
private boolean changingActivity;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
setContentView(R.layout.activity_main);
|
|
|
|
// Set up fields.
|
|
wifiOnByMe = false;
|
|
btOnByMe = false;
|
|
changingActivity = false;
|
|
|
|
// Set up gui components.
|
|
startButton = (Button)findViewById(R.id.startButton);
|
|
startButton.setEnabled(false);
|
|
connectButton = (Button)findViewById(R.id.connectButton);
|
|
|
|
// Set up services.
|
|
btManager = BTCommunicator.getInstance();
|
|
wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);
|
|
|
|
if(!btManager.isBTSupported()){
|
|
// El dispositivo no soporta BlueTooth.
|
|
Toast.makeText(this, R.string.bt_no_support, Toast.LENGTH_LONG).show();
|
|
finish();
|
|
System.exit(0);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onResume(){
|
|
super.onResume();
|
|
|
|
if(!btManager.isBTEnabled()){
|
|
enableBT();
|
|
}else if(btManager.isBTEnabled() && !wifiManager.isWifiEnabled()){
|
|
enableWifi();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onPause(){
|
|
super.onPause();
|
|
|
|
if(!changingActivity){
|
|
if(btManager.isBTEnabled() && btOnByMe)
|
|
btManager.disableBT();
|
|
if(wifiManager.isWifiEnabled() && wifiOnByMe)
|
|
setWifi(false);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy(){
|
|
super.onDestroy();
|
|
if(btManager.isConnected()){
|
|
try{
|
|
btManager.stopConnection();
|
|
}catch(IOException io){
|
|
Logger.log_e(TAG, CLASS_NAME + ".onDestroy() :: Error closing the connection with the robot: " + io.getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
// Inflate the menu; this adds items to the action bar if it is present.
|
|
getMenuInflater().inflate(R.menu.main, menu);
|
|
return true;
|
|
}
|
|
|
|
protected void onActivityResult(int request, int result, Intent data){
|
|
if(request == REQUEST_ENABLE_BT){
|
|
if(result == RESULT_OK){
|
|
if(!wifiManager.isWifiEnabled())
|
|
enableWifi();
|
|
}else{
|
|
Toast.makeText(this, R.string.bt_on_fail, Toast.LENGTH_SHORT).show();
|
|
}
|
|
}else if(request == REQUEST_CAM_ACTIVITY){
|
|
changingActivity = false;
|
|
if(result == ProjectConstants.RESULT_CAMERA_FAILURE){
|
|
Toast.makeText(this, R.string.cam_fail, Toast.LENGTH_SHORT).show();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start the camera capture activity if a server was found through service discovery.
|
|
*
|
|
* @param serverFound Indicates if a server was found, doh!
|
|
* @param ipAddress The ip address of the server.
|
|
*/
|
|
private void startCamActivity(boolean serverFound, String ipAddress){
|
|
if(serverFound){
|
|
Logger.log_d(TAG, CLASS_NAME + ".startCamActivity() :: Launching camera activity.");
|
|
Intent intent = new Intent(this, CamActivity.class);
|
|
changingActivity = true;
|
|
intent.putExtra("address", ipAddress);
|
|
startActivityForResult(intent, REQUEST_CAM_ACTIVITY);
|
|
}else{
|
|
Logger.log_d(TAG, CLASS_NAME + ".startCamActivity() :: Cannot launch camera activity.");
|
|
Toast.makeText(this, R.string.badIpToast, Toast.LENGTH_SHORT).show();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the state of the device's WiFi radio.
|
|
*
|
|
* @param radioState The state to set the radio to; true for on, false for off.
|
|
*/
|
|
private void setWifi(boolean radioState){
|
|
wifiManager.setWifiEnabled(radioState);
|
|
Logger.log_d(TAG, CLASS_NAME + ".setWifi() :: setting wifi to " + (radioState ? "on" : "off"));
|
|
if(radioState)
|
|
wifiOnByMe = true;
|
|
else
|
|
wifiOnByMe = false;
|
|
}
|
|
|
|
/**
|
|
* Shows a WifiOnDialog.
|
|
*/
|
|
private void enableWifi(){
|
|
if(!wifiManager.isWifiEnabled()){
|
|
DialogFragment wifiOn = new WifiOnDialog();
|
|
((WifiOnDialog)wifiOn).setWifiManager(wifiManager);
|
|
wifiOn.show(getFragmentManager(), "wifi_on");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Launches the standard Bluetooth enable activity.
|
|
*/
|
|
private void enableBT(){
|
|
Logger.log_d(TAG, CLASS_NAME + ".enableBT() :: Enabling the Bluetooth radio.");
|
|
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
|
|
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
|
|
btOnByMe = true;
|
|
}
|
|
|
|
/**
|
|
* Commodity method for showing toasts from an AsyncTask.
|
|
*
|
|
* @param stringId The id of the string resource to show on the toast.
|
|
* @param length Time to show the toast.
|
|
*/
|
|
protected void showToast(int stringId, int length){
|
|
Toast.makeText(this, stringId, length).show();
|
|
}
|
|
|
|
/**
|
|
* Commodity method that builds an standard Android progress dialog.
|
|
*
|
|
* The dialog is created as not cancellable and uses an undeterminate spinner as visual style.
|
|
*
|
|
* @param msg The descriptive text shown by the dialog.
|
|
* @return The built dialog.
|
|
*/
|
|
private ProgressDialog buildProgressDialog(String msg){
|
|
ProgressDialog dialog = new ProgressDialog(this);
|
|
dialog.setMessage(msg);
|
|
dialog.setCancelable(false);
|
|
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
|
dialog.setProgress(0);
|
|
return dialog;
|
|
}
|
|
|
|
/**
|
|
* Listener method for the WifiOnDialog.
|
|
*
|
|
* This method is called when the user chooses to accept the dialog. It just shows an information
|
|
* message with a toast and marks the WiFi radio as turned on by the application.
|
|
*
|
|
* @param dialog The dialog that called this method.
|
|
*/
|
|
@Override
|
|
public void onWifiOnDialogPositiveClick(DialogFragment dialog) {
|
|
Toast.makeText(this, R.string.wifi_on_success, Toast.LENGTH_SHORT).show();
|
|
wifiOnByMe = true;
|
|
}
|
|
|
|
/**
|
|
* Listener method for the WifiOnDialog.
|
|
*
|
|
* This method is called when the user chooses to cancel the dialog. It just shows an error message
|
|
* with a toast and finishes the application.
|
|
*
|
|
* @param dialog The dialog that called this method.
|
|
*/
|
|
@Override
|
|
public void onWifiOnDialogNegativeClick(DialogFragment dialog) {
|
|
Toast.makeText(this, R.string.wifi_on_fail, Toast.LENGTH_LONG).show();
|
|
finish();
|
|
};
|
|
|
|
/**
|
|
* Shows the robot selection dialog.
|
|
*
|
|
* @param view The view that called this method.
|
|
*/
|
|
public void connectWithRobot(View view){
|
|
if(btManager.isBTEnabled()){
|
|
DialogFragment connectBot = new ConnectRobotDialog();
|
|
connectBot.show(getFragmentManager(), "connect_bot");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Launches the service discovery task.
|
|
*/
|
|
public void startConnections(View view){
|
|
ServiceDiscoveryTask serviceDiscovery = new ServiceDiscoveryTask();
|
|
serviceDiscovery.execute();
|
|
}
|
|
|
|
/**
|
|
* Listener method for the ConnectRobotDialog.
|
|
*
|
|
* When a user selects a robot to connect to in the dialog, this method launches the connection setup task
|
|
* defined in the ConnectRobotTask.
|
|
*
|
|
* @param dialog The dialog that called this method.
|
|
* @param robot The robot selected by the user in the format NAME\nMAC_ADDRESS
|
|
*/
|
|
@Override
|
|
public void onConnectRobotDialogItemClick(DialogFragment dialog, String robot) {
|
|
String macAddress = robot.substring(robot.indexOf('\n')+1);
|
|
Logger.log_d(TAG, CLASS_NAME + ".onConnectRobotDialogItemClick() :: MAC address: " + macAddress);
|
|
connectButton.setEnabled(false);
|
|
ConnectRobotTask robotTask = new ConnectRobotTask(macAddress);
|
|
robotTask.execute();
|
|
}
|
|
|
|
/**
|
|
* Asynchronous task for ad hoc UDP service discovery.
|
|
*
|
|
* @author Miguel Angel Astor Romero
|
|
*/
|
|
private class ServiceDiscoveryTask extends AsyncTask<Void, Void, Boolean>{
|
|
|
|
private final String CLASS_NAME = ServiceDiscoveryTask.class.getSimpleName();
|
|
|
|
private MulticastSocket udpSocket;
|
|
private DatagramPacket packet;
|
|
private MulticastLock multicastLock;
|
|
|
|
public ServiceDiscoveryTask(){
|
|
// Open a multicast socket and join the project's multicast group.
|
|
try{
|
|
udpSocket = new MulticastSocket(ProjectConstants.SERVER_UDP_PORT);
|
|
InetAddress group = InetAddress.getByName(ProjectConstants.MULTICAST_ADDRESS);
|
|
udpSocket.joinGroup(group);
|
|
}catch(IOException io){
|
|
Logger.log_e(TAG ,CLASS_NAME + ".ServiceDiscoveryTask() :: " + io.getMessage());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPreExecute(){
|
|
super.onPreExecute();
|
|
progressDialog = buildProgressDialog(getString(R.string.serv_wait));
|
|
progressDialog.show();
|
|
}
|
|
|
|
@Override
|
|
protected Boolean doInBackground(Void... params){
|
|
boolean result, done = false;
|
|
byte[] buffer = ProjectConstants.SERVICE_DISCOVERY_MESSAGE.getBytes();
|
|
|
|
// Create a buffer and tell Android we want to receive multicast datagrams.
|
|
packet = new DatagramPacket(buffer, buffer.length);
|
|
multicastLock = wifiManager.createMulticastLock(TAG);
|
|
multicastLock.setReferenceCounted(true);
|
|
multicastLock.acquire();
|
|
|
|
// Listen for a UDP datagram on the multicast group.
|
|
// If the datagram received contains a string with it's content equal to "NxtAR server here!"
|
|
// then assume the server found is a valid controller device.
|
|
try{
|
|
while(!done){
|
|
udpSocket.receive(packet);
|
|
Logger.log_d(TAG, CLASS_NAME + ".run() :: Found a server at " + packet.getAddress().getHostAddress());
|
|
String received = new String(packet.getData());
|
|
Logger.log_d(TAG, CLASS_NAME + ".doInBackground() :: Packet payload is\n" + received);
|
|
if(received.compareTo(ProjectConstants.SERVICE_DISCOVERY_MESSAGE) == 0)
|
|
done = true;
|
|
}
|
|
result = true;
|
|
}catch(IOException io){
|
|
Logger.log_e(TAG, CLASS_NAME + ".doInBackground() :: " + io.getMessage());
|
|
result = false;
|
|
}
|
|
|
|
// Tell Android we do not want to receive more UDP datagrams to save battery life.
|
|
if(multicastLock != null){
|
|
multicastLock.release();
|
|
multicastLock = null;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Boolean result){
|
|
super.onPostExecute(result);
|
|
|
|
progressDialog.dismiss();
|
|
progressDialog = null;
|
|
|
|
// If a server was found then start the next activity.
|
|
startButton.setEnabled(false);
|
|
|
|
if(packet != null){
|
|
showToast(R.string.serv_connected, Toast.LENGTH_SHORT);
|
|
startCamActivity(result, packet.getAddress().getHostAddress());
|
|
}else{
|
|
showToast(R.string.serv_fail, Toast.LENGTH_SHORT);
|
|
startCamActivity(false, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This task handles the establishing of the connection with the NXT robot.
|
|
*
|
|
* @author miky
|
|
*/
|
|
private class ConnectRobotTask extends AsyncTask<Void, Void, Boolean>{
|
|
private final String CLASS_NAME = ConnectRobotTask.class.getSimpleName();
|
|
private String macAddress;
|
|
|
|
public ConnectRobotTask(String macAddress){
|
|
this.macAddress = macAddress;
|
|
}
|
|
|
|
@Override
|
|
protected void onPreExecute(){
|
|
super.onPreExecute();
|
|
progressDialog = buildProgressDialog(getString(R.string.bt_wait));
|
|
progressDialog.show();
|
|
}
|
|
|
|
@Override
|
|
protected Boolean doInBackground(Void... params){
|
|
boolean connSet;
|
|
Logger.log_d(TAG, CLASS_NAME + "doInBackground() :: Establishing connection with the robot.");
|
|
try{
|
|
connSet = btManager.establishConnection(macAddress);
|
|
}catch(IOException e){
|
|
Logger.log_e(TAG, CLASS_NAME + "doInBackground() :: Error during the connection attempt.");
|
|
connSet = false;
|
|
}
|
|
return connSet;
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Boolean result){
|
|
super.onPostExecute(result);
|
|
|
|
progressDialog.dismiss();
|
|
progressDialog = null;
|
|
|
|
if(result){
|
|
Logger.log_d(TAG, CLASS_NAME + "doInBackground() :: Connection successful.");
|
|
showToast(R.string.conn_established, Toast.LENGTH_SHORT);
|
|
startButton.setEnabled(true);
|
|
}else{
|
|
Logger.log_d(TAG, CLASS_NAME + "doInBackground() :: Connection failed.");
|
|
showToast(R.string.conn_failed, Toast.LENGTH_LONG);
|
|
connectButton.setEnabled(true);
|
|
}
|
|
}
|
|
}
|
|
}
|