• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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