• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 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 static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import static org.mockito.Mockito.mock;
23 import static org.mockito.Mockito.never;
24 import static org.mockito.Mockito.times;
25 import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.when;
27 
28 import android.content.Context;
29 import android.content.pm.PackageManager;
30 import android.content.res.Resources;
31 import android.graphics.Bitmap;
32 import android.graphics.BitmapFactory;
33 
34 import androidx.test.InstrumentationRegistry;
35 import androidx.test.runner.AndroidJUnit4;
36 
37 import com.android.bluetooth.TestUtils;
38 import com.android.bluetooth.audio_util.Image;
39 import com.android.bluetooth.avrcpcontroller.BipEncoding;
40 import com.android.bluetooth.avrcpcontroller.BipImageDescriptor;
41 
42 import org.junit.After;
43 import org.junit.Before;
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 
47 import java.io.ByteArrayOutputStream;
48 import java.io.InputStream;
49 import java.io.OutputStream;
50 
51 import com.android.obex.HeaderSet;
52 import com.android.obex.Operation;
53 import com.android.obex.ResponseCodes;
54 
55 @RunWith(AndroidJUnit4.class)
56 public class AvrcpBipObexServerTest {
57     private static final String TYPE_GET_LINKED_THUMBNAIL = "x-bt/img-thm";
58     private static final String TYPE_GET_IMAGE_PROPERTIES = "x-bt/img-properties";
59     private static final String TYPE_GET_IMAGE = "x-bt/img-img";
60     private static final String TYPE_BAD = "x-bt/bad-type";
61 
62     private static final byte HEADER_ID_IMG_HANDLE = 0x30;
63     private static final byte HEADER_ID_IMG_DESCRIPTOR = 0x71;
64 
65     private static final byte[] BLUETOOTH_UUID_AVRCP_COVER_ART = new byte[] {
66         (byte) 0x71,
67         (byte) 0x63,
68         (byte) 0xDD,
69         (byte) 0x54,
70         (byte) 0x4A,
71         (byte) 0x7E,
72         (byte) 0x11,
73         (byte) 0xE2,
74         (byte) 0xB4,
75         (byte) 0x7C,
76         (byte) 0x00,
77         (byte) 0x50,
78         (byte) 0xC2,
79         (byte) 0x49,
80         (byte) 0x00,
81         (byte) 0x48
82     };
83 
84     private static final byte[] NOT_BLUETOOTH_UUID_AVRCP_COVER_ART = new byte[] {
85         (byte) 0x00,
86         (byte) 0x00,
87         (byte) 0x00,
88         (byte) 0x00,
89         (byte) 0x00,
90         (byte) 0x00,
91         (byte) 0x00,
92         (byte) 0x00,
93         (byte) 0x00,
94         (byte) 0x00,
95         (byte) 0x00,
96         (byte) 0x00,
97         (byte) 0x00,
98         (byte) 0x00,
99         (byte) 0x00,
100         (byte) 0x00
101     };
102 
103     private static final String IMAGE_HANDLE_1 = "0000001";
104     private static final String IMAGE_HANDLE_UNSTORED = "0000256";
105     private static final String IMAGE_HANDLE_INVALID = "abc1234"; // no non-numeric characters
106 
107     private Resources mTestResources;
108     private CoverArt mCoverArt;
109 
110     private AvrcpCoverArtService mAvrcpCoverArtService = null;
111     private AvrcpBipObexServer.Callback mCallback = null;
112 
113     private HeaderSet mRequest = null;
114     private HeaderSet mReply = null;
115     private ByteArrayOutputStream mOutputStream = null;
116 
117     private AvrcpBipObexServer mAvrcpBipObexServer;
118 
119     @Before
setUp()120     public void setUp() throws Exception {
121         mTestResources = TestUtils.getTestApplicationResources(
122                 InstrumentationRegistry.getTargetContext());
123 
124         mCoverArt = loadCoverArt(com.android.bluetooth.tests.R.raw.image_200_200);
125 
126         mAvrcpCoverArtService = mock(AvrcpCoverArtService.class);
127         mCallback = mock(AvrcpBipObexServer.Callback.class);
128 
129         mRequest = new HeaderSet();
130         mReply = new HeaderSet();
131         mOutputStream = new ByteArrayOutputStream();
132 
133         mAvrcpBipObexServer = new AvrcpBipObexServer(mAvrcpCoverArtService, mCallback);
134     }
135 
136     @After
tearDown()137     public void tearDown() throws Exception {
138         mAvrcpBipObexServer = null;
139         mOutputStream = null;
140         mReply = null;
141         mRequest = null;
142         mCallback = null;
143         mAvrcpCoverArtService = null;
144         mCoverArt = null;
145         mTestResources = null;
146     }
147 
loadCoverArt(int resId)148     private CoverArt loadCoverArt(int resId) {
149         InputStream imageInputStream = mTestResources.openRawResource(resId);
150         Bitmap bitmap = BitmapFactory.decodeStream(imageInputStream);
151         Image image = new Image(null, bitmap);
152         return new CoverArt(image);
153     }
154 
setCoverArtAvailableAtHandle(String handle, CoverArt art)155     private void setCoverArtAvailableAtHandle(String handle, CoverArt art) {
156         art.setImageHandle(handle);
157         when(mAvrcpCoverArtService.getImage(handle)).thenReturn(art);
158     }
159 
160     /**
161      * Creates a mocked operation that can be used by our server as a client request
162      *
163      * Our server will use:
164      *  - getReceivedHeader
165      *  - sendHeaders
166      *  - getMaxPacketSize
167      *  - openOutputStream
168      */
makeOperation(HeaderSet requestHeaders, OutputStream os)169     private Operation makeOperation(HeaderSet requestHeaders, OutputStream os) throws Exception {
170         Operation op = mock(Operation.class);
171         when(op.getReceivedHeader()).thenReturn(requestHeaders);
172         when(op.getMaxPacketSize()).thenReturn(256);
173         when(op.openOutputStream()).thenReturn(os);
174         return op;
175     }
176 
makeDescriptor(int encoding, int width, int height)177     private byte[] makeDescriptor(int encoding, int width, int height) {
178         return new BipImageDescriptor.Builder()
179                 .setEncoding(encoding)
180                 .setFixedDimensions(width, height)
181                 .build().serialize();
182     }
183 
184     /**
185      * Make sure we let a connection through with a valid UUID
186      */
187     @Test
testConnectWithValidUuidHeader()188     public void testConnectWithValidUuidHeader() throws Exception {
189         mRequest.setHeader(HeaderSet.TARGET, BLUETOOTH_UUID_AVRCP_COVER_ART);
190         int responseCode = mAvrcpBipObexServer.onConnect(mRequest, mReply);
191         verify(mCallback, times(1)).onConnected();
192         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
193     }
194 
195     /**
196      * Make sure we deny a connection when there is an invalid UUID
197      */
198     @Test
testConnectWithInvalidUuidHeader()199     public void testConnectWithInvalidUuidHeader() throws Exception {
200         mRequest.setHeader(HeaderSet.TARGET, NOT_BLUETOOTH_UUID_AVRCP_COVER_ART);
201         int responseCode = mAvrcpBipObexServer.onConnect(mRequest, mReply);
202         verify(mCallback, never()).onConnected();
203         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE);
204     }
205 
206     /**
207      * Make sure onDisconnect notifies the callbacks in the proper way
208      */
209     @Test
testDisonnect()210     public void testDisonnect() {
211         mAvrcpBipObexServer.onDisconnect(mRequest, mReply);
212         verify(mCallback, times(1)).onDisconnected();
213     }
214 
215     /**
216      * Make sure onClose notifies the callbacks in the proper way
217      */
218     @Test
testOnClose()219     public void testOnClose() {
220         mAvrcpBipObexServer.onClose();
221         verify(mCallback, times(1)).onClose();
222     }
223 
224     /**
225      * Make sure onGet handles null headers gracefully
226      */
227     @Test
testOnGetNoHeaders()228     public void testOnGetNoHeaders() throws Exception {
229         Operation op = makeOperation(null, mOutputStream);
230         int responseCode = mAvrcpBipObexServer.onGet(op);
231         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
232     }
233 
234     /**
235      * Make sure onGet handles bad type gracefully
236      */
237     @Test
testOnGetBadType()238     public void testOnGetBadType() throws Exception {
239         mRequest.setHeader(HeaderSet.TYPE, TYPE_BAD);
240         Operation op = makeOperation(mRequest, mOutputStream);
241         int responseCode = mAvrcpBipObexServer.onGet(op);
242         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
243     }
244 
245     /**
246      * Make sure onGet handles no type gracefully
247      */
248     @Test
testOnGetNoType()249     public void testOnGetNoType() throws Exception {
250         mRequest.setHeader(HeaderSet.TYPE, null);
251         Operation op = makeOperation(mRequest, mOutputStream);
252         int responseCode = mAvrcpBipObexServer.onGet(op);
253         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
254     }
255 
256     /**
257      * Make sure a getImageThumbnail request with a valid handle works
258      */
259     @Test
testGetLinkedThumbnailWithValidHandle()260     public void testGetLinkedThumbnailWithValidHandle() throws Exception {
261         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_LINKED_THUMBNAIL);
262         mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
263         setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
264         Operation op = makeOperation(mRequest, mOutputStream);
265         int responseCode = mAvrcpBipObexServer.onGet(op);
266         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
267     }
268 
269     /**
270      * Make sure a getImageThumbnail request with a unstored handle returns OBEX_HTTP_NOT_FOUND
271      */
272     @Test
testGetLinkedThumbnailWithValidUnstoredHandle()273     public void testGetLinkedThumbnailWithValidUnstoredHandle() throws Exception {
274         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_LINKED_THUMBNAIL);
275         mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_UNSTORED);
276         Operation op = makeOperation(mRequest, mOutputStream);
277         int responseCode = mAvrcpBipObexServer.onGet(op);
278         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_FOUND);
279     }
280 
281     /**
282      * Make sure a getImageThumbnail request with an invalidly formatted handle returns
283      * OBEX_HTTP_BAD_REQUEST
284      */
285     @Test
testGetLinkedThumbnailWithInvalidHandle()286     public void testGetLinkedThumbnailWithInvalidHandle() throws Exception {
287         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_LINKED_THUMBNAIL);
288         mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_INVALID);
289         Operation op = makeOperation(mRequest, mOutputStream);
290         int responseCode = mAvrcpBipObexServer.onGet(op);
291         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_PRECON_FAILED);
292     }
293 
294     /**
295      * Make sure a getImageThumbnail request with an invalidly formatted handle returns
296      * OBEX_HTTP_BAD_REQUEST
297      */
298     @Test
testGetLinkedThumbnailWithNullHandle()299     public void testGetLinkedThumbnailWithNullHandle() throws Exception {
300         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_LINKED_THUMBNAIL);
301         mRequest.setHeader(HEADER_ID_IMG_HANDLE, null);
302         Operation op = makeOperation(mRequest, mOutputStream);
303         int responseCode = mAvrcpBipObexServer.onGet(op);
304         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
305     }
306 
307     /**
308      * Make sure a getImageProperties request with a valid handle returns a valie properties object
309      */
310     @Test
testGetImagePropertiesWithValidHandle()311     public void testGetImagePropertiesWithValidHandle() throws Exception {
312         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE_PROPERTIES);
313         mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
314         setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
315         Operation op = makeOperation(mRequest, mOutputStream);
316         int responseCode = mAvrcpBipObexServer.onGet(op);
317         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
318     }
319 
320     /**
321      * Make sure a getImageProperties request with a unstored handle returns OBEX_HTTP_NOT_FOUND
322      */
323     @Test
testGetImagePropertiesWithValidUnstoredHandle()324     public void testGetImagePropertiesWithValidUnstoredHandle() throws Exception {
325         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE_PROPERTIES);
326         mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_UNSTORED);
327         Operation op = makeOperation(mRequest, mOutputStream);
328         int responseCode = mAvrcpBipObexServer.onGet(op);
329         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_FOUND);
330     }
331 
332     /**
333      * Make sure a getImageProperties request with an invalidly formatted handle returns
334      * OBEX_HTTP_BAD_REQUEST
335      */
336     @Test
testGetImagePropertiesWithInvalidHandle()337     public void testGetImagePropertiesWithInvalidHandle() throws Exception {
338         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE_PROPERTIES);
339         mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_INVALID);
340         Operation op = makeOperation(mRequest, mOutputStream);
341         int responseCode = mAvrcpBipObexServer.onGet(op);
342         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_PRECON_FAILED);
343     }
344 
345     /**
346      * Make sure a getImageProperties request with an invalidly formatted handle returns
347      * OBEX_HTTP_BAD_REQUEST
348      */
349     @Test
testGetImagePropertiesWithNullHandle()350     public void testGetImagePropertiesWithNullHandle() throws Exception {
351         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE_PROPERTIES);
352         mRequest.setHeader(HEADER_ID_IMG_HANDLE, null);
353         Operation op = makeOperation(mRequest, mOutputStream);
354         int responseCode = mAvrcpBipObexServer.onGet(op);
355         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
356     }
357 
358     /**
359      * Make sure a GetImage request with a null descriptor returns a native image
360      */
361     @Test
testGetImageWithValidHandleAndNullDescriptor()362     public void testGetImageWithValidHandleAndNullDescriptor() throws Exception {
363         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
364         mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
365         mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, null);
366         setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
367         Operation op = makeOperation(mRequest, mOutputStream);
368         int responseCode = mAvrcpBipObexServer.onGet(op);
369         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
370     }
371 
372     /**
373      * Make sure a GetImage request with a valid descriptor returns an image
374      */
375     @Test
testGetImageWithValidHandleAndValidDescriptor()376     public void testGetImageWithValidHandleAndValidDescriptor() throws Exception {
377         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
378         mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
379         mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, makeDescriptor(BipEncoding.JPEG, 200, 200));
380         setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
381         Operation op = makeOperation(mRequest, mOutputStream);
382         int responseCode = mAvrcpBipObexServer.onGet(op);
383         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
384     }
385 
386     /**
387      * Make sure a GetImage request with a valid, but unsupported descriptor, returns NOT_ACCEPTABLE
388      */
389     @Test
testGetImageWithValidHandleAndInvalidDescriptor()390     public void testGetImageWithValidHandleAndInvalidDescriptor() throws Exception {
391         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
392         mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
393         mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR,
394                 makeDescriptor(BipEncoding.WBMP /* No Android support, won't work */, 200, 200));
395         setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
396         Operation op = makeOperation(mRequest, mOutputStream);
397         int responseCode = mAvrcpBipObexServer.onGet(op);
398         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE);
399     }
400 
401     /**
402      * Make sure a GetImage request with a unstored handle returns OBEX_HTTP_NOT_FOUND
403      */
404     @Test
testGetImageWithValidUnstoredHandle()405     public void testGetImageWithValidUnstoredHandle() throws Exception {
406         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
407         mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_UNSTORED);
408         mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, makeDescriptor(BipEncoding.JPEG, 200, 200));
409         Operation op = makeOperation(mRequest, mOutputStream);
410         int responseCode = mAvrcpBipObexServer.onGet(op);
411         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_FOUND);
412     }
413 
414     /**
415      * Make sure a getImage request with an invalidly formatted handle returns OBEX_HTTP_BAD_REQUEST
416      */
417     @Test
testGetImageWithInvalidHandle()418     public void testGetImageWithInvalidHandle() throws Exception {
419         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
420         mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_INVALID);
421         mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, makeDescriptor(BipEncoding.JPEG, 200, 200));
422         Operation op = makeOperation(mRequest, mOutputStream);
423         int responseCode = mAvrcpBipObexServer.onGet(op);
424         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_PRECON_FAILED);
425     }
426 
427     /**
428      * Make sure a getImage request with a null handle returns OBEX_HTTP_BAD_REQUEST
429      */
430     @Test
testGetImageWithNullHandle()431     public void testGetImageWithNullHandle() throws Exception {
432         mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
433         mRequest.setHeader(HEADER_ID_IMG_HANDLE, null);
434         mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, makeDescriptor(BipEncoding.JPEG, 200, 200));
435         Operation op = makeOperation(mRequest, mOutputStream);
436         int responseCode = mAvrcpBipObexServer.onGet(op);
437         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
438     }
439 
440     /**
441      * Make sure onPut is not a supported action
442      */
443     @Test
testOnPut()444     public void testOnPut() {
445         Operation op = null;
446         int responseCode = mAvrcpBipObexServer.onPut(op);
447         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED);
448     }
449 
450     /**
451      * Make sure onAbort is not a supported action
452      */
453     @Test
testOnAbort()454     public void testOnAbort() {
455         HeaderSet request = null;
456         HeaderSet reply = null;
457         int responseCode = mAvrcpBipObexServer.onAbort(request, reply);
458         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED);
459     }
460 
461     /**
462      * Make sure onSetPath is not a supported action
463      */
464     @Test
testOnSetPath()465     public void testOnSetPath() {
466         HeaderSet request = null;
467         HeaderSet reply = null;
468         boolean backup = false;
469         boolean create = false;
470         int responseCode = mAvrcpBipObexServer.onSetPath(request, reply, backup, create);
471         assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED);
472     }
473 }
474