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 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) { 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 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, 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 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.equals(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 } 444