blob: 953571c1f8026002a26e5eae1e6b5165ac02f3b6 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.midi;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.midi.MidiDevice;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiManager;
import android.os.Build;
import android.os.Handler;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A Java class implementing midi::MidiManagerAndroid functionality.
*/
@JNINamespace("midi")
@TargetApi(Build.VERSION_CODES.M)
class MidiManagerAndroid {
/**
* Set true when this instance is successfully initialized.
*/
private boolean mIsInitialized;
/**
* The devices held by this manager.
*/
private final List<MidiDeviceAndroid> mDevices = new ArrayList<>();
/**
* The device information instances which are being initialized.
*/
private final Set<MidiDeviceInfo> mPendingDevices = new HashSet<>();
/**
* The underlying MidiManager.
*/
private final MidiManager mManager;
/**
* Callbacks will run on the message queue associated with this handler.
*/
private final Handler mHandler;
/**
* The associated midi::MidiDeviceAndroid instance.
*/
private final long mNativeManagerPointer;
/**
* True is this object is stopped.
* This is needed because MidiManagerAndroid functions are called from the IO thread but
* callbacks are called on the UI thread (because the IO thread doesn't have a Looper). We need
* to protect each native function call with a synchronized block that also checks this flag.
*/
private boolean mStopped;
/**
* Checks if Android MIDI is supported on the device.
*/
@CalledByNative
static boolean hasSystemFeatureMidi() {
return ContextUtils.getApplicationContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_MIDI);
}
/**
* A creation function called by C++.
* @param nativeManagerPointer The native pointer to a midi::MidiManagerAndroid object.
*/
@CalledByNative
static MidiManagerAndroid create(long nativeManagerPointer) {
return new MidiManagerAndroid(nativeManagerPointer);
}
/**
* @param nativeManagerPointer The native pointer to a midi::MidiManagerAndroid object.
*/
private MidiManagerAndroid(long nativeManagerPointer) {
assert !ThreadUtils.runningOnUiThread();
mManager = (MidiManager) ContextUtils.getApplicationContext().getSystemService(
Context.MIDI_SERVICE);
mHandler = new Handler(ThreadUtils.getUiThreadLooper());
mNativeManagerPointer = nativeManagerPointer;
}
/**
* Initializes this object.
* This function must be called right after creation.
*/
@CalledByNative
void initialize() {
if (mManager == null) {
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (MidiManagerAndroid.this) {
if (mStopped) {
return;
}
MidiManagerAndroidJni.get().onInitializationFailed(mNativeManagerPointer);
}
}
});
return;
}
mManager.registerDeviceCallback(new MidiManager.DeviceCallback() {
@Override
public void onDeviceAdded(MidiDeviceInfo device) {
MidiManagerAndroid.this.onDeviceAdded(device);
}
@Override
public void onDeviceRemoved(MidiDeviceInfo device) {
MidiManagerAndroid.this.onDeviceRemoved(device);
}
}, mHandler);
MidiDeviceInfo[] infos = mManager.getDevices();
for (final MidiDeviceInfo info : infos) {
mPendingDevices.add(info);
openDevice(info);
}
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (MidiManagerAndroid.this) {
if (mStopped) {
return;
}
if (mPendingDevices.isEmpty() && !mIsInitialized) {
MidiManagerAndroidJni.get().onInitialized(
mNativeManagerPointer, mDevices.toArray(new MidiDeviceAndroid[0]));
mIsInitialized = true;
}
}
}
});
}
/**
* Marks this object as stopped.
*/
@CalledByNative
synchronized void stop() {
mStopped = true;
}
private void openDevice(final MidiDeviceInfo info) {
mManager.openDevice(info, new MidiManager.OnDeviceOpenedListener() {
@Override
public void onDeviceOpened(MidiDevice device) {
MidiManagerAndroid.this.onDeviceOpened(device, info);
}
}, mHandler);
}
/**
* Called when a midi device is attached.
* @param info the attached device information.
*/
private void onDeviceAdded(final MidiDeviceInfo info) {
if (!mIsInitialized) {
mPendingDevices.add(info);
}
openDevice(info);
}
/**
* Called when a midi device is detached.
* @param info the detached device information.
*/
private synchronized void onDeviceRemoved(MidiDeviceInfo info) {
if (mStopped) {
return;
}
for (MidiDeviceAndroid device : mDevices) {
if (device.isOpen() && device.getInfo().getId() == info.getId()) {
device.close();
MidiManagerAndroidJni.get().onDetached(mNativeManagerPointer, device);
}
}
}
private synchronized void onDeviceOpened(MidiDevice device, MidiDeviceInfo info) {
if (mStopped) {
return;
}
mPendingDevices.remove(info);
if (device != null) {
MidiDeviceAndroid xdevice = new MidiDeviceAndroid(device);
mDevices.add(xdevice);
if (mIsInitialized) {
MidiManagerAndroidJni.get().onAttached(mNativeManagerPointer, xdevice);
}
}
if (!mIsInitialized && mPendingDevices.isEmpty()) {
MidiManagerAndroidJni.get().onInitialized(
mNativeManagerPointer, mDevices.toArray(new MidiDeviceAndroid[0]));
mIsInitialized = true;
}
}
@NativeMethods
interface Natives {
void onInitialized(long nativeMidiManagerAndroid, MidiDeviceAndroid[] devices);
void onInitializationFailed(long nativeMidiManagerAndroid);
void onAttached(long nativeMidiManagerAndroid, MidiDeviceAndroid device);
void onDetached(long nativeMidiManagerAndroid, MidiDeviceAndroid device);
}
}