TvInputManagerServicepublic final class TvInputManagerService extends com.android.server.SystemService This class provides a system service that manages television inputs. |
Fields Summary |
---|
private static final boolean | DEBUG | private static final String | TAG | private final android.content.Context | mContext | private final TvInputHardwareManager | mTvInputHardwareManager | private final android.content.ContentResolver | mContentResolver | private final Object | mLock | private int | mCurrentUserId | private final android.util.SparseArray | mUserStates | private final WatchLogHandler | mWatchLogHandler |
Constructors Summary |
---|
public TvInputManagerService(android.content.Context context)
super(context);
mContext = context;
mContentResolver = context.getContentResolver();
mWatchLogHandler = new WatchLogHandler(mContentResolver, IoThread.get().getLooper());
mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
synchronized (mLock) {
mUserStates.put(mCurrentUserId, new UserState(mContext, mCurrentUserId));
}
|
Methods Summary |
---|
private void | abortPendingCreateSessionRequestsLocked(com.android.server.tv.TvInputManagerService$ServiceState serviceState, java.lang.String inputId, int userId)
// Let clients know the create session requests are failed.
UserState userState = getUserStateLocked(userId);
List<SessionState> sessionsToAbort = new ArrayList<>();
for (IBinder sessionToken : serviceState.sessionTokens) {
SessionState sessionState = userState.sessionStateMap.get(sessionToken);
if (sessionState.session == null && (inputId == null
|| sessionState.info.getId().equals(inputId))) {
sessionsToAbort.add(sessionState);
}
}
for (SessionState sessionState : sessionsToAbort) {
removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
sendSessionTokenToClientLocked(sessionState.client,
sessionState.info.getId(), null, null, sessionState.seq);
}
updateServiceConnectionLocked(serviceState.component, userId);
| private void | buildTvContentRatingSystemListLocked(int userId)
UserState userState = getUserStateLocked(userId);
userState.contentRatingSystemList.clear();
final PackageManager pm = mContext.getPackageManager();
Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
for (ResolveInfo resolveInfo :
pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
ActivityInfo receiver = resolveInfo.activityInfo;
Bundle metaData = receiver.metaData;
if (metaData == null) {
continue;
}
int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
if (xmlResId == 0) {
Slog.w(TAG, "Missing meta-data '"
+ TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
+ receiver.packageName + "/" + receiver.name);
continue;
}
userState.contentRatingSystemList.add(
TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
receiver.applicationInfo));
}
| private void | buildTvInputListLocked(int userId, java.lang.String[] updatedPackages)
UserState userState = getUserStateLocked(userId);
userState.packageSet.clear();
if (DEBUG) Slog.d(TAG, "buildTvInputList");
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> services = pm.queryIntentServices(
new Intent(TvInputService.SERVICE_INTERFACE),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
for (ResolveInfo ri : services) {
ServiceInfo si = ri.serviceInfo;
if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
+ android.Manifest.permission.BIND_TV_INPUT);
continue;
}
ComponentName component = new ComponentName(si.packageName, si.name);
if (hasHardwarePermission(pm, component)) {
ServiceState serviceState = userState.serviceStateMap.get(component);
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(component, userId);
userState.serviceStateMap.put(component, serviceState);
updateServiceConnectionLocked(component, userId);
} else {
inputList.addAll(serviceState.inputList);
}
} else {
try {
inputList.add(TvInputInfo.createTvInputInfo(mContext, ri));
} catch (XmlPullParserException | IOException e) {
Slog.e(TAG, "failed to load TV input " + si.name, e);
continue;
}
}
userState.packageSet.add(si.packageName);
}
Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
for (TvInputInfo info : inputList) {
if (DEBUG) {
Slog.d(TAG, "add " + info.getId());
}
TvInputState state = userState.inputMap.get(info.getId());
if (state == null) {
state = new TvInputState();
}
state.info = info;
inputMap.put(info.getId(), state);
}
for (String inputId : inputMap.keySet()) {
if (!userState.inputMap.containsKey(inputId)) {
notifyInputAddedLocked(userState, inputId);
} else if (updatedPackages != null) {
// Notify the package updates
ComponentName component = inputMap.get(inputId).info.getComponent();
for (String updatedPackage : updatedPackages) {
if (component.getPackageName().equals(updatedPackage)) {
updateServiceConnectionLocked(component, userId);
notifyInputUpdatedLocked(userState, inputId);
break;
}
}
}
}
for (String inputId : userState.inputMap.keySet()) {
if (!inputMap.containsKey(inputId)) {
TvInputInfo info = userState.inputMap.get(inputId).info;
ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
if (serviceState != null) {
abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
}
notifyInputRemovedLocked(userState, inputId);
}
}
userState.inputMap.clear();
userState.inputMap = inputMap;
| private void | createSessionInternalLocked(android.media.tv.ITvInputService service, android.os.IBinder sessionToken, int userId)
UserState userState = getUserStateLocked(userId);
SessionState sessionState = userState.sessionStateMap.get(sessionToken);
if (DEBUG) {
Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.info.getId() + ")");
}
InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
// Set up a callback to send the session token.
ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
// Create a session. When failed, send a null token immediately.
try {
service.createSession(channels[1], callback, sessionState.info.getId());
} catch (RemoteException e) {
Slog.e(TAG, "error in createSession", e);
removeSessionStateLocked(sessionToken, userId);
sendSessionTokenToClientLocked(sessionState.client, sessionState.info.getId(), null,
null, sessionState.seq);
}
channels[1].dispose();
| private com.android.server.tv.TvInputManagerService$ServiceState | getServiceStateLocked(android.content.ComponentName component, int userId)
UserState userState = getUserStateLocked(userId);
ServiceState serviceState = userState.serviceStateMap.get(component);
if (serviceState == null) {
throw new IllegalStateException("Service state not found for " + component + " (userId="
+ userId + ")");
}
return serviceState;
| private android.media.tv.ITvInputSession | getSessionLocked(android.os.IBinder sessionToken, int callingUid, int userId)
return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
| private android.media.tv.ITvInputSession | getSessionLocked(com.android.server.tv.TvInputManagerService$SessionState sessionState)
ITvInputSession session = sessionState.session;
if (session == null) {
throw new IllegalStateException("Session not yet created for token "
+ sessionState.sessionToken);
}
return session;
| private com.android.server.tv.TvInputManagerService$SessionState | getSessionStateLocked(android.os.IBinder sessionToken, int callingUid, int userId)
UserState userState = getUserStateLocked(userId);
SessionState sessionState = userState.sessionStateMap.get(sessionToken);
if (sessionState == null) {
throw new SessionNotFoundException("Session state not found for token " + sessionToken);
}
// Only the application that requested this session or the system can access it.
if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
throw new SecurityException("Illegal access to the session with token " + sessionToken
+ " from uid " + callingUid);
}
return sessionState;
| private com.android.server.tv.TvInputManagerService$UserState | getUserStateLocked(int userId)
UserState userState = mUserStates.get(userId);
if (userState == null) {
throw new IllegalStateException("User state not found for user ID " + userId);
}
return userState;
| private static boolean | hasHardwarePermission(android.content.pm.PackageManager pm, android.content.ComponentName component)
return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
| private void | notifyInputAddedLocked(com.android.server.tv.TvInputManagerService$UserState userState, java.lang.String inputId)
if (DEBUG) {
Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
}
for (ITvInputManagerCallback callback : userState.callbackSet) {
try {
callback.onInputAdded(inputId);
} catch (RemoteException e) {
Slog.e(TAG, "failed to report added input to callback", e);
}
}
| private void | notifyInputRemovedLocked(com.android.server.tv.TvInputManagerService$UserState userState, java.lang.String inputId)
if (DEBUG) {
Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
}
for (ITvInputManagerCallback callback : userState.callbackSet) {
try {
callback.onInputRemoved(inputId);
} catch (RemoteException e) {
Slog.e(TAG, "failed to report removed input to callback", e);
}
}
| private void | notifyInputStateChangedLocked(com.android.server.tv.TvInputManagerService$UserState userState, java.lang.String inputId, int state, android.media.tv.ITvInputManagerCallback targetCallback)
if (DEBUG) {
Slog.d(TAG, "notifyInputStateChangedLocked(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", e);
}
}
} else {
try {
targetCallback.onInputStateChanged(inputId, state);
} catch (RemoteException e) {
Slog.e(TAG, "failed to report state change to callback", e);
}
}
| private void | notifyInputUpdatedLocked(com.android.server.tv.TvInputManagerService$UserState userState, java.lang.String inputId)
if (DEBUG) {
Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
}
for (ITvInputManagerCallback callback : userState.callbackSet) {
try {
callback.onInputUpdated(inputId);
} catch (RemoteException e) {
Slog.e(TAG, "failed to report updated input to callback", e);
}
}
| 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, null);
buildTvContentRatingSystemListLocked(mCurrentUserId);
}
}
mTvInputHardwareManager.onBootPhase(phase);
| public void | onStart()
publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
| private void | registerBroadcastReceivers()
PackageMonitor monitor = new PackageMonitor() {
private void buildTvInputList(String[] packages) {
synchronized (mLock) {
buildTvInputListLocked(getChangingUserId(), packages);
buildTvContentRatingSystemListLocked(getChangingUserId());
}
}
@Override
public void onPackageUpdateFinished(String packageName, int uid) {
if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
// This callback is invoked when the TV input is reinstalled.
// In this case, isReplacing() always returns true.
buildTvInputList(new String[] { packageName });
}
@Override
public void onPackagesAvailable(String[] packages) {
if (DEBUG) {
Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
}
// This callback is invoked when the media on which some packages exist become
// available.
if (isReplacing()) {
buildTvInputList(packages);
}
}
@Override
public void onPackagesUnavailable(String[] packages) {
// This callback is invoked when the media on which some packages exist become
// unavailable.
if (DEBUG) {
Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
+ ")");
}
if (isReplacing()) {
buildTvInputList(packages);
}
}
@Override
public void onSomePackagesChanged() {
// TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
// the TV inputs.
if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
if (isReplacing()) {
if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
// When the package is updated, buildTvInputListLocked is called in other
// methods instead.
return;
}
buildTvInputList(null);
}
@Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
UserState userState = getUserStateLocked(getChangingUserId());
if (!userState.packageSet.contains(packageName)) {
// Not a TV input package.
return;
}
}
ArrayList<ContentProviderOperation> operations =
new ArrayList<ContentProviderOperation>();
String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
String[] selectionArgs = { packageName };
operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
.withSelection(selection, selectionArgs).build());
operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
.withSelection(selection, selectionArgs).build());
operations.add(ContentProviderOperation
.newDelete(TvContract.WatchedPrograms.CONTENT_URI)
.withSelection(selection, selectionArgs).build());
ContentProviderResult[] results = null;
try {
results = mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
} catch (RemoteException | OperationApplicationException e) {
Slog.e(TAG, "error in applyBatch", e);
}
if (DEBUG) {
Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
+ ")");
Slog.d(TAG, "results=" + results);
}
}
};
monitor.register(mContext, null, UserHandle.ALL, true);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_USER_SWITCHED.equals(action)) {
switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
}
}
}, UserHandle.ALL, intentFilter, null, null);
| private void | releaseSessionLocked(android.os.IBinder sessionToken, int callingUid, int userId)
SessionState sessionState = null;
try {
sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
if (sessionState.session != null) {
UserState userState = getUserStateLocked(userId);
if (sessionToken == userState.mainSessionToken) {
setMainLocked(sessionToken, false, callingUid, userId);
}
sessionState.session.release();
}
} catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in releaseSession", e);
} finally {
if (sessionState != null) {
sessionState.session = null;
}
}
removeSessionStateLocked(sessionToken, userId);
| private void | removeSessionStateLocked(android.os.IBinder sessionToken, int userId)
UserState userState = getUserStateLocked(userId);
if (sessionToken == userState.mainSessionToken) {
if (DEBUG) {
Slog.d(TAG, "mainSessionToken=null");
}
userState.mainSessionToken = null;
}
// Remove the session state from the global session state map of the current user.
SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
if (sessionState == null) {
return;
}
// Also remove the session token from the session token list of the current client and
// service.
ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
if (clientState != null) {
clientState.sessionTokens.remove(sessionToken);
if (clientState.isEmpty()) {
userState.clientStateMap.remove(sessionState.client.asBinder());
}
}
TvInputInfo info = sessionState.info;
if (info != null) {
ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
if (serviceState != null) {
serviceState.sessionTokens.remove(sessionToken);
}
}
updateServiceConnectionLocked(sessionState.info.getComponent(), userId);
// Log the end of watch.
SomeArgs args = SomeArgs.obtain();
args.arg1 = sessionToken;
args.arg2 = System.currentTimeMillis();
mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
| private void | removeUser(int userId)
synchronized (mLock) {
UserState userState = mUserStates.get(userId);
if (userState == null) {
return;
}
// Release created sessions.
for (SessionState state : userState.sessionStateMap.values()) {
if (state.session != null) {
try {
state.session.release();
} catch (RemoteException e) {
Slog.e(TAG, "error in release", e);
}
}
}
userState.sessionStateMap.clear();
// Unregister all callbacks and unbind all services.
for (ServiceState serviceState : userState.serviceStateMap.values()) {
if (serviceState.callback != null) {
try {
serviceState.service.unregisterCallback(serviceState.callback);
} catch (RemoteException e) {
Slog.e(TAG, "error in unregisterCallback", e);
}
}
mContext.unbindService(serviceState.connection);
}
userState.serviceStateMap.clear();
// Clear everything else.
userState.inputMap.clear();
userState.packageSet.clear();
userState.contentRatingSystemList.clear();
userState.clientStateMap.clear();
userState.callbackSet.clear();
userState.mainSessionToken = null;
mUserStates.remove(userId);
}
| private int | resolveCallingUserId(int callingPid, int callingUid, int requestedUserId, java.lang.String methodName)
return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
false, methodName, null);
| private void | sendSessionTokenToClientLocked(android.media.tv.ITvInputClient client, java.lang.String inputId, android.os.IBinder sessionToken, android.view.InputChannel channel, int seq)
try {
client.onSessionCreated(inputId, sessionToken, channel, seq);
} catch (RemoteException e) {
Slog.e(TAG, "error in onSessionCreated", e);
}
| private void | setMainLocked(android.os.IBinder sessionToken, boolean isMain, int callingUid, int userId)
try {
SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
if (sessionState.hardwareSessionToken != null) {
sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
Process.SYSTEM_UID, userId);
}
ServiceState serviceState = getServiceStateLocked(sessionState.info.getComponent(), userId);
if (!serviceState.isHardware) {
return;
}
ITvInputSession session = getSessionLocked(sessionState);
session.setMain(isMain);
} catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in setMain", e);
}
| private void | setStateLocked(java.lang.String inputId, int state, int userId)
UserState userState = getUserStateLocked(userId);
TvInputState inputState = userState.inputMap.get(inputId);
ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
int oldState = inputState.state;
inputState.state = state;
if (serviceState != null && serviceState.service == null
&& shouldMaintainConnection(serviceState)) {
// We don't notify state change while reconnecting. It should remain disconnected.
return;
}
if (oldState != state) {
notifyInputStateChangedLocked(userState, inputId, state, null);
}
| private static boolean | shouldMaintainConnection(com.android.server.tv.TvInputManagerService$ServiceState serviceState)
return !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
// TODO: Find a way to maintain connection to hardware TV input service only when necessary.
| private void | switchUser(int userId)
synchronized (mLock) {
if (mCurrentUserId == userId) {
return;
}
// final int oldUserId = mCurrentUserId;
// TODO: Release services and sessions in the old user state, if needed.
mCurrentUserId = userId;
UserState userState = mUserStates.get(userId);
if (userState == null) {
userState = new UserState(mContext, userId);
}
mUserStates.put(userId, userState);
buildTvInputListLocked(userId, null);
buildTvContentRatingSystemListLocked(userId);
}
| private void | updateServiceConnectionLocked(android.content.ComponentName component, int userId)
UserState userState = getUserStateLocked(userId);
ServiceState serviceState = userState.serviceStateMap.get(component);
if (serviceState == null) {
return;
}
if (serviceState.reconnecting) {
if (!serviceState.sessionTokens.isEmpty()) {
// wait until all the sessions are removed.
return;
}
serviceState.reconnecting = false;
}
boolean maintainConnection = shouldMaintainConnection(serviceState);
if (serviceState.service == 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.bound) {
// We have already bound to the service so we don't try to bind again until after we
// unbind later on.
return;
}
if (DEBUG) {
Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
}
Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
serviceState.bound = mContext.bindServiceAsUser(
i, serviceState.connection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
} else if (serviceState.service != 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(service=" + component + ")");
}
mContext.unbindService(serviceState.connection);
userState.serviceStateMap.remove(component);
}
|
|