diff options
author | Wonsik Kim <wonsik@google.com> | 2014-07-18 02:19:12 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-07-17 21:11:29 +0000 |
commit | 61635036ac29012a45715effcf60b3c395f05f17 (patch) | |
tree | d7eb4a31cccf17cc2c98131f3ec5f22fe0810c27 | |
parent | ca58ddf7c82dd0857de0c3d49d7eb87a842ee4ce (diff) | |
parent | 187423c0bc4b27479bc8c23bd86969429094b296 (diff) | |
download | frameworks_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
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 + } + } } } |