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 android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
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.RemoteException;
29 import android.os.IBinder.DeathRecipient;
30 import android.os.UserHandle;
31 import android.util.Log;
32 import android.util.Slog;
33 
34 import java.io.PrintWriter;
35 import java.lang.ref.WeakReference;
36 import java.util.Objects;
37 
38 /**
39  * Maintains a connection to a particular remote display provider service.
40  */
41 final class RemoteDisplayProviderProxy implements ServiceConnection {
42     private static final String TAG = "RemoteDisplayProvider";  // max. 23 chars
43     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
44 
45     private final Context mContext;
46     private final ComponentName mComponentName;
47     private final int mUserId;
48     private final Handler mHandler;
49 
50     private Callback mDisplayStateCallback;
51 
52     // Connection state
53     private boolean mRunning;
54     private boolean mBound;
55     private Connection mActiveConnection;
56     private boolean mConnectionReady;
57 
58     // Logical state
59     private int mDiscoveryMode;
60     private String mSelectedDisplayId;
61     private RemoteDisplayState mDisplayState;
62     private boolean mScheduledDisplayStateChangedCallback;
63 
RemoteDisplayProviderProxy(Context context, ComponentName componentName, int userId)64     public RemoteDisplayProviderProxy(Context context, ComponentName componentName,
65             int userId) {
66         mContext = context;
67         mComponentName = componentName;
68         mUserId = userId;
69         mHandler = new Handler();
70     }
71 
dump(PrintWriter pw, String prefix)72     public void dump(PrintWriter pw, String prefix) {
73         pw.println(prefix + "Proxy");
74         pw.println(prefix + "  mUserId=" + mUserId);
75         pw.println(prefix + "  mRunning=" + mRunning);
76         pw.println(prefix + "  mBound=" + mBound);
77         pw.println(prefix + "  mActiveConnection=" + mActiveConnection);
78         pw.println(prefix + "  mConnectionReady=" + mConnectionReady);
79         pw.println(prefix + "  mDiscoveryMode=" + mDiscoveryMode);
80         pw.println(prefix + "  mSelectedDisplayId=" + mSelectedDisplayId);
81         pw.println(prefix + "  mDisplayState=" + mDisplayState);
82     }
83 
setCallback(Callback callback)84     public void setCallback(Callback callback) {
85         mDisplayStateCallback = callback;
86     }
87 
getDisplayState()88     public RemoteDisplayState getDisplayState() {
89         return mDisplayState;
90     }
91 
setDiscoveryMode(int mode)92     public void setDiscoveryMode(int mode) {
93         if (mDiscoveryMode != mode) {
94             mDiscoveryMode = mode;
95             if (mConnectionReady) {
96                 mActiveConnection.setDiscoveryMode(mode);
97             }
98             updateBinding();
99         }
100     }
101 
setSelectedDisplay(String id)102     public void setSelectedDisplay(String id) {
103         if (!Objects.equals(mSelectedDisplayId, id)) {
104             if (mConnectionReady && mSelectedDisplayId != null) {
105                 mActiveConnection.disconnect(mSelectedDisplayId);
106             }
107             mSelectedDisplayId = id;
108             if (mConnectionReady && id != null) {
109                 mActiveConnection.connect(id);
110             }
111             updateBinding();
112         }
113     }
114 
setDisplayVolume(int volume)115     public void setDisplayVolume(int volume) {
116         if (mConnectionReady && mSelectedDisplayId != null) {
117             mActiveConnection.setVolume(mSelectedDisplayId, volume);
118         }
119     }
120 
adjustDisplayVolume(int delta)121     public void adjustDisplayVolume(int delta) {
122         if (mConnectionReady && mSelectedDisplayId != null) {
123             mActiveConnection.adjustVolume(mSelectedDisplayId, delta);
124         }
125     }
126 
hasComponentName(String packageName, String className)127     public boolean hasComponentName(String packageName, String className) {
128         return mComponentName.getPackageName().equals(packageName)
129                 && mComponentName.getClassName().equals(className);
130     }
131 
getFlattenedComponentName()132     public String getFlattenedComponentName() {
133         return mComponentName.flattenToShortString();
134     }
135 
start()136     public void start() {
137         if (!mRunning) {
138             if (DEBUG) {
139                 Slog.d(TAG, this + ": Starting");
140             }
141 
142             mRunning = true;
143             updateBinding();
144         }
145     }
146 
stop()147     public void stop() {
148         if (mRunning) {
149             if (DEBUG) {
150                 Slog.d(TAG, this + ": Stopping");
151             }
152 
153             mRunning = false;
154             updateBinding();
155         }
156     }
157 
rebindIfDisconnected()158     public void rebindIfDisconnected() {
159         if (mActiveConnection == null && shouldBind()) {
160             unbind();
161             bind();
162         }
163     }
164 
updateBinding()165     private void updateBinding() {
166         if (shouldBind()) {
167             bind();
168         } else {
169             unbind();
170         }
171     }
172 
shouldBind()173     private boolean shouldBind() {
174         if (mRunning) {
175             // Bind whenever there is a discovery request or selected display.
176             if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE
177                     || mSelectedDisplayId != null) {
178                 return true;
179             }
180         }
181         return false;
182     }
183 
bind()184     private void bind() {
185         if (!mBound) {
186             if (DEBUG) {
187                 Slog.d(TAG, this + ": Binding");
188             }
189 
190             Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
191             service.setComponent(mComponentName);
192             try {
193                 mBound = mContext.bindServiceAsUser(service, this,
194                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
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.equals(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