From 061897207a88bb00064e115aab76372550d8a1a0 Mon Sep 17 00:00:00 2001 From: Joseph Liu Date: Mon, 7 Mar 2022 22:52:27 -0800 Subject: [PATCH 01/14] Merged with Brian's Code --- .idea/misc.xml | 5 + .idea/vcs.xml | 6 + app/src/main/AndroidManifest.xml | 3 + .../bluetoothlegatt/BluetoothLeService.java | 319 ++++++++++++++++++ .../DeviceControlActivity.java | 314 +++++++++++++++++ .../bluetoothlegatt/DeviceScanActivity.java | 271 +++++++++++++++ .../bluetoothlegatt/SampleGattAttributes.java | 42 +++ .../actionbar_indeterminate_progress.xml | 23 ++ .../layout/gatt_services_characteristics.xml | 71 ++++ app/src/main/res/layout/listitem_device.xml | 28 ++ app/src/main/res/menu/gatt_services.xml | 29 ++ app/src/main/res/menu/main.xml | 29 ++ app/src/main/res/values/strings.xml | 20 ++ 13 files changed, 1160 insertions(+) create mode 100644 .idea/vcs.xml create mode 100644 app/src/main/java/com/example/bottomnav/bluetoothlegatt/BluetoothLeService.java create mode 100644 app/src/main/java/com/example/bottomnav/bluetoothlegatt/DeviceControlActivity.java create mode 100644 app/src/main/java/com/example/bottomnav/bluetoothlegatt/DeviceScanActivity.java create mode 100644 app/src/main/java/com/example/bottomnav/bluetoothlegatt/SampleGattAttributes.java create mode 100644 app/src/main/res/layout/actionbar_indeterminate_progress.xml create mode 100644 app/src/main/res/layout/gatt_services_characteristics.xml create mode 100644 app/src/main/res/layout/listitem_device.xml create mode 100644 app/src/main/res/menu/gatt_services.xml create mode 100644 app/src/main/res/menu/main.xml diff --git a/.idea/misc.xml b/.idea/misc.xml index a219808..3165193 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -12,6 +12,11 @@ + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6df4fb5..19b819a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,9 @@ + + + 0) { + final StringBuilder stringBuilder = new StringBuilder(data.length); + for(byte byteChar : data) + stringBuilder.append(String.format("%02X ", byteChar)); + intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString()); + } + } + sendBroadcast(intent); + } + + public class LocalBinder extends Binder { + BluetoothLeService getService() { + return BluetoothLeService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + @Override + public boolean onUnbind(Intent intent) { + // After using a given device, you should make sure that BluetoothGatt.close() is called + // such that resources are cleaned up properly. In this particular example, close() is + // invoked when the UI is disconnected from the Service. + close(); + return super.onUnbind(intent); + } + + private final IBinder mBinder = new LocalBinder(); + + /** + * Initializes a reference to the local Bluetooth adapter. + * + * @return Return true if the initialization is successful. + */ + public boolean initialize() { + // For API level 18 and above, get a reference to BluetoothAdapter through + // BluetoothManager. + if (mBluetoothManager == null) { + mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); + if (mBluetoothManager == null) { + Log.e(TAG, "Unable to initialize BluetoothManager."); + return false; + } + } + + mBluetoothAdapter = mBluetoothManager.getAdapter(); + if (mBluetoothAdapter == null) { + Log.e(TAG, "Unable to obtain a BluetoothAdapter."); + return false; + } + + return true; + } + + /** + * Connects to the GATT server hosted on the Bluetooth LE device. + * + * @param address The device address of the destination device. + * + * @return Return true if the connection is initiated successfully. The connection result + * is reported asynchronously through the + * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} + * callback. + */ + public boolean connect(final String address) { + if (mBluetoothAdapter == null || address == null) { + Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); + return false; + } + + // Previously connected device. Try to reconnect. + if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) + && mBluetoothGatt != null) { + Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection."); + if (mBluetoothGatt.connect()) { + mConnectionState = STATE_CONNECTING; + return true; + } else { + return false; + } + } + + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); + if (device == null) { + Log.w(TAG, "Device not found. Unable to connect."); + return false; + } + // We want to directly connect to the device, so we are setting the autoConnect + // parameter to false. + mBluetoothGatt = device.connectGatt(this, false, mGattCallback); + Log.d(TAG, "Trying to create a new connection."); + mBluetoothDeviceAddress = address; + mConnectionState = STATE_CONNECTING; + return true; + } + + /** + * Disconnects an existing connection or cancel a pending connection. The disconnection result + * is reported asynchronously through the + * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} + * callback. + */ + public void disconnect() { + if (mBluetoothAdapter == null || mBluetoothGatt == null) { + Log.w(TAG, "BluetoothAdapter not initialized"); + return; + } + mBluetoothGatt.disconnect(); + } + + /** + * After using a given BLE device, the app must call this method to ensure resources are + * released properly. + */ + public void close() { + if (mBluetoothGatt == null) { + return; + } + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + + /** + * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported + * asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)} + * callback. + * + * @param characteristic The characteristic to read from. + */ + public void readCharacteristic(BluetoothGattCharacteristic characteristic) { + if (mBluetoothAdapter == null || mBluetoothGatt == null) { + Log.w(TAG, "BluetoothAdapter not initialized"); + return; + } + mBluetoothGatt.readCharacteristic(characteristic); + } + + /** + * Enables or disables notification on a give characteristic. + * + * @param characteristic Characteristic to act on. + * @param enabled If true, enable notification. False otherwise. + */ + public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, + boolean enabled) { + if (mBluetoothAdapter == null || mBluetoothGatt == null) { + Log.w(TAG, "BluetoothAdapter not initialized"); + return; + } + mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); + + // This is specific to Heart Rate Measurement. + if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { + BluetoothGattDescriptor descriptor = characteristic.getDescriptor( + UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); + descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + mBluetoothGatt.writeDescriptor(descriptor); + } + } + + /** + * Retrieves a list of supported GATT services on the connected device. This should be + * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully. + * + * @return A {@code List} of supported services. + */ + public List getSupportedGattServices() { + if (mBluetoothGatt == null) return null; + + return mBluetoothGatt.getServices(); + } +} diff --git a/app/src/main/java/com/example/bottomnav/bluetoothlegatt/DeviceControlActivity.java b/app/src/main/java/com/example/bottomnav/bluetoothlegatt/DeviceControlActivity.java new file mode 100644 index 0000000..f0bbea0 --- /dev/null +++ b/app/src/main/java/com/example/bottomnav/bluetoothlegatt/DeviceControlActivity.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2013 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.example.bottomnav.bluetoothlegatt; + +import com.example.bottomnav.R; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.EditText; +import android.app.Activity; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattService; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ExpandableListView; +import android.widget.SimpleExpandableListAdapter; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * For a given BLE device, this Activity provides the user interface to connect, display data, + * and display GATT services and characteristics supported by the device. The Activity + * communicates with {@code BluetoothLeService}, which in turn interacts with the + * Bluetooth LE API. + */ +public class DeviceControlActivity extends Activity { + private final static String TAG = DeviceControlActivity.class.getSimpleName(); + + public static final String EXTRAS_DEVICE_NAME = "DEVICE_NAME"; + public static final String EXTRAS_DEVICE_ADDRESS = "DEVICE_ADDRESS"; + + private TextView mConnectionState; + private TextView mDataField; + private String mDeviceName; + private String mDeviceAddress; + private ExpandableListView mGattServicesList; + private BluetoothLeService mBluetoothLeService; + private ArrayList> mGattCharacteristics = + new ArrayList>(); + private boolean mConnected = false; + private BluetoothGattCharacteristic mNotifyCharacteristic; + + private final String LIST_NAME = "NAME"; + private final String LIST_UUID = "UUID"; + + // Code to manage Service lifecycle. + private final ServiceConnection mServiceConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName componentName, IBinder service) { + mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService(); + if (!mBluetoothLeService.initialize()) { + Log.e(TAG, "Unable to initialize Bluetooth"); + finish(); + } + // Automatically connects to the device upon successful start-up initialization. + mBluetoothLeService.connect(mDeviceAddress); + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + mBluetoothLeService = null; + } + }; + + // Handles various events fired by the Service. + // ACTION_GATT_CONNECTED: connected to a GATT server. + // ACTION_GATT_DISCONNECTED: disconnected from a GATT server. + // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services. + // ACTION_DATA_AVAILABLE: received data from the device. This can be a result of read + // or notification operations. + private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { + mConnected = true; + updateConnectionState(R.string.connected); + invalidateOptionsMenu(); + } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { + mConnected = false; + updateConnectionState(R.string.disconnected); + invalidateOptionsMenu(); + clearUI(); + } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { + // Show all the supported services and characteristics on the user interface. + displayGattServices(mBluetoothLeService.getSupportedGattServices()); + } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) { + displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); + } + } + }; + + // If a given GATT characteristic is selected, check for supported features. This sample + // demonstrates 'Read' and 'Notify' features. See + // http://d.android.com/reference/android/bluetooth/BluetoothGatt.html for the complete + // list of supported characteristic features. + private final ExpandableListView.OnChildClickListener servicesListClickListner = + new ExpandableListView.OnChildClickListener() { + @Override + public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, + int childPosition, long id) { + if (mGattCharacteristics != null) { + final BluetoothGattCharacteristic characteristic = + mGattCharacteristics.get(groupPosition).get(childPosition); + final int charaProp = characteristic.getProperties(); + if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) { + // If there is an active notification on a characteristic, clear + // it first so it doesn't update the data field on the user interface. + if (mNotifyCharacteristic != null) { + mBluetoothLeService.setCharacteristicNotification( + mNotifyCharacteristic, false); + mNotifyCharacteristic = null; + } + mBluetoothLeService.readCharacteristic(characteristic); + } + if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { + mNotifyCharacteristic = characteristic; + mBluetoothLeService.setCharacteristicNotification( + characteristic, true); + } + return true; + } + return false; + } + }; + + private void clearUI() { + mGattServicesList.setAdapter((SimpleExpandableListAdapter) null); + mDataField.setText(R.string.no_data); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.gatt_services_characteristics); + + final Intent intent = getIntent(); + mDeviceName = intent.getStringExtra(EXTRAS_DEVICE_NAME); + mDeviceAddress = intent.getStringExtra(EXTRAS_DEVICE_ADDRESS); + + // Sets up UI references. + ((TextView) findViewById(R.id.device_address)).setText(mDeviceAddress); + mGattServicesList = (ExpandableListView) findViewById(R.id.gatt_services_list); + mGattServicesList.setOnChildClickListener(servicesListClickListner); + mConnectionState = (TextView) findViewById(R.id.connection_state); + mDataField = (TextView) findViewById(R.id.data_value); + + getActionBar().setTitle(mDeviceName); + getActionBar().setDisplayHomeAsUpEnabled(true); + Intent gattServiceIntent = new Intent(this, BluetoothLeService.class); + bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE); + } + + @Override + protected void onResume() { + super.onResume(); + registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter()); + if (mBluetoothLeService != null) { + final boolean result = mBluetoothLeService.connect(mDeviceAddress); + Log.d(TAG, "Connect request result=" + result); + } + } + + @Override + protected void onPause() { + super.onPause(); + unregisterReceiver(mGattUpdateReceiver); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unbindService(mServiceConnection); + mBluetoothLeService = null; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.gatt_services, menu); + if (mConnected) { + menu.findItem(R.id.menu_connect).setVisible(false); + menu.findItem(R.id.menu_disconnect).setVisible(true); + } else { + menu.findItem(R.id.menu_connect).setVisible(true); + menu.findItem(R.id.menu_disconnect).setVisible(false); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch(item.getItemId()) { + case R.id.menu_connect: + mBluetoothLeService.connect(mDeviceAddress); + return true; + case R.id.menu_disconnect: + mBluetoothLeService.disconnect(); + return true; + case android.R.id.home: + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void updateConnectionState(final int resourceId) { + runOnUiThread(new Runnable() { + @Override + public void run() { + mConnectionState.setText(resourceId); + } + }); + } + + private void displayData(String data) { + if (data != null) { + mDataField.setText(data); + } + } + + // Demonstrates how to iterate through the supported GATT Services/Characteristics. + // In this sample, we populate the data structure that is bound to the ExpandableListView + // on the UI. + private void displayGattServices(List gattServices) { + if (gattServices == null) return; + String uuid = null; + String unknownServiceString = getResources().getString(R.string.unknown_service); + String unknownCharaString = getResources().getString(R.string.unknown_characteristic); + ArrayList> gattServiceData = new ArrayList>(); + ArrayList>> gattCharacteristicData + = new ArrayList>>(); + mGattCharacteristics = new ArrayList>(); + + // Loops through available GATT Services. + for (BluetoothGattService gattService : gattServices) { + HashMap currentServiceData = new HashMap(); + uuid = gattService.getUuid().toString(); + currentServiceData.put( + LIST_NAME, SampleGattAttributes.lookup(uuid, unknownServiceString)); + currentServiceData.put(LIST_UUID, uuid); + gattServiceData.add(currentServiceData); + + ArrayList> gattCharacteristicGroupData = + new ArrayList>(); + List gattCharacteristics = + gattService.getCharacteristics(); + ArrayList charas = + new ArrayList(); + + // Loops through available Characteristics. + for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { + charas.add(gattCharacteristic); + HashMap currentCharaData = new HashMap(); + uuid = gattCharacteristic.getUuid().toString(); + currentCharaData.put( + LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString)); + currentCharaData.put(LIST_UUID, uuid); + gattCharacteristicGroupData.add(currentCharaData); + } + mGattCharacteristics.add(charas); + gattCharacteristicData.add(gattCharacteristicGroupData); + } + + SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter( + this, + gattServiceData, + android.R.layout.simple_expandable_list_item_2, + new String[] {LIST_NAME, LIST_UUID}, + new int[] { android.R.id.text1, android.R.id.text2 }, + gattCharacteristicData, + android.R.layout.simple_expandable_list_item_2, + new String[] {LIST_NAME, LIST_UUID}, + new int[] { android.R.id.text1, android.R.id.text2 } + ); + mGattServicesList.setAdapter(gattServiceAdapter); + } + + private static IntentFilter makeGattUpdateIntentFilter() { + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED); + intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED); + intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED); + intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE); + return intentFilter; + } +} diff --git a/app/src/main/java/com/example/bottomnav/bluetoothlegatt/DeviceScanActivity.java b/app/src/main/java/com/example/bottomnav/bluetoothlegatt/DeviceScanActivity.java new file mode 100644 index 0000000..4d60266 --- /dev/null +++ b/app/src/main/java/com/example/bottomnav/bluetoothlegatt/DeviceScanActivity.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2013 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.example.bottomnav.bluetoothlegatt; + + +import android.app.Activity; +import android.app.ListActivity; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.example.bottomnav.R; + +import java.util.ArrayList; + +/** + * Activity for scanning and displaying available Bluetooth LE devices. + */ +public class DeviceScanActivity extends ListActivity { + private LeDeviceListAdapter mLeDeviceListAdapter; + private BluetoothAdapter mBluetoothAdapter; + private boolean mScanning; + private Handler mHandler; + + private static final int REQUEST_ENABLE_BT = 1; + // Stops scanning after 10 seconds. + private static final long SCAN_PERIOD = 10000; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActionBar().setTitle(R.string.title_devices); + mHandler = new Handler(); + + // 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)) { + Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); + finish(); + } + + // Initializes a Bluetooth adapter. For API level 18 and above, get a reference to + // BluetoothAdapter through BluetoothManager. + final BluetoothManager bluetoothManager = + (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); + mBluetoothAdapter = bluetoothManager.getAdapter(); + + // Checks if Bluetooth is supported on the device. + if (mBluetoothAdapter == null) { + Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show(); + finish(); + return; + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.main, menu); + if (!mScanning) { + menu.findItem(R.id.menu_stop).setVisible(false); + menu.findItem(R.id.menu_scan).setVisible(true); + menu.findItem(R.id.menu_refresh).setActionView(null); + } else { + menu.findItem(R.id.menu_stop).setVisible(true); + menu.findItem(R.id.menu_scan).setVisible(false); + menu.findItem(R.id.menu_refresh).setActionView( + R.layout.actionbar_indeterminate_progress); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_scan: + mLeDeviceListAdapter.clear(); + scanLeDevice(true); + break; + case R.id.menu_stop: + scanLeDevice(false); + break; + } + return true; + } + + @Override + protected void onResume() { + super.onResume(); + + // Ensures Bluetooth is enabled on the device. If Bluetooth is not currently enabled, + // fire an intent to display a dialog asking the user to grant permission to enable it. + if (!mBluetoothAdapter.isEnabled()) { + if (!mBluetoothAdapter.isEnabled()) { + Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); + } + } + + // Initializes list view adapter. + mLeDeviceListAdapter = new LeDeviceListAdapter(); + setListAdapter(mLeDeviceListAdapter); + scanLeDevice(true); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // User chose not to enable Bluetooth. + if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) { + finish(); + return; + } + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + protected void onPause() { + super.onPause(); + scanLeDevice(false); + mLeDeviceListAdapter.clear(); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position); + if (device == null) return; + final Intent intent = new Intent(this, DeviceControlActivity.class); + intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_NAME, device.getName()); + intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_ADDRESS, device.getAddress()); + if (mScanning) { + mBluetoothAdapter.stopLeScan(mLeScanCallback); + mScanning = false; + } + startActivity(intent); + } + + private void scanLeDevice(final boolean enable) { + if (enable) { + // Stops scanning after a pre-defined scan period. + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + mScanning = false; + mBluetoothAdapter.stopLeScan(mLeScanCallback); + invalidateOptionsMenu(); + } + }, SCAN_PERIOD); + + mScanning = true; + mBluetoothAdapter.startLeScan(mLeScanCallback); + } else { + mScanning = false; + mBluetoothAdapter.stopLeScan(mLeScanCallback); + } + invalidateOptionsMenu(); + } + + // Adapter for holding devices found through scanning. + private class LeDeviceListAdapter extends BaseAdapter { + private ArrayList mLeDevices; + private LayoutInflater mInflator; + + public LeDeviceListAdapter() { + super(); + mLeDevices = new ArrayList(); + mInflator = DeviceScanActivity.this.getLayoutInflater(); + } + + public void addDevice(BluetoothDevice device) { + if(!mLeDevices.contains(device)) { + mLeDevices.add(device); + } + } + + public BluetoothDevice getDevice(int position) { + return mLeDevices.get(position); + } + + public void clear() { + mLeDevices.clear(); + } + + @Override + public int getCount() { + return mLeDevices.size(); + } + + @Override + public Object getItem(int i) { + return mLeDevices.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + ViewHolder viewHolder; + // General ListView optimization code. + if (view == null) { + view = mInflator.inflate(R.layout.listitem_device, null); + viewHolder = new ViewHolder(); + viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address); + viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name); + view.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) view.getTag(); + } + + BluetoothDevice device = mLeDevices.get(i); + final String deviceName = device.getName(); + if (deviceName != null && deviceName.length() > 0) + viewHolder.deviceName.setText(deviceName); + else + viewHolder.deviceName.setText(R.string.unknown_device); + viewHolder.deviceAddress.setText(device.getAddress()); + + return view; + } + } + + // Device scan callback. + private BluetoothAdapter.LeScanCallback mLeScanCallback = + new BluetoothAdapter.LeScanCallback() { + + @Override + public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { + runOnUiThread(new Runnable() { + @Override + public void run() { + mLeDeviceListAdapter.addDevice(device); + mLeDeviceListAdapter.notifyDataSetChanged(); + } + }); + } + }; + + static class ViewHolder { + TextView deviceName; + TextView deviceAddress; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bottomnav/bluetoothlegatt/SampleGattAttributes.java b/app/src/main/java/com/example/bottomnav/bluetoothlegatt/SampleGattAttributes.java new file mode 100644 index 0000000..32860db --- /dev/null +++ b/app/src/main/java/com/example/bottomnav/bluetoothlegatt/SampleGattAttributes.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 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.example.bottomnav.bluetoothlegatt; + +import java.util.HashMap; + +/** + * This class includes a small subset of standard GATT attributes for demonstration purposes. + */ +public class SampleGattAttributes { + private static HashMap attributes = new HashMap(); + public static String HEART_RATE_MEASUREMENT = "00002a37-0000-1000-8000-00805f9b34fb"; + public static String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb"; + + static { + // Sample Services. + attributes.put("0000180d-0000-1000-8000-00805f9b34fb", "Heart Rate Service"); + attributes.put("0000180a-0000-1000-8000-00805f9b34fb", "Device Information Service"); + // Sample Characteristics. + attributes.put(HEART_RATE_MEASUREMENT, "Heart Rate Measurement"); + attributes.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String"); + } + + public static String lookup(String uuid, String defaultName) { + String name = attributes.get(uuid); + return name == null ? defaultName : name; + } +} diff --git a/app/src/main/res/layout/actionbar_indeterminate_progress.xml b/app/src/main/res/layout/actionbar_indeterminate_progress.xml new file mode 100644 index 0000000..a950833 --- /dev/null +++ b/app/src/main/res/layout/actionbar_indeterminate_progress.xml @@ -0,0 +1,23 @@ + + + + diff --git a/app/src/main/res/layout/gatt_services_characteristics.xml b/app/src/main/res/layout/gatt_services_characteristics.xml new file mode 100644 index 0000000..2f31061 --- /dev/null +++ b/app/src/main/res/layout/gatt_services_characteristics.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/listitem_device.xml b/app/src/main/res/layout/listitem_device.xml new file mode 100644 index 0000000..eff44fc --- /dev/null +++ b/app/src/main/res/layout/listitem_device.xml @@ -0,0 +1,28 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/gatt_services.xml b/app/src/main/res/menu/gatt_services.xml new file mode 100644 index 0000000..464d32f --- /dev/null +++ b/app/src/main/res/menu/gatt_services.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml new file mode 100644 index 0000000..39dd66a --- /dev/null +++ b/app/src/main/res/menu/main.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a8342fe..5911a11 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,4 +4,24 @@ Dashboard Notifications Settings + + BLE is not supported + Data: + Device address: + State: + No data + Connected + Disconnected + BLE Device Scan + Bluetooth not supported. + + Unknown device + Unknown characteristic + Unknown service + + + Connect + Disconnect + Scan + Stop \ No newline at end of file From de2cd78f9802bdc02907cfac2d7eb4e27901fa63 Mon Sep 17 00:00:00 2001 From: Joseph Liu Date: Thu, 7 Apr 2022 21:31:52 -0700 Subject: [PATCH 02/14] Attempted at ListView --- .idea/misc.xml | 7 +- .../com/example/bottomnav/MainActivity.java | 18 +++++ .../ui/notifications/ListDisplay.java | 77 +++++++++++++++++++ .../notifications/NotificationsFragment.java | 37 ++++++--- app/src/main/res/layout/activity_listview.xml | 15 ++++ app/src/main/res/layout/activity_main.xml | 18 ++++- 6 files changed, 158 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/example/bottomnav/ui/notifications/ListDisplay.java create mode 100644 app/src/main/res/layout/activity_listview.xml diff --git a/.idea/misc.xml b/.idea/misc.xml index 3165193..10083bd 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -14,9 +14,14 @@ + + + + + - + diff --git a/app/src/main/java/com/example/bottomnav/MainActivity.java b/app/src/main/java/com/example/bottomnav/MainActivity.java index 5a549f1..4610416 100644 --- a/app/src/main/java/com/example/bottomnav/MainActivity.java +++ b/app/src/main/java/com/example/bottomnav/MainActivity.java @@ -1,6 +1,9 @@ package com.example.bottomnav; import android.os.Bundle; +import android.widget.ArrayAdapter; +import android.widget.ListView; + import com.google.android.material.bottomnavigation.BottomNavigationView; import androidx.appcompat.app.AppCompatActivity; import androidx.navigation.NavController; @@ -13,10 +16,24 @@ public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; + String[] CAN_receiver = new String[]{"petals", "bms", "dashboard", "petals", "petals", + "dashboard", "bms", "dashboard", "lights", "petals", "bms"}; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // Creates an Adapter that adapts array CAN_receiver to display + ArrayAdapter adapter = new ArrayAdapter(this, + R.layout.activity_listview, CAN_receiver); + + // A listView is created and adapted + ListView listView = findViewById(R.id.mobile_list); + listView.setAdapter(adapter); + + /* binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); @@ -28,6 +45,7 @@ protected void onCreate(Bundle savedInstanceState) { NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main); NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); NavigationUI.setupWithNavController(binding.navView, navController); + */ } } \ No newline at end of file diff --git a/app/src/main/java/com/example/bottomnav/ui/notifications/ListDisplay.java b/app/src/main/java/com/example/bottomnav/ui/notifications/ListDisplay.java new file mode 100644 index 0000000..1124ddd --- /dev/null +++ b/app/src/main/java/com/example/bottomnav/ui/notifications/ListDisplay.java @@ -0,0 +1,77 @@ +package com.example.bottomnav.ui.notifications; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +import com.example.bottomnav.R; + +import java.util.ArrayList; + +public class ListDisplay extends Activity { + //Array of strings + String[] CAN_receiver = new String[]{"petals", "bms", "dashboard", "petals", "petals", + "dashboard", "bms", "dashboard", "lights", "petals", "bms"}; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); +// setContentView(R.layout.activity_listview); +// +// // Creates an Adapter that adapts array CAN_receiver to display +// ArrayAdapter adapter = new ArrayAdapter(this, +// R.layout.activity_listview, CAN_receiver); +// +// // A listView is created and adapted +// ListView listView = findViewById(R.id.mobile_list); +// listView.setAdapter(adapter); + } + + + + + + + + + + // A kinda HashSet of CANPackets that only store 1 value + ArrayList index = new ArrayList<>(); + int x = 0; + ArrayList values = new ArrayList<>(); + + public void display() { + // Just a loop + while (x < 1000) { + //get new CANPackets + CANPacket packet = new CANPacket(0x02af2b17, 3.30); + + // Find position to insert packet._value into by looking through INDEX + // If packet._id not already in INDEX, add it and then insert packet._value into VALUES + for (int i = 0; i < index.size(); i++) { + if (index.get(i) == packet._id) { + values.remove(i); + values.add(i, packet._value); + } else if (i == index.size() - 1) { + index.add(packet._id); + } + } + + // Create new adapter and listView and display it + ArrayAdapter adp = new ArrayAdapter(this, R.layout.activity_listview, index); + ListView listView = findViewById(R.id.mobile_list); + listView.setAdapter(adp); + } + } + + class CANPacket { + CANPacket(int id, double value) { + _id = id; + _value = value; + } + + int _id; + double _value; + } +} diff --git a/app/src/main/java/com/example/bottomnav/ui/notifications/NotificationsFragment.java b/app/src/main/java/com/example/bottomnav/ui/notifications/NotificationsFragment.java index 0a3f420..5cd741e 100644 --- a/app/src/main/java/com/example/bottomnav/ui/notifications/NotificationsFragment.java +++ b/app/src/main/java/com/example/bottomnav/ui/notifications/NotificationsFragment.java @@ -4,6 +4,8 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -20,20 +22,31 @@ public class NotificationsFragment extends Fragment { private NotificationsViewModel notificationsViewModel; private FragmentNotificationsBinding binding; + //Array of strings + String[] CAN_receiver = new String[]{"petals", "bms", "dashboard", "petals", "petals", + "dashboard", "bms", "dashboard", "lights", "petals", "bms"}; + + @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - notificationsViewModel = new ViewModelProvider(this).get(NotificationsViewModel.class); - - binding = FragmentNotificationsBinding.inflate(inflater, container, false); - - final TextView textView = binding.textNotifications; - notificationsViewModel.getText().observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(@Nullable String s) { - textView.setText(s); - } - }); - return binding.getRoot(); + View view = inflater.inflate(R.layout.fragment_notifications, container, false); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + String[] CAN_receiver = new String[]{"petals", "bms", "dashboard", "petals", "petals", + "dashboard", "bms", "dashboard", "lights", "petals", "bms"}; + + // Creates an Adapter that adapts array CAN_receiver to display + ArrayAdapter adapter = new ArrayAdapter(getActivity(), + R.layout.activity_listview, CAN_receiver); + + // A listView is created and adapted + ListView listView = view.findViewById(R.id.mobile_list); + listView.setAdapter(adapter); } @Override diff --git a/app/src/main/res/layout/activity_listview.xml b/app/src/main/res/layout/activity_listview.xml new file mode 100644 index 0000000..d224c52 --- /dev/null +++ b/app/src/main/res/layout/activity_listview.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a57477b..b566d56 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,4 +1,19 @@ + + + + + + + \ No newline at end of file From 04577d67ab3094ff092c3ab08d23ad1d9efd9590 Mon Sep 17 00:00:00 2001 From: Joseph Liu Date: Thu, 7 Apr 2022 22:30:03 -0700 Subject: [PATCH 03/14] Fixed current listview (static) --- .../com/example/bottomnav/MainActivity.java | 18 ++---------------- .../ui/notifications/ListDisplay.java | 4 ++-- .../notifications/NotificationsFragment.java | 9 +++------ app/src/main/res/layout/activity_main.xml | 15 --------------- .../res/layout/fragment_notifications.xml | 19 +++++-------------- ...ivity_listview.xml => listview_layout.xml} | 15 +++++++++++++-- 6 files changed, 25 insertions(+), 55 deletions(-) rename app/src/main/res/layout/{activity_listview.xml => listview_layout.xml} (62%) diff --git a/app/src/main/java/com/example/bottomnav/MainActivity.java b/app/src/main/java/com/example/bottomnav/MainActivity.java index 4610416..77c28ee 100644 --- a/app/src/main/java/com/example/bottomnav/MainActivity.java +++ b/app/src/main/java/com/example/bottomnav/MainActivity.java @@ -4,36 +4,22 @@ import android.widget.ArrayAdapter; import android.widget.ListView; -import com.google.android.material.bottomnavigation.BottomNavigationView; import androidx.appcompat.app.AppCompatActivity; import androidx.navigation.NavController; import androidx.navigation.Navigation; import androidx.navigation.ui.AppBarConfiguration; import androidx.navigation.ui.NavigationUI; + import com.example.bottomnav.databinding.ActivityMainBinding; public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; - String[] CAN_receiver = new String[]{"petals", "bms", "dashboard", "petals", "petals", - "dashboard", "bms", "dashboard", "lights", "petals", "bms"}; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - // Creates an Adapter that adapts array CAN_receiver to display - ArrayAdapter adapter = new ArrayAdapter(this, - R.layout.activity_listview, CAN_receiver); - - // A listView is created and adapted - ListView listView = findViewById(R.id.mobile_list); - listView.setAdapter(adapter); - - /* binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); @@ -45,7 +31,7 @@ protected void onCreate(Bundle savedInstanceState) { NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main); NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); NavigationUI.setupWithNavController(binding.navView, navController); - */ + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/bottomnav/ui/notifications/ListDisplay.java b/app/src/main/java/com/example/bottomnav/ui/notifications/ListDisplay.java index 1124ddd..f569f46 100644 --- a/app/src/main/java/com/example/bottomnav/ui/notifications/ListDisplay.java +++ b/app/src/main/java/com/example/bottomnav/ui/notifications/ListDisplay.java @@ -59,8 +59,8 @@ public void display() { } // Create new adapter and listView and display it - ArrayAdapter adp = new ArrayAdapter(this, R.layout.activity_listview, index); - ListView listView = findViewById(R.id.mobile_list); + ArrayAdapter adp = new ArrayAdapter(this, R.layout.listview_layout, index); + ListView listView = findViewById(R.id.listy); listView.setAdapter(adp); } } diff --git a/app/src/main/java/com/example/bottomnav/ui/notifications/NotificationsFragment.java b/app/src/main/java/com/example/bottomnav/ui/notifications/NotificationsFragment.java index 5cd741e..591e0d4 100644 --- a/app/src/main/java/com/example/bottomnav/ui/notifications/NotificationsFragment.java +++ b/app/src/main/java/com/example/bottomnav/ui/notifications/NotificationsFragment.java @@ -37,15 +37,12 @@ public View onCreateView(@NonNull LayoutInflater inflater, public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - String[] CAN_receiver = new String[]{"petals", "bms", "dashboard", "petals", "petals", - "dashboard", "bms", "dashboard", "lights", "petals", "bms"}; - // Creates an Adapter that adapts array CAN_receiver to display - ArrayAdapter adapter = new ArrayAdapter(getActivity(), - R.layout.activity_listview, CAN_receiver); + ArrayAdapter adapter = new ArrayAdapter(getActivity(), + R.layout.listview_layout, CAN_receiver); // A listView is created and adapted - ListView listView = view.findViewById(R.id.mobile_list); + ListView listView = view.findViewById(R.id.listy); listView.setAdapter(adapter); } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b566d56..5becd5f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,19 +1,5 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_notifications.xml b/app/src/main/res/layout/fragment_notifications.xml index 01a3222..50ad31f 100644 --- a/app/src/main/res/layout/fragment_notifications.xml +++ b/app/src/main/res/layout/fragment_notifications.xml @@ -1,5 +1,5 @@ - - - \ No newline at end of file + android:id="@+id/listy" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_listview.xml b/app/src/main/res/layout/listview_layout.xml similarity index 62% rename from app/src/main/res/layout/activity_listview.xml rename to app/src/main/res/layout/listview_layout.xml index d224c52..7e243e2 100644 --- a/app/src/main/res/layout/activity_listview.xml +++ b/app/src/main/res/layout/listview_layout.xml @@ -1,4 +1,15 @@ + + + + + \ No newline at end of file From 03e9d46cd5674cb3776934087f96bd9778e4d7fb Mon Sep 17 00:00:00 2001 From: Joseph Liu Date: Thu, 7 Apr 2022 23:30:42 -0700 Subject: [PATCH 04/14] you can now edit the listview! --- .../notifications/NotificationsFragment.java | 21 +++++++++++++++++- .../res/layout/fragment_notifications.xml | 22 ++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/bottomnav/ui/notifications/NotificationsFragment.java b/app/src/main/java/com/example/bottomnav/ui/notifications/NotificationsFragment.java index 591e0d4..2b8842a 100644 --- a/app/src/main/java/com/example/bottomnav/ui/notifications/NotificationsFragment.java +++ b/app/src/main/java/com/example/bottomnav/ui/notifications/NotificationsFragment.java @@ -5,6 +5,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import androidx.annotation.NonNull; @@ -37,10 +39,27 @@ public View onCreateView(@NonNull LayoutInflater inflater, public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + EditText editText = view.findViewById(R.id.editText); + Button button = view.findViewById(R.id.addButton); + // Creates an Adapter that adapts array CAN_receiver to display - ArrayAdapter adapter = new ArrayAdapter(getActivity(), + ArrayAdapter adapter = new ArrayAdapter<>(getActivity(), R.layout.listview_layout, CAN_receiver); + // Creates new button logic with counter as final one-element array + final int[] counter = {0}; + View.OnClickListener onClickListener = v -> { // lambda function + CAN_receiver[counter[0]] = editText.getText().toString(); + if (counter[0] >= CAN_receiver.length) { + counter[0] = 0; + } else { + counter[0]++; + } + editText.setText(""); + adapter.notifyDataSetChanged(); + }; + button.setOnClickListener(onClickListener); + // A listView is created and adapted ListView listView = view.findViewById(R.id.listy); listView.setAdapter(adapter); diff --git a/app/src/main/res/layout/fragment_notifications.xml b/app/src/main/res/layout/fragment_notifications.xml index 50ad31f..a5683b0 100644 --- a/app/src/main/res/layout/fragment_notifications.xml +++ b/app/src/main/res/layout/fragment_notifications.xml @@ -1,14 +1,30 @@ - - + +