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 /** 36 * This class keeps Content Control Ids for LE Audio profiles. 37 */ 38 public class ContentControlIdKeeper { 39 private static final String TAG = "ContentControlIdKeeper"; 40 41 public static final int CCID_INVALID = 0; 42 public static final int CCID_MIN = 0x01; 43 public static final int CCID_MAX = 0xFF; 44 45 private static SortedSet<Integer> sAssignedCcidList = new TreeSet(); 46 private static HashMap<ParcelUuid, Pair<Integer, Integer>> sUuidToCcidContextPair = 47 new HashMap(); 48 private static ServiceFactory sServiceFactory = null; 49 initForTesting(ServiceFactory instance)50 static synchronized void initForTesting(ServiceFactory instance) { 51 sAssignedCcidList = new TreeSet(); 52 sUuidToCcidContextPair = new HashMap(); 53 sServiceFactory = instance; 54 } 55 56 /** 57 * Functions is used to acquire Content Control ID (Ccid). Ccid is connected 58 * with a context type and the user uuid. In most of cases user uuid is the GATT service 59 * UUID which makes use of Ccid 60 * 61 * @param userUuid user identifier (GATT service) 62 * @param contextType the context types as defined in {@link BluetoothLeAudio} 63 * @return ccid to be used in the Gatt service Ccid characteristic. 64 */ acquireCcid(ParcelUuid userUuid, int contextType)65 public static synchronized int acquireCcid(ParcelUuid userUuid, int contextType) { 66 int ccid = CCID_INVALID; 67 if (contextType == BluetoothLeAudio.CONTEXT_TYPE_INVALID) { 68 Log.e(TAG, "Invalid context type value: " + contextType); 69 return ccid; 70 } 71 72 // Remove any previous mapping 73 Pair<Integer, Integer> ccidContextPair = sUuidToCcidContextPair.get(userUuid); 74 if (ccidContextPair != null) { 75 releaseCcid(ccidContextPair.first); 76 } 77 78 if (sAssignedCcidList.size() == 0) { 79 ccid = CCID_MIN; 80 } else if (sAssignedCcidList.last() < CCID_MAX) { 81 ccid = sAssignedCcidList.last() + 1; 82 } else if (sAssignedCcidList.first() > CCID_MIN) { 83 ccid = sAssignedCcidList.first() - 1; 84 } else { 85 int first_ccid_avail = sAssignedCcidList.first() + 1; 86 while (first_ccid_avail < CCID_MAX - 1) { 87 if (!sAssignedCcidList.contains(first_ccid_avail)) { 88 ccid = first_ccid_avail; 89 break; 90 } 91 first_ccid_avail++; 92 } 93 } 94 95 if (ccid != CCID_INVALID) { 96 sAssignedCcidList.add(ccid); 97 sUuidToCcidContextPair.put(userUuid, new Pair(ccid, contextType)); 98 99 if (sServiceFactory == null) { 100 sServiceFactory = new ServiceFactory(); 101 } 102 /* Notify LeAudioService about new ccid */ 103 LeAudioService service = sServiceFactory.getLeAudioService(); 104 if (service != null) { 105 service.setCcidInformation(userUuid, ccid, contextType); 106 } 107 } 108 return ccid; 109 } 110 111 /** 112 * Release the acquired Ccid 113 * 114 * @param value Ccid value to release 115 */ releaseCcid(int value)116 public static synchronized void releaseCcid(int value) { 117 ParcelUuid uuid = null; 118 119 for (Entry entry : sUuidToCcidContextPair.entrySet()) { 120 if (Objects.equals(value, ((Pair<Integer, Integer>) entry.getValue()).first)) { 121 uuid = (ParcelUuid) entry.getKey(); 122 break; 123 } 124 } 125 if (uuid == null) { 126 Log.e(TAG, "Tried to remove an unknown CCID: " + value); 127 return; 128 } 129 130 if (sAssignedCcidList.contains(value)) { 131 if (sServiceFactory == null) { 132 sServiceFactory = new ServiceFactory(); 133 } 134 /* Notify LeAudioService about new value */ 135 LeAudioService service = sServiceFactory.getLeAudioService(); 136 if (service != null) { 137 service.setCcidInformation(uuid, value, 0); 138 } 139 140 sAssignedCcidList.remove(value); 141 sUuidToCcidContextPair.remove(uuid); 142 } 143 } 144 145 /** 146 * Get Ccid information. 147 * 148 * @return Map of acquired ccids along with the user information. 149 */ 150 public static synchronized Map<ParcelUuid, Pair<Integer, Integer>> getUuidToCcidContextPairMap()151 getUuidToCcidContextPairMap() { 152 return Collections.unmodifiableMap(sUuidToCcidContextPair); 153 } 154 } 155