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

Commit 47a260f8 authored by shubang's avatar shubang
Browse files

Add API getCurrentChannelInfos

Test: cuttlefish + sample tuner input
Change-Id: Ia13bebfb595ce1886c9aa540121f6836a404fd25
parent e174d038
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.media.tv.ITvInputClient;
import android.media.tv.ITvInputHardware;
import android.media.tv.ITvInputHardwareCallback;
import android.media.tv.ITvInputManagerCallback;
import android.media.tv.TvChannelInfo;
import android.media.tv.TvContentRatingSystemInfo;
import android.media.tv.TvInputHardwareInfo;
import android.media.tv.TvInputInfo;
@@ -88,6 +89,8 @@ interface ITvInputManager {
    void timeShiftSetPlaybackParams(in IBinder sessionToken, in PlaybackParams params, int userId);
    void timeShiftEnablePositionTracking(in IBinder sessionToken, boolean enable, int userId);

    List<TvChannelInfo> getTvCurrentChannelInfos(int userId);

    // For the recording session
    void startRecording(in IBinder sessionToken, in Uri programUri, in Bundle params, int userId);
    void stopRecording(in IBinder sessionToken, int userId);
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.media.tv;

parcelable TvChannelInfo;
+148 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.media.tv;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * @hide
 */
public final class TvChannelInfo implements Parcelable {
    static final String TAG = "TvChannelInfo";
    public static final int APP_TAG_SELF = 0;
    public static final int APP_TYPE_SELF = 1;
    public static final int APP_TYPE_SYSTEM = 2;
    public static final int APP_TYPE_NON_SYSTEM = 3;

    /** @hide */
    @IntDef(prefix = "APP_TYPE_", value = {APP_TYPE_SELF, APP_TYPE_SYSTEM, APP_TYPE_NON_SYSTEM})
    @Retention(RetentionPolicy.SOURCE)
    public @interface AppType {}

    public static final @NonNull Parcelable.Creator<TvChannelInfo> CREATOR =
            new Parcelable.Creator<TvChannelInfo>() {
                @Override
                public TvChannelInfo createFromParcel(Parcel source) {
                    try {
                        return new TvChannelInfo(source);
                    } catch (Exception e) {
                        Log.e(TAG, "Exception creating TvChannelInfo from parcel", e);
                        return null;
                    }
                }

                @Override
                public TvChannelInfo[] newArray(int size) {
                    return new TvChannelInfo[size];
                }
            };


    private final String mInputId;
    @Nullable private final Uri mChannelUri;
    private final boolean mIsRecordingSession;
    private final boolean mIsForeground;
    @AppType private final int mAppType;
    private final int mAppTag;

    public TvChannelInfo(
            String inputId, @Nullable Uri channelUri, boolean isRecordingSession,
            boolean isForeground, @AppType int appType, int appTag) {
        mInputId = inputId;
        mChannelUri = channelUri;
        mIsRecordingSession = isRecordingSession;
        mIsForeground = isForeground;
        mAppType = appType;
        mAppTag = appTag;
    }


    private TvChannelInfo(Parcel source) {
        mInputId = source.readString();
        String uriString = source.readString();
        mChannelUri = uriString == null ? null : Uri.parse(uriString);
        mIsRecordingSession = (source.readInt() == 1);
        mIsForeground = (source.readInt() == 1);
        mAppType = source.readInt();
        mAppTag = source.readInt();
    }

    public String getInputId() {
        return mInputId;
    }

    public Uri getChannelUri() {
        return mChannelUri;
    }

    public boolean isRecordingSession() {
        return mIsRecordingSession;
    }

    public boolean isForeground() {
        return mIsForeground;
    }

    /**
     * Gets app tag.
     * <p>App tag is used to differentiate one app from another.
     * {@link #APP_TAG_SELF} is for current app.
     */
    public int getAppTag() {
        return mAppTag;
    }

    @AppType
    public int getAppType() {
        return mAppType;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mInputId);
        String uriString = mChannelUri == null ? null : mChannelUri.toString();
        dest.writeString(uriString);
        dest.writeInt(mIsRecordingSession ? 1 : 0);
        dest.writeInt(mIsForeground ? 1 : 0);
        dest.writeInt(mAppType);
        dest.writeInt(mAppTag);
    }

    @Override
    public String toString() {
        return "inputID=" + mInputId
                + ";channelUri=" + mChannelUri
                + ";isRecording=" + mIsRecordingSession
                + ";isForeground=" + mIsForeground
                + ";appType=" + mAppType
                + ";appTag=" + mAppTag;
    }
}
+13 −0
Original line number Diff line number Diff line
@@ -1952,6 +1952,19 @@ public final class TvInputManager {
        }
    }

    /**
     * @hide
     */
    public List<TvChannelInfo> getTvCurrentChannelInfos() {
        // TODO: handle retuned() cases and add a method to TvInputCallback
        // TODO: unhide
        try {
            return mService.getTvCurrentChannelInfos(mUserId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * The Session provides the per-session functionality of TV inputs.
     * @hide
+103 −2
Original line number Diff line number Diff line
@@ -22,7 +22,9 @@ import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;

import android.annotation.NonNull;
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;
@@ -33,6 +35,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
@@ -51,6 +54,7 @@ import android.media.tv.ITvInputService;
import android.media.tv.ITvInputServiceCallback;
import android.media.tv.ITvInputSession;
import android.media.tv.ITvInputSessionCallback;
import android.media.tv.TvChannelInfo;
import android.media.tv.TvContentRating;
import android.media.tv.TvContentRatingSystemInfo;
import android.media.tv.TvContract;
@@ -78,6 +82,7 @@ import android.util.SparseArray;
import android.view.InputChannel;
import android.view.Surface;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
@@ -108,6 +113,9 @@ public final class TvInputManagerService extends SystemService {
    private static final boolean DEBUG = false;
    private static final String TAG = "TvInputManagerService";
    private static final String DVB_DIRECTORY = "/dev/dvb";
    private static final int APP_TAG_SELF = TvChannelInfo.APP_TAG_SELF;
    private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS =
            "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS";

    // There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d,
    // another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the
@@ -136,6 +144,8 @@ public final class TvInputManagerService extends SystemService {

    private final WatchLogHandler mWatchLogHandler;

    private final ActivityManager mActivityManager;

    public TvInputManagerService(Context context) {
        super(context);

@@ -144,6 +154,9 @@ public final class TvInputManagerService extends SystemService {
                IoThread.get().getLooper());
        mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());

        mActivityManager =
                (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);

        synchronized (mLock) {
            getOrCreateUserStateLocked(mCurrentUserId);
        }
@@ -694,6 +707,8 @@ public final class TvInputManagerService extends SystemService {
                sessionState.session.asBinder().unlinkToDeath(sessionState, 0);
                sessionState.session.release();
            }
            sessionState.isCurrent = false;
            sessionState.currentChannel = null;
        } catch (RemoteException | SessionNotFoundException e) {
            Slog.e(TAG, "error in releaseSession", e);
        } finally {
@@ -1394,13 +1409,17 @@ public final class TvInputManagerService extends SystemService {
                    try {
                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
                                channelUri, params);
                        UserState userState = getOrCreateUserStateLocked(resolvedUserId);
                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
                        if (sessionState != null) {
                            sessionState.isCurrent = true;
                            sessionState.currentChannel = channelUri;
                        }
                        if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
                            // Do not log the watch history for passthrough inputs.
                            return;
                        }

                        UserState userState = getOrCreateUserStateLocked(resolvedUserId);
                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
                        if (sessionState.isRecordingSession) {
                            return;
                        }
@@ -2049,6 +2068,80 @@ public final class TvInputManagerService extends SystemService {
            return clientPid;
        }

        @Override
        public List<TvChannelInfo> getTvCurrentChannelInfos(@UserIdInt int userId) {
            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
                    Binder.getCallingUid(), userId, "getTvCurrentChannelInfos");
            final long identity = Binder.clearCallingIdentity();
            try {
                synchronized (mLock) {
                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
                    List<TvChannelInfo> channelInfos = new ArrayList<>();
                    boolean watchedProgramsAccess = hasAccessWatchedProgramsPermission();
                    for (SessionState state : userState.sessionStateMap.values()) {
                        if (state.isCurrent) {
                            Integer appTag;
                            int appType;
                            if (state.callingUid == Binder.getCallingUid()) {
                                appTag = APP_TAG_SELF;
                                appType = TvChannelInfo.APP_TYPE_SELF;
                            } else {
                                appTag = userState.mAppTagMap.get(state.callingUid);
                                if (appTag == null) {
                                    appTag = userState.mNextAppTag++;
                                    userState.mAppTagMap.put(state.callingUid, appTag);
                                }
                                appType = isSystemApp(state.componentName.getPackageName())
                                        ? TvChannelInfo.APP_TYPE_SYSTEM
                                        : TvChannelInfo.APP_TYPE_NON_SYSTEM;
                            }
                            channelInfos.add(new TvChannelInfo(
                                    state.inputId,
                                    watchedProgramsAccess ? state.currentChannel : null,
                                    state.isRecordingSession,
                                    isForeground(state.callingPid),
                                    appType,
                                    appTag));
                        }
                    }
                    return channelInfos;
                }
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        protected 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() {
            return mContext.checkCallingPermission(PERMISSION_ACCESS_WATCHED_PROGRAMS)
                    == PackageManager.PERMISSION_GRANTED;
        }

        private boolean isSystemApp(String pkg) {
            try {
                return (mContext.getPackageManager().getApplicationInfo(pkg, 0).flags
                        & ApplicationInfo.FLAG_SYSTEM) != 0;
            } catch (NameNotFoundException e) {
                return false;
            }
        }

        /**
         * Add a hardware device in the TvInputHardwareManager for CTS testing
         * purpose.
@@ -2250,6 +2343,11 @@ public final class TvInputManagerService extends SystemService {
        // service.
        private final PersistentDataStore persistentDataStore;

        @GuardedBy("mLock")
        private final Map<Integer, Integer> mAppTagMap = new HashMap<>();
        @GuardedBy("mLock")
        private int mNextAppTag = 1;

        private UserState(Context context, int userId) {
            persistentDataStore = new PersistentDataStore(context, userId);
        }
@@ -2336,6 +2434,9 @@ public final class TvInputManagerService extends SystemService {
        // Not null if this session represents an external device connected to a hardware TV input.
        private IBinder hardwareSessionToken;

        private boolean isCurrent = false;
        private Uri currentChannel = null;

        private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
                boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
                int callingPid, int userId, String sessionId) {