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.avrcpcontroller;
18 
19 import android.bluetooth.BluetoothDevice;
20 import android.net.Uri;
21 import android.os.SystemClock;
22 import android.support.v4.media.session.MediaSessionCompat;
23 import android.support.v4.media.session.PlaybackStateCompat;
24 import android.util.Log;
25 
26 import java.util.Arrays;
27 
28 /*
29  * Contains information about remote player
30  */
31 class AvrcpPlayer {
32     private static final String TAG = AvrcpPlayer.class.getSimpleName();
33 
34     public static final int DEFAULT_ID = -1;
35 
36     public static final int TYPE_UNKNOWN = -1;
37     public static final int TYPE_AUDIO = 0;
38     public static final int TYPE_VIDEO = 1;
39     public static final int TYPE_BROADCASTING_AUDIO = 2;
40     public static final int TYPE_BROADCASTING_VIDEO = 3;
41 
42     public static final int SUB_TYPE_UNKNOWN = -1;
43     public static final int SUB_TYPE_AUDIO_BOOK = 0;
44     public static final int SUB_TYPE_PODCAST = 1;
45 
46     public static final int FEATURE_PLAY = 40;
47     public static final int FEATURE_STOP = 41;
48     public static final int FEATURE_PAUSE = 42;
49     public static final int FEATURE_REWIND = 44;
50     public static final int FEATURE_FAST_FORWARD = 45;
51     public static final int FEATURE_FORWARD = 47;
52     public static final int FEATURE_PREVIOUS = 48;
53     public static final int FEATURE_BROWSING = 59;
54     public static final int FEATURE_NOW_PLAYING = 65;
55 
56     private BluetoothDevice mDevice;
57     private int mPlayStatus = PlaybackStateCompat.STATE_NONE;
58     private long mPlayTime = PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN;
59     private float mPlaySpeed = 1;
60     private int mId;
61     private String mName = "";
62     private byte[] mPlayerFeatures = new byte[16];
63     private long mAvailableActions = PlaybackStateCompat.ACTION_PREPARE;
64     private AvrcpItem mCurrentTrack;
65     private PlaybackStateCompat mPlaybackStateCompat;
66     private PlayerApplicationSettings mSupportedPlayerApplicationSettings =
67             new PlayerApplicationSettings();
68     private PlayerApplicationSettings mCurrentPlayerApplicationSettings;
69 
AvrcpPlayer( BluetoothDevice device, int id, String name, byte[] playerFeatures, int playStatus)70     private AvrcpPlayer(
71             BluetoothDevice device, int id, String name, byte[] playerFeatures, int playStatus) {
72         mDevice = device;
73         mId = id;
74         mName = name;
75         mPlayerFeatures = Arrays.copyOf(playerFeatures, playerFeatures.length);
76         PlaybackStateCompat.Builder playbackStateBuilder =
77                 new PlaybackStateCompat.Builder().setActions(mAvailableActions);
78         mPlaybackStateCompat = playbackStateBuilder.build();
79         updateAvailableActions();
80         setPlayStatus(playStatus);
81     }
82 
getDevice()83     public BluetoothDevice getDevice() {
84         return mDevice;
85     }
86 
getId()87     public int getId() {
88         return mId;
89     }
90 
getName()91     public String getName() {
92         return mName;
93     }
94 
setPlayTime(int playTime)95     public void setPlayTime(int playTime) {
96         mPlayTime = playTime;
97         mPlaybackStateCompat =
98                 new PlaybackStateCompat.Builder(mPlaybackStateCompat)
99                         .setState(mPlayStatus, mPlayTime, mPlaySpeed)
100                         .build();
101     }
102 
getPlayTime()103     public long getPlayTime() {
104         return mPlayTime;
105     }
106 
setPlayStatus(int playStatus)107     public void setPlayStatus(int playStatus) {
108         if (mPlayTime != PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN) {
109             mPlayTime +=
110                     mPlaySpeed
111                             * (SystemClock.elapsedRealtime()
112                                     - mPlaybackStateCompat.getLastPositionUpdateTime());
113         }
114         mPlayStatus = playStatus;
115         switch (mPlayStatus) {
116             case PlaybackStateCompat.STATE_STOPPED:
117                 mPlaySpeed = 0;
118                 break;
119             case PlaybackStateCompat.STATE_PLAYING:
120                 mPlaySpeed = 1;
121                 break;
122             case PlaybackStateCompat.STATE_PAUSED:
123                 mPlaySpeed = 0;
124                 break;
125             case PlaybackStateCompat.STATE_FAST_FORWARDING:
126                 mPlaySpeed = 3;
127                 break;
128             case PlaybackStateCompat.STATE_REWINDING:
129                 mPlaySpeed = -3;
130                 break;
131         }
132 
133         mPlaybackStateCompat =
134                 new PlaybackStateCompat.Builder(mPlaybackStateCompat)
135                         .setState(mPlayStatus, mPlayTime, mPlaySpeed)
136                         .build();
137     }
138 
setSupportedPlayerApplicationSettings( PlayerApplicationSettings playerApplicationSettings)139     public void setSupportedPlayerApplicationSettings(
140             PlayerApplicationSettings playerApplicationSettings) {
141         mSupportedPlayerApplicationSettings = playerApplicationSettings;
142         updateAvailableActions();
143     }
144 
setCurrentPlayerApplicationSettings( PlayerApplicationSettings playerApplicationSettings)145     public void setCurrentPlayerApplicationSettings(
146             PlayerApplicationSettings playerApplicationSettings) {
147         Log.d(TAG, "Play application settings changed, settings=" + playerApplicationSettings);
148         mCurrentPlayerApplicationSettings = playerApplicationSettings;
149         MediaSessionCompat session = BluetoothMediaBrowserService.getSession();
150         session.setRepeatMode(
151                 mCurrentPlayerApplicationSettings.getSetting(
152                         PlayerApplicationSettings.REPEAT_STATUS));
153         session.setShuffleMode(
154                 mCurrentPlayerApplicationSettings.getSetting(
155                         PlayerApplicationSettings.SHUFFLE_STATUS));
156     }
157 
getPlayStatus()158     public int getPlayStatus() {
159         return mPlayStatus;
160     }
161 
supportsFeature(int featureId)162     public boolean supportsFeature(int featureId) {
163         int byteNumber = featureId / 8;
164         byte bitMask = (byte) (1 << (featureId % 8));
165         return (mPlayerFeatures[byteNumber] & bitMask) == bitMask;
166     }
167 
supportsSetting(int settingType, int settingValue)168     public boolean supportsSetting(int settingType, int settingValue) {
169         return mSupportedPlayerApplicationSettings.supportsSetting(settingType, settingValue);
170     }
171 
getPlaybackState()172     public PlaybackStateCompat getPlaybackState() {
173         Log.d(TAG, "getPlayBackState state " + mPlayStatus + " time " + mPlayTime);
174         return mPlaybackStateCompat;
175     }
176 
updateCurrentTrack(AvrcpItem update)177     public synchronized void updateCurrentTrack(AvrcpItem update) {
178         if (update != null) {
179             long trackNumber = update.getTrackNumber();
180             mPlaybackStateCompat =
181                     new PlaybackStateCompat.Builder(mPlaybackStateCompat)
182                             .setActiveQueueItemId(trackNumber - 1)
183                             .build();
184         }
185         mCurrentTrack = update;
186     }
187 
notifyImageDownload(String uuid, Uri imageUri)188     public synchronized boolean notifyImageDownload(String uuid, Uri imageUri) {
189         Log.d(TAG, "Got an image download -- uuid=" + uuid + ", uri=" + imageUri);
190         if (uuid == null || imageUri == null || mCurrentTrack == null) return false;
191         if (uuid.equals(mCurrentTrack.getCoverArtUuid())) {
192             mCurrentTrack.setCoverArtLocation(imageUri);
193             Log.d(TAG, "Image UUID '" + uuid + "' was added to current track.");
194             return true;
195         }
196         return false;
197     }
198 
getCurrentTrack()199     public synchronized AvrcpItem getCurrentTrack() {
200         return mCurrentTrack;
201     }
202 
updateAvailableActions()203     private void updateAvailableActions() {
204         mAvailableActions = PlaybackStateCompat.ACTION_PREPARE;
205         if (supportsFeature(FEATURE_PLAY)) {
206             mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_PLAY;
207         }
208         if (supportsFeature(FEATURE_STOP)) {
209             mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_STOP;
210         }
211         if (supportsFeature(FEATURE_PAUSE)) {
212             mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_PAUSE;
213         }
214         if (supportsFeature(FEATURE_REWIND)) {
215             mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_REWIND;
216         }
217         if (supportsFeature(FEATURE_FAST_FORWARD)) {
218             mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_FAST_FORWARD;
219         }
220         if (supportsFeature(FEATURE_FORWARD)) {
221             mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
222         }
223         if (supportsFeature(FEATURE_PREVIOUS)) {
224             mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
225         }
226         if (mSupportedPlayerApplicationSettings.supportsSetting(
227                 PlayerApplicationSettings.REPEAT_STATUS)) {
228             mAvailableActions |= PlaybackStateCompat.ACTION_SET_REPEAT_MODE;
229         }
230         if (mSupportedPlayerApplicationSettings.supportsSetting(
231                 PlayerApplicationSettings.SHUFFLE_STATUS)) {
232             mAvailableActions |= PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE;
233         }
234         mPlaybackStateCompat =
235                 new PlaybackStateCompat.Builder(mPlaybackStateCompat)
236                         .setActions(mAvailableActions)
237                         .build();
238 
239         Log.d(TAG, "Supported Actions = " + mAvailableActions);
240     }
241 
242     @Override
toString()243     public String toString() {
244         return "<AvrcpPlayer id="
245                 + mId
246                 + " name="
247                 + mName
248                 + " track="
249                 + mCurrentTrack
250                 + " playState="
251                 + AvrcpControllerUtils.playbackStateCompatToString(mPlaybackStateCompat)
252                 + ">";
253     }
254 
255     /** A Builder object for an AvrcpPlayer */
256     public static class Builder {
257         private BluetoothDevice mDevice = null;
258         private int mPlayerId = AvrcpPlayer.DEFAULT_ID;
259         private String mPlayerName = null;
260         private byte[] mSupportedFeatures = new byte[16];
261 
262         private int mPlayStatus = PlaybackStateCompat.STATE_NONE;
263 
264         private AvrcpItem mTrack = null;
265 
266         /**
267          * Set the device that this Player came from
268          *
269          * @param device The BleutoothDevice representing the remote device
270          * @return This object, so you can continue building
271          */
setDevice(BluetoothDevice device)272         public Builder setDevice(BluetoothDevice device) {
273             mDevice = device;
274             return this;
275         }
276 
277         /**
278          * Set the Player ID for this Player
279          *
280          * @param playerId The ID for this player, defined in AVRCP 6.10.2.1
281          * @return This object, so you can continue building
282          */
setPlayerId(int playerId)283         public Builder setPlayerId(int playerId) {
284             mPlayerId = playerId;
285             return this;
286         }
287 
288         /**
289          * Set the name for this Player. This is what users will see when browsing.
290          *
291          * @param name The name for this player, defined in AVRCP 6.10.2.1
292          * @return This object, so you can continue building
293          */
setName(String name)294         public Builder setName(String name) {
295             mPlayerName = name;
296             return this;
297         }
298 
299         /**
300          * Set the entire set of supported features for this Player.
301          *
302          * @param supportedFeatures The feature set for this player, defined in AVRCP 6.10.2.1
303          * @return This object, so you can continue building
304          */
setSupportedFeatures(byte[] supportedFeatures)305         public Builder setSupportedFeatures(byte[] supportedFeatures) {
306             mSupportedFeatures = supportedFeatures;
307             return this;
308         }
309 
310         /**
311          * Set a single features as supported for this Player.
312          *
313          * @param feature The feature for this player, defined in AVRCP 6.10.2.1
314          * @return This object, so you can continue building
315          */
setSupportedFeature(int feature)316         public Builder setSupportedFeature(int feature) {
317             int byteNumber = feature / 8;
318             byte bitMask = (byte) (1 << (feature % 8));
319             mSupportedFeatures[byteNumber] = (byte) (mSupportedFeatures[byteNumber] | bitMask);
320             return this;
321         }
322 
323         /**
324          * Set the initial play status of the Player.
325          *
326          * @param playStatus The play state for this player as a PlaybackStateCompat.STATE_* value
327          * @return This object, so you can continue building
328          */
setPlayStatus(int playStatus)329         public Builder setPlayStatus(int playStatus) {
330             mPlayStatus = playStatus;
331             return this;
332         }
333 
334         /**
335          * Set the initial play status of the Player.
336          *
337          * @param track The initial track for this player
338          * @return This object, so you can continue building
339          */
setCurrentTrack(AvrcpItem track)340         public Builder setCurrentTrack(AvrcpItem track) {
341             mTrack = track;
342             return this;
343         }
344 
build()345         public AvrcpPlayer build() {
346             AvrcpPlayer player =
347                     new AvrcpPlayer(
348                             mDevice, mPlayerId, mPlayerName, mSupportedFeatures, mPlayStatus);
349             player.updateCurrentTrack(mTrack);
350             return player;
351         }
352     }
353 }
354