Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7528f961 authored by shubang's avatar shubang
Browse files

TIAF: Handle multi-user cases in TvIAppManagerService

Bug: 205796541
Test: mmm
Change-Id: Ib3e14247ff488a8ff46050adc7bed6aa2c3f87c0
parent 083bd48e
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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
+17 −1
Original line number Diff line number Diff line
@@ -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
+9 −7
Original line number Diff line number Diff line
@@ -470,6 +470,7 @@ public final class TvInputManagerService extends SystemService {
    }

    private void stopUser(int userId) {
        synchronized (mLock) {
            if (userId == mCurrentUserId) {
                switchUser(ActivityManager.getCurrentUser());
                return;
@@ -479,6 +480,7 @@ public final class TvInputManagerService extends SystemService {
            unbindServiceOfUserLocked(userId);
            mRunningProfiles.remove(userId);
        }
    }

    private void startProfileLocked(int userId) {
        mRunningProfiles.add(userId);
+185 −1
Original line number Diff line number Diff line
@@ -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;