package io.trygvis.soilmoisture; import android.app.ActionBar; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.ListActivity; import android.app.ProgressDialog; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.database.DataSetObservable; import android.database.DataSetObserver; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ListAdapter; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.crashlytics.android.Crashlytics; import java.util.ArrayList; import java.util.List; import io.fabric.sdk.android.Fabric; import io.trygvis.android.LocalBinder; import io.trygvis.android.bt.BtActivitySupport; import io.trygvis.android.bt.BtDevice; import static io.trygvis.soilmoisture.ExceptionHandler.EXCEPTION_HANDLER; import static io.trygvis.soilmoisture.SoilMoistureService.SoilMoistureListener; import static java.lang.String.valueOf; public class MainActivity extends ListActivity { private final static String TAG = MainActivity.class.getSimpleName(); private static final long SCAN_PERIOD = 3 * 1000; private static final int REQUEST_ENABLE_BT = 1; private final BtActivitySupport btActivitySupport = new BtActivitySupport(this, REQUEST_ENABLE_BT); private final SoilMoistureListener serviceListener = new MainSoilMoistureListener(); private final MainActivity context = this; private DeviceListAdapter deviceList; private ServiceConnection serviceConnection; private SoilMoistureService soilMoistureService; private ProgressDialog initializing; private boolean ready; private int red; private int yellow; private int green; protected void onCreate(Bundle savedInstanceState) { Log.i(TAG, "onCreate"); Thread.setDefaultUncaughtExceptionHandler(EXCEPTION_HANDLER); super.onCreate(savedInstanceState); Fabric.with(this, new Crashlytics()); ActionBar actionBar = getActionBar(); if (actionBar != null) { actionBar.setTitle(R.string.title_devices); } if (!btActivitySupport.onCreate()) { finish(); return; } serviceConnection = new ServiceConnection() { @SuppressWarnings("unchecked") @Override public void onServiceConnected(ComponentName componentName, IBinder service) { Log.i(TAG, "onServiceConnected"); soilMoistureService = ((LocalBinder) service).getService(); registerReceiver(serviceListener, SoilMoistureListener.INTENT_FILTER); if (soilMoistureService.isReady()) { serviceListener.onReady(true); for (SmDevice smDevice : soilMoistureService.getDevices()) { serviceListener.onNewDevice(smDevice.getBtDevice().getAddress()); } } } @Override public void onServiceDisconnected(ComponentName componentName) { Log.i(TAG, "onServiceDisconnected"); soilMoistureService = null; stopScan(); } }; bindService(new Intent(this, DefaultSoilMoistureService.class), serviceConnection, BIND_AUTO_CREATE); // initializing = ProgressDialog. // show(this, "Initializing", "Connecting to Bluetooth system.", true); green = getResources().getColor(R.color.green); yellow = getResources().getColor(R.color.yellow); red = getResources().getColor(R.color.red); setContentView(R.layout.main); // This will be replaced quite fast, but when resuming it needs to be here. deviceList = new DeviceListAdapter(); setListAdapter(deviceList); } @Override protected void onDestroy() { Log.i(TAG, "onDestroy"); super.onDestroy(); if (serviceConnection != null) { unbindService(serviceConnection); } } @Override protected void onResume() { Log.i(TAG, "onResume"); super.onResume(); if (!btActivitySupport.enableBt()) { finish(); return; } registerReceiver(serviceListener, SoilMoistureListener.INTENT_FILTER); } @Override protected void onPause() { Log.i(TAG, "onPause"); super.onPause(); stopScan(); unregisterReceiver(serviceListener); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.i(TAG, "onActivityResult"); if (!btActivitySupport.onActivityResult(requestCode, resultCode, data)) { finish(); } super.onActivityResult(requestCode, resultCode, data); } @Override public boolean onCreateOptionsMenu(Menu menu) { Log.i(TAG, "onCreateOptionsMenu"); // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); MenuItem stop = menu.findItem(R.id.menu_stop); MenuItem scan = menu.findItem(R.id.menu_scan); MenuItem refresh = menu.findItem(R.id.menu_refresh); MenuItem showAll = menu.findItem(R.id.menu_show_all); MenuItem groupByDevice = menu.findItem(R.id.menu_group_by_device); if (ready) { if (!soilMoistureService.isScanning()) { stop.setVisible(false); scan.setVisible(true); refresh.setActionView(null); } else { stop.setVisible(true); scan.setVisible(false); refresh.setActionView(R.layout.actionbar_indeterminate_progress); } showAll.setChecked(deviceList.isShowAll()); groupByDevice.setChecked(deviceList.isGroupByDevice()); } else { stop.setVisible(false); scan.setVisible(true); refresh.setActionView(null); showAll.setVisible(false); groupByDevice.setVisible(false); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { Log.i(TAG, "onOptionsItemSelected"); boolean consumed = true; switch (item.getItemId()) { case R.id.menu_scan: startScan(); break; case R.id.menu_stop: stopScan(); break; case R.id.menu_show_all: item.setChecked(!item.isChecked()); deviceList.setShowAll(item.isChecked()); break; case R.id.menu_group_by_device: item.setChecked(!item.isChecked()); deviceList.setGroupByDevice(item.isChecked()); break; default: consumed = super.onOptionsItemSelected(item); } return consumed; } private void startScan() { soilMoistureService.startScanning(SCAN_PERIOD); } private void stopScan() { if (soilMoistureService != null) { soilMoistureService.stopScanning(); } } // ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- public static class ProbeDeviceDialogFragment extends DialogFragment { private MainActivity mainActivity; @Override public Dialog onCreateDialog(Bundle savedInstanceState) { String address = getArguments().getString("address"); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage(R.string.dialog_probe_device). setPositiveButton(R.string.yes, (dialog, id) -> mainActivity.probe(address)); return builder.create(); } @Override public void onAttach(Activity activity) { super.onAttach(activity); mainActivity = (MainActivity) activity; } } private void probe(String address) { soilMoistureService.probe(address); } private boolean onBtDeviceLongClick(BtDevice device) { ProbeDeviceDialogFragment dialog = new ProbeDeviceDialogFragment(); Bundle arguments = new Bundle(); arguments.putString("address", device.getAddress()); dialog.setArguments(arguments); dialog.show(getFragmentManager(), "ProbeDeviceDialogFragment"); return true; } private boolean onSmDeviceLongClick(SmDevice device) { return onBtDeviceLongClick(device.getBtDevice()); } private void onSmDeviceClick(SmDevice device) { Log.i(TAG, "onSmDeviceClick, device=" + device.getBtDevice().getId()); } private void onSensorClick(SmSensor sensor) { Log.i(TAG, "onSensorClick, device=" + sensor.getDevice().getBtDevice().getId() + "/" + sensor.getIndex()); // sensor.readCurrentValue(); Intent intent = new Intent(this, SensorActivity.class); intent.putExtra(SensorActivity.EXTRA_ADDRESS, sensor.getDevice().getBtDevice().getAddress()); intent.putExtra(SensorActivity.EXTRA_NUMBER, sensor.getIndex()); startActivity(intent); } // ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- class DeviceItem { final SmDevice device; final TextView statusBar; final TextView deviceName; final TextView deviceAddress; final TextView rssi; final TextView info; DeviceItem(SmDevice device, View view) { this.device = device; this.statusBar = (TextView) view.findViewById(R.id.status_bar); this.deviceName = (TextView) view.findViewById(R.id.device_name); this.deviceAddress = (TextView) view.findViewById(R.id.device_address); this.rssi = (TextView) view.findViewById(R.id.device_rssi); this.info = (TextView) view.findViewById(R.id.device_info); } public void update() { statusBar.setVisibility(deviceList.isGroupByDevice() ? View.VISIBLE : View.GONE); statusBar.setBackgroundColor(statusColor(device)); if (device.getName() != null) { deviceName.setText(device.getName()); } else { deviceName.setText(R.string.unknown_device); } String address = device.getBtDevice().getAddress(); if (!device.isProbed()) { address += " not probed"; } else if (device.isUseful()) { address += " useful"; } else { address += " not useful"; } address += ", connected=" + device.getBtDevice().connected(); address += ", recentlySeen=" + device.getBtDevice().isRecentlySeen(); deviceAddress.setText(address); String rssi = getText(R.string.rssi) + ": " + (device.getBtDevice().getRssi() != null ? valueOf(device.getBtDevice().getRssi()) : getText(R.string.unknown)); rssi += ", device: " + device.toString(); this.rssi.setText(rssi); // boolean useful = device.isUseful(); // // if (useful) { // info.setText("Number of sensors: " + device.getSensors().size()); // } else { // info.setText(""); // } } } class SensorItem { final SmDevice device; final SmSensor sensor; final TextView statusBar; final TextView description; final ProgressBar sensorProgress; SensorItem(SmSensor sensor, View view) { this.sensor = sensor; this.statusBar = (TextView) view.findViewById(R.id.status_bar); this.description = (TextView) view.findViewById(R.id.description); this.sensorProgress = (ProgressBar) view.findViewById(R.id.sensor_progress); sensorProgress.setMax(1024); device = sensor.getDevice(); view.setClickable(true); view.setOnClickListener(v -> { Log.i(TAG, "onClick, SmSensor: " + // "position=" + position + ", " + "sensor=" + sensor /*+ ", " + "tag=" + v.getTag()*/); onSensorClick(sensor); }); } public void update() { statusBar.setVisibility(deviceList.isGroupByDevice() ? View.GONE : View.VISIBLE); statusBar.setBackgroundColor(statusColor(device)); if (deviceList.isGroupByDevice()) { statusBar.setVisibility(View.GONE); } else { statusBar.setVisibility(View.VISIBLE); statusBar.setBackgroundColor(statusColor(device)); } Integer value = sensor.getLastValue(); String text = "Sensor " + sensor; text += ", value: " + (value == null ? "Unknown" : value); description.setText(text); sensorProgress.setProgress(value != null ? value : 0); } } private int statusColor(SmDevice device) { if (device.getBtDevice().connected()) { return green; } else if (device.getBtDevice().isRecentlySeen()) { return yellow; } else { return red; } } private class DeviceListAdapter implements ListAdapter { private final DataSetObservable dataSetObservable = new DataSetObservable(); private List devices = new ArrayList<>(); private List current = new ArrayList<>(); private LayoutInflater inflater = MainActivity.this.getLayoutInflater(); private boolean groupByDevice = true; private boolean showAll = true; public void sort() { Log.i(TAG, "sort(), groupByDevice=" + groupByDevice + ", showAll=" + showAll); current = new ArrayList<>(); List usefulDevices = new ArrayList<>(devices.size()); List unusefulDevices = new ArrayList<>(devices.size()); for (SmDevice d : devices) { if (d.isUseful()) { usefulDevices.add(d); } else { unusefulDevices.add(d.getBtDevice()); } } List sensors = new ArrayList<>(); for (SmDevice d : devices) { if (d.isUseful()) { sensors.addAll(d.getSensors()); } } if (groupByDevice) { // current.addAll(usefulDevices); for (SmDevice device : usefulDevices) { current.add(device); current.addAll(device.getSensors()); } if (showAll) { current.addAll(unusefulDevices); } } else { current.addAll(sensors); } Log.i(TAG, "sort result:"); for (int i = 0; i < current.size(); i++) { Object o = current.get(i); Log.i(TAG, i + " = " + o); } dataSetObservable.notifyChanged(); } public void notifyDataSetChanged() { Log.i(TAG, "notifyDataSetChanged"); dataSetObservable.notifyChanged(); } public void setShowAll(boolean showAll) { if (showAll == this.showAll) { return; } this.showAll = showAll; sort(); } public boolean isShowAll() { return showAll; } public void setGroupByDevice(boolean groupByDevice) { if (groupByDevice == this.groupByDevice) { return; } this.groupByDevice = groupByDevice; sort(); } public boolean isGroupByDevice() { return groupByDevice; } // ----------------------------------------------------------------------- // ListAdapter Implementation // ----------------------------------------------------------------------- @Override public void registerDataSetObserver(DataSetObserver observer) { dataSetObservable.registerObserver(observer); } @Override public void unregisterDataSetObserver(DataSetObserver observer) { dataSetObservable.unregisterObserver(observer); } @Override public boolean areAllItemsEnabled() { return true; } @Override public boolean isEnabled(int position) { return true; } @Override public boolean hasStableIds() { return false; // return true; } @Override public int getItemViewType(int position) { // Object o = current.get(position); // // if (o instanceof BtDevice) { // return 0; // } else if (o instanceof SmDevice) { // return 1; // } else if (o instanceof SmSensor) { // return 2; // } // // throw new RuntimeException("Unknown kind: " + o.getClass()); return IGNORE_ITEM_VIEW_TYPE; // throw new RuntimeException("Not supported"); } @Override public int getViewTypeCount() { return 1; // return IGNORE_ITEM_VIEW_TYPE; } @Override public boolean isEmpty() { return current.isEmpty(); } @Override public int getCount() { return current.size(); } @Override public Object getItem(int position) { return current.get(position); } @Override public long getItemId(int position) { Log.i(TAG, "getItemId, position=" + position); Object o = current.get(position); if (o instanceof BtDevice) { BtDevice btDevice = (BtDevice) o; return 1000 + btDevice.getId(); } else if (o instanceof SmDevice) { SmDevice smDevice = (SmDevice) o; return 2000 + smDevice.getId(); } else if (o instanceof SmSensor) { SmSensor smSensor = (SmSensor) o; return 3000 + smSensor.getId(); } throw new RuntimeException("Not implemented"); } @Override public View getView(int position, View view, ViewGroup viewGroup) { // Log.i(TAG, "getView, position=" + position + ", view=" + view); Object o = current.get(position); if (o instanceof BtDevice) { //noinspection unchecked return getBtDeviceView((BtDevice) o, view); } else if (o instanceof SmDevice) { return getSmDeviceView((SmDevice) o, view); } else if (o instanceof SmSensor) { return getSoilSensorView((SmSensor) o, view); } throw new RuntimeException("Not implemented"); } private View getBtDeviceView(BtDevice device, View view) { if (view == null) { view = inflater.inflate(R.layout.fragment_device, null); view.setTag(new DeviceItem(device.getTag(), view)); view.setOnLongClickListener(v -> MainActivity.this.onBtDeviceLongClick(device)); } DeviceItem item = (DeviceItem) view.getTag(); if (device.getName() != null) { item.deviceName.setText(device.getName()); } else { item.deviceName.setText(R.string.unknown_device); } String address = device.getAddress() + ", not useful"; item.deviceAddress.setText(address); item.rssi.setText(getText(R.string.rssi) + ": " + (device.getRssi() != null ? valueOf(device.getRssi()) : getText(R.string.unknown))); return view; } private View getSmDeviceView(SmDevice smDevice, View view) { if (view == null) { view = inflater.inflate(R.layout.fragment_device, null); view.setTag(new DeviceItem(smDevice, view)); view.setOnClickListener(v -> onSmDeviceClick(smDevice)); view.setOnLongClickListener(v -> onSmDeviceLongClick(smDevice)); } ((DeviceItem) view.getTag()).update(); return view; } private View getSoilSensorView(SmSensor smSensor, View view) { if (view == null) { view = inflater.inflate(R.layout.fragment_main_sensor, null); SensorItem item = new SensorItem(smSensor, view); view.setTag(item); } ((SensorItem) view.getTag()).update(); return view; } } private class MainSoilMoistureListener extends SoilMoistureListener { @Override public void onToast(int id, int length) { CharSequence text = getText(id); Log.i(TAG, "Toast: " + text); Toast.makeText(context, text, length).show(); } @Override public void onReady(boolean ok) { ready = ok; if (!ready) { Toast.makeText(context, "Could not initialize services.", Toast.LENGTH_SHORT). show(); finish(); } else { deviceList = new DeviceListAdapter(); setListAdapter(deviceList); deviceList.notifyDataSetChanged(); startScan(); if (initializing != null) { initializing.dismiss(); } } } @Override public void onScanStarted() { invalidateOptionsMenu(); } @Override public void onScanStopped() { invalidateOptionsMenu(); } @Override public void onNewDevice(String address) { SmDevice device = soilMoistureService.getDevice(address); deviceList.devices.add(device); deviceList.sort(); } @Override public void onNewSample(String address, int sensor) { deviceList.notifyDataSetChanged(); } @Override public void onDevicePropertyUpdated(String address) { deviceList.notifyDataSetChanged(); } } }