blob: 1652388e59cefae55491635ee4154730597cab44 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth.avrcp;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.audio_util.Image;
import com.android.bluetooth.avrcpcontroller.BipEncoding;
import com.android.bluetooth.avrcpcontroller.BipImageDescriptor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import javax.obex.HeaderSet;
import javax.obex.Operation;
import javax.obex.ResponseCodes;
@RunWith(AndroidJUnit4.class)
public class AvrcpBipObexServerTest {
private static final String TYPE_GET_LINKED_THUMBNAIL = "x-bt/img-thm";
private static final String TYPE_GET_IMAGE_PROPERTIES = "x-bt/img-properties";
private static final String TYPE_GET_IMAGE = "x-bt/img-img";
private static final String TYPE_BAD = "x-bt/bad-type";
private static final byte HEADER_ID_IMG_HANDLE = 0x30;
private static final byte HEADER_ID_IMG_DESCRIPTOR = 0x71;
private static final byte[] BLUETOOTH_UUID_AVRCP_COVER_ART = new byte[] {
(byte) 0x71,
(byte) 0x63,
(byte) 0xDD,
(byte) 0x54,
(byte) 0x4A,
(byte) 0x7E,
(byte) 0x11,
(byte) 0xE2,
(byte) 0xB4,
(byte) 0x7C,
(byte) 0x00,
(byte) 0x50,
(byte) 0xC2,
(byte) 0x49,
(byte) 0x00,
(byte) 0x48
};
private static final byte[] NOT_BLUETOOTH_UUID_AVRCP_COVER_ART = new byte[] {
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00
};
private static final String IMAGE_HANDLE_1 = "0000001";
private static final String IMAGE_HANDLE_UNSTORED = "0000256";
private static final String IMAGE_HANDLE_INVALID = "abc1234"; // no non-numeric characters
private Context mTargetContext;
private Resources mTestResources;
private CoverArt mCoverArt;
private AvrcpCoverArtService mAvrcpCoverArtService = null;
private AvrcpBipObexServer.Callback mCallback = null;
private HeaderSet mRequest = null;
private HeaderSet mReply = null;
private ByteArrayOutputStream mOutputStream = null;
private AvrcpBipObexServer mAvrcpBipObexServer;
@Before
public void setUp() throws Exception {
mTargetContext = InstrumentationRegistry.getTargetContext();
try {
mTestResources = mTargetContext.getPackageManager()
.getResourcesForApplication("com.android.bluetooth.tests");
} catch (PackageManager.NameNotFoundException e) {
assertWithMessage("Setup Failure Unable to get resources" + e.toString()).fail();
}
mCoverArt = loadCoverArt(com.android.bluetooth.tests.R.raw.image_200_200);
mAvrcpCoverArtService = mock(AvrcpCoverArtService.class);
mCallback = mock(AvrcpBipObexServer.Callback.class);
mRequest = new HeaderSet();
mReply = new HeaderSet();
mOutputStream = new ByteArrayOutputStream();
mAvrcpBipObexServer = new AvrcpBipObexServer(mAvrcpCoverArtService, mCallback);
}
@After
public void tearDown() throws Exception {
mAvrcpBipObexServer = null;
mOutputStream = null;
mReply = null;
mRequest = null;
mCallback = null;
mAvrcpCoverArtService = null;
mCoverArt = null;
mTargetContext = null;
mTestResources = null;
}
private CoverArt loadCoverArt(int resId) {
InputStream imageInputStream = mTestResources.openRawResource(resId);
Bitmap bitmap = BitmapFactory.decodeStream(imageInputStream);
Image image = new Image(null, bitmap);
return new CoverArt(image);
}
private void setCoverArtAvailableAtHandle(String handle, CoverArt art) {
art.setImageHandle(handle);
when(mAvrcpCoverArtService.getImage(handle)).thenReturn(art);
}
/**
* Creates a mocked operation that can be used by our server as a client request
*
* Our server will use:
* - getReceivedHeader
* - sendHeaders
* - getMaxPacketSize
* - openOutputStream
*/
private Operation makeOperation(HeaderSet requestHeaders, OutputStream os) throws Exception {
Operation op = mock(Operation.class);
when(op.getReceivedHeader()).thenReturn(requestHeaders);
when(op.getMaxPacketSize()).thenReturn(256);
when(op.openOutputStream()).thenReturn(os);
return op;
}
private byte[] makeDescriptor(int encoding, int width, int height) {
return new BipImageDescriptor.Builder()
.setEncoding(encoding)
.setFixedDimensions(width, height)
.build().serialize();
}
/**
* Make sure we let a connection through with a valid UUID
*/
@Test
public void testConnectWithValidUuidHeader() throws Exception {
mRequest.setHeader(HeaderSet.TARGET, BLUETOOTH_UUID_AVRCP_COVER_ART);
int responseCode = mAvrcpBipObexServer.onConnect(mRequest, mReply);
verify(mCallback, times(1)).onConnected();
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
}
/**
* Make sure we deny a connection when there is an invalid UUID
*/
@Test
public void testConnectWithInvalidUuidHeader() throws Exception {
mRequest.setHeader(HeaderSet.TARGET, NOT_BLUETOOTH_UUID_AVRCP_COVER_ART);
int responseCode = mAvrcpBipObexServer.onConnect(mRequest, mReply);
verify(mCallback, never()).onConnected();
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE);
}
/**
* Make sure onDisconnect notifies the callbacks in the proper way
*/
@Test
public void testDisonnect() {
mAvrcpBipObexServer.onDisconnect(mRequest, mReply);
verify(mCallback, times(1)).onDisconnected();
}
/**
* Make sure onClose notifies the callbacks in the proper way
*/
@Test
public void testOnClose() {
mAvrcpBipObexServer.onClose();
verify(mCallback, times(1)).onClose();
}
/**
* Make sure onGet handles null headers gracefully
*/
@Test
public void testOnGetNoHeaders() throws Exception {
Operation op = makeOperation(null, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
}
/**
* Make sure onGet handles bad type gracefully
*/
@Test
public void testOnGetBadType() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_BAD);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
}
/**
* Make sure onGet handles no type gracefully
*/
@Test
public void testOnGetNoType() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, null);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
}
/**
* Make sure a getImageThumbnail request with a valid handle works
*/
@Test
public void testGetLinkedThumbnailWithValidHandle() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_LINKED_THUMBNAIL);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
}
/**
* Make sure a getImageThumbnail request with a unstored handle returns OBEX_HTTP_NOT_FOUND
*/
@Test
public void testGetLinkedThumbnailWithValidUnstoredHandle() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_LINKED_THUMBNAIL);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_UNSTORED);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_FOUND);
}
/**
* Make sure a getImageThumbnail request with an invalidly formatted handle returns
* OBEX_HTTP_BAD_REQUEST
*/
@Test
public void testGetLinkedThumbnailWithInvalidHandle() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_LINKED_THUMBNAIL);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_INVALID);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_PRECON_FAILED);
}
/**
* Make sure a getImageThumbnail request with an invalidly formatted handle returns
* OBEX_HTTP_BAD_REQUEST
*/
@Test
public void testGetLinkedThumbnailWithNullHandle() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_LINKED_THUMBNAIL);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, null);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
}
/**
* Make sure a getImageProperties request with a valid handle returns a valie properties object
*/
@Test
public void testGetImagePropertiesWithValidHandle() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE_PROPERTIES);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
}
/**
* Make sure a getImageProperties request with a unstored handle returns OBEX_HTTP_NOT_FOUND
*/
@Test
public void testGetImagePropertiesWithValidUnstoredHandle() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE_PROPERTIES);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_UNSTORED);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_FOUND);
}
/**
* Make sure a getImageProperties request with an invalidly formatted handle returns
* OBEX_HTTP_BAD_REQUEST
*/
@Test
public void testGetImagePropertiesWithInvalidHandle() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE_PROPERTIES);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_INVALID);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_PRECON_FAILED);
}
/**
* Make sure a getImageProperties request with an invalidly formatted handle returns
* OBEX_HTTP_BAD_REQUEST
*/
@Test
public void testGetImagePropertiesWithNullHandle() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE_PROPERTIES);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, null);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
}
/**
* Make sure a GetImage request with a null descriptor returns a native image
*/
@Test
public void testGetImageWithValidHandleAndNullDescriptor() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, null);
setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
}
/**
* Make sure a GetImage request with a valid descriptor returns an image
*/
@Test
public void testGetImageWithValidHandleAndValidDescriptor() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, makeDescriptor(BipEncoding.JPEG, 200, 200));
setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
}
/**
* Make sure a GetImage request with a valid, but unsupported descriptor, returns NOT_ACCEPTABLE
*/
@Test
public void testGetImageWithValidHandleAndInvalidDescriptor() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR,
makeDescriptor(BipEncoding.WBMP /* No Android support, won't work */, 200, 200));
setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE);
}
/**
* Make sure a GetImage request with a unstored handle returns OBEX_HTTP_NOT_FOUND
*/
@Test
public void testGetImageWithValidUnstoredHandle() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_UNSTORED);
mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, makeDescriptor(BipEncoding.JPEG, 200, 200));
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_FOUND);
}
/**
* Make sure a getImage request with an invalidly formatted handle returns OBEX_HTTP_BAD_REQUEST
*/
@Test
public void testGetImageWithInvalidHandle() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_INVALID);
mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, makeDescriptor(BipEncoding.JPEG, 200, 200));
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_PRECON_FAILED);
}
/**
* Make sure a getImage request with a null handle returns OBEX_HTTP_BAD_REQUEST
*/
@Test
public void testGetImageWithNullHandle() throws Exception {
mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
mRequest.setHeader(HEADER_ID_IMG_HANDLE, null);
mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, makeDescriptor(BipEncoding.JPEG, 200, 200));
Operation op = makeOperation(mRequest, mOutputStream);
int responseCode = mAvrcpBipObexServer.onGet(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
}
/**
* Make sure onPut is not a supported action
*/
@Test
public void testOnPut() {
Operation op = null;
int responseCode = mAvrcpBipObexServer.onPut(op);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED);
}
/**
* Make sure onAbort is not a supported action
*/
@Test
public void testOnAbort() {
HeaderSet request = null;
HeaderSet reply = null;
int responseCode = mAvrcpBipObexServer.onAbort(request, reply);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED);
}
/**
* Make sure onSetPath is not a supported action
*/
@Test
public void testOnSetPath() {
HeaderSet request = null;
HeaderSet reply = null;
boolean backup = false;
boolean create = false;
int responseCode = mAvrcpBipObexServer.onSetPath(request, reply, backup, create);
assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED);
}
}