• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.PendingIntent;
20 import android.app.Service;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.media.IRemoteDisplayCallback;
24 import android.media.IRemoteDisplayProvider;
25 import android.media.RemoteDisplayState;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.os.Looper;
29 import android.os.Message;
30 import android.os.RemoteException;
31 import android.provider.Settings;
32 import android.util.ArrayMap;
33 
34 import 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  *   &lt;application>
72  *       &lt;uses-library android:name="com.android.media.remotedisplay" />
73  *
74  *       &lt;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  *           &lt;intent-filter>
79  *               &lt;action android:name="com.android.media.remotedisplay.RemoteDisplayProvider" />
80  *           &lt;/intent-filter>
81  *       &lt;/service>
82  *   &lt;/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  */
92 public 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      */
RemoteDisplayProvider(Context context)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      */
getContext()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      */
getBinder()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      */
onDiscoveryModeChanged(int mode)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      */
onConnect(RemoteDisplay display)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      */
onDisconnect(RemoteDisplay display)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      */
onSetVolume(RemoteDisplay display, int volume)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      */
onAdjustVolume(RemoteDisplay display, int delta)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      */
getDiscoveryMode()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      */
getDisplays()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      */
addDisplay(RemoteDisplay display)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      */
updateDisplay(RemoteDisplay display)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      */
removeDisplay(RemoteDisplay display)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      */
findRemoteDisplay(String id)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      */
getSettingsPendingIntent()288     public PendingIntent getSettingsPendingIntent() {
289         if (mSettingsPendingIntent == null) {
290             Intent settingsIntent = new Intent(Settings.ACTION_CAST_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 
setCallback(IRemoteDisplayCallback callback)300     void setCallback(IRemoteDisplayCallback callback) {
301         mCallback = callback;
302         publishState();
303     }
304 
setDiscoveryMode(int mode)305     void setDiscoveryMode(int mode) {
306         if (mDiscoveryMode != mode) {
307             mDiscoveryMode = mode;
308             onDiscoveryModeChanged(mode);
309         }
310     }
311 
publishState()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
setCallback(IRemoteDisplayCallback callback)330         public void setCallback(IRemoteDisplayCallback callback) {
331             mHandler.obtainMessage(MSG_SET_CALLBACK, callback).sendToTarget();
332         }
333 
334         @Override
setDiscoveryMode(int mode)335         public void setDiscoveryMode(int mode) {
336             mHandler.obtainMessage(MSG_SET_DISCOVERY_MODE, mode, 0).sendToTarget();
337         }
338 
339         @Override
connect(String id)340         public void connect(String id) {
341             mHandler.obtainMessage(MSG_CONNECT, id).sendToTarget();
342         }
343 
344         @Override
disconnect(String id)345         public void disconnect(String id) {
346             mHandler.obtainMessage(MSG_DISCONNECT, id).sendToTarget();
347         }
348 
349         @Override
setVolume(String id, int volume)350         public void setVolume(String id, int volume) {
351             mHandler.obtainMessage(MSG_SET_VOLUME, volume, 0, id).sendToTarget();
352         }
353 
354         @Override
adjustVolume(String id, int delta)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 {
ProviderHandler(Looper looper)361         public ProviderHandler(Looper looper) {
362             super(looper, null, true);
363         }
364 
365         @Override
handleMessage(Message msg)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