blob: 9693dc69fc33d80dd1c2e488574dc239657bd6b9 [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 and
* limitations under the License.
*/
package android.media.routeprovider;
import android.media.session.Route;
import android.media.session.Session;
import android.media.session.RouteInterface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ResultReceiver;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
/**
* Represents an interface that an application may use to send requests to a
* connected media route.
* <p>
* A {@link RouteProviderService} may expose multiple interfaces on a
* {@link RouteConnection} for a {@link Session} to interact with. A
* provider creates an interface with
* {@link RouteConnection#addRouteInterface(String)} to allow messages to be
* routed appropriately. Events are then sent through a specific interface and
* all commands being sent on the interface will be sent to any registered
* {@link CommandListener}s.
* <p>
* An interface instance can only be registered on one {@link RouteConnection}.
* To use the same interface on multiple connections a new instance must be
* created for each connection.
* <p>
* It is recommended you wrap this interface with a standard implementation to
* avoid errors, but for simple interfaces this class may be used directly. TODO
* add link to sample code.
*/
public final class RouteInterfaceHandler {
private static final String TAG = "RouteInterfaceHandler";
private final Object mLock = new Object();
private final RouteConnection mConnection;
private final String mName;
private ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
/**
* Create a new RouteInterface for a given connection. This can be used to
* send events on the given interface and register listeners for commands
* from the connected session.
*
* @param connection The connection this interface sends events on
* @param ifaceName The name of this interface
* @hide
*/
public RouteInterfaceHandler(RouteConnection connection, String ifaceName) {
if (connection == null) {
throw new IllegalArgumentException("connection may not be null");
}
if (TextUtils.isEmpty(ifaceName)) {
throw new IllegalArgumentException("ifaceName can not be empty");
}
mConnection = connection;
mName = ifaceName;
}
/**
* Send an event on this interface to the connected session.
*
* @param event The event to send
* @param extras Any extras for the event
*/
public void sendEvent(String event, Bundle extras) {
mConnection.sendEvent(mName, event, extras);
}
/**
* Send a result from a command to the specified callback. The result codes
* in {@link RouteInterface} must be used. More information
* about the result, whether successful or an error, should be included in
* the extras.
*
* @param cb The callback to send the result to
* @param resultCode The result code for the call
* @param extras Any extras to include
*/
public static void sendResult(ResultReceiver cb, int resultCode, Bundle extras) {
if (cb != null) {
cb.send(resultCode, extras);
}
}
/**
* Add a listener for this interface. If a handler is specified callbacks
* will be performed on the handler's thread, otherwise the callers thread
* will be used.
*
* @param listener The listener to receive calls on.
* @param handler The handler whose thread to post calls on or null.
*/
public void addListener(CommandListener listener, Handler handler) {
if (listener == null) {
throw new IllegalArgumentException("listener may not be null");
}
Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
synchronized (mLock) {
if (findIndexOfListenerLocked(listener) != -1) {
Log.d(TAG, "Listener is already added, ignoring");
return;
}
mListeners.add(new MessageHandler(looper, listener));
}
}
/**
* Remove a listener from this interface.
*
* @param listener The listener to stop receiving commands on.
*/
public void removeListener(CommandListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener may not be null");
}
synchronized (mLock) {
int index = findIndexOfListenerLocked(listener);
if (index != -1) {
mListeners.remove(index);
}
}
}
/**
* @hide
*/
public void onCommand(String command, Bundle args, ResultReceiver cb) {
synchronized (mLock) {
Command cmd = new Command(command, args, cb);
for (int i = mListeners.size() - 1; i >= 0; i--) {
mListeners.get(i).post(MessageHandler.MSG_COMMAND, cmd);
}
}
}
/**
* Get the interface name.
*
* @return The name of this interface
*/
public String getName() {
return mName;
}
private int findIndexOfListenerLocked(CommandListener listener) {
if (listener == null) {
throw new IllegalArgumentException("Callback cannot be null");
}
for (int i = mListeners.size() - 1; i >= 0; i--) {
MessageHandler handler = mListeners.get(i);
if (listener == handler.mListener) {
return i;
}
}
return -1;
}
/**
* Handles commands sent to the interface.
* <p>
* Register an InterfaceListener using {@link #addListener}.
*/
public abstract static class CommandListener {
/**
* This is called when a command is received that matches this
* interface. Commands are sent by a {@link Session} that is
* connected to the route this interface is registered with.
*
* @param iface The interface the command was received on.
* @param command The command or method to invoke.
* @param args Any args that were included with the command. May be
* null.
* @param cb The callback provided to send a response on. May be null.
* @return true if the command was handled, false otherwise. If the
* command was not handled an error will be sent automatically.
* true may be returned if the command will be handled
* asynchronously.
* @see Route
* @see Session
*/
public abstract boolean onCommand(RouteInterfaceHandler iface, String command, Bundle args,
ResultReceiver cb);
}
private class MessageHandler extends Handler {
private static final int MSG_COMMAND = 1;
private final CommandListener mListener;
public MessageHandler(Looper looper, CommandListener listener) {
super(looper, null, true /* async */);
mListener = listener;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_COMMAND:
Command cmd = (Command) msg.obj;
if (!mListener.onCommand(RouteInterfaceHandler.this, cmd.command, cmd.args, cmd.cb)) {
sendResult(cmd.cb, RouteInterface.RESULT_COMMAND_NOT_SUPPORTED,
null);
}
break;
}
}
public void post(int what, Object obj) {
obtainMessage(what, obj).sendToTarget();
}
}
private final static class Command {
public final String command;
public final Bundle args;
public final ResultReceiver cb;
public Command(String command, Bundle args, ResultReceiver cb) {
this.command = command;
this.args = args;
this.cb = cb;
}
}
}