TIF: Change the isForeground API to isVisible and isMainSession

isForeground is not a good approach to indentify current channel info

And add a permission for tuned info.

Bug: 180482268
Test: atest CtsPermission2TestCases
Test: atest TvInputManagerTest#testGetCurrentTunedInfos
Change-Id: Ib1c1f2da719336ae856684e843b06f8b9b442723
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index adbf18f..eb7d1c2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -19,6 +19,7 @@
     field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
     field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
     field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
+    field public static final String ACCESS_TUNED_INFO = "android.permission.ACCESS_TUNED_INFO";
     field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
     field public static final String ACCESS_TV_TUNER = "android.permission.ACCESS_TV_TUNER";
     field public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE";
@@ -5635,8 +5636,9 @@
     method public int getAppType();
     method @Nullable public android.net.Uri getChannelUri();
     method @NonNull public String getInputId();
-    method public boolean isForeground();
+    method public boolean isMainSession();
     method public boolean isRecordingSession();
+    method public boolean isVisible();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int APP_TAG_SELF = 0; // 0x0
     field public static final int APP_TYPE_NON_SYSTEM = 3; // 0x3
@@ -5760,7 +5762,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void addBlockedRating(@NonNull android.media.tv.TvContentRating);
     method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig);
     method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String);
-    method @NonNull @RequiresPermission("com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS") public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos();
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos();
     method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
     method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public java.util.List<android.media.tv.TvInputHardwareInfo> getHardwareList();
     method @RequiresPermission(android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS) public java.util.List<android.media.tv.TvContentRatingSystemInfo> getTvContentRatingSystemList();
@@ -5787,7 +5789,7 @@
   }
 
   public abstract static class TvInputManager.TvInputCallback {
-    method public void onCurrentTunedInfosUpdated(@NonNull java.util.List<android.media.tv.TunedInfo>);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public void onCurrentTunedInfosUpdated(@NonNull java.util.List<android.media.tv.TunedInfo>);
   }
 
   public abstract class TvInputService extends android.app.Service {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9d65e71..83a3c89 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5734,6 +5734,12 @@
     <permission android:name="android.permission.SET_CLIP_SOURCE"
                 android:protectionLevel="signature|recents" />
 
+    <!-- @SystemApi Allows an application to access TV tuned info
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.ACCESS_TUNED_INFO"
+        android:protectionLevel="signature|privileged|vendorPrivileged" />
+
     <!-- Allows an application to indicate via
          {@link android.content.pm.PackageInstaller.SessionParams#setRequireUserAction(boolean)}
          that user action should not be required for an app update.
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 5544eb4..5ad9b7e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -458,6 +458,7 @@
         <permission name="android.permission.ACCESS_TV_TUNER" />
         <permission name="android.permission.TUNER_RESOURCE_ACCESS" />
         <!-- Permissions required for CTS test - TVInputManagerTest -->
+        <permission name="android.permission.ACCESS_TUNED_INFO" />
         <permission name="android.permission.TV_INPUT_HARDWARE" />
         <!-- Permission required for CTS test - PrivilegedLocationPermissionTest -->
         <permission name="android.permission.LOCATION_HARDWARE" />
diff --git a/media/java/android/media/tv/TunedInfo.java b/media/java/android/media/tv/TunedInfo.java
index 6199c89..20acefa 100644
--- a/media/java/android/media/tv/TunedInfo.java
+++ b/media/java/android/media/tv/TunedInfo.java
@@ -25,6 +25,7 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.Surface;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -92,18 +93,20 @@
     private final String mInputId;
     @Nullable private final Uri mChannelUri;
     private final boolean mIsRecordingSession;
-    private final boolean mIsForeground;
+    private final boolean mIsVisible;
+    private final boolean mIsMainSession;
     @AppType private final int mAppType;
     private final int mAppTag;
 
     /** @hide */
     public TunedInfo(
             String inputId, @Nullable Uri channelUri, boolean isRecordingSession,
-            boolean isForeground, @AppType int appType, int appTag) {
+            boolean isVisible, boolean isMainSession, @AppType int appType, int appTag) {
         mInputId = inputId;
         mChannelUri = channelUri;
         mIsRecordingSession = isRecordingSession;
-        mIsForeground = isForeground;
+        mIsVisible = isVisible;
+        mIsMainSession = isMainSession;
         mAppType = appType;
         mAppTag = appTag;
     }
@@ -114,7 +117,8 @@
         String uriString = source.readString();
         mChannelUri = uriString == null ? null : Uri.parse(uriString);
         mIsRecordingSession = (source.readInt() == 1);
-        mIsForeground = (source.readInt() == 1);
+        mIsVisible = (source.readInt() == 1);
+        mIsMainSession = (source.readInt() == 1);
         mAppType = source.readInt();
         mAppTag = source.readInt();
     }
@@ -145,11 +149,23 @@
     }
 
     /**
-     * Returns {@code true} if the application is a foreground application.
-     * @see android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+     * Returns {@code true} if the corresponding session is visible.
+     * <p>The system checks whether the {@link Surface} of the session is {@code null} or not. When
+     * it becomes invisible, the surface is destroyed and set to null.
+     * @see TvInputService.Session#onSetSurface(Surface)
+     * @see android.view.SurfaceView#notifySurfaceDestroyed
      */
