media/java/android/media/tv/ITvInputHardware.aidl \
media/java/android/media/tv/ITvInputHardwareCallback.aidl \
media/java/android/media/tv/ITvInputManager.aidl \
+ media/java/android/media/tv/ITvInputManagerCallback.aidl \
media/java/android/media/tv/ITvInputService.aidl \
media/java/android/media/tv/ITvInputServiceCallback.aidl \
media/java/android/media/tv/ITvInputSession.aidl \
}
public final class TvInputManager {
- method public boolean getAvailability(java.lang.String);
+ method public int getInputState(java.lang.String);
method public java.util.List<android.media.tv.TvInputInfo> getTvInputList();
+ method public void registerListener(android.media.tv.TvInputManager.TvInputListener, android.os.Handler);
+ method public void unregisterListener(android.media.tv.TvInputManager.TvInputListener);
+ field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
+ field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
+ field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
field public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3; // 0x3
field public static final int VIDEO_UNAVAILABLE_REASON_TUNE = 1; // 0x1
field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0
public static abstract class TvInputManager.TvInputListener {
ctor public TvInputManager.TvInputListener();
- method public void onAvailabilityChanged(java.lang.String, boolean);
+ method public void onInputStateChanged(java.lang.String, int);
}
public abstract class TvInputService extends android.app.Service {
*/
oneway interface ITvInputClient {
void onSessionCreated(in String inputId, IBinder token, in InputChannel channel, int seq);
- void onAvailabilityChanged(in String inputId, boolean isAvailable);
void onSessionReleased(int seq);
void onSessionEvent(in String name, in Bundle args, int seq);
void onChannelRetuned(in Uri channelUri, int seq);
import android.content.ComponentName;
import android.graphics.Rect;
+import android.media.tv.ITvInputClient;
import android.media.tv.ITvInputHardware;
import android.media.tv.ITvInputHardwareCallback;
-import android.media.tv.ITvInputClient;
+import android.media.tv.ITvInputManagerCallback;
import android.media.tv.TvInputHardwareInfo;
import android.media.tv.TvInputInfo;
import android.media.tv.TvTrackInfo;
interface ITvInputManager {
List<TvInputInfo> getTvInputList(int userId);
- boolean getAvailability(in ITvInputClient client, in String inputId, int userId);
-
- void registerCallback(in ITvInputClient client, in String inputId, int userId);
- void unregisterCallback(in ITvInputClient client, in String inputId, int userId);
+ void registerCallback(in ITvInputManagerCallback callback, int userId);
+ void unregisterCallback(in ITvInputManagerCallback callback, int userId);
void createSession(in ITvInputClient client, in String inputId, int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
// For TV input hardware binding
List<TvInputHardwareInfo> getHardwareList();
+ /*
+ * All TvInputServices which want to use hardware must call this method on
+ * BOOT_COMPLETE.
+ */
+ void registerTvInputInfo(in TvInputInfo info, int deviceId);
ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
- int userId);
+ in TvInputInfo info, int userId);
void releaseTvInputHardware(int deviceId, in ITvInputHardware hardware, int userId);
}
--- /dev/null
+/*
+ * 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 android.media.tv;
+
+/**
+ * Interface to receive callbacks from ITvInputManager regardless of sessions.
+ * @hide
+ */
+oneway interface ITvInputManagerCallback {
+ void onInputStateChanged(in String inputId, int state);
+}
* @hide
*/
oneway interface ITvInputServiceCallback {
- void onAvailabilityChanged(in String inputId, boolean isAvailable);
+ void onInputStateChanged(int state);
}
// Attributes from XML meta data.
private String mSetupActivity;
private String mSettingsActivity;
- private int mType;
+ private int mType = TYPE_VIRTUAL;
/**
* Create a new instance of the TvInputInfo class,
Log.d(TAG, "Settings activity loaded. [" + input.mSettingsActivity + "] for "
+ si.name);
}
- input.mType = sa.getInt(
- com.android.internal.R.styleable.TvInputService_tvInputType, TYPE_VIRTUAL);
- if (DEBUG) {
- Log.d(TAG, "Type loaded. [" + input.mType + "] for " + si.name);
+ if (pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE, si.packageName)
+ == PackageManager.PERMISSION_GRANTED) {
+ input.mType = sa.getInt(
+ com.android.internal.R.styleable.TvInputService_tvInputType, TYPE_VIRTUAL);
+ if (DEBUG) {
+ Log.d(TAG, "Type loaded. [" + input.mType + "] for " + si.name);
+ }
}
sa.recycle();
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Pools.Pool;
import android.util.Pools.SimplePool;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
*/
public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3;
+ /**
+ * The TV input is connected.
+ * <p>
+ * State for {@link #getInputState} and {@link
+ * TvInputManager.TvInputListener#onInputStateChanged}.
+ * </p>
+ */
+ public static final int INPUT_STATE_CONNECTED = 0;
+ /**
+ * The TV input is connected but in standby mode. It would take a while until it becomes
+ * fully ready.
+ * <p>
+ * State for {@link #getInputState} and {@link
+ * TvInputManager.TvInputListener#onInputStateChanged}.
+ * </p>
+ */
+ public static final int INPUT_STATE_CONNECTED_STANDBY = 1;
+ /**
+ * The TV input is disconnected.
+ * <p>
+ * State for {@link #getInputState} and {@link
+ * TvInputManager.TvInputListener#onInputStateChanged}.
+ * </p>
+ */
+ public static final int INPUT_STATE_DISCONNECTED = 2;
+
private final ITvInputManager mService;
- // A mapping from an input to the list of its TvInputListenerRecords.
- private final Map<String, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
- new HashMap<String, List<TvInputListenerRecord>>();
+ private final Object mLock = new Object();
+
+ // @GuardedBy(mLock)
+ private final List<TvInputListenerRecord> mTvInputListenerRecordsList =
+ new LinkedList<TvInputListenerRecord>();
+
+ // A mapping from TV input ID to the state of corresponding input.
+ // @GuardedBy(mLock)
+ private final Map<String, Integer> mStateMap = new ArrayMap<String, Integer>();
// A mapping from the sequence number of a session to its SessionCallbackRecord.
private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
private final ITvInputClient mClient;
+ private final ITvInputManagerCallback mCallback;
+
private final int mUserId;
/**
*/
public abstract static class TvInputListener {
/**
- * This is called when the availability status of a given TV input is changed.
+ * This is called when the state of a given TV input is changed.
*
* @param inputId the id of the TV input.
- * @param isAvailable {@code true} if the given TV input is available to show TV programs.
- * {@code false} otherwise.
+ * @param state state of the TV input. The value is one of the following:
+ * <ul>
+ * <li>{@link TvInputManager#INPUT_STATE_CONNECTED}
+ * <li>{@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY}
+ * <li>{@link TvInputManager#INPUT_STATE_DISCONNECTED}
+ * </ul>
*/
- public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+ public void onInputStateChanged(String inputId, int state) {
}
}
return mListener;
}
- public void postAvailabilityChanged(final String inputId, final boolean isAvailable) {
+ public void postStateChanged(final String inputId, final int state) {
mHandler.post(new Runnable() {
@Override
public void run() {
- mListener.onAvailabilityChanged(inputId, isAvailable);
+ mListener.onInputStateChanged(inputId, state);
}
});
}
record.postSessionEvent(eventType, eventArgs);
}
}
-
+ };
+ mCallback = new ITvInputManagerCallback.Stub() {
@Override
- public void onAvailabilityChanged(String inputId, boolean isAvailable) {
- synchronized (mTvInputListenerRecordsMap) {
- List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
- if (records == null) {
- // Silently ignore - no listener is registered yet.
- return;
- }
- int recordsCount = records.size();
- for (int i = 0; i < recordsCount; i++) {
- records.get(i).postAvailabilityChanged(inputId, isAvailable);
+ public void onInputStateChanged(String inputId, int state) {
+ synchronized (mLock) {
+ mStateMap.put(inputId, state);
+ for (TvInputListenerRecord record : mTvInputListenerRecordsList) {
+ record.postStateChanged(inputId, state);
}
}
}
};
+ try {
+ mService.registerCallback(mCallback, mUserId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "mService.registerCallback failed: " + e);
+ }
}
/**
}
/**
- * Returns the availability of a given TV input.
+ * Returns the state of a given TV input. It retuns one of the following:
+ * <ul>
+ * <li>{@link #INPUT_STATE_CONNECTED}
+ * <li>{@link #INPUT_STATE_CONNECTED_STANDBY}
+ * <li>{@link #INPUT_STATE_DISCONNECTED}
+ * </ul>
*
* @param inputId the id of the TV input.
- * @throws IllegalArgumentException if the argument is {@code null}.
- * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given
- * TV input.
+ * @throws IllegalArgumentException if the argument is {@code null} or if there is no
+ * {@link TvInputInfo} corresponding to {@code inputId}.
*/
- public boolean getAvailability(String inputId) {
+ public int getInputState(String inputId) {
if (inputId == null) {
throw new IllegalArgumentException("id cannot be null");
}
- synchronized (mTvInputListenerRecordsMap) {
- List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
- if (records == null || records.size() == 0) {
- throw new IllegalStateException("At least one listener should be registered.");
+ synchronized (mLock) {
+ Integer state = mStateMap.get(inputId);
+ if (state == null) {
+ throw new IllegalArgumentException("Unrecognized input ID: " + inputId);
}
- }
- try {
- return mService.getAvailability(mClient, inputId, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
+ return state.intValue();
}
}
/**
- * Registers a {@link TvInputListener} for a given TV input.
+ * Registers a {@link TvInputListener}.
*
- * @param inputId the id of the TV input.
- * @param listener a listener used to monitor status of the given TV input.
+ * @param listener a listener used to monitor status of the TV inputs.
* @param handler a {@link Handler} that the status change will be delivered to.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
- * @hide
*/
- public void registerListener(String inputId, TvInputListener listener, Handler handler) {
- if (inputId == null) {
- throw new IllegalArgumentException("id cannot be null");
- }
+ public void registerListener(TvInputListener listener, Handler handler) {
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
if (handler == null) {
throw new IllegalArgumentException("handler cannot be null");
}
- synchronized (mTvInputListenerRecordsMap) {
- List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
- if (records == null) {
- records = new ArrayList<TvInputListenerRecord>();
- mTvInputListenerRecordsMap.put(inputId, records);
- try {
- mService.registerCallback(mClient, inputId, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
- records.add(new TvInputListenerRecord(listener, handler));
+ synchronized (mLock) {
+ mTvInputListenerRecordsList.add(new TvInputListenerRecord(listener, handler));
}
}
/**
- * Unregisters the existing {@link TvInputListener} for a given TV input.
+ * Unregisters the existing {@link TvInputListener}.
*
- * @param inputId the id of the TV input.
- * @param listener the existing listener to remove for the given TV input.
+ * @param listener the existing listener to remove.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
- * @hide
*/
- public void unregisterListener(String inputId, final TvInputListener listener) {
- if (inputId == null) {
- throw new IllegalArgumentException("id cannot be null");
- }
+ public void unregisterListener(final TvInputListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
- synchronized (mTvInputListenerRecordsMap) {
- List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
- if (records == null) {
- Log.e(TAG, "No listener found for " + inputId);
- return;
- }
- for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) {
+ synchronized (mLock) {
+ for (Iterator<TvInputListenerRecord> it = mTvInputListenerRecordsList.iterator();
+ it.hasNext(); ) {
TvInputListenerRecord record = it.next();
if (record.getListener() == listener) {
it.remove();
- }
- }
- if (records.isEmpty()) {
- try {
- mService.unregisterCallback(mClient, inputId, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- mTvInputListenerRecordsMap.remove(inputId);
+ break;
}
}
}
*/
public static final String SERVICE_META_DATA = "android.media.tv.input";
- private String mId;
private final Handler mHandler = new ServiceHandler();
private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
new RemoteCallbackList<ITvInputServiceCallback>();
- // STOPSHIP: Redesign the API around the availability change. For now, the service will be
- // always available.
- private final boolean mAvailable = true;
-
- @Override
- public void onCreate() {
- super.onCreate();
- mId = TvInputInfo.generateInputIdForComponentName(
- new ComponentName(getPackageName(), getClass().getName()));
- }
@Override
public final IBinder onBind(Intent intent) {
public void registerCallback(ITvInputServiceCallback cb) {
if (cb != null) {
mCallbacks.register(cb);
- // The first time a callback is registered, the service needs to report its
- // availability status so that the system can know its initial value.
- try {
- cb.onAvailabilityChanged(mId, mAvailable);
- } catch (RemoteException e) {
- Log.e(TAG, "error in onAvailabilityChanged", e);
- }
}
}
@SuppressLint("HandlerLeak")
private final class ServiceHandler extends Handler {
private static final int DO_CREATE_SESSION = 1;
- private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
@Override
public final void handleMessage(Message msg) {
args.recycle();
return;
}
- case DO_BROADCAST_AVAILABILITY_CHANGE: {
- boolean isAvailable = (Boolean) msg.obj;
- int n = mCallbacks.beginBroadcast();
- try {
- for (int i = 0; i < n; i++) {
- mCallbacks.getBroadcastItem(i).onAvailabilityChanged(mId, isAvailable);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Unexpected exception", e);
- } finally {
- mCallbacks.finishBroadcast();
- }
- return;
- }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
return;
package com.android.server.tv;
+import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
+import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
+
import android.content.Context;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiHotplugEvent;
import android.media.AudioDevicePort;
import android.media.AudioManager;
import android.media.AudioPatch;
import android.media.tv.ITvInputHardware;
import android.media.tv.ITvInputHardwareCallback;
import android.media.tv.TvInputHardwareInfo;
+import android.media.tv.TvInputInfo;
import android.media.tv.TvStreamConfig;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.KeyEvent;
import android.view.Surface;
+import com.android.server.SystemService;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
*
* @hide
*/
-class TvInputHardwareManager implements TvInputHal.Callback {
+class TvInputHardwareManager
+ implements TvInputHal.Callback, HdmiControlManager.HotplugEventListener {
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 TvInputManagerService.Client mClient;
private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
private final AudioManager mAudioManager;
+ private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
+ // TODO: Should handle INACTIVE case.
+ private final SparseArray<TvInputInfo> mTvInputInfoMap = new SparseArray<TvInputInfo>();
+
+ // Calls to mClient should happen here.
+ private final HandlerThread mHandlerThread = new HandlerThread(TAG);
+ private final Handler mHandler;
private final Object mLock = new Object();
- public TvInputHardwareManager(Context context) {
+ public TvInputHardwareManager(Context context, TvInputManagerService.Client client) {
mContext = context;
+ mClient = client;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- // TODO(hdmi): mHdmiManager = mContext.getSystemService(...);
- // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient();
mHal.init();
+
+ mHandlerThread.start();
+ mHandler = new ClientHandler(mHandlerThread.getLooper());
+ }
+
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ HdmiControlManager hdmiControlManager =
+ (HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE);
+ hdmiControlManager.addHotplugEventListener(this);
+ }
}
@Override
private void buildInfoListLocked() {
mInfoList.clear();
for (int i = 0; i < mConnections.size(); ++i) {
- mInfoList.add(mConnections.valueAt(i).getInfoLocked());
+ mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked());
}
}
Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
return;
}
- connection.resetLocked(null, null, null, null);
+ connection.resetLocked(null, null, null, null, null);
mConnections.remove(deviceId);
buildInfoListLocked();
// TODO: notify if necessary
return false;
}
+ private int convertConnectedToState(boolean connected) {
+ if (connected) {
+ return INPUT_STATE_CONNECTED;
+ } else {
+ return INPUT_STATE_DISCONNECTED;
+ }
+ }
+
+ public void registerTvInputInfo(TvInputInfo info, int deviceId) {
+ if (info.getType() == TvInputInfo.TYPE_VIRTUAL) {
+ throw new IllegalArgumentException("info (" + info + ") has virtual type.");
+ }
+ synchronized (mLock) {
+ if (mTvInputInfoMap.indexOfKey(deviceId) >= 0) {
+ Slog.w(TAG, "Trying to override previous registration: old = "
+ + mTvInputInfoMap.get(deviceId) + ":" + deviceId + ", new = "
+ + info + ":" + deviceId);
+ }
+ mTvInputInfoMap.put(deviceId, info);
+
+ for (int i = 0; i < mHdmiStateMap.size(); ++i) {
+ String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i));
+ if (inputId != null && inputId.equals(info.getId())) {
+ mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+ convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
+ inputId).sendToTarget();
+ }
+ }
+ }
+ }
+
/**
* 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,
* release is notified via ITvInputHardwareCallback.onReleased().
*/
public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
- int callingUid, int resolvedUserId) {
+ TvInputInfo info, int callingUid, int resolvedUserId) {
if (callback == null) {
throw new NullPointerException();
}
return null;
}
if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
- TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked());
+ TvInputHardwareImpl hardware =
+ new TvInputHardwareImpl(connection.getHardwareInfoLocked());
try {
callback.asBinder().linkToDeath(connection, 0);
} catch (RemoteException e) {
hardware.release();
return null;
}
- connection.resetLocked(hardware, callback, callingUid, resolvedUserId);
+ connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
}
return connection.getHardwareLocked();
}
|| checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
return;
}
- connection.resetLocked(null, null, null, null);
+ connection.resetLocked(null, null, null, null, null);
+ }
+ }
+
+ private String findInputIdForHdmiPortLocked(int port) {
+ for (TvInputHardwareInfo hardwareInfo : mInfoList) {
+ if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
+ && hardwareInfo.getHdmiPortId() == port) {
+ TvInputInfo info = mTvInputInfoMap.get(hardwareInfo.getDeviceId());
+ return (info == null) ? null : info.getId();
+ }
+ }
+ return null;
+ }
+
+ // HdmiControlManager.HotplugEventListener implementation.
+
+ @Override
+ public void onReceived(HdmiHotplugEvent event) {
+ String inputId = null;
+
+ synchronized (mLock) {
+ mHdmiStateMap.put(event.getPort(), event.isConnected());
+ inputId = findInputIdForHdmiPortLocked(event.getPort());
+ if (inputId == null) {
+ return;
+ }
+ mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+ convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
}
}
private class Connection implements IBinder.DeathRecipient {
- private final TvInputHardwareInfo mInfo;
+ private final TvInputHardwareInfo mHardwareInfo;
+ private TvInputInfo 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;
+ public Connection(TvInputHardwareInfo hardwareInfo) {
+ mHardwareInfo = hardwareInfo;
}
// *Locked methods assume TvInputHardwareManager.mLock is held.
- public void resetLocked(TvInputHardwareImpl hardware,
- ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) {
+ public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
+ TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
if (mHardware != null) {
try {
mCallback.onReleased();
}
mHardware = hardware;
mCallback = callback;
+ mInfo = info;
mCallingUid = callingUid;
mResolvedUserId = resolvedUserId;
mConfigs = configs;
}
- public TvInputHardwareInfo getInfoLocked() {
+ public TvInputHardwareInfo getHardwareInfoLocked() {
+ return mHardwareInfo;
+ }
+
+ public TvInputInfo getInfoLocked() {
return mInfo;
}
@Override
public void binderDied() {
synchronized (mLock) {
- resetLocked(null, null, null, null);
+ resetLocked(null, null, null, null, null);
}
}
}
return false;
}
}
+
+ private class ClientHandler extends Handler {
+ private static final int DO_SET_AVAILABLE = 1;
+
+ ClientHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public final void handleMessage(Message msg) {
+ switch (msg.what) {
+ case DO_SET_AVAILABLE: {
+ String inputId = (String) msg.obj;
+ int state = msg.arg1;
+ mClient.setState(inputId, state);
+ break;
+ }
+ default: {
+ Slog.w(TAG, "Unhandled message: " + msg);
+ break;
+ }
+ }
+ }
+ }
}
package com.android.server.tv;
+import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
+import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
+
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.media.tv.ITvInputHardware;
import android.media.tv.ITvInputHardwareCallback;
import android.media.tv.ITvInputManager;
+import android.media.tv.ITvInputManagerCallback;
import android.media.tv.ITvInputService;
import android.media.tv.ITvInputServiceCallback;
import android.media.tv.ITvInputSession;
mContentResolver = context.getContentResolver();
mLogHandler = new LogHandler(IoThread.get().getLooper());
- mTvInputHardwareManager = new TvInputHardwareManager(context);
+ mTvInputHardwareManager = new TvInputHardwareManager(context, new Client());
synchronized (mLock) {
mUserStates.put(mCurrentUserId, new UserState());
buildTvInputListLocked(mCurrentUserId);
}
}
+ mTvInputHardwareManager.onBootPhase(phase);
}
private void registerBroadcastReceivers() {
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
UserState userState = getUserStateLocked(mCurrentUserId);
- if (!userState.packageList.contains(packageName)) {
+ if (!userState.packageSet.contains(packageName)) {
// Not a TV input package.
return;
}
private void buildTvInputListLocked(int userId) {
UserState userState = getUserStateLocked(userId);
- userState.inputMap.clear();
- userState.packageList.clear();
+
+ Map<String, TvInputState> oldInputMap = userState.inputMap;
+ userState.inputMap = new HashMap<String, TvInputState>();
+
+ userState.packageSet.clear();
if (DEBUG) Slog.d(TAG, "buildTvInputList");
PackageManager pm = mContext.getPackageManager();
try {
TvInputInfo info = TvInputInfo.createTvInputInfo(mContext, ri);
if (DEBUG) Slog.d(TAG, "add " + info.getId());
- userState.inputMap.put(info.getId(), info);
- userState.packageList.add(si.packageName);
+ TvInputState state = oldInputMap.get(info.getId());
+ if (state == null) {
+ state = new TvInputState();
+ }
+ userState.inputMap.put(info.getId(), state);
+ state.mInfo = info;
+ userState.packageSet.add(si.packageName);
// Reconnect the service if existing input is updated.
updateServiceConnectionLocked(info.getId(), userId);
Slog.e(TAG, "Can't load TV input " + si.name, e);
}
}
+ oldInputMap.clear();
}
private void switchUser(int userId) {
}
Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
- userState.inputMap.get(inputId).getComponent());
+ userState.inputMap.get(inputId).mInfo.getComponent());
// Binding service may fail if the service is updating.
// In that case, the connection will be revived in buildTvInputListLocked called by
// onSomePackagesChanged.
updateServiceConnectionLocked(sessionState.mInputId, userId);
}
- private void unregisterCallbackInternalLocked(IBinder clientToken, String inputId,
+ private void unregisterClientInternalLocked(IBinder clientToken, String inputId,
int userId) {
UserState userState = getUserStateLocked(userId);
ClientState clientState = userState.clientStateMap.get(clientToken);
}
}
- private void broadcastServiceAvailabilityChangedLocked(ServiceState serviceState) {
- for (IBinder clientToken : serviceState.mClientTokens) {
+ private void notifyStateChangedLocked(UserState userState, String inputId,
+ int state, ITvInputManagerCallback targetCallback) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyStateChangedLocked: inputId = " + inputId
+ + "; state = " + state);
+ }
+ if (targetCallback == null) {
+ for (ITvInputManagerCallback callback : userState.callbackSet) {
+ try {
+ callback.onInputStateChanged(inputId, state);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report state change to callback.");
+ }
+ }
+ } else {
try {
- ITvInputClient.Stub.asInterface(clientToken).onAvailabilityChanged(
- serviceState.mTvInputInfo.getId(), serviceState.mAvailable);
+ targetCallback.onInputStateChanged(inputId, state);
} catch (RemoteException e) {
- Slog.e(TAG, "error in onAvailabilityChanged", e);
+ Slog.e(TAG, "Failed to report state change to callback.");
}
}
}
+ private void setStateLocked(String inputId, int state, int userId) {
+ UserState userState = getUserStateLocked(userId);
+ TvInputState inputState = userState.inputMap.get(inputId);
+ ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ int oldState = inputState.mState;
+ inputState.mState = state;
+ boolean isStateEmpty = serviceState.mClientTokens.isEmpty()
+ && serviceState.mSessionTokens.isEmpty();
+ if (serviceState != null && serviceState.mService == null && !isStateEmpty) {
+ // We don't notify state change while reconnecting. It should remain disconnected.
+ return;
+ }
+ if (oldState != state) {
+ notifyStateChangedLocked(userState, inputId, state, null);
+ }
+ }
+
private final class BinderService extends ITvInputManager.Stub {
@Override
public List<TvInputInfo> getTvInputList(int userId) {
try {
synchronized (mLock) {
UserState userState = getUserStateLocked(resolvedUserId);
- return new ArrayList<TvInputInfo>(userState.inputMap.values());
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public boolean getAvailability(final ITvInputClient client, final String inputId,
- int userId) {
- final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, "getAvailability");
- final long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- UserState userState = getUserStateLocked(resolvedUserId);
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
- if (serviceState != null) {
- // We already know the status of this input service. Return the cached
- // status.
- return serviceState.mAvailable;
+ List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
+ for (TvInputState state : userState.inputMap.values()) {
+ inputList.add(state.mInfo);
}
+ return inputList;
}
} finally {
Binder.restoreCallingIdentity(identity);
}
- // STOPSHIP: Redesign the API around the availability change. For now, the service
- // will be always available.
- return true;
}
@Override
- public void registerCallback(final ITvInputClient client, final String inputId,
- int userId) {
+ public void registerCallback(final ITvInputManagerCallback callback, int userId) {
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Binder.getCallingUid(), userId, "registerCallback");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- // Create a new service callback and add it to the callback map of the current
- // service.
UserState userState = getUserStateLocked(resolvedUserId);
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
- if (serviceState == null) {
- serviceState = new ServiceState(
- userState.inputMap.get(inputId), resolvedUserId);
- userState.serviceStateMap.put(inputId, serviceState);
- }
- IBinder clientToken = client.asBinder();
- if (!serviceState.mClientTokens.contains(clientToken)) {
- serviceState.mClientTokens.add(clientToken);
- }
-
- ClientState clientState = userState.clientStateMap.get(clientToken);
- if (clientState == null) {
- clientState = createClientStateLocked(clientToken, resolvedUserId);
- }
- if (!clientState.mInputIds.contains(inputId)) {
- clientState.mInputIds.add(inputId);
- }
-
- if (serviceState.mService != null) {
- if (serviceState.mCallback != null) {
- // We already handled.
- return;
- }
- serviceState.mCallback = new ServiceCallback(resolvedUserId);
- try {
- serviceState.mService.registerCallback(serviceState.mCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "error in registerCallback", e);
- }
- } else {
- updateServiceConnectionLocked(inputId, resolvedUserId);
+ userState.callbackSet.add(callback);
+ for (TvInputState state : userState.inputMap.values()) {
+ notifyStateChangedLocked(userState, state.mInfo.getId(),
+ state.mState, callback);
}
}
} finally {
}
@Override
- public void unregisterCallback(ITvInputClient client, String inputId, int userId) {
+ public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Binder.getCallingUid(), userId, "unregisterCallback");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- unregisterCallbackInternalLocked(client.asBinder(), inputId, resolvedUserId);
+ UserState userState = getUserStateLocked(resolvedUserId);
+ userState.callbackSet.remove(callback);
}
} finally {
Binder.restoreCallingIdentity(identity);
ServiceState serviceState = userState.serviceStateMap.get(inputId);
if (serviceState == null) {
serviceState = new ServiceState(
- userState.inputMap.get(inputId), resolvedUserId);
+ userState.inputMap.get(inputId).mInfo, resolvedUserId);
userState.serviceStateMap.put(inputId, serviceState);
}
// Send a null token immediately while reconnecting.
}
// Create a log entry and fill it later.
- String packageName = userState.inputMap.get(sessionState.mInputId)
+ String packageName = userState.inputMap.get(sessionState.mInputId).mInfo
.getServiceInfo().packageName;
ContentValues values = new ContentValues();
values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
@Override
public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
- if (mContext.checkCallingPermission(
- android.Manifest.permission.TV_INPUT_HARDWARE)
+ if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
!= PackageManager.PERMISSION_GRANTED) {
return null;
}
}
@Override
+ public void registerTvInputInfo(TvInputInfo info, int deviceId) {
+ if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
+ != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mTvInputHardwareManager.registerTvInputInfo(info, deviceId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public ITvInputHardware acquireTvInputHardware(int deviceId,
- ITvInputHardwareCallback callback, int userId) throws RemoteException {
- if (mContext.checkCallingPermission(
- android.Manifest.permission.TV_INPUT_HARDWARE)
+ ITvInputHardwareCallback callback, TvInputInfo info, int userId)
+ throws RemoteException {
+ if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
!= PackageManager.PERMISSION_GRANTED) {
return null;
}
userId, "acquireTvInputHardware");
try {
return mTvInputHardwareManager.acquireHardware(
- deviceId, callback, callingUid, resolvedUserId);
+ deviceId, callback, info, callingUid, resolvedUserId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@Override
public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
throws RemoteException {
- if (mContext.checkCallingPermission(
- android.Manifest.permission.TV_INPUT_HARDWARE)
+ if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
pw.println("UserState (" + userId + "):");
pw.increaseIndent();
- pw.println("inputMap: inputId -> TvInputInfo");
+ pw.println("inputMap: inputId -> TvInputState");
pw.increaseIndent();
- for (TvInputInfo info : userState.inputMap.values()) {
- pw.println(info.toString());
+ for (TvInputState state : userState.inputMap.values()) {
+ pw.println(state.toString());
}
pw.decreaseIndent();
- pw.println("packageList:");
+ pw.println("packageSet:");
pw.increaseIndent();
- for (String packageName : userState.packageList) {
+ for (String packageName : userState.packageSet) {
pw.println(packageName);
}
pw.decreaseIndent();
pw.println("mService: " + service.mService);
pw.println("mCallback: " + service.mCallback);
pw.println("mBound: " + service.mBound);
- pw.println("mAvailable: " + service.mAvailable);
pw.println("mReconnecting: " + service.mReconnecting);
pw.decreaseIndent();
}
pw.decreaseIndent();
+ pw.println("callbackSet:");
+ pw.increaseIndent();
+ for (ITvInputManagerCallback callback : userState.callbackSet) {
+ pw.println(callback.toString());
+ }
+ pw.decreaseIndent();
+
pw.decreaseIndent();
}
}
}
}
+ private static final class TvInputState {
+ // A TvInputInfo object which represents the TV input.
+ private TvInputInfo mInfo;
+
+ // The state of TV input. Connected by default.
+ private int mState = INPUT_STATE_CONNECTED;
+
+ @Override
+ public String toString() {
+ return "mInfo: " + mInfo + "; mState: " + mState;
+ }
+ }
+
private static final class UserState {
- // A mapping from the TV input id to its TvInputInfo.
- private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>();
+ // A mapping from the TV input id to its TvInputState.
+ private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
- // A list of all TV input packages.
- private final Set<String> packageList = new HashSet<String>();
+ // A set of all TV input packages.
+ private final Set<String> packageSet = new HashSet<String>();
// A mapping from the token of a client to its state.
private final Map<IBinder, ClientState> clientStateMap =
// A mapping from the token of a TV input session to its state.
private final Map<IBinder, SessionState> sessionStateMap =
new HashMap<IBinder, SessionState>();
+
+ // A set of callbacks.
+ private final Set<ITvInputManagerCallback> callbackSet =
+ new HashSet<ITvInputManagerCallback>();
}
private final class ClientState implements IBinder.DeathRecipient {
synchronized (mLock) {
UserState userState = getUserStateLocked(mUserId);
// DO NOT remove the client state of clientStateMap in this method. It will be
- // removed in releaseSessionLocked() or unregisterCallbackInternalLocked().
+ // removed in releaseSessionLocked() or unregisterClientInternalLocked().
ClientState clientState = userState.clientStateMap.get(mClientToken);
if (clientState != null) {
while (clientState.mSessionTokens.size() > 0) {
clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
}
while (clientState.mInputIds.size() > 0) {
- unregisterCallbackInternalLocked(
+ unregisterClientInternalLocked(
mClientToken, clientState.mInputIds.get(0), mUserId);
}
}
private ITvInputService mService;
private ServiceCallback mCallback;
private boolean mBound;
- private boolean mAvailable;
private boolean mReconnecting;
private ServiceState(TvInputInfo inputInfo, int userId) {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
+ String inputId = mTvInputInfo.getId();
if (DEBUG) {
- Slog.d(TAG, "onServiceConnected(inputId=" + mTvInputInfo.getId() + ")");
+ Slog.d(TAG, "onServiceConnected(inputId=" + inputId + ")");
}
synchronized (mLock) {
- ServiceState serviceState = getServiceStateLocked(mTvInputInfo.getId(), mUserId);
+ UserState userState = getUserStateLocked(mUserId);
+ ServiceState serviceState = userState.serviceStateMap.get(inputId);
serviceState.mService = ITvInputService.Stub.asInterface(service);
// Register a callback, if we need to.
if (!serviceState.mClientTokens.isEmpty() && serviceState.mCallback == null) {
- serviceState.mCallback = new ServiceCallback(mUserId);
+ serviceState.mCallback = new ServiceCallback(mTvInputInfo.getId(), mUserId);
try {
serviceState.mService.registerCallback(serviceState.mCallback);
} catch (RemoteException e) {
for (IBinder sessionToken : serviceState.mSessionTokens) {
createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
}
+
+ TvInputState inputState = userState.inputMap.get(inputId);
+ if (inputState != null && inputState.mState != INPUT_STATE_DISCONNECTED) {
+ notifyStateChangedLocked(userState, mTvInputInfo.getId(),
+ inputState.mState, null);
+ }
}
}
}
}
- if (serviceState.mAvailable) {
- serviceState.mAvailable = false;
- broadcastServiceAvailabilityChangedLocked(serviceState);
- }
+ notifyStateChangedLocked(userState, mTvInputInfo.getId(),
+ INPUT_STATE_DISCONNECTED, null);
updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
}
}
}
private final class ServiceCallback extends ITvInputServiceCallback.Stub {
+ private final String mInputId;
private final int mUserId;
- ServiceCallback(int userId) {
+ ServiceCallback(String inputId, int userId) {
+ mInputId = inputId;
mUserId = userId;
}
@Override
- public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+ public void onInputStateChanged(int state) {
if (DEBUG) {
- Slog.d(TAG, "onAvailabilityChanged(inputId=" + inputId + ", isAvailable="
- + isAvailable + ")");
+ Slog.d(TAG, "onInputStateChanged(inputId=" + mInputId + ", state="
+ + state + ")");
}
synchronized (mLock) {
- ServiceState serviceState = getServiceStateLocked(inputId, mUserId);
- if (serviceState.mAvailable != isAvailable) {
- serviceState.mAvailable = isAvailable;
- broadcastServiceAvailabilityChangedLocked(serviceState);
- }
+ setStateLocked(mInputId, state, mUserId);
}
}
}
mContentResolver.update(uri, values, null, null);
}
}
+
+ final class Client {
+ public void setState(String inputId, int state) {
+ synchronized (mLock) {
+ setStateLocked(inputId, state, mCurrentUserId);
+ }
+ }
+ }
}