Loading media/java/android/media/tv/tuner/Tuner.java +10 −2 Original line number Original line Diff line number Diff line Loading @@ -1728,7 +1728,9 @@ public class Tuner implements AutoCloseable { } } int res = nativeSetMaxNumberOfFrontends(frontendType, maxNumber); int res = nativeSetMaxNumberOfFrontends(frontendType, maxNumber); if (res == RESULT_SUCCESS) { if (res == RESULT_SUCCESS) { // TODO: b/211778848 Update Tuner Resource Manager. if (!mTunerResourceManager.setMaxNumberOfFrontends(frontendType, maxNumber)) { res = RESULT_INVALID_ARGUMENT; } } } return res; return res; } } Loading @@ -1749,7 +1751,13 @@ public class Tuner implements AutoCloseable { TunerVersionChecker.TUNER_VERSION_2_0, "Set maximum Frontends")) { TunerVersionChecker.TUNER_VERSION_2_0, "Set maximum Frontends")) { return -1; return -1; } } return nativeGetMaxNumberOfFrontends(frontendType); int maxNumFromHAL = nativeGetMaxNumberOfFrontends(frontendType); int maxNumFromTRM = mTunerResourceManager.getMaxNumberOfFrontends(frontendType); if (maxNumFromHAL != maxNumFromTRM) { Log.w(TAG, "max num of usable frontend is out-of-sync b/w " + maxNumFromHAL + " != " + maxNumFromTRM); } return maxNumFromHAL; } } /** @hide */ /** @hide */ Loading media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java +37 −0 Original line number Original line Diff line number Diff line Loading @@ -400,6 +400,43 @@ public class TunerResourceManager { return result; return result; } } /** * Sets the maximum usable frontends number of a given frontend type. It is used to enable or * disable frontends when cable connection status is changed by user. * * @param frontendType the frontendType which the maximum usable number will be set for. * @param maxNum the new maximum usable number. * * @return true if successful and false otherwise. */ public boolean setMaxNumberOfFrontends(int frontendType, int maxNum) { boolean result = false; try { result = mService.setMaxNumberOfFrontends(frontendType, maxNum); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return result; } /** * Get the maximum usable frontends number of a given frontend type. * * @param frontendType the frontendType which the maximum usable number will be queried for. * * @return the maximum usable number of the queried frontend type. Returns -1 when the * frontendType is invalid */ public int getMaxNumberOfFrontends(int frontendType) { int result = -1; try { result = mService.getMaxNumberOfFrontends(frontendType); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return result; } /** /** * Requests from the client to share frontend with an existing client. * Requests from the client to share frontend with an existing client. * * Loading media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl +21 −0 Original line number Original line Diff line number Diff line Loading @@ -165,6 +165,27 @@ interface ITunerResourceManager { */ */ boolean requestFrontend(in TunerFrontendRequest request, out int[] frontendHandle); boolean requestFrontend(in TunerFrontendRequest request, out int[] frontendHandle); /* * Sets the maximum usable frontends number of a given frontend type. It is used to enable or * disable frontends when cable connection status is changed by user. * * @param frontendType the frontendType which the maximum usable number will be set for. * @param maxNumber the new maximum usable number. * * @return true if successful and false otherwise. */ boolean setMaxNumberOfFrontends(in int frontendType, in int maxNum); /* * Get the maximum usable frontends number of a given frontend type. * * @param frontendType the frontendType which the maximum usable number will be queried for. * * @return the maximum usable number of the queried frontend type. Returns -1 when the * frontendType is invalid */ int getMaxNumberOfFrontends(in int frontendType); /* /* * Requests to share frontend with an existing client. * Requests to share frontend with an existing client. * * Loading services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java +25 −0 Original line number Original line Diff line number Diff line Loading @@ -15,6 +15,8 @@ */ */ package com.android.server.tv.tunerresourcemanager; package com.android.server.tv.tunerresourcemanager; import android.media.tv.tunerresourcemanager.TunerResourceManager; import java.util.HashSet; import java.util.HashSet; import java.util.Set; import java.util.Set; Loading Loading @@ -62,6 +64,11 @@ public final class ClientProfile { */ */ private int mNiceValue; private int mNiceValue; /** * The handle of the primary frontend resource */ private int mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; /** /** * List of the frontend handles that are used by the current client. * List of the frontend handles that are used by the current client. */ */ Loading Loading @@ -174,6 +181,22 @@ public final class ClientProfile { mUsingFrontendHandles.add(frontendHandle); mUsingFrontendHandles.add(frontendHandle); } } /** * Set the primary frontend used by the client * * @param frontendHandle being used. */ public void setPrimaryFrontend(int frontendHandle) { mPrimaryUsingFrontendHandle = frontendHandle; } /** * Get the primary frontend used by the client */ public int getPrimaryFrontend() { return mPrimaryUsingFrontendHandle; } /** /** * Update the set of client that share frontend with the current client. * Update the set of client that share frontend with the current client. * * Loading Loading @@ -206,6 +229,7 @@ public final class ClientProfile { public void releaseFrontend() { public void releaseFrontend() { mUsingFrontendHandles.clear(); mUsingFrontendHandles.clear(); mShareFeClientIds.clear(); mShareFeClientIds.clear(); mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; } } /** /** Loading Loading @@ -276,6 +300,7 @@ public final class ClientProfile { public void reclaimAllResources() { public void reclaimAllResources() { mUsingFrontendHandles.clear(); mUsingFrontendHandles.clear(); mShareFeClientIds.clear(); mShareFeClientIds.clear(); mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; mUsingLnbHandles.clear(); mUsingLnbHandles.clear(); mUsingCasSystemId = INVALID_RESOURCE_ID; mUsingCasSystemId = INVALID_RESOURCE_ID; mUsingCiCamId = INVALID_RESOURCE_ID; mUsingCiCamId = INVALID_RESOURCE_ID; Loading services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +189 −79 Original line number Original line Diff line number Diff line Loading @@ -42,6 +42,7 @@ import android.os.SystemClock; import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Log; import android.util.Slog; import android.util.Slog; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -72,14 +73,28 @@ public class TunerResourceManagerService extends SystemService implements IBinde private static final long INVALID_THREAD_ID = -1; private static final long INVALID_THREAD_ID = -1; private static final long TRMS_LOCK_TIMEOUT = 500; private static final long TRMS_LOCK_TIMEOUT = 500; private static final int INVALID_FE_COUNT = -1; // Map of the registered client profiles // Map of the registered client profiles private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>(); private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>(); private int mNextUnusedClientId = 0; private int mNextUnusedClientId = 0; // Map of the current available frontend resources // Map of the current available frontend resources private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>(); private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>(); // Backup Map of the current available frontend resources // SparseIntArray of the max usable number for each frontend resource type private SparseIntArray mFrontendMaxUsableNums = new SparseIntArray(); // SparseIntArray of the currently used number for each frontend resource type private SparseIntArray mFrontendUsedNums = new SparseIntArray(); // SparseIntArray of the existing number for each frontend resource type private SparseIntArray mFrontendExistingNums = new SparseIntArray(); // Backups for the frontend resource maps for enabling testing with custom resource maps // such as TunerTest.testHasUnusedFrontend1() private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>(); private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>(); private SparseIntArray mFrontendMaxUsableNumsBackup = new SparseIntArray(); private SparseIntArray mFrontendUsedNumsBackup = new SparseIntArray(); private SparseIntArray mFrontendExistingNumsBackup = new SparseIntArray(); // Map of the current available lnb resources // Map of the current available lnb resources private Map<Integer, LnbResource> mLnbResources = new HashMap<>(); private Map<Integer, LnbResource> mLnbResources = new HashMap<>(); // Map of the current available Cas resources // Map of the current available Cas resources Loading Loading @@ -267,6 +282,29 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } } } @Override public boolean setMaxNumberOfFrontends(int frontendType, int maxUsableNum) { enforceTunerAccessPermission("requestFrontend"); enforceTrmAccessPermission("requestFrontend"); if (maxUsableNum < 0) { Slog.w(TAG, "setMaxNumberOfFrontends failed with maxUsableNum:" + maxUsableNum + " frontendType:" + frontendType); return false; } synchronized (mLock) { return setMaxNumberOfFrontendsInternal(frontendType, maxUsableNum); } } @Override public int getMaxNumberOfFrontends(int frontendType) { enforceTunerAccessPermission("requestFrontend"); enforceTrmAccessPermission("requestFrontend"); synchronized (mLock) { return getMaxNumberOfFrontendsInternal(frontendType); } } @Override @Override public void shareFrontend(int selfClientId, int targetClientId) throws RemoteException { public void shareFrontend(int selfClientId, int targetClientId) throws RemoteException { enforceTunerAccessPermission("shareFrontend"); enforceTunerAccessPermission("shareFrontend"); Loading Loading @@ -572,71 +610,19 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } synchronized (mLock) { synchronized (mLock) { if (mClientProfiles != null) { dumpMap(mClientProfiles, "ClientProfiles:", "\n", pw); pw.println("ClientProfiles:"); dumpMap(mFrontendResources, "FrontendResources:", "\n", pw); pw.increaseIndent(); dumpSIA(mFrontendExistingNums, "FrontendExistingNums:", ", ", pw); for (Map.Entry<Integer, ClientProfile> entry : mClientProfiles.entrySet()) { dumpSIA(mFrontendUsedNums, "FrontendUsedNums:", ", ", pw); pw.println(entry.getKey() + " : " + entry.getValue()); dumpSIA(mFrontendMaxUsableNums, "FrontendMaxUsableNums:", ", ", pw); } dumpMap(mFrontendResourcesBackup, "FrontendResourcesBackUp:", "\n", pw); pw.decreaseIndent(); dumpSIA(mFrontendExistingNumsBackup, "FrontendExistingNumsBackup:", ", ", pw); } dumpSIA(mFrontendUsedNumsBackup, "FrontendUsedNumsBackup:", ", ", pw); dumpSIA(mFrontendMaxUsableNumsBackup, "FrontendUsedNumsBackup:", ", ", pw); if (mFrontendResources != null) { dumpMap(mLnbResources, "LnbResource:", "\n", pw); pw.println("FrontendResources:"); dumpMap(mCasResources, "CasResource:", "\n", pw); pw.increaseIndent(); dumpMap(mCiCamResources, "CiCamResource:", "\n", pw); for (Map.Entry<Integer, FrontendResource> entry dumpMap(mListeners, "Listners:", "\n", pw); : mFrontendResources.entrySet()) { pw.println(entry.getKey() + " : " + entry.getValue()); } pw.decreaseIndent(); } if (mFrontendResourcesBackup != null) { pw.println("FrontendResourcesBackUp:"); pw.increaseIndent(); for (Map.Entry<Integer, FrontendResource> entry : mFrontendResourcesBackup.entrySet()) { pw.println(entry.getKey() + " : " + entry.getValue()); } pw.decreaseIndent(); } if (mLnbResources != null) { pw.println("LnbResources:"); pw.increaseIndent(); for (Map.Entry<Integer, LnbResource> entry : mLnbResources.entrySet()) { pw.println(entry.getKey() + " : " + entry.getValue()); } pw.decreaseIndent(); } if (mCasResources != null) { pw.println("CasResources:"); pw.increaseIndent(); for (Map.Entry<Integer, CasResource> entry : mCasResources.entrySet()) { pw.println(entry.getKey() + " : " + entry.getValue()); } pw.decreaseIndent(); } if (mCiCamResources != null) { pw.println("CiCamResources:"); pw.increaseIndent(); for (Map.Entry<Integer, CiCamResource> entry : mCiCamResources.entrySet()) { pw.println(entry.getKey() + " : " + entry.getValue()); } pw.decreaseIndent(); } if (mListeners != null) { pw.println("Listners:"); pw.increaseIndent(); for (Map.Entry<Integer, ResourcesReclaimListenerRecord> entry : mListeners.entrySet()) { pw.println(entry.getKey() + " : " + entry.getValue()); } pw.decreaseIndent(); } } } } } Loading Loading @@ -786,10 +772,10 @@ public class TunerResourceManagerService extends SystemService implements IBinde protected void storeResourceMapInternal(int resourceType) { protected void storeResourceMapInternal(int resourceType) { switch (resourceType) { switch (resourceType) { case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: if (mFrontendResources != null && mFrontendResources.size() > 0) { replaceFeResourceMap(mFrontendResources, mFrontendResourcesBackup); mFrontendResourcesBackup.putAll(mFrontendResources); replaceFeCounts(mFrontendExistingNums, mFrontendExistingNumsBackup); mFrontendResources.clear(); replaceFeCounts(mFrontendUsedNums, mFrontendUsedNumsBackup); } replaceFeCounts(mFrontendMaxUsableNums, mFrontendMaxUsableNumsBackup); break; break; // TODO: implement for other resource type when needed // TODO: implement for other resource type when needed default: default: Loading @@ -800,9 +786,10 @@ public class TunerResourceManagerService extends SystemService implements IBinde protected void clearResourceMapInternal(int resourceType) { protected void clearResourceMapInternal(int resourceType) { switch (resourceType) { switch (resourceType) { case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: if (mFrontendResources != null) { replaceFeResourceMap(null, mFrontendResources); mFrontendResources.clear(); replaceFeCounts(null, mFrontendExistingNums); } replaceFeCounts(null, mFrontendUsedNums); replaceFeCounts(null, mFrontendMaxUsableNums); break; break; // TODO: implement for other resource type when needed // TODO: implement for other resource type when needed default: default: Loading @@ -813,12 +800,10 @@ public class TunerResourceManagerService extends SystemService implements IBinde protected void restoreResourceMapInternal(int resourceType) { protected void restoreResourceMapInternal(int resourceType) { switch (resourceType) { switch (resourceType) { case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: if (mFrontendResourcesBackup != null replaceFeResourceMap(mFrontendResourcesBackup, mFrontendResources); && mFrontendResourcesBackup.size() > 0) { replaceFeCounts(mFrontendExistingNumsBackup, mFrontendExistingNums); mFrontendResources.clear(); replaceFeCounts(mFrontendUsedNumsBackup, mFrontendUsedNums); mFrontendResources.putAll(mFrontendResourcesBackup); replaceFeCounts(mFrontendMaxUsableNumsBackup, mFrontendMaxUsableNums); mFrontendResourcesBackup.clear(); } break; break; // TODO: implement for other resource type when needed // TODO: implement for other resource type when needed default: default: Loading Loading @@ -954,6 +939,11 @@ public class TunerResourceManagerService extends SystemService implements IBinde for (FrontendResource fr : getFrontendResources().values()) { for (FrontendResource fr : getFrontendResources().values()) { if (fr.getType() == request.frontendType) { if (fr.getType() == request.frontendType) { if (!fr.isInUse()) { if (!fr.isInUse()) { // Unused resource cannot be acquired if the max is already reached, but // TRM still has to look for the reclaim candidate if (isFrontendMaxNumUseReached(request.frontendType)) { continue; } // Grant unused frontend with no exclusive group members first. // Grant unused frontend with no exclusive group members first. if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) { if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) { grantingFrontendHandle = fr.getHandle(); grantingFrontendHandle = fr.getHandle(); Loading Loading @@ -1021,6 +1011,9 @@ public class TunerResourceManagerService extends SystemService implements IBinde for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) { for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) { getFrontendResource(inUseHandle).setOwner(newOwnerId); getFrontendResource(inUseHandle).setOwner(newOwnerId); } } // change the primary frontend newOwnerProfile.setPrimaryFrontend(currentOwnerProfile.getPrimaryFrontend()); currentOwnerProfile.setPrimaryFrontend(TunerResourceManager.INVALID_RESOURCE_HANDLE); // double check there is no other resources tied to the previous owner // double check there is no other resources tied to the previous owner for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) { for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) { int ownerId = getFrontendResource(inUseHandle).getOwnerClientId(); int ownerId = getFrontendResource(inUseHandle).getOwnerClientId(); Loading Loading @@ -1657,11 +1650,13 @@ public class TunerResourceManagerService extends SystemService implements IBinde FrontendResource grantingFrontend = getFrontendResource(grantingHandle); FrontendResource grantingFrontend = getFrontendResource(grantingHandle); ClientProfile ownerProfile = getClientProfile(ownerClientId); ClientProfile ownerProfile = getClientProfile(ownerClientId); grantingFrontend.setOwner(ownerClientId); grantingFrontend.setOwner(ownerClientId); increFrontendNum(mFrontendUsedNums, grantingFrontend.getType()); ownerProfile.useFrontend(grantingHandle); ownerProfile.useFrontend(grantingHandle); for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) { for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) { getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId); getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId); ownerProfile.useFrontend(exclusiveGroupMember); ownerProfile.useFrontend(exclusiveGroupMember); } } ownerProfile.setPrimaryFrontend(grantingHandle); } } private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) { private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) { Loading Loading @@ -1755,6 +1750,109 @@ public class TunerResourceManagerService extends SystemService implements IBinde return mFrontendResources; return mFrontendResources; } } private boolean setMaxNumberOfFrontendsInternal(int frontendType, int maxUsableNum) { int usedNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT); if (usedNum == INVALID_FE_COUNT || usedNum <= maxUsableNum) { mFrontendMaxUsableNums.put(frontendType, maxUsableNum); return true; } else { Slog.e(TAG, "max number of frontend for frontendType: " + frontendType + " cannot be set to a value lower than the current usage count." + " (requested max num = " + maxUsableNum + ", current usage = " + usedNum); return false; } } private int getMaxNumberOfFrontendsInternal(int frontendType) { int existingNum = mFrontendExistingNums.get(frontendType, INVALID_FE_COUNT); if (existingNum == INVALID_FE_COUNT) { Log.e(TAG, "existingNum is -1 for " + frontendType); return -1; } int maxUsableNum = mFrontendMaxUsableNums.get(frontendType, INVALID_FE_COUNT); if (maxUsableNum == INVALID_FE_COUNT) { return existingNum; } else { return maxUsableNum; } } private boolean isFrontendMaxNumUseReached(int frontendType) { int maxUsableNum = mFrontendMaxUsableNums.get(frontendType, INVALID_FE_COUNT); if (maxUsableNum == INVALID_FE_COUNT) { return false; } int useNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT); if (useNum == INVALID_FE_COUNT) { useNum = 0; } return useNum >= maxUsableNum; } private void increFrontendNum(SparseIntArray targetNums, int frontendType) { int num = targetNums.get(frontendType, INVALID_FE_COUNT); if (num == INVALID_FE_COUNT) { targetNums.put(frontendType, 1); } else { targetNums.put(frontendType, num + 1); } } private void decreFrontendNum(SparseIntArray targetNums, int frontendType) { int num = targetNums.get(frontendType, INVALID_FE_COUNT); if (num != INVALID_FE_COUNT) { targetNums.put(frontendType, num - 1); } } private void replaceFeResourceMap(Map<Integer, FrontendResource> srcMap, Map<Integer, FrontendResource> dstMap) { if (dstMap != null) { dstMap.clear(); if (srcMap != null && srcMap.size() > 0) { dstMap.putAll(srcMap); } } } private void replaceFeCounts(SparseIntArray srcCounts, SparseIntArray dstCounts) { if (dstCounts != null) { dstCounts.clear(); if (srcCounts != null) { for (int i = 0; i < srcCounts.size(); i++) { dstCounts.put(srcCounts.keyAt(i), srcCounts.valueAt(i)); } } } } private void dumpMap(Map<?, ?> targetMap, String headline, String delimiter, IndentingPrintWriter pw) { if (targetMap != null) { pw.println(headline); pw.increaseIndent(); for (Map.Entry<?, ?> entry : targetMap.entrySet()) { pw.print(entry.getKey() + " : " + entry.getValue()); pw.print(delimiter); } pw.println(); pw.decreaseIndent(); } } private void dumpSIA(SparseIntArray array, String headline, String delimiter, IndentingPrintWriter pw) { if (array != null) { pw.println(headline); pw.increaseIndent(); for (int i = 0; i < array.size(); i++) { pw.print(array.keyAt(i) + " : " + array.valueAt(i)); pw.print(delimiter); } pw.println(); pw.decreaseIndent(); } } private void addFrontendResource(FrontendResource newFe) { private void addFrontendResource(FrontendResource newFe) { // Update the exclusive group member list in all the existing Frontend resource // Update the exclusive group member list in all the existing Frontend resource for (FrontendResource fe : getFrontendResources().values()) { for (FrontendResource fe : getFrontendResources().values()) { Loading @@ -1771,6 +1869,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } // Update resource list and available id list // Update resource list and available id list mFrontendResources.put(newFe.getHandle(), newFe); mFrontendResources.put(newFe.getHandle(), newFe); increFrontendNum(mFrontendExistingNums, newFe.getType()); } } private void removeFrontendResource(int removingHandle) { private void removeFrontendResource(int removingHandle) { Loading @@ -1789,6 +1889,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde getFrontendResource(excGroupmemberFeHandle) getFrontendResource(excGroupmemberFeHandle) .removeExclusiveGroupMemberFeId(fe.getHandle()); .removeExclusiveGroupMemberFeId(fe.getHandle()); } } decreFrontendNum(mFrontendExistingNums, fe.getType()); mFrontendResources.remove(removingHandle); mFrontendResources.remove(removingHandle); } } Loading Loading @@ -1918,6 +2019,15 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } } } int primaryFeId = profile.getPrimaryFrontend(); if (primaryFeId != TunerResourceManager.INVALID_RESOURCE_HANDLE) { FrontendResource primaryFe = getFrontendResource(primaryFeId); if (primaryFe != null) { decreFrontendNum(mFrontendUsedNums, primaryFe.getType()); } } profile.releaseFrontend(); profile.releaseFrontend(); } } Loading Loading
media/java/android/media/tv/tuner/Tuner.java +10 −2 Original line number Original line Diff line number Diff line Loading @@ -1728,7 +1728,9 @@ public class Tuner implements AutoCloseable { } } int res = nativeSetMaxNumberOfFrontends(frontendType, maxNumber); int res = nativeSetMaxNumberOfFrontends(frontendType, maxNumber); if (res == RESULT_SUCCESS) { if (res == RESULT_SUCCESS) { // TODO: b/211778848 Update Tuner Resource Manager. if (!mTunerResourceManager.setMaxNumberOfFrontends(frontendType, maxNumber)) { res = RESULT_INVALID_ARGUMENT; } } } return res; return res; } } Loading @@ -1749,7 +1751,13 @@ public class Tuner implements AutoCloseable { TunerVersionChecker.TUNER_VERSION_2_0, "Set maximum Frontends")) { TunerVersionChecker.TUNER_VERSION_2_0, "Set maximum Frontends")) { return -1; return -1; } } return nativeGetMaxNumberOfFrontends(frontendType); int maxNumFromHAL = nativeGetMaxNumberOfFrontends(frontendType); int maxNumFromTRM = mTunerResourceManager.getMaxNumberOfFrontends(frontendType); if (maxNumFromHAL != maxNumFromTRM) { Log.w(TAG, "max num of usable frontend is out-of-sync b/w " + maxNumFromHAL + " != " + maxNumFromTRM); } return maxNumFromHAL; } } /** @hide */ /** @hide */ Loading
media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java +37 −0 Original line number Original line Diff line number Diff line Loading @@ -400,6 +400,43 @@ public class TunerResourceManager { return result; return result; } } /** * Sets the maximum usable frontends number of a given frontend type. It is used to enable or * disable frontends when cable connection status is changed by user. * * @param frontendType the frontendType which the maximum usable number will be set for. * @param maxNum the new maximum usable number. * * @return true if successful and false otherwise. */ public boolean setMaxNumberOfFrontends(int frontendType, int maxNum) { boolean result = false; try { result = mService.setMaxNumberOfFrontends(frontendType, maxNum); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return result; } /** * Get the maximum usable frontends number of a given frontend type. * * @param frontendType the frontendType which the maximum usable number will be queried for. * * @return the maximum usable number of the queried frontend type. Returns -1 when the * frontendType is invalid */ public int getMaxNumberOfFrontends(int frontendType) { int result = -1; try { result = mService.getMaxNumberOfFrontends(frontendType); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return result; } /** /** * Requests from the client to share frontend with an existing client. * Requests from the client to share frontend with an existing client. * * Loading
media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl +21 −0 Original line number Original line Diff line number Diff line Loading @@ -165,6 +165,27 @@ interface ITunerResourceManager { */ */ boolean requestFrontend(in TunerFrontendRequest request, out int[] frontendHandle); boolean requestFrontend(in TunerFrontendRequest request, out int[] frontendHandle); /* * Sets the maximum usable frontends number of a given frontend type. It is used to enable or * disable frontends when cable connection status is changed by user. * * @param frontendType the frontendType which the maximum usable number will be set for. * @param maxNumber the new maximum usable number. * * @return true if successful and false otherwise. */ boolean setMaxNumberOfFrontends(in int frontendType, in int maxNum); /* * Get the maximum usable frontends number of a given frontend type. * * @param frontendType the frontendType which the maximum usable number will be queried for. * * @return the maximum usable number of the queried frontend type. Returns -1 when the * frontendType is invalid */ int getMaxNumberOfFrontends(in int frontendType); /* /* * Requests to share frontend with an existing client. * Requests to share frontend with an existing client. * * Loading
services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java +25 −0 Original line number Original line Diff line number Diff line Loading @@ -15,6 +15,8 @@ */ */ package com.android.server.tv.tunerresourcemanager; package com.android.server.tv.tunerresourcemanager; import android.media.tv.tunerresourcemanager.TunerResourceManager; import java.util.HashSet; import java.util.HashSet; import java.util.Set; import java.util.Set; Loading Loading @@ -62,6 +64,11 @@ public final class ClientProfile { */ */ private int mNiceValue; private int mNiceValue; /** * The handle of the primary frontend resource */ private int mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; /** /** * List of the frontend handles that are used by the current client. * List of the frontend handles that are used by the current client. */ */ Loading Loading @@ -174,6 +181,22 @@ public final class ClientProfile { mUsingFrontendHandles.add(frontendHandle); mUsingFrontendHandles.add(frontendHandle); } } /** * Set the primary frontend used by the client * * @param frontendHandle being used. */ public void setPrimaryFrontend(int frontendHandle) { mPrimaryUsingFrontendHandle = frontendHandle; } /** * Get the primary frontend used by the client */ public int getPrimaryFrontend() { return mPrimaryUsingFrontendHandle; } /** /** * Update the set of client that share frontend with the current client. * Update the set of client that share frontend with the current client. * * Loading Loading @@ -206,6 +229,7 @@ public final class ClientProfile { public void releaseFrontend() { public void releaseFrontend() { mUsingFrontendHandles.clear(); mUsingFrontendHandles.clear(); mShareFeClientIds.clear(); mShareFeClientIds.clear(); mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; } } /** /** Loading Loading @@ -276,6 +300,7 @@ public final class ClientProfile { public void reclaimAllResources() { public void reclaimAllResources() { mUsingFrontendHandles.clear(); mUsingFrontendHandles.clear(); mShareFeClientIds.clear(); mShareFeClientIds.clear(); mPrimaryUsingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; mUsingLnbHandles.clear(); mUsingLnbHandles.clear(); mUsingCasSystemId = INVALID_RESOURCE_ID; mUsingCasSystemId = INVALID_RESOURCE_ID; mUsingCiCamId = INVALID_RESOURCE_ID; mUsingCiCamId = INVALID_RESOURCE_ID; Loading
services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +189 −79 Original line number Original line Diff line number Diff line Loading @@ -42,6 +42,7 @@ import android.os.SystemClock; import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Log; import android.util.Slog; import android.util.Slog; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -72,14 +73,28 @@ public class TunerResourceManagerService extends SystemService implements IBinde private static final long INVALID_THREAD_ID = -1; private static final long INVALID_THREAD_ID = -1; private static final long TRMS_LOCK_TIMEOUT = 500; private static final long TRMS_LOCK_TIMEOUT = 500; private static final int INVALID_FE_COUNT = -1; // Map of the registered client profiles // Map of the registered client profiles private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>(); private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>(); private int mNextUnusedClientId = 0; private int mNextUnusedClientId = 0; // Map of the current available frontend resources // Map of the current available frontend resources private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>(); private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>(); // Backup Map of the current available frontend resources // SparseIntArray of the max usable number for each frontend resource type private SparseIntArray mFrontendMaxUsableNums = new SparseIntArray(); // SparseIntArray of the currently used number for each frontend resource type private SparseIntArray mFrontendUsedNums = new SparseIntArray(); // SparseIntArray of the existing number for each frontend resource type private SparseIntArray mFrontendExistingNums = new SparseIntArray(); // Backups for the frontend resource maps for enabling testing with custom resource maps // such as TunerTest.testHasUnusedFrontend1() private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>(); private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>(); private SparseIntArray mFrontendMaxUsableNumsBackup = new SparseIntArray(); private SparseIntArray mFrontendUsedNumsBackup = new SparseIntArray(); private SparseIntArray mFrontendExistingNumsBackup = new SparseIntArray(); // Map of the current available lnb resources // Map of the current available lnb resources private Map<Integer, LnbResource> mLnbResources = new HashMap<>(); private Map<Integer, LnbResource> mLnbResources = new HashMap<>(); // Map of the current available Cas resources // Map of the current available Cas resources Loading Loading @@ -267,6 +282,29 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } } } @Override public boolean setMaxNumberOfFrontends(int frontendType, int maxUsableNum) { enforceTunerAccessPermission("requestFrontend"); enforceTrmAccessPermission("requestFrontend"); if (maxUsableNum < 0) { Slog.w(TAG, "setMaxNumberOfFrontends failed with maxUsableNum:" + maxUsableNum + " frontendType:" + frontendType); return false; } synchronized (mLock) { return setMaxNumberOfFrontendsInternal(frontendType, maxUsableNum); } } @Override public int getMaxNumberOfFrontends(int frontendType) { enforceTunerAccessPermission("requestFrontend"); enforceTrmAccessPermission("requestFrontend"); synchronized (mLock) { return getMaxNumberOfFrontendsInternal(frontendType); } } @Override @Override public void shareFrontend(int selfClientId, int targetClientId) throws RemoteException { public void shareFrontend(int selfClientId, int targetClientId) throws RemoteException { enforceTunerAccessPermission("shareFrontend"); enforceTunerAccessPermission("shareFrontend"); Loading Loading @@ -572,71 +610,19 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } synchronized (mLock) { synchronized (mLock) { if (mClientProfiles != null) { dumpMap(mClientProfiles, "ClientProfiles:", "\n", pw); pw.println("ClientProfiles:"); dumpMap(mFrontendResources, "FrontendResources:", "\n", pw); pw.increaseIndent(); dumpSIA(mFrontendExistingNums, "FrontendExistingNums:", ", ", pw); for (Map.Entry<Integer, ClientProfile> entry : mClientProfiles.entrySet()) { dumpSIA(mFrontendUsedNums, "FrontendUsedNums:", ", ", pw); pw.println(entry.getKey() + " : " + entry.getValue()); dumpSIA(mFrontendMaxUsableNums, "FrontendMaxUsableNums:", ", ", pw); } dumpMap(mFrontendResourcesBackup, "FrontendResourcesBackUp:", "\n", pw); pw.decreaseIndent(); dumpSIA(mFrontendExistingNumsBackup, "FrontendExistingNumsBackup:", ", ", pw); } dumpSIA(mFrontendUsedNumsBackup, "FrontendUsedNumsBackup:", ", ", pw); dumpSIA(mFrontendMaxUsableNumsBackup, "FrontendUsedNumsBackup:", ", ", pw); if (mFrontendResources != null) { dumpMap(mLnbResources, "LnbResource:", "\n", pw); pw.println("FrontendResources:"); dumpMap(mCasResources, "CasResource:", "\n", pw); pw.increaseIndent(); dumpMap(mCiCamResources, "CiCamResource:", "\n", pw); for (Map.Entry<Integer, FrontendResource> entry dumpMap(mListeners, "Listners:", "\n", pw); : mFrontendResources.entrySet()) { pw.println(entry.getKey() + " : " + entry.getValue()); } pw.decreaseIndent(); } if (mFrontendResourcesBackup != null) { pw.println("FrontendResourcesBackUp:"); pw.increaseIndent(); for (Map.Entry<Integer, FrontendResource> entry : mFrontendResourcesBackup.entrySet()) { pw.println(entry.getKey() + " : " + entry.getValue()); } pw.decreaseIndent(); } if (mLnbResources != null) { pw.println("LnbResources:"); pw.increaseIndent(); for (Map.Entry<Integer, LnbResource> entry : mLnbResources.entrySet()) { pw.println(entry.getKey() + " : " + entry.getValue()); } pw.decreaseIndent(); } if (mCasResources != null) { pw.println("CasResources:"); pw.increaseIndent(); for (Map.Entry<Integer, CasResource> entry : mCasResources.entrySet()) { pw.println(entry.getKey() + " : " + entry.getValue()); } pw.decreaseIndent(); } if (mCiCamResources != null) { pw.println("CiCamResources:"); pw.increaseIndent(); for (Map.Entry<Integer, CiCamResource> entry : mCiCamResources.entrySet()) { pw.println(entry.getKey() + " : " + entry.getValue()); } pw.decreaseIndent(); } if (mListeners != null) { pw.println("Listners:"); pw.increaseIndent(); for (Map.Entry<Integer, ResourcesReclaimListenerRecord> entry : mListeners.entrySet()) { pw.println(entry.getKey() + " : " + entry.getValue()); } pw.decreaseIndent(); } } } } } Loading Loading @@ -786,10 +772,10 @@ public class TunerResourceManagerService extends SystemService implements IBinde protected void storeResourceMapInternal(int resourceType) { protected void storeResourceMapInternal(int resourceType) { switch (resourceType) { switch (resourceType) { case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: if (mFrontendResources != null && mFrontendResources.size() > 0) { replaceFeResourceMap(mFrontendResources, mFrontendResourcesBackup); mFrontendResourcesBackup.putAll(mFrontendResources); replaceFeCounts(mFrontendExistingNums, mFrontendExistingNumsBackup); mFrontendResources.clear(); replaceFeCounts(mFrontendUsedNums, mFrontendUsedNumsBackup); } replaceFeCounts(mFrontendMaxUsableNums, mFrontendMaxUsableNumsBackup); break; break; // TODO: implement for other resource type when needed // TODO: implement for other resource type when needed default: default: Loading @@ -800,9 +786,10 @@ public class TunerResourceManagerService extends SystemService implements IBinde protected void clearResourceMapInternal(int resourceType) { protected void clearResourceMapInternal(int resourceType) { switch (resourceType) { switch (resourceType) { case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: if (mFrontendResources != null) { replaceFeResourceMap(null, mFrontendResources); mFrontendResources.clear(); replaceFeCounts(null, mFrontendExistingNums); } replaceFeCounts(null, mFrontendUsedNums); replaceFeCounts(null, mFrontendMaxUsableNums); break; break; // TODO: implement for other resource type when needed // TODO: implement for other resource type when needed default: default: Loading @@ -813,12 +800,10 @@ public class TunerResourceManagerService extends SystemService implements IBinde protected void restoreResourceMapInternal(int resourceType) { protected void restoreResourceMapInternal(int resourceType) { switch (resourceType) { switch (resourceType) { case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: if (mFrontendResourcesBackup != null replaceFeResourceMap(mFrontendResourcesBackup, mFrontendResources); && mFrontendResourcesBackup.size() > 0) { replaceFeCounts(mFrontendExistingNumsBackup, mFrontendExistingNums); mFrontendResources.clear(); replaceFeCounts(mFrontendUsedNumsBackup, mFrontendUsedNums); mFrontendResources.putAll(mFrontendResourcesBackup); replaceFeCounts(mFrontendMaxUsableNumsBackup, mFrontendMaxUsableNums); mFrontendResourcesBackup.clear(); } break; break; // TODO: implement for other resource type when needed // TODO: implement for other resource type when needed default: default: Loading Loading @@ -954,6 +939,11 @@ public class TunerResourceManagerService extends SystemService implements IBinde for (FrontendResource fr : getFrontendResources().values()) { for (FrontendResource fr : getFrontendResources().values()) { if (fr.getType() == request.frontendType) { if (fr.getType() == request.frontendType) { if (!fr.isInUse()) { if (!fr.isInUse()) { // Unused resource cannot be acquired if the max is already reached, but // TRM still has to look for the reclaim candidate if (isFrontendMaxNumUseReached(request.frontendType)) { continue; } // Grant unused frontend with no exclusive group members first. // Grant unused frontend with no exclusive group members first. if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) { if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) { grantingFrontendHandle = fr.getHandle(); grantingFrontendHandle = fr.getHandle(); Loading Loading @@ -1021,6 +1011,9 @@ public class TunerResourceManagerService extends SystemService implements IBinde for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) { for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) { getFrontendResource(inUseHandle).setOwner(newOwnerId); getFrontendResource(inUseHandle).setOwner(newOwnerId); } } // change the primary frontend newOwnerProfile.setPrimaryFrontend(currentOwnerProfile.getPrimaryFrontend()); currentOwnerProfile.setPrimaryFrontend(TunerResourceManager.INVALID_RESOURCE_HANDLE); // double check there is no other resources tied to the previous owner // double check there is no other resources tied to the previous owner for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) { for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) { int ownerId = getFrontendResource(inUseHandle).getOwnerClientId(); int ownerId = getFrontendResource(inUseHandle).getOwnerClientId(); Loading Loading @@ -1657,11 +1650,13 @@ public class TunerResourceManagerService extends SystemService implements IBinde FrontendResource grantingFrontend = getFrontendResource(grantingHandle); FrontendResource grantingFrontend = getFrontendResource(grantingHandle); ClientProfile ownerProfile = getClientProfile(ownerClientId); ClientProfile ownerProfile = getClientProfile(ownerClientId); grantingFrontend.setOwner(ownerClientId); grantingFrontend.setOwner(ownerClientId); increFrontendNum(mFrontendUsedNums, grantingFrontend.getType()); ownerProfile.useFrontend(grantingHandle); ownerProfile.useFrontend(grantingHandle); for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) { for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) { getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId); getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId); ownerProfile.useFrontend(exclusiveGroupMember); ownerProfile.useFrontend(exclusiveGroupMember); } } ownerProfile.setPrimaryFrontend(grantingHandle); } } private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) { private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) { Loading Loading @@ -1755,6 +1750,109 @@ public class TunerResourceManagerService extends SystemService implements IBinde return mFrontendResources; return mFrontendResources; } } private boolean setMaxNumberOfFrontendsInternal(int frontendType, int maxUsableNum) { int usedNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT); if (usedNum == INVALID_FE_COUNT || usedNum <= maxUsableNum) { mFrontendMaxUsableNums.put(frontendType, maxUsableNum); return true; } else { Slog.e(TAG, "max number of frontend for frontendType: " + frontendType + " cannot be set to a value lower than the current usage count." + " (requested max num = " + maxUsableNum + ", current usage = " + usedNum); return false; } } private int getMaxNumberOfFrontendsInternal(int frontendType) { int existingNum = mFrontendExistingNums.get(frontendType, INVALID_FE_COUNT); if (existingNum == INVALID_FE_COUNT) { Log.e(TAG, "existingNum is -1 for " + frontendType); return -1; } int maxUsableNum = mFrontendMaxUsableNums.get(frontendType, INVALID_FE_COUNT); if (maxUsableNum == INVALID_FE_COUNT) { return existingNum; } else { return maxUsableNum; } } private boolean isFrontendMaxNumUseReached(int frontendType) { int maxUsableNum = mFrontendMaxUsableNums.get(frontendType, INVALID_FE_COUNT); if (maxUsableNum == INVALID_FE_COUNT) { return false; } int useNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT); if (useNum == INVALID_FE_COUNT) { useNum = 0; } return useNum >= maxUsableNum; } private void increFrontendNum(SparseIntArray targetNums, int frontendType) { int num = targetNums.get(frontendType, INVALID_FE_COUNT); if (num == INVALID_FE_COUNT) { targetNums.put(frontendType, 1); } else { targetNums.put(frontendType, num + 1); } } private void decreFrontendNum(SparseIntArray targetNums, int frontendType) { int num = targetNums.get(frontendType, INVALID_FE_COUNT); if (num != INVALID_FE_COUNT) { targetNums.put(frontendType, num - 1); } } private void replaceFeResourceMap(Map<Integer, FrontendResource> srcMap, Map<Integer, FrontendResource> dstMap) { if (dstMap != null) { dstMap.clear(); if (srcMap != null && srcMap.size() > 0) { dstMap.putAll(srcMap); } } } private void replaceFeCounts(SparseIntArray srcCounts, SparseIntArray dstCounts) { if (dstCounts != null) { dstCounts.clear(); if (srcCounts != null) { for (int i = 0; i < srcCounts.size(); i++) { dstCounts.put(srcCounts.keyAt(i), srcCounts.valueAt(i)); } } } } private void dumpMap(Map<?, ?> targetMap, String headline, String delimiter, IndentingPrintWriter pw) { if (targetMap != null) { pw.println(headline); pw.increaseIndent(); for (Map.Entry<?, ?> entry : targetMap.entrySet()) { pw.print(entry.getKey() + " : " + entry.getValue()); pw.print(delimiter); } pw.println(); pw.decreaseIndent(); } } private void dumpSIA(SparseIntArray array, String headline, String delimiter, IndentingPrintWriter pw) { if (array != null) { pw.println(headline); pw.increaseIndent(); for (int i = 0; i < array.size(); i++) { pw.print(array.keyAt(i) + " : " + array.valueAt(i)); pw.print(delimiter); } pw.println(); pw.decreaseIndent(); } } private void addFrontendResource(FrontendResource newFe) { private void addFrontendResource(FrontendResource newFe) { // Update the exclusive group member list in all the existing Frontend resource // Update the exclusive group member list in all the existing Frontend resource for (FrontendResource fe : getFrontendResources().values()) { for (FrontendResource fe : getFrontendResources().values()) { Loading @@ -1771,6 +1869,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } // Update resource list and available id list // Update resource list and available id list mFrontendResources.put(newFe.getHandle(), newFe); mFrontendResources.put(newFe.getHandle(), newFe); increFrontendNum(mFrontendExistingNums, newFe.getType()); } } private void removeFrontendResource(int removingHandle) { private void removeFrontendResource(int removingHandle) { Loading @@ -1789,6 +1889,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde getFrontendResource(excGroupmemberFeHandle) getFrontendResource(excGroupmemberFeHandle) .removeExclusiveGroupMemberFeId(fe.getHandle()); .removeExclusiveGroupMemberFeId(fe.getHandle()); } } decreFrontendNum(mFrontendExistingNums, fe.getType()); mFrontendResources.remove(removingHandle); mFrontendResources.remove(removingHandle); } } Loading Loading @@ -1918,6 +2019,15 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } } } int primaryFeId = profile.getPrimaryFrontend(); if (primaryFeId != TunerResourceManager.INVALID_RESOURCE_HANDLE) { FrontendResource primaryFe = getFrontendResource(primaryFeId); if (primaryFe != null) { decreFrontendNum(mFrontendUsedNums, primaryFe.getType()); } } profile.releaseFrontend(); profile.releaseFrontend(); } } Loading