blob: 38f1cb80296c4ba9f20d78b86b77d13f76a40688 [file] [log] [blame]
/*
* Copyright (C) 2014 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 an
* limitations under the License.
*/
package com.android.server;
import android.content.Context;
import android.midi.IMidiDeviceServer;
import android.midi.IMidiListener;
import android.midi.IMidiManager;
import android.midi.MidiDeviceInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.util.IndentingPrintWriter;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
public class MidiService extends IMidiManager.Stub {
private static final String TAG = "MidiService";
private final Context mContext;
// list of all our clients, keyed by Binder token
private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
// list of all devices, keyed by MidiDeviceInfo
private final HashMap<MidiDeviceInfo, Device> mDevicesByInfo
= new HashMap<MidiDeviceInfo, Device>();
// list of all devices, keyed by IMidiDeviceServer
private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>();
// used for assigning IDs to MIDI devices
private int mNextDeviceId = 1;
private final class Client implements IBinder.DeathRecipient {
// Binder token for this client
private final IBinder mToken;
// This client's UID
private final int mUid;
// This client's PID
private final int mPid;
// List of all receivers for this client
private final ArrayList<IMidiListener> mListeners = new ArrayList<IMidiListener>();
public Client(IBinder token) {
mToken = token;
mUid = Binder.getCallingUid();
mPid = Binder.getCallingPid();
}
public int getUid() {
return mUid;
}
public void addListener(IMidiListener listener) {
mListeners.add(listener);
}
public void removeListener(IMidiListener listener) {
mListeners.remove(listener);
if (mListeners.size() == 0) {
removeClient(mToken);
}
}
public void deviceAdded(Device device) {
// ignore private devices that our client cannot access
if (!device.isUidAllowed(mUid)) return;
MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try {
for (IMidiListener listener : mListeners) {
listener.onDeviceAdded(deviceInfo);
}
} catch (RemoteException e) {
Log.e(TAG, "remote exception", e);
}
}
public void deviceRemoved(Device device) {
// ignore private devices that our client cannot access
if (!device.isUidAllowed(mUid)) return;
MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try {
for (IMidiListener listener : mListeners) {
listener.onDeviceRemoved(deviceInfo);
}
} catch (RemoteException e) {
Log.e(TAG, "remote exception", e);
}
}
public void binderDied() {
removeClient(mToken);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Client: UID: ");
sb.append(mUid);
sb.append(" PID: ");
sb.append(mPid);
sb.append(" listener count: ");
sb.append(mListeners.size());
return sb.toString();
}
}
private Client getClient(IBinder token) {
synchronized (mClients) {
Client client = mClients.get(token);
if (client == null) {
client = new Client(token);
try {
token.linkToDeath(client, 0);
} catch (RemoteException e) {
return null;
}
mClients.put(token, client);
}
return client;
}
}
private void removeClient(IBinder token) {
mClients.remove(token);
}
private final class Device implements IBinder.DeathRecipient {
private final IMidiDeviceServer mServer;
private final MidiDeviceInfo mDeviceInfo;
// UID of device creator
private final int mUid;
// PID of device creator
private final int mPid;
public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo) {
mServer = server;
mDeviceInfo = deviceInfo;
mUid = Binder.getCallingUid();
mPid = Binder.getCallingPid();
}
public MidiDeviceInfo getDeviceInfo() {
return mDeviceInfo;
}
public IMidiDeviceServer getDeviceServer() {
return mServer;
}
public boolean isUidAllowed(int uid) {
// FIXME
return true;
}
public void binderDied() {
synchronized (mDevicesByServer) {
removeDeviceLocked(this);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Device: ");
sb.append(mDeviceInfo);
sb.append(" UID: ");
sb.append(mUid);
sb.append(" PID: ");
sb.append(mPid);
return sb.toString();
}
}
public MidiService(Context context) {
mContext = context;
}
public void registerListener(IBinder token, IMidiListener listener) {
Client client = getClient(token);
if (client == null) return;
client.addListener(listener);
}
public void unregisterListener(IBinder token, IMidiListener listener) {
Client client = getClient(token);
if (client == null) return;
client.removeListener(listener);
}
public MidiDeviceInfo[] getDeviceList() {
return mDevicesByInfo.keySet().toArray(new MidiDeviceInfo[0]);
}
public IMidiDeviceServer openDevice(IBinder token, MidiDeviceInfo deviceInfo) {
Device device = mDevicesByInfo.get(deviceInfo);
if (device == null) {
Log.e(TAG, "device not found in openDevice: " + deviceInfo);
return null;
}
if (!device.isUidAllowed(Binder.getCallingUid())) {
throw new SecurityException("Attempt to open private device with wrong UID");
}
return device.getDeviceServer();
}
public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
int numOutputPorts, Bundle properties, boolean isPrivate, int type) {
if (type != MidiDeviceInfo.TYPE_VIRTUAL && Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("only system can create non-virtual devices");
}
MidiDeviceInfo deviceInfo;
Device device;
synchronized (mDevicesByServer) {
int id = mNextDeviceId++;
deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts, properties);
IBinder binder = server.asBinder();
device = new Device(server, deviceInfo);
try {
binder.linkToDeath(device, 0);
} catch (RemoteException e) {
return null;
}
mDevicesByInfo.put(deviceInfo, device);
mDevicesByServer.put(server.asBinder(), device);
}
synchronized (mClients) {
for (Client c : mClients.values()) {
c.deviceAdded(device);
}
}
return deviceInfo;
}
public void unregisterDeviceServer(IMidiDeviceServer server) {
synchronized (mDevicesByServer) {
removeDeviceLocked(mDevicesByServer.get(server.asBinder()));
}
}
// synchronize on mDevicesByServer
private void removeDeviceLocked(Device device) {
if (mDevicesByServer.remove(device.getDeviceServer().asBinder()) != null) {
mDevicesByInfo.remove(device.getDeviceInfo());
synchronized (mClients) {
for (Client c : mClients.values()) {
c.deviceRemoved(device);
}
}
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
pw.println("MIDI Manager State:");
pw.increaseIndent();
pw.println("Devices:");
pw.increaseIndent();
for (Device device : mDevicesByInfo.values()) {
pw.println(device.toString());
}
pw.decreaseIndent();
pw.println("Clients:");
pw.increaseIndent();
for (Client client : mClients.values()) {
pw.println(client.toString());
}
pw.decreaseIndent();
}
}