1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.media.remotedisplay; 18 19 import android.annotation.SystemApi; 20 import android.app.PendingIntent; 21 import android.app.Service; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.media.IRemoteDisplayCallback; 25 import android.media.IRemoteDisplayProvider; 26 import android.media.RemoteDisplayState; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.Looper; 30 import android.os.Message; 31 import android.os.RemoteException; 32 import android.provider.Settings; 33 import android.util.ArrayMap; 34 35 import java.util.Collection; 36 37 /** 38 * Base class for remote display providers implemented as unbundled services. 39 * <p> 40 * To implement your remote display provider service, create a subclass of 41 * {@link Service} and override the {@link Service#onBind Service.onBind()} method 42 * to return the provider's binder when the {@link #SERVICE_INTERFACE} is requested. 43 * </p> 44 * <pre> 45 * public class SampleRemoteDisplayProviderService extends Service { 46 * private SampleProvider mProvider; 47 * 48 * public IBinder onBind(Intent intent) { 49 * if (intent.getAction().equals(RemoteDisplayProvider.SERVICE_INTERFACE)) { 50 * if (mProvider == null) { 51 * mProvider = new SampleProvider(this); 52 * } 53 * return mProvider.getBinder(); 54 * } 55 * return null; 56 * } 57 * 58 * class SampleProvider extends RemoteDisplayProvider { 59 * public SampleProvider() { 60 * super(SampleRemoteDisplayProviderService.this); 61 * } 62 * 63 * // --- Implementation goes here --- 64 * } 65 * } 66 * </pre> 67 * <p> 68 * Declare your remote display provider service in your application manifest 69 * like this: 70 * </p> 71 * <pre> 72 * <application> 73 * <uses-library android:name="com.android.media.remotedisplay" /> 74 * 75 * <service android:name=".SampleRemoteDisplayProviderService" 76 * android:label="@string/sample_remote_display_provider_service" 77 * android:exported="true" 78 * android:permission="android.permission.BIND_REMOTE_DISPLAY"> 79 * <intent-filter> 80 * <action android:name="com.android.media.remotedisplay.RemoteDisplayProvider" /> 81 * </intent-filter> 82 * </service> 83 * </application> 84 * </pre> 85 * <p> 86 * This object is not thread safe. It is only intended to be accessed on the 87 * {@link Context#getMainLooper main looper thread} of an application. 88 * </p><p> 89 * IMPORTANT: This class is effectively a public API for unbundled applications, and 90 * must remain API stable. See README.txt in the root of this package for more information. 91 * </p> 92 * 93 * @hide 94 */ 95 @SystemApi 96 public abstract class RemoteDisplayProvider { 97 private static final int MSG_SET_CALLBACK = 1; 98 private static final int MSG_SET_DISCOVERY_MODE = 2; 99 private static final int MSG_CONNECT = 3; 100 private static final int MSG_DISCONNECT = 4; 101 private static final int MSG_SET_VOLUME = 5; 102 private static final int MSG_ADJUST_VOLUME = 6; 103 104 private final Context mContext; 105 private final ProviderStub mStub; 106 private final ProviderHandler mHandler; 107 private final ArrayMap<String, RemoteDisplay> mDisplays = 108 new ArrayMap<String, RemoteDisplay>(); 109 private IRemoteDisplayCallback mCallback; 110 private int mDiscoveryMode = DISCOVERY_MODE_NONE; 111 112 private PendingIntent mSettingsPendingIntent; 113 114 /** 115 * The {@link Intent} that must be declared as handled by the service. 116 * Put this in your manifest. 117 */ 118 public static final String SERVICE_INTERFACE = RemoteDisplayState.SERVICE_INTERFACE; 119 120 /** 121 * Discovery mode: Do not perform any discovery. 122 */ 123 public static final int DISCOVERY_MODE_NONE = RemoteDisplayState.DISCOVERY_MODE_NONE; 124 125 /** 126 * Discovery mode: Passive or low-power periodic discovery. 127 * <p> 128 * This mode indicates that an application is interested in knowing whether there 129 * are any remote displays paired or available but doesn't need the latest or 130 * most detailed information. The provider may scan at a lower rate or rely on 131 * knowledge of previously paired devices. 132 * </p> 133 */ 134 public static final int DISCOVERY_MODE_PASSIVE = RemoteDisplayState.DISCOVERY_MODE_PASSIVE; 135 136 /** 137 * Discovery mode: Active discovery. 138 * <p> 139 * This mode indicates that the user is actively trying to connect to a route 140 * and we should perform continuous scans. This mode may use significantly more 141 * power but is intended to be short-lived. 142 * </p> 143 */ 144 public static final int DISCOVERY_MODE_ACTIVE = RemoteDisplayState.DISCOVERY_MODE_ACTIVE; 145 146 /** 147 * Creates a remote display provider. 148 * 149 * @param context The application context for the remote display provider. 150 */ RemoteDisplayProvider(Context context)151 public RemoteDisplayProvider(Context context) { 152 mContext = context; 153 mStub = new ProviderStub(); 154 mHandler = new ProviderHandler(context.getMainLooper()); 155 } 156 157 /** 158 * Gets the context of the remote display provider. 159 */ getContext()160 public final Context getContext() { 161 return mContext; 162 } 163 164 /** 165 * Gets the Binder associated with the provider. 166 * <p> 167 * This is intended to be used for the onBind() method of a service that implements 168 * a remote display provider service. 169 * </p> 170 * 171 * @return The IBinder instance associated with the provider. 172 */ getBinder()173 public IBinder getBinder() { 174 return mStub; 175 } 176 177 /** 178 * Called when the current discovery mode changes. 179 * 180 * @param mode The new discovery mode. 181 */ onDiscoveryModeChanged(int mode)182 public void onDiscoveryModeChanged(int mode) { 183 } 184 185 /** 186 * Called when the system would like to connect to a display. 187 * 188 * @param display The remote display. 189 */ onConnect(RemoteDisplay display)190 public void onConnect(RemoteDisplay display) { 191 } 192 193 /** 194 * Called when the system would like to disconnect from a display. 195 * 196 * @param display The remote display. 197 */ onDisconnect(RemoteDisplay display)198 public void onDisconnect(RemoteDisplay display) { 199 } 200 201 /** 202 * Called when the system would like to set the volume of a display. 203 * 204 * @param display The remote display. 205 * @param volume The desired volume. 206 */ onSetVolume(RemoteDisplay display, int volume)207 public void onSetVolume(RemoteDisplay display, int volume) { 208 } 209 210 /** 211 * Called when the system would like to adjust the volume of a display. 212 * 213 * @param display The remote display. 214 * @param delta An increment to add to the current volume, such as +1 or -1. 215 */ onAdjustVolume(RemoteDisplay display, int delta)216 public void onAdjustVolume(RemoteDisplay display, int delta) { 217 } 218 219 /** 220 * Gets the current discovery mode. 221 * 222 * @return The current discovery mode. 223 */ getDiscoveryMode()224 public int getDiscoveryMode() { 225 return mDiscoveryMode; 226 } 227 228 /** 229 * Gets the current collection of displays. 230 * 231 * @return The current collection of displays, which must not be modified. 232 */ getDisplays()233 public Collection<RemoteDisplay> getDisplays() { 234 return mDisplays.values(); 235 } 236 237 /** 238 * Adds the specified remote display and notifies the system. 239 * 240 * @param display The remote display that was added. 241 * @throws IllegalStateException if there is already a display with the same id. 242 */ addDisplay(RemoteDisplay display)243 public void addDisplay(RemoteDisplay display) { 244 if (display == null || mDisplays.containsKey(display.getId())) { 245 throw new IllegalArgumentException("display"); 246 } 247 mDisplays.put(display.getId(), display); 248 publishState(); 249 } 250 251 /** 252 * Updates information about the specified remote display and notifies the system. 253 * 254 * @param display The remote display that was added. 255 * @throws IllegalStateException if the display was n 256 */ updateDisplay(RemoteDisplay display)257 public void updateDisplay(RemoteDisplay display) { 258 if (display == null || mDisplays.get(display.getId()) != display) { 259 throw new IllegalArgumentException("display"); 260 } 261 publishState(); 262 } 263 264 /** 265 * Removes the specified remote display and tells the system about it. 266 * 267 * @param display The remote display that was removed. 268 */ removeDisplay(RemoteDisplay display)269 public void removeDisplay(RemoteDisplay display) { 270 if (display == null || mDisplays.get(display.getId()) != display) { 271 throw new IllegalArgumentException("display"); 272 } 273 mDisplays.remove(display.getId()); 274 publishState(); 275 } 276 277 /** 278 * Finds the remote display with the specified id, returns null if not found. 279 * 280 * @param id Id of the remote display. 281 * @return The display, or null if none. 282 */ findRemoteDisplay(String id)283 public RemoteDisplay findRemoteDisplay(String id) { 284 return mDisplays.get(id); 285 } 286 287 /** 288 * Gets a pending intent to launch the remote display settings activity. 289 * 290 * @return A pending intent to launch the settings activity. 291 */ getSettingsPendingIntent()292 public PendingIntent getSettingsPendingIntent() { 293 if (mSettingsPendingIntent == null) { 294 Intent settingsIntent = new Intent(Settings.ACTION_CAST_SETTINGS); 295 settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 296 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 297 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 298 mSettingsPendingIntent = PendingIntent.getActivity( 299 mContext, 0, settingsIntent, 0, null); 300 } 301 return mSettingsPendingIntent; 302 } 303 setCallback(IRemoteDisplayCallback callback)304 void setCallback(IRemoteDisplayCallback callback) { 305 mCallback = callback; 306 publishState(); 307 } 308 setDiscoveryMode(int mode)309 void setDiscoveryMode(int mode) { 310 if (mDiscoveryMode != mode) { 311 mDiscoveryMode = mode; 312 onDiscoveryModeChanged(mode); 313 } 314 } 315 publishState()316 void publishState() { 317 if (mCallback != null) { 318 RemoteDisplayState state = new RemoteDisplayState(); 319 final int count = mDisplays.size(); 320 for (int i = 0; i < count; i++) { 321 final RemoteDisplay display = mDisplays.valueAt(i); 322 state.displays.add(display.getInfo()); 323 } 324 try { 325 mCallback.onStateChanged(state); 326 } catch (RemoteException ex) { 327 // system server died? 328 } 329 } 330 } 331 332 final class ProviderStub extends IRemoteDisplayProvider.Stub { 333 @Override setCallback(IRemoteDisplayCallback callback)334 public void setCallback(IRemoteDisplayCallback callback) { 335 mHandler.obtainMessage(MSG_SET_CALLBACK, callback).sendToTarget(); 336 } 337 338 @Override setDiscoveryMode(int mode)339 public void setDiscoveryMode(int mode) { 340 mHandler.obtainMessage(MSG_SET_DISCOVERY_MODE, mode, 0).sendToTarget(); 341 } 342 343 @Override connect(String id)344 public void connect(String id) { 345 mHandler.obtainMessage(MSG_CONNECT, id).sendToTarget(); 346 } 347 348 @Override disconnect(String id)349 public void disconnect(String id) { 350 mHandler.obtainMessage(MSG_DISCONNECT, id).sendToTarget(); 351 } 352 353 @Override setVolume(String id, int volume)354 public void setVolume(String id, int volume) { 355 mHandler.obtainMessage(MSG_SET_VOLUME, volume, 0, id).sendToTarget(); 356 } 357 358 @Override adjustVolume(String id, int delta)359 public void adjustVolume(String id, int delta) { 360 mHandler.obtainMessage(MSG_ADJUST_VOLUME, delta, 0, id).sendToTarget(); 361 } 362 } 363 364 final class ProviderHandler extends Handler { ProviderHandler(Looper looper)365 public ProviderHandler(Looper looper) { 366 super(looper, null, true); 367 } 368 369 @Override handleMessage(Message msg)370 public void handleMessage(Message msg) { 371 switch (msg.what) { 372 case MSG_SET_CALLBACK: { 373 setCallback((IRemoteDisplayCallback)msg.obj); 374 break; 375 } 376 case MSG_SET_DISCOVERY_MODE: { 377 setDiscoveryMode(msg.arg1); 378 break; 379 } 380 case MSG_CONNECT: { 381 RemoteDisplay display = findRemoteDisplay((String)msg.obj); 382 if (display != null) { 383 onConnect(display); 384 } 385 break; 386 } 387 case MSG_DISCONNECT: { 388 RemoteDisplay display = findRemoteDisplay((String)msg.obj); 389 if (display != null) { 390 onDisconnect(display); 391 } 392 break; 393 } 394 case MSG_SET_VOLUME: { 395 RemoteDisplay display = findRemoteDisplay((String)msg.obj); 396 if (display != null) { 397 onSetVolume(display, msg.arg1); 398 } 399 break; 400 } 401 case MSG_ADJUST_VOLUME: { 402 RemoteDisplay display = findRemoteDisplay((String)msg.obj); 403 if (display != null) { 404 onAdjustVolume(display, msg.arg1); 405 } 406 break; 407 } 408 } 409 } 410 } 411 } 412