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