/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.media; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.media.IRemoteDisplayCallback; import android.media.IRemoteDisplayProvider; import android.media.RemoteDisplayState; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.IBinder.DeathRecipient; import android.os.UserHandle; import android.util.Log; import android.util.Slog; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.Objects; /** * Maintains a connection to a particular remote display provider service. */ final class RemoteDisplayProviderProxy implements ServiceConnection { private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; private final ComponentName mComponentName; private final int mUserId; private final Handler mHandler; private Callback mDisplayStateCallback; // Connection state private boolean mRunning; private boolean mBound; private Connection mActiveConnection; private boolean mConnectionReady; // Logical state private int mDiscoveryMode; private String mSelectedDisplayId; private RemoteDisplayState mDisplayState; private boolean mScheduledDisplayStateChangedCallback; public RemoteDisplayProviderProxy(Context context, ComponentName componentName, int userId) { mContext = context; mComponentName = componentName; mUserId = userId; mHandler = new Handler(); } public void dump(PrintWriter pw, String prefix) { pw.println(prefix + "Proxy"); pw.println(prefix + " mUserId=" + mUserId); pw.println(prefix + " mRunning=" + mRunning); pw.println(prefix + " mBound=" + mBound); pw.println(prefix + " mActiveConnection=" + mActiveConnection); pw.println(prefix + " mConnectionReady=" + mConnectionReady); pw.println(prefix + " mDiscoveryMode=" + mDiscoveryMode); pw.println(prefix + " mSelectedDisplayId=" + mSelectedDisplayId); pw.println(prefix + " mDisplayState=" + mDisplayState); } public void setCallback(Callback callback) { mDisplayStateCallback = callback; } public RemoteDisplayState getDisplayState() { return mDisplayState; } public void setDiscoveryMode(int mode) { if (mDiscoveryMode != mode) { mDiscoveryMode = mode; if (mConnectionReady) { mActiveConnection.setDiscoveryMode(mode); } updateBinding(); } } public void setSelectedDisplay(String id) { if (!Objects.equals(mSelectedDisplayId, id)) { if (mConnectionReady && mSelectedDisplayId != null) { mActiveConnection.disconnect(mSelectedDisplayId); } mSelectedDisplayId = id; if (mConnectionReady && id != null) { mActiveConnection.connect(id); } updateBinding(); } } public void setDisplayVolume(int volume) { if (mConnectionReady && mSelectedDisplayId != null) { mActiveConnection.setVolume(mSelectedDisplayId, volume); } } public void adjustDisplayVolume(int delta) { if (mConnectionReady && mSelectedDisplayId != null) { mActiveConnection.adjustVolume(mSelectedDisplayId, delta); } } public boolean hasComponentName(String packageName, String className) { return mComponentName.getPackageName().equals(packageName) && mComponentName.getClassName().equals(className); } public String getFlattenedComponentName() { return mComponentName.flattenToShortString(); } public void start() { if (!mRunning) { if (DEBUG) { Slog.d(TAG, this + ": Starting"); } mRunning = true; updateBinding(); } } public void stop() { if (mRunning) { if (DEBUG) { Slog.d(TAG, this + ": Stopping"); } mRunning = false; updateBinding(); } } public void rebindIfDisconnected() { if (mActiveConnection == null && shouldBind()) { unbind(); bind(); } } private void updateBinding() { if (shouldBind()) { bind(); } else { unbind(); } } private boolean shouldBind() { if (mRunning) { // Bind whenever there is a discovery request or selected display. if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE || mSelectedDisplayId != null) { return true; } } return false; } private void bind() { if (!mBound) { if (DEBUG) { Slog.d(TAG, this + ": Binding"); } Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE); service.setComponent(mComponentName); try { mBound = mContext.bindServiceAsUser(service, this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, new UserHandle(mUserId)); if (!mBound && DEBUG) { Slog.d(TAG, this + ": Bind failed"); } } catch (SecurityException ex) { if (DEBUG) { Slog.d(TAG, this + ": Bind failed", ex); } } } } private void unbind() { if (mBound) { if (DEBUG) { Slog.d(TAG, this + ": Unbinding"); } mBound = false; disconnect(); mContext.unbindService(this); } } @Override public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG) { Slog.d(TAG, this + ": Connected"); } if (mBound) { disconnect(); IRemoteDisplayProvider provider = IRemoteDisplayProvider.Stub.asInterface(service); if (provider != null) { Connection connection = new Connection(provider); if (connection.register()) { mActiveConnection = connection; } else { if (DEBUG) { Slog.d(TAG, this + ": Registration failed"); } } } else { Slog.e(TAG, this + ": Service returned invalid remote display provider binder"); } } } @Override public void onServiceDisconnected(ComponentName name) { if (DEBUG) { Slog.d(TAG, this + ": Service disconnected"); } disconnect(); } private void onConnectionReady(Connection connection) { if (mActiveConnection == connection) { mConnectionReady = true; if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE) { mActiveConnection.setDiscoveryMode(mDiscoveryMode); } if (mSelectedDisplayId != null) { mActiveConnection.connect(mSelectedDisplayId); } } } private void onConnectionDied(Connection connection) { if (mActiveConnection == connection) { if (DEBUG) { Slog.d(TAG, this + ": Service connection died"); } disconnect(); } } private void onDisplayStateChanged(Connection connection, RemoteDisplayState state) { if (mActiveConnection == connection) { if (DEBUG) { Slog.d(TAG, this + ": State changed, state=" + state); } setDisplayState(state); } } private void disconnect() { if (mActiveConnection != null) { if (mSelectedDisplayId != null) { mActiveConnection.disconnect(mSelectedDisplayId); } mConnectionReady = false; mActiveConnection.dispose(); mActiveConnection = null; setDisplayState(null); } } private void setDisplayState(RemoteDisplayState state) { if (!Objects.equals(mDisplayState, state)) { mDisplayState = state; if (!mScheduledDisplayStateChangedCallback) { mScheduledDisplayStateChangedCallback = true; mHandler.post(mDisplayStateChanged); } } } @Override public String toString() { return "Service connection " + mComponentName.flattenToShortString(); } private final Runnable mDisplayStateChanged = new Runnable() { @Override public void run() { mScheduledDisplayStateChangedCallback = false; if (mDisplayStateCallback != null) { mDisplayStateCallback.onDisplayStateChanged( RemoteDisplayProviderProxy.this, mDisplayState); } } }; public interface Callback { void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state); } private final class Connection implements DeathRecipient { private final IRemoteDisplayProvider mProvider; private final ProviderCallback mCallback; public Connection(IRemoteDisplayProvider provider) { mProvider = provider; mCallback = new ProviderCallback(this); } public boolean register() { try { mProvider.asBinder().linkToDeath(this, 0); mProvider.setCallback(mCallback); mHandler.post(new Runnable() { @Override public void run() { onConnectionReady(Connection.this); } }); return true; } catch (RemoteException ex) { binderDied(); } return false; } public void dispose() { mProvider.asBinder().unlinkToDeath(this, 0); mCallback.dispose(); } public void setDiscoveryMode(int mode) { try { mProvider.setDiscoveryMode(mode); } catch (RemoteException ex) { Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex); } } public void connect(String id) { try { mProvider.connect(id); } catch (RemoteException ex) { Slog.e(TAG, "Failed to deliver request to connect to display.", ex); } } public void disconnect(String id) { try { mProvider.disconnect(id); } catch (RemoteException ex) { Slog.e(TAG, "Failed to deliver request to disconnect from display.", ex); } } public void setVolume(String id, int volume) { try { mProvider.setVolume(id, volume); } catch (RemoteException ex) { Slog.e(TAG, "Failed to deliver request to set display volume.", ex); } } public void adjustVolume(String id, int volume) { try { mProvider.adjustVolume(id, volume); } catch (RemoteException ex) { Slog.e(TAG, "Failed to deliver request to adjust display volume.", ex); } } @Override public void binderDied() { mHandler.post(new Runnable() { @Override public void run() { onConnectionDied(Connection.this); } }); } void postStateChanged(final RemoteDisplayState state) { mHandler.post(new Runnable() { @Override public void run() { onDisplayStateChanged(Connection.this, state); } }); } } /** * Receives callbacks from the service. *

* This inner class is static and only retains a weak reference to the connection * to prevent the client from being leaked in case the service is holding an * active reference to the client's callback. *

*/ private static final class ProviderCallback extends IRemoteDisplayCallback.Stub { private final WeakReference mConnectionRef; public ProviderCallback(Connection connection) { mConnectionRef = new WeakReference(connection); } public void dispose() { mConnectionRef.clear(); } @Override public void onStateChanged(RemoteDisplayState state) throws RemoteException { Connection connection = mConnectionRef.get(); if (connection != null) { connection.postStateChanged(state); } } } }