From 9796ba2f6d176d552e17f41191014aa0b317a165 Mon Sep 17 00:00:00 2001 From: Amy Date: Fri, 14 Feb 2020 19:19:38 -0800 Subject: [PATCH 1/4] Implement TRM setFrontendInfoList logic with unit tests Note that this CL includes some format changes and redundant implementation removing in ClientProfile.java and TunerResourceManagerService.java. The key change of the CL is the FrontendResource.java object and the impl of the setFrontendResourceList method in TunerResourceManagerService.java. Test: atest TunerResourceManagerServiceTest Bug: 147380513 Change-Id: I3df8e386ad37bc67694594b07de04cc9f3b88e02 (cherry picked from commit caf201a8e72583dc6d1c717bbc252e5178219671) --- .../tunerresourcemanager/ClientProfile.java | 70 ++---- .../FrontendResource.java | 204 ++++++++++++++++++ .../TunerResourceManagerService.java | 183 +++++++++++----- .../TunerResourceManagerServiceTest.java | 190 ++++++++++++++++ 4 files changed, 541 insertions(+), 106 deletions(-) create mode 100644 services/core/java/com/android/server/tv/tunerresourcemanager/FrontendResource.java create mode 100644 services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java 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 bad2b78dab48b..07c565a729bbe 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.server.tv.tunerresourcemanager; /** @@ -23,12 +22,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 +42,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; @@ -66,18 +67,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() { @@ -118,24 +116,21 @@ public final class ClientProfile { @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 +138,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 +148,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 +158,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 0000000000000..a109265e2f502 --- /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 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 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 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 49a7045bf57a7..c91a9297eb17a 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -33,28 +33,40 @@ 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 mClientProfiles = new SparseArray<>(); - private SparseArray mListeners = new SparseArray<>(); - private int mNextUnusedFrontendId = 0; + // Array of the registered client profiles + @VisibleForTesting private SparseArray mClientProfiles = new SparseArray<>(); + private int mNextUnusedClientId = 0; private List mReleasedClientId = new ArrayList(); + + // Array of the current available frontend resources + @VisibleForTesting + private SparseArray mFrontendResources = new SparseArray<>(); + @VisibleForTesting private SparseArray mFrontendTypeMap = new SparseArray<>(); + // Array of the current available frontend ids private List mAvailableFrontendIds = new ArrayList(); + private SparseArray mListeners = new SparseArray<>(); + private TvInputManager mManager; + // Used to synchronize the access to the service. + private final Object mLock = new Object(); + public TunerResourceManagerService(@Nullable Context context) { super(context); } @@ -62,22 +74,20 @@ 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); + mManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE); } private final class BinderService extends ITunerResourceManager.Stub { @Override public void registerClientProfile(@NonNull ResourceClientProfile profile, - @NonNull IResourcesReclaimListener listener, - @NonNull int[] clientId) { + @NonNull IResourcesReclaimListener 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++; + clientId[0] = mNextUnusedClientId++; } else { clientId[0] = mReleasedClientId.get(0); mReleasedClientId.remove(0); @@ -90,12 +100,11 @@ public class TunerResourceManagerService extends SystemService { int callingPid = mManager.getClientPid(profile.getTvInputSessionId()); - ClientProfile clientProfile = new ClientProfile.ClientProfileBuilder( - clientId[0]) - .tvInputSessionId(profile.getTvInputSessionId()) - .useCase(profile.getUseCase()) - .processId(callingPid) - .build(); + ClientProfile clientProfile = new ClientProfile.Builder(clientId[0]) + .tvInputSessionId(profile.getTvInputSessionId()) + .useCase(profile.getUseCase()) + .processId(callingPid) + .build(); mClientProfiles.append(clientId[0], clientProfile); mListeners.append(clientId[0], listener); } @@ -114,14 +123,16 @@ public class TunerResourceManagerService extends SystemService { @Override public boolean updateClientPriority(int clientId, int priority, int niceValue) { if (DEBUG) { - Slog.d(TAG, "updateClientPriority(clientId=" + clientId - + ", priority=" + priority + ", niceValue=" + niceValue + ")"); + 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."); + Slog.e(TAG, + "Can not find client profile with id " + clientId + + " when trying to update the client priority."); return false; } @@ -132,26 +143,22 @@ public class TunerResourceManagerService extends SystemService { } @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,26 +173,19 @@ public class TunerResourceManagerService extends SystemService { @Override public boolean requestFrontend(@NonNull TunerFrontendRequest request, - @NonNull int[] frontendId) throws RemoteException { + @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()); + Slog.e(TAG, "Request from unregistered client. Id: " + request.getClientId()); return false; } - String sessionId = mClientProfiles.get(request.getClientId()) - .getTvInputSessionId(); + String sessionId = mClientProfiles.get(request.getClientId()).getTvInputSessionId(); if (DEBUG) { Slog.d(TAG, "session Id:" + sessionId + ")"); @@ -201,14 +201,13 @@ public class TunerResourceManagerService extends SystemService { @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 +245,91 @@ 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 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 updatingFrontendIds = new ArrayList<>(mAvailableFrontendIds); + + // Update frontendResources sparse array and other mappings accordingly + for (int i = 0; i < infos.length; i++) { + if (mFrontendResources.get(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 = mFrontendResources.get(feId.intValue()); + if (fe.getExclusiveGroupId() == newFe.getExclusiveGroupId()) { + newFe.addExclusiveGroupMemberFeId(fe.getId()); + newFe.addExclusiveGroupMemberFeId(fe.getExclusiveGroupMemberFeIds()); + for (Integer excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) { + mFrontendResources.get(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 = mFrontendResources.get(removingId.intValue()); + fe.removeExclusiveGroupMemberFeId(new Integer(fe.getId())); + for (Integer excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) { + mFrontendResources.get(excGroupmemberFeId.intValue()) + .removeExclusiveGroupMemberFeId(new Integer(fe.getId())); + } + mFrontendResources.remove(removingId.intValue()); + mAvailableFrontendIds.remove(removingId); + } + for (int i = 0; i < mFrontendResources.size(); i++) { + int key = mFrontendResources.keyAt(i); + // get the object by the key. + FrontendResource r = mFrontendResources.get(key); + } + } + + @VisibleForTesting + protected SparseArray getFrontendResources() { + return mFrontendResources; + } + + private void enforceAccessPermission() { + getContext().enforceCallingOrSelfPermission( + "android.permission.TUNER_RESOURCE_ACCESS", TAG); + } } 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 0000000000000..02060ee03d86c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java @@ -0,0 +1,190 @@ +/* + * 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 android.content.Context; +import android.content.ContextWrapper; +import android.media.tv.tuner.frontend.FrontendSettings; +import android.media.tv.tunerresourcemanager.TunerFrontendInfo; +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.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; + private TunerResourceManagerService mTunerResourceManagerService; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + mTunerResourceManagerService = new TunerResourceManagerService(mContextSpy) {}; + } + + @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 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_DVBT, 1 /*exclusiveGroupId*/); + infos[3] = + new TunerFrontendInfo(3 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + + SparseArray 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()); + assertThat(resources.get(1).getExclusiveGroupMemberFeIds()) + .isEqualTo(new ArrayList(Arrays.asList(2, 3))); + assertThat(resources.get(2).getExclusiveGroupMemberFeIds()) + .isEqualTo(new ArrayList(Arrays.asList(1, 3))); + assertThat(resources.get(3).getExclusiveGroupMemberFeIds()) + .isEqualTo(new ArrayList(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_DVBT, 1 /*exclusiveGroupId*/); + + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + SparseArray resources0 = + mTunerResourceManagerService.getFrontendResources(); + + mTunerResourceManagerService.setFrontendInfoListInternal(infos); + SparseArray 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_DVBT, 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 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_DVBT, 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 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); + } + } +} From 38ff0181c3ab1db077f160d9d940048ced73b81e Mon Sep 17 00:00:00 2001 From: Amy Date: Tue, 18 Feb 2020 21:12:04 -0800 Subject: [PATCH 2/4] Add requestFrontend implementation in TunerResourceManager Test: atest TunerResourceManagerServiceTest Bug: 147380513 Change-Id: Icec048913e2cbe141448c0c322d37c233588dfcd (cherry picked from commit 9d150f58bd4e1994c406c7f0034ce7b3c664eace) --- .../ResourceClientProfile.java | 3 +- .../TunerResourceManagerService.java | 278 +++++++++++++----- .../TunerResourceManagerServiceTest.java | 277 ++++++++++++++++- 3 files changed, 481 insertions(+), 77 deletions(-) diff --git a/media/java/android/media/tv/tunerresourcemanager/ResourceClientProfile.java b/media/java/android/media/tv/tunerresourcemanager/ResourceClientProfile.java index 68372444cb837..598ff8f3f0758 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/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index c91a9297eb17a..ede620257dc47 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -48,6 +48,9 @@ public class TunerResourceManagerService extends SystemService { private static final String TAG = "TunerResourceManagerService"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + 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 mClientProfiles = new SparseArray<>(); private int mNextUnusedClientId = 0; @@ -56,7 +59,6 @@ public class TunerResourceManagerService extends SystemService { // Array of the current available frontend resources @VisibleForTesting private SparseArray mFrontendResources = new SparseArray<>(); - @VisibleForTesting private SparseArray mFrontendTypeMap = new SparseArray<>(); // Array of the current available frontend ids private List mAvailableFrontendIds = new ArrayList(); @@ -73,40 +75,34 @@ public class TunerResourceManagerService extends SystemService { @Override public void onStart() { - publishBinderService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, new BinderService()); + 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); } 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] = mNextUnusedClientId++; - } else { - clientId[0] = mReleasedClientId.get(0); - mReleasedClientId.remove(0); + if (clientId == null || clientId.length != 1) { + throw new RemoteException("clientId must be a size 1 array!"); } - if (mManager == null) { - Slog.e(TAG, "TvInputManager is null. Can't register client profile."); - return; + synchronized (mLock) { + registerClientProfileInternal(profile, listener, clientId); } - - int callingPid = mManager.getClientPid(profile.getTvInputSessionId()); - - ClientProfile clientProfile = new ClientProfile.Builder(clientId[0]) - .tvInputSessionId(profile.getTvInputSessionId()) - .useCase(profile.getUseCase()) - .processId(callingPid) - .build(); - mClientProfiles.append(clientId[0], clientProfile); - mListeners.append(clientId[0], listener); } @Override @@ -122,24 +118,11 @@ public class TunerResourceManagerService extends SystemService { @Override public boolean updateClientPriority(int clientId, int priority, int niceValue) { - if (DEBUG) { - Slog.d(TAG, - "updateClientPriority(clientId=" + clientId + ", priority=" + priority - + ", niceValue=" + niceValue + ")"); + enforceAccessPermission(); + + synchronized (mLock) { + return updateClientPriorityInternal(clientId, priority, 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 @@ -174,28 +157,17 @@ 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 + ")"); + enforceAccessPermission(); + if (frontendId == null) { + throw new RemoteException("frontendId can't be null"); } - - frontendId[0] = TunerResourceManager.INVALID_FRONTEND_ID; - - if (mClientProfiles.get(request.getClientId()) == null) { - Slog.e(TAG, "Request from unregistered client. Id: " + request.getClientId()); - return false; + synchronized (mLock) { + try { + return requestFrontendInternal(request, frontendId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } - - 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 @@ -256,6 +228,61 @@ public class TunerResourceManagerService extends SystemService { } } + @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 + if (mReleasedClientId.isEmpty()) { + clientId[0] = mNextUnusedClientId++; + } else { + clientId[0] = mReleasedClientId.get(0); + mReleasedClientId.remove(0); + } + + int callingPid = mManager.getClientPid(profile.getTvInputSessionId()); + + ClientProfile clientProfile = new ClientProfile.Builder(clientId[0]) + .tvInputSessionId(profile.getTvInputSessionId()) + .useCase(profile.getUseCase()) + .processId(callingPid) + .build(); + clientProfile.setPriority(getClientPriority(profile.getUseCase(), callingPid)); + + mClientProfiles.append(clientId[0], clientProfile); + mListeners.append(clientId[0], listener); + } + + @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) { @@ -273,7 +300,7 @@ public class TunerResourceManagerService extends SystemService { // Update frontendResources sparse array and other mappings accordingly for (int i = 0; i < infos.length; i++) { - if (mFrontendResources.get(infos[i].getId()) != null) { + if (getFrontendResource(infos[i].getId()) != null) { if (DEBUG) { Slog.d(TAG, "Frontend id=" + infos[i].getId() + "exists."); } @@ -286,12 +313,12 @@ public class TunerResourceManagerService extends SystemService { .build(); // Update the exclusive group member list in all the existing Frontend resource for (Integer feId : mAvailableFrontendIds) { - FrontendResource fe = mFrontendResources.get(feId.intValue()); + FrontendResource fe = getFrontendResource(feId.intValue()); if (fe.getExclusiveGroupId() == newFe.getExclusiveGroupId()) { newFe.addExclusiveGroupMemberFeId(fe.getId()); newFe.addExclusiveGroupMemberFeId(fe.getExclusiveGroupMemberFeIds()); for (Integer excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) { - mFrontendResources.get(excGroupmemberFeId.intValue()) + getFrontendResource(excGroupmemberFeId.intValue()) .addExclusiveGroupMemberFeId(newFe.getId()); } fe.addExclusiveGroupMemberFeId(newFe.getId()); @@ -307,20 +334,131 @@ public class TunerResourceManagerService extends SystemService { // 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 = mFrontendResources.get(removingId.intValue()); + FrontendResource fe = getFrontendResource(removingId.intValue()); fe.removeExclusiveGroupMemberFeId(new Integer(fe.getId())); for (Integer excGroupmemberFeId : fe.getExclusiveGroupMemberFeIds()) { - mFrontendResources.get(excGroupmemberFeId.intValue()) + getFrontendResource(excGroupmemberFeId.intValue()) .removeExclusiveGroupMemberFeId(new Integer(fe.getId())); } mFrontendResources.remove(removingId.intValue()); mAvailableFrontendIds.remove(removingId); } - for (int i = 0; i < mFrontendResources.size(); i++) { - int key = mFrontendResources.keyAt(i); - // get the object by the key. - FrontendResource r = mFrontendResources.get(key); + } + + @VisibleForTesting + protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendId) + throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "requestFrontend(request=" + request + ")"); } + + frontendId[0] = TunerResourceManager.INVALID_FRONTEND_ID; + ClientProfile requestClient = getClientProfile(request.getClientId()); + if (requestClient == null) { + Slog.e(TAG, "Request from unregistered client. Id: " + request.getClientId()); + return false; + } + + 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; + updateFrontendResourcesOnNewGrant(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()); + updateFrontendResourcesOnNewGrant(frontendId[0], request.getClientId()); + return true; + } + + return false; + } + + @VisibleForTesting + protected int getClientPriority(int useCase, int callingPid) { + // TODO: how to get fg/bg information from pid + if (DEBUG) { + Slog.d(TAG, "getClientPriority useCase=" + useCase + + ", calling Pid=" + callingPid + ")"); + } + + // TODO: get priority from the table built from the useCase config xml + return 0; + } + + @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 updateFrontendResourcesOnNewGrant(int grantingId, int ownerClientId) { + FrontendResource grantingFrontend = getFrontendResource(grantingId); + grantingFrontend.setOwner(ownerClientId); + for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeIds()) { + getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId); + } + } + + /** + * 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 getClientProfiles() { + return mClientProfiles; } @VisibleForTesting 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 index 02060ee03d86c..72fbaef339a38 100644 --- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java @@ -18,11 +18,19 @@ 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; @@ -32,6 +40,7 @@ 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; @@ -45,13 +54,24 @@ import java.util.Arrays; 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())); - mTunerResourceManagerService = new TunerResourceManagerService(mContextSpy) {}; + 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 @@ -85,9 +105,9 @@ public class TunerResourceManagerServiceTest { infos[1] = new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); infos[2] = - new TunerFrontendInfo(2 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + new TunerFrontendInfo(2 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); infos[3] = - new TunerFrontendInfo(3 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + new TunerFrontendInfo(3 /*id*/, FrontendSettings.TYPE_ATSC, 1 /*exclusiveGroupId*/); mTunerResourceManagerService.setFrontendInfoListInternal(infos); SparseArray resources = @@ -117,7 +137,7 @@ public class TunerResourceManagerServiceTest { infos[0] = new TunerFrontendInfo(0 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); infos[1] = - new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); mTunerResourceManagerService.setFrontendInfoListInternal(infos); SparseArray resources0 = @@ -139,7 +159,7 @@ public class TunerResourceManagerServiceTest { infos0[1] = new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); infos0[2] = - new TunerFrontendInfo(2 /*id*/, FrontendSettings.TYPE_DVBT, 2 /*exclusiveGroupId*/); + new TunerFrontendInfo(2 /*id*/, FrontendSettings.TYPE_DVBS, 2 /*exclusiveGroupId*/); mTunerResourceManagerService.setFrontendInfoListInternal(infos0); TunerFrontendInfo[] infos1 = new TunerFrontendInfo[1]; @@ -168,7 +188,7 @@ public class TunerResourceManagerServiceTest { infos0[1] = new TunerFrontendInfo(1 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); infos0[2] = - new TunerFrontendInfo(2 /*id*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/); + new TunerFrontendInfo(2 /*id*/, FrontendSettings.TYPE_DVBS, 1 /*exclusiveGroupId*/); mTunerResourceManagerService.setFrontendInfoListInternal(infos0); TunerFrontendInfo[] infos1 = new TunerFrontendInfo[1]; @@ -187,4 +207,249 @@ public class TunerResourceManagerServiceTest { 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]); + } } From abdf7b6a5735789455157c351a65497006c847ba Mon Sep 17 00:00:00 2001 From: Amy Date: Wed, 19 Feb 2020 19:40:30 -0800 Subject: [PATCH 3/4] Add a TRM use case priority hints info object This object includes a parser that reads the vendor config xml Test: atest UseCasePriorityHintsTest Bug: 147380513 Change-Id: I4bff0bc196a0c2a78ce4305acec7ce7cc9a4fa70 (cherry picked from commit 2f62bf64ca8be21d6cc222f061250b0ef56b2ffe) --- .../TunerResourceManagerService.java | 15 +- .../UseCasePriorityHints.java | 234 ++++++++++++++++++ .../UseCasePriorityHintsTest.java | 100 ++++++++ 3 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java create mode 100644 services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/UseCasePriorityHintsTest.java 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 ede620257dc47..621f2b024e843 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -65,6 +65,7 @@ public class TunerResourceManagerService extends SystemService { private SparseArray 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(); @@ -84,6 +85,7 @@ public class TunerResourceManagerService extends SystemService { 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 { @@ -409,14 +411,21 @@ public class TunerResourceManagerService extends SystemService { @VisibleForTesting protected int getClientPriority(int useCase, int callingPid) { - // TODO: how to get fg/bg information from pid if (DEBUG) { Slog.d(TAG, "getClientPriority useCase=" + useCase + ", calling Pid=" + callingPid + ")"); } - // TODO: get priority from the table built from the useCase config xml - return 0; + if (isForeground(callingPid)) { + return mPriorityCongfig.getForegroundPriority(useCase); + } + return mPriorityCongfig.getBackgroundPriority(useCase); + } + + @VisibleForTesting + protected boolean isForeground(int callingPid) { + // TODO: how to get fg/bg information from pid + return true; } @VisibleForTesting 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 0000000000000..e6d672d9acbc5 --- /dev/null +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java @@ -0,0 +1,234 @@ +/* + * 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 mPriorityHints = new SparseArray<>(); + + List 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/UseCasePriorityHintsTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/UseCasePriorityHintsTest.java new file mode 100644 index 0000000000000..8a75d5c2a3804 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/UseCasePriorityHintsTest.java @@ -0,0 +1,100 @@ +/* + * 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 = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + + @Before + public void setUp() throws Exception { + mPriorityHints = new UseCasePriorityHints(); + } + + @Test + public void parseTest_parseSampleXml() { + try { + mPriorityHints.parseInternal( + new ByteArrayInputStream(mExampleXML.getBytes(StandardCharsets.UTF_8))); + } catch (IOException | XmlPullParserException e) { + Slog.e(TAG, "Error parse xml.", e); + } + + // 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); + } +} From 630fc01bf6644833c35747e1dfd5c781ab20c53b Mon Sep 17 00:00:00 2001 From: Amy Date: Thu, 20 Feb 2020 20:02:42 -0800 Subject: [PATCH 4/4] Fix issues in register/unregister client API implementation Issues include: 1. clientId array size checking too hard 2. missed use case validation 3. missed in use resources update when unregistering client(same in on binder dead, will be in separate CL) 4. Calling PID should have only be used when tvinputsession id is null 5. Adding resources list to client profile Test: atest TunerResourceManagerServiceTest Bug: 147380513 Change-Id: I09b4105659a491b3c07e3b737fe0617b83d24447 (cherry picked from commit 19b71275e0880b7963f842a1287173db816b40ab) --- .../tunerresourcemanager/ClientProfile.java | 32 +++++++ .../TunerResourceManagerService.java | 87 ++++++++++++------- .../UseCasePriorityHints.java | 6 +- .../TunerResourceManagerServiceTest.java | 42 +++++++++ .../UseCasePriorityHintsTest.java | 19 +++- 5 files changed, 149 insertions(+), 37 deletions(-) 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 07c565a729bbe..4eff954f1ec88 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java @@ -15,6 +15,9 @@ */ 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. @@ -59,6 +62,11 @@ public final class ClientProfile { */ private int mNiceValue; + /** + * List of the frontend ids that are used by the current client. + */ + private List mUsingFrontendIds = new ArrayList<>(); + /** * Optional arbitrary priority value given by the client. * @@ -114,6 +122,30 @@ 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 getInUseFrontendIds() { + return mUsingFrontendIds; + } + + /** + * Called when the client released a frontend. + * + *

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[id=" + this.mId + ", tvInputSessionId=" + this.mTvInputSessionId 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 621f2b024e843..cb31a502ecfa0 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -28,6 +28,7 @@ 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; @@ -54,7 +55,7 @@ public class TunerResourceManagerService extends SystemService { // Array of the registered client profiles @VisibleForTesting private SparseArray mClientProfiles = new SparseArray<>(); private int mNextUnusedClientId = 0; - private List mReleasedClientId = new ArrayList(); + private List mRegisteredClientIds = new ArrayList(); // Array of the current available frontend resources @VisibleForTesting @@ -98,8 +99,12 @@ public class TunerResourceManagerService extends SystemService { throw new RemoteException("ResourceClientProfile can't be null"); } - if (clientId == null || clientId.length != 1) { - throw new RemoteException("clientId must be a size 1 array!"); + if (clientId == null) { + throw new RemoteException("clientId can't be null!"); + } + + if (!mPriorityCongfig.isDefinedUseCase(profile.getUseCase())) { + throw new RemoteException("Use undefined client use case:" + profile.getUseCase()); } synchronized (mLock) { @@ -108,20 +113,20 @@ public class TunerResourceManagerService extends SystemService { } @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) { enforceAccessPermission(); - synchronized (mLock) { return updateClientPriorityInternal(clientId, priority, niceValue); } @@ -243,24 +248,38 @@ public class TunerResourceManagerService extends SystemService { return; } // TODO tell if the client already exists - if (mReleasedClientId.isEmpty()) { - clientId[0] = mNextUnusedClientId++; - } else { - clientId[0] = mReleasedClientId.get(0); - mReleasedClientId.remove(0); - } + clientId[0] = mNextUnusedClientId++; - int callingPid = mManager.getClientPid(profile.getTvInputSessionId()); + 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(callingPid) + .processId(pid) .build(); - clientProfile.setPriority(getClientPriority(profile.getUseCase(), callingPid)); + 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 @@ -355,12 +374,11 @@ public class TunerResourceManagerService extends SystemService { } frontendId[0] = TunerResourceManager.INVALID_FRONTEND_ID; - ClientProfile requestClient = getClientProfile(request.getClientId()); - if (requestClient == null) { - Slog.e(TAG, "Request from unregistered client. Id: " + request.getClientId()); + 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 @@ -393,7 +411,7 @@ public class TunerResourceManagerService extends SystemService { // Grant frontend when there is unused resource. if (grantingFrontendId > -1) { frontendId[0] = grantingFrontendId; - updateFrontendResourcesOnNewGrant(frontendId[0], request.getClientId()); + updateFrontendClientMappingOnNewGrant(frontendId[0], request.getClientId()); return true; } @@ -402,7 +420,7 @@ public class TunerResourceManagerService extends SystemService { if (inUseLowestPriorityFrId > -1 && (requestClient.getPriority() > currentLowestPriority)) { frontendId[0] = inUseLowestPriorityFrId; reclaimFrontendResource(getFrontendResource(frontendId[0]).getOwnerClientId()); - updateFrontendResourcesOnNewGrant(frontendId[0], request.getClientId()); + updateFrontendClientMappingOnNewGrant(frontendId[0], request.getClientId()); return true; } @@ -410,20 +428,20 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - protected int getClientPriority(int useCase, int callingPid) { + protected int getClientPriority(int useCase, int pid) { if (DEBUG) { Slog.d(TAG, "getClientPriority useCase=" + useCase - + ", calling Pid=" + callingPid + ")"); + + ", pid=" + pid + ")"); } - if (isForeground(callingPid)) { + if (isForeground(pid)) { return mPriorityCongfig.getForegroundPriority(useCase); } return mPriorityCongfig.getBackgroundPriority(useCase); } @VisibleForTesting - protected boolean isForeground(int callingPid) { + protected boolean isForeground(int pid) { // TODO: how to get fg/bg information from pid return true; } @@ -439,11 +457,14 @@ public class TunerResourceManagerService extends SystemService { } } - private void updateFrontendResourcesOnNewGrant(int grantingId, int ownerClientId) { + 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); } } @@ -475,6 +496,10 @@ public class TunerResourceManagerService extends SystemService { 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 index e6d672d9acbc5..8c2de475b99bb 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/UseCasePriorityHints.java @@ -95,8 +95,10 @@ public class UseCasePriorityHints { } 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"); + } 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); 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 index 72fbaef339a38..192c6fe4ab75f 100644 --- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java @@ -452,4 +452,46 @@ public class TunerResourceManagerServiceTest { .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 index 8a75d5c2a3804..ab5665ba99fc2 100644 --- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/UseCasePriorityHintsTest.java +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/UseCasePriorityHintsTest.java @@ -58,17 +58,16 @@ public class UseCasePriorityHintsTest { @Before public void setUp() throws Exception { mPriorityHints = new UseCasePriorityHints(); - } - - @Test - public void parseTest_parseSampleXml() { 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); @@ -97,4 +96,16 @@ public class UseCasePriorityHintsTest { 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(); + } }