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