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  *   &lt;application>
73  *       &lt;uses-library android:name="com.android.media.remotedisplay" />
74  *
75  *       &lt;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  *           &lt;intent-filter>
80  *               &lt;action android:name="com.android.media.remotedisplay.RemoteDisplayProvider" />
81  *           &lt;/intent-filter>
82  *       &lt;/service>
83  *   &lt;/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