• 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.server.media;
18 
19 import com.android.internal.util.Objects;
20 
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.media.IRemoteDisplayCallback;
26 import android.media.IRemoteDisplayProvider;
27 import android.media.RemoteDisplayState;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.RemoteException;
31 import android.os.IBinder.DeathRecipient;
32 import android.os.UserHandle;
33 import android.util.Log;
34 import android.util.Slog;
35 
36 import java.io.PrintWriter;
37 import java.lang.ref.WeakReference;
38 
39 /**
40  * Maintains a connection to a particular remote display provider service.
41  */
42 final class RemoteDisplayProviderProxy implements ServiceConnection {
43     private static final String TAG = "RemoteDisplayProvider";  // max. 23 chars
44     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
45 
46     private final Context mContext;
47     private final ComponentName mComponentName;
48     private final int mUserId;
49     private final Handler mHandler;
50 
51     private Callback mDisplayStateCallback;
52 
53     // Connection state
54     private boolean mRunning;
55     private boolean mBound;
56     private Connection mActiveConnection;
57     private boolean mConnectionReady;
58 
59     // Logical state
60     private int mDiscoveryMode;
61     private String mSelectedDisplayId;
62     private RemoteDisplayState mDisplayState;
63     private boolean mScheduledDisplayStateChangedCallback;
64 
RemoteDisplayProviderProxy(Context context, ComponentName componentName, int userId)65     public RemoteDisplayProviderProxy(Context context, ComponentName componentName,
66             int userId) {
67         mContext = context;
68         mComponentName = componentName;
69         mUserId = userId;
70         mHandler = new Handler();
71     }
72 
dump(PrintWriter pw, String prefix)73     public void dump(PrintWriter pw, String prefix) {
74         pw.println(prefix + "Proxy");
75         pw.println(prefix + "  mUserId=" + mUserId);
76         pw.println(prefix + "  mRunning=" + mRunning);
77         pw.println(prefix + "  mBound=" + mBound);
78         pw.println(prefix + "  mActiveConnection=" + mActiveConnection);
79         pw.println(prefix + "  mConnectionReady=" + mConnectionReady);
80         pw.println(prefix + "  mDiscoveryMode=" + mDiscoveryMode);
81         pw.println(prefix + "  mSelectedDisplayId=" + mSelectedDisplayId);
82         pw.println(prefix + "  mDisplayState=" + mDisplayState);
83     }
84 
setCallback(Callback callback)85     public void setCallback(Callback callback) {
86         mDisplayStateCallback = callback;
87     }
88 
getDisplayState()89     public RemoteDisplayState getDisplayState() {
90         return mDisplayState;
91     }
92 
setDiscoveryMode(int mode)93     public void setDiscoveryMode(int mode) {
94         if (mDiscoveryMode != mode) {
95             mDiscoveryMode = mode;
96             if (mConnectionReady) {
97                 mActiveConnection.setDiscoveryMode(mode);
98             }
99             updateBinding();
100         }
101     }
102 
setSelectedDisplay(String id)103     public void setSelectedDisplay(String id) {
104         if (!Objects.equal(mSelectedDisplayId, id)) {
105             if (mConnectionReady && mSelectedDisplayId != null) {
106                 mActiveConnection.disconnect(mSelectedDisplayId);
107             }
108             mSelectedDisplayId = id;
109             if (mConnectionReady && id != null) {
110                 mActiveConnection.connect(id);
111             }
112             updateBinding();
113         }
114     }
115 
setDisplayVolume(int volume)116     public void setDisplayVolume(int volume) {
117         if (mConnectionReady && mSelectedDisplayId != null) {
118             mActiveConnection.setVolume(mSelectedDisplayId, volume);
119         }
120     }
121 
adjustDisplayVolume(int delta)122     public void adjustDisplayVolume(int delta) {
123         if (mConnectionReady && mSelectedDisplayId != null) {
124             mActiveConnection.adjustVolume(mSelectedDisplayId, delta);
125         }
126     }
127 
hasComponentName(String packageName, String className)128     public boolean hasComponentName(String packageName, String className) {
129         return mComponentName.getPackageName().equals(packageName)
130                 && mComponentName.getClassName().equals(className);
131     }
132 
getFlattenedComponentName()133     public String getFlattenedComponentName() {
134         return mComponentName.flattenToShortString();
135     }
136 
start()137     public void start() {
138         if (!mRunning) {
139             if (DEBUG) {
140                 Slog.d(TAG, this + ": Starting");
141             }
142 
143             mRunning = true;
144             updateBinding();
145         }
146     }
147 
stop()148     public void stop() {
149         if (mRunning) {
150             if (DEBUG) {
151                 Slog.d(TAG, this + ": Stopping");
152             }
153 
154             mRunning = false;
155             updateBinding();
156         }
157     }
158 
rebindIfDisconnected()159     public void rebindIfDisconnected() {
160         if (mActiveConnection == null && shouldBind()) {
161             unbind();
162             bind();
163         }
164     }
165 
updateBinding()166     private void updateBinding() {
167         if (shouldBind()) {
168             bind();
169         } else {
170             unbind();
171         }
172     }
173 
shouldBind()174     private boolean shouldBind() {
175         if (mRunning) {
176             // Bind whenever there is a discovery request or selected display.
177             if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE
178                     || mSelectedDisplayId != null) {
179                 return true;
180             }
181         }
182         return false;
183     }
184 
bind()185     private void bind() {
186         if (!mBound) {
187             if (DEBUG) {
188                 Slog.d(TAG, this + ": Binding");
189             }
190 
191             Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
192             service.setComponent(mComponentName);
193             try {
194                 mBound = mContext.bindServiceAsUser(service, this, Context.BIND_AUTO_CREATE,
195                         new UserHandle(mUserId));
196                 if (!mBound && DEBUG) {
197                     Slog.d(TAG, this + ": Bind failed");
198                 }
199             } catch (SecurityException ex) {
200                 if (DEBUG) {
201                     Slog.d(TAG, this + ": Bind failed", ex);
202                 }
203             }
204         }
205     }
206 
unbind()207     private void unbind() {
208         if (mBound) {
209             if (DEBUG) {
210                 Slog.d(TAG, this + ": Unbinding");
211             }
212 
213             mBound = false;
214             disconnect();
215             mContext.unbindService(this);
216         }
217     }
218 
219     @Override
onServiceConnected(ComponentName name, IBinder service)220     public void onServiceConnected(ComponentName name, IBinder service) {
221         if (DEBUG) {
222             Slog.d(TAG, this + ": Connected");
223         }
224 
225         if (mBound) {
226             disconnect();
227 
228             IRemoteDisplayProvider provider = IRemoteDisplayProvider.Stub.asInterface(service);
229             if (provider != null) {
230                 Connection connection = new Connection(provider);
231                 if (connection.register()) {
232                     mActiveConnection = connection;
233                 } else {
234                     if (DEBUG) {
235                         Slog.d(TAG, this + ": Registration failed");
236                     }
237                 }
238             } else {
239                 Slog.e(TAG, this + ": Service returned invalid remote display provider binder");
240             }
241         }
242     }
243 
244     @Override
onServiceDisconnected(ComponentName name)245     public void onServiceDisconnected(ComponentName name) {
246         if (DEBUG) {
247             Slog.d(TAG, this + ": Service disconnected");
248         }
249         disconnect();
250     }
251 
onConnectionReady(Connection connection)252     private void onConnectionReady(Connection connection) {
253         if (mActiveConnection == connection) {
254             mConnectionReady = true;
255 
256             if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE) {
257                 mActiveConnection.setDiscoveryMode(mDiscoveryMode);
258             }
259             if (mSelectedDisplayId != null) {
260                 mActiveConnection.connect(mSelectedDisplayId);
261             }
262         }
263     }
264 
onConnectionDied(Connection connection)265     private void onConnectionDied(Connection connection) {
266         if (mActiveConnection == connection) {
267             if (DEBUG) {
268                 Slog.d(TAG, this + ": Service connection died");
269             }
270             disconnect();
271         }
272     }
273 
onDisplayStateChanged(Connection connection, RemoteDisplayState state)274     private void onDisplayStateChanged(Connection connection, RemoteDisplayState state) {
275         if (mActiveConnection == connection) {
276             if (DEBUG) {
277                 Slog.d(TAG, this + ": State changed, state=" + state);
278             }
279             setDisplayState(state);
280         }
281     }
282 
disconnect()283     private void disconnect() {
284         if (mActiveConnection != null) {
285             if (mSelectedDisplayId != null) {
286                 mActiveConnection.disconnect(mSelectedDisplayId);
287             }
288             mConnectionReady = false;
289             mActiveConnection.dispose();
290             mActiveConnection = null;
291             setDisplayState(null);
292         }
293     }
294 
setDisplayState(RemoteDisplayState state)295     private void setDisplayState(RemoteDisplayState state) {
296         if (!Objects.equal(mDisplayState, state)) {
297             mDisplayState = state;
298             if (!mScheduledDisplayStateChangedCallback) {
299                 mScheduledDisplayStateChangedCallback = true;
300                 mHandler.post(mDisplayStateChanged);
301             }
302         }
303     }
304 
305     @Override
toString()306     public String toString() {
307         return "Service connection " + mComponentName.flattenToShortString();
308     }
309 
310     private final Runnable mDisplayStateChanged = new Runnable() {
311         @Override
312         public void run() {
313             mScheduledDisplayStateChangedCallback = false;
314             if (mDisplayStateCallback != null) {
315                 mDisplayStateCallback.onDisplayStateChanged(
316                         RemoteDisplayProviderProxy.this, mDisplayState);
317             }
318         }
319     };
320 
321     public interface Callback {
onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state)322         void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state);
323     }
324 
325     private final class Connection implements DeathRecipient {
326         private final IRemoteDisplayProvider mProvider;
327         private final ProviderCallback mCallback;
328 
Connection(IRemoteDisplayProvider provider)329         public Connection(IRemoteDisplayProvider provider) {
330             mProvider = provider;
331             mCallback = new ProviderCallback(this);
332         }
333 
register()334         public boolean register() {
335             try {
336                 mProvider.asBinder().linkToDeath(this, 0);
337                 mProvider.setCallback(mCallback);
338                 mHandler.post(new Runnable() {
339                     @Override
340                     public void run() {
341                         onConnectionReady(Connection.this);
342                     }
343                 });
344                 return true;
345             } catch (RemoteException ex) {
346                 binderDied();
347             }
348             return false;
349         }
350 
dispose()351         public void dispose() {
352             mProvider.asBinder().unlinkToDeath(this, 0);
353             mCallback.dispose();
354         }
355 
setDiscoveryMode(int mode)356         public void setDiscoveryMode(int mode) {
357             try {
358                 mProvider.setDiscoveryMode(mode);
359             } catch (RemoteException ex) {
360                 Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex);
361             }
362         }
363 
connect(String id)364         public void connect(String id) {
365             try {
366                 mProvider.connect(id);
367             } catch (RemoteException ex) {
368                 Slog.e(TAG, "Failed to deliver request to connect to display.", ex);
369             }
370         }
371 
disconnect(String id)372         public void disconnect(String id) {
373             try {
374                 mProvider.disconnect(id);
375             } catch (RemoteException ex) {
376                 Slog.e(TAG, "Failed to deliver request to disconnect from display.", ex);
377             }
378         }
379 
setVolume(String id, int volume)380         public void setVolume(String id, int volume) {
381             try {
382                 mProvider.setVolume(id, volume);
383             } catch (RemoteException ex) {
384                 Slog.e(TAG, "Failed to deliver request to set display volume.", ex);
385             }
386         }
387 
adjustVolume(String id, int volume)388         public void adjustVolume(String id, int volume) {
389             try {
390                 mProvider.adjustVolume(id, volume);
391             } catch (RemoteException ex) {
392                 Slog.e(TAG, "Failed to deliver request to adjust display volume.", ex);
393             }
394         }
395 
396         @Override
binderDied()397         public void binderDied() {
398             mHandler.post(new Runnable() {
399                 @Override
400                 public void run() {
401                     onConnectionDied(Connection.this);
402                 }
403             });
404         }
405 
postStateChanged(final RemoteDisplayState state)406         void postStateChanged(final RemoteDisplayState state) {
407             mHandler.post(new Runnable() {
408                 @Override
409                 public void run() {
410                     onDisplayStateChanged(Connection.this, state);
411                 }
412             });
413         }
414     }
415 
416     /**
417      * Receives callbacks from the service.
418      * <p>
419      * This inner class is static and only retains a weak reference to the connection
420      * to prevent the client from being leaked in case the service is holding an
421      * active reference to the client's callback.
422      * </p>
423      */
424     private static final class ProviderCallback extends IRemoteDisplayCallback.Stub {
425         private final WeakReference<Connection> mConnectionRef;
426 
ProviderCallback(Connection connection)427         public ProviderCallback(Connection connection) {
428             mConnectionRef = new WeakReference<Connection>(connection);
429         }
430 
dispose()431         public void dispose() {
432             mConnectionRef.clear();
433         }
434 
435         @Override
onStateChanged(RemoteDisplayState state)436         public void onStateChanged(RemoteDisplayState state) throws RemoteException {
437             Connection connection = mConnectionRef.get();
438             if (connection != null) {
439                 connection.postStateChanged(state);
440             }
441         }
442     }
443 }
444