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