Skip to content

Commit

Permalink
Adds a Bluetooth Details Screen with a RSSI graph
Browse files Browse the repository at this point in the history
  • Loading branch information
christianrowlands committed Jan 10, 2024
1 parent f516f5f commit 815314d
Show file tree
Hide file tree
Showing 14 changed files with 546 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.craxiom.networksurvey.fragments

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.fragment.navArgs
import com.craxiom.messaging.BluetoothRecord
import com.craxiom.messaging.BluetoothRecordData
import com.craxiom.networksurvey.listeners.IBluetoothSurveyRecordListener
import com.craxiom.networksurvey.services.NetworkSurveyService
import com.craxiom.networksurvey.ui.UNKNOWN_RSSI
import com.craxiom.networksurvey.ui.bluetooth.BluetoothDetailsScreen
import com.craxiom.networksurvey.ui.bluetooth.BluetoothDetailsViewModel
import com.craxiom.networksurvey.util.NsTheme
import timber.log.Timber

/**
* The fragment that displays the details of a single Bluetooth device from the scan results.
*/
class BluetoothDetailsFragment : AServiceDataFragment(), IBluetoothSurveyRecordListener {
private lateinit var bluetoothData: BluetoothRecordData
private lateinit var viewModel: BluetoothDetailsViewModel

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val args: BluetoothDetailsFragmentArgs by navArgs()
bluetoothData = args.bluetoothData

val composeView = ComposeView(requireContext())

composeView.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
viewModel = viewModel()
viewModel.bluetoothData = bluetoothData
if (bluetoothData.hasSignalStrength()) {
viewModel.addInitialRssi(bluetoothData.signalStrength.value)
} else {
viewModel.addInitialRssi(UNKNOWN_RSSI)
}
NsTheme {
BluetoothDetailsScreen(viewModel = viewModel)
}
}
}

return composeView
}

override fun onResume() {
super.onResume()

startAndBindToService()
}

override fun onSurveyServiceConnected(service: NetworkSurveyService?) {
if (service == null) return
service.registerBluetoothSurveyRecordListener(this)
}

override fun onSurveyServiceDisconnecting(service: NetworkSurveyService?) {
if (service == null) return
service.unregisterBluetoothSurveyRecordListener(this)
}

override fun onBluetoothSurveyRecord(bluetoothRecord: BluetoothRecord?) {
if (bluetoothRecord == null) return
if (bluetoothRecord.data.sourceAddress.equals(bluetoothData.sourceAddress)) {
if (bluetoothRecord.data.hasSignalStrength()) {
viewModel.addNewRssi(bluetoothRecord.data.signalStrength.value)
} else {
Timber.i("No signal strength present for ${bluetoothData.sourceAddress} in the bluetooth record")
viewModel.addNewRssi(UNKNOWN_RSSI)
}
}
}

override fun onBluetoothSurveyRecords(bluetoothRecords: MutableList<BluetoothRecord>?) {
val matchedRecord =
bluetoothRecords?.find { it.data.sourceAddress.equals(bluetoothData.sourceAddress) }

if (matchedRecord == null) {
Timber.i("No bluetooth record found for ${bluetoothData.sourceAddress} in the bluetooth scan results")
viewModel.addNewRssi(UNKNOWN_RSSI)
return
}

if (matchedRecord.data.hasSignalStrength()) {
viewModel.addNewRssi(matchedRecord.data.signalStrength.value)
} else {
Timber.i("No signal strength present for ${bluetoothData.sourceAddress} in the bluetooth record")
viewModel.addNewRssi(UNKNOWN_RSSI)

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.navigation.Navigation;
import androidx.navigation.fragment.NavHostFragment;
import androidx.preference.PreferenceManager;

import com.craxiom.messaging.BluetoothRecord;
import com.craxiom.messaging.BluetoothRecordData;
import com.craxiom.networksurvey.R;
import com.craxiom.networksurvey.constants.NetworkSurveyConstants;
import com.craxiom.networksurvey.databinding.FragmentBluetoothListBinding;
Expand Down Expand Up @@ -106,7 +108,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(applicationContext);
viewModel.setSortByIndex(preferences.getInt(NetworkSurveyConstants.PROPERTY_BLUETOOTH_DEVICES_SORT_ORDER, 0));

bluetoothRecyclerViewAdapter = new BluetoothRecyclerViewAdapter(bluetoothRecordSortedSet, getContext());
bluetoothRecyclerViewAdapter = new BluetoothRecyclerViewAdapter(bluetoothRecordSortedSet, getContext(), this);
binding.bluetoothDeviceList.setAdapter(bluetoothRecyclerViewAdapter);

binding.pauseButton.setOnClickListener(v -> viewModel.toggleUpdatesPaused());
Expand Down Expand Up @@ -231,6 +233,19 @@ public void onBluetoothSurveyRecords(List<BluetoothRecord> bluetoothRecords)
});
}

/**
* Navigates to the Bluetooth details screen for the selected Bluetooth device.
*/
public void navigateToBluetoothDetails(BluetoothRecordData bluetoothData)
{
FragmentActivity activity = getActivity();
if (activity == null) return;

Navigation.findNavController(activity, getId())
.navigate(BluetoothFragmentDirections.actionBtListFragmentToBtDetailsFragment(
bluetoothData));
}

/**
* Updates the view with the information stored in the view model.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
import com.craxiom.messaging.BluetoothRecordData;
import com.craxiom.networksurvey.R;
import com.craxiom.networksurvey.constants.BluetoothMessageConstants;
import com.craxiom.networksurvey.util.ColorUtils;

import timber.log.Timber;

/**
* The recycler view for the list of Bluetooth devices displayed in the UI.
Expand All @@ -25,11 +28,13 @@ public class BluetoothRecyclerViewAdapter extends RecyclerView.Adapter<Bluetooth
{
private final SortedList<BluetoothRecord> bluetoothRecords;
private final Context context;
private final BluetoothFragment bluetoothFragment;

BluetoothRecyclerViewAdapter(SortedList<BluetoothRecord> items, Context context)
BluetoothRecyclerViewAdapter(SortedList<BluetoothRecord> items, Context context, BluetoothFragment bluetoothFragment)
{
bluetoothRecords = items;
this.context = context;
this.bluetoothFragment = bluetoothFragment;
}

@NonNull
Expand All @@ -47,6 +52,7 @@ public void onBindViewHolder(final ViewHolder holder, int position)
{
final BluetoothRecord bluetoothRecord = bluetoothRecords.get(position);
final BluetoothRecordData data = bluetoothRecord.getData();
holder.bluetoothData = data;
final String sourceAddress = data.getSourceAddress();
if (!sourceAddress.isEmpty())
{
Expand All @@ -56,9 +62,9 @@ public void onBindViewHolder(final ViewHolder holder, int position)

if (data.hasSignalStrength())
{
final float signalStrength = data.getSignalStrength().getValue();
final int signalStrength = (int) data.getSignalStrength().getValue();
holder.signalStrength.setText(context.getString(R.string.dbm_value, String.valueOf(signalStrength)));
holder.signalStrength.setTextColor(context.getResources().getColor(getColorForSignalStrength(signalStrength), null));
holder.signalStrength.setTextColor(context.getResources().getColor(ColorUtils.getColorForSignalStrength(signalStrength), null));
} else
{
holder.signalStrength.setText("");
Expand All @@ -83,43 +89,25 @@ public int getItemCount()
}

/**
* @param signalStrength The signal strength value in dBm.
* @return The resource ID for the color that should be used for the signal strength text.
* Navigates to the Bluetooth details screen for the selected Bluetooth device.
*/
private int getColorForSignalStrength(float signalStrength)
private void navigateToDetails(BluetoothRecordData bluetoothData)
{
final int colorResourceId;
if (signalStrength > -60)
{
colorResourceId = R.color.rssi_green;
} else if (signalStrength > -70)
{
colorResourceId = R.color.rssi_yellow;
} else if (signalStrength > -80)
{
colorResourceId = R.color.rssi_orange;
} else if (signalStrength > -90)
{
colorResourceId = R.color.rssi_red;
} else
{
colorResourceId = R.color.rssi_deep_red;
}

return colorResourceId;
bluetoothFragment.navigateToBluetoothDetails(bluetoothData);
}

/**
* The holder for the view components that go into the View. These UI components will be updated with the content
* in the onBindViewHolder method.
*/
static class ViewHolder extends RecyclerView.ViewHolder
class ViewHolder extends RecyclerView.ViewHolder
{
final View mView;
final TextView sourceAddress;
final TextView signalStrength;
final TextView otaDeviceName;
final TextView supportedTechnologies;
BluetoothRecordData bluetoothData;

ViewHolder(View view)
{
Expand All @@ -129,6 +117,16 @@ static class ViewHolder extends RecyclerView.ViewHolder
signalStrength = view.findViewById(R.id.bluetooth_signal_strength);
otaDeviceName = view.findViewById(R.id.otaDeviceName);
supportedTechnologies = view.findViewById(R.id.supportedTechnologies);

mView.setOnClickListener(v -> {
if (bluetoothData.getSourceAddress().isEmpty())
{
Timber.wtf("The source address is empty so we are unable to show the bluetooth details screen.");
return;
}

navigateToDetails(bluetoothData);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void onBindViewHolder(final ViewHolder holder, int position)
{
final int signalStrength = (int) data.getSignalStrength().getValue();
holder.signalStrength.setText(context.getString(R.string.dbm_value, String.valueOf(signalStrength)));
holder.signalStrength.setTextColor(context.getResources().getColor(ColorUtils.getColorForWifiSignalStrength(signalStrength), null));
holder.signalStrength.setTextColor(context.getResources().getColor(ColorUtils.getColorForSignalStrength(signalStrength), null));
} else
{
holder.signalStrength.setText("");
Expand All @@ -98,7 +98,6 @@ public int getItemCount()
*/
private void navigateToWifiDetails(WifiNetwork wifiNetwork)
{

wifiNetworksFragment.navigateToWifiDetails(wifiNetwork);
}

Expand Down Expand Up @@ -132,7 +131,6 @@ class ViewHolder extends RecyclerView.ViewHolder
passpoint = view.findViewById(R.id.wifi_passpoint);
capabilities = view.findViewById(R.id.wifi_capabilities);

// FIXME Make a click anywhere on the row navigate to the details screen, even when clicking on text
mView.setOnClickListener(v -> {
Float signalStrength = null;
WifiBeaconRecordData data = wifiRecord.getData();
Expand All @@ -155,7 +153,7 @@ class ViewHolder extends RecyclerView.ViewHolder
data.hasChannel() ? data.getChannel().getValue() : null,
WifiBeaconMessageConstants.getEncryptionTypeString(data.getEncryptionType()),
data.hasPasspoint() ? data.getPasspoint().getValue() : null,
"");
capabilities.getText().toString());
navigateToWifiDetails(wifiNetwork);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,17 @@ class WifiDetailsFragment : AServiceDataFragment(), IWifiSurveyRecordListener {
}

override fun onWifiBeaconSurveyRecords(wifiBeaconRecords: MutableList<WifiRecordWrapper>?) {
val targetWifiRecordWrapper =
val matchedWifiRecordWrapper =
wifiBeaconRecords?.find { it.wifiBeaconRecord.data.bssid.equals(wifiNetwork.bssid) }

if (targetWifiRecordWrapper == null) {
if (matchedWifiRecordWrapper == null) {
Timber.i("No wifi record found for ${wifiNetwork.bssid} in the wifi scan results")
viewModel.addNewRssi(UNKNOWN_RSSI)
return
}

if (targetWifiRecordWrapper.wifiBeaconRecord.data.hasSignalStrength()) {
viewModel.addNewRssi(targetWifiRecordWrapper.wifiBeaconRecord.data.signalStrength.value)
if (matchedWifiRecordWrapper.wifiBeaconRecord.data.hasSignalStrength()) {
viewModel.addNewRssi(matchedWifiRecordWrapper.wifiBeaconRecord.data.signalStrength.value)
} else {
Timber.i("No signal strength present for ${wifiNetwork.bssid} in the wifi beacon record")
viewModel.addNewRssi(UNKNOWN_RSSI)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ public void onWifiScanUpdate(List<ScanResult> apScanResults)
}

/**
* Notification for when a new single Bluetooth Clasic scan result is available to process.
* Notification for when a new single Bluetooth Classic scan result is available to process.
*
* @param device The Bluetooth device object associated with the scan.
* @param rssi The RSSI value associated with the scan.
Expand Down
Loading

0 comments on commit 815314d

Please sign in to comment.