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 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 
     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 
     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 
     85     public void setCallback(Callback callback) {
     86         mDisplayStateCallback = callback;
     87     }
     88 
     89     public RemoteDisplayState getDisplayState() {
     90         return mDisplayState;
     91     }
     92 
     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 
    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 
    116     public void setDisplayVolume(int volume) {
    117         if (mConnectionReady && mSelectedDisplayId != null) {
    118             mActiveConnection.setVolume(mSelectedDisplayId, volume);
    119         }
    120     }
    121 
    122     public void adjustDisplayVolume(int delta) {
    123         if (mConnectionReady && mSelectedDisplayId != null) {
    124             mActiveConnection.adjustVolume(mSelectedDisplayId, delta);
    125         }
    126     }
    127 
    128     public boolean hasComponentName(String packageName, String className) {
    129         return mComponentName.getPackageName().equals(packageName)
    130                 && mComponentName.getClassName().equals(className);
    131     }
    132 
    133     public String getFlattenedComponentName() {
    134         return mComponentName.flattenToShortString();
    135     }
    136 
    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 
    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 
    159     public void rebindIfDisconnected() {
    160         if (mActiveConnection == null && shouldBind()) {
    161             unbind();
    162             bind();
    163         }
    164     }
    165 
    166     private void updateBinding() {
    167         if (shouldBind()) {
    168             bind();
    169         } else {
    170             unbind();
    171         }
    172     }
    173 
    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 
    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 
    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.equal(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