package eu.hgross.blaubot; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.net.ConnectivityManager; import android.net.wifi.WifiManager; import android.net.wifi.p2p.WifiP2pManager; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.ListView; import android.widget.Spinner; import android.widget.TabHost; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import eu.hgross.blaubot.android.BlaubotAndroid; import eu.hgross.blaubot.android.bluetooth.BlaubotBluetoothAdapter; import eu.hgross.blaubot.android.bluetooth.BlaubotBluetoothBeacon; import eu.hgross.blaubot.android.nfc.BlaubotNFCBeacon; import eu.hgross.blaubot.android.views.DebugView; import eu.hgross.blaubot.android.views.ExpandableHeightListView; import eu.hgross.blaubot.android.wifi.BlaubotWifiAdapter; import eu.hgross.blaubot.android.wifip2p.BlaubotWifiP2PBeacon; import eu.hgross.blaubot.core.BlaubotDevice; import eu.hgross.blaubot.core.BlaubotFactory; import eu.hgross.blaubot.core.BlaubotUUIDSet; import eu.hgross.blaubot.core.IBlaubotAdapter; import eu.hgross.blaubot.core.IBlaubotDevice; import eu.hgross.blaubot.core.acceptor.discovery.IBlaubotBeacon; import eu.hgross.blaubot.core.statemachine.IBlaubotConnectionStateMachineListener; import eu.hgross.blaubot.core.statemachine.states.IBlaubotState; import eu.hgross.blaubot.ethernet.BlaubotBonjourBeacon; import eu.hgross.blaubot.ethernet.BlaubotEthernetAdapter; import eu.hgross.blaubot.ethernet.BlaubotEthernetMulticastBeacon; import eu.hgross.blaubot.util.Log; import eu.hgross.blaubot.websocket.BlaubotWebsocketAdapter; /** * Blaubot configurator to dynamically create a blaubot instance with different beacons and adapters * * @author Henning Gross {@literal (mail.to@henning-gross.de)} */ public class BlaubotConfiguratorActivity extends Activity { private static final String LOG_TAG = "BlaubotConfiguratorActivity"; /** * The app's uuid. * It has to be unique for your specific application. Blaubot will separate different * UUIDs to ensure that multiple apps don't interfere. */ private static final UUID APP_UUID = DemoConstants.APP_UUID_CONFIGURATOR; private static final int DEFAULT_ACCEPTOR_PORT = 17171; private static final int DEFAULT_WEBSOCKET_ACCEPTOR_PORT = 8080; private static final int DEFAULT_BONJOUR_BEACON_PORT = 17172; private static final int DEFAULT_WIFI_P2P_BEACON_PORT = 17174; private static final int DEFAULT_MULTICAST_BEACON_PORT = 17173; private static final int DEFAULT_MULTICAST_BEACON_BROADCAST_PORT = 17175; /** * Max waiting time for the Blaubot instance to stop (seconds). */ private static final long STOP_TIMEOUT = 5; /** * The tab tag for the config view */ private static final String TAB_CONFIG = "Config"; /** * The tab tag for the debug view */ private static final String TAB_DEBUG_VIEW = "DebugView"; private DebugView mDebugView; private Handler mUiHandler; private BlaubotAndroid mBlaubot; private List<IBlaubotBeacon> beacons; private List<IBlaubotAdapter> adapters; private WifiManager.MulticastLock mMulticastLock; private WifiP2pManager.Channel mWifiP2PAcceptorChannel; private WifiP2pManager.Channel mWifiP2PBeaconChannel; private ExpandableHeightListView mBeaconsListView; private Spinner mAdapterSpinner; private BlaubotBeaconArrayAdapter mBeaconsAdapter; private BlaubotAdapterArrayAdapter mAdaptersAdapter; private Button mCreateBlaubotInstanceButton; private View mConfiguratorContainer; private View mDebugViewContainer; private TabHost mTabHost; private View mDebugViewDisabledMessage; private Button mDestroyBlaubotInstanceButton; /** * If anything goes wrong initializing adapters/beacons/... the exception will be stored here. */ private RuntimeException mCreationFailureException; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.configurator_activity); this.mUiHandler = new Handler(Looper.getMainLooper()); this.mDebugView = (DebugView) findViewById(R.id.debugView); this.mBeaconsListView = (ExpandableHeightListView) findViewById(R.id.beaconsList); this.mBeaconsListView.setExpanded(true); this.mAdapterSpinner = (Spinner) findViewById(R.id.adapterSpinner); this.mCreateBlaubotInstanceButton = (Button) findViewById(R.id.createBlaubotInstanceButton); this.mDestroyBlaubotInstanceButton = (Button) findViewById(R.id.destroyBlaubotInstanceButton); this.mConfiguratorContainer = findViewById(R.id.configuratorContainer); this.mDebugViewContainer = findViewById(R.id.debugViewContainer); this.mDebugViewDisabledMessage = findViewById(R.id.debugViewDisabledMessage); this.mTabHost = (TabHost) findViewById(R.id.tabHost); this.mTabHost.setup(); // create tabspecs TabHost.TabSpec configTabSpec = this.mTabHost.newTabSpec(TAB_CONFIG); configTabSpec.setContent(R.id.configuratorContainer); configTabSpec.setIndicator("Config"); TabHost.TabSpec debugViewTabSpec = this.mTabHost.newTabSpec(TAB_DEBUG_VIEW); debugViewTabSpec.setContent(R.id.debugViewContainer); debugViewTabSpec.setIndicator("DebugView"); this.mTabHost.addTab(configTabSpec); this.mTabHost.addTab(debugViewTabSpec); // initally don't show debug view disableDebugView(); this.mDestroyBlaubotInstanceButton.setEnabled(false); // wire and set up everything try { setUp(); } catch (RuntimeException e) { Log.e(LOG_TAG, "Creation failed.", e); e.printStackTrace(); mCreationFailureException = e; } } /** * Set up all beacons, adapters and views * * @throws RuntimeException if something goes wrong setting all the things up. */ private void setUp() throws RuntimeException { // get system services WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE); ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); WifiP2pManager wifiP2pManager = (WifiP2pManager) getSystemService(WIFI_P2P_SERVICE); // lock for multicast and bonjour beacon if (wifiManager != null) { mMulticastLock = wifiManager.createMulticastLock("BlaubotMulticastLock"); mMulticastLock.setReferenceCounted(true); mMulticastLock.acquire(); } // channels for wifip2p this.mWifiP2PBeaconChannel = wifiP2pManager.initialize(this, getMainLooper(), new WifiP2pManager.ChannelListener() { @Override public void onChannelDisconnected() { Log.e(LOG_TAG, "onChannelDisconnected for beacon channel"); } }); this.mWifiP2PAcceptorChannel = wifiP2pManager.initialize(this, getMainLooper(), new WifiP2pManager.ChannelListener() { @Override public void onChannelDisconnected() { Log.e(LOG_TAG, "onChannelDisconnected for acceptor channel"); } }); // create beacons and adapters final IBlaubotDevice ownDevice = new BlaubotDevice(); final BlaubotUUIDSet uuidSet = new BlaubotUUIDSet(APP_UUID); final InetAddress localInetAddress = BlaubotFactory.getLocalIpAddress(); final String hostAddress = localInetAddress.getHostAddress(); // create all adapters BlaubotBluetoothAdapter blaubotBluetoothAdapter = new BlaubotBluetoothAdapter(uuidSet, ownDevice); BlaubotEthernetAdapter blaubotEthernetAdapter = new BlaubotEthernetAdapter(ownDevice, DEFAULT_ACCEPTOR_PORT, localInetAddress); BlaubotWifiAdapter blaubotWifiAdapter = new BlaubotWifiAdapter(ownDevice, uuidSet, DEFAULT_ACCEPTOR_PORT, wifiManager, connectivityManager); BlaubotWebsocketAdapter websocketAdapter = new BlaubotWebsocketAdapter(ownDevice, hostAddress, DEFAULT_WEBSOCKET_ACCEPTOR_PORT); // create all beacons BlaubotBluetoothBeacon bluetoothBeacon = new BlaubotBluetoothBeacon(); BlaubotBonjourBeacon bonjourBeacon = new BlaubotBonjourBeacon(localInetAddress, DEFAULT_BONJOUR_BEACON_PORT); BlaubotNFCBeacon nfcBeacon = new BlaubotNFCBeacon(); BlaubotEthernetMulticastBeacon multicastBeacon = new BlaubotEthernetMulticastBeacon(DEFAULT_MULTICAST_BEACON_PORT, DEFAULT_MULTICAST_BEACON_BROADCAST_PORT); BlaubotWifiP2PBeacon wifiP2PBeacon = new BlaubotWifiP2PBeacon(wifiP2pManager, mWifiP2PBeaconChannel, DEFAULT_WIFI_P2P_BEACON_PORT); // fill lists this.beacons = Arrays.asList(bluetoothBeacon, bonjourBeacon, nfcBeacon, multicastBeacon, wifiP2PBeacon); this.adapters = Arrays.asList(blaubotBluetoothAdapter, blaubotEthernetAdapter, blaubotWifiAdapter, websocketAdapter); this.mBeaconsAdapter = new BlaubotBeaconArrayAdapter(this.beacons); this.mAdaptersAdapter = new BlaubotAdapterArrayAdapter(this.adapters); this.mBeaconsListView.setAdapter(this.mBeaconsAdapter); this.mBeaconsListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); this.mBeaconsListView.setItemChecked(0, true); this.mAdapterSpinner.setAdapter(this.mAdaptersAdapter); // Bind create mCreateBlaubotInstanceButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { IBlaubotAdapter selectedAdapter = (IBlaubotAdapter) mAdapterSpinner.getSelectedItem(); Set<IBlaubotBeacon> selectedBeacons = mBeaconsAdapter.getSelectedBeacons(); // validate first if (selectedBeacons.isEmpty()) { mUiHandler.post(new Runnable() { @Override public void run() { Toast.makeText(BlaubotConfiguratorActivity.this, "Please select at least one beacon.", Toast.LENGTH_SHORT).show(); } }); return; } boolean stopped = stopAndDestroyBlaubotInstance(); if (!stopped) { return; } Log.d(LOG_TAG, "Creating blaubot from configurator."); Log.d(LOG_TAG, "Selected beacons: " + selectedBeacons); Log.d(LOG_TAG, "Chosen adapter: " + selectedAdapter); // construct blaubot final ArrayList<IBlaubotBeacon> beacons = new ArrayList<>(selectedBeacons); final ArrayList<IBlaubotAdapter> adapters = new ArrayList<>(); adapters.add(selectedAdapter); mBlaubot = new BlaubotAndroid(ownDevice, uuidSet, adapters, beacons); // call lifecycle events mBlaubot.registerReceivers(BlaubotConfiguratorActivity.this); mBlaubot.onResume(BlaubotConfiguratorActivity.this); // regsiter with debug view mDebugView.registerBlaubotInstance(mBlaubot); // enable destroy button mDestroyBlaubotInstanceButton.setEnabled(true); // show the debug view enableDebugView(); mTabHost.setCurrentTabByTag(TAB_DEBUG_VIEW); } }); // Bind destroy mDestroyBlaubotInstanceButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mBlaubot == null) { return; } boolean destroyed = stopAndDestroyBlaubotInstance(); if (destroyed) { mUiHandler.post(new Runnable() { @Override public void run() { Toast.makeText(BlaubotConfiguratorActivity.this, "Successfully stopped and destroyed the Blaubot instance.", Toast.LENGTH_SHORT).show(); } }); } } }); } /** * @return true, iff the blaubot instance was stopped and destroyed successfully or there wasn't an instance after all */ private boolean stopAndDestroyBlaubotInstance() { if (mBlaubot != null) { if (mBlaubot.isStarted()) { final CountDownLatch latch = new CountDownLatch(1); mBlaubot.getConnectionStateMachine().addConnectionStateMachineListener(new IBlaubotConnectionStateMachineListener() { @Override public void onStateChanged(IBlaubotState oldState, IBlaubotState newState) { } @Override public void onStateMachineStopped() { latch.countDown(); } @Override public void onStateMachineStarted() { } }); mBlaubot.stopBlaubot(); try { boolean timedOut = !latch.await(STOP_TIMEOUT, TimeUnit.SECONDS); if (timedOut) { mUiHandler.post(new Runnable() { @Override public void run() { Toast.makeText(BlaubotConfiguratorActivity.this, "Error: The blaubot instance did not stop fast enough.", Toast.LENGTH_SHORT).show(); } }); return false; } } catch (InterruptedException e) { e.printStackTrace(); return false; } } mDebugView.unregisterBlaubotInstance(); mBlaubot.onPause(BlaubotConfiguratorActivity.this); mBlaubot.unregisterReceivers(BlaubotConfiguratorActivity.this); try { mBlaubot.close(); } catch (IOException e) { e.printStackTrace(); } mBlaubot = null; disableDebugView(); mDestroyBlaubotInstanceButton.setEnabled(false); } return true; } @Override protected void onNewIntent(Intent intent) { Log.d(LOG_TAG, "LifeCycle.onNewIntent(" + intent + ")"); if (mBlaubot != null && mBlaubot instanceof BlaubotAndroid) { BlaubotAndroid blaubotAndroid = (BlaubotAndroid) mBlaubot; blaubotAndroid.onNewIntent(intent); } super.onNewIntent(intent); } @Override protected void onResume() { Log.d(LOG_TAG, "LifeCycle.onResume"); if (mCreationFailureException != null) { // stop on error AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setIcon(R.drawable.ic_stopped); builder.setTitle("Creation error"); builder.setMessage("Could not create Blaubot. Is Wi-Fi/Bluetooth/... turned on? Exception: " + mCreationFailureException + "") .setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { BlaubotConfiguratorActivity.this.finish(); } }); builder.create().show(); super.onResume(); return; } if (mBlaubot != null && mBlaubot instanceof BlaubotAndroid) { BlaubotAndroid blaubotAndroid = (BlaubotAndroid) mBlaubot; blaubotAndroid.setContext(this); blaubotAndroid.registerReceivers(this); blaubotAndroid.onResume(this); } super.onResume(); } @Override protected void onPause() { Log.d(LOG_TAG, "LifeCycle.onPause"); super.onPause(); if (mBlaubot != null && mBlaubot instanceof BlaubotAndroid) { BlaubotAndroid blaubotAndroid = (BlaubotAndroid) mBlaubot; blaubotAndroid.unregisterReceivers(this); blaubotAndroid.onResume(this); } } @Override protected void onStop() { Log.d(LOG_TAG, "LifeCycle.onStop"); if (mBlaubot != null && mBlaubot instanceof BlaubotAndroid) { BlaubotAndroid blaubotAndroid = (BlaubotAndroid) mBlaubot; blaubotAndroid.stopBlaubot(); } super.onStop(); } @Override protected void onDestroy() { Log.d(LOG_TAG, "LifeCycle.onDestroy"); super.onDestroy(); } private void enableDebugView() { mDebugView.setVisibility(View.VISIBLE); mDebugViewDisabledMessage.setVisibility(View.GONE); } private void disableDebugView() { mDebugView.setVisibility(View.INVISIBLE); mDebugViewDisabledMessage.setVisibility(View.VISIBLE); } private class BlaubotBeaconArrayAdapter extends ArrayAdapter<IBlaubotBeacon> { private Set<IBlaubotBeacon> selectedBeacons = Collections.newSetFromMap(new HashMap<IBlaubotBeacon, Boolean>()); public BlaubotBeaconArrayAdapter(List<IBlaubotBeacon> beacons) { super(BlaubotConfiguratorActivity.this, R.layout.beacon_list_item, R.id.name, beacons); } @Override public View getView(int position, View convertView, ViewGroup parent) { final IBlaubotBeacon beacon = getItem(position); boolean reused = convertView != null; View view = reused ? convertView : View.inflate(this.getContext(), R.layout.beacon_list_item, null); TextView nameTextView = (TextView) view.findViewById(R.id.name); ImageView iconImageView = (ImageView) view.findViewById(R.id.icon); final CheckBox checkBox = (CheckBox) view.findViewById(R.id.checkbox); checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { selectedBeacons.add(beacon); } else { selectedBeacons.remove(beacon); } } }); if (reused) { // if reused, maintain checkbox state checkBox.setSelected(selectedBeacons.contains(beacon)); } view.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { checkBox.toggle(); } }); iconImageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_scout)); nameTextView.setText(beacon.getClass().getSimpleName() + ""); return view; } /** * The list of selectedBeacons * * @return */ public Set<IBlaubotBeacon> getSelectedBeacons() { return selectedBeacons; } } /** * Shows list */ private class BlaubotAdapterArrayAdapter extends ArrayAdapter<IBlaubotAdapter> { public BlaubotAdapterArrayAdapter(List<IBlaubotAdapter> adapters) { super(BlaubotConfiguratorActivity.this, R.layout.adapter_list_item, R.id.name, adapters); } @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { return getCustomView(position, convertView, parent); } @Override public View getView(int position, View convertView, ViewGroup parent) { return getCustomView(position, convertView, parent); } private View getCustomView(int position, View convertView, ViewGroup parent) { final IBlaubotAdapter adapter = getItem(position); View view = convertView != null ? convertView : View.inflate(this.getContext(), R.layout.adapter_list_item, null); TextView nameTextView = (TextView) view.findViewById(R.id.name); ImageView iconImageView = (ImageView) view.findViewById(R.id.icon); iconImageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_merge)); nameTextView.setText(adapter.getClass().getSimpleName() + ""); return view; } } }