blob: 7017e444d717acb850701751996c48d1fdfce044 [file] [log] [blame]
/*
* Copyright (C) 2013 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 com.android.media.remotedisplay;
import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.IRemoteDisplayCallback;
import android.media.IRemoteDisplayProvider;
import android.media.RemoteDisplayState;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.ArrayMap;
import java.util.Collection;
/**
* Base class for remote display providers implemented as unbundled services.
* <p>
* To implement your remote display provider service, create a subclass of
* {@link Service} and override the {@link Service#onBind Service.onBind()} method
* to return the provider's binder when the {@link #SERVICE_INTERFACE} is requested.
* </p>
* <pre>
* public class SampleRemoteDisplayProviderService extends Service {
* private SampleProvider mProvider;
*
* public IBinder onBind(Intent intent) {
* if (intent.getAction().equals(RemoteDisplayProvider.SERVICE_INTERFACE)) {
* if (mProvider == null) {
* mProvider = new SampleProvider(this);
* }
* return mProvider.getBinder();
* }
* return null;
* }
*
* class SampleProvider extends RemoteDisplayProvider {
* public SampleProvider() {
* super(SampleRemoteDisplayProviderService.this);
* }
*
* // --- Implementation goes here ---
* }
* }
* </pre>
* <p>
* Declare your remote display provider service in your application manifest
* like this:
* </p>
* <pre>
* &lt;application>
* &lt;uses-library android:name="com.android.media.remotedisplay" />
*
* &lt;service android:name=".SampleRemoteDisplayProviderService"
* android:label="@string/sample_remote_display_provider_service"
* android:exported="true"
* android:permission="android.permission.BIND_REMOTE_DISPLAY">
* &lt;intent-filter>
* &lt;action android:name="com.android.media.remotedisplay.RemoteDisplayProvider" />
* &lt;/intent-filter>
* &lt;/service>
* &lt;/application>
* </pre>
* <p>
* This object is not thread safe. It is only intended to be accessed on the
* {@link Context#getMainLooper main looper thread} of an application.
* </p><p>
* IMPORTANT: This class is effectively a public API for unbundled applications, and
* must remain API stable. See README.txt in the root of this package for more information.
* </p>
*
* @hide
*/
@SystemApi
public abstract class RemoteDisplayProvider {
private static final int MSG_SET_CALLBACK = 1;
private static final int MSG_SET_DISCOVERY_MODE = 2;
private static final int MSG_CONNECT = 3;
private static final int MSG_DISCONNECT = 4;
private static final int MSG_SET_VOLUME = 5;
private static final int MSG_ADJUST_VOLUME = 6;
private final Context mContext;
private final ProviderStub mStub;
private final ProviderHandler mHandler;
private final ArrayMap<String, RemoteDisplay> mDisplays =
new ArrayMap<String, RemoteDisplay>();
private IRemoteDisplayCallback mCallback;
private int mDiscoveryMode = DISCOVERY_MODE_NONE;
private PendingIntent mSettingsPendingIntent;
/**
* The {@link Intent} that must be declared as handled by the service.
* Put this in your manifest.
*/
public static final String SERVICE_INTERFACE = RemoteDisplayState.SERVICE_INTERFACE;
/**
* Discovery mode: Do not perform any discovery.
*/
public static final int DISCOVERY_MODE_NONE = RemoteDisplayState.DISCOVERY_MODE_NONE;
/**
* Discovery mode: Passive or low-power periodic discovery.
* <p>
* This mode indicates that an application is interested in knowing whether there
* are any remote displays paired or available but doesn't need the latest or
* most detailed information. The provider may scan at a lower rate or rely on
* knowledge of previously paired devices.
* </p>
*/
public static final int DISCOVERY_MODE_PASSIVE = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
/**
* Discovery mode: Active discovery.
* <p>
* This mode indicates that the user is actively trying to connect to a route
* and we should perform continuous scans. This mode may use significantly more
* power but is intended to be short-lived.
* </p>
*/
public static final int DISCOVERY_MODE_ACTIVE = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
/**
* Creates a remote display provider.
*
* @param context The application context for the remote display provider.
*/
public RemoteDisplayProvider(Context context) {
mContext = context;
mStub = new ProviderStub();
mHandler = new ProviderHandler(context.getMainLooper());
}
/**
* Gets the context of the remote display provider.
*/
public final Context getContext() {
return mContext;
}
/**
* Gets the Binder associated with the provider.
* <p>
* This is intended to be used for the onBind() method of a service that implements
* a remote display provider service.
* </p>
*
* @return The IBinder instance associated with the provider.
*/
public IBinder getBinder() {
return mStub;
}
/**
* Called when the current discovery mode changes.
*
* @param mode The new discovery mode.
*/
public void onDiscoveryModeChanged(int mode) {
}
/**
* Called when the system would like to connect to a display.
*
* @param display The remote display.
*/
public void onConnect(RemoteDisplay display) {
}
/**
* Called when the system would like to disconnect from a display.
*
* @param display The remote display.
*/
public void onDisconnect(RemoteDisplay display) {
}
/**
* Called when the system would like to set the volume of a display.
*
* @param display The remote display.
* @param volume The desired volume.
*/
public void onSetVolume(RemoteDisplay display, int volume) {
}
/**
* Called when the system would like to adjust the volume of a display.
*
* @param display The remote display.
* @param delta An increment to add to the current volume, such as +1 or -1.
*/
public void onAdjustVolume(RemoteDisplay display, int delta) {
}
/**
* Gets the current discovery mode.
*
* @return The current discovery mode.
*/
public int getDiscoveryMode() {
return mDiscoveryMode;
}
/**
* Gets the current collection of displays.
*
* @return The current collection of displays, which must not be modified.
*/
public Collection<RemoteDisplay> getDisplays() {
return mDisplays.values();
}
/**
* Adds the specified remote display and notifies the system.
*
* @param display The remote display that was added.
* @throws IllegalStateException if there is already a display with the same id.
*/
public void addDisplay(RemoteDisplay display) {
if (display == null || mDisplays.containsKey(display.getId())) {
throw new IllegalArgumentException("display");
}
mDisplays.put(display.getId(), display);
publishState();
}
/**
* Updates information about the specified remote display and notifies the system.
*
* @param display The remote display that was added.
* @throws IllegalStateException if the display was n
*/
public void updateDisplay(RemoteDisplay display) {
if (display == null || mDisplays.get(display.getId()) != display) {
throw new IllegalArgumentException("display");
}
publishState();
}
/**
* Removes the specified remote display and tells the system about it.
*
* @param display The remote display that was removed.
*/
public void removeDisplay(RemoteDisplay display) {
if (display == null || mDisplays.get(display.getId()) != display) {
throw new IllegalArgumentException("display");
}
mDisplays.remove(display.getId());
publishState();
}
/**
* Finds the remote display with the specified id, returns null if not found.
*
* @param id Id of the remote display.
* @return The display, or null if none.
*/
public RemoteDisplay findRemoteDisplay(String id) {
return mDisplays.get(id);
}
/**
* Gets a pending intent to launch the remote display settings activity.
*
* @return A pending intent to launch the settings activity.
*/
public PendingIntent getSettingsPendingIntent() {
if (mSettingsPendingIntent == null) {
Intent settingsIntent = new Intent(Settings.ACTION_CAST_SETTINGS);
settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
mSettingsPendingIntent = PendingIntent.getActivity(
mContext, 0, settingsIntent, 0, null);
}
return mSettingsPendingIntent;
}
void setCallback(IRemoteDisplayCallback callback) {
mCallback = callback;
publishState();
}
void setDiscoveryMode(int mode) {
if (mDiscoveryMode != mode) {
mDiscoveryMode = mode;
onDiscoveryModeChanged(mode);
}
}
void publishState() {
if (mCallback != null) {
RemoteDisplayState state = new RemoteDisplayState();
final int count = mDisplays.size();
for (int i = 0; i < count; i++) {
final RemoteDisplay display = mDisplays.valueAt(i);
state.displays.add(display.getInfo());
}
try {
mCallback.onStateChanged(state);
} catch (RemoteException ex) {
// system server died?
}
}
}
final class ProviderStub extends IRemoteDisplayProvider.Stub {
@Override
public void setCallback(IRemoteDisplayCallback callback) {
mHandler.obtainMessage(MSG_SET_CALLBACK, callback).sendToTarget();
}
@Override
public void setDiscoveryMode(int mode) {
mHandler.obtainMessage(MSG_SET_DISCOVERY_MODE, mode, 0).sendToTarget();
}
@Override
public void connect(String id) {
mHandler.obtainMessage(MSG_CONNECT, id).sendToTarget();
}
@Override
public void disconnect(String id) {
mHandler.obtainMessage(MSG_DISCONNECT, id).sendToTarget();
}
@Override
public void setVolume(String id, int volume) {
mHandler.obtainMessage(MSG_SET_VOLUME, volume, 0, id).sendToTarget();
}
@Override
public void adjustVolume(String id, int delta) {
mHandler.obtainMessage(MSG_ADJUST_VOLUME, delta, 0, id).sendToTarget();
}
}
final class ProviderHandler extends Handler {
public ProviderHandler(Looper looper) {
super(looper, null, true);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_CALLBACK: {
setCallback((IRemoteDisplayCallback)msg.obj);
break;
}
case MSG_SET_DISCOVERY_MODE: {
setDiscoveryMode(msg.arg1);
break;
}
case MSG_CONNECT: {
RemoteDisplay display = findRemoteDisplay((String)msg.obj);
if (display != null) {
onConnect(display);
}
break;
}
case MSG_DISCONNECT: {
RemoteDisplay display = findRemoteDisplay((String)msg.obj);
if (display != null) {
onDisconnect(display);
}
break;
}
case MSG_SET_VOLUME: {
RemoteDisplay display = findRemoteDisplay((String)msg.obj);
if (display != null) {
onSetVolume(display, msg.arg1);
}
break;
}
case MSG_ADJUST_VOLUME: {
RemoteDisplay display = findRemoteDisplay((String)msg.obj);
if (display != null) {
onAdjustVolume(display, msg.arg1);
}
break;
}
}
}
}
}