From 7528f96173c008c69d22c0cdfa2daf6ca747ab60 Mon Sep 17 00:00:00 2001 From: shubang Date: Wed, 10 Nov 2021 00:51:18 -0800 Subject: [PATCH] TIAF: Handle multi-user cases in TvIAppManagerService Bug: 205796541 Test: mmm Change-Id: Ib3e14247ff488a8ff46050adc7bed6aa2c3f87c0 --- .../media/tv/interactive/ITvIAppService.aidl | 3 + .../media/tv/interactive/TvIAppService.java | 18 +- .../server/tv/TvInputManagerService.java | 16 +- .../tv/interactive/TvIAppManagerService.java | 186 +++++++++++++++++- 4 files changed, 214 insertions(+), 9 deletions(-) diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvIAppService.aidl index c4f82ebc830fd..2f165f0de7e5b 100644 --- a/media/java/android/media/tv/interactive/ITvIAppService.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppService.aidl @@ -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); } \ No newline at end of file diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java index 8863729bfab3f..78b8173e0af78 100644 --- a/media/java/android/media/tv/interactive/TvIAppService.java +++ b/media/java/android/media/tv/interactive/TvIAppService.java @@ -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 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. * *

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 diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index e9d5ad6f6d37d..e69acc3071fbe 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -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) { diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java index 4d1ff9eb3f810..dfb07523d8304 100644 --- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java @@ -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 mUserStates = new SparseArray<>(); + private final UserManager mUserManager; + /** * Initializes the system service. *

@@ -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 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 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;