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 the argument is null, or if there is already a display with 242 * the same id. 243 */ addDisplay(RemoteDisplay display)244 public void addDisplay(RemoteDisplay display) { 245 if (display == null) { 246 throw new IllegalArgumentException("display cannot be null"); 247 } 248 String displayId = display.getId(); 249 if (mDisplays.containsKey(displayId)) { 250 throw new IllegalArgumentException("display already exists with id: " + displayId); 251 } 252 mDisplays.put(displayId, display); 253 publishState(); 254 } 255 256 /** 257 * Updates information about the specified remote display and notifies the system. 258 * 259 * @param display The remote display that was added. 260 * @throws IllegalStateException if the argument is null, or if the provider is not aware of the 261 * display. 262 */ updateDisplay(RemoteDisplay display)263 public void updateDisplay(RemoteDisplay display) { 264 if (display == null) { 265 throw new IllegalArgumentException("display cannot be null"); 266 } 267 String displayId = display.getId(); 268 if (mDisplays.get(displayId) != display) { 269 throw new IllegalArgumentException("unexpected display with id: " + displayId); 270 } 271 publishState(); 272 } 273 274 /** 275 * Removes the specified remote display and tells the system about it. 276 * 277 * @param display The remote display that was removed. 278 * @throws IllegalStateException if the argument is null, or if the provider is not aware of the 279 * display. 280 */ removeDisplay(RemoteDisplay display)281 public void removeDisplay(RemoteDisplay display) { 282 if (display == null) { 283 throw new IllegalArgumentException("display cannot be null"); 284 } 285 String displayId = display.getId(); 286 if (mDisplays.get(displayId) != display) { 287 throw new IllegalArgumentException("unexpected display with id: " + displayId); 288 } 289 mDisplays.remove(displayId); 290 publishState(); 291 } 292 293 /** 294 * Finds the remote display with the specified id, returns null if not found. 295 * 296 * @param id Id of the remote display. 297 * @return The display, or null if none. 298 */ findRemoteDisplay(String id)299 public RemoteDisplay findRemoteDisplay(String id) { 300 return mDisplays.get(id); 301 } 302 303 /** 304 * Gets a pending intent to launch the remote display settings activity. 305 * 306 * @return A pending intent to launch the settings activity. 307 */ getSettingsPendingIntent()308 public PendingIntent getSettingsPendingIntent() { 309 if (mSettingsPendingIntent == null) { 310 Intent settingsIntent = new Intent(Settings.ACTION_CAST_SETTINGS); 311 settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 312 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 313 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 314 mSettingsPendingIntent = PendingIntent.getActivity( 315 mContext, 0, settingsIntent, PendingIntent.FLAG_IMMUTABLE, null); 316 } 317 return mSettingsPendingIntent; 318 } 319 setCallback(IRemoteDisplayCallback callback)320 void setCallback(IRemoteDisplayCallback callback) { 321 mCallback = callback; 322 publishState(); 323 } 324 setDiscoveryMode(int mode)325 void setDiscoveryMode(int mode) { 326 if (mDiscoveryMode != mode) { 327 mDiscoveryMode = mode; 328 onDiscoveryModeChanged(mode); 329 } 330 } 331 publishState()332 void publishState() { 333 if (mCallback != null) { 334 RemoteDisplayState state = new RemoteDisplayState(); 335 final int count = mDisplays.size(); 336 for (int i = 0; i < count; i++) { 337 final RemoteDisplay display = mDisplays.valueAt(i); 338 state.displays.add(display.getInfo()); 339 } 340 try { 341 mCallback.onStateChanged(state); 342 } catch (RemoteException ex) { 343 // system server died? 344 } 345 } 346 } 347 348 final class ProviderStub extends IRemoteDisplayProvider.Stub { 349 @Override setCallback(IRemoteDisplayCallback callback)350 public void setCallback(IRemoteDisplayCallback callback) { 351 mHandler.obtainMessage(MSG_SET_CALLBACK, callback).sendToTarget(); 352 } 353 354 @Override setDiscoveryMode(int mode)355 public void setDiscoveryMode(int mode) { 356 mHandler.obtainMessage(MSG_SET_DISCOVERY_MODE, mode, 0).sendToTarget(); 357 } 358 359 @Override connect(String id)360 public void connect(String id) { 361 mHandler.obtainMessage(MSG_CONNECT, id).sendToTarget(); 362 } 363 364 @Override disconnect(String id)365 public void disconnect(String id) { 366 mHandler.obtainMessage(MSG_DISCONNECT, id).sendToTarget(); 367 } 368 369 @Override setVolume(String id, int volume)370 public void setVolume(String id, int volume) { 371 mHandler.obtainMessage(MSG_SET_VOLUME, volume, 0, id).sendToTarget(); 372 } 373 374 @Override adjustVolume(String id, int delta)375 public void adjustVolume(String id, int delta) { 376 mHandler.obtainMessage(MSG_ADJUST_VOLUME, delta, 0, id).sendToTarget(); 377 } 378 } 379 380 final class ProviderHandler extends Handler { ProviderHandler(Looper looper)381 public ProviderHandler(Looper looper) { 382 super(looper, null, true); 383 } 384 385 @Override handleMessage(Message msg)386 public void handleMessage(Message msg) { 387 switch (msg.what) { 388 case MSG_SET_CALLBACK: { 389 setCallback((IRemoteDisplayCallback)msg.obj); 390 break; 391 } 392 case MSG_SET_DISCOVERY_MODE: { 393 setDiscoveryMode(msg.arg1); 394 break; 395 } 396 case MSG_CONNECT: { 397 RemoteDisplay display = findRemoteDisplay((String)msg.obj); 398 if (display != null) { 399 onConnect(display); 400 } 401 break; 402 } 403 case MSG_DISCONNECT: { 404 RemoteDisplay display = findRemoteDisplay((String)msg.obj); 405 if (display != null) { 406 onDisconnect(display); 407 } 408 break; 409 } 410 case MSG_SET_VOLUME: { 411 RemoteDisplay display = findRemoteDisplay((String)msg.obj); 412 if (display != null) { 413 onSetVolume(display, msg.arg1); 414 } 415 break; 416 } 417 case MSG_ADJUST_VOLUME: { 418 RemoteDisplay display = findRemoteDisplay((String)msg.obj); 419 if (display != null) { 420 onAdjustVolume(display, msg.arg1); 421 } 422 break; 423 } 424 } 425 } 426 } 427 } 428