blob: ba98a0a9fd4e82d13e3296b7fd5febcc614c4f78 [file] [log] [blame]
/*
* 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);
}
}
}
}