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 22 import com.android.bluetooth.Utils; 23 24 import java.util.ArrayDeque; 25 import java.util.Arrays; 26 import java.util.Collection; 27 28 /************************************************************************************************* 29 * Helper classes used for callback/response of browsing commands:- 30 * 1) To bundle parameters for native callbacks/response. 31 * 2) Stores information of Addressed and Browsed Media Players. 32 ************************************************************************************************/ 33 34 class AvrcpCmd { 35 36 AvrcpCmd() {} 37 38 /* Helper classes to pass parameters from callbacks to Avrcp handler */ 39 class FolderItemsCmd { 40 byte mScope; 41 long mStartItem; 42 long mEndItem; 43 byte mNumAttr; 44 int[] mAttrIDs; 45 public byte[] mAddress; 46 47 FolderItemsCmd(byte[] address, byte scope, long startItem, long endItem, byte numAttr, 48 int[] attrIds) { 49 mAddress = address; 50 this.mScope = scope; 51 this.mStartItem = startItem; 52 this.mEndItem = endItem; 53 this.mNumAttr = numAttr; 54 this.mAttrIDs = attrIds; 55 } 56 57 @Override 58 public String toString() { 59 StringBuilder sb = new StringBuilder(); 60 sb.append("[FolderItemCmd: scope " + mScope); 61 sb.append(" start " + mStartItem); 62 sb.append(" end " + mEndItem); 63 sb.append(" numAttr " + mNumAttr); 64 sb.append(" attrs: "); 65 for (int i = 0; i < mNumAttr; i++) { 66 sb.append(mAttrIDs[i] + " "); 67 } 68 return sb.toString(); 69 } 70 } 71 72 class ItemAttrCmd { 73 byte mScope; 74 byte[] mUid; 75 int mUidCounter; 76 byte mNumAttr; 77 int[] mAttrIDs; 78 public byte[] mAddress; 79 80 ItemAttrCmd(byte[] address, byte scope, byte[] uid, int uidCounter, byte numAttr, 81 int[] attrIDs) { 82 mAddress = address; 83 mScope = scope; 84 mUid = uid; 85 mUidCounter = uidCounter; 86 mNumAttr = numAttr; 87 mAttrIDs = attrIDs; 88 } 89 90 @Override 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 109 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 mItemType; 122 int[] mPlayerIds; 123 byte[] mPlayerTypes; 124 int[] mPlayerSubTypes; 125 byte[] mPlayStatusValues; 126 short[] mFeatureBitMaskValues; 127 String[] mPlayerNameList; 128 int mNumItems; 129 130 MediaPlayerListRsp(byte status, short uidCounter, int numItems, byte itemType, int[] playerIds, 131 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.mItemType = 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 166 FolderItemsRsp(byte status, short uidCounter, byte scope, int numItems, byte[] folderTypes, 167 byte[] playable, byte[] itemTypes, byte[] itemsUid, String[] displayNameArray, 168 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 190 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 mMajorType; 202 private int mSubType; 203 private byte mPlayStatus; 204 private short[] mFeatureBitMask; 205 @NonNull private String mPackageName; 206 @NonNull private String mDisplayableName; 207 @Nullable private MediaController mMediaController; 208 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.mPlayStatus = playStatus; 215 // store a copy the FeatureBitMask array 216 this.mFeatureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length); 217 Arrays.sort(this.mFeatureBitMask); 218 this.setPackageName(packageName); 219 this.setDisplayableName(displayableName); 220 this.setMediaController(controller); 221 } 222 223 /* getters and setters */ 224 byte getPlayStatus() { 225 return mPlayStatus; 226 } 227 228 void setPlayStatus(byte playStatus) { 229 this.mPlayStatus = playStatus; 230 } 231 232 MediaController getMediaController() { 233 return mMediaController; 234 } 235 236 void setMediaController(MediaController mediaController) { 237 if (mediaController != null) { 238 this.mPackageName = mediaController.getPackageName(); 239 } 240 this.mMediaController = mediaController; 241 } 242 243 void setPackageName(@NonNull String name) { 244 // Controller determines package name when it is set. 245 if (mMediaController != null) { 246 return; 247 } 248 this.mPackageName = name; 249 } 250 251 String getPackageName() { 252 if (mMediaController != null) { 253 return mMediaController.getPackageName(); 254 } else if (mPackageName != null) { 255 return mPackageName; 256 } 257 return null; 258 } 259 260 byte getMajorType() { 261 return mMajorType; 262 } 263 264 void setMajorType(byte majorType) { 265 this.mMajorType = majorType; 266 } 267 268 int getSubType() { 269 return mSubType; 270 } 271 272 void setSubType(int subType) { 273 this.mSubType = subType; 274 } 275 276 String getDisplayableName() { 277 return mDisplayableName; 278 } 279 280 void setDisplayableName(@Nullable String displayableName) { 281 if (displayableName == null) { 282 displayableName = ""; 283 } 284 this.mDisplayableName = displayableName; 285 } 286 287 short[] getFeatureBitMask() { 288 return mFeatureBitMask; 289 } 290 291 void setFeatureBitMask(short[] featureBitMask) { 292 synchronized (this) { 293 this.mFeatureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length); 294 Arrays.sort(this.mFeatureBitMask); 295 } 296 } 297 298 boolean isBrowseSupported() { 299 synchronized (this) { 300 if (this.mFeatureBitMask == null) { 301 return false; 302 } 303 for (short bit : this.mFeatureBitMask) { 304 if (bit == AvrcpConstants.AVRC_PF_BROWSE_BIT_NO) { 305 return true; 306 } 307 } 308 } 309 return false; 310 } 311 312 /** Tests if the view of this player presented to the controller is different enough to 313 * justify sending an Available Players Changed update */ 314 public boolean equalView(MediaPlayerInfo other) { 315 return (this.mMajorType == other.getMajorType()) && (this.mSubType == other.getSubType()) 316 && Arrays.equals(this.mFeatureBitMask, other.getFeatureBitMask()) 317 && this.mDisplayableName.equals(other.getDisplayableName()); 318 } 319 320 @Override 321 public String toString() { 322 StringBuilder sb = new StringBuilder(); 323 sb.append("MediaPlayerInfo "); 324 sb.append(getPackageName()); 325 sb.append(" (as '" + getDisplayableName() + "')"); 326 sb.append(" Type = " + getMajorType()); 327 sb.append(", SubType = " + getSubType()); 328 sb.append(", Status = " + mPlayStatus); 329 sb.append(" Feature Bits ["); 330 short[] bits = getFeatureBitMask(); 331 for (int i = 0; i < bits.length; i++) { 332 if (i != 0) { 333 sb.append(" "); 334 } 335 sb.append(bits[i]); 336 } 337 sb.append("] Controller: "); 338 sb.append(getMediaController()); 339 return sb.toString(); 340 } 341 } 342 343 /* stores information for browsable Media Players available in the system */ 344 class BrowsePlayerInfo { 345 public String packageName; 346 public String displayableName; 347 public String serviceClass; 348 349 BrowsePlayerInfo(String packageName, String displayableName, String serviceClass) { 350 this.packageName = packageName; 351 this.displayableName = displayableName; 352 this.serviceClass = serviceClass; 353 } 354 355 @Override 356 public String toString() { 357 StringBuilder sb = new StringBuilder(); 358 sb.append("BrowsePlayerInfo "); 359 sb.append(packageName); 360 sb.append(" ( as '" + displayableName + "')"); 361 sb.append(" service " + serviceClass); 362 return sb.toString(); 363 } 364 } 365 366 class FolderItemsData { 367 /* initialize sizes for rsp parameters */ int mNumItems; 368 int[] mAttributesNum; 369 byte[] mFolderTypes; 370 byte[] mItemTypes; 371 byte[] mPlayable; 372 byte[] mItemUid; 373 String[] mDisplayNames; 374 int[] mAttrIds; 375 String[] mAttrValues; 376 int mAttrCounter; 377 378 FolderItemsData(int size) { 379 mNumItems = size; 380 mAttributesNum = new int[size]; 381 382 mFolderTypes = new byte[size]; /* folderTypes */ 383 mItemTypes = new byte[size]; /* folder or media item */ 384 mPlayable = new byte[size]; 385 Arrays.fill(mFolderTypes, AvrcpConstants.FOLDER_TYPE_MIXED); 386 Arrays.fill(mItemTypes, AvrcpConstants.BTRC_ITEM_MEDIA); 387 Arrays.fill(mPlayable, AvrcpConstants.ITEM_PLAYABLE); 388 389 mItemUid = new byte[size * AvrcpConstants.UID_SIZE]; 390 mDisplayNames = new String[size]; 391 392 mAttrIds = null; /* array of attr ids */ 393 mAttrValues = null; /* array of attr values */ 394 } 395 } 396 397 /** A queue that evicts the first element when you add an element to the end when it reaches a 398 * maximum size. 399 * This is useful for keeping a FIFO queue of items where the items drop off the front, i.e. a log 400 * with a maximum size. 401 */ 402 class EvictingQueue<E> extends ArrayDeque<E> { 403 private int mMaxSize; 404 405 EvictingQueue(int maxSize) { 406 super(); 407 mMaxSize = maxSize; 408 } 409 410 EvictingQueue(int maxSize, int initialElements) { 411 super(initialElements); 412 mMaxSize = maxSize; 413 } 414 415 EvictingQueue(int maxSize, Collection<? extends E> c) { 416 super(c); 417 mMaxSize = maxSize; 418 } 419 420 @Override 421 public void addFirst(E e) { 422 if (super.size() == mMaxSize) { 423 return; 424 } 425 super.addFirst(e); 426 } 427 428 @Override 429 public void addLast(E e) { 430 if (super.size() == mMaxSize) { 431 super.remove(); 432 } 433 super.addLast(e); 434 } 435 436 @Override 437 public boolean offerFirst(E e) { 438 if (super.size() == mMaxSize) { 439 return false; 440 } 441 return super.offerFirst(e); 442 } 443 444 @Override 445 public boolean offerLast(E e) { 446 if (super.size() == mMaxSize) { 447 super.remove(); 448 } 449 return super.offerLast(e); 450 } 451 } 452