blob: b248ee0759d80e864c6c8080a889c16e817de999 [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
19import com.android.internal.util.Objects;
20
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.media.IRemoteDisplayCallback;
26import android.media.IRemoteDisplayProvider;
27import android.media.RemoteDisplayState;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.RemoteException;
31import android.os.IBinder.DeathRecipient;
32import android.os.UserHandle;
33import android.util.Log;
34import android.util.Slog;
35
36import java.io.PrintWriter;
37import java.lang.ref.WeakReference;
38
39/**
40 * Maintains a connection to a particular remote display provider service.
41 */
42final 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
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
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
85 public void setCallback(Callback callback) {
86 mDisplayStateCallback = callback;
87 }
88
89 public RemoteDisplayState getDisplayState() {
90 return mDisplayState;
91 }
92
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
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
116 public void setDisplayVolume(int volume) {
117 if (mConnectionReady && mSelectedDisplayId != null) {
118 mActiveConnection.setVolume(mSelectedDisplayId, volume);
119 }
120 }
121
122 public void adjustDisplayVolume(int delta) {
123 if (mConnectionReady && mSelectedDisplayId != null) {
124 mActiveConnection.adjustVolume(mSelectedDisplayId, delta);
125 }
126 }
127
128 public boolean hasComponentName(String packageName, String className) {
129 return mComponentName.getPackageName().equals(packageName)
130 && mComponentName.getClassName().equals(className);
131 }
132
133 public String getFlattenedComponentName() {
134 return mComponentName.flattenToShortString();
135 }
136
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
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
159 public void rebindIfDisconnected() {
160 if (mActiveConnection == null && shouldBind()) {
161 unbind();
162 bind();
163 }
164 }
165
166 private void updateBinding() {
167 if (shouldBind()) {
168 bind();
169 } else {
170 unbind();
171 }
172 }
173
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
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
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
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
245 public void onServiceDisconnected(ComponentName name) {
246 if (DEBUG) {
247 Slog.d(TAG, this + ": Service disconnected");
248 }
249 disconnect();
250 }
251
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
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
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
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
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
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 {
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
329 public Connection(IRemoteDisplayProvider provider) {
330 mProvider = provider;
331 mCallback = new ProviderCallback(this);
332 }
333
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
351 public void dispose() {
352 mProvider.asBinder().unlinkToDeath(this, 0);
353 mCallback.dispose();
354 }
355
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
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
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
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
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
397 public void binderDied() {
398 mHandler.post(new Runnable() {
399 @Override
400 public void run() {
401 onConnectionDied(Connection.this);
402 }
403 });
404 }
405
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
427 public ProviderCallback(Connection connection) {
428 mConnectionRef = new WeakReference<Connection>(connection);
429 }
430
431 public void dispose() {
432 mConnectionRef.clear();
433 }
434
435 @Override
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}