-    public boolean isForeground() {
-        return mIsForeground;
+    public boolean isVisible() {
+        return mIsVisible;
+    }
+
+    /**
+     * Returns {@code true} if the corresponding session is set as main session.
+     * @see TvView#setMain
+     * @see TvInputService.Session#onSetMain
+     */
+    public boolean isMainSession() {
+        return mIsMainSession;
     }
 
     /**
@@ -180,7 +196,8 @@
         String uriString = mChannelUri == null ? null : mChannelUri.toString();
         dest.writeString(uriString);
         dest.writeInt(mIsRecordingSession ? 1 : 0);
-        dest.writeInt(mIsForeground ? 1 : 0);
+        dest.writeInt(mIsVisible ? 1 : 0);
+        dest.writeInt(mIsMainSession ? 1 : 0);
         dest.writeInt(mAppType);
         dest.writeInt(mAppTag);
     }
@@ -190,7 +207,8 @@
         return "inputID=" + mInputId
                 + ";channelUri=" + mChannelUri
                 + ";isRecording=" + mIsRecordingSession
-                + ";isForeground=" + mIsForeground
+                + ";isVisible=" + mIsVisible
+                + ";isMainSession=" + mIsMainSession
                 + ";appType=" + mAppType
                 + ";appTag=" + mAppTag;
     }
@@ -206,7 +224,8 @@
         return TextUtils.equals(mInputId, other.getInputId())
                 && Objects.equals(mChannelUri, other.mChannelUri)
                 && mIsRecordingSession == other.mIsRecordingSession
-                && mIsForeground == other.mIsForeground
+                && mIsVisible == other.mIsVisible
+                && mIsMainSession == other.mIsMainSession
                 && mAppType == other.mAppType
                 && mAppTag == other.mAppTag;
     }
@@ -214,6 +233,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(
-                mInputId, mChannelUri, mIsRecordingSession, mIsForeground, mAppType, mAppTag);
+                mInputId, mChannelUri, mIsRecordingSession, mIsVisible, mIsMainSession, mAppType,
+                mAppTag);
     }
 }
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index c0185dc..34e4609 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -907,6 +907,7 @@
          * @hide
          */
         @SystemApi
+        @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO)
         public void onCurrentTunedInfosUpdated(@NonNull List<TunedInfo> tunedInfos) {
         }
     }
@@ -1989,7 +1990,7 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission("com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS")
+    @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO)
     @NonNull
     public List<TunedInfo> getCurrentTunedInfos() {
         try {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 0c47cf8..7aa160d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -426,6 +426,7 @@
     <uses-permission android:name="android.permission.ACCESS_SHARED_LIBRARIES" />
 
     <!-- Permissions required for CTS test - TVInputManagerTest -->
+    <uses-permission android:name="android.permission.ACCESS_TUNED_INFO" />
     <uses-permission android:name="android.permission.TV_INPUT_HARDWARE" />
 
     <!-- Permission needed for CTS test - PrivilegedLocationPermissionTest -->
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e728ab0..49170f3 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -24,7 +24,6 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -109,6 +108,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.regex.Matcher;
@@ -511,11 +511,21 @@
                 sessionStatesToRelease.add(sessionState);
             }
         }
+        boolean notifyInfoUpdated = false;
         for (SessionState sessionState : sessionStatesToRelease) {
             try {
                 sessionState.session.release();
+                sessionState.currentChannel = null;
+                if (sessionState.isCurrent) {
+                    sessionState.isCurrent = false;
+                    notifyInfoUpdated = true;
+                }
             } catch (RemoteException e) {
                 Slog.e(TAG, "error in release", e);
+            } finally {
+                if (notifyInfoUpdated) {
+                    notifyCurrentChannelInfosUpdatedLocked(userState);
+                }
             }
             clearSessionAndNotifyClientLocked(sessionState);
         }
@@ -576,12 +586,22 @@
                 return;
             }
             // Release all created sessions.
+            boolean notifyInfoUpdated = false;
             for (SessionState state : userState.sessionStateMap.values()) {
                 if (state.session != null) {
                     try {
                         state.session.release();
+                        state.currentChannel = null;
+                        if (state.isCurrent) {
+                            state.isCurrent = false;
+                            notifyInfoUpdated = true;
+                        }
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in release", e);
+                    } finally {
+                        if (notifyInfoUpdated) {
+                            notifyCurrentChannelInfosUpdatedLocked(userState);
+                        }
                     }
                 }
             }
@@ -826,9 +846,11 @@
                 sessionState.session.asBinder().unlinkToDeath(sessionState, 0);
                 sessionState.session.release();
             }
-            sessionState.isCurrent = false;
             sessionState.currentChannel = null;
-            notifyCurrentChannelInfosUpdatedLocked(userState);
+            if (sessionState.isCurrent) {
+                sessionState.isCurrent = false;
+                notifyCurrentChannelInfosUpdatedLocked(userState);
+            }
         } catch (RemoteException | SessionNotFoundException e) {
             Slog.e(TAG, "error in releaseSession", e);
         } finally {
@@ -898,6 +920,11 @@
             }
             ITvInputSession session = getSessionLocked(sessionState);
             session.setMain(isMain);
+            if (sessionState.isMainSession != isMain) {
+                UserState userState = getUserStateLocked(userId);
+                sessionState.isMainSession = isMain;
+                notifyCurrentChannelInfosUpdatedLocked(userState);
+            }
         } catch (RemoteException | SessionNotFoundException e) {
             Slog.e(TAG, "error in setMain", e);
         }
@@ -987,6 +1014,10 @@
             try {
                 ITvInputManagerCallback callback = userState.mCallbacks.getBroadcastItem(i);
                 Pair<Integer, Integer> pidUid = userState.callbackPidUidMap.get(callback);
+                if (mContext.checkPermission(android.Manifest.permission.ACCESS_TUNED_INFO,
+                        pidUid.first, pidUid.second) != PackageManager.PERMISSION_GRANTED) {
+                    continue;
+                }
                 List<TunedInfo> infos = getCurrentTunedInfosInternalLocked(
                         userState, pidUid.first, pidUid.second);
                 callback.onCurrentTunedInfosUpdated(infos);
@@ -1517,6 +1548,11 @@
                             getSessionLocked(sessionState.hardwareSessionToken,
                                     Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
                         }
+                        boolean isVisible = (surface == null);
+                        if (sessionState.isVisible != isVisible) {
+                            sessionState.isVisible = isVisible;
+                            notifyCurrentChannelInfosUpdatedLocked(userState);
+                        }
                     } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in setSurface", e);
                     }
@@ -1609,9 +1645,12 @@
                         UserState userState = getOrCreateUserStateLocked(resolvedUserId);
                         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
                                 userState);
-                        sessionState.isCurrent = true;
-                        sessionState.currentChannel = channelUri;
-                        notifyCurrentChannelInfosUpdatedLocked(userState);
+                        if (!sessionState.isCurrent
+                                || !Objects.equals(sessionState.currentChannel, channelUri)) {
+                            sessionState.isCurrent = true;
+                            sessionState.currentChannel = channelUri;
+                            notifyCurrentChannelInfosUpdatedLocked(userState);
+                        }
                         if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
                             // Do not log the watch history for passthrough inputs.
                             return;
@@ -2309,6 +2348,11 @@
 
         @Override
         public List<TunedInfo> getCurrentTunedInfos(@UserIdInt int userId) {
+            if (mContext.checkCallingPermission(android.Manifest.permission.ACCESS_TUNED_INFO)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException(
+                        "The caller does not have access tuned info permission");
+            }
             int callingPid = Binder.getCallingPid();
             int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
@@ -2524,7 +2568,8 @@
                         state.inputId,
                         watchedProgramsAccess ? state.currentChannel : null,
                         state.isRecordingSession,
-                        isForeground(state.callingPid),
+                        state.isVisible,
+                        state.isMainSession,
                         appType,
                         appTag));
             }
@@ -2532,23 +2577,6 @@
         return channelInfos;
     }
 
-    private boolean isForeground(int pid) {
-        if (mActivityManager == null) {
-            return false;
-        }
-        List<RunningAppProcessInfo> appProcesses = mActivityManager.getRunningAppProcesses();
-        if (appProcesses == null) {
-            return false;
-        }
-        for (RunningAppProcessInfo appProcess : appProcesses) {
-            if (appProcess.pid == pid
-                    && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private boolean hasAccessWatchedProgramsPermission(int callingPid, int callingUid) {
         return mContext.checkPermission(PERMISSION_ACCESS_WATCHED_PROGRAMS, callingPid, callingUid)
                 == PackageManager.PERMISSION_GRANTED;
@@ -2788,6 +2816,8 @@
 
         private boolean isCurrent = false;
         private Uri currentChannel = null;
+        private boolean isVisible = false;
+        private boolean isMainSession = false;
 
         private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
                 boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
@@ -3039,16 +3069,19 @@
                 if (mSessionState.session == null || mSessionState.client == null) {
                     return;
                 }
-                mSessionState.isCurrent = true;
-                mSessionState.currentChannel = channelUri;
-                UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
-                notifyCurrentChannelInfosUpdatedLocked(userState);
                 try {
                     // TODO: Consider adding this channel change in the watch log. When we do
                     // that, how we can protect the watch log from malicious tv inputs should
                     // be addressed. e.g. add a field which represents where the channel change
                     // originated from.
                     mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
+                    if (!mSessionState.isCurrent
+                            || !Objects.equals(mSessionState.currentChannel, channelUri)) {
+                        UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
+                        mSessionState.isCurrent = true;
+                        mSessionState.currentChannel = channelUri;
+                        notifyCurrentChannelInfosUpdatedLocked(userState);
+                    }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "error in onChannelRetuned", e);
                 }