Skip to content

Commit 94f4817

Browse files
committed
Merge branch 'release/v0.0.16'
2 parents 5e79157 + 813a815 commit 94f4817

35 files changed

+367
-72
lines changed

.github/workflows/android.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ jobs:
1212
runs-on: ubuntu-latest
1313

1414
steps:
15-
- uses: actions/checkout@v3
16-
- name: set up JDK 17
17-
uses: actions/setup-java@v3
15+
- uses: actions/checkout@v4
16+
- name: set up JDK 21
17+
uses: actions/setup-java@v4
1818
with:
19-
java-version: '17'
19+
java-version: '21'
2020
distribution: 'temurin'
21-
cache: gradle
21+
cache: 'gradle'
2222

2323
- name: Grant execute permission for gradlew
2424
run: chmod +x gradlew
2525
- name: Build with Gradle
26-
run: ./gradlew build
26+
run: ./gradlew build

BLE-MIDI-library/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ publishing {
5858
release(MavenPublication) {
5959
group = 'jp.kshoji'
6060
artifactId = 'ble-midi'
61-
version = '0.0.15'
61+
version = '0.0.16'
6262

6363
afterEvaluate {
6464
from components.release

BLE-MIDI-library/src/main/java/jp/kshoji/blemidi/central/BleMidiCallback.java

+40-12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import android.bluetooth.BluetoothGattDescriptor;
1010
import android.bluetooth.BluetoothGattService;
1111
import android.bluetooth.BluetoothProfile;
12+
import android.bluetooth.BluetoothStatusCodes;
1213
import android.content.BroadcastReceiver;
1314
import android.content.Context;
1415
import android.content.Intent;
@@ -140,6 +141,9 @@ public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
140141
public void run() {
141142
// this calls onCharacteristicRead after completed
142143
gatt.readCharacteristic(manufacturerCharacteristic);
144+
if (gattRequestQueue.size() > 0) {
145+
gattRequestQueue.remove(0).run();
146+
}
143147
}
144148
});
145149
}
@@ -151,6 +155,9 @@ public void run() {
151155
public void run() {
152156
// this calls onCharacteristicRead after completed
153157
gatt.readCharacteristic(modelCharacteristic);
158+
if (gattRequestQueue.size() > 0) {
159+
gattRequestQueue.remove(0).run();
160+
}
154161
}
155162
});
156163
}
@@ -160,7 +167,9 @@ public void run() {
160167
gattRequestQueue.add(new Runnable() {
161168
@Override
162169
public void run() {
163-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
170+
// if the app is running on Meta/Oculus, don't set the mtu
171+
boolean isOculusDevices = "miramar".equals(Build.DEVICE) || "hollywood".equals(Build.DEVICE) || "eureka".equals(Build.DEVICE);
172+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE || isOculusDevices) {
164173
// Android 14: the default MTU size set to 517
165174
// https://developer.android.com/about/versions/14/behavior-changes-all#mtu-set-to-517
166175
final int mtu = 517;
@@ -179,6 +188,7 @@ public void run() {
179188
} else {
180189
// request maximum MTU size
181190
// this calls onMtuChanged after completed
191+
// NOTE: Some devices already have MTU set to 517, so the `onMtuChanged` method is not called.
182192
boolean result = gatt.requestMtu(517); // GATT_MAX_MTU_SIZE defined at `stack/include/gatt_api.h`
183193
Log.d(Constants.TAG, "Central requestMtu address: " + gatt.getDevice().getAddress() + ", succeed: " + result);
184194
}
@@ -328,13 +338,25 @@ public void run() {
328338
}
329339

330340
@Override
331-
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
332-
super.onCharacteristicChanged(gatt, characteristic);
341+
public void onCharacteristicChanged(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) {
342+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
343+
Set<MidiInputDevice> midiInputDevices = midiInputDevicesMap.get(gatt.getDevice().getAddress());
344+
if (midiInputDevices != null) {
345+
for (MidiInputDevice midiInputDevice : midiInputDevices) {
346+
((InternalMidiInputDevice) midiInputDevice).incomingData(value);
347+
}
348+
}
349+
}
350+
}
333351

334-
Set<MidiInputDevice> midiInputDevices = midiInputDevicesMap.get(gatt.getDevice().getAddress());
335-
if (midiInputDevices != null) {
336-
for (MidiInputDevice midiInputDevice : midiInputDevices) {
337-
((InternalMidiInputDevice)midiInputDevice).incomingData(characteristic.getValue());
352+
@Override
353+
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
354+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
355+
Set<MidiInputDevice> midiInputDevices = midiInputDevicesMap.get(gatt.getDevice().getAddress());
356+
if (midiInputDevices != null) {
357+
for (MidiInputDevice midiInputDevice : midiInputDevices) {
358+
((InternalMidiInputDevice) midiInputDevice).incomingData(characteristic.getValue());
359+
}
338360
}
339361
}
340362
}
@@ -705,8 +727,12 @@ public void configureAsCentralDevice() throws SecurityException {
705727
List<BluetoothGattDescriptor> descriptors = midiInputCharacteristic.getDescriptors();
706728
for (BluetoothGattDescriptor descriptor : descriptors) {
707729
if (BleUuidUtils.matches(BleUuidUtils.fromShortValue(0x2902), descriptor.getUuid())) {
708-
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
709-
bluetoothGatt.writeDescriptor(descriptor);
730+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
731+
bluetoothGatt.writeDescriptor(descriptor, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
732+
} else {
733+
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
734+
bluetoothGatt.writeDescriptor(descriptor);
735+
}
710736
}
711737
}
712738

@@ -806,17 +832,19 @@ public void configureAsCentralDevice() {
806832
}
807833

808834
@Override
809-
public void transferData(@NonNull byte[] writeBuffer) throws SecurityException {
835+
public boolean transferData(@NonNull byte[] writeBuffer) throws SecurityException {
810836
try {
811837
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
812-
bluetoothGatt.writeCharacteristic(midiOutputCharacteristic, writeBuffer, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
838+
int result = bluetoothGatt.writeCharacteristic(midiOutputCharacteristic, writeBuffer, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
839+
return result == BluetoothStatusCodes.SUCCESS;
813840
} else {
814841
midiOutputCharacteristic.setValue(writeBuffer);
815-
bluetoothGatt.writeCharacteristic(midiOutputCharacteristic);
842+
return bluetoothGatt.writeCharacteristic(midiOutputCharacteristic);
816843
}
817844
} catch (Throwable ignored) {
818845
// android.os.DeadObjectException will be thrown
819846
// ignore it
847+
return false;
820848
}
821849
}
822850

BLE-MIDI-library/src/main/java/jp/kshoji/blemidi/central/BleMidiCentralProvider.java

+58-29
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import java.util.List;
2929
import java.util.Set;
30+
import java.util.concurrent.Executor;
3031

3132
import jp.kshoji.blemidi.device.MidiInputDevice;
3233
import jp.kshoji.blemidi.device.MidiOutputDevice;
@@ -109,21 +110,24 @@ public BleMidiCentralProvider(@NonNull final Context context) throws Unsupported
109110
throw new UnsupportedOperationException("Bluetooth LE not supported on this device.");
110111
}
111112

112-
try {
113-
// Checks `android.software.companion_device_setup` feature specified at AndroidManifest.xml
114-
FeatureInfo[] reqFeatures = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_CONFIGURATIONS).reqFeatures;
115-
if (reqFeatures != null) {
116-
for (FeatureInfo feature : reqFeatures) {
117-
if (feature == null) {
118-
continue;
119-
}
120-
if (PackageManager.FEATURE_COMPANION_DEVICE_SETUP.equals(feature.name)) {
121-
useCompanionDeviceSetup = true;
122-
break;
113+
// if the context is not Activity, it can't use CompanionDeviceManager
114+
if (context instanceof Activity) {
115+
try {
116+
// Checks `android.software.companion_device_setup` feature specified at AndroidManifest.xml
117+
FeatureInfo[] reqFeatures = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_CONFIGURATIONS).reqFeatures;
118+
if (reqFeatures != null) {
119+
for (FeatureInfo feature : reqFeatures) {
120+
if (feature == null) {
121+
continue;
122+
}
123+
if (PackageManager.FEATURE_COMPANION_DEVICE_SETUP.equals(feature.name)) {
124+
useCompanionDeviceSetup = true;
125+
break;
126+
}
123127
}
124128
}
129+
} catch (PackageManager.NameNotFoundException ignored) {
125130
}
126-
} catch (PackageManager.NameNotFoundException ignored) {
127131
}
128132

129133
bluetoothAdapter = ((BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
@@ -219,24 +223,49 @@ public void startScanDevice(int timeoutInMilliSeconds) throws SecurityException
219223
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && useCompanionDeviceSetup) {
220224
final CompanionDeviceManager deviceManager = context.getSystemService(CompanionDeviceManager.class);
221225
final AssociationRequest associationRequest = BleMidiDeviceUtils.getBleMidiAssociationRequest(context);
222-
// TODO: use another associate API when SDK_INT >= VERSION_CODES.TIRAMISU
223-
try {
224-
deviceManager.associate(associationRequest,
225-
new CompanionDeviceManager.Callback() {
226-
@Override
227-
public void onDeviceFound(final IntentSender intentSender) {
228-
try {
229-
((Activity) context).startIntentSenderForResult(intentSender, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0);
230-
} catch (IntentSender.SendIntentException e) {
231-
Log.e(Constants.TAG, e.getMessage(), e);
232-
}
233-
}
226+
final CompanionDeviceManager.Callback associationCallback = new CompanionDeviceManager.Callback() {
227+
@Override
228+
public void onAssociationPending(@NonNull IntentSender intentSender) {
229+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
230+
try {
231+
((Activity) context).startIntentSenderForResult(intentSender, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0);
232+
} catch (IntentSender.SendIntentException e) {
233+
Log.e(Constants.TAG, e.getMessage(), e);
234+
}
235+
} else {
236+
// calls onDeviceFound
237+
super.onAssociationPending(intentSender);
238+
}
239+
}
234240

235-
@Override
236-
public void onFailure(final CharSequence error) {
237-
Log.e(Constants.TAG, "onFailure error: " + error);
238-
}
239-
}, null);
241+
@Override
242+
public void onDeviceFound(final IntentSender intentSender) {
243+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
244+
try {
245+
((Activity) context).startIntentSenderForResult(intentSender, SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0);
246+
} catch (IntentSender.SendIntentException e) {
247+
Log.e(Constants.TAG, e.getMessage(), e);
248+
}
249+
}
250+
}
251+
252+
@Override
253+
public void onFailure(final CharSequence error) {
254+
Log.e(Constants.TAG, "onFailure error: " + error);
255+
}
256+
};
257+
258+
try {
259+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
260+
deviceManager.associate(associationRequest, new Executor() {
261+
@Override
262+
public void execute(Runnable command) {
263+
command.run();
264+
}
265+
}, associationCallback);
266+
} else {
267+
deviceManager.associate(associationRequest, associationCallback, null);
268+
}
240269
} catch (IllegalStateException ignored) {
241270
Log.e(Constants.TAG, ignored.getMessage(), ignored);
242271
// Must declare uses-feature android.software.companion_device_setup in manifest to use this API

BLE-MIDI-library/src/main/java/jp/kshoji/blemidi/device/MidiOutputDevice.java

+18-6
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ public abstract class MidiOutputDevice {
2020
* Transfer data
2121
*
2222
* @param writeBuffer byte array to write
23+
* @return true if transfer succeed
2324
*/
24-
protected abstract void transferData(@NonNull byte[] writeBuffer);
25+
protected abstract boolean transferData(@NonNull byte[] writeBuffer);
2526

2627
/**
2728
* Obtains the device name
@@ -79,14 +80,16 @@ public void run() {
7980
while (transferDataThreadAlive && isRunning) {
8081
synchronized (transferDataStream) {
8182
if (writtenDataCount > 0) {
82-
transferData(transferDataStream.toByteArray());
83-
transferDataStream.reset();
84-
writtenDataCount = 0;
83+
if (transferData(transferDataStream.toByteArray())) {
84+
// reset the stream if transfer succeed
85+
transferDataStream.reset();
86+
writtenDataCount = 0;
87+
}
8588
}
8689
}
8790

8891
try {
89-
Thread.sleep(10);
92+
Thread.sleep(10); // BluetoothGatt.WRITE_CHARACTERISTIC_TIME_TO_WAIT
9093
} catch (InterruptedException ignored) {
9194
}
9295
}
@@ -238,7 +241,16 @@ public final void sendMidiSystemExclusive(@NonNull byte[] systemExclusive) {
238241
writeBuffer[0] = (byte) (0x80 | ((timestamp >> 7) & 0x3f));
239242

240243
// immediately transfer data
241-
transferData(writeBuffer);
244+
while (true) {
245+
if (transferData(writeBuffer)) {
246+
break;
247+
}
248+
249+
try {
250+
Thread.sleep(10); // BluetoothGatt.WRITE_CHARACTERISTIC_TIME_TO_WAIT
251+
} catch (InterruptedException ignored) {
252+
}
253+
}
242254

243255
timestamp = System.currentTimeMillis() % MAX_TIMESTAMP;
244256
}

BLE-MIDI-library/src/main/java/jp/kshoji/blemidi/peripheral/BleMidiPeripheralProvider.java

+10-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import android.bluetooth.BluetoothGattService;
1313
import android.bluetooth.BluetoothManager;
1414
import android.bluetooth.BluetoothProfile;
15+
import android.bluetooth.BluetoothStatusCodes;
1516
import android.bluetooth.le.AdvertiseCallback;
1617
import android.bluetooth.le.AdvertiseData;
1718
import android.bluetooth.le.AdvertiseSettings;
@@ -707,13 +708,18 @@ public String getModel() {
707708
}
708709

709710
@Override
710-
public void transferData(@NonNull byte[] writeBuffer) throws SecurityException {
711-
midiOutputCharacteristic.setValue(writeBuffer);
712-
711+
public boolean transferData(@NonNull byte[] writeBuffer) throws SecurityException {
713712
try {
714-
bluetoothGattServer.notifyCharacteristicChanged(bluetoothDevice, midiOutputCharacteristic, false);
713+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
714+
int result = bluetoothGattServer.notifyCharacteristicChanged(bluetoothDevice, midiOutputCharacteristic, false, writeBuffer);
715+
return result == BluetoothStatusCodes.SUCCESS;
716+
} else {
717+
midiOutputCharacteristic.setValue(writeBuffer);
718+
return bluetoothGattServer.notifyCharacteristicChanged(bluetoothDevice, midiOutputCharacteristic, false);
719+
}
715720
} catch (Throwable ignored) {
716721
// ignore it
722+
return false;
717723
}
718724
}
719725

BLE-MIDI-library/src/main/java/jp/kshoji/blemidi/util/BleUtils.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public static boolean isBluetoothEnabled(@NonNull final Context context) {
9191
/**
9292
* Request code for BLE MIDI device selection
9393
*/
94-
public static final int SELECT_DEVICE_REQUEST_CODE = 0x5e1ec7;
94+
public static final int SELECT_DEVICE_REQUEST_CODE = 0x5e1e;
9595

9696
/**
9797
* Enables bluetooth function.<br />

BLE-MIDI-library/src/main/java/jp/kshoji/unity/midi/BleMidiUnityPlayerActivity.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import android.app.Activity;
44
import android.bluetooth.le.ScanResult;
5+
import android.companion.AssociationInfo;
56
import android.companion.CompanionDeviceManager;
67
import android.content.Intent;
8+
import android.os.Build;
79
import android.util.Log;
810

911
import com.unity3d.player.UnityPlayerActivity;
@@ -22,7 +24,14 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
2224
super.onActivityResult(requestCode, resultCode, data);
2325

2426
if (requestCode == BleUtils.SELECT_DEVICE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
25-
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
27+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
28+
try {
29+
AssociationInfo associationInfo = data.getParcelableExtra(CompanionDeviceManager.EXTRA_ASSOCIATION, AssociationInfo.class);
30+
bleMidiCentralProvider.connectGatt(associationInfo.getAssociatedDevice().getBleDevice().getDevice());
31+
} catch (Throwable t) {
32+
Log.d(Constants.TAG, t.getMessage(), t);
33+
}
34+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
2635
try {
2736
ScanResult scanResult = data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE);
2837
bleMidiCentralProvider.connectGatt(scanResult.getDevice());

0 commit comments

Comments
 (0)