From 06a551ee7b9ef226acb6a537f457abda5c137a58 Mon Sep 17 00:00:00 2001 From: Amy Date: Thu, 7 Nov 2019 13:44:33 -0800 Subject: [PATCH] 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 --- .../android/app/SystemServiceRegistry.java | 13 + core/java/android/content/Context.java | 12 + .../media/tv/tuner/ITunerResourceManager.aidl | 11 + .../media/tv/tuner/TunerResourceManager.java | 407 ++++++++++++++++++ .../server/tv/tuner/ClientProfile.java | 209 +++++++++ .../tv/tuner/TunerResourceManagerService.java | 258 +++++++++++ .../java/com/android/server/SystemServer.java | 3 + 7 files changed, 913 insertions(+) create mode 100644 media/java/android/media/tv/tuner/TunerResourceManager.java create mode 100644 services/core/java/com/android/server/tv/tuner/ClientProfile.java create mode 100644 services/core/java/com/android/server/tv/tuner/TunerResourceManagerService.java diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 50b9d6b47e020..8b07418668bad 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -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() { + @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() { @Override diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 249e58244870d..49f62f4078068 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -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) diff --git a/media/java/android/media/tv/tuner/ITunerResourceManager.aidl b/media/java/android/media/tv/tuner/ITunerResourceManager.aidl index 7c33e93f45f93..758c68949b657 100644 --- a/media/java/android/media/tv/tuner/ITunerResourceManager.aidl +++ b/media/java/android/media/tv/tuner/ITunerResourceManager.aidl @@ -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); } diff --git a/media/java/android/media/tv/tuner/TunerResourceManager.java b/media/java/android/media/tv/tuner/TunerResourceManager.java new file mode 100644 index 0000000000000..68ca5722ecad6 --- /dev/null +++ b/media/java/android/media/tv/tuner/TunerResourceManager.java @@ -0,0 +1,407 @@ +/* + * 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. + *

Resources include: + *

    + *
  • TunerFrontend {@link android.media.tv.tuner.frontend}. + *
  • TunerLnb {@link android.media.tv.tuner.Lnb}. + *
  • MediaCas {@link android.media.MediaCas}. + *
      + * + *

      Expected workflow is: + *

        + *
      • Tuner Java/MediaCas/TIF update resources of the current device with TRM. + *
      • Client registers its profile through {@link #registerClientProfile(ResourceClientProfile, + * Executor, ResourceListener, int[])}. + *
      • Client requests resources through request APIs. + *
      • 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. + *
          + * + *

          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. + * + *

          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}. + * + *

          We recommend creating a new tuner instance for different use cases instead of using this + * API since different use cases may need different resources. + * + *

          If TIS updates use case, it needs to ensure underneath resources are exchangeable between + * two different use cases. + * + *

          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. + * + *

          Note: 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. + * + *

          Note: 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. + * + *

          Note: 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. + * + *

          There are three possible scenarios: + *

            + *
          • If there is frontend available, the API would send the id back. + * + *
          • 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. + * + *
          • If no frontend can be granted, the API would return false. + *
              + * + *

              Note: {@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. + * + *

              Note: {@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. + * + *

              There are three possible scenarios: + *

                + *
              • If there is Cas session available, the API would send the id back. + * + *
              • 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. + * + *

                Note: {@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. + * + *

                There are three possible scenarios: + *

                  + *
                • If there is Lnb available, the API would send the id back. + * + *
                • 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. + * + *
                • If no Lnb system can be granted, the API would return false. + *
                    + * + *

                    Note: {@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. + * + *

                    Client must call this whenever it releases a Tuner frontend. + * + *

                    Note: {@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. + * + *

                    Client must call this whenever it releases a Cas session. + * + *

                    Note: {@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. + * + *

                    Client must call this whenever it releases an Lnb. + * + *

                    Note: {@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(); + } +} diff --git a/services/core/java/com/android/server/tv/tuner/ClientProfile.java b/services/core/java/com/android/server/tv/tuner/ClientProfile.java new file mode 100644 index 0000000000000..3845195e6643b --- /dev/null +++ b/services/core/java/com/android/server/tv/tuner/ClientProfile.java @@ -0,0 +1,209 @@ +/* + * 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. + * + *

                    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. + * + *

                    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; + } + } +} diff --git a/services/core/java/com/android/server/tv/tuner/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tuner/TunerResourceManagerService.java new file mode 100644 index 0000000000000..e8764214ca210 --- /dev/null +++ b/services/core/java/com/android/server/tv/tuner/TunerResourceManagerService.java @@ -0,0 +1,258 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.tv.TvInputManager; +import android.media.tv.tuner.CasSessionRequest; +import android.media.tv.tuner.ITunerResourceManager; +import android.media.tv.tuner.ITunerResourceManagerListener; +import android.media.tv.tuner.ResourceClientProfile; +import android.media.tv.tuner.TunerFrontendInfo; +import android.media.tv.tuner.TunerFrontendRequest; +import android.media.tv.tuner.TunerLnbRequest; +import android.media.tv.tuner.TunerResourceManager; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.server.SystemService; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides a system service that manages the TV tuner resources. + * + * @hide + */ +public class TunerResourceManagerService extends SystemService { + private static final String TAG = "TunerResourceManagerService"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private SparseArray mClientProfiles = new SparseArray<>(); + private SparseArray mListeners = new SparseArray<>(); + private int mNextUnusedFrontendId = 0; + private List mReleasedClientId = new ArrayList(); + private List mAvailableFrontendIds = new ArrayList(); + + private TvInputManager mManager; + + public TunerResourceManagerService(@Nullable Context context) { + super(context); + } + + @Override + public void onStart() { + publishBinderService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, new BinderService()); + mManager = (TvInputManager) getContext() + .getSystemService(Context.TV_INPUT_SERVICE); + } + + private final class BinderService extends ITunerResourceManager.Stub { + @Override + public void registerClientProfile(@NonNull ResourceClientProfile profile, + @NonNull ITunerResourceManagerListener listener, + @NonNull int[] clientId) { + if (DEBUG) { + Slog.d(TAG, "registerClientProfile(clientProfile=" + profile + ")"); + } + + // TODO tell if the client already exists + if (mReleasedClientId.isEmpty()) { + clientId[0] = mNextUnusedFrontendId++; + } else { + clientId[0] = mReleasedClientId.get(0); + mReleasedClientId.remove(0); + } + + if (mManager == null) { + Slog.e(TAG, "TvInputManager is null. Can't register client profile."); + return; + } + + int callingPid = mManager.getClientPid(profile.getTvInputSessionId()); + + ClientProfile clientProfile = new ClientProfile.ClientProfileBuilder( + clientId[0]) + .tvInputSessionId(profile.getTvInputSessionId()) + .useCase(profile.getUseCase()) + .processId(callingPid) + .build(); + mClientProfiles.append(clientId[0], clientProfile); + mListeners.append(clientId[0], listener); + } + + @Override + public void unregisterClientProfile(int clientId) { + if (DEBUG) { + Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")"); + } + + mClientProfiles.remove(clientId); + mListeners.remove(clientId); + mReleasedClientId.add(clientId); + } + + @Override + public boolean updateClientPriority(int clientId, int priority, int niceValue) { + if (DEBUG) { + Slog.d(TAG, "updateClientPriority(clientId=" + clientId + + ", priority=" + priority + ", niceValue=" + niceValue + ")"); + } + + ClientProfile profile = mClientProfiles.get(clientId); + if (profile == null) { + Slog.e(TAG, "Can not find client profile with id " + clientId + + " when trying to update the client priority."); + return false; + } + + profile.setPriority(priority); + profile.setNiceValue(niceValue); + + return true; + } + + @Override + public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos) + throws RemoteException { + if (infos == null || infos.length == 0) { + Slog.d(TAG, "Can't update with empty frontend info"); + return; + } + + if (DEBUG) { + Slog.d(TAG, "updateFrontendInfo:"); + for (int i = 0; i < infos.length; i++) { + Slog.d(TAG, infos[i].toString()); + } + } + } + + @Override + public void updateCasInfo(int casSystemId, int maxSessionNum) { + if (DEBUG) { + Slog.d(TAG, "updateCasInfo(casSystemId=" + + casSystemId + ", maxSessionNum=" + maxSessionNum + ")"); + } + } + + @Override + public void setLnbInfoList(int[] lnbIds) { + if (DEBUG) { + for (int i = 0; i < lnbIds.length; i++) { + Slog.d(TAG, "updateLnbInfo(lnbId=" + lnbIds[i] + ")"); + } + } + } + + @Override + public boolean requestFrontend(@NonNull TunerFrontendRequest request, + @NonNull int[] frontendId) throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "requestFrontend(request=" + request + ")"); + } + + frontendId[0] = TunerResourceManager.INVALID_FRONTEND_ID; + + if (getContext() == null) { + Slog.e(TAG, "Can not find context when requesting frontend"); + return false; + } + + if (mClientProfiles.get(request.getClientId()) == null) { + Slog.e(TAG, "Request from unregistered client. Id: " + + request.getClientId()); + return false; + } + + String sessionId = mClientProfiles.get(request.getClientId()) + .getTvInputSessionId(); + + if (DEBUG) { + Slog.d(TAG, "session Id:" + sessionId + ")"); + } + + if (DEBUG) { + Slog.d(TAG, "No available Frontend found."); + } + + return false; + } + + @Override + public void shareFrontend(int selfClientId, int targetClientId) { + if (DEBUG) { + Slog.d(TAG, "shareFrontend from " + + selfClientId + " with " + targetClientId); + } + } + + @Override + public boolean requestCasSession(@NonNull CasSessionRequest request, + @NonNull int[] sessionResourceId) { + if (DEBUG) { + Slog.d(TAG, "requestCasSession(request=" + request + ")"); + } + + return true; + } + + @Override + public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbId) { + if (DEBUG) { + Slog.d(TAG, "requestLnb(request=" + request + ")"); + } + return true; + } + + @Override + public void releaseFrontend(int frontendId) { + if (DEBUG) { + Slog.d(TAG, "releaseFrontend(id=" + frontendId + ")"); + } + } + + @Override + public void releaseCasSession(int sessionResourceId) { + if (DEBUG) { + Slog.d(TAG, "releaseCasSession(sessionResourceId=" + sessionResourceId + ")"); + } + } + + @Override + public void releaseLnb(int lnbId) { + if (DEBUG) { + Slog.d(TAG, "releaseLnb(lnbId=" + lnbId + ")"); + } + } + + @Override + public boolean isHigherPriority(ResourceClientProfile challengerProfile, + ResourceClientProfile holderProfile) { + if (DEBUG) { + Slog.d(TAG, "isHigherPriority(challengerProfile=" + challengerProfile + + ", holderProfile=" + challengerProfile + ")"); + } + return true; + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index eef6c63d29c99..93662c91af90f 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -162,6 +162,7 @@ import com.android.server.textservices.TextServicesManagerService; import com.android.server.trust.TrustManagerService; import com.android.server.tv.TvInputManagerService; import com.android.server.tv.TvRemoteService; +import com.android.server.tv.tuner.TunerResourceManagerService; import com.android.server.twilight.TwilightService; import com.android.server.uri.UriGrantsManagerService; import com.android.server.usage.UsageStatsService; @@ -1854,6 +1855,8 @@ public final class SystemServer { || mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { t.traceBegin("StartTvInputManager"); mSystemServiceManager.startService(TvInputManagerService.class); + t.traceBegin("StartTunerResourceManager"); + mSystemServiceManager.startService(TunerResourceManagerService.class); t.traceEnd(); }