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 com.android.internal.util.Objects; 20 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.ServiceConnection; 25 import android.media.IRemoteDisplayCallback; 26 import android.media.IRemoteDisplayProvider; 27 import android.media.RemoteDisplayState; 28 import android.os.Handler; 29 import android.os.IBinder; 30 import android.os.RemoteException; 31 import android.os.IBinder.DeathRecipient; 32 import android.os.UserHandle; 33 import android.util.Log; 34 import android.util.Slog; 35 36 import java.io.PrintWriter; 37 import java.lang.ref.WeakReference; 38 39 /** 40 * Maintains a connection to a particular remote display provider service. 41 */ 42 final 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 RemoteDisplayProviderProxy(Context context, ComponentName componentName, int userId)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 dump(PrintWriter pw, String prefix)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 setCallback(Callback callback)85 public void setCallback(Callback callback) { 86 mDisplayStateCallback = callback; 87 } 88 getDisplayState()89 public RemoteDisplayState getDisplayState() { 90 return mDisplayState; 91 } 92 setDiscoveryMode(int mode)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 setSelectedDisplay(String id)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 setDisplayVolume(int volume)116 public void setDisplayVolume(int volume) { 117 if (mConnectionReady && mSelectedDisplayId != null) { 118 mActiveConnection.setVolume(mSelectedDisplayId, volume); 119 } 120 } 121 adjustDisplayVolume(int delta)122 public void adjustDisplayVolume(int delta) { 123 if (mConnectionReady && mSelectedDisplayId != null) { 124 mActiveConnection.adjustVolume(mSelectedDisplayId, delta); 125 } 126 } 127 hasComponentName(String packageName, String className)128 public boolean hasComponentName(String packageName, String className) { 129 return mComponentName.getPackageName().equals(packageName) 130 && mComponentName.getClassName().equals(className); 131 } 132 getFlattenedComponentName()133 public String getFlattenedComponentName() { 134 return mComponentName.flattenToShortString(); 135 } 136 start()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 stop()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 rebindIfDisconnected()159 public void rebindIfDisconnected() { 160 if (mActiveConnection == null && shouldBind()) { 161 unbind(); 162 bind(); 163 } 164 } 165 updateBinding()166 private void updateBinding() { 167 if (shouldBind()) { 168 bind(); 169 } else { 170 unbind(); 171 } 172 } 173 shouldBind()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 bind()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 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.equal(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