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 List<FiraHybridSessionPhaseList> mPhaseList; 42 43 public static final String KEY_BUNDLE_VERSION = "bundle_version"; 44 public static final String KEY_NUMBER_OF_PHASES = "number_of_phases"; 45 public static final String KEY_MESSAGE_CONTROL = "message_control"; 46 public static final String KEY_UPDATE_TIME = "update_time"; 47 public static final String KEY_PHASE_LIST = "phase_list"; 48 49 @Override getBundleVersion()50 public int getBundleVersion() { 51 return BUNDLE_VERSION_CURRENT; 52 } 53 getNumberOfPhases()54 public int getNumberOfPhases() { 55 return mNumberOfPhases; 56 } 57 getPhaseList()58 public List<FiraHybridSessionPhaseList> getPhaseList() { 59 return mPhaseList; 60 } 61 FiraHybridSessionControllerConfig(int numberOfPhases, List<FiraHybridSessionPhaseList> phaseList)62 private FiraHybridSessionControllerConfig(int numberOfPhases, 63 List<FiraHybridSessionPhaseList> phaseList) { 64 mNumberOfPhases = numberOfPhases; 65 mPhaseList = phaseList; 66 } 67 68 //TODO, move these utility methods to helper class 69 @Nullable byteArrayToIntArray(@ullable byte[] bytes)70 private static int[] byteArrayToIntArray(@Nullable byte[] bytes) { 71 if (bytes == null) { 72 return null; 73 } 74 75 int[] values = new int[bytes.length]; 76 for (int i = 0; i < values.length; i++) { 77 values[i] = bytes[i]; 78 } 79 return values; 80 } 81 82 @Nullable intArrayToByteArray(@ullable int[] values)83 private static byte[] intArrayToByteArray(@Nullable int[] values) { 84 if (values == null) { 85 return null; 86 } 87 byte[] bytes = new byte[values.length]; 88 for (int i = 0; i < values.length; i++) { 89 bytes[i] = (byte) values[i]; 90 } 91 return bytes; 92 } 93 toBundle()94 public PersistableBundle toBundle() { 95 PersistableBundle bundle = super.toBundle(); 96 bundle.putInt(KEY_BUNDLE_VERSION, getBundleVersion()); 97 bundle.putInt(KEY_NUMBER_OF_PHASES, mNumberOfPhases); 98 99 ByteBuffer buffer = ByteBuffer.allocate(mNumberOfPhases * PHASE_LIST_SIZE); 100 buffer.order(ByteOrder.LITTLE_ENDIAN); 101 for (FiraHybridSessionPhaseList phaseList : mPhaseList) { 102 buffer.putInt(phaseList.getSessionId()); 103 buffer.putShort(phaseList.getStartSlotIndex()); 104 buffer.putShort(phaseList.getEndSlotIndex()); 105 buffer.putInt(phaseList.getMessageControl()); 106 buffer.putLong(uwbAddressToLong(phaseList.getMacAddress())); 107 } 108 109 bundle.putIntArray(KEY_PHASE_LIST, byteArrayToIntArray(buffer.array())); 110 return bundle; 111 } 112 fromBundle(PersistableBundle bundle)113 public static FiraHybridSessionControllerConfig fromBundle(PersistableBundle bundle) { 114 switch (bundle.getInt(KEY_BUNDLE_VERSION)) { 115 case BUNDLE_VERSION_1: 116 return parseVersion1(bundle); 117 default: 118 throw new IllegalArgumentException("Invalid bundle version"); 119 } 120 } 121 parseVersion1(PersistableBundle bundle)122 private static FiraHybridSessionControllerConfig parseVersion1(PersistableBundle bundle) { 123 FiraHybridSessionControllerConfig.Builder builder = 124 new FiraHybridSessionControllerConfig.Builder(); 125 126 int numberOfPhases = bundle.getInt(KEY_NUMBER_OF_PHASES); 127 builder.setNumberOfPhases(numberOfPhases); 128 129 byte[] phaseByteArray = intArrayToByteArray(bundle.getIntArray(KEY_PHASE_LIST)); 130 ByteBuffer buffer = ByteBuffer.wrap(phaseByteArray); 131 buffer.order(ByteOrder.LITTLE_ENDIAN); 132 133 for (int i = 0; i < numberOfPhases; i++) { 134 final int sessionId = buffer.getInt(); 135 final short startSlotIndex = buffer.getShort(); 136 final short endSlotIndex = buffer.getShort(); 137 final byte messageControl = (byte) buffer.getInt(); 138 final int addressLength = ((messageControl & 0x01) == SHORT_MAC_ADDRESS) 139 ? UwbAddress.SHORT_ADDRESS_BYTE_LENGTH 140 : UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH; 141 142 FiraHybridSessionPhaseList mFiraHybridSessionPhaseList = new FiraHybridSessionPhaseList( 143 sessionId, 144 startSlotIndex, 145 endSlotIndex, 146 messageControl, 147 longToUwbAddress(buffer.getLong(), addressLength) 148 ); 149 150 builder.addPhaseList(mFiraHybridSessionPhaseList); 151 } 152 return builder.build(); 153 } 154 155 /** Builder */ 156 public static class Builder { 157 private int mNumberOfPhases; 158 private final List<FiraHybridSessionPhaseList> mPhaseList = new ArrayList<>(); 159 setNumberOfPhases(int numberOfPhases)160 public FiraHybridSessionControllerConfig.Builder setNumberOfPhases(int numberOfPhases) { 161 mNumberOfPhases = numberOfPhases; 162 return this; 163 } 164 addPhaseList( FiraHybridSessionPhaseList phaseList)165 public FiraHybridSessionControllerConfig.Builder addPhaseList( 166 FiraHybridSessionPhaseList phaseList) { 167 mPhaseList.add(phaseList); 168 return this; 169 } 170 build()171 public FiraHybridSessionControllerConfig build() { 172 if (mPhaseList.size() == 0) { 173 throw new IllegalStateException("No hybrid session phase list have been set"); 174 } 175 return new FiraHybridSessionControllerConfig( 176 mNumberOfPhases, 177 mPhaseList); 178 } 179 } 180 181 /** Defines parameters for hybrid session's secondary phase list */ 182 public static class FiraHybridSessionPhaseList { 183 private final int mSessionId; 184 185 @IntRange(from = 1, to = 32767) 186 private final short mStartSlotIndex; 187 188 @IntRange(from = 1, to = 32767) 189 private final short mEndSlotIndex; 190 private final byte mMessageControl; 191 private final UwbAddress mMacAddress; 192 FiraHybridSessionPhaseList(int sessionId, @IntRange(from = 1, to = 32767) short startSlotIndex, @IntRange(from = 1, to = 32767) short endSlotIndex, byte messageControl, UwbAddress macAddress)193 public FiraHybridSessionPhaseList(int sessionId, 194 @IntRange(from = 1, to = 32767) short startSlotIndex, 195 @IntRange(from = 1, to = 32767) short endSlotIndex, 196 byte messageControl, 197 UwbAddress macAddress) { 198 mSessionId = sessionId; 199 mStartSlotIndex = startSlotIndex; 200 mEndSlotIndex = endSlotIndex; 201 mMessageControl = messageControl; 202 mMacAddress = macAddress; 203 } 204 getSessionId()205 public int getSessionId() { 206 return mSessionId; 207 } 208 209 @IntRange(from = 1, to = 32767) getStartSlotIndex()210 public short getStartSlotIndex() { 211 return mStartSlotIndex; 212 } 213 214 @IntRange(from = 1, to = 32767) getEndSlotIndex()215 public short getEndSlotIndex() { 216 return mEndSlotIndex; 217 } 218 getMessageControl()219 public byte getMessageControl() { 220 return mMessageControl; 221 } 222 getMacAddress()223 public UwbAddress getMacAddress() { 224 return mMacAddress; 225 } 226 } 227 } 228