/**
* @file DashboardActivity.java
* @brief Activity for Dashboard showing products.
* @author Hollis Kim (wowhos@gmail.com)
* @date Oct/1/2015
* Copyright (c) 2014 General Electric Corporation - Confidential - All rights reserved.
*/
package com.firstbuild.androidapp.dashboard;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.firstbuild.androidapp.OpalValues;
import com.firstbuild.androidapp.ParagonValues;
import com.firstbuild.androidapp.R;
import com.firstbuild.androidapp.addproduct.AddProductActivity;
import com.firstbuild.androidapp.opal.OpalMainActivity;
import com.firstbuild.androidapp.paragon.ParagonMainActivity;
import com.firstbuild.androidapp.productmanager.ProductInfo;
import com.firstbuild.androidapp.productmanager.ProductManager;
import com.firstbuild.androidapp.viewutil.SwipeMenu;
import com.firstbuild.androidapp.viewutil.SwipeMenuCreator;
import com.firstbuild.androidapp.viewutil.SwipeMenuItem;
import com.firstbuild.androidapp.viewutil.SwipeMenuListView;
import com.firstbuild.commonframework.blemanager.BleListener;
import com.firstbuild.commonframework.blemanager.BleManager;
import com.firstbuild.commonframework.blemanager.BleValues;
import com.firstbuild.tools.MathTools;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.TimeZone;
public class DashboardActivity extends AppCompatActivity {
static final int REQUEST_ENABLE_BT = 1234;
private String TAG = DashboardActivity.class.getSimpleName();
private SwipeMenuListView listViewProduct;
private ProductListAdapter adapterDashboard;
// Bluetooth adapter handler
private BluetoothAdapter bluetoothAdapter = null;
private View layoutNoProduct;
private BleListener bleListener = new BleListener() {
@Override
public void onScanDevices(HashMap<String, BluetoothDevice> bluetoothDevices) {
super.onScanDevices(bluetoothDevices);
Log.d(TAG, "onScanDevices IN");
}
@Override
public void onScanStateChanged(int status) {
super.onScanStateChanged(status);
Log.d(TAG, "[onScanStateChanged] status: " + status);
if (status == BleValues.START_SCAN) {
Log.d(TAG, "Scanning BLE devices");
}
else {
Log.d(TAG, "Stop scanning BLE devices");
}
}
@Override
public void onConnectionStateChanged(final String address, final int status) {
super.onConnectionStateChanged(address, status);
Log.d(TAG, "[onConnectionStateChanged] address: " + address + ", status: " + status);
ProductInfo productInfo = ProductManager.getInstance().getProductByAddress(address);
if (productInfo != null && status == BluetoothProfile.STATE_DISCONNECTED) {
productInfo.disconnected();
runOnUiThread(new Runnable() {
@Override
public void run() {
adapterDashboard.notifyDataSetChanged();
listViewProduct.invalidateViews();
}
});
checkBleTurnOn();
}
}
@Override
public void onServicesDiscovered(String address, List<BluetoothGattService> bleGattServices) {
super.onServicesDiscovered(address, bleGattServices);
Log.d(TAG, "[onServicesDiscovered] address: " + address);
ProductInfo productInfo = ProductManager.getInstance().getProductByAddress(address);
if (productInfo != null) {
productInfo.connected();
productInfo.initMustData();
// Should subscribe to notification as it is initial request to the BLE device
requestMustHaveData(productInfo, false);
// According to the spec, Application should send local epoch time to Opal device
// after Connection to the GATT server is made
if(productInfo.type == ProductInfo.PRODUCT_TYPE_OPAL) {
sendPhoneLocalEpochTimeToOpal(productInfo);
}
else {
// Do nothing
}
runOnUiThread(new Runnable() {
@Override
public void run() {
adapterDashboard.notifyDataSetChanged();
listViewProduct.invalidateViews();
}
});
}
}
@Override
public void onCharacteristicRead(String address, String uuid, byte[] value, int status) {
super.onCharacteristicRead(address, uuid, value, status);
Log.d(TAG, "[onCharacteristicRead] address: " + address + ", uuid: " + uuid);
onReceivedData(address, uuid, value);
}
@Override
public void onCharacteristicWrite(String address, String uuid, byte[] value, int status) {
super.onCharacteristicWrite(address, uuid, value, status);
Log.d(TAG, "[onCharacteristicWrite] address: " + address + ", uuid: " + uuid);
}
@Override
public void onCharacteristicChanged(String address, String uuid, byte[] value) {
super.onCharacteristicChanged(address, uuid, value);
Log.d(TAG, "[onCharacteristicChanged] address: " + address + ", uuid: " + uuid);
onReceivedData(address, uuid, value);
}
@Override
public void onDescriptorWrite(String address, String uuid, byte[] value, int status) {
super.onDescriptorWrite(address, uuid, value, status);
Log.d(TAG, "[onDescriptorWrite] address: " + address + ", uuid: " + uuid + ", value : " + MathTools.byteArrayToHex(value) + ", status" + status);
}
};
private void sendPhoneLocalEpochTimeToOpal(ProductInfo product) {
if(product.bluetoothDevice != null) {
Log.d(TAG, "[HANS] sendPhoneLocalEpochTimeToOpal : " + product.nickname);
ByteBuffer valueBuffer = ByteBuffer.allocate(4);
Calendar calendar = Calendar.getInstance();
// Get UTC time
Long millis = calendar.getTimeInMillis();
// Get local time from UTC time + Current Zone time + DST
millis += TimeZone.getDefault().getOffset(millis);
Long localTime = millis/1000;
// Long localTime = 1468936790L; This is current time for testing purpose
// "13:59:50", i can test if 2pm schedule item works or not within 10 sec
// Since ByteBuffer's byte order is BIG_ENDIAN by default, set it to use LITTEL_ENDIAN
valueBuffer.order(ByteOrder.LITTLE_ENDIAN);
valueBuffer.putInt(localTime.intValue());
Log.d(TAG, "[HANS] current local time : " + localTime.intValue());
Log.d(TAG, "[HANS] current local time in buffer array format: " + MathTools.byteArrayToHex(valueBuffer.array()));
BleManager.getInstance().writeCharacteristics(product.bluetoothDevice, OpalValues.OPAL_TIME_SYNC_UUID, valueBuffer.array());
}
else {
// Should we reconnect if bluetoothdevice is not available ?
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_ENABLE_BT) {
if (resultCode == -1) {
// Success
Log.d(TAG, "Bluetooth adapter is enabled. Start scanning.");
}
else if (resultCode == 0) {
Log.d(TAG, "Bluetooth adapter is still disabled");
}
else {
// Else
}
}
else {
}
}
@Override
public void onBackPressed() {
//TODO: Consider later if app exit when press back button.
super.onBackPressed();
}
@Override
protected void onPause() {
super.onPause();
BleManager.getInstance().removeListener(bleListener);
}
@Override
protected void onResume() {
Log.d(TAG, "onResume");
super.onResume();
// Initialize ble manager
BleManager.getInstance().initBleManager(this);
// Add ble event listener
BleManager.getInstance().addListener(bleListener);
// Check if Bluetooth turned off.
if (checkBleTurnOn()) {
requestUpdateProducts();
}
}
/**
* Check if bluetooto turned off.
*
* @return true if turned on.
*/
private boolean checkBleTurnOn() {
// Check bluetooth adapter. If the adapter is disabled, enable it
boolean result = BleManager.getInstance().isBluetoothEnabled();
if (!result) {
Log.d(TAG, "Bluetooth adapter is disabled. Enable bluetooth adapter.");
int size = ProductManager.getInstance().getSize();
for (int i = 0; i < size; i++) {
ProductInfo productInfo = ProductManager.getInstance().getProduct(i);
productInfo.disconnected();
}
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
else {
Log.d(TAG, "Bluetooth adapter is already enabled. Start connect");
}
return result;
}
/**
* Request initial data to registed product.
*/
private void requestUpdateProducts() {
int size = ProductManager.getInstance().getSize();
for (int i = 0; i < size; i++) {
ProductInfo productInfo = ProductManager.getInstance().getProduct(i);
if(productInfo.bluetoothDevice == null) {
productInfo.bluetoothDevice = BleManager.getInstance().connect(productInfo.address);
}
}
for (int i = 0; i < size; i++) {
ProductInfo productInfo = ProductManager.getInstance().getProduct(i);
requestMustHaveData(productInfo, true);
}
updateListView();
}
/**
* Must get datas.
*
* @param productInfo Object of ProductInfo.
* @param readOnly perform read opeartions only
*/
private void requestMustHaveData(ProductInfo productInfo, boolean readOnly) {
if (productInfo.bluetoothDevice != null) {
// read must have characteristics
for(String uuid : productInfo.getMustHaveUUIDList()) {
BleManager.getInstance().readCharacteristics(productInfo.bluetoothDevice, uuid);
}
if(readOnly == false) {
// set must-have-notification characteristics
for(String uuid : productInfo.getMustHaveNotificationUUIDList()) {
BleManager.getInstance().setCharacteristicNotification(productInfo.bluetoothDevice, uuid, true);
}
}
}
}
/**
* Update List view. if no item for list show robot image.
*/
private void updateListView() {
adapterDashboard.notifyDataSetChanged();
listViewProduct.invalidateViews();
if (adapterDashboard.getCount() == 0) {
layoutNoProduct.setVisibility(View.VISIBLE);
listViewProduct.setVisibility(View.GONE);
}
else {
layoutNoProduct.setVisibility(View.GONE);
listViewProduct.setVisibility(View.VISIBLE);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate IN");
setContentView(R.layout.activity_dashboard);
setTheme(R.style.AppTheme);
Toolbar toolbar = (Toolbar) findViewById(R.id.app_bar);
toolbar.setTitle("");
((TextView) toolbar.findViewById(R.id.toolbar_title)).setText(R.string.header_title_dashboard);
setSupportActionBar(toolbar);
listViewProduct = (SwipeMenuListView) findViewById(R.id.listProduct);
layoutNoProduct = findViewById(R.id.img_no_product);
layoutNoProduct.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getResources().getString(R.string.urlFistBuild))));
}
});
adapterDashboard = new ProductListAdapter();
listViewProduct.setAdapter(adapterDashboard);
// step 1. create a MenuCreator
SwipeMenuCreator creator = new SwipeMenuCreator() {
@Override
public void create(SwipeMenu menu) {
SwipeMenuItem item;
// create "delete" item
item = new SwipeMenuItem(getApplicationContext());
item.setBackground(R.color.colorParagonHighlight);
item.setWidth(dp2px(90));
item.setTitle("Delete");
item.setTitleSize(18);
item.setTitleColor(Color.WHITE);
menu.addMenuItem(item);
}
};
// set creator
listViewProduct.setMenuCreator(creator);
// step 2. listener item click event
listViewProduct.setOnMenuItemClickListener(new SwipeMenuListView.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(final int position, SwipeMenu menu, int index) {
if (index == 0) {
// delete
Log.d(TAG, "onMenuItemClick 0");
new MaterialDialog.Builder(DashboardActivity.this)
.title("Delete product")
.content("Are you sure?")
.positiveText("Yes")
.negativeText("no")
.cancelable(false)
.callback(new MaterialDialog.ButtonCallback() {
@Override
public void onPositive(MaterialDialog dialog) {
ProductInfo product = ProductManager.getInstance().getProduct(position);
BleManager.getInstance().removeDevice(product.bluetoothDevice);
ProductManager.getInstance().remove(position);
updateListView();
}
@Override
public void onNegative(MaterialDialog dialog) {
}
@Override
public void onNeutral(MaterialDialog dialog) {
}
})
.show();
}
return false;
}
});
listViewProduct.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
onItemClicked(position);
}
});
findViewById(R.id.btnAddProduct).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(DashboardActivity.this, AddProductActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(intent);
// remove all ble operations
cancelBleOperations(false);
}
});
// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Log.d(TAG, "BLE is not supported - Stop activity!");
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
BleManager.getInstance().removeListener(bleListener);
finish();
}
else {
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
bluetoothAdapter = bluetoothManager.getAdapter();
// Checks if Bluetooth is supported on the device.
if (bluetoothAdapter == null) {
Log.d(TAG, "Bluetooth is not supported - Stop activity!");
Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
BleManager.getInstance().removeListener(bleListener);
finish();
}
else {
// Do nothing
}
}
}
/**
* Convert dp to pixel.
*
* @param dp dp value.
* @return pixel value.
*/
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
getResources().getDisplayMetrics());
}
/**
* call when press item on listview.
*
* @param position 0 index of list item.
*/
public void onItemClicked(int position) {
ProductInfo productInfo = adapterDashboard.getItem(position);
if (productInfo.isConnected() && productInfo.isAllMustDataReceived()) {
ProductManager.getInstance().setCurrent(position);
Class<?> cls = getTargetActivityClass(productInfo.type);
Intent intent = new Intent(DashboardActivity.this, cls);
startActivity(intent);
cancelBleOperations(true);
}
else {
Log.d(TAG, "onItemClicked but error :" + productInfo.type);
}
}
/**
* cancel active BLE operations from the Queue
* @param maintainCurrent indicates whether the ble operations pertaining to the current product should be maintained or not
*/
private void cancelBleOperations(boolean maintainCurrent) {
ProductInfo currentProduct = ProductManager.getInstance().getCurrent();
for(ProductInfo p : ProductManager.getInstance().getProducts()) {
if(maintainCurrent == true && p.bluetoothDevice.equals(currentProduct.bluetoothDevice))
continue;
BleManager.getInstance().cancelOperations(p.bluetoothDevice);
}
}
@NonNull
private Class<?> getTargetActivityClass(int type) {
Class<?> ret;
switch(type) {
case ProductInfo.PRODUCT_TYPE_PARAGON:
ret = ParagonMainActivity.class;
break;
case ProductInfo.PRODUCT_TYPE_OPAL:
ret = OpalMainActivity.class;
break;
default:
Log.d(TAG, "Unsupported type : " + type);
ret = ParagonMainActivity.class;
}
return ret;
}
/**
* Called when data comming from Pragon Master.
*
* @param address Address of BLE.
* @param uuid UUID
* @param value value get from BLE.
*/
private void onReceivedData(String address, String uuid, byte[] value) {
Log.d(TAG, "onReceivedData :" + uuid);
if (value == null) {
Log.d(TAG, "onReceivedData :value is null");
return;
}
ByteBuffer byteBuffer = ByteBuffer.wrap(value);
ProductInfo product = ProductManager.getInstance().getProductByAddress(address);
if (product == null) {
Log.d(TAG, "Not found product by Address [" + address);
return;
}
product.connected();
switch (uuid.toUpperCase()) {
case ParagonValues.CHARACTERISTIC_BATTERY_LEVEL:
break;
case ParagonValues.CHARACTERISTIC_ELAPSED_TIME:
break;
case ParagonValues.CHARACTERISTIC_BURNER_STATUS:
break;
case ParagonValues.CHARACTERISTIC_PROBE_CONNECTION_STATE:
break;
case ParagonValues.CHARACTERISTIC_COOK_MODE:
break;
case ParagonValues.CHARACTERISTIC_COOK_CONFIGURATION:
break;
case ParagonValues.CHARACTERISTIC_CURRENT_COOK_STATE:
break;
case ParagonValues.CHARACTERISTIC_CURRENT_TEMPERATURE:
//Skip refresh ui.
return;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
adapterDashboard.notifyDataSetChanged();
}
});
}
// @Override
// public boolean onCreateOptionsMenu(Menu menu) {
// // Inflate the menu; this adds items to the action bar if it is present.
// getMenuInflater().inflate(R.menu.menu_dashboard, menu);
// return true;
// }
//
// @Override
// public boolean onOptionsItemSelected(MenuItem item) {
// // Handle action bar item clicks here. The action bar will
// // automatically handle clicks on the Home/Up button, so long
// // as you specify a parent activity in AndroidManifest.xml.
// int id = item.getItemId();
//
// //noinspection SimplifiableIfStatement
// if (id == R.id.action_settings) {
// return true;
// }
//
// return super.onOptionsItemSelected(item);
// }
public class ProductListAdapter extends BaseAdapter {
@Override
public int getCount() {
return ProductManager.getInstance().getSize();
}
@Override
public ProductInfo getItem(int position) {
return ProductManager.getInstance().getProduct(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.d(TAG, "ProductListAdapter : getView()");
if (convertView == null) {
convertView = View.inflate(getApplicationContext(), R.layout.adapter_product_card_view, null);
convertView.setTag(new ViewHolder(convertView));
}
ViewHolder holderDashboard = (ViewHolder) convertView.getTag();
ProductInfo currentProduct = getItem(position);
holderDashboard.textNickname.setText(currentProduct.nickname);
int numMustData = currentProduct.getMustDataStatus();
String deviceId = (currentProduct.bluetoothDevice == null) ? currentProduct.address : currentProduct.bluetoothDevice.getName();
Log.d(TAG, "Product name : " + deviceId + " numMustData :" + numMustData);
if (currentProduct.isConnected()) {
if (currentProduct.isAllMustDataReceived()) {
holderDashboard.layoutProgress.setVisibility(View.GONE);
holderDashboard.layoutStatus.setVisibility(View.VISIBLE);
}
else {
holderDashboard.progressBar.setIndeterminate(false);
holderDashboard.progressBar.setMax(currentProduct.getNumMustInitData());
holderDashboard.progressBar.setProgress(numMustData);
holderDashboard.layoutProgress.setVisibility(View.VISIBLE);
holderDashboard.layoutStatus.setVisibility(View.GONE);
}
}
else {
holderDashboard.progressBar.setIndeterminate(true);
holderDashboard.layoutProgress.setVisibility(View.VISIBLE);
holderDashboard.layoutStatus.setVisibility(View.GONE);
}
// Let each productInfo instance handle product specific UI update
currentProduct.updateDashboardItemUI(holderDashboard);
return convertView;
}
public class ViewHolder {
public ImageView imageMark;
public ImageView imageLogo;
public TextView textNickname;
public TextView textCooking;
public TextView textBattery;
public ImageView imageBattery;
public View layoutProgress;
public View layoutStatus;
public ProgressBar progressBar;
public ViewHolder(View view) {
imageMark = (ImageView) view.findViewById(R.id.image_mark);
imageLogo = (ImageView) view.findViewById(R.id.image_logo);
textNickname = (TextView) view.findViewById(R.id.item_nickname);
textCooking = (TextView) view.findViewById(R.id.text_cooking);
textBattery = (TextView) view.findViewById(R.id.text_battery);
imageBattery = (ImageView) view.findViewById(R.id.image_battery);
layoutProgress = view.findViewById(R.id.layout_progressbar);
layoutStatus = view.findViewById(R.id.layout_status);
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
view.setTag(this);
}
}
}
}