Skip to content
This repository has been archived by the owner on Apr 9, 2021. It is now read-only.

[MRG] Bluetooth Feature for Form Exchanging #260

Merged
merged 41 commits into from
Jul 17, 2019

Conversation

huangyz0918
Copy link
Contributor

@huangyz0918 huangyz0918 commented Jun 5, 2019

Closes #253 #265

What has been done to verify that this works as intended?

I have tested it using my Huawei Nexus 6P (Android 8.0) and Meizu M5 (Android 6.0).

Why is this the best possible solution? Were any other approaches considered?

Now, we are going to build a bluetooth transfer feature. Using bluetooth, we can share form instances between two devices. For bluetooth BLE and bluetooth classic, I choose bluetooth classic because bluetooth classic is compatible with more devices (BLE can only supports device with Android Version >= 4.3).

I'm going to introduce the basic bluetooth tools for ODK Skunkworks-Crow in the PR, The basic tools can be divides into two parts, the bluetooth client and bluetooth server. For data transmission, we need one device act as a bluetooth client and another as a server. If one is not ready, the bluetooth transmission will failed. So I want to build an alert dialog to ask which method our user would like to use, the Wifi Hotspot? or the Bluetooth? Only two device choose the same approach, the connection can be established.

For building this new feature, we have several tasks,

  • build basic bluetooth transfer client method.
  • build basic bluetooth transfer server method.
  • build a new BluetoothActivity.java for showing scanning results and device list.
  • build data transfer methods (short messages, files).
  • rename files to make them more universal (e.g, HpSenderActivity.java and BtSenderActivity.java)
  • build the basic alert dialog to ask user to choose a transfer method.
  • build the sending/receiving forms (instance) feature.
  • improve the UI/UX progress.

How does this change affect users? Describe intentional changes to behavior and behavior that could have accidentally been affected by code changes. In other words, what are the regression risks?

It needs tests with new features and make sure the old features work well.

Before submitting this PR, please make sure you have:

  • run ./gradlew checkCode and confirmed all checks still pass OR confirm CircleCI build passes
  • verified that any code or assets from external sources are properly credited in comments and/or in the about file.

@huangyz0918 huangyz0918 marked this pull request as ready for review June 24, 2019 14:16
@huangyz0918 huangyz0918 changed the title Bluetooth Utilities [WIP] Bluetooth Utilities Jun 25, 2019
added alert dialog while connecting to other device; extracted the common methods to
PermissionUtilies.java.
@huangyz0918
Copy link
Contributor Author

huangyz0918 commented Jun 26, 2019

Now, I finished the bluetooth tools for file sending and receiving, I made a demo for sending a short message using bluetooth, you can see this video demo.

I created a bluetooth package in the root of this project,

  • BluetoothBasic.java: Basic methods for bluetooth connecting/sending/loop reading.
  • BluetoothReceiver.java: BroadcastReceiver for system bluetooth services.
  • BluetoothClient.java: extend from BluetoothBasic.java, for data sending.
  • BluetoothServer.java: extend from BluetoothBasic.java, for data receiving.
  • BluetoothUtils.java: common methods for bluetooth status checking, etc.

I built the whole bluetooth tools with data receiving/sending, but when I refer to the form instances, I saw there are a lot of code related to data processing in UploadJob.java and DownloadJob.java, so I'm confused: should I migrate the bluetooth sending/receiving code into the exist *Job.java files or build another BtUploadJob.java and extract some common method into utils like UploadUtils.java?

@shobhitagarwal1612 @lakshyagupta21 @Chromicle I think I need some suggestions for further updating, thanks.

@huangyz0918
Copy link
Contributor Author

@lakshyagupta21 @shobhitagarwal1612 I think it's ready for a review.

@huangyz0918 huangyz0918 changed the title [WIP] Bluetooth Utilities [MRG] Bluetooth Feature for Form Exchanging Jun 30, 2019
Copy link
Contributor

@lakshyagupta21 lakshyagupta21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things looks good to me, but it needs some code changes related to style and some case handling please take a look.

