blob: a5fe9f2ceb397d8ba1eff28336f2397a8607a916 [file] [log] [blame]
Jeff Brown69b07162013-11-07 00:30:16 -08001/*
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.server.media;
18
Jeff Brown69b07162013-11-07 00:30:16 -080019import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.media.IRemoteDisplayCallback;
24import android.media.IRemoteDisplayProvider;
25import android.media.RemoteDisplayState;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.RemoteException;
29import android.os.IBinder.DeathRecipient;
30import android.os.UserHandle;
31import android.util.Log;
32import android.util.Slog;
33
34import java.io.PrintWriter;
35import java.lang.ref.WeakReference;
Kenny Roote6585b32013-12-13 12:00:26 -080036import java.util.Objects;
Jeff Brown69b07162013-11-07 00:30:16 -080037
38/**
39 * Maintains a connection to a particular remote display provider service.
40 */
41final 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
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
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
84 public void setCallback(Callback callback) {
85 mDisplayStateCallback = callback;
86 }
87
88 public RemoteDisplayState getDisplayState() {
89 return mDisplayState;
90 }
91
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
102 public void setSelectedDisplay(String id) {
Kenny Roote6585b32013-12-13 12:00:26 -0800103 if (!Objects.equals(mSelectedDisplayId, id)) {
Jeff Brown69b07162013-11-07 00:30:16 -0800104 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
115 public void setDisplayVolume(int volume) {
116 if (mConnectionReady && mSelectedDisplayId != null) {
117 mActiveConnection.setVolume(mSelectedDisplayId, volume);
118 }
119 }
120
121 public void adjustDisplayVolume(int delta) {
122 if (mConnectionReady && mSelectedDisplayId != null) {
123 mActiveConnection.adjustVolume(mSelectedDisplayId, delta);
124 }
125 }
126
127 public boolean hasComponentName(String packageName, String className) {
128 return mComponentName.getPackageName().equals(packageName)
129 && mComponentName.getClassName().equals(className);
130 }
131
132 public String getFlattenedComponentName() {
133 return mComponentName.flattenToShortString();
134 }
135
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
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
158 public void rebindIfDisconnected() {
159 if (mActiveConnection == null && shouldBind()) {
160 unbind();
161 bind();
162 }
163 }
164
165 private void updateBinding() {
166 if (shouldBind()) {
167 bind();
168 } else {
169 unbind();
170 }
171 }
172
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
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, Context.BIND_AUTO_CREATE,
194 new UserHandle(mUserId));
195 if (!mBound && DEBUG) {
196 Slog.d(TAG, this + ": Bind failed");
197 }
198 } catch (SecurityException ex) {
199 if (DEBUG) {
200 Slog.d(TAG, this + ": Bind failed", ex);
201 }
202 }
203 }
204 }
205
206 private void unbind() {
207 if (mBound) {
208 if (DEBUG) {
209 Slog.d(TAG, this + ": Unbinding");
210 }
211
212 mBound = false;
213 disconnect();
214 mContext.unbindService(this);
215 }
216 }
217
218 @Override
219 public void onServiceConnected(ComponentName name, IBinder service) {
220 if (DEBUG) {
221 Slog.d(TAG, this + ": Connected");
222 }
223
224 if (mBound) {
225 disconnect();
226
227 IRemoteDisplayProvider provider = IRemoteDisplayProvider.Stub.asInterface(service);
228 if (provider != null) {
229 Connection connection = new Connection(provider);
230 if (connection.register()) {
231 mActiveConnection = connection;
232 } else {
233 if (DEBUG) {
234 Slog.d(TAG, this + ": Registration failed");
235 }
236 }
237 } else {
238 Slog.e(TAG, this + ": Service returned invalid remote display provider binder");
239 }
240 }
241 }
242
243 @Override
244 public void onServiceDisconnected(ComponentName name) {
245 if (DEBUG) {
246 Slog.d(TAG, this + ": Service disconnected");
247 }
248 disconnect();
249 }
250
251 private void onConnectionReady(Connection connection) {
252 if (mActiveConnection == connection) {
253 mConnectionReady = true;
254
255 if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE) {
256 mActiveConnection.setDiscoveryMode(mDiscoveryMode);
257 }
258 if (mSelectedDisplayId != null) {
259 mActiveConnection.connect(mSelectedDisplayId);
260 }
261 }
262 }
263
264 private void onConnectionDied(Connection connection) {
265 if (mActiveConnection == connection) {
266 if (DEBUG) {
267 Slog.d(TAG, this + ": Service connection died");
268 }
269 disconnect();
270 }
271 }
272
273 private void onDisplayStateChanged(Connection connection, RemoteDisplayState state) {
274 if (mActiveConnection == connection) {
275 if (DEBUG) {
276 Slog.d(TAG, this + ": State changed, state=" + state);
277 }
278 setDisplayState(state);
279 }
280 }
281
282 private void disconnect() {
283 if (mActiveConnection != null) {
284 if (mSelectedDisplayId != null) {
285 mActiveConnection.disconnect(mSelectedDisplayId);
286 }
287 mConnectionReady = false;
288 mActiveConnection.dispose();
289 mActiveConnection = null;
290 setDisplayState(null);
291 }
292 }
293
294 private void setDisplayState(RemoteDisplayState state) {
Kenny Roote6585b32013-12-13 12:00:26 -0800295 if (!Objects.equals(mDisplayState, state)) {
Jeff Brown69b07162013-11-07 00:30:16 -0800296 mDisplayState = state;
297 if (!mScheduledDisplayStateChangedCallback) {
298 mScheduledDisplayStateChangedCallback = true;
299 mHandler.post(mDisplayStateChanged);
300 }
301 }
302 }
303
304 @Override
305 public String toString() {
306 return "Service connection " + mComponentName.flattenToShortString();
307 }
308
309 private final Runnable mDisplayStateChanged = new Runnable() {
310 @Override
311 public void run() {
312 mScheduledDisplayStateChangedCallback = false;
313 if (mDisplayStateCallback != null) {
314 mDisplayStateCallback.onDisplayStateChanged(
315 RemoteDisplayProviderProxy.this, mDisplayState);
316 }
317 }
318 };
319
320 public interface Callback {
321 void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state);
322 }
323
324 private final class Connection implements DeathRecipient {
325 private final IRemoteDisplayProvider mProvider;
326 private final ProviderCallback mCallback;
327
328 public Connection(IRemoteDisplayProvider provider) {
329 mProvider = provider;
330 mCallback = new ProviderCallback(this);
331 }
332
333 public boolean register() {
334 try {
335 mProvider.asBinder().linkToDeath(this, 0);
336 mProvider.setCallback(mCallback);
337 mHandler.post(new Runnable() {
338 @Override
339 public void run() {
340 onConnectionReady(Connection.this);
341 }
342 });
343 return true;
344 } catch (RemoteException ex) {
345 binderDied();
346 }
347 return false;
348 }
349
350 public void dispose() {
351 mProvider.asBinder().unlinkToDeath(this, 0);
352 mCallback.dispose();
353 }
354
355 public void setDiscoveryMode(int mode) {
356 try {
357 mProvider.setDiscoveryMode(mode);
358 } catch (RemoteException ex) {
359 Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex);
360 }
361 }
362
363 public void connect(String id) {
364 try {
365 mProvider.connect(id);
366 } catch (RemoteException ex) {
367 Slog.e(TAG, "Failed to deliver request to connect to display.", ex);
368 }
369 }
370
371 public void disconnect(String id) {
372 try {
373 mProvider.disconnect(id);
374 } catch (RemoteException ex) {
375 Slog.e(TAG, "Failed to deliver request to disconnect from display.", ex);
376 }
377 }
378
379 public void setVolume(String id, int volume) {
380 try {
381 mProvider.setVolume(id, volume);
382 } catch (RemoteException ex) {
383 Slog.e(TAG, "Failed to deliver request to set display volume.", ex);
384 }
385 }
386
387 public void adjustVolume(String id, int volume) {
388 try {
389 mProvider.adjustVolume(id, volume);
390 } catch (RemoteException ex) {
391 Slog.e(TAG, "Failed to deliver request to adjust display volume.", ex);
392 }
393 }
394
395 @Override
396 public void binderDied() {
397 mHandler.post(new Runnable() {
398 @Override
399 public void run() {
400 onConnectionDied(Connection.this);
401 }
402 });
403 }
404
405 void postStateChanged(final RemoteDisplayState state) {
406 mHandler.post(new Runnable() {
407 @Override
408 public void run() {
409 onDisplayStateChanged(Connection.this, state);
410 }
411 });
412 }
413 }
414
415 /**
416 * Receives callbacks from the service.
417 * <p>
418 * This inner class is static and only retains a weak reference to the connection
419 * to prevent the client from being leaked in case the service is holding an
420 * active reference to the client's callback.
421 * </p>
422 */
423 private static final class ProviderCallback extends IRemoteDisplayCallback.Stub {
424 private final WeakReference<Connection> mConnectionRef;
425
426 public ProviderCallback(Connection connection) {
427 mConnectionRef = new WeakReference<Connection>(connection);
428 }
429
430 public void dispose() {
431 mConnectionRef.clear();
432 }
433
434 @Override
435 public void onStateChanged(RemoteDisplayState state) throws RemoteException {
436 Connection connection = mConnectionRef.get();
437 if (connection != null) {
438 connection.postStateChanged(state);
439 }
440 }
441 }
442}