summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWonsik Kim <wonsik@google.com>2014-07-18 02:19:12 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-07-17 21:11:29 +0000
commit61635036ac29012a45715effcf60b3c395f05f17 (patch)
treed7eb4a31cccf17cc2c98131f3ec5f22fe0810c27
parentca58ddf7c82dd0857de0c3d49d7eb87a842ee4ce (diff)
parent187423c0bc4b27479bc8c23bd86969429094b296 (diff)
downloadframeworks_base-61635036ac29012a45715effcf60b3c395f05f17.zip
frameworks_base-61635036ac29012a45715effcf60b3c395f05f17.tar.gz
frameworks_base-61635036ac29012a45715effcf60b3c395f05f17.tar.bz2
Merge "TIF: one-to-many relationship for TvInputService to TvInputInfo" into lmp-dev
-rw-r--r--media/java/android/media/tv/ITvInputService.aidl8
-rw-r--r--media/java/android/media/tv/ITvInputServiceCallback.aidl9
-rw-r--r--media/java/android/media/tv/TvInputInfo.java78
-rw-r--r--media/java/android/media/tv/TvInputService.java100
-rw-r--r--services/core/java/com/android/server/tv/TvInputHardwareManager.java80
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java327
6 files changed, 474 insertions, 128 deletions
diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl
index 992e424..158223a 100644
--- a/media/java/android/media/tv/ITvInputService.aidl
+++ b/media/java/android/media/tv/ITvInputService.aidl
@@ -18,6 +18,7 @@ package android.media.tv;
import android.media.tv.ITvInputServiceCallback;
import android.media.tv.ITvInputSessionCallback;
+import android.media.tv.TvInputHardwareInfo;
import android.view.InputChannel;
/**
@@ -27,5 +28,10 @@ import android.view.InputChannel;
oneway interface ITvInputService {
void registerCallback(ITvInputServiceCallback callback);
void unregisterCallback(in ITvInputServiceCallback callback);
- void createSession(in InputChannel channel, ITvInputSessionCallback callback);
+ void createSession(in InputChannel channel, ITvInputSessionCallback callback,
+ in String inputId);
+
+ // For hardware TvInputService
+ void notifyHardwareAdded(in TvInputHardwareInfo info);
+ void notifyHardwareRemoved(int deviceId);
}
diff --git a/media/java/android/media/tv/ITvInputServiceCallback.aidl b/media/java/android/media/tv/ITvInputServiceCallback.aidl
index 1fdb8c5..287da71 100644
--- a/media/java/android/media/tv/ITvInputServiceCallback.aidl
+++ b/media/java/android/media/tv/ITvInputServiceCallback.aidl
@@ -16,13 +16,14 @@
package android.media.tv;
-import android.content.ComponentName;
+import android.media.tv.TvInputInfo;
/**
- * Helper interface for ITvInputService to allow the TV input to notify the client when its status
- * has been changed.
+ * Helper interface for ITvInputService to allow the service to notify the
+ * TvInputManagerService.
* @hide
*/
oneway interface ITvInputServiceCallback {
- void onInputStateChanged(int state);
+ void addTvInput(in TvInputInfo inputInfo);
+ void removeTvInput(in String inputId);
}
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 37f166b..8cd2f9b1 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -27,6 +27,8 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.media.tv.TvInputHardwareInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -88,9 +90,46 @@ public final class TvInputInfo implements Parcelable {
* instantiating it from the given Context and ResolveInfo.
*
* @param service The ResolveInfo returned from the package manager about this TV input service.
- * @hide */
+ * @hide
+ */
public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service)
throws XmlPullParserException, IOException {
+ return createTvInputInfo(context, service, generateInputIdForComponentName(
+ new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name)));
+ }
+
+ /**
+ * Create a new instance of the TvInputInfo class,
+ * instantiating it from the given Context, ResolveInfo, and HdmiCecDeviceInfo.
+ *
+ * @param service The ResolveInfo returned from the package manager about this TV input service.
+ * @param cecInfo The HdmiCecDeviceInfo for a HDMI CEC logical device.
+ * @hide
+ */
+ public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
+ HdmiCecDeviceInfo cecInfo) throws XmlPullParserException, IOException {
+ return createTvInputInfo(context, service, generateInputIdForHdmiCec(
+ new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
+ cecInfo));
+ }
+
+ /**
+ * Create a new instance of the TvInputInfo class,
+ * instantiating it from the given Context, ResolveInfo, and TvInputHardwareInfo.
+ *
+ * @param service The ResolveInfo returned from the package manager about this TV input service.
+ * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device.
+ * @hide
+ */
+ public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
+ TvInputHardwareInfo hardwareInfo) throws XmlPullParserException, IOException {
+ return createTvInputInfo(context, service, generateInputIdForHardware(
+ new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
+ hardwareInfo));
+ }
+
+ private static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
+ String id) throws XmlPullParserException, IOException {
ServiceInfo si = service.serviceInfo;
PackageManager pm = context.getPackageManager();
XmlResourceParser parser = null;
@@ -115,7 +154,7 @@ public final class TvInputInfo implements Parcelable {
"Meta-data does not start with tv-input-service tag in " + si.name);
}
- TvInputInfo input = new TvInputInfo(context, service, null);
+ TvInputInfo input = new TvInputInfo(context, service, id, null);
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.TvInputService);
input.mSetupActivity = sa.getString(
@@ -153,12 +192,13 @@ public final class TvInputInfo implements Parcelable {
* Constructor.
*
* @param service The ResolveInfo returned from the package manager about this TV input service.
- * @hide
+ * @param id ID of this TV input. Should be generated via generateInputId*().
+ * @param parentId ID of this TV input's parent input. {@code null} if none exists.
*/
- private TvInputInfo(Context context, ResolveInfo service, String parentId) {
+ private TvInputInfo(Context context, ResolveInfo service, String id, String parentId) {
mService = service;
ServiceInfo si = service.serviceInfo;
- mId = generateInputIdForComponentName(new ComponentName(si.packageName, si.name));
+ mId = id;
mParentId = parentId;
}
@@ -311,13 +351,37 @@ public final class TvInputInfo implements Parcelable {
*
* @param name the component name for generating an input id.
* @return the generated input id for the given {@code name}.
- * @hide
*/
- public static final String generateInputIdForComponentName(ComponentName name) {
+ private static final String generateInputIdForComponentName(ComponentName name) {
return name.flattenToShortString();
}
/**
+ * Used to generate an input id from a ComponentName and HdmiCecDeviceInfo.
+ *
+ * @param name the component name for generating an input id.
+ * @param cecInfo HdmiCecDeviceInfo describing this TV input.
+ * @return the generated input id for the given {@code name} and {@code cecInfo}.
+ */
+ private static final String generateInputIdForHdmiCec(
+ ComponentName name, HdmiCecDeviceInfo cecInfo) {
+ return name.flattenToShortString() + String.format("|CEC%08X%08X",
+ cecInfo.getPhysicalAddress(), cecInfo.getLogicalAddress());
+ }
+
+ /**
+ * Used to generate an input id from a ComponentName and TvInputHardwareInfo
+ *
+ * @param name the component name for generating an input id.
+ * @param hardwareInfo TvInputHardwareInfo describing this TV input.
+ * @return the generated input id for the given {@code name} and {@code hardwareInfo}.
+ */
+ private static final String generateInputIdForHardware(
+ ComponentName name, TvInputHardwareInfo hardwareInfo) {
+ return name.flattenToShortString() + String.format("|HW%d", hardwareInfo.getDeviceId());
+ }
+
+ /**
* Used to make this class parcelable.
*
* @hide
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 3206320..32cf5da 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -17,6 +17,7 @@
package android.media.tv;
import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -104,7 +105,8 @@ public abstract class TvInputService extends Service {
}
@Override
- public void createSession(InputChannel channel, ITvInputSessionCallback cb) {
+ public void createSession(InputChannel channel, ITvInputSessionCallback cb,
+ String inputId) {
if (channel == null) {
Log.w(TAG, "Creating session without input channel");
}
@@ -114,8 +116,21 @@ public abstract class TvInputService extends Service {
SomeArgs args = SomeArgs.obtain();
args.arg1 = channel;
args.arg2 = cb;
+ args.arg3 = inputId;
mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
}
+
+ @Override
+ public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) {
+ mHandler.obtainMessage(ServiceHandler.DO_ADD_TV_INPUT_FROM_HARDWARE,
+ hardwareInfo).sendToTarget();
+ }
+
+ @Override
+ public void notifyHardwareRemoved(int deviceId) {
+ mHandler.obtainMessage(ServiceHandler.DO_REMOVE_TV_INPUT_FROM_HARDWARE,
+ deviceId, 0).sendToTarget();
+ }
};
}
@@ -138,6 +153,44 @@ public abstract class TvInputService extends Service {
public abstract Session onCreateSession();
/**
+ * Returns a concrete implementation of {@link Session}.
+ * <p>
+ * May return {@code null} if this TV input service fails to create a session for some reason.
+ * </p>
+ * @param inputId The ID of the TV input associated with the session.
+ *
+ * @hide
+ */
+ @SystemApi
+ public Session onCreateSession(String inputId) {
+ return onCreateSession();
+ }
+
+ /**
+ * Returns a new TvInputInfo object if this service is responsible for {@code hardwareInfo};
+ * otherwise, return {@code null}. Override to modify default behavior of ignoring all input.
+ *
+ * @param hardwareInfo TvInputHardwareInfo object just added.
+ *
+ * @hide
+ */
+ @SystemApi
+ public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
+ return null;
+ }
+
+ /**
+ * Returns the input ID for {@code deviceId} if it is handled by this service;
+ * otherwise, return {@code null}. Override to modify default behavior of ignoring all input.
+ *
+ * @hide
+ */
+ @SystemApi
+ public String onHardwareRemoved(int deviceId) {
+ return null;
+ }
+
+ /**
* Base class for derived classes to implement to provide a TV input session.
*/
public abstract class Session implements KeyEvent.Callback {
@@ -715,6 +768,32 @@ public abstract class TvInputService extends Service {
@SuppressLint("HandlerLeak")
private final class ServiceHandler extends Handler {
private static final int DO_CREATE_SESSION = 1;
+ private static final int DO_ADD_TV_INPUT_FROM_HARDWARE = 2;
+ private static final int DO_REMOVE_TV_INPUT_FROM_HARDWARE = 3;
+
+ private void broadcastAddTvInput(TvInputInfo inputInfo) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ try {
+ mCallbacks.getBroadcastItem(i).addTvInput(inputInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while broadcasting: " + e);
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+
+ private void broadcastRemoveTvInput(String inputId) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ try {
+ mCallbacks.getBroadcastItem(i).removeTvInput(inputId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while broadcasting: " + e);
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
@Override
public final void handleMessage(Message msg) {
@@ -723,8 +802,9 @@ public abstract class TvInputService extends Service {
SomeArgs args = (SomeArgs) msg.obj;
InputChannel channel = (InputChannel) args.arg1;
ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
+ String inputId = (String) args.arg3;
try {
- Session sessionImpl = onCreateSession();
+ Session sessionImpl = onCreateSession(inputId);
if (sessionImpl == null) {
// Failed to create a session.
cb.onSessionCreated(null);
@@ -740,6 +820,22 @@ public abstract class TvInputService extends Service {
args.recycle();
return;
}
+ case DO_ADD_TV_INPUT_FROM_HARDWARE: {
+ TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
+ TvInputInfo inputInfo = onHardwareAdded(hardwareInfo);
+ if (inputInfo != null) {
+ broadcastAddTvInput(inputInfo);
+ }
+ return;
+ }
+ case DO_REMOVE_TV_INPUT_FROM_HARDWARE: {
+ int deviceId = msg.arg1;
+ String inputId = onHardwareRemoved(deviceId);
+ if (inputId != null) {
+ broadcastRemoveTvInput(inputId);
+ }
+ return;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
return;
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 8f237db..252f2f4 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -20,8 +20,10 @@ 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.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiHotplugEvent;
+import android.hardware.hdmi.IHdmiDeviceEventListener;
import android.media.AudioDevicePort;
import android.media.AudioManager;
import android.media.AudioPatch;
@@ -33,7 +35,6 @@ 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;
@@ -66,27 +67,24 @@ class TvInputHardwareManager
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 Listener mListener;
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>();
+ private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener();
- // Calls to mClient should happen here.
- private final HandlerThread mHandlerThread = new HandlerThread(TAG);
- private final Handler mHandler;
+ // Calls to mListener should happen here.
+ private final Handler mHandler = new ListenerHandler();
private final Object mLock = new Object();
- public TvInputHardwareManager(Context context, TvInputManagerService.Client client) {
+ public TvInputHardwareManager(Context context, Listener listener) {
mContext = context;
- mClient = client;
+ mListener = listener;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mHal.init();
-
- mHandlerThread.start();
- mHandler = new ClientHandler(mHandlerThread.getLooper());
}
public void onBootPhase(int phase) {
@@ -105,7 +103,8 @@ class TvInputHardwareManager
connection.updateConfigsLocked(configs);
mConnections.put(info.getDeviceId(), connection);
buildInfoListLocked();
- // TODO: notify if necessary
+ mHandler.obtainMessage(
+ ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget();
}
}
@@ -127,7 +126,8 @@ class TvInputHardwareManager
connection.resetLocked(null, null, null, null, null);
mConnections.remove(deviceId);
buildInfoListLocked();
- // TODO: notify if necessary
+ mHandler.obtainMessage(
+ ListenerHandler.HARDWARE_DEVICE_REMOVED, deviceId, 0).sendToTarget();
}
}
@@ -191,7 +191,7 @@ class TvInputHardwareManager
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,
+ mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
inputId).sendToTarget();
}
@@ -273,7 +273,7 @@ class TvInputHardwareManager
if (inputId == null) {
return;
}
- mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+ mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
}
}
@@ -502,20 +502,48 @@ class TvInputHardwareManager
}
}
- private class ClientHandler extends Handler {
- private static final int DO_SET_AVAILABLE = 1;
+ interface Listener {
+ public void onStateChanged(String inputId, int state);
+ public void onHardwareDeviceAdded(TvInputHardwareInfo info);
+ public void onHardwareDeviceRemoved(int deviceId);
+ public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice);
+ public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice);
+ }
- ClientHandler(Looper looper) {
- super(looper);
- }
+ private class ListenerHandler extends Handler {
+ private static final int STATE_CHANGED = 1;
+ private static final int HARDWARE_DEVICE_ADDED = 2;
+ private static final int HARDWARE_DEVICE_REMOVED = 3;
+ private static final int CEC_DEVICE_ADDED = 4;
+ private static final int CEC_DEVICE_REMOVED = 5;
@Override
public final void handleMessage(Message msg) {
switch (msg.what) {
- case DO_SET_AVAILABLE: {
+ case STATE_CHANGED: {
String inputId = (String) msg.obj;
int state = msg.arg1;
- mClient.setState(inputId, state);
+ mListener.onStateChanged(inputId, state);
+ break;
+ }
+ case HARDWARE_DEVICE_ADDED: {
+ TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
+ mListener.onHardwareDeviceAdded(info);
+ break;
+ }
+ case HARDWARE_DEVICE_REMOVED: {
+ int deviceId = msg.arg1;
+ mListener.onHardwareDeviceRemoved(deviceId);
+ break;
+ }
+ case CEC_DEVICE_ADDED: {
+ HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
+ mListener.onHdmiCecDeviceAdded(info);
+ break;
+ }
+ case CEC_DEVICE_REMOVED: {
+ HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
+ mListener.onHdmiCecDeviceRemoved(info);
break;
}
default: {
@@ -525,4 +553,14 @@ class TvInputHardwareManager
}
}
}
+
+ private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
+ @Override
+ public void onStatusChanged(HdmiCecDeviceInfo deviceInfo, boolean activated) {
+ mHandler.obtainMessage(
+ activated ? ListenerHandler.CEC_DEVICE_ADDED
+ : ListenerHandler.CEC_DEVICE_REMOVED,
+ 0, 0, deviceInfo).sendToTarget();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 20fdefa..1fb0833 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -37,6 +37,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.Cursor;
import android.graphics.Rect;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.media.tv.ITvInputClient;
import android.media.tv.ITvInputHardware;
import android.media.tv.ITvInputHardwareCallback;
@@ -80,6 +81,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -113,7 +115,7 @@ public final class TvInputManagerService extends SystemService {
mContentResolver = context.getContentResolver();
mLogHandler = new LogHandler(IoThread.get().getLooper());
- mTvInputHardwareManager = new TvInputHardwareManager(context, new Client());
+ mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
synchronized (mLock) {
mUserStates.put(mCurrentUserId, new UserState());
@@ -129,6 +131,7 @@ public final class TvInputManagerService extends SystemService {
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
registerBroadcastReceivers();
+ } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mLock) {
buildTvInputListLocked(mCurrentUserId);
}
@@ -201,6 +204,11 @@ public final class TvInputManagerService extends SystemService {
}, UserHandle.ALL, intentFilter, null, null);
}
+ private static boolean hasHardwarePermission(PackageManager pm, ComponentName name) {
+ return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
+ name.getPackageName()) == PackageManager.PERMISSION_GRANTED;
+ }
+
private void buildTvInputListLocked(int userId) {
UserState userState = getUserStateLocked(userId);
@@ -214,6 +222,7 @@ public final class TvInputManagerService extends SystemService {
List<ResolveInfo> services = pm.queryIntentServices(
new Intent(TvInputService.SERVICE_INTERFACE),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ List<TvInputInfo> infoList = new ArrayList<TvInputInfo>();
for (ResolveInfo ri : services) {
ServiceInfo si = ri.serviceInfo;
if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
@@ -222,18 +231,37 @@ public final class TvInputManagerService extends SystemService {
continue;
}
try {
- TvInputInfo info = TvInputInfo.createTvInputInfo(mContext, ri);
- if (DEBUG) Slog.d(TAG, "add " + info.getId());
- TvInputState state = oldInputMap.get(info.getId());
- if (state == null) {
- state = new TvInputState();
+ infoList.clear();
+ ComponentName service = new ComponentName(si.packageName, si.name);
+ if (hasHardwarePermission(pm, service)) {
+ ServiceState serviceState = userState.serviceStateMap.get(service);
+ if (serviceState == null) {
+ // We see this hardware TV input service for the first time; we need to
+ // prepare the ServiceState object so that we can connect to the service and
+ // let it add TvInputInfo objects to mInputList if there's any.
+ serviceState = new ServiceState(service, userId);
+ userState.serviceStateMap.put(service, serviceState);
+ } else {
+ infoList.addAll(serviceState.mInputList);
+ }
+ } else {
+ infoList.add(TvInputInfo.createTvInputInfo(mContext, ri));
+ }
+
+ for (TvInputInfo info : infoList) {
+ if (DEBUG) Slog.d(TAG, "add " + info.getId());
+ TvInputState state = oldInputMap.get(info.getId());
+ if (state == null) {
+ state = new TvInputState();
+ }
+ userState.inputMap.put(info.getId(), state);
+ state.mInfo = info;
}
- 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);
+ updateServiceConnectionLocked(service, userId);
+
+ userState.packageSet.add(si.packageName);
} catch (IOException | XmlPullParserException e) {
Slog.e(TAG, "Can't load TV input " + si.name, e);
}
@@ -305,11 +333,11 @@ public final class TvInputManagerService extends SystemService {
return userState;
}
- private ServiceState getServiceStateLocked(String inputId, int userId) {
+ private ServiceState getServiceStateLocked(ComponentName name, int userId) {
UserState userState = getUserStateLocked(userId);
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ ServiceState serviceState = userState.serviceStateMap.get(name);
if (serviceState == null) {
- throw new IllegalStateException("Service state not found for " + inputId + " (userId="
+ throw new IllegalStateException("Service state not found for " + name + " (userId="
+ userId + ")");
}
return serviceState;
@@ -344,9 +372,16 @@ public final class TvInputManagerService extends SystemService {
false, methodName, null);
}
- private void updateServiceConnectionLocked(String inputId, int userId) {
+ private static boolean shouldMaintainConnection(ServiceState serviceState) {
+ return !serviceState.mClientTokens.isEmpty()
+ || !serviceState.mSessionTokens.isEmpty()
+ || serviceState.mIsHardware;
+ // TODO: Find a way to maintain connection only when necessary.
+ }
+
+ private void updateServiceConnectionLocked(ComponentName service, int userId) {
UserState userState = getUserStateLocked(userId);
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ ServiceState serviceState = userState.serviceStateMap.get(service);
if (serviceState == null) {
return;
}
@@ -357,9 +392,8 @@ public final class TvInputManagerService extends SystemService {
}
serviceState.mReconnecting = false;
}
- boolean isStateEmpty = serviceState.mClientTokens.isEmpty()
- && serviceState.mSessionTokens.isEmpty();
- if (serviceState.mService == null && !isStateEmpty && userId == mCurrentUserId) {
+ boolean maintainConnection = shouldMaintainConnection(serviceState);
+ if (serviceState.mService == null && maintainConnection && userId == mCurrentUserId) {
// This means that the service is not yet connected but its state indicates that we
// have pending requests. Then, connect the service.
if (serviceState.mBound) {
@@ -368,25 +402,23 @@ public final class TvInputManagerService extends SystemService {
return;
}
if (DEBUG) {
- Slog.d(TAG, "bindServiceAsUser(inputId=" + inputId + ", userId=" + userId
- + ")");
+ Slog.d(TAG, "bindServiceAsUser(service=" + service + ", userId=" + userId + ")");
}
- Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
- userState.inputMap.get(inputId).mInfo.getComponent());
+ Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(service);
// Binding service may fail if the service is updating.
// In that case, the connection will be revived in buildTvInputListLocked called by
// onSomePackagesChanged.
serviceState.mBound = mContext.bindServiceAsUser(
i, serviceState.mConnection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
- } else if (serviceState.mService != null && isStateEmpty) {
+ } else if (serviceState.mService != null && !maintainConnection) {
// This means that the service is already connected but its state indicates that we have
// nothing to do with it. Then, disconnect the service.
if (DEBUG) {
- Slog.d(TAG, "unbindService(inputId=" + inputId + ")");
+ Slog.d(TAG, "unbindService(service=" + service + ")");
}
mContext.unbindService(serviceState.mConnection);
- userState.serviceStateMap.remove(inputId);
+ userState.serviceStateMap.remove(service);
}
}
@@ -407,7 +439,7 @@ public final class TvInputManagerService extends SystemService {
final UserState userState = getUserStateLocked(userId);
final SessionState sessionState = userState.sessionStateMap.get(sessionToken);
if (DEBUG) {
- Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.mInputId + ")");
+ Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.mInfo.getId() + ")");
}
final InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
@@ -417,14 +449,14 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onSessionCreated(ITvInputSession session) {
if (DEBUG) {
- Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInputId + ")");
+ Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInfo.getId() + ")");
}
synchronized (mLock) {
sessionState.mSession = session;
if (session == null) {
removeSessionStateLocked(sessionToken, userId);
- sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
- null, null, sessionState.mSeq);
+ sendSessionTokenToClientLocked(sessionState.mClient,
+ sessionState.mInfo.getId(), null, null, sessionState.mSeq);
} else {
try {
session.asBinder().linkToDeath(sessionState, 0);
@@ -439,8 +471,9 @@ public final class TvInputManagerService extends SystemService {
}
clientState.mSessionTokens.add(sessionState.mSessionToken);
- sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
- sessionToken, channels[0], sessionState.mSeq);
+ sendSessionTokenToClientLocked(sessionState.mClient,
+ sessionState.mInfo.getId(), sessionToken, channels[0],
+ sessionState.mSeq);
}
channels[0].dispose();
}
@@ -540,12 +573,12 @@ public final class TvInputManagerService extends SystemService {
// Create a session. When failed, send a null token immediately.
try {
- service.createSession(channels[1], callback);
+ service.createSession(channels[1], callback, sessionState.mInfo.getId());
} catch (RemoteException e) {
Slog.e(TAG, "error in createSession", e);
removeSessionStateLocked(sessionToken, userId);
- sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, null, null,
- sessionState.mSeq);
+ sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInfo.getId(), null,
+ null, sessionState.mSeq);
}
channels[1].dispose();
}
@@ -595,11 +628,14 @@ public final class TvInputManagerService extends SystemService {
}
}
- ServiceState serviceState = userState.serviceStateMap.get(sessionState.mInputId);
- if (serviceState != null) {
- serviceState.mSessionTokens.remove(sessionToken);
+ TvInputInfo info = sessionState.mInfo;
+ if (info != null) {
+ ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
+ if (serviceState != null) {
+ serviceState.mSessionTokens.remove(sessionToken);
+ }
}
- updateServiceConnectionLocked(sessionState.mInputId, userId);
+ updateServiceConnectionLocked(sessionState.mInfo.getComponent(), userId);
}
private void unregisterClientInternalLocked(IBinder clientToken, String inputId,
@@ -613,7 +649,11 @@ public final class TvInputManagerService extends SystemService {
}
}
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ TvInputInfo info = userState.inputMap.get(inputId).mInfo;
+ if (info == null) {
+ return;
+ }
+ ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
if (serviceState == null) {
return;
}
@@ -633,7 +673,7 @@ public final class TvInputManagerService extends SystemService {
Slog.e(TAG, "error in unregisterCallback", e);
} finally {
serviceState.mCallback = null;
- updateServiceConnectionLocked(inputId, userId);
+ updateServiceConnectionLocked(info.getComponent(), userId);
}
}
@@ -666,9 +706,8 @@ public final class TvInputManagerService extends SystemService {
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) {
+ if (serviceState != null && serviceState.mService == null
+ && shouldMaintainConnection(serviceState)) {
// We don't notify state change while reconnecting. It should remain disconnected.
return;
}
@@ -741,11 +780,11 @@ public final class TvInputManagerService extends SystemService {
try {
synchronized (mLock) {
UserState userState = getUserStateLocked(resolvedUserId);
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ TvInputInfo info = userState.inputMap.get(inputId).mInfo;
+ ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
if (serviceState == null) {
- serviceState = new ServiceState(
- userState.inputMap.get(inputId).mInfo, resolvedUserId);
- userState.serviceStateMap.put(inputId, serviceState);
+ serviceState = new ServiceState(info.getComponent(), resolvedUserId);
+ userState.serviceStateMap.put(info.getComponent(), serviceState);
}
// Send a null token immediately while reconnecting.
if (serviceState.mReconnecting == true) {
@@ -755,7 +794,7 @@ public final class TvInputManagerService extends SystemService {
// Create a new session token and a session state.
IBinder sessionToken = new Binder();
- SessionState sessionState = new SessionState(sessionToken, inputId, client,
+ SessionState sessionState = new SessionState(sessionToken, info, client,
seq, callingUid, resolvedUserId);
// Add them to the global session state map of the current user.
@@ -768,7 +807,7 @@ public final class TvInputManagerService extends SystemService {
createSessionInternalLocked(serviceState.mService, sessionToken,
resolvedUserId);
} else {
- updateServiceConnectionLocked(inputId, resolvedUserId);
+ updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
}
}
} finally {
@@ -861,8 +900,7 @@ public final class TvInputManagerService extends SystemService {
}
// Create a log entry and fill it later.
- String packageName = userState.inputMap.get(sessionState.mInputId).mInfo
- .getServiceInfo().packageName;
+ String packageName = sessionState.mInfo.getServiceInfo().packageName;
ContentValues values = new ContentValues();
values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
@@ -1025,6 +1063,7 @@ public final class TvInputManagerService extends SystemService {
@Override
public void registerTvInputInfo(TvInputInfo info, int deviceId) {
+ // TODO: Revisit to sort out deviceId ownership.
if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
!= PackageManager.PERMISSION_GRANTED) {
return;
@@ -1149,9 +1188,9 @@ public final class TvInputManagerService extends SystemService {
}
pw.decreaseIndent();
- pw.println("serviceStateMap: inputId -> ServiceState");
+ pw.println("serviceStateMap: ComponentName -> ServiceState");
pw.increaseIndent();
- for (Map.Entry<String, ServiceState> entry :
+ for (Map.Entry<ComponentName, ServiceState> entry :
userState.serviceStateMap.entrySet()) {
ServiceState service = entry.getValue();
pw.println(entry.getKey() + ": " + service);
@@ -1189,7 +1228,7 @@ public final class TvInputManagerService extends SystemService {
pw.println(entry.getKey() + ": " + session);
pw.increaseIndent();
- pw.println("mInputId: " + session.mInputId);
+ pw.println("mInfo: " + session.mInfo);
pw.println("mClient: " + session.mClient);
pw.println("mSeq: " + session.mSeq);
pw.println("mCallingUid: " + session.mCallingUid);
@@ -1239,8 +1278,8 @@ public final class TvInputManagerService extends SystemService {
new HashMap<IBinder, ClientState>();
// A mapping from the name of a TV input service to its state.
- private final Map<String, ServiceState> serviceStateMap =
- new HashMap<String, ServiceState>();
+ private final Map<ComponentName, ServiceState> serviceStateMap =
+ new HashMap<ComponentName, ServiceState>();
// A mapping from the token of a TV input session to its state.
private final Map<IBinder, SessionState> sessionStateMap =
@@ -1293,21 +1332,24 @@ public final class TvInputManagerService extends SystemService {
private final List<IBinder> mClientTokens = new ArrayList<IBinder>();
private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
private final ServiceConnection mConnection;
- private final TvInputInfo mTvInputInfo;
+ private final ComponentName mName;
+ private final boolean mIsHardware;
+ private final List<TvInputInfo> mInputList = new ArrayList<TvInputInfo>();
private ITvInputService mService;
private ServiceCallback mCallback;
private boolean mBound;
private boolean mReconnecting;
- private ServiceState(TvInputInfo inputInfo, int userId) {
- mTvInputInfo = inputInfo;
- mConnection = new InputServiceConnection(inputInfo, userId);
+ private ServiceState(ComponentName name, int userId) {
+ mName = name;
+ mConnection = new InputServiceConnection(name, userId);
+ mIsHardware = hasHardwarePermission(mContext.getPackageManager(), mName);
}
}
private final class SessionState implements IBinder.DeathRecipient {
- private final String mInputId;
+ private final TvInputInfo mInfo;
private final ITvInputClient mClient;
private final int mSeq;
private final int mCallingUid;
@@ -1316,10 +1358,10 @@ public final class TvInputManagerService extends SystemService {
private ITvInputSession mSession;
private Uri mLogUri;
- private SessionState(IBinder sessionToken, String inputId, ITvInputClient client, int seq,
+ private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client, int seq,
int callingUid, int userId) {
mSessionToken = sessionToken;
- mInputId = inputId;
+ mInfo = info;
mClient = client;
mSeq = seq;
mCallingUid = callingUid;
@@ -1343,28 +1385,29 @@ public final class TvInputManagerService extends SystemService {
}
private final class InputServiceConnection implements ServiceConnection {
- private final TvInputInfo mTvInputInfo;
+ private final ComponentName mName;
private final int mUserId;
- private InputServiceConnection(TvInputInfo inputInfo, int userId) {
+ private InputServiceConnection(ComponentName name, int userId) {
+ mName = name;
mUserId = userId;
- mTvInputInfo = inputInfo;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- String inputId = mTvInputInfo.getId();
if (DEBUG) {
- Slog.d(TAG, "onServiceConnected(inputId=" + inputId + ")");
+ Slog.d(TAG, "onServiceConnected(name=" + name + ")");
}
synchronized (mLock) {
+ List<TvInputHardwareInfo> hardwareInfoList =
+ mTvInputHardwareManager.getHardwareList();
UserState userState = getUserStateLocked(mUserId);
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ ServiceState serviceState = userState.serviceStateMap.get(mName);
serviceState.mService = ITvInputService.Stub.asInterface(service);
// Register a callback, if we need to.
- if (!serviceState.mClientTokens.isEmpty() && serviceState.mCallback == null) {
- serviceState.mCallback = new ServiceCallback(mTvInputInfo.getId(), mUserId);
+ if (serviceState.mIsHardware && serviceState.mCallback == null) {
+ serviceState.mCallback = new ServiceCallback(mName, mUserId);
try {
serviceState.mService.registerCallback(serviceState.mCallback);
} catch (RemoteException e) {
@@ -1377,10 +1420,24 @@ public final class TvInputManagerService extends SystemService {
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);
+ for (TvInputState inputState : userState.inputMap.values()) {
+ if (inputState.mInfo.getComponent().equals(name)
+ && inputState.mState != INPUT_STATE_DISCONNECTED) {
+ notifyStateChangedLocked(userState, inputState.mInfo.getId(),
+ inputState.mState, null);
+ }
+ }
+
+ if (serviceState.mIsHardware) {
+ for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
+ try {
+ serviceState.mService.notifyHardwareAdded(hardwareInfo);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHardwareAdded", e);
+ }
+ }
+
+ // TODO: Grab CEC devices and notify them to the service.
}
}
}
@@ -1388,15 +1445,15 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG) {
- Slog.d(TAG, "onServiceDisconnected(inputId=" + mTvInputInfo.getId() + ")");
+ Slog.d(TAG, "onServiceDisconnected(name=" + name + ")");
}
- if (!mTvInputInfo.getComponent().equals(name)) {
+ if (!mName.equals(name)) {
throw new IllegalArgumentException("Mismatched ComponentName: "
- + mTvInputInfo.getComponent() + " (expected), " + name + " (actual).");
+ + mName + " (expected), " + name + " (actual).");
}
synchronized (mLock) {
UserState userState = getUserStateLocked(mUserId);
- ServiceState serviceState = userState.serviceStateMap.get(mTvInputInfo.getId());
+ ServiceState serviceState = userState.serviceStateMap.get(mName);
if (serviceState != null) {
serviceState.mReconnecting = true;
serviceState.mBound = false;
@@ -1409,35 +1466,72 @@ public final class TvInputManagerService extends SystemService {
if (sessionState.mSession == null) {
removeSessionStateLocked(sessionToken, sessionState.mUserId);
sendSessionTokenToClientLocked(sessionState.mClient,
- sessionState.mInputId, null, null, sessionState.mSeq);
+ sessionState.mInfo.getId(), null, null, sessionState.mSeq);
}
}
- notifyStateChangedLocked(userState, mTvInputInfo.getId(),
- INPUT_STATE_DISCONNECTED, null);
- updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
+ for (TvInputState inputState : userState.inputMap.values()) {
+ if (inputState.mInfo.getComponent().equals(name)) {
+ notifyStateChangedLocked(userState, inputState.mInfo.getId(),
+ INPUT_STATE_DISCONNECTED, null);
+ }
+ }
+ updateServiceConnectionLocked(mName, mUserId);
}
}
}
}
private final class ServiceCallback extends ITvInputServiceCallback.Stub {
- private final String mInputId;
+ private final ComponentName mName;
private final int mUserId;
- ServiceCallback(String inputId, int userId) {
- mInputId = inputId;
+ ServiceCallback(ComponentName name, int userId) {
+ mName = name;
mUserId = userId;
}
@Override
- public void onInputStateChanged(int state) {
- if (DEBUG) {
- Slog.d(TAG, "onInputStateChanged(inputId=" + mInputId + ", state="
- + state + ")");
+ public void addTvInput(TvInputInfo inputInfo) {
+ synchronized (mLock) {
+ if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "The caller does not have permission to add a TV input ("
+ + inputInfo + ").");
+ return;
+ }
+
+ ServiceState serviceState = getServiceStateLocked(mName, mUserId);
+ serviceState.mInputList.add(inputInfo);
+ buildTvInputListLocked(mUserId);
}
+ }
+
+ @Override
+ public void removeTvInput(String inputId) {
synchronized (mLock) {
- setStateLocked(mInputId, state, mUserId);
+ if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "The caller does not have permission to remove a TV input ("
+ + inputId + ").");
+ return;
+ }
+
+ ServiceState serviceState = getServiceStateLocked(mName, mUserId);
+ boolean removed = false;
+ for (Iterator<TvInputInfo> it = serviceState.mInputList.iterator();
+ it.hasNext(); ) {
+ if (it.next().getId().equals(inputId)) {
+ it.remove();
+ removed = true;
+ break;
+ }
+ }
+ if (removed) {
+ buildTvInputListLocked(mUserId);
+ } else {
+ Slog.e(TAG, "TvInputInfo with inputId=" + inputId + " not found.");
+ }
}
}
}
@@ -1586,11 +1680,58 @@ public final class TvInputManagerService extends SystemService {
}
}
- final class Client {
- public void setState(String inputId, int state) {
+ final class HardwareListener implements TvInputHardwareManager.Listener {
+ @Override
+ public void onStateChanged(String inputId, int state) {
synchronized (mLock) {
setStateLocked(inputId, state, mCurrentUserId);
}
}
+
+ @Override
+ public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(mCurrentUserId);
+ // Broadcast the event to all hardware inputs.
+ for (ServiceState serviceState : userState.serviceStateMap.values()) {
+ if (!serviceState.mIsHardware || serviceState.mService == null) continue;
+ try {
+ serviceState.mService.notifyHardwareAdded(info);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHardwareAdded", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onHardwareDeviceRemoved(int deviceId) {
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(mCurrentUserId);
+ // Broadcast the event to all hardware inputs.
+ for (ServiceState serviceState : userState.serviceStateMap.values()) {
+ if (!serviceState.mIsHardware || serviceState.mService == null) continue;
+ try {
+ serviceState.mService.notifyHardwareRemoved(deviceId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHardwareRemoved", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice) {
+ synchronized (mLock) {
+ // TODO
+ }
+ }
+
+ @Override
+ public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice) {
+ synchronized (mLock) {
+ // TODO
+ }
+ }
}
}