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, 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