blob: ba98a0a9fd4e82d13e3296b7fd5febcc614c4f78 [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 {
Dianne Hackbornd69e4c12015-04-24 09:54:54 -0700193 mBound = mContext.bindServiceAsUser(service, this,
194 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
Jeff Brown69b07162013-11-07 00:30:16 -0800195 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) {
Kenny Roote6585b32013-12-13 12:00:26 -0800296 if (!Objects.equals(mDisplayState, state)) {
Jeff Brown69b07162013-11-07 00:30:16 -0800297 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}