RemoteDisplayProviderProxy.java revision 9158825f9c41869689d6b1786d7c7aa8bdd524ce
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 17package com.android.server.media; 18 19import 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; 36import java.util.Objects; 37 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) { 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, 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) { 295 if (!Objects.equals(mDisplayState, state)) { 296 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} 443