Merge "TIF: Keep recording sessions while changing user" into nyc-dev
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 7bf0cb2..52baa60 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -415,7 +415,38 @@
             if (mCurrentUserId == userId) {
                 return;
             }
-            clearSessionAndServiceStatesLocked(mUserStates.get(mCurrentUserId));
+            UserState userState = mUserStates.get(mCurrentUserId);
+            List<SessionState> sessionStatesToRelease = new ArrayList<>();
+            for (SessionState sessionState : userState.sessionStateMap.values()) {
+                if (sessionState.session != null && !sessionState.isRecordingSession) {
+                    sessionStatesToRelease.add(sessionState);
+                }
+            }
+            for (SessionState sessionState : sessionStatesToRelease) {
+                try {
+                    sessionState.session.release();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in release", e);
+                }
+                clearSessionAndNotifyClientLocked(sessionState);
+            }
+
+            for (Iterator<ComponentName> it = userState.serviceStateMap.keySet().iterator();
+                 it.hasNext(); ) {
+                ComponentName component = it.next();
+                ServiceState serviceState = userState.serviceStateMap.get(component);
+                if (serviceState != null && serviceState.sessionTokens.isEmpty()) {
+                    if (serviceState.callback != null) {
+                        try {
+                            serviceState.service.unregisterCallback(serviceState.callback);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in unregisterCallback", e);
+                        }
+                    }
+                    mContext.unbindService(serviceState.connection);
+                    it.remove();
+                }
+            }
 
             mCurrentUserId = userId;
             getOrCreateUserStateLocked(userId);
@@ -426,13 +457,61 @@
         }
     }
 
+    private void clearSessionAndNotifyClientLocked(SessionState state) {
+        if (state.client != null) {
+            try {
+                state.client.onSessionReleased(state.seq);
+            } catch(RemoteException e) {
+                Slog.e(TAG, "error in onSessionReleased", e);
+            }
+        }
+        // If there are any other sessions based on this session, they should be released.
+        UserState userState = getOrCreateUserStateLocked(state.userId);
+        for (SessionState sessionState : userState.sessionStateMap.values()) {
+            if (state.sessionToken == sessionState.hardwareSessionToken) {
+                releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID, state.userId);
+                try {
+                    sessionState.client.onSessionReleased(sessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onSessionReleased", e);
+                }
+            }
+        }
+        removeSessionStateLocked(state.sessionToken, state.userId);
+    }
+
     private void removeUser(int userId) {
         synchronized (mLock) {
             UserState userState = mUserStates.get(userId);
             if (userState == null) {
                 return;
             }
-            clearSessionAndServiceStatesLocked(userState);
+            // Release all 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.service != null) {
+                    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();
@@ -446,35 +525,6 @@
         }
     }
 
-    private void clearSessionAndServiceStatesLocked(UserState userState) {
-        // 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.service != null) {
-                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();
-    }
-
     private ContentResolver getContentResolverForUser(int userId) {
         UserHandle user = new UserHandle(userId);
         Context context;
@@ -539,11 +589,6 @@
                 false, methodName, null);
     }
 
-    private static boolean shouldMaintainConnection(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 updateServiceConnectionLocked(ComponentName component, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
         ServiceState serviceState = userState.serviceStateMap.get(component);
@@ -557,8 +602,19 @@
             }
             serviceState.reconnecting = false;
         }
-        boolean maintainConnection = shouldMaintainConnection(serviceState);
-        if (serviceState.service == null && maintainConnection && userId == mCurrentUserId) {
+
+        boolean shouldBind;
+        if (userId == mCurrentUserId) {
+            shouldBind = !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
+        } else {
+            // For a non-current user,
+            // if sessionTokens is not empty, it contains recording sessions only
+            // because other sessions must have been removed while switching user
+            // and non-recording sessions are not created by createSession().
+            shouldBind = !serviceState.sessionTokens.isEmpty();
+        }
+
+        if (serviceState.service == null && shouldBind) {
             // 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) {
@@ -575,7 +631,7 @@
                     i, serviceState.connection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                     new UserHandle(userId));
-        } else if (serviceState.service != null && !maintainConnection) {
+        } else if (serviceState.service != null && !shouldBind) {
             // 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) {
@@ -811,7 +867,7 @@
         int oldState = inputState.state;
         inputState.state = state;
         if (serviceState != null && serviceState.service == null
-                && shouldMaintainConnection(serviceState)) {
+                && (!serviceState.sessionTokens.isEmpty() || serviceState.isHardware)) {
             // We don't notify state change while reconnecting. It should remain disconnected.
             return;
         }
@@ -1080,6 +1136,13 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
+                    if (userId != mCurrentUserId && !isRecordingSession) {
+                        // A non-recording session of a backgroud (non-current) user
+                        // should not be created.
+                        // Let the client get onConnectionFailed callback for this case.
+                        sendSessionTokenToClientLocked(client, inputId, null, null, seq);
+                        return;
+                    }
                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
                     TvInputState inputState = userState.inputMap.get(inputId);
                     if (inputState == null) {
@@ -2073,27 +2136,7 @@
         public void binderDied() {
             synchronized (mLock) {
                 session = null;
-                if (client != null) {
-                    try {
-                        client.onSessionReleased(seq);
-                    } catch(RemoteException e) {
-                        Slog.e(TAG, "error in onSessionReleased", e);
-                    }
-                }
-                // If there are any other sessions based on this session, they should be released.
-                UserState userState = getOrCreateUserStateLocked(userId);
-                for (SessionState sessionState : userState.sessionStateMap.values()) {
-                    if (sessionToken == sessionState.hardwareSessionToken) {
-                        releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID,
-                                userId);
-                        try {
-                            sessionState.client.onSessionReleased(sessionState.seq);
-                        } catch (RemoteException e) {
-                            Slog.e(TAG, "error in onSessionReleased", e);
-                        }
-                    }
-                }
-                removeSessionStateLocked(sessionToken, userId);
+                clearSessionAndNotifyClientLocked(this);
             }
         }
     }