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

Commit 9796ba2f authored by Amy's avatar Amy
Browse files

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 caf201a8)
parent 85cbe43d
Loading
Loading
Loading
Loading
+17 −53
Original line number Diff line number Diff line
@@ -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}.
          *
+204 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.tv.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;
        }
    }
}
+130 −53
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ 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;
@@ -47,14 +48,25 @@ 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;
    // Array of the registered client profiles
    @VisibleForTesting private SparseArray<ClientProfile> mClientProfiles = new SparseArray<>();
    private int mNextUnusedClientId = 0;
    private List<Integer> mReleasedClientId = new ArrayList<Integer>();

    // Array of the current available frontend resources
    @VisibleForTesting
    private SparseArray<FrontendResource> mFrontendResources = new SparseArray<>();
    @VisibleForTesting private SparseArray<Integer[]> mFrontendTypeMap = 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;

    // 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,8 +100,7 @@ public class TunerResourceManagerService extends SystemService {

            int callingPid = mManager.getClientPid(profile.getTvInputSessionId());

            ClientProfile clientProfile = new ClientProfile.ClientProfileBuilder(
                    clientId[0])
            ClientProfile clientProfile = new ClientProfile.Builder(clientId[0])
                                                  .tvInputSessionId(profile.getTvInputSessionId())
                                                  .useCase(profile.getUseCase())
                                                  .processId(callingPid)
@@ -114,13 +123,15 @@ 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
                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;
            }

            if (DEBUG) {
                Slog.d(TAG, "updateFrontendInfo:");
                for (int i = 0; i < infos.length; i++) {
                    Slog.d(TAG, infos[i].toString());
        public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos) throws RemoteException {
            enforceAccessPermission();
            if (infos == null) {
                throw new RemoteException("TunerFrontendInfo can't be null");
            }
            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 + ")");
            }
        }

@@ -173,19 +180,12 @@ public class TunerResourceManagerService extends SystemService {

            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
                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<Integer> 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<FrontendResource> getFrontendResources() {
        return mFrontendResources;
    }

    private void enforceAccessPermission() {
        getContext().enforceCallingOrSelfPermission(
                "android.permission.TUNER_RESOURCE_ACCESS", TAG);
    }
}
+190 −0

File added.

Preview size limit exceeded, changes collapsed.