1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.uwb.support.fira;
17 
18 import android.os.PersistableBundle;
19 import android.uwb.UwbAddress;
20 
21 import androidx.annotation.IntRange;
22 import androidx.annotation.Nullable;
23 
24 import java.nio.ByteBuffer;
25 import java.nio.ByteOrder;
26 import java.util.ArrayList;
27 import java.util.List;
28 
29 /**
30  * Uwb Hybrid session controller configuration
31  */
32 public class FiraHybridSessionControllerConfig extends FiraParams {
33     private static final int BUNDLE_VERSION_1 = 1;
34     private static final int BUNDLE_VERSION_CURRENT = BUNDLE_VERSION_1;
35 
36     private static final int SHORT_MAC_ADDRESS = 0;
37     private static final int EXTENDED_MAC_ADDRESS = 1;
38     private static final int PHASE_LIST_SIZE = 20;
39 
40     private final int mNumberOfPhases;
41     private final byte[] mUpdateTime;
42     private final byte mMessageControl;
43     private final List<FiraHybridSessionPhaseList> mPhaseList;
44 
45     public static final String KEY_BUNDLE_VERSION = "bundle_version";
46     public static final String KEY_NUMBER_OF_PHASES = "number_of_phases";
47     public static final String KEY_MESSAGE_CONTROL = "message_control";
48     public static final String KEY_UPDATE_TIME = "update_time";
49     public static final String KEY_PHASE_LIST = "phase_list";
50 
51     @Override
getBundleVersion()52     public int getBundleVersion() {
53         return BUNDLE_VERSION_CURRENT;
54     }
55 
getNumberOfPhases()56     public int getNumberOfPhases() {
57         return mNumberOfPhases;
58     }
59 
getUpdateTime()60     public byte[] getUpdateTime() {
61         return mUpdateTime;
62     }
63 
getMessageControl()64     public byte getMessageControl() {
65         return mMessageControl;
66     }
67 
getPhaseList()68     public List<FiraHybridSessionPhaseList> getPhaseList() {
69         return mPhaseList;
70     }
71 
FiraHybridSessionControllerConfig(int numberOfPhases, byte[] updateTime, byte messageControl, List<FiraHybridSessionPhaseList> phaseList)72     private FiraHybridSessionControllerConfig(int numberOfPhases, byte[] updateTime,
73             byte messageControl, List<FiraHybridSessionPhaseList> phaseList) {
74         mNumberOfPhases = numberOfPhases;
75         mUpdateTime = updateTime;
76         mMessageControl = messageControl;
77         mPhaseList = phaseList;
78     }
79 
80     //TODO, move these utility methods to helper class
81     @Nullable
byteArrayToIntArray(@ullable byte[] bytes)82     private static int[] byteArrayToIntArray(@Nullable byte[] bytes) {
83         if (bytes == null) {
84             return null;
85         }
86 
87         int[] values = new int[bytes.length];
88         for (int i = 0; i < values.length; i++) {
89             values[i] = bytes[i];
90         }
91         return values;
92     }
93 
94     @Nullable
intArrayToByteArray(@ullable int[] values)95     private static byte[] intArrayToByteArray(@Nullable int[] values) {
96         if (values == null) {
97             return null;
98         }
99         byte[] bytes = new byte[values.length];
100         for (int i = 0; i < values.length; i++) {
101             bytes[i] = (byte) values[i];
102         }
103         return bytes;
104     }
105 
toBundle()106     public PersistableBundle toBundle() {
107         PersistableBundle bundle = super.toBundle();
108         bundle.putInt(KEY_BUNDLE_VERSION, getBundleVersion());
109         bundle.putInt(KEY_MESSAGE_CONTROL, mMessageControl);
110         bundle.putInt(KEY_NUMBER_OF_PHASES, mNumberOfPhases);
111         bundle.putIntArray(KEY_UPDATE_TIME, byteArrayToIntArray(mUpdateTime));
112 
113         ByteBuffer buffer = ByteBuffer.allocate(mNumberOfPhases * PHASE_LIST_SIZE);
114         buffer.order(ByteOrder.LITTLE_ENDIAN);
115         for (FiraHybridSessionPhaseList phaseList : mPhaseList) {
116             buffer.putInt(phaseList.getSessionHandle());
117             buffer.putShort(phaseList.getStartSlotIndex());
118             buffer.putShort(phaseList.getEndSlotIndex());
119             buffer.putInt(phaseList.getPhaseParticipation());
120             buffer.putLong(uwbAddressToLong(phaseList.getMacAddress()));
121         }
122 
123         bundle.putIntArray(KEY_PHASE_LIST, byteArrayToIntArray(buffer.array()));
124         return bundle;
125     }
126 
fromBundle(PersistableBundle bundle)127     public static FiraHybridSessionControllerConfig fromBundle(PersistableBundle bundle) {
128         switch (bundle.getInt(KEY_BUNDLE_VERSION)) {
129             case BUNDLE_VERSION_1:
130                 return parseVersion1(bundle);
131             default:
132                 throw new IllegalArgumentException("Invalid bundle version");
133         }
134     }
135 
parseVersion1(PersistableBundle bundle)136     private static FiraHybridSessionControllerConfig parseVersion1(PersistableBundle bundle) {
137         FiraHybridSessionControllerConfig.Builder builder =
138                 new FiraHybridSessionControllerConfig.Builder();
139 
140         int numberOfPhases = bundle.getInt(KEY_NUMBER_OF_PHASES);
141         builder.setNumberOfPhases(numberOfPhases);
142         builder.setUpdateTime(intArrayToByteArray(bundle.getIntArray(KEY_UPDATE_TIME)));
143         int messageControl = bundle.getInt(KEY_MESSAGE_CONTROL);
144         byte macAddressMode = (byte) (messageControl & 0x01);
145         builder.setMacAddressMode(macAddressMode);
146 
147         byte[] phaseByteArray = intArrayToByteArray(bundle.getIntArray(KEY_PHASE_LIST));
148         ByteBuffer buffer = ByteBuffer.wrap(phaseByteArray);
149         buffer.order(ByteOrder.LITTLE_ENDIAN);
150 
151         for (int i = 0; i < numberOfPhases; i++) {
152             FiraHybridSessionPhaseList mFiraHybridSessionPhaseList = new FiraHybridSessionPhaseList(
153                     buffer.getInt(),
154                     buffer.getShort(),
155                     buffer.getShort(),
156                     (byte) buffer.getInt(),
157                     longToUwbAddress(buffer.getLong(),
158                         (macAddressMode == SHORT_MAC_ADDRESS
159                         ? UwbAddress.SHORT_ADDRESS_BYTE_LENGTH
160                                     : UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH)));
161             builder.addPhaseList(mFiraHybridSessionPhaseList);
162         }
163         return builder.build();
164     }
165 
166     /** Builder */
167     public static class Builder {
168         private int mNumberOfPhases;
169         private byte[] mUpdateTime;
170         private byte mMacAddressMode;
171         private final List<FiraHybridSessionPhaseList> mPhaseList = new ArrayList<>();
172 
setNumberOfPhases(int numberOfPhases)173         public FiraHybridSessionControllerConfig.Builder setNumberOfPhases(int numberOfPhases) {
174             mNumberOfPhases = numberOfPhases;
175             return this;
176         }
177 
setUpdateTime(byte[] updateTime)178         public FiraHybridSessionControllerConfig.Builder setUpdateTime(byte[] updateTime) {
179             mUpdateTime = updateTime;
180             return this;
181         }
182 
setMacAddressMode(byte macAddressMode)183         public FiraHybridSessionControllerConfig.Builder setMacAddressMode(byte macAddressMode) {
184             mMacAddressMode = macAddressMode;
185             return this;
186         }
187 
addPhaseList( FiraHybridSessionPhaseList phaseList)188         public FiraHybridSessionControllerConfig.Builder addPhaseList(
189                 FiraHybridSessionPhaseList phaseList) {
190             mPhaseList.add(phaseList);
191             return this;
192         }
193 
build()194         public FiraHybridSessionControllerConfig build() {
195             if (mPhaseList.size() == 0) {
196                 throw new IllegalStateException("No hybrid session phase list have been set");
197             }
198             return new FiraHybridSessionControllerConfig(
199                     mNumberOfPhases,
200                     mUpdateTime,
201                     (byte) (mMacAddressMode & 0x01),
202                     mPhaseList);
203         }
204     }
205 
206     /** Defines parameters for hybrid session's secondary phase list */
207     public static class FiraHybridSessionPhaseList {
208         private final int mSessionHandle;
209 
210         @IntRange(from = 1, to = 32767)
211         private final short mStartSlotIndex;
212 
213         @IntRange(from = 1, to = 32767)
214         private final short mEndSlotIndex;
215         private final byte mPhaseParticipation;
216         private final UwbAddress mMacAddress;
217 
FiraHybridSessionPhaseList(int sessionHandle, @IntRange(from = 1, to = 32767) short startSlotIndex, @IntRange(from = 1, to = 32767) short endSlotIndex, @PhaseParticipationHybridSessionController byte phaseParticipation, UwbAddress macAddress)218         public FiraHybridSessionPhaseList(int sessionHandle,
219                 @IntRange(from = 1, to = 32767) short startSlotIndex,
220                 @IntRange(from = 1, to = 32767) short endSlotIndex,
221                 @PhaseParticipationHybridSessionController byte phaseParticipation,
222                 UwbAddress macAddress) {
223             mSessionHandle = sessionHandle;
224             mStartSlotIndex = startSlotIndex;
225             mEndSlotIndex = endSlotIndex;
226             mPhaseParticipation = phaseParticipation;
227             mMacAddress = macAddress;
228         }
229 
getSessionHandle()230         public int getSessionHandle() {
231             return mSessionHandle;
232         }
233 
234         @IntRange(from = 1, to = 32767)
getStartSlotIndex()235         public short getStartSlotIndex() {
236             return mStartSlotIndex;
237         }
238 
239         @IntRange(from = 1, to = 32767)
getEndSlotIndex()240         public short getEndSlotIndex() {
241             return mEndSlotIndex;
242         }
243 
244         @PhaseParticipationHybridSessionController
getPhaseParticipation()245         public byte getPhaseParticipation() {
246             return mPhaseParticipation;
247         }
248 
getMacAddress()249         public UwbAddress getMacAddress() {
250             return mMacAddress;
251         }
252     }
253 }
254