blob: e34f42bde915e967cff795ade8ea25a64464a29d [file] [log] [blame]
/*
* Copyright (C) 2014 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.tv;
import android.content.Context;
import android.media.tv.ITvInputHardware;
import android.media.tv.ITvInputHardwareCallback;
import android.media.tv.TvInputHardwareInfo;
import android.media.tv.TvStreamConfig;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.Surface;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A helper class for TvInputManagerService to handle TV input hardware.
*
* This class does a basic connection management and forwarding calls to TvInputHal which eventually
* calls to tv_input HAL module.
*
* @hide
*/
class TvInputHardwareManager implements TvInputHal.Callback {
private static final String TAG = TvInputHardwareManager.class.getSimpleName();
private final TvInputHal mHal = new TvInputHal(this);
private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
private final Context mContext;
private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
private final Object mLock = new Object();
public TvInputHardwareManager(Context context) {
mContext = context;
// TODO(hdmi): mHdmiManager = mContext.getSystemService(...);
// TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient();
mHal.init();
}
@Override
public void onDeviceAvailable(
TvInputHardwareInfo info, TvStreamConfig[] configs) {
synchronized (mLock) {
Connection connection = new Connection(info);
connection.updateConfigsLocked(configs);
mConnections.put(info.getDeviceId(), connection);
buildInfoListLocked();
// TODO: notify if necessary
}
}
private void buildInfoListLocked() {
mInfoList.clear();
for (int i = 0; i < mConnections.size(); ++i) {
mInfoList.add(mConnections.valueAt(i).getInfoLocked());
}
}
@Override
public void onDeviceUnavailable(int deviceId) {
synchronized (mLock) {
Connection connection = mConnections.get(deviceId);
if (connection == null) {
Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
return;
}
connection.resetLocked(null, null, null, null);
mConnections.remove(deviceId);
buildInfoListLocked();
// TODO: notify if necessary
}
}
@Override
public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
synchronized (mLock) {
Connection connection = mConnections.get(deviceId);
if (connection == null) {
Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
+ deviceId);
return;
}
connection.updateConfigsLocked(configs);
try {
connection.getCallbackLocked().onStreamConfigChanged(configs);
} catch (RemoteException e) {
Slog.e(TAG, "onStreamConfigurationChanged: " + e);
}
}
}
public List<TvInputHardwareInfo> getHardwareList() {
synchronized (mLock) {
return mInfoList;
}
}
/**
* Create a TvInputHardware object with a specific deviceId. One service at a time can access
* the object, and if more than one process attempts to create hardware with the same deviceId,
* the latest service will get the object and all the other hardware are released. The
* release is notified via ITvInputHardwareCallback.onReleased().
*/
public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
int callingUid, int resolvedUserId) {
if (callback == null) {
throw new NullPointerException();
}
synchronized (mLock) {
Connection connection = mConnections.get(deviceId);
if (connection == null) {
Slog.e(TAG, "Invalid deviceId : " + deviceId);
return null;
}
if (connection.getCallingUidLocked() != callingUid
|| connection.getResolvedUserIdLocked() != resolvedUserId) {
TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked());
try {
callback.asBinder().linkToDeath(connection, 0);
} catch (RemoteException e) {
hardware.release();
return null;
}
connection.resetLocked(hardware, callback, callingUid, resolvedUserId);
}
return connection.getHardwareLocked();
}
}
/**
* Release the specified hardware.
*/
public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
int resolvedUserId) {
synchronized (mLock) {
Connection connection = mConnections.get(deviceId);
if (connection == null) {
Slog.e(TAG, "Invalid deviceId : " + deviceId);
return;
}
if (connection.getHardwareLocked() != hardware
|| connection.getCallingUidLocked() != callingUid
|| connection.getResolvedUserIdLocked() != resolvedUserId) {
return;
}
connection.resetLocked(null, null, null, null);
}
}
private class Connection implements IBinder.DeathRecipient {
private final TvInputHardwareInfo mInfo;
private TvInputHardwareImpl mHardware = null;
private ITvInputHardwareCallback mCallback;
private TvStreamConfig[] mConfigs = null;
private Integer mCallingUid = null;
private Integer mResolvedUserId = null;
public Connection(TvInputHardwareInfo info) {
mInfo = info;
}
// *Locked methods assume TvInputHardwareManager.mLock is held.
public void resetLocked(TvInputHardwareImpl hardware,
ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) {
if (mHardware != null) {
try {
mCallback.onReleased();
} catch (RemoteException e) {
Slog.e(TAG, "Connection::resetHardware: " + e);
}
mHardware.release();
}
mHardware = hardware;
mCallback = callback;
mCallingUid = callingUid;
mResolvedUserId = resolvedUserId;
if (mHardware != null && mCallback != null) {
try {
mCallback.onStreamConfigChanged(getConfigsLocked());
} catch (RemoteException e) {
Slog.e(TAG, "Connection::resetHardware: " + e);
}
}
}
public void updateConfigsLocked(TvStreamConfig[] configs) {
mConfigs = configs;
}
public TvInputHardwareInfo getInfoLocked() {
return mInfo;
}
public ITvInputHardware getHardwareLocked() {
return mHardware;
}
public ITvInputHardwareCallback getCallbackLocked() {
return mCallback;
}
public TvStreamConfig[] getConfigsLocked() {
return mConfigs;
}
public int getCallingUidLocked() {
return mCallingUid;
}
public int getResolvedUserIdLocked() {
return mResolvedUserId;
}
@Override
public void binderDied() {
synchronized (mLock) {
resetLocked(null, null, null, null);
}
}
}
private class TvInputHardwareImpl extends ITvInputHardware.Stub {
private final TvInputHardwareInfo mInfo;
private boolean mReleased = false;
private final Object mImplLock = new Object();
public TvInputHardwareImpl(TvInputHardwareInfo info) {
mInfo = info;
}
public void release() {
synchronized (mImplLock) {
mReleased = true;
}
}
@Override
public boolean setSurface(Surface surface, TvStreamConfig config)
throws RemoteException {
synchronized (mImplLock) {
if (mReleased) {
throw new IllegalStateException("Device already released.");
}
if (mInfo.getType() == TvInputHal.TYPE_HDMI) {
if (surface != null) {
// Set "Active Source" for HDMI.
// TODO(hdmi): mHdmiClient.deviceSelect(...);
mActiveHdmiSources.add(mInfo.getDeviceId());
} else {
mActiveHdmiSources.remove(mInfo.getDeviceId());
if (mActiveHdmiSources.size() == 0) {
// Tell HDMI that no HDMI source is active
// TODO(hdmi): mHdmiClient.portSelect(null);
}
}
}
return mHal.setSurface(mInfo.getDeviceId(), surface, config) == TvInputHal.SUCCESS;
}
}
@Override
public void setVolume(float volume) throws RemoteException {
synchronized (mImplLock) {
if (mReleased) {
throw new IllegalStateException("Device already released.");
}
}
// TODO
}
@Override
public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
synchronized (mImplLock) {
if (mReleased) {
throw new IllegalStateException("Device already released.");
}
}
if (mInfo.getType() != TvInputHal.TYPE_HDMI) {
return false;
}
// TODO(hdmi): mHdmiClient.sendKeyEvent(event);
return false;
}
}
}