1 /* 2 * Copyright 2021 HIMSA II K/S - www.himsa.com. 3 * Represented by EHIMA - www.ehima.com 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.bluetooth.le_audio; 19 20 import android.bluetooth.BluetoothLeAudio; 21 import android.os.ParcelUuid; 22 import android.util.Log; 23 import android.util.Pair; 24 25 import com.android.bluetooth.btservice.ServiceFactory; 26 27 import java.util.Collections; 28 import java.util.HashMap; 29 import java.util.Map; 30 import java.util.Map.Entry; 31 import java.util.Objects; 32 import java.util.SortedSet; 33 import java.util.TreeSet; 34 35 /** This class keeps Content Control Ids for LE Audio profiles. */ 36 public class ContentControlIdKeeper { 37 private static final String TAG = "ContentControlIdKeeper"; 38 39 public static final int CCID_INVALID = 0; 40 public static final int CCID_MIN = 0x01; 41 public static final int CCID_MAX = 0xFF; 42 43 private static SortedSet<Integer> sAssignedCcidList = new TreeSet(); 44 private static HashMap<ParcelUuid, Pair<Integer, Integer>> sUuidToCcidContextPair = 45 new HashMap(); 46 private static ServiceFactory sServiceFactory = null; 47 initForTesting(ServiceFactory instance)48 static synchronized void initForTesting(ServiceFactory instance) { 49 sAssignedCcidList = new TreeSet(); 50 sUuidToCcidContextPair = new HashMap(); 51 sServiceFactory = instance; 52 } 53 54 /** 55 * Functions is used to acquire Content Control ID (Ccid). Ccid is connected with a context type 56 * and the user uuid. In most of cases user uuid is the GATT service UUID which makes use of 57 * Ccid 58 * 59 * @param userUuid user identifier (GATT service) 60 * @param contextType the context types as defined in {@link BluetoothLeAudio} 61 * @return ccid to be used in the Gatt service Ccid characteristic. 62 */ acquireCcid(ParcelUuid userUuid, int contextType)63 public static synchronized int acquireCcid(ParcelUuid userUuid, int contextType) { 64 int ccid = CCID_INVALID; 65 if (contextType == BluetoothLeAudio.CONTEXT_TYPE_INVALID) { 66 Log.e(TAG, "Invalid context type value: " + contextType); 67 return ccid; 68 } 69 70 // Remove any previous mapping 71 Pair<Integer, Integer> ccidContextPair = sUuidToCcidContextPair.get(userUuid); 72 if (ccidContextPair != null) { 73 releaseCcid(ccidContextPair.first); 74 } 75 76 if (sAssignedCcidList.size() == 0) { 77 ccid = CCID_MIN; 78 } else if (sAssignedCcidList.last() < CCID_MAX) { 79 ccid = sAssignedCcidList.last() + 1; 80 } else if (sAssignedCcidList.first() > CCID_MIN) { 81 ccid = sAssignedCcidList.first() - 1; 82 } else { 83 int first_ccid_avail = sAssignedCcidList.first() + 1; 84 while (first_ccid_avail < CCID_MAX - 1) { 85 if (!sAssignedCcidList.contains(first_ccid_avail)) { 86 ccid = first_ccid_avail; 87 break; 88 } 89 first_ccid_avail++; 90 } 91 } 92 93 if (ccid != CCID_INVALID) { 94 sAssignedCcidList.add(ccid); 95 sUuidToCcidContextPair.put(userUuid, new Pair(ccid, contextType)); 96 97 if (sServiceFactory == null) { 98 sServiceFactory = new ServiceFactory(); 99 } 100 /* Notify LeAudioService about new ccid */ 101 LeAudioService service = sServiceFactory.getLeAudioService(); 102 if (service != null) { 103 service.setCcidInformation(userUuid, ccid, contextType); 104 } 105 } 106 return ccid; 107 } 108 109 /** 110 * Release the acquired Ccid 111 * 112 * @param value Ccid value to release 113 */ releaseCcid(int value)114 public static synchronized void releaseCcid(int value) { 115 ParcelUuid uuid = null; 116 117 for (Entry entry : sUuidToCcidContextPair.entrySet()) { 118 if (Objects.equals(value, ((Pair<Integer, Integer>) entry.getValue()).first)) { 119 uuid = (ParcelUuid) entry.getKey(); 120 break; 121 } 122 } 123 if (uuid == null) { 124 Log.e(TAG, "Tried to remove an unknown CCID: " + value); 125 return; 126 } 127 128 if (sAssignedCcidList.contains(value)) { 129 if (sServiceFactory == null) { 130 sServiceFactory = new ServiceFactory(); 131 } 132 /* Notify LeAudioService about new value */ 133 LeAudioService service = sServiceFactory.getLeAudioService(); 134 if (service != null) { 135 service.setCcidInformation(uuid, value, 0); 136 } 137 138 sAssignedCcidList.remove(value); 139 sUuidToCcidContextPair.remove(uuid); 140 } 141 } 142 143 /** 144 * Get Ccid information. 145 * 146 * @return Map of acquired ccids along with the user information. 147 */ 148 public static synchronized Map<ParcelUuid, Pair<Integer, Integer>> getUuidToCcidContextPairMap()149 getUuidToCcidContextPairMap() { 150 return Collections.unmodifiableMap(sUuidToCcidContextPair); 151 } 152 } 153