1 /* 2 * Copyright (C) 2016 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 17 package com.android.bluetooth.avrcp; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.media.session.MediaSession; 22 23 import com.android.bluetooth.Utils; 24 25 import java.util.List; 26 import java.util.Arrays; 27 import java.util.ArrayDeque; 28 import java.util.Collection; 29 30 /************************************************************************************************* 31 * Helper classes used for callback/response of browsing commands:- 32 * 1) To bundle parameters for native callbacks/response. 33 * 2) Stores information of Addressed and Browsed Media Players. 34 ************************************************************************************************/ 35 36 class AvrcpCmd { 37 AvrcpCmd()38 public AvrcpCmd() {} 39 40 /* Helper classes to pass parameters from callbacks to Avrcp handler */ 41 class FolderItemsCmd { 42 byte mScope; 43 long mStartItem; 44 long mEndItem; 45 byte mNumAttr; 46 int[] mAttrIDs; 47 public byte[] mAddress; 48 FolderItemsCmd(byte[] address, byte scope, long startItem, long endItem, byte numAttr, int[] attrIds)49 public FolderItemsCmd(byte[] address, byte scope, long startItem, long endItem, 50 byte numAttr, int[] attrIds) { 51 mAddress = address; 52 this.mScope = scope; 53 this.mStartItem = startItem; 54 this.mEndItem = endItem; 55 this.mNumAttr = numAttr; 56 this.mAttrIDs = attrIds; 57 } 58 toString()59 public String toString() { 60 StringBuilder sb = new StringBuilder(); 61 sb.append("[FolderItemCmd: scope " + mScope); 62 sb.append(" start " + mStartItem); 63 sb.append(" end " + mEndItem); 64 sb.append(" numAttr " + mNumAttr); 65 sb.append(" attrs: "); 66 for (int i = 0; i < mNumAttr; i++) { 67 sb.append(mAttrIDs[i] + " "); 68 } 69 return sb.toString(); 70 } 71 } 72 73 class ItemAttrCmd { 74 byte mScope; 75 byte[] mUid; 76 int mUidCounter; 77 byte mNumAttr; 78 int[] mAttrIDs; 79 public byte[] mAddress; 80 ItemAttrCmd(byte[] address, byte scope, byte[] uid, int uidCounter, byte numAttr, int[] attrIDs)81 public ItemAttrCmd(byte[] address, byte scope, byte[] uid, int uidCounter, byte numAttr, 82 int[] attrIDs) { 83 mAddress = address; 84 mScope = scope; 85 mUid = uid; 86 mUidCounter = uidCounter; 87 mNumAttr = numAttr; 88 mAttrIDs = attrIDs; 89 } 90 toString()91 public String toString() { 92 StringBuilder sb = new StringBuilder(); 93 sb.append("[ItemAttrCmd: scope " + mScope); 94 sb.append(" uid " + Utils.byteArrayToString(mUid)); 95 sb.append(" numAttr " + mNumAttr); 96 sb.append(" attrs: "); 97 for (int i = 0; i < mNumAttr; i++) { 98 sb.append(mAttrIDs[i] + " "); 99 } 100 return sb.toString(); 101 } 102 } 103 104 class ElementAttrCmd { 105 byte mNumAttr; 106 int[] mAttrIDs; 107 public byte[] mAddress; 108 ElementAttrCmd(byte[] address, byte numAttr, int[] attrIDs)109 public ElementAttrCmd(byte[] address, byte numAttr, int[] attrIDs) { 110 mAddress = address; 111 mNumAttr = numAttr; 112 mAttrIDs = attrIDs; 113 } 114 } 115 } 116 117 /* Helper classes to pass parameters to native response */ 118 class MediaPlayerListRsp { 119 byte mStatus; 120 short mUIDCounter; 121 byte itemType; 122 int[] mPlayerIds; 123 byte[] mPlayerTypes; 124 int[] mPlayerSubTypes; 125 byte[] mPlayStatusValues; 126 short[] mFeatureBitMaskValues; 127 String[] mPlayerNameList; 128 int mNumItems; 129 MediaPlayerListRsp(byte status, short UIDCounter, int numItems, byte itemType, int[] playerIds, byte[] playerTypes, int[] playerSubTypes, byte[] playStatusValues, short[] featureBitMaskValues, String[] playerNameList)130 public MediaPlayerListRsp(byte status, short UIDCounter, int numItems, byte itemType, 131 int[] playerIds, byte[] playerTypes, int[] playerSubTypes, byte[] playStatusValues, 132 short[] featureBitMaskValues, String[] playerNameList) { 133 this.mStatus = status; 134 this.mUIDCounter = UIDCounter; 135 this.mNumItems = numItems; 136 this.itemType = itemType; 137 this.mPlayerIds = playerIds; 138 this.mPlayerTypes = playerTypes; 139 this.mPlayerSubTypes = new int[numItems]; 140 this.mPlayerSubTypes = playerSubTypes; 141 this.mPlayStatusValues = new byte[numItems]; 142 this.mPlayStatusValues = playStatusValues; 143 int bitMaskSize = AvrcpConstants.AVRC_FEATURE_MASK_SIZE; 144 this.mFeatureBitMaskValues = new short[numItems * bitMaskSize]; 145 for (int bitMaskIndex = 0; bitMaskIndex < (numItems * bitMaskSize); bitMaskIndex++) { 146 this.mFeatureBitMaskValues[bitMaskIndex] = featureBitMaskValues[bitMaskIndex]; 147 } 148 this.mPlayerNameList = playerNameList; 149 } 150 } 151 152 class FolderItemsRsp { 153 byte mStatus; 154 short mUIDCounter; 155 byte mScope; 156 int mNumItems; 157 byte[] mFolderTypes; 158 byte[] mPlayable; 159 byte[] mItemTypes; 160 byte[] mItemUid; 161 String[] mDisplayNames; /* display name of the item. Eg: Folder name or song name */ 162 int[] mAttributesNum; 163 int[] mAttrIds; 164 String[] mAttrValues; 165 FolderItemsRsp(byte Status, short UIDCounter, byte scope, int numItems, byte[] folderTypes, byte[] playable, byte[] ItemTypes, byte[] ItemsUid, String[] displayNameArray, int[] AttributesNum, int[] AttrIds, String[] attrValues)166 public FolderItemsRsp(byte Status, short UIDCounter, byte scope, int numItems, 167 byte[] folderTypes, byte[] playable, byte[] ItemTypes, byte[] ItemsUid, 168 String[] displayNameArray, int[] AttributesNum, int[] AttrIds, String[] attrValues) { 169 this.mStatus = Status; 170 this.mUIDCounter = UIDCounter; 171 this.mScope = scope; 172 this.mNumItems = numItems; 173 this.mFolderTypes = folderTypes; 174 this.mPlayable = playable; 175 this.mItemTypes = ItemTypes; 176 this.mItemUid = ItemsUid; 177 this.mDisplayNames = displayNameArray; 178 this.mAttributesNum = AttributesNum; 179 this.mAttrIds = AttrIds; 180 this.mAttrValues = attrValues; 181 } 182 } 183 184 class ItemAttrRsp { 185 byte mStatus; 186 byte mNumAttr; 187 int[] mAttributesIds; 188 String[] mAttributesArray; 189 ItemAttrRsp(byte status, int[] attributesIds, String[] attributesArray)190 public ItemAttrRsp(byte status, int[] attributesIds, String[] attributesArray) { 191 mStatus = status; 192 mNumAttr = (byte) attributesIds.length; 193 mAttributesIds = attributesIds; 194 mAttributesArray = attributesArray; 195 } 196 } 197 198 /* stores information of Media Players in the system */ 199 class MediaPlayerInfo { 200 201 private byte majorType; 202 private int subType; 203 private byte playStatus; 204 private short[] featureBitMask; 205 private @NonNull String packageName; 206 private @NonNull String displayableName; 207 private @Nullable MediaController mediaController; 208 MediaPlayerInfo(@ullable MediaController controller, byte majorType, int subType, byte playStatus, short[] featureBitMask, @NonNull String packageName, @Nullable String displayableName)209 MediaPlayerInfo(@Nullable MediaController controller, byte majorType, int subType, 210 byte playStatus, short[] featureBitMask, @NonNull String packageName, 211 @Nullable String displayableName) { 212 this.setMajorType(majorType); 213 this.setSubType(subType); 214 this.playStatus = playStatus; 215 // store a copy the FeatureBitMask array 216 this.featureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length); 217 Arrays.sort(this.featureBitMask); 218 this.setPackageName(packageName); 219 this.setDisplayableName(displayableName); 220 this.setMediaController(controller); 221 } 222 223 /* getters and setters */ getPlayStatus()224 byte getPlayStatus() { 225 return playStatus; 226 } 227 setPlayStatus(byte playStatus)228 void setPlayStatus(byte playStatus) { 229 this.playStatus = playStatus; 230 } 231 getMediaController()232 MediaController getMediaController() { 233 return mediaController; 234 } 235 setMediaController(MediaController mediaController)236 void setMediaController(MediaController mediaController) { 237 if (mediaController != null) { 238 this.packageName = mediaController.getPackageName(); 239 } 240 this.mediaController = mediaController; 241 } 242 setPackageName(@onNull String name)243 void setPackageName(@NonNull String name) { 244 // Controller determines package name when it is set. 245 if (mediaController != null) return; 246 this.packageName = name; 247 } 248 getPackageName()249 String getPackageName() { 250 if (mediaController != null) { 251 return mediaController.getPackageName(); 252 } else if (packageName != null) { 253 return packageName; 254 } 255 return null; 256 } 257 getMajorType()258 byte getMajorType() { 259 return majorType; 260 } 261 setMajorType(byte majorType)262 void setMajorType(byte majorType) { 263 this.majorType = majorType; 264 } 265 getSubType()266 int getSubType() { 267 return subType; 268 } 269 setSubType(int subType)270 void setSubType(int subType) { 271 this.subType = subType; 272 } 273 getDisplayableName()274 String getDisplayableName() { 275 return displayableName; 276 } 277 setDisplayableName(@ullable String displayableName)278 void setDisplayableName(@Nullable String displayableName) { 279 if (displayableName == null) displayableName = ""; 280 this.displayableName = displayableName; 281 } 282 getFeatureBitMask()283 short[] getFeatureBitMask() { 284 return featureBitMask; 285 } 286 setFeatureBitMask(short[] featureBitMask)287 void setFeatureBitMask(short[] featureBitMask) { 288 synchronized (this) { 289 this.featureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length); 290 Arrays.sort(this.featureBitMask); 291 } 292 } 293 isBrowseSupported()294 boolean isBrowseSupported() { 295 synchronized (this) { 296 if (this.featureBitMask == null) return false; 297 for (short bit : this.featureBitMask) { 298 if (bit == AvrcpConstants.AVRC_PF_BROWSE_BIT_NO) return true; 299 } 300 } 301 return false; 302 } 303 304 /** Tests if the view of this player presented to the controller is different enough to 305 * justify sending an Available Players Changed update */ equalView(MediaPlayerInfo other)306 public boolean equalView(MediaPlayerInfo other) { 307 return (this.majorType == other.getMajorType()) && (this.subType == other.getSubType()) 308 && Arrays.equals(this.featureBitMask, other.getFeatureBitMask()) 309 && this.displayableName.equals(other.getDisplayableName()); 310 } 311 312 @Override toString()313 public String toString() { 314 StringBuilder sb = new StringBuilder(); 315 sb.append("MediaPlayerInfo "); 316 sb.append(getPackageName()); 317 sb.append(" (as '" + getDisplayableName() + "')"); 318 sb.append(" Type = " + getMajorType()); 319 sb.append(", SubType = " + getSubType()); 320 sb.append(", Status = " + playStatus); 321 sb.append(" Feature Bits ["); 322 short[] bits = getFeatureBitMask(); 323 for (int i = 0; i < bits.length; i++) { 324 if (i != 0) sb.append(" "); 325 sb.append(bits[i]); 326 } 327 sb.append("] Controller: "); 328 sb.append(getMediaController()); 329 return sb.toString(); 330 } 331 } 332 333 /* stores information for browsable Media Players available in the system */ 334 class BrowsePlayerInfo { 335 String packageName; 336 String displayableName; 337 String serviceClass; 338 BrowsePlayerInfo(String packageName, String displayableName, String serviceClass)339 public BrowsePlayerInfo(String packageName, String displayableName, String serviceClass) { 340 this.packageName = packageName; 341 this.displayableName = displayableName; 342 this.serviceClass = serviceClass; 343 } 344 345 @Override toString()346 public String toString() { 347 StringBuilder sb = new StringBuilder(); 348 sb.append("BrowsePlayerInfo "); 349 sb.append(packageName); 350 sb.append(" ( as '" + displayableName + "')"); 351 sb.append(" service " + serviceClass); 352 return sb.toString(); 353 } 354 } 355 356 class FolderItemsData { 357 /* initialize sizes for rsp parameters */ 358 int mNumItems; 359 int[] mAttributesNum; 360 byte[] mFolderTypes; 361 byte[] mItemTypes; 362 byte[] mPlayable; 363 byte[] mItemUid; 364 String[] mDisplayNames; 365 int[] mAttrIds; 366 String[] mAttrValues; 367 int attrCounter; 368 FolderItemsData(int size)369 public FolderItemsData(int size) { 370 mNumItems = size; 371 mAttributesNum = new int[size]; 372 373 mFolderTypes = new byte[size]; /* folderTypes */ 374 mItemTypes = new byte[size]; /* folder or media item */ 375 mPlayable = new byte[size]; 376 Arrays.fill(mFolderTypes, AvrcpConstants.FOLDER_TYPE_MIXED); 377 Arrays.fill(mItemTypes, AvrcpConstants.BTRC_ITEM_MEDIA); 378 Arrays.fill(mPlayable, AvrcpConstants.ITEM_PLAYABLE); 379 380 mItemUid = new byte[size * AvrcpConstants.UID_SIZE]; 381 mDisplayNames = new String[size]; 382 383 mAttrIds = null; /* array of attr ids */ 384 mAttrValues = null; /* array of attr values */ 385 } 386 } 387 388 /** A queue that evicts the first element when you add an element to the end when it reaches a 389 * maximum size. 390 * This is useful for keeping a FIFO queue of items where the items drop off the front, i.e. a log 391 * with a maximum size. 392 */ 393 class EvictingQueue<E> extends ArrayDeque<E> { 394 private int mMaxSize; 395 EvictingQueue(int maxSize)396 public EvictingQueue(int maxSize) { 397 super(); 398 mMaxSize = maxSize; 399 } 400 EvictingQueue(int maxSize, int initialElements)401 public EvictingQueue(int maxSize, int initialElements) { 402 super(initialElements); 403 mMaxSize = maxSize; 404 } 405 EvictingQueue(int maxSize, Collection<? extends E> c)406 public EvictingQueue(int maxSize, Collection<? extends E> c) { 407 super(c); 408 mMaxSize = maxSize; 409 } 410 411 @Override addFirst(E e)412 public void addFirst(E e) { 413 if (super.size() == mMaxSize) return; 414 super.addFirst(e); 415 } 416 417 @Override addLast(E e)418 public void addLast(E e) { 419 if (super.size() == mMaxSize) { 420 super.remove(); 421 } 422 super.addLast(e); 423 } 424 425 @Override offerFirst(E e)426 public boolean offerFirst(E e) { 427 if (super.size() == mMaxSize) return false; 428 return super.offerFirst(e); 429 } 430 431 @Override offerLast(E e)432 public boolean offerLast(E e) { 433 if (super.size() == mMaxSize) { 434 super.remove(); 435 } 436 return super.offerLast(e); 437 } 438 } 439