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
17package com.android.bluetooth.avrcp;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.media.session.MediaSession;
22
23import com.android.bluetooth.Utils;
24
25import java.util.List;
26import java.util.Arrays;
27import java.util.ArrayDeque;
28import 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
36class AvrcpCmd {
37
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
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
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
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
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        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 */
118class 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
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
152class 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    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
184class ItemAttrRsp {
185    byte mStatus;
186    byte mNumAttr;
187    int[] mAttributesIds;
188    String[] mAttributesArray;
189
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 */
199class 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
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 */
224    byte getPlayStatus() {
225        return playStatus;
226    }
227
228    void setPlayStatus(byte playStatus) {
229        this.playStatus = playStatus;
230    }
231
232    MediaController getMediaController() {
233        return mediaController;
234    }
235
236    void setMediaController(MediaController mediaController) {
237        if (mediaController != null) {
238            this.packageName = mediaController.getPackageName();
239        }
240        this.mediaController = mediaController;
241    }
242
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
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
258    byte getMajorType() {
259        return majorType;
260    }
261
262    void setMajorType(byte majorType) {
263        this.majorType = majorType;
264    }
265
266    int getSubType() {
267        return subType;
268    }
269
270    void setSubType(int subType) {
271        this.subType = subType;
272    }
273
274    String getDisplayableName() {
275        return displayableName;
276    }
277
278    void setDisplayableName(@Nullable String displayableName) {
279        if (displayableName == null) displayableName = "";
280        this.displayableName = displayableName;
281    }
282
283    short[] getFeatureBitMask() {
284        return featureBitMask;
285    }
286
287    void setFeatureBitMask(short[] featureBitMask) {
288        synchronized (this) {
289            this.featureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
290            Arrays.sort(this.featureBitMask);
291        }
292    }
293
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 */
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
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 */
334class BrowsePlayerInfo {
335    String packageName;
336    String displayableName;
337    String serviceClass;
338
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
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
356class 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
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 */
393class EvictingQueue<E> extends ArrayDeque<E> {
394    private int mMaxSize;
395
396    public EvictingQueue(int maxSize) {
397        super();
398        mMaxSize = maxSize;
399    }
400
401    public EvictingQueue(int maxSize, int initialElements) {
402        super(initialElements);
403        mMaxSize = maxSize;
404    }
405
406    public EvictingQueue(int maxSize, Collection<? extends E> c) {
407        super(c);
408        mMaxSize = maxSize;
409    }
410
411    @Override
412    public void addFirst(E e) {
413        if (super.size() == mMaxSize) return;
414        super.addFirst(e);
415    }
416
417    @Override
418    public void addLast(E e) {
419        if (super.size() == mMaxSize) {
420            super.remove();
421        }
422        super.addLast(e);
423    }
424
425    @Override
426    public boolean offerFirst(E e) {
427        if (super.size() == mMaxSize) return false;
428        return super.offerFirst(e);
429    }
430
431    @Override
432    public boolean offerLast(E e) {
433        if (super.size() == mMaxSize) {
434            super.remove();
435        }
436        return super.offerLast(e);
437    }
438}
439