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

Commit 06a551ee authored by Amy's avatar Amy
Browse files

Add a TunerResourceManagerService as an Android System Serivce.

This service is used to interact with TunerHAL interface and the Tuner
framework to manage the Tuner resources currently used on the device.
Tuner framework claims resource from this service. The service will
handle the requests from multiple applications based on their priority.

Sepolicy changes are in https://android-review.googlesource.com/c/platform/system/sepolicy/+/1161873
Test: cuttlefish
Bug:
Change-Id: Ifedc4e6f120e711aadffdf715d8720e0b64fbe16
parent cbe55af3
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -109,6 +109,8 @@ import android.media.session.MediaSessionManager;
import android.media.soundtrigger.SoundTriggerManager;
import android.media.tv.ITvInputManager;
import android.media.tv.TvInputManager;
import android.media.tv.tuner.ITunerResourceManager;
import android.media.tv.tuner.TunerResourceManager;
import android.net.ConnectivityDiagnosticsManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityThread;
@@ -937,6 +939,17 @@ public final class SystemServiceRegistry {
                return new TvInputManager(service, ctx.getUserId());
            }});

        registerService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, TunerResourceManager.class,
                new CachedServiceFetcher<TunerResourceManager>() {
            @Override
            public TunerResourceManager createService(ContextImpl ctx)
                    throws ServiceNotFoundException {
                IBinder iBinder =
                        ServiceManager.getServiceOrThrow(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
                ITunerResourceManager service = ITunerResourceManager.Stub.asInterface(iBinder);
                return new TunerResourceManager(service, ctx.getUserId());
            }});

        registerService(Context.NETWORK_SCORE_SERVICE, NetworkScoreManager.class,
                new CachedServiceFetcher<NetworkScoreManager>() {
            @Override
+12 −0
Original line number Diff line number Diff line
@@ -3457,6 +3457,7 @@ public abstract class Context {
            CONSUMER_IR_SERVICE,
            //@hide: TRUST_SERVICE,
            TV_INPUT_SERVICE,
            //@hide: TV_TUNER_RESOURCE_MGR_SERVICE,
            //@hide: NETWORK_SCORE_SERVICE,
            USAGE_STATS_SERVICE,
            MEDIA_SESSION_SERVICE,
@@ -4756,6 +4757,17 @@ public abstract class Context {
     */
    public static final String TV_INPUT_SERVICE = "tv_input";

    /**
     * Use with {@link #getSystemService(String)} to retrieve a
     * {@link android.media.tv.TunerResourceManager} for interacting with TV
     * tuner resources on the device.
     *
     * @see #getSystemService(String)
     * @see android.media.tv.TunerResourceManager
     * @hide
     */
    public static final String TV_TUNER_RESOURCE_MGR_SERVICE = "tv_tuner_resource_mgr";

    /**
     * {@link android.net.NetworkScoreManager} for managing network scoring.
     * @see #getSystemService(String)
+11 −0
Original line number Diff line number Diff line
@@ -230,4 +230,15 @@ interface ITunerResourceManager {
     * @param lnbId the id of the released Tuner Lnb.
     */
    void releaseLnb(in int lnbId);

    /*
     * Compare two clients' priority.
     *
     * @param challengerProfile the {@link ResourceClientProfile} of the challenger.
     * @param holderProfile the {@link ResourceClientProfile} of the holder of the resource.
     *
     * @return true if the challenger has higher priority than the holder.
     */
    boolean isHigherPriority(in ResourceClientProfile challengerProfile,
            in ResourceClientProfile holderProfile);
}
+407 −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.tuner;

import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.RemoteException;
import android.util.Log;

import java.util.concurrent.Executor;

/**
 * Interface of the Tuner Resource Manager(TRM). It manages resources used by TV Tuners.
 * <p>Resources include:
 * <ul>
 * <li>TunerFrontend {@link android.media.tv.tuner.frontend}.
 * <li>TunerLnb {@link android.media.tv.tuner.Lnb}.
 * <li>MediaCas {@link android.media.MediaCas}.
 * <ul>
 *
 * <p>Expected workflow is:
 * <ul>
 * <li>Tuner Java/MediaCas/TIF update resources of the current device with TRM.
 * <li>Client registers its profile through {@link #registerClientProfile(ResourceClientProfile,
 * Executor, ResourceListener, int[])}.
 * <li>Client requests resources through request APIs.
 * <li>If the resource needs to be handed to a higher priority client from a lower priority
 * one, TRM calls ITunerResourceManagerListener registered by the lower priority client to release
 * the resource.
 * <ul>
 *
 * <p>TRM also exposes its priority comparison algorithm as a helping method to other services.
 * {@see #isHigherPriority(ResourceClientProfile, ResourceClientProfile)}.
 *
 * @hide
 */
@RequiresFeature(PackageManager.FEATURE_LIVE_TV)
@SystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE)
public class TunerResourceManager {
    private static final String TAG = "TunerResourceManager";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    public static final int INVALID_FRONTEND_ID = -1;
    public static final int INVALID_CAS_SESSION_RESOURCE_ID = -1;
    public static final int INVALID_LNB_ID = -1;
    public static final int INVALID_TV_INPUT_DEVICE_ID = -1;
    public static final int INVALID_TV_INPUT_PORT_ID = -1;

    private final ITunerResourceManager mService;
    private final int mUserId;

    /**
     * @hide
     */
    public TunerResourceManager(ITunerResourceManager service, int userId) {
        mService = service;
        mUserId = userId;
    }

    /**
     * This API is used by the client to register their profile with the Tuner Resource manager.
     *
     * <p>The profile contains information that can show the base priority score of the client.
     *
     * @param profile {@link ResourceClientProfile} profile of the current client. Undefined use
     *                case would cause IllegalArgumentException.
     * @param executor the executor on which the listener would be invoked.
     * @param listener {@link ResourceListener} callback to reclaim clients' resources when needed.
     * @param clientId returned a clientId from the resource manager when the
     *                 the client registeres.
     * @throws IllegalArgumentException when {@code profile} contains undefined use case.
     */
    public void registerClientProfile(@NonNull ResourceClientProfile profile,
                        @NonNull @CallbackExecutor Executor executor,
                        @NonNull ResourceListener listener,
                        @NonNull int[] clientId) {
        // TODO: throw new IllegalArgumentException("Unknown client use case")
        // when the use case is not defined.
        try {
            mService.registerClientProfile(profile,
                    new ITunerResourceManagerListener.Stub() {
                    @Override
                public void onResourcesReclaim() {
                        final long identity = Binder.clearCallingIdentity();
                        try {
                            executor.execute(() -> listener.onResourcesReclaim());
                        } finally {
                            Binder.restoreCallingIdentity(identity);
                        }
                    }
                }, clientId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * This API is used by the client to unregister their profile with the
     * Tuner Resource manager.
     *
     * @param clientId the client id that needs to be unregistered.
     */
    public void unregisterClientProfile(int clientId) {
        try {
            mService.unregisterClientProfile(clientId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * This API is used by client to update its registered {@link ResourceClientProfile}.
     *
     * <p>We recommend creating a new tuner instance for different use cases instead of using this
     * API since different use cases may need different resources.
     *
     * <p>If TIS updates use case, it needs to ensure underneath resources are exchangeable between
     * two different use cases.
     *
     * <p>Only the arbitrary priority and niceValue are allowed to be updated.
     *
     * @param clientId the id of the client that is updating its profile.
     * @param priority the priority that the client would like to update to.
     * @param niceValue the nice value that the client would like to update to.
     *
     * @return true if the update is successful.
     */
    public boolean updateClientPriority(int clientId, int priority, int niceValue) {
        boolean result = false;
        try {
            result = mService.updateClientPriority(clientId, priority, niceValue);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        return result;
    }

    /**
     * Updates the current TRM of the TunerHAL Frontend information.
     *
     * <p><strong>Note:</strong> This update must happen before the first
     * {@link #requestFrontend(TunerFrontendRequest, int[])} and {@link #releaseFrontend(int)} call.
     *
     * @param infos an array of the available {@link TunerFrontendInfo} information.
     */
    public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos) {
        try {
            mService.setFrontendInfoList(infos);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Updates the TRM of the current CAS information.
     *
     * <p><strong>Note:</strong> This update must happen before the first
     * {@link #requestCasSession(CasSessionRequest, int[])} and {@link #releaseCasSession(int)}
     * call.
     *
     * @param casSystemId id of the updating CAS system.
     * @param maxSessionNum the max session number of the CAS system that is updated.
     */
    public void updateCasInfo(int casSystemId, int maxSessionNum) {
        try {
            mService.updateCasInfo(casSystemId, maxSessionNum);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Updates the TRM of the current Lnb information.
     *
     * <p><strong>Note:</strong> This update must happen before the first
     * {@link #requestLnb(TunerLnbRequest, int[])} and {@link #releaseLnb(int)} call.
     *
     * @param lnbIds ids of the updating lnbs.
     */
    public void setLnbInfoList(int[] lnbIds) {
        try {
            mService.setLnbInfoList(lnbIds);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Requests a frontend resource.
     *
     * <p>There are three possible scenarios:
     * <ul>
     * <li>If there is frontend available, the API would send the id back.
     *
     * <li>If no Frontend is available but the current request info can show higher priority than
     * other uses of Frontend, the API will send
     * {@link ITunerResourceManagerListener#onResourcesReclaim()} to the {@link Tuner}. Tuner would
     * handle the resource reclaim on the holder of lower priority and notify the holder of its
     * resource loss.
     *
     * <li>If no frontend can be granted, the API would return false.
     * <ul>
     *
     * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
     * before this request.
     *
     * @param request {@link TunerFrontendRequest} information of the current request.
     * @param frontendId a one-element array to return the granted frontendId. If
     *                   no frontend granted, this will return {@link #INVALID_FRONTEND_ID}.
     *
     * @return true if there is frontend granted.
     */
    public boolean requestFrontend(@NonNull TunerFrontendRequest request,
                @Nullable int[] frontendId) {
        boolean result = false;
        try {
            result = mService.requestFrontend(request, frontendId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        return result;
    }

    /**
     * Requests from the client to share frontend with an existing client.
     *
     * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
     * before this request.
     *
     * @param selfClientId the id of the client that sends the request.
     * @param targetClientId the id of the client to share the frontend with.
     */
    public void shareFrontend(int selfClientId, int targetClientId) {
        try {
            mService.shareFrontend(selfClientId, targetClientId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Requests a CAS session resource.
     *
     * <p>There are three possible scenarios:
     * <ul>
     * <li>If there is Cas session available, the API would send the id back.
     *
     * <li>If no Cas system is available but the current request info can show higher priority than
     * other uses of the cas sessions under the requested cas system, the API will send
     * {@link ITunerResourceManagerListener#onResourcesReclaim()} to the {@link Tuner}. Tuner would
     * handle the resource reclaim on the holder of lower priority and notify the holder of its
     * resource loss.
     *
     * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this
     * request.
     *
     * @param request {@link CasSessionRequest} information of the current request.
     * @param sessionResourceId a one-element array to return the granted cas session id.
     *                          If no CAS granted, this will return
     *                          {@link #INVALID_CAS_SESSION_RESOURCE_ID}.
     *
     * @return true if there is CAS session granted.
     */
    public boolean requestCasSession(@NonNull CasSessionRequest request,
                @NonNull int[] sessionResourceId) {
        boolean result = false;
        try {
            result = mService.requestCasSession(request, sessionResourceId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        return result;
    }

    /**
     * Requests a Tuner Lnb resource.
     *
     * <p>There are three possible scenarios:
     * <ul>
     * <li>If there is Lnb available, the API would send the id back.
     *
     * <li>If no Lnb is available but the current request has a higher priority than other uses of
     * lnbs, the API will send {@link ITunerResourceManagerListener#onResourcesReclaim()} to the
     * {@link Tuner}. Tuner would handle the resource reclaim on the holder of lower priority and
     * notify the holder of its resource loss.
     *
     * <li>If no Lnb system can be granted, the API would return false.
     * <ul>
     *
     * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this request.
     *
     * @param request {@link TunerLnbRequest} information of the current request.
     * @param lnbId a one-element array to return the granted Lnb id.
     *              If no Lnb granted, this will return {@link #INVALID_LNB_ID}.
     *
     * @return true if there is Lnb granted.
     */
    public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbId) {
        boolean result = false;
        try {
            result = mService.requestLnb(request, lnbId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        return result;
    }

    /**
     * Notifies the TRM that the given frontend has been released.
     *
     * <p>Client must call this whenever it releases a Tuner frontend.
     *
     * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
     * before this release.
     *
     * @param frontendId the id of the released frontend.
     */
    public void releaseFrontend(int frontendId) {
        try {
            mService.releaseFrontend(frontendId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Notifies the TRM that the given Cas session has been released.
     *
     * <p>Client must call this whenever it releases a Cas session.
     *
     * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this
     * release.
     *
     * @param sessionResourceId the id of the released CAS session.
     */
    public void releaseCasSession(int sessionResourceId) {
        try {
            mService.releaseCasSession(sessionResourceId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Notifies the TRM that the Lnb with the given id has been released.
     *
     * <p>Client must call this whenever it releases an Lnb.
     *
     * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this release.
     *
     * @param lnbId the id of the released Tuner Lnb.
     */
    public void releaseLnb(int lnbId) {
        try {
            mService.releaseLnb(lnbId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Compare two clients' priority.
     *
     * @param challengerProfile the {@link ResourceClientProfile} of the challenger.
     * @param holderProfile the {@link ResourceClientProfile} of the holder of the resource.
     *
     * @return true if the challenger has higher priority than the holder.
     */
    public boolean isHigherPriority(ResourceClientProfile challengerProfile,
            ResourceClientProfile holderProfile) {
        try {
            return mService.isHigherPriority(challengerProfile, holderProfile);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Interface used to receive events from TunerResourceManager.
     */
    public abstract static class ResourceListener {
        /*
         * To reclaim all the resources of the callack owner.
         */
        public abstract void onResourcesReclaim();
    }
}
+209 −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 com.android.server.tv.tuner;

/**
  * A client profile object used by the Tuner Resource Manager to record the registered clients'
  * information.
  *
  * @hide
  */
public final class ClientProfile {
    public static final int INVALID_GROUP_ID = -1;
    /**
     * Client id sent to the client when registering with
     * {@link #registerClientProfile(ResourceClientProfile, TunerResourceManagerCallback, int[])}
     */
    private final int mClientId;

    /**
     * see {@link ResourceClientProfile}
     */
    private final String mTvInputSessionId;

    /**
     * see {@link ResourceClientProfile}
     */
    private final int mUseCase;

    /**
     * Process id queried from {@link TvInputManager#}
     */
    private final int mProcessId;

    /**
     * All the clients that share the same resource would be under the same group id.
     *
     * <p>If a client's resource is to be reclaimed, all other clients under the same group id
     * also lose their resources.
     */
    private int mGroupId = INVALID_GROUP_ID;

    /**
     * Optional nice value for TRM to reduce client’s priority.
     */
    private int mNiceValue;

    /**
     * Optional arbitrary priority value given by the client.
     *
     * <p>This value can override the default priorotiy calculated from
     * the client profile.
     */
    private int mPriority;

    private ClientProfile(ClientProfileBuilder builder) {
        this.mClientId = builder.mClientId;
        this.mTvInputSessionId = builder.mTvInputSessionId;
        this.mUseCase = builder.mUseCase;
        this.mProcessId = builder.mProcessId;
        this.mGroupId = builder.mGroupId;
        this.mNiceValue = builder.mNiceValue;
        this.mPriority = builder.mPriority;
    }

    public int getClientId() {
        return mClientId;
    }

    public String getTvInputSessionId() {
        return mTvInputSessionId;
    }

    public int getUseCase() {
        return mUseCase;
    }

    public int getProcessId() {
        return mProcessId;
    }

    public int getGroupId() {
        return mGroupId;
    }

    public int getPriority() {
        return mPriority;
    }

    public int getNiceValue() {
        return mNiceValue;
    }

    public void setGroupId(int groupId) {
        mGroupId = groupId;
    }

    public void setPriority(int priority) {
        mPriority = priority;
    }

    public void setNiceValue(int niceValue) {
        mNiceValue = niceValue;
    }

    @Override
    public String toString() {
        return "ClientProfile: " + this.mClientId + ", " + this.mTvInputSessionId + ", "
                + this.mUseCase + ", " + this.mProcessId;
    }

    public static class ClientProfileBuilder {
        private final int mClientId;
        private String mTvInputSessionId;
        private int mUseCase;
        private int mProcessId;
        private int mGroupId;
        private int mNiceValue;
        private int mPriority;

        ClientProfileBuilder(int clientId) {
            this.mClientId = clientId;
        }

        /**
          * Builder for {@link ClientProfile}.
          *
          * @param useCase the useCase of the client.
          */
        public ClientProfileBuilder useCase(int useCase) {
            this.mUseCase = useCase;
            return this;
        }

        /**
          * Builder for {@link ClientProfile}.
          *
          * @param tvInputSessionId the id of the tv input session.
          */
        public ClientProfileBuilder tvInputSessionId(String tvInputSessionId) {
            this.mTvInputSessionId = tvInputSessionId;
            return this;
        }

        /**
          * Builder for {@link ClientProfile}.
          *
          * @param processId the id of process.
          */
        public ClientProfileBuilder processId(int processId) {
            this.mProcessId = processId;
            return this;
        }


        /**
          * Builder for {@link ClientProfile}.
          *
          * @param groupId the id of the group that shares the same resource.
          */
        public ClientProfileBuilder groupId(int groupId) {
            this.mGroupId = groupId;
            return this;
        }

        /**
          * Builder for {@link ClientProfile}.
          *
          * @param niceValue the nice value of the client.
          */
        public ClientProfileBuilder niceValue(int niceValue) {
            this.mNiceValue = niceValue;
            return this;
        }

        /**
          * Builder for {@link ClientProfile}.
          *
          * @param priority the priority value of the client.
          */
        public ClientProfileBuilder priority(int priority) {
            this.mPriority = priority;
            return this;
        }

        /**
          * Build a {@link ClientProfile}.
          *
          * @return {@link ClientProfile}.
          */
        public ClientProfile build() {
            ClientProfile clientProfile = new ClientProfile(this);
            return clientProfile;
        }
    }
}
Loading