PersistableBundleCompat extras = new PersistableBundleCompat();
extras.putLongArray(UploadJob.INSTANCES, instancesToSend);
extras.putBoolean("isBluetooth", false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

@@ -45,6 +49,7 @@
public static final String INSTANCE_IDS = "instance_ids";
private static final String INSTANCE_LIST_ACTIVITY_SORTING_ORDER = "instanceListActivitySortingOrder";
private static final int INSTANCE_LOADER = 1;
private static final String[] TRANSFER_OPTIONS = {"via bluetooth", "via hotspot"};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets create or resuse the string resources and via can be removed from the string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

// show dialog and connected
Timber.d("Start Sending");
processSelectedFiles(instancesToSend);

// close connection
socket.close();
serverSocket.close();
if (socket != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When all the form transfer is done newly created bluetoothSocket also needs to closed for both sender and receiver other it may result in memory leaks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks for reminding. Fixed.

/**
* rescan the bluetooth devices and update the list.
*/
public void rescan() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rescan logic should not be present in adpater class same for below method addBoundedDevices

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See updated.

.subscribe(bluetoothEvent -> {
switch (bluetoothEvent.getStatus()) {
case CONNECTED:
progressDialog.setMessage("Connected, start downloading...");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create string resource for this or use the same that is defined for hotspot.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@lakshyagupta21
Copy link
Contributor

Also one thing that I noticed while scanning the devices is that it is showing all the devices which was paired before to the device but not available at the moment of scanning, so please take a look at that as well.

@@ -18,6 +18,10 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- Bluetooth Related -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The permissions are self explanatory. No need for this comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

}
}

public interface Listener {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rename this interface to something less generic like BluetoothReceiverListener

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

public interface Listener {
void foundDevice(BluetoothDevice device);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of the method should display its intention. So, onDeviceFound would sound more accurate. Comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored.

/**
* Checking if current device supports bluetooth.
*/
public static boolean isSupportBluetooth() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method name should be isBluetoothSupported

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed.

Intent intent = new Intent(getActivity(), SendActivity.class);
Intent hotspotIntent = new Intent(getActivity(), HpSenderActivity.class);
Intent bluetoothIntent = new Intent(getActivity(), BtSenderActivity.class);
setupSendingIntent(hotspotIntent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setup should happen after the TRANSFER_OPTION has been selected to prevent extra initialisation of the other intent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

startActivity(hotspotIntent);
getActivity().finish();
}
break;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switch cases have most of their code common. Please reuse the code.

skunkworks_crow/src/main/res/values/strings.xml Outdated Show resolved Hide resolved
android:gravity="center_vertical"
android:textSize="12sp"
tools:text="default device address" />

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove blank line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

tools:text="default device address" />

</LinearLayout>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@huangyz0918
Copy link
Contributor Author

@lakshyagupta21 @shobhitagarwal1612 I have improved the code as your suggested, please have a new round review and make sure the code is good to merge, thanks.

I have tested the new feature using sender/receiver with success:

  1. Google Nexus 6P Android 8.0.0 (sender/receiver)
  2. Google Nexus 6P Android 8.1.0 (sender/receiver)
  3. Google Nexus 5 Android 6.0.1 (sender/receiver)
  4. Meizu M5 Android 6.0 (sender/receiver)

if (bluetoothSocket.isConnected()) {
rxEventBus.post(new BluetoothEvent(BluetoothEvent.Status.CONNECTED));
}
if (bluetoothSocket.isConnected()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be replaced with else

Copy link
Contributor

@lakshyagupta21 lakshyagupta21 Jul 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should go in else because previous if block says it to connect so I think this if stmt check can be removed and we can directly write
rxEventBus.post(new BluetoothEvent(BluetoothEvent.Status.CONNECTED));
as it is expected that

if (!bluetoothSocket.isConnected()) {
    bluetoothSocket.connect();
}

will ensure that bluetooth is connected and if by any chance it fails then it should throw some error or callback that we need to handle explicitly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed with @lakshyagupta21 , just removing the if-else.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is bluetoothSocket.connect(); a synchronous event? Are there any callbacks for bluetooth connected or disconnected? If not then we need to fix it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second problem is that we shouldn't establish bluetooth socket connection within DownloadJob. DownloadJob's purpose is to start download task and it shouldn't be doing anything else. This would testing the code in the later part difficult.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is bluetoothSocket.connect(); a synchronous event? Are there any callbacks for bluetooth connected or disconnected? If not then we need to fix it.

This method is synchronous yes, and if the connection cannot be established, it will throw an IO Exception. So we don't have to fix that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second problem is that we shouldn't establish bluetooth socket connection within DownloadJob. DownloadJob's purpose is to start download task and it shouldn't be doing anything else. This would testing the code in the later part difficult.

An issue is, we cannot pass an object as a parameter into our jobs, I tried to establish the socket connection in the activity but it seems the job is not relay on that. Besides, the socket and stream for hotspot are created in the job as well, so I think it doesn't matter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shobhitagarwal1612 Can you suggest a forward, I tried the workarounds but I'm not able to think one because DownloadJob needs socket objects and we cannot pass the socket objects directly in the DownloadJob and UploadJob.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. I'll give it a try and post it here.

@huangyz0918
Copy link
Contributor Author

I notice an issue when we are trying to send form instances, using two Google Nexus 6P (android 8.0.0 and android 8.0.1): The progress will stuck when we send form instance(s), I cannot use hotspot method to verify where this issue comes from (since wifi cannot be established in android 8.0.1, the hotspot cannot be turned on and the QR Code didn't show up either), so can you help me with that issue? @lakshyagupta21 @shobhitagarwal1612 I don't know if you have notice the android 8+ issue with hotspot, and I want to validate if the issue comes from here and fix that.

@lakshyagupta21
Copy link
Contributor

@huangyz0918 I'm still not sure about this PR whether it will break anything or not, and I need some more time to test it and I know that some of your tasks are dependent on this PR and @shobhitagarwal1612 is also thinking about solving the above issue. So we can merge this PR in some intermediate branch and then you can work considering that as your development branch. This way you'll get unblocked.
What do you say @shobhitagarwal1612 and @huangyz0918 ?

@huangyz0918
Copy link
Contributor Author

So we can merge this PR in some intermediate branch and then you can work considering that as your development branch.

Good idea to merge that into a dev branch. @lakshyagupta21 and I think you can have tests in the dev until you think it's ready for merging into master. How about @shobhitagarwal1612 , what do you think?

@shobhitagarwal1612
Copy link
Contributor

I think merging into a dev branch is not a good idea as this PR touches a lot of code that would create an extra overhead of keeping up that branch with the latest master branch. So, all of the subsequent PRs will still have lots of conflicts. So lets make sure that this PR does not disrupt the existing wifi-hotspot transfer functionality and merge this as it is. Then we can test this everyday and open issues as we find them.


boolean receiverFound = false;
for (ShadowApplication.Wrapper wrapper : registeredReceivers) {
if (!receiverFound) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its always going to be false first so whenever it gets inside the for each loop then the receiver will be assigned, then there is no use of iterating over registered receivers.
Anyways, the code requires clean up that we can do later.

@lakshyagupta21
Copy link
Contributor

@huangyz0918 @shobhitagarwal1612 Merging this PR, I did some testing on Bluetooth form transfer from Android 7 to Android 9, but I was not able to transfer the other way. And there were some issues related to UI/UX as well I think it is not in parity with the Hotspot functionality, like

  1. before pressing back while form transfer it should show some dialog to get the confirmation for stopping the connection.
  2. BlueTooth is still 'ON' even if the connection is complete/canceled.
  3. I'm not able to connect to new bluetooth directly via an app and it doesn't show me any new device.
    These are the basic issues which I think user will face, there can be more.

@lakshyagupta21 lakshyagupta21 merged commit be7cab3 into getodk:master Jul 17, 2019
@huangyz0918
Copy link
Contributor Author

I'm not able to connect to new bluetooth directly via an app and it doesn't show me any new device.

I think you should wait longer since the bluetooth classic takes a very long time to discovery some other devices. I'm not sure how to improve that, any suggestions? @lakshyagupta21

@huangyz0918
Copy link
Contributor Author

huangyz0918 commented Jul 18, 2019

BlueTooth is still 'ON' even if the connection is complete/canceled.

I can fix this, and I'll open an issue for that.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature] Add the bluetooth development framework
3 participants