Home | History | Annotate | Download | only in media
      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