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