Merge "TIAF: Handle multi-user cases in TvIAppManagerService"

This commit is contained in:
Shubang Lu
2021-11-22 06:40:38 +00:00
committed by Android (Google) Code Review
4 changed files with 214 additions and 9 deletions

View File

@@ -16,6 +16,7 @@
package android.media.tv.interactive;
import android.media.tv.interactive.ITvIAppServiceCallback;
import android.media.tv.interactive.ITvIAppSessionCallback;
/**
@@ -24,5 +25,7 @@ import android.media.tv.interactive.ITvIAppSessionCallback;
* @hide
*/
oneway interface ITvIAppService {
void registerCallback(in ITvIAppServiceCallback callback);
void unregisterCallback(in ITvIAppServiceCallback callback);
void createSession(in ITvIAppSessionCallback callback, in String iAppServiceId, int type);
}

View File

@@ -26,6 +26,7 @@ import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import android.view.KeyEvent;
@@ -62,11 +63,26 @@ public abstract class TvIAppService extends Service {
public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
private final Handler mServiceHandler = new ServiceHandler();
private final RemoteCallbackList<ITvIAppServiceCallback> mCallbacks =
new RemoteCallbackList<>();
/** @hide */
@Override
public final IBinder onBind(Intent intent) {
ITvIAppService.Stub tvIAppServiceBinder = new ITvIAppService.Stub() {
@Override
public void registerCallback(ITvIAppServiceCallback cb) {
if (cb != null) {
mCallbacks.register(cb);
}
}
@Override
public void unregisterCallback(ITvIAppServiceCallback cb) {
if (cb != null) {
mCallbacks.unregister(cb);
}
}
@Override
public void createSession(ITvIAppSessionCallback cb, String iAppServiceId, int type) {
@@ -137,7 +153,7 @@ public abstract class TvIAppService extends Service {
* Called when the application sets the surface.
*
* <p>The TV IApp service should render interactive app UI onto the given surface. When
* called with {@code null}, the input service should immediately free any references to the
* called with {@code null}, the IApp service should immediately free any references to the
* currently set surface and stop using it.
*
* @param surface The surface to be used for interactive app UI rendering. Can be

View File

@@ -470,14 +470,16 @@ public final class TvInputManagerService extends SystemService {
}
private void stopUser(int userId) {
if (userId == mCurrentUserId) {
switchUser(ActivityManager.getCurrentUser());
return;
}
synchronized (mLock) {
if (userId == mCurrentUserId) {
switchUser(ActivityManager.getCurrentUser());
return;
}
releaseSessionOfUserLocked(userId);
unbindServiceOfUserLocked(userId);
mRunningProfiles.remove(userId);
releaseSessionOfUserLocked(userId);
unbindServiceOfUserLocked(userId);
mRunningProfiles.remove(userId);
}
}
private void startProfileLocked(int userId) {

View File

@@ -27,6 +27,7 @@ import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.media.tv.interactive.ITvIAppClient;
import android.media.tv.interactive.ITvIAppManager;
import android.media.tv.interactive.ITvIAppManagerCallback;
@@ -42,6 +43,7 @@ import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
@@ -58,10 +60,12 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class provides a system service that manages interactive TV applications.
*/
@@ -81,6 +85,8 @@ public class TvIAppManagerService extends SystemService {
@GuardedBy("mLock")
private final SparseArray<UserState> mUserStates = new SparseArray<>();
private final UserManager mUserManager;
/**
* Initializes the system service.
* <p>
@@ -93,6 +99,7 @@ public class TvIAppManagerService extends SystemService {
public TvIAppManagerService(Context context) {
super(context);
mContext = context;
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
}
@GuardedBy("mLock")
@@ -330,11 +337,188 @@ public class TvIAppManagerService extends SystemService {
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// TODO: handle switch / start / stop user
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));
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
startUser(userId);
} else if (Intent.ACTION_USER_STOPPED.equals(action)) {
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
stopUser(userId);
}
}
}, UserHandle.ALL, intentFilter, null, null);
}
private void switchUser(int userId) {
synchronized (mLock) {
if (mCurrentUserId == userId) {
return;
}
UserInfo userInfo = mUserManager.getUserInfo(userId);
if (userInfo.isProfile()) {
Slog.w(TAG, "cannot switch to a profile!");
return;
}
for (int runningId : mRunningProfiles) {
releaseSessionOfUserLocked(runningId);
unbindServiceOfUserLocked(runningId);
}
mRunningProfiles.clear();
releaseSessionOfUserLocked(mCurrentUserId);
unbindServiceOfUserLocked(mCurrentUserId);
mCurrentUserId = userId;
buildTvIAppServiceListLocked(userId, null);
}
}
private void removeUser(int userId) {
synchronized (mLock) {
UserState userState = getUserStateLocked(userId);
if (userState == null) {
return;
}
// Release all created sessions.
for (SessionState state : userState.mSessionStateMap.values()) {
if (state.mSession != null) {
try {
state.mSession.release();
} catch (RemoteException e) {
Slog.e(TAG, "error in release", e);
}
}
}
userState.mSessionStateMap.clear();
// Unregister all callbacks and unbind all services.
for (ServiceState serviceState : userState.mServiceStateMap.values()) {
if (serviceState.mService != null) {
if (serviceState.mCallback != null) {
try {
serviceState.mService.unregisterCallback(serviceState.mCallback);
} catch (RemoteException e) {
Slog.e(TAG, "error in unregisterCallback", e);
}
}
mContext.unbindService(serviceState.mConnection);
}
}
userState.mServiceStateMap.clear();
// Clear everything else.
userState.mIAppMap.clear();
userState.mPackageSet.clear();
userState.mClientStateMap.clear();
userState.mCallbacks.kill();
mRunningProfiles.remove(userId);
mUserStates.remove(userId);
if (userId == mCurrentUserId) {
switchUser(UserHandle.USER_SYSTEM);
}
}
}
private void startUser(int userId) {
synchronized (mLock) {
if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) {
// user already started
return;
}
UserInfo userInfo = mUserManager.getUserInfo(userId);
UserInfo parentInfo = mUserManager.getProfileParent(userId);
if (userInfo.isProfile()
&& parentInfo != null
&& parentInfo.id == mCurrentUserId) {
// only the children of the current user can be started in background
startProfileLocked(userId);
}
}
}
private void stopUser(int userId) {
synchronized (mLock) {
if (userId == mCurrentUserId) {
switchUser(ActivityManager.getCurrentUser());
return;
}
releaseSessionOfUserLocked(userId);
unbindServiceOfUserLocked(userId);
mRunningProfiles.remove(userId);
}
}
@GuardedBy("mLock")
private void startProfileLocked(int userId) {
mRunningProfiles.add(userId);
buildTvIAppServiceListLocked(userId, null);
}
@GuardedBy("mLock")
private void releaseSessionOfUserLocked(int userId) {
UserState userState = getUserStateLocked(userId);
if (userState == null) {
return;
}
List<SessionState> sessionStatesToRelease = new ArrayList<>();
for (SessionState sessionState : userState.mSessionStateMap.values()) {
if (sessionState.mSession != null) {
sessionStatesToRelease.add(sessionState);
}
}
for (SessionState sessionState : sessionStatesToRelease) {
try {
sessionState.mSession.release();
} catch (RemoteException e) {
Slog.e(TAG, "error in release", e);
}
clearSessionAndNotifyClientLocked(sessionState);
}
}
@GuardedBy("mLock")
private void unbindServiceOfUserLocked(int userId) {
UserState userState = getUserStateLocked(userId);
if (userState == null) {
return;
}
for (Iterator<ComponentName> it = userState.mServiceStateMap.keySet().iterator();
it.hasNext(); ) {
ComponentName component = it.next();
ServiceState serviceState = userState.mServiceStateMap.get(component);
if (serviceState != null && serviceState.mSessionTokens.isEmpty()) {
if (serviceState.mCallback != null) {
try {
serviceState.mService.unregisterCallback(serviceState.mCallback);
} catch (RemoteException e) {
Slog.e(TAG, "error in unregisterCallback", e);
}
}
mContext.unbindService(serviceState.mConnection);
it.remove();
}
}
}
@GuardedBy("mLock")
private void clearSessionAndNotifyClientLocked(SessionState state) {
if (state.mClient != null) {
try {
state.mClient.onSessionReleased(state.mSeq);
} catch (RemoteException e) {
Slog.e(TAG, "error in onSessionReleased", e);
}
}
removeSessionStateLocked(state.mSessionToken, state.mUserId);
}
private SessionState getSessionState(IBinder sessionToken) {
// TODO: implement user state and get session from it.
return null;