diff options
7 files changed, 1453 insertions, 159 deletions
diff --git a/media/java/android/media/tv/tunerresourcemanager/ResourceClientProfile.java b/media/java/android/media/tv/tunerresourcemanager/ResourceClientProfile.java index 68372444cb83..598ff8f3f075 100644 --- a/media/java/android/media/tv/tunerresourcemanager/ResourceClientProfile.java +++ b/media/java/android/media/tv/tunerresourcemanager/ResourceClientProfile.java @@ -78,7 +78,8 @@ public final class ResourceClientProfile implements Parcelable { * {@link android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE} * {@link android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD}. * New [use case : priority value] pair can be defined in the manifest by the - * OEM. Any undefined use case would cause IllegalArgumentException. + * OEM. The id of the useCaseVendor should be passed through this parameter. Any + * undefined use case would cause IllegalArgumentException. */ public ResourceClientProfile(@NonNull String tvInputSessionId, int useCase) { diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java index bad2b78dab48..4eff954f1ec8 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.server.tv.tunerresourcemanager; +import java.util.ArrayList; +import java.util.List; + /** * A client profile object used by the Tuner Resource Manager to record the registered clients' * information. @@ -23,12 +25,14 @@ package com.android.server.tv.tunerresourcemanager; * @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; + private final int mId; /** * see {@link ResourceClientProfile} @@ -41,7 +45,7 @@ public final class ClientProfile { private final int mUseCase; /** - * Process id queried from {@link TvInputManager#} + * Process id queried from {@link TvInputManager#getPid(String)}. */ private final int mProcessId; @@ -59,6 +63,11 @@ public final class ClientProfile { private int mNiceValue; /** + * List of the frontend ids that are used by the current client. + */ + private List<Integer> mUsingFrontendIds = new ArrayList<>(); + + /** * Optional arbitrary priority value given by the client. * * <p>This value can override the default priorotiy calculated from @@ -66,18 +75,15 @@ public final class ClientProfile { */ private int mPriority; - private ClientProfile(ClientProfileBuilder builder) { - this.mClientId = builder.mClientId; + private ClientProfile(Builder builder) { + this.mId = builder.mId; 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 int getId() { + return mId; } public String getTvInputSessionId() { @@ -116,26 +122,47 @@ public final class ClientProfile { mNiceValue = niceValue; } + /** + * Set when the client starts to use a frontend. + * + * @param frontendId being used. + */ + public void useFrontend(int frontendId) { + mUsingFrontendIds.add(frontendId); + } + + public List<Integer> getInUseFrontendIds() { + return mUsingFrontendIds; + } + + /** + * Called when the client released a frontend. + * + * <p>This could happen when client resource reclaimed. + * + * @param frontendId being released. + */ + public void releaseFrontend(int frontendId) { + mUsingFrontendIds.remove(frontendId); + } + @Override public String toString() { - return "ClientProfile: " + this.mClientId + ", " + this.mTvInputSessionId + ", " - + this.mUseCase + ", " + this.mProcessId; + return "ClientProfile[id=" + this.mId + ", tvInputSessionId=" + this.mTvInputSessionId + + ", useCase=" + this.mUseCase + ", processId=" + this.mProcessId + "]"; } /** * Builder class for {@link ClientProfile}. */ - public static class ClientProfileBuilder { - private final int mClientId; + public static class Builder { + private final int mId; 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(int id) { + this.mId = id; } /** @@ -143,7 +170,7 @@ public final class ClientProfile { * * @param useCase the useCase of the client. */ - public ClientProfileBuilder useCase(int useCase) { + public Builder useCase(int useCase) { this.mUseCase = useCase; return this; } @@ -153,7 +180,7 @@ public final class ClientProfile { * * @param tvInputSessionId the id of the tv input session. */ - public ClientProfileBuilder tvInputSessionId(String tvInputSessionId) { + public Builder tvInputSessionId(String tvInputSessionId) { this.mTvInputSessionId = tvInputSessionId; return this; } @@ -163,42 +190,11 @@ public final class ClientProfile { * * @param processId the id of process. */ - public ClientProfileBuilder processId(int processId) { + public Builder 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}. * diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java new file mode 100644 index 000000000000..a109265e2f50 --- /dev/null +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java @@ -0,0 +1,204 @@ +/* + * 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.tunerresourcemanager; + +import android.annotation.Nullable; +import android.media.tv.tuner.frontend.FrontendSettings.Type; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A frontend resource object used by the Tuner Resource Manager to record the tuner frontend + * information. + * + * @hide + */ +public final class FrontendResource { + public static final int INVALID_OWNER_ID = -1; + + /** + * Id of the current frontend. Should not be changed and should be aligned with the driver level + * implementation. + */ + private final int mId; + + /** + * see {@link android.media.tv.tuner.frontend.FrontendSettings.Type} + */ + @Type private final int mType; + + /** + * The exclusive group id of the FE. FEs under the same id can't be used at the same time. + */ + private final int mExclusiveGroupId; + + /** + * An array to save all the FE ids under the same exclisive group. + */ + private List<Integer> mExclusiveGroupMemberFeIds = new ArrayList<>(); + + /** + * If the current resource is in use. Once resources under the same exclusive group id is in use + * all other resources in the same group would be considered in use. + */ + private boolean mIsInUse; + + /** + * The owner client's id if this resource is occupied. Owner of the resource under the same + * exclusive group id would be considered as the whole group's owner. + */ + private int mOwnerClientId = INVALID_OWNER_ID; + + private FrontendResource(Builder builder) { + this.mId = builder.mId; + this.mType = builder.mType; + this.mExclusiveGroupId = builder.mExclusiveGroupId; + } + + public int getId() { + return mId; + } + + public int getType() { + return mType; + } + + public int getExclusiveGroupId() { + return mExclusiveGroupId; + } + + public List<Integer> getExclusiveGroupMemberFeIds() { + return mExclusiveGroupMemberFeIds; + } + + /** + * Add one id into the exclusive group member id list. + * + * @param id the id to be added. + */ + public void addExclusiveGroupMemberFeId(int id) { + mExclusiveGroupMemberFeIds.add(id); + } + + /** + * Add one id list to the exclusive group member id list. + * + * @param ids the id list to be added. + */ + public void addExclusiveGroupMemberFeId(List<Integer> ids) { + mExclusiveGroupMemberFeIds.addAll(ids); + } + + /** + * Remove one id from the exclusive group member id list. + * + * @param id the id to be removed. + */ + public void removeExclusiveGroupMemberFeId(int id) { + mExclusiveGroupMemberFeIds.remove(new Integer(id)); + } + + public boolean isInUse() { + return mIsInUse; + } + + public int getOwnerClientId() { + return mOwnerClientId; + } + + /** + * Set an owner client on the resource. + * + * @param ownerClientId the id of the owner client. + */ + public void setOwner(int ownerClientId) { + mIsInUse = true; + mOwnerClientId = ownerClientId; + } + + /** + * Remove an owner client from the resource. + */ + public void removeOwner() { + mIsInUse = false; + mOwnerClientId = INVALID_OWNER_ID; + } + + @Override + public String toString() { + return "FrontendResource[id=" + this.mId + ", type=" + this.mType + + ", exclusiveGId=" + this.mExclusiveGroupId + ", exclusiveGMemeberIds=" + + Arrays.toString(this.mExclusiveGroupMemberFeIds.toArray()) + + ", isInUse=" + this.mIsInUse + ", ownerClientId=" + this.mOwnerClientId + "]"; + } + + @Override + public boolean equals(@Nullable Object o) { + if (o instanceof FrontendResource) { + FrontendResource fe = (FrontendResource) o; + return mId == fe.getId() && mType == fe.getType() + && mExclusiveGroupId == fe.getExclusiveGroupId() + && mExclusiveGroupMemberFeIds.equals(fe.getExclusiveGroupMemberFeIds()) + && mIsInUse == fe.isInUse() && mOwnerClientId == fe.getOwnerClientId(); + } + return false; + } + + /** + * Builder class for {@link FrontendResource}. + */ + public static class Builder { + private final int mId; + @Type private int mType; + private int mExclusiveGroupId; + + Builder(int id) { + this.mId = id; + } + + /** + * Builder for {@link FrontendResource}. + * + * @param type the type of the frontend. See {@link Type} + */ + public Builder type(@Type int type) { + this.mType = type; + return this; + } + + /** + * Builder for {@link FrontendResource}. + * + * @param exclusiveGroupId the id of exclusive group. + */ + public Builder exclusiveGroupId(int exclusiveGroupId) { + this.mExclusiveGroupId = exclusiveGroupId; + return this; + } + + /** + * Build a {@link FrontendResource}. + * + * @return {@link FrontendResource}. + */ + public FrontendResource build() { + FrontendResource frontendResource = new FrontendResource(this); + return frontendResource; + } + } +} diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index 49a7045bf57a..cb31a502ecfa 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -28,32 +28,48 @@ import android.media.tv.tunerresourcemanager.TunerFrontendInfo; import android.media.tv.tunerresourcemanager.TunerFrontendRequest; import android.media.tv.tunerresourcemanager.TunerLnbRequest; import android.media.tv.tunerresourcemanager.TunerResourceManager; +import android.os.Binder; import android.os.RemoteException; import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.annotations.VisibleForTesting; 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 - */ + * 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<ClientProfile> mClientProfiles = new SparseArray<>(); - private SparseArray<IResourcesReclaimListener> mListeners = new SparseArray<>(); - private int mNextUnusedFrontendId = 0; - private List<Integer> mReleasedClientId = new ArrayList<Integer>(); + public static final int INVALID_CLIENT_ID = -1; + private static final int MAX_CLIENT_PRIORITY = 1000; + + // Array of the registered client profiles + @VisibleForTesting private SparseArray<ClientProfile> mClientProfiles = new SparseArray<>(); + private int mNextUnusedClientId = 0; + private List<Integer> mRegisteredClientIds = new ArrayList<Integer>(); + + // Array of the current available frontend resources + @VisibleForTesting + private SparseArray<FrontendResource> mFrontendResources = new SparseArray<>(); + // Array of the current available frontend ids private List<Integer> mAvailableFrontendIds = new ArrayList<Integer>(); + private SparseArray<IResourcesReclaimListener> mListeners = new SparseArray<>(); + private TvInputManager mManager; + private UseCasePriorityHints mPriorityCongfig = new UseCasePriorityHints(); + + // Used to synchronize the access to the service. + private final Object mLock = new Object(); public TunerResourceManagerService(@Nullable Context context) { super(context); @@ -61,97 +77,78 @@ public class TunerResourceManagerService extends SystemService { @Override public void onStart() { - publishBinderService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, new BinderService()); - mManager = (TvInputManager) getContext() - .getSystemService(Context.TV_INPUT_SERVICE); + onStart(false /*isForTesting*/); + } + + @VisibleForTesting + protected void onStart(boolean isForTesting) { + if (!isForTesting) { + publishBinderService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, new BinderService()); + } + mManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE); + mPriorityCongfig.parse(); } private final class BinderService extends ITunerResourceManager.Stub { @Override public void registerClientProfile(@NonNull ResourceClientProfile profile, - @NonNull IResourcesReclaimListener listener, - @NonNull int[] clientId) { - if (DEBUG) { - Slog.d(TAG, "registerClientProfile(clientProfile=" + profile + ")"); + @NonNull IResourcesReclaimListener listener, @NonNull int[] clientId) + throws RemoteException { + enforceAccessPermission(); + if (profile == null) { + throw new RemoteException("ResourceClientProfile can't be null"); } - // TODO tell if the client already exists - if (mReleasedClientId.isEmpty()) { - clientId[0] = mNextUnusedFrontendId++; - } else { - clientId[0] = mReleasedClientId.get(0); - mReleasedClientId.remove(0); + if (clientId == null) { + throw new RemoteException("clientId can't be null!"); } - if (mManager == null) { - Slog.e(TAG, "TvInputManager is null. Can't register client profile."); - return; + if (!mPriorityCongfig.isDefinedUseCase(profile.getUseCase())) { + throw new RemoteException("Use undefined client use case:" + profile.getUseCase()); } - 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); + synchronized (mLock) { + registerClientProfileInternal(profile, listener, clientId); + } } @Override - public void unregisterClientProfile(int clientId) { - if (DEBUG) { - Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")"); + public void unregisterClientProfile(int clientId) throws RemoteException { + enforceAccessPermission(); + synchronized (mLock) { + if (!checkClientExists(clientId)) { + Slog.e(TAG, "Unregistering non exists client:" + clientId); + return; + } + unregisterClientProfileInternal(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; + enforceAccessPermission(); + synchronized (mLock) { + return updateClientPriorityInternal(clientId, priority, niceValue); } - - 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; + public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos) throws RemoteException { + enforceAccessPermission(); + if (infos == null) { + throw new RemoteException("TunerFrontendInfo can't be null"); } - - if (DEBUG) { - Slog.d(TAG, "updateFrontendInfo:"); - for (int i = 0; i < infos.length; i++) { - Slog.d(TAG, infos[i].toString()); - } + synchronized (mLock) { + setFrontendInfoListInternal(infos); } } @Override public void updateCasInfo(int casSystemId, int maxSessionNum) { if (DEBUG) { - Slog.d(TAG, "updateCasInfo(casSystemId=" - + casSystemId + ", maxSessionNum=" + maxSessionNum + ")"); + Slog.d(TAG, + "updateCasInfo(casSystemId=" + casSystemId + + ", maxSessionNum=" + maxSessionNum + ")"); } } @@ -166,49 +163,30 @@ public class TunerResourceManagerService extends SystemService { @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; + @NonNull int[] frontendId) throws RemoteException { + enforceAccessPermission(); + if (frontendId == null) { + throw new RemoteException("frontendId can't be null"); } - - 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."); + synchronized (mLock) { + try { + return requestFrontendInternal(request, frontendId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } - - return false; } @Override public void shareFrontend(int selfClientId, int targetClientId) { if (DEBUG) { - Slog.d(TAG, "shareFrontend from " - + selfClientId + " with " + targetClientId); + Slog.d(TAG, "shareFrontend from " + selfClientId + " with " + targetClientId); } } @Override - public boolean requestCasSession(@NonNull CasSessionRequest request, - @NonNull int[] sessionResourceId) { + public boolean requestCasSession( + @NonNull CasSessionRequest request, @NonNull int[] sessionResourceId) { if (DEBUG) { Slog.d(TAG, "requestCasSession(request=" + request + ")"); } @@ -246,13 +224,284 @@ public class TunerResourceManagerService extends SystemService { } @Override - public boolean isHigherPriority(ResourceClientProfile challengerProfile, - ResourceClientProfile holderProfile) { + public boolean isHigherPriority( + ResourceClientProfile challengerProfile, ResourceClientProfile holderProfile) { if (DEBUG) { - Slog.d(TAG, "isHigherPriority(challengerProfile=" + challengerProfile - + ", holderProfile=" + challengerProfile + ")"); + Slog.d(TAG, + "isHigherPriority(challengerProfile=" + challengerProfile + + ", holderProfile=" + challengerProfile + ")"); } return true; } } + + @VisibleForTesting + protected void registerClientProfileInternal(ResourceClientProfile profile, + IResourcesReclaimListener listener, int[] clientId) { + if (DEBUG) { + Slog.d(TAG, "registerClientProfile(clientProfile=" + profile + ")"); + } + + clientId[0] = INVALID_CLIENT_ID; + if (mManager == null) { + Slog.e(TAG, "TvInputManager is null. Can't register client profile."); + return; + } + // TODO tell if the client already exists + clientId[0] = mNextUnusedClientId++; + + int pid = profile.getTvInputSessionId() == null + ? Binder.getCallingPid() /*callingPid*/ + : mManager.getClientPid(profile.getTvInputSessionId()); /*tvAppId*/ + + ClientProfile clientProfile = new ClientProfile.Builder(clientId[0]) + .tvInputSessionId(profile.getTvInputSessionId()) + .useCase(profile.getUseCase()) + .processId(pid) + .build(); + clientProfile.setPriority(getClientPriority(profile.getUseCase(), pid)); + + mClientProfiles.append(clientId[0], clientProfile); + mListeners.append(clientId[0], listener); + mRegisteredClientIds.add(clientId[0]); + } + + @VisibleForTesting + protected void unregisterClientProfileInternal(int clientId) { + if (DEBUG) { + Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")"); + } + for (int id : getClientProfile(clientId).getInUseFrontendIds()) { + getFrontendResource(id).removeOwner(); + for (int groupMemberId : getFrontendResource(id).getExclusiveGroupMemberFeIds()) { + getFrontendResource(groupMemberId).removeOwner(); + } + } + mClientProfiles.remove(clientId); + mListeners.remove(clientId); + mRegisteredClientIds.remove(clientId); + } + + @VisibleForTesting + protected boolean updateClientPriorityInternal(int clientId, int priority, int niceValue) { + if (DEBUG) { + Slog.d(TAG, + "updateClientPriority(clientId=" + clientId + ", priority=" + priority + + ", niceValue=" + niceValue + ")"); + } + + ClientProfile profile = getClientProfile(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; + } + + @VisibleForTesting + protected void setFrontendInfoListInternal(TunerFrontendInfo[] infos) { + if (DEBUG) { + Slog.d(TAG, "updateFrontendInfo:"); + for (int i = 0; i < infos.length; i++) { + Slog.d(TAG, infos[i].toString()); + } + } + + // An arrayList to record the frontends pending on updating. Ids will be removed + // from this list once its updating finished. Any frontend left in this list when all + // the updates are done will be removed from mAvailableFrontendIds and + // mFrontendResources. + List<Integer> updatingFrontendIds = new ArrayList<>(mAvailableFrontendIds); + + // Update frontendResources sparse array and other mappings accordingly + for (int i = 0; i < infos.length; i++) { + if (getFrontendResource(infos[i].getId()) != null) { + if (DEBUG) { + Slog.d(TAG, "Frontend id=" + infos[i].getId() + "exists."); + } + updatingFrontendIds.remove(new Integer(infos[i].getId())); + } else { + // Add a new fe resource + FrontendResource newFe = new FrontendResource.Builder(infos[i].getId()) + .type(infos[i].getFrontendType()) + .exclusiveGroupId(infos[i].getExclusiveGroupId()) + .build(); + // Update the exclusive group member list in all the existing Frontend resource + for (Integer feId : mAvailableFrontendIds) { + FrontendResource fe = getFrontendResource(feId.intValue()); + if (fe.getExclusiveGroupId() == newFe.getExclusiveGroupId()) { + newFe.addExclusiveGroupMemberFeId(fe.getId()); + newFe.addExclusiveGroupMemberFeId(fe.getExclusiveGroupMemberFeIds()); + for (Integer excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) { + getFrontendResource(excGroupmemberFeId.intValue()) + .addExclusiveGroupMemberFeId(newFe.getId()); + } + fe.addExclusiveGroupMemberFeId(newFe.getId()); + break; + } + } + // Update resource list and available id list + mFrontendResources.append(newFe.getId(), newFe); + mAvailableFrontendIds.add(newFe.getId()); + } + } + + // TODO check if the removing resource is in use or not. Handle the conflict. + for (Integer removingId : updatingFrontendIds) { + // update the exclusive group id memver list + FrontendResource fe = getFrontendResource(removingId.intValue()); + fe.removeExclusiveGroupMemberFeId(new Integer(fe.getId())); + for (Integer excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) { + getFrontendResource(excGroupmemberFeId.intValue()) + .removeExclusiveGroupMemberFeId(new Integer(fe.getId())); + } + mFrontendResources.remove(removingId.intValue()); + mAvailableFrontendIds.remove(removingId); + } + } + + @VisibleForTesting + protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendId) + throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "requestFrontend(request=" + request + ")"); + } + + frontendId[0] = TunerResourceManager.INVALID_FRONTEND_ID; + if (!checkClientExists(request.getClientId())) { + Slog.e(TAG, "Request frontend from unregistered client:" + request.getClientId()); + return false; + } + ClientProfile requestClient = getClientProfile(request.getClientId()); + int grantingFrontendId = -1; + int inUseLowestPriorityFrId = -1; + // Priority max value is 1000 + int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; + for (int id : mAvailableFrontendIds) { + FrontendResource fr = getFrontendResource(id); + if (fr.getType() == request.getFrontendType()) { + if (!fr.isInUse()) { + // Grant unused frontend with no exclusive group members first. + if (fr.getExclusiveGroupMemberFeIds().size() == 0) { + grantingFrontendId = id; + break; + } else if (grantingFrontendId < 0) { + // Grant the unused frontend with lower id first if all the unused + // frontends have exclusive group members. + grantingFrontendId = id; + } + } else if (grantingFrontendId < 0) { + // Record the frontend id with the lowest client priority among all the + // in use frontends when no available frontend has been found. + int priority = getOwnerClientPriority(id); + if (currentLowestPriority > priority) { + inUseLowestPriorityFrId = id; + currentLowestPriority = priority; + } + } + } + } + + // Grant frontend when there is unused resource. + if (grantingFrontendId > -1) { + frontendId[0] = grantingFrontendId; + updateFrontendClientMappingOnNewGrant(frontendId[0], request.getClientId()); + return true; + } + + // When all the resources are occupied, grant the lowest priority resource if the + // request client has higher priority. + if (inUseLowestPriorityFrId > -1 && (requestClient.getPriority() > currentLowestPriority)) { + frontendId[0] = inUseLowestPriorityFrId; + reclaimFrontendResource(getFrontendResource(frontendId[0]).getOwnerClientId()); + updateFrontendClientMappingOnNewGrant(frontendId[0], request.getClientId()); + return true; + } + + return false; + } + + @VisibleForTesting + protected int getClientPriority(int useCase, int pid) { + if (DEBUG) { + Slog.d(TAG, "getClientPriority useCase=" + useCase + + ", pid=" + pid + ")"); + } + + if (isForeground(pid)) { + return mPriorityCongfig.getForegroundPriority(useCase); + } + return mPriorityCongfig.getBackgroundPriority(useCase); + } + + @VisibleForTesting + protected boolean isForeground(int pid) { + // TODO: how to get fg/bg information from pid + return true; + } + + @VisibleForTesting + protected void reclaimFrontendResource(int reclaimingId) throws RemoteException { + if (mListeners.get(reclaimingId) != null) { + try { + mListeners.get(reclaimingId).onReclaimResources(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + private void updateFrontendClientMappingOnNewGrant(int grantingId, int ownerClientId) { + FrontendResource grantingFrontend = getFrontendResource(grantingId); + ClientProfile ownerProfile = getClientProfile(ownerClientId); + grantingFrontend.setOwner(ownerClientId); + ownerProfile.useFrontend(grantingId); + for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeIds()) { + getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId); + ownerProfile.useFrontend(exclusiveGroupMember); + } + } + + /** + * Get the owner client's priority from the frontend id. + * + * @param frontendId an in use frontend id. + * @return the priority of the owner client of the frontend. + */ + private int getOwnerClientPriority(int frontendId) { + return getClientProfile(getFrontendResource(frontendId).getOwnerClientId()).getPriority(); + } + + private ClientProfile getClientProfile(int clientId) { + return mClientProfiles.get(clientId); + } + + protected FrontendResource getFrontendResource(int frontendId) { + return mFrontendResources.get(frontendId); + } + + @VisibleForTesting + protected SparseArray<ClientProfile> getClientProfiles() { + return mClientProfiles; + } + + @VisibleForTesting + protected SparseArray<FrontendResource> getFrontendResources() { + return mFrontendResources; + } + + private boolean checkClientExists(int clientId) { + return mRegisteredClientIds.contains(clientId); + } + + private void enforceAccessPermission() { + getContext().enforceCallingOrSelfPermission( + "android.permission.TUNER_RESOURCE_ACCESS", TAG); + } } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java b/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java new file mode 100644 index 000000000000..8c2de475b99b --- /dev/null +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java @@ -0,0 +1,236 @@ +/* + * 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.tunerresourcemanager; + +import android.media.tv.TvInputService; +import android.media.tv.TvInputService.PriorityHintUseCaseType; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.Xml; + +import com.android.internal.annotations.VisibleForTesting; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides the Tuner Resource Manager use case priority hints config info including a + * parser that can read the xml config from the vendors. + * + * @hide + */ +public class UseCasePriorityHints { + private static final String TAG = "UseCasePriorityHints"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private static final String PATH_TO_VENDOR_CONFIG_XML = + "/vendor/etc/tunerResourceManagerUseCaseConfig.xml"; + private static final int INVALID_PRIORITY_VALUE = -1; + private static final int INVALID_USE_CASE = -1; + + /** + * Array of the configured use case priority hints. Key is the use case id. Value is a size 2 + * int array. The first element carries the priority of the use case on foreground. The second + * shows the background priority. + */ + SparseArray<int[]> mPriorityHints = new SparseArray<>(); + + List<Integer> mVendorDefinedUseCase = new ArrayList<>(); + + private int mDefaultForeground = 150; + private int mDefaultBackground = 50; + + int getForegroundPriority(int useCase) { + if (mPriorityHints.get(useCase) != null && mPriorityHints.get(useCase).length == 2) { + return mPriorityHints.get(useCase)[0]; + } + return mDefaultForeground; + } + + int getBackgroundPriority(int useCase) { + if (mPriorityHints.get(useCase) != null && mPriorityHints.get(useCase).length == 2) { + return mPriorityHints.get(useCase)[1]; + } + return mDefaultBackground; + } + + boolean isDefinedUseCase(int useCase) { + return (mVendorDefinedUseCase.contains(useCase) || isPredefinedUseCase(useCase)); + } + + /** + * To parse the vendor use case config. + */ + public void parse() { + // Override the default priority with vendor setting if available. + File file = new File(PATH_TO_VENDOR_CONFIG_XML); + if (file.exists()) { + try { + InputStream in = new FileInputStream(file); + parseInternal(in); + return; + } catch (IOException e) { + Slog.e(TAG, "Error reading vendor file: " + file, e); + } catch (XmlPullParserException e) { + Slog.e(TAG, "Unable to parse vendor file: " + file, e); + } + } else { + if (DEBUG) { + Slog.i(TAG, "no vendor priority configuration available. Using default priority"); + } + addNewUseCasePriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND, 180, 100); + addNewUseCasePriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN, 450, 200); + addNewUseCasePriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 480, 300); + addNewUseCasePriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, 490, 400); + addNewUseCasePriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD, 600, 500); + } + } + + // We don't use namespaces + private static final String NS = null; + + @VisibleForTesting + protected void parseInternal(InputStream in) + throws IOException, XmlPullParserException { + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + parser.setInput(in, null); + parser.nextTag(); + readUseCase(parser); + in.close(); + } catch (IOException | XmlPullParserException e) { + throw e; + } + for (int i = 0; i < mPriorityHints.size(); i++) { + int useCase = mPriorityHints.keyAt(i); + int[] priorities = mPriorityHints.get(useCase); + if (DEBUG) { + Slog.d(TAG, "{defaultFg=" + mDefaultForeground + + ", defaultBg=" + mDefaultBackground + "}"); + Slog.d(TAG, "{useCase=" + useCase + + ", fg=" + priorities[0] + + ", bg=" + priorities[1] + + "}"); + } + } + } + + private void readUseCase(XmlPullParser parser) + throws XmlPullParserException, IOException { + parser.require(XmlPullParser.START_TAG, NS, "config"); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + int useCase; + if (name.equals("useCaseDefault")) { + mDefaultForeground = readAttributeToInt("fgPriority", parser); + mDefaultBackground = readAttributeToInt("bgPriority", parser); + parser.nextTag(); + parser.require(XmlPullParser.END_TAG, NS, name); + } else if (name.equals("useCasePreDefined")) { + useCase = formatTypeToNum("type", parser); + if (useCase == INVALID_USE_CASE) { + Slog.e(TAG, "Wrong predefined use case name given in the vendor config."); + continue; + } + addNewUseCasePriority(useCase, + readAttributeToInt("fgPriority", parser), + readAttributeToInt("bgPriority", parser)); + parser.nextTag(); + parser.require(XmlPullParser.END_TAG, NS, name); + } else if (name.equals("useCaseVendor")) { + useCase = readAttributeToInt("id", parser); + addNewUseCasePriority(useCase, + readAttributeToInt("fgPriority", parser), + readAttributeToInt("bgPriority", parser)); + mVendorDefinedUseCase.add(useCase); + parser.nextTag(); + parser.require(XmlPullParser.END_TAG, NS, name); + } else { + skip(parser); + } + } + } + + private void skip(XmlPullParser parser) throws XmlPullParserException, IOException { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException(); + } + int depth = 1; + while (depth != 0) { + switch (parser.next()) { + case XmlPullParser.END_TAG: + depth--; + break; + case XmlPullParser.START_TAG: + depth++; + break; + } + } + } + + private int readAttributeToInt(String attributeName, XmlPullParser parser) { + return Integer.valueOf(parser.getAttributeValue(null, attributeName)); + } + + private void addNewUseCasePriority(int useCase, int fgPriority, int bgPriority) { + int[] priorities = {fgPriority, bgPriority}; + mPriorityHints.append(useCase, priorities); + } + + @PriorityHintUseCaseType + private static int formatTypeToNum(String attributeName, XmlPullParser parser) { + String useCaseName = parser.getAttributeValue(null, attributeName); + switch (useCaseName) { + case "USE_CASE_BACKGROUND": + return TvInputService.PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND; + case "USE_CASE_SCAN": + return TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN; + case "USE_CASE_PLAYBACK": + return TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK; + case "USE_CASE_LIVE": + return TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE; + case "USE_CASE_RECORD": + return TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD; + default: + return INVALID_USE_CASE; + } + } + + private static boolean isPredefinedUseCase(int useCase) { + switch (useCase) { + case TvInputService.PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND: + case TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN: + case TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK: + case TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE: + case TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD: + return true; + default: + return false; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java new file mode 100644 index 000000000000..192c6fe4ab75 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java @@ -0,0 +1,497 @@ +/* + * 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.tunerresourcemanager; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.media.tv.ITvInputManager; +import android.media.tv.TvInputManager; +import android.media.tv.TvInputService; +import android.media.tv.tuner.frontend.FrontendSettings; +import android.media.tv.tunerresourcemanager.ResourceClientProfile; +import android.media.tv.tunerresourcemanager.TunerFrontendInfo; +import android.media.tv.tunerresourcemanager.TunerFrontendRequest; +import android.media.tv.tunerresourcemanager.TunerResourceManager; +import android.os.RemoteException; +import android.util.SparseArray; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Tests for {@link TunerResourceManagerService} class. + */ +@SmallTest +@RunWith(JUnit4.class) +public class TunerResourceManagerServiceTest { + private static final String TAG = "TunerResourceManagerServiceTest"; + private Context mContextSpy; + @Mock private ITvInputManager mITvInputManagerMock; + private TunerResourceManagerService mTunerResourceManagerService; + private int mReclaimingId; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + TvInputManager tvInputManager = new TvInputManager(mITvInputManagerMock, 0); + mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + when(mContextSpy.getSystemService(Context.TV_INPUT_SERVICE)).thenReturn(tvInputManager); + mTunerResourceManagerService = new TunerResourceManagerService(mContextSpy) { + @Override + protected void reclaimFrontendResource(int reclaimingId) { + mReclaimingId = reclaimingId; + } + }; + mTunerResourceManagerService.onStart(true /*isForTesting*/); + mReclaimingId = -1; + } + + @Test + public void setFrontendListTest_addFrontendResources_noExclusiveGroupId() { + // Init frontend resources. + TunerFrontendInfo[] infos = new TunerFrontendInfo[2]; + infos[0] = + new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 0 /*exclusiveGroupId*/); + infos[1] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + + SparseArray<FrontendResource> resources = + mTunerResourceManagerService.getFrontendResources(); + assertThat(resources.size()).isEqualTo(infos.length); + for (int id = 0; id < infos.length; id++) { + FrontendResource fe = resources.get(infos[id].getId()); + assertThat(fe.getId()).isEqualTo(infos[id].getId()); + assertThat(fe.getType()).isEqualTo(infos[id].getFrontendType()); + assertThat(fe.getExclusiveGroupId()).isEqualTo(infos[id].getExclusiveGroupId()); + assertThat(fe.getExclusiveGroupMemberFeIds().size()).isEqualTo(0); + } + } + + @Test + public void setFrontendListTest_addFrontendResources_underTheSameExclusiveGroupId() { + // Init frontend resources. + TunerFrontendInfo[] infos = new TunerFrontendInfo[4]; + infos[0] = + new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 0 /*exclusiveGroupId*/); + infos[1] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + infos[2] = + new TunerFrontendInfo(2 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); + infos[3] = + new TunerFrontendInfo(3 /*id*/, FrontendSettings.TYPE_ATSC, 1 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + + SparseArray<FrontendResource> resources = + mTunerResourceManagerService.getFrontendResources(); + assertThat(resources.size()).isEqualTo(infos.length); + for (int id = 0; id < infos.length; id++) { + FrontendResource fe = resources.get(infos[id].getId()); + assertThat(fe.getId()).isEqualTo(infos[id].getId()); + assertThat(fe.getType()).isEqualTo(infos[id].getFrontendType()); + assertThat(fe.getExclusiveGroupId()).isEqualTo(infos[id].getExclusiveGroupId()); + } + + assertThat(resources.get(0).getExclusiveGroupMemberFeIds()) + .isEqualTo(new ArrayList<Integer>()); + assertThat(resources.get(1).getExclusiveGroupMemberFeIds()) + .isEqualTo(new ArrayList<Integer>(Arrays.asList(2, 3))); + assertThat(resources.get(2).getExclusiveGroupMemberFeIds()) + .isEqualTo(new ArrayList<Integer>(Arrays.asList(1, 3))); + assertThat(resources.get(3).getExclusiveGroupMemberFeIds()) + .isEqualTo(new ArrayList<Integer>(Arrays.asList(1, 2))); + } + + @Test + public void setFrontendListTest_updateExistingFrontendResources() { + // Init frontend resources. + TunerFrontendInfo[] infos = new TunerFrontendInfo[2]; + infos[0] = + new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + infos[1] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); + + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + SparseArray<FrontendResource> resources0 = + mTunerResourceManagerService.getFrontendResources(); + + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + SparseArray<FrontendResource> resources1 = + mTunerResourceManagerService.getFrontendResources(); + + assertThat(resources0).isEqualTo(resources1); + } + + @Test + public void setFrontendListTest_removeFrontendResources_noExclusiveGroupId() { + // Init frontend resources. + TunerFrontendInfo[] infos0 = new TunerFrontendInfo[3]; + infos0[0] = + new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 0 /*exclusiveGroupId*/); + infos0[1] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + infos0[2] = + new TunerFrontendInfo(2 /*id*/, FrontendSettings.TYPE_DVBS, 2 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos0); + + TunerFrontendInfo[] infos1 = new TunerFrontendInfo[1]; + infos1[0] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos1); + + SparseArray<FrontendResource> resources = + mTunerResourceManagerService.getFrontendResources(); + assertThat(resources.size()).isEqualTo(infos1.length); + for (int id = 0; id < infos1.length; id++) { + FrontendResource fe = resources.get(infos1[id].getId()); + assertThat(fe.getId()).isEqualTo(infos1[id].getId()); + assertThat(fe.getType()).isEqualTo(infos1[id].getFrontendType()); + assertThat(fe.getExclusiveGroupId()).isEqualTo(infos1[id].getExclusiveGroupId()); + assertThat(fe.getExclusiveGroupMemberFeIds().size()).isEqualTo(0); + } + } + + @Test + public void setFrontendListTest_removeFrontendResources_underTheSameExclusiveGroupId() { + // Init frontend resources. + TunerFrontendInfo[] infos0 = new TunerFrontendInfo[3]; + infos0[0] = + new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 0 /*exclusiveGroupId*/); + infos0[1] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + infos0[2] = + new TunerFrontendInfo(2 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos0); + + TunerFrontendInfo[] infos1 = new TunerFrontendInfo[1]; + infos1[0] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos1); + + SparseArray<FrontendResource> resources = + mTunerResourceManagerService.getFrontendResources(); + assertThat(resources.size()).isEqualTo(infos1.length); + for (int id = 0; id < infos1.length; id++) { + FrontendResource fe = resources.get(infos1[id].getId()); + assertThat(fe.getId()).isEqualTo(infos1[id].getId()); + assertThat(fe.getType()).isEqualTo(infos1[id].getFrontendType()); + assertThat(fe.getExclusiveGroupId()).isEqualTo(infos1[id].getExclusiveGroupId()); + assertThat(fe.getExclusiveGroupMemberFeIds().size()).isEqualTo(0); + } + } + + @Test + public void requestFrontendTest_ClientNotRegistered() { + TunerFrontendRequest request = + new TunerFrontendRequest(0 /*clientId*/, FrontendSettings.TYPE_DVBT); + int[] frontendId = new int[1]; + try { + assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId)) + .isFalse(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + assertThat(frontendId[0]).isEqualTo(TunerResourceManager.INVALID_FRONTEND_ID); + } + + @Test + public void requestFrontendTest_NoFrontendWithGiveTypeAvailable() { + ResourceClientProfile profile = new ResourceClientProfile("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + int[] clientId = new int[1]; + mTunerResourceManagerService.registerClientProfileInternal( + profile, null /*listener*/, clientId); + assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + + // Init frontend resources. + TunerFrontendInfo[] infos = new TunerFrontendInfo[1]; + infos[0] = + new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBS, 0 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + + TunerFrontendRequest request = + new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + int[] frontendId = new int[1]; + try { + assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId)) + .isFalse(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + assertThat(frontendId[0]).isEqualTo(TunerResourceManager.INVALID_FRONTEND_ID); + } + + @Test + public void requestFrontendTest_FrontendWithNoExclusiveGroupAvailable() { + ResourceClientProfile profile = new ResourceClientProfile("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + int[] clientId = new int[1]; + mTunerResourceManagerService.registerClientProfileInternal( + profile, null /*listener*/, clientId); + assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + + // Init frontend resources. + TunerFrontendInfo[] infos = new TunerFrontendInfo[3]; + infos[0] = + new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 0 /*exclusiveGroupId*/); + infos[1] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + infos[2] = + new TunerFrontendInfo(2 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + + TunerFrontendRequest request = + new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + int[] frontendId = new int[1]; + try { + assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId)) + .isTrue(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + assertThat(frontendId[0]).isEqualTo(0); + } + + @Test + public void requestFrontendTest_FrontendWithExclusiveGroupAvailable() { + ResourceClientProfile profile0 = new ResourceClientProfile("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + ResourceClientProfile profile1 = new ResourceClientProfile("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + int[] clientId0 = new int[1]; + int[] clientId1 = new int[1]; + mTunerResourceManagerService.registerClientProfileInternal( + profile0, null /*listener*/, clientId0); + mTunerResourceManagerService.registerClientProfileInternal( + profile1, null /*listener*/, clientId1); + assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + + // Init frontend resources. + TunerFrontendInfo[] infos = new TunerFrontendInfo[3]; + infos[0] = + new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 0 /*exclusiveGroupId*/); + infos[1] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + infos[2] = + new TunerFrontendInfo(2 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + + int[] frontendId = new int[1]; + TunerFrontendRequest request = + new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + try { + assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId)) + .isTrue(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + assertThat(frontendId[0]).isEqualTo(infos[0].getId()); + + request = + new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + try { + assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId)) + .isTrue(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + assertThat(frontendId[0]).isEqualTo(infos[1].getId()); + assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[1].getId()) + .isInUse()).isTrue(); + assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[2].getId()) + .isInUse()).isTrue(); + } + + @Test + public void requestFrontendTest_NoFrontendAvailable_RequestWithLowerPriority() { + // Register clients + ResourceClientProfile[] profiles = new ResourceClientProfile[2]; + profiles[0] = new ResourceClientProfile("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + profiles[1] = new ResourceClientProfile("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + int[] clientPriorities = {100, 50}; + int[] clientId0 = new int[1]; + int[] clientId1 = new int[1]; + mTunerResourceManagerService.registerClientProfileInternal( + profiles[0], null /*listener*/, clientId0); + assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + mTunerResourceManagerService.getClientProfiles().get(clientId0[0]) + .setPriority(clientPriorities[0]); + mTunerResourceManagerService.registerClientProfileInternal( + profiles[1], null /*listener*/, clientId1); + assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + mTunerResourceManagerService.getClientProfiles().get(clientId1[0]) + .setPriority(clientPriorities[1]); + + // Init frontend resources. + TunerFrontendInfo[] infos = new TunerFrontendInfo[2]; + infos[0] = + new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + infos[1] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + + TunerFrontendRequest request = + new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + int[] frontendId = new int[1]; + try { + assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId)) + .isTrue(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + request = + new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + try { + assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId)) + .isFalse(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + assertThat(mReclaimingId).isEqualTo(-1); + + request = + new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS); + try { + assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId)) + .isFalse(); + assertThat(mReclaimingId).isEqualTo(-1); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Test + public void requestFrontendTest_NoFrontendAvailable_RequestWithHigherPriority() { + // Register clients + ResourceClientProfile[] profiles = new ResourceClientProfile[2]; + profiles[0] = new ResourceClientProfile("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + profiles[1] = new ResourceClientProfile("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + int[] clientPriorities = {100, 500}; + int[] clientId0 = new int[1]; + int[] clientId1 = new int[1]; + mTunerResourceManagerService.registerClientProfileInternal( + profiles[0], null /*listener*/, clientId0); + assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + mTunerResourceManagerService.getClientProfiles().get(clientId0[0]) + .setPriority(clientPriorities[0]); + mTunerResourceManagerService.registerClientProfileInternal( + profiles[1], null /*listener*/, clientId1); + assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + mTunerResourceManagerService.getClientProfiles().get(clientId1[0]) + .setPriority(clientPriorities[1]); + + // Init frontend resources. + TunerFrontendInfo[] infos = new TunerFrontendInfo[2]; + infos[0] = + new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + infos[1] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + + TunerFrontendRequest request = + new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + int[] frontendId = new int[1]; + try { + assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId)) + .isTrue(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + assertThat(frontendId[0]).isEqualTo(infos[0].getId()); + + request = + new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS); + try { + assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId)) + .isTrue(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + assertThat(frontendId[0]).isEqualTo(infos[1].getId()); + assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[0].getId()) + .isInUse()).isTrue(); + assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[1].getId()) + .isInUse()).isTrue(); + assertThat(mTunerResourceManagerService.getFrontendResources() + .get(infos[0].getId()).getOwnerClientId()).isEqualTo(clientId1[0]); + assertThat(mTunerResourceManagerService.getFrontendResources() + .get(infos[1].getId()).getOwnerClientId()).isEqualTo(clientId1[0]); + assertThat(mReclaimingId).isEqualTo(clientId0[0]); + } + + @Test + public void unregisterClientTest_usingFrontend() { + // Register client + ResourceClientProfile profile = new ResourceClientProfile("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + int[] clientId = new int[1]; + mTunerResourceManagerService.registerClientProfileInternal( + profile, null /*listener*/, clientId); + assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + + // Init frontend resources. + TunerFrontendInfo[] infos = new TunerFrontendInfo[2]; + infos[0] = + new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + infos[1] = + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + + TunerFrontendRequest request = + new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + int[] frontendId = new int[1]; + try { + assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendId)) + .isTrue(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + assertThat(frontendId[0]).isEqualTo(infos[0].getId()); + assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[0].getId()) + .isInUse()).isTrue(); + assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[1].getId()) + .isInUse()).isTrue(); + + // Unregister client when using frontend + mTunerResourceManagerService.unregisterClientProfileInternal(clientId[0]); + assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[0].getId()) + .isInUse()).isFalse(); + assertThat(mTunerResourceManagerService.getFrontendResources().get(infos[1].getId()) + .isInUse()).isFalse(); + + } +} diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/UseCasePriorityHintsTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/UseCasePriorityHintsTest.java new file mode 100644 index 000000000000..ab5665ba99fc --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/UseCasePriorityHintsTest.java @@ -0,0 +1,111 @@ +/* + * 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.tunerresourcemanager; + +import static com.google.common.truth.Truth.assertThat; + +import android.media.tv.TvInputService; +import android.util.Slog; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Tests for {@link UseCasePriorityHints} class. + */ +@SmallTest +@RunWith(JUnit4.class) +public class UseCasePriorityHintsTest { + private static final String TAG = "UseCasePriorityHintsTest"; + private UseCasePriorityHints mPriorityHints; + + private final String mExampleXML = + "<!-- A sample Use Case Priority Hints xml -->" + + "<config version=\"1.0\" xmlns:xi=\"http://www.w3.org/2001/XInclude\">" + + "<useCaseDefault fgPriority=\"150\" bgPriority=\"50\"/>" + + "<useCasePreDefined type=\"USE_CASE_RECORD\" fgPriority=\"600\" bgPriority=\"500\"/>" + + "<useCasePreDefined type=\"USE_CASE_LIVE\" fgPriority=\"490\" bgPriority=\"400\"/>" + + "<useCasePreDefined type=\"USE_CASE_PLAYBACK\" fgPriority=\"480\"" + + " bgPriority=\"300\"/>" + + "<useCasePreDefined type=\"USE_CASE_BACKGROUND\" fgPriority=\"180\"" + + " bgPriority=\"100\"/>" + + "<useCaseVendor type=\"VENDOR_USE_CASE_1\" id=\"1001\" fgPriority=\"300\"" + + " bgPriority=\"80\"/>" + + "</config>"; + + @Before + public void setUp() throws Exception { + mPriorityHints = new UseCasePriorityHints(); + try { + mPriorityHints.parseInternal( + new ByteArrayInputStream(mExampleXML.getBytes(StandardCharsets.UTF_8))); + } catch (IOException | XmlPullParserException e) { + Slog.e(TAG, "Error parse xml.", e); + } + } + + @Test + public void parseTest_parseSampleXml() { + // Pre-defined foreground + assertThat(mPriorityHints.getForegroundPriority( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND)).isEqualTo(180); + assertThat(mPriorityHints.getForegroundPriority( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN)).isEqualTo(150); + assertThat(mPriorityHints.getForegroundPriority( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK)).isEqualTo(480); + assertThat(mPriorityHints.getForegroundPriority( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE)).isEqualTo(490); + assertThat(mPriorityHints.getForegroundPriority( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD)).isEqualTo(600); + + // Pre-defined background + assertThat(mPriorityHints.getBackgroundPriority( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND)).isEqualTo(100); + assertThat(mPriorityHints.getBackgroundPriority( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN)).isEqualTo(50); + assertThat(mPriorityHints.getBackgroundPriority( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK)).isEqualTo(300); + assertThat(mPriorityHints.getBackgroundPriority( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE)).isEqualTo(400); + assertThat(mPriorityHints.getBackgroundPriority( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD)).isEqualTo(500); + + // Vendor use case + assertThat(mPriorityHints.getForegroundPriority(1001)).isEqualTo(300); + assertThat(mPriorityHints.getBackgroundPriority(1001)).isEqualTo(80); + } + + @Test + public void isDefinedUseCaseTest_invalidUseCase() { + assertThat(mPriorityHints.isDefinedUseCase(1992)).isFalse(); + } + + @Test + public void isDefinedUseCaseTest_validUseCase() { + assertThat(mPriorityHints.isDefinedUseCase(1001)).isTrue(); + assertThat(mPriorityHints.isDefinedUseCase( + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD)).isTrue(); + } +} |