Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 123a1bbc authored by Hieu Dang's avatar Hieu Dang Committed by Automerger Merge Worker
Browse files

Merge "Add BluetoothOppObexServerSessionTest" am: 00a8d653

parents 4cd7c764 00a8d653
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import com.android.obex.HeaderSet;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;

/**
@@ -171,6 +172,14 @@ public class BluetoothMethodProxy {
        return contentResolver.acquireUnstableContentProviderClient(name);
    }

    /**
     * Proxies {@link ContentResolver#openOutputStream(Uri)}.
     */
    public OutputStream contentResolverOpenOutputStream(ContentResolver contentResolver, Uri uri)
            throws FileNotFoundException {
        return contentResolver.openOutputStream(uri);
    }

    /**
     * Proxies {@link Context#sendBroadcast(Intent)}.
     */
+38 −17
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.os.SystemClock;
import android.util.Log;
import android.webkit.MimeTypeMap;

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.Utils;
@@ -55,6 +56,8 @@ import com.android.obex.ResponseCodes;
import com.android.obex.ServerRequestHandler;
import com.android.obex.ServerSession;

import com.google.common.annotations.VisibleForTesting;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -71,36 +74,46 @@ public class BluetoothOppObexServerSession extends ServerRequestHandler
    private static final boolean D = Constants.DEBUG;
    private static final boolean V = Constants.VERBOSE;

    private ObexTransport mTransport;
    @VisibleForTesting
    public ObexTransport mTransport;

    private Context mContext;
    @VisibleForTesting
    public Context mContext;

    private Handler mCallback = null;
    @VisibleForTesting
    public Handler mCallback = null;

    /* status when server is blocking for user/auto confirmation */
    private boolean mServerBlocking = true;
    @VisibleForTesting
    public boolean mServerBlocking = true;

    /* the current transfer info */
    private BluetoothOppShareInfo mInfo;
    @VisibleForTesting
    public BluetoothOppShareInfo mInfo;

    /* info id when we insert the record */
    private int mLocalShareInfoId;

    private int mAccepted = BluetoothShare.USER_CONFIRMATION_PENDING;
    @VisibleForTesting
    public int mAccepted = BluetoothShare.USER_CONFIRMATION_PENDING;

    private boolean mInterrupted = false;

    private ServerSession mSession;
    @VisibleForTesting
    public ServerSession mSession;

    private long mTimestamp;

    private BluetoothOppReceiveFileInfo mFileInfo;
    @VisibleForTesting
    BluetoothOppReceiveFileInfo mFileInfo;

    private WakeLock mPartialWakeLock;

    @VisibleForTesting
    boolean mTimeoutMsgSent = false;

    private BluetoothOppService mBluetoothOppService;
    @VisibleForTesting
    public BluetoothOppService mBluetoothOppService;

    private int mNumFilesAttemptedToReceive;

@@ -288,7 +301,8 @@ public class BluetoothOppObexServerSession extends ServerRequestHandler
                    BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
        }

        Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
        Uri contentUri = BluetoothMethodProxy.getInstance().contentResolverInsert(
                mContext.getContentResolver(), BluetoothShare.CONTENT_URI, values);
        mLocalShareInfoId = Integer.parseInt(contentUri.getPathSegments().get(1));

        if (V) {
@@ -304,10 +318,10 @@ public class BluetoothOppObexServerSession extends ServerRequestHandler
                while (mServerBlocking) {
                    wait(1000);
                    if (mCallback != null && !mTimeoutMsgSent) {
                        mTimeoutMsgSent = true;
                        mCallback.sendMessageDelayed(mCallback.obtainMessage(
                                        BluetoothOppObexSession.MSG_CONNECT_TIMEOUT),
                                BluetoothOppObexSession.SESSION_TIMEOUT);
                        mTimeoutMsgSent = true;
                        if (V) {
                            Log.v(TAG, "MSG_CONNECT_TIMEOUT sent");
                        }
@@ -368,7 +382,8 @@ public class BluetoothOppObexServerSession extends ServerRequestHandler
                updateValues.put(BluetoothShare._DATA, mFileInfo.mFileName);
                updateValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_RUNNING);
                updateValues.put(BluetoothShare.URI, mFileInfo.mInsertUri.toString());
                mContext.getContentResolver().update(contentUri, updateValues, null, null);
                BluetoothMethodProxy.getInstance().contentResolverUpdate(
                        mContext.getContentResolver(), contentUri, updateValues, null, null);

                mInfo.mUri = mFileInfo.mInsertUri;
                status = receiveFile(mFileInfo, op);
@@ -406,7 +421,9 @@ public class BluetoothOppObexServerSession extends ServerRequestHandler

            Log.i(TAG, "Rejected incoming request");
            if (mFileInfo.mInsertUri != null) {
                mContext.getContentResolver().delete(mFileInfo.mInsertUri, null, null);
                BluetoothMethodProxy.getInstance().contentResolverDelete(
                        mContext.getContentResolver(), mFileInfo.mInsertUri, null,
                        null);
            }
            // set status as local cancel
            status = BluetoothShare.STATUS_CANCELED;
@@ -443,7 +460,8 @@ public class BluetoothOppObexServerSession extends ServerRequestHandler
        if (!error) {
            ContentValues updateValues = new ContentValues();
            updateValues.put(BluetoothShare._DATA, fileInfo.mFileName);
            mContext.getContentResolver().update(contentUri, updateValues, null, null);
            BluetoothMethodProxy.getInstance().contentResolverUpdate(mContext.getContentResolver(),
                    contentUri, updateValues, null, null);
        }

        long position = 0;
@@ -452,7 +470,8 @@ public class BluetoothOppObexServerSession extends ServerRequestHandler

        if (!error) {
            try {
                os = mContext.getContentResolver().openOutputStream(fileInfo.mInsertUri);
                os = BluetoothMethodProxy.getInstance().contentResolverOpenOutputStream(
                        mContext.getContentResolver(), fileInfo.mInsertUri);
            } catch (FileNotFoundException e) {
                Log.e(TAG, "Error when openOutputStream");
                error = true;
@@ -499,7 +518,9 @@ public class BluetoothOppObexServerSession extends ServerRequestHandler
                            || currentTime - prevTimestamp > Constants.NFC_ALIVE_CHECK_MS) {
                        ContentValues updateValues = new ContentValues();
                        updateValues.put(BluetoothShare.CURRENT_BYTES, position);
                        mContext.getContentResolver().update(contentUri, updateValues, null, null);
                        BluetoothMethodProxy.getInstance().contentResolverUpdate(
                                mContext.getContentResolver(), contentUri, updateValues, null,
                                null);
                        prevPercent = percent;
                        prevTimestamp = currentTime;
                    }
+324 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 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.opp;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;

import android.content.Context;
import android.content.ContextWrapper;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;

import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.obex.HeaderSet;
import com.android.obex.Operation;
import com.android.obex.ResponseCodes;

import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;


@RunWith(AndroidJUnit4.class)
public class BluetoothOppObexServerSessionTest {
    @Mock
    BluetoothMethodProxy mMethodProxy;

    Context mTargetContext;
    @Mock
    BluetoothObexTransport mTransport;

    @Mock
    BluetoothOppService mBluetoothOppService;
    @Mock
    Operation mOperation;

    BluetoothOppObexServerSession mServerSession;

    @Before
    public void setUp() throws IOException {
        MockitoAnnotations.initMocks(this);
        mTargetContext = spy(
                new ContextWrapper(
                        InstrumentationRegistry.getInstrumentation().getTargetContext()));
        mServerSession = new BluetoothOppObexServerSession(mTargetContext, mTransport,
                mBluetoothOppService);

        // to control the mServerSession.mSession
        InputStream input = mock(InputStream.class);
        OutputStream output = mock(OutputStream.class);
        doReturn(-1).when(input).read();
        doReturn(input).when(mTransport).openInputStream();
        doReturn(output).when(mTransport).openOutputStream();

        BluetoothMethodProxy.setInstanceForTesting(mMethodProxy);
    }

    @After
    public void tearDown() {
        BluetoothMethodProxy.setInstanceForTesting(null);
    }

    @Test
    public void constructor_createInstanceCorrectly() {
        mServerSession = new BluetoothOppObexServerSession(mTargetContext, mTransport,
                mBluetoothOppService);
        assertThat(mServerSession.mBluetoothOppService).isEqualTo(mBluetoothOppService);
        assertThat(mServerSession.mTransport).isEqualTo(mTransport);
        assertThat(mServerSession.mContext).isEqualTo(mTargetContext);
    }

    @Test
    public void unblock() {
        assertThat(mServerSession.mServerBlocking).isTrue();
        mServerSession.unblock();
        assertThat(mServerSession.mServerBlocking).isFalse();
    }

    @Test
    public void preStart_thenStart_thenStop_flowWorksCorrectly() {
        Handler handler = mock(Handler.class);
        assertThat(mServerSession.mSession).isNull();
        assertThat(mServerSession.mCallback).isNull();
        mServerSession.preStart();
        assertThat(mServerSession.mSession).isNotNull();
        assertThat(mServerSession.mCallback).isNull();
        mServerSession.start(handler, 0);
        assertThat(mServerSession.mSession).isNotNull();
        assertThat(mServerSession.mCallback).isEqualTo(handler);
        mServerSession.stop();
        assertThat(mServerSession.mSession).isNull();
        assertThat(mServerSession.mCallback).isNull();
    }

    @Test
    public void addShare_updatesShareInfo() {
        Uri uri = Uri.parse("file://Idontknow//Justmadeitup");
        String hintString = "this is a object that take 4 bytes";
        String filename = "random.jpg";
        String mimetype = "image/jpeg";
        int direction = BluetoothShare.DIRECTION_INBOUND;
        String destination = "01:23:45:67:89:AB";
        int visibility = BluetoothShare.VISIBILITY_VISIBLE;
        int confirm = BluetoothShare.USER_CONFIRMATION_CONFIRMED;
        int status = BluetoothShare.STATUS_PENDING;
        int totalBytes = 1023;
        int currentBytes = 42;
        int timestamp = 123456789;
        boolean mediaScanned = false;
        BluetoothOppShareInfo info = new BluetoothOppShareInfo(0, uri, hintString, filename,
                mimetype, direction, destination, visibility, confirm, status, totalBytes,
                currentBytes, timestamp, mediaScanned);

        mServerSession.addShare(info);
        assertThat(mServerSession.mInfo).isEqualTo(info);
    }

    @Test
    public void onPut_withUserConfirmationDenied_returnsObexHttpForbidden() {
        mServerSession.mAccepted = BluetoothShare.USER_CONFIRMATION_DENIED;
        assertThat(mServerSession.onPut(mOperation)).isEqualTo(ResponseCodes.OBEX_HTTP_FORBIDDEN);
    }

    @Test
    public void onPut_withClosedOperation_returnsObexHttpBadRequest() throws IOException {
        doThrow(new IOException()).when(mOperation).getReceivedHeader();
        assertThat(mServerSession.onPut(mOperation)).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
    }

    @Test
    public void onPut_withZeroLengthInHeader_returnsLengthRequired() throws IOException {
        String name = "";
        long length = 0;
        String mimeType = "text/plain";
        HeaderSet headerSet = new HeaderSet();
        doReturn(headerSet).when(mOperation).getReceivedHeader();
        headerSet.setHeader(HeaderSet.NAME, name);
        headerSet.setHeader(HeaderSet.LENGTH, length);
        headerSet.setHeader(HeaderSet.TYPE, mimeType);
        assertThat(mServerSession.onPut(mOperation)).isEqualTo(
                ResponseCodes.OBEX_HTTP_LENGTH_REQUIRED);
    }

    @Test
    public void onPut_withZeroLengthNameInHeader_returnsHttpBadRequest() throws IOException {
        String name = "";
        long length = 10;
        String mimeType = "text/plain";
        HeaderSet headerSet = new HeaderSet();
        doReturn(headerSet).when(mOperation).getReceivedHeader();
        headerSet.setHeader(HeaderSet.NAME, name);
        headerSet.setHeader(HeaderSet.LENGTH, length);
        headerSet.setHeader(HeaderSet.TYPE, mimeType);
        assertThat(mServerSession.onPut(mOperation)).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
    }

    @Test
    public void onPut_withNoMimeTypeInHeader_returnsHttpBadRequest() throws IOException {
        String name = "randomFile";
        long length = 10;
        String mimeType = null;
        HeaderSet headerSet = new HeaderSet();
        doReturn(headerSet).when(mOperation).getReceivedHeader();
        headerSet.setHeader(HeaderSet.NAME, name);
        headerSet.setHeader(HeaderSet.LENGTH, length);
        headerSet.setHeader(HeaderSet.TYPE, mimeType);
        assertThat(mServerSession.onPut(mOperation)).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
    }

    @Test
    public void onPut_withUnsupportedMimeTypeInHeader_returnsHttpBadRequest() throws IOException {
        String name = "randomFile.3danimation";
        long length = 10;
        String mimeType = "3danimation/superultrasonic";
        HeaderSet headerSet = new HeaderSet();
        headerSet.setHeader(HeaderSet.NAME, name);
        headerSet.setHeader(HeaderSet.LENGTH, length);
        headerSet.setHeader(HeaderSet.TYPE, mimeType);
        doReturn(headerSet).when(mOperation).getReceivedHeader();
        assertThat(mServerSession.onPut(mOperation)).isEqualTo(
                ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE);
    }

    @Test
    public void onPut_returnsObexHttpOk() throws IOException {
        // The flow of this test is as follow
        // onPut(mOperation) -> check many fileName, length, mimeType from op.getReceivedHeader()
        // insert the newly received info into ContentResolver
        // unblock the server and remove timeout message
        // modify mInfo & mFileInfo, manipulate receiveFile() then return ResponseCodes.OBEX_HTTP_OK

        Assume.assumeTrue("Ignore test when if there is not media mounted",
                Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED));
        String name = "randomFile.txt";
        long length = 10;
        String mimeType = "text/plain";
        Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/1");
        Uri uri = Uri.parse("file://Idontknow//Justmadeitup");
        int direction = BluetoothShare.DIRECTION_INBOUND;
        String hint = "file://Idontknow//Justmadeitup//" + name;
        String destination = "01:23:45:67:89:AB";
        int visibility = BluetoothShare.VISIBILITY_VISIBLE;
        int confirm = BluetoothShare.USER_CONFIRMATION_CONFIRMED;
        int status = BluetoothShare.STATUS_SUCCESS;
        int totalBytes = 1023;
        int currentBytes = 42;
        int timestamp = 123456789;
        boolean mediaScanned = false;
        mServerSession.mInfo = new BluetoothOppShareInfo(0, uri, hint, name,
                mimeType, direction, destination, visibility, confirm, status, totalBytes,
                currentBytes, timestamp, mediaScanned);
        mServerSession.mFileInfo = new BluetoothOppReceiveFileInfo(name, length, uri, status);

        HeaderSet headerSet = new HeaderSet();
        headerSet.setHeader(HeaderSet.NAME, name);
        headerSet.setHeader(HeaderSet.LENGTH, length);
        headerSet.setHeader(HeaderSet.TYPE, mimeType);
        doReturn(headerSet).when(mOperation).getReceivedHeader();

        doReturn(contentUri).when(mMethodProxy)
                .contentResolverInsert(any(), eq(BluetoothShare.CONTENT_URI), any());

        // unblocking the session
        mServerSession.unblock();
        mServerSession.mAccepted = BluetoothShare.USER_CONFIRMATION_CONFIRMED;
        Handler handler = mock(Handler.class);
        doAnswer(arg -> {
            mServerSession.unblock();
            // to ignore removeMessage, which is not mockable
            mServerSession.mTimeoutMsgSent = false;
            return true;
        }).when(handler).sendMessageAtTime(
                argThat(arg -> arg.what == BluetoothOppObexSession.MSG_CONNECT_TIMEOUT), anyLong());
        mServerSession.start(handler, 0);

        // manipulate ReceiveFile
        InputStream is = mock(InputStream.class);
        OutputStream os = mock(OutputStream.class);
        doReturn(is).when(mOperation).openInputStream();
        doReturn(10).when(mOperation).getMaxPacketSize();
        doReturn(os).when(mMethodProxy).contentResolverOpenOutputStream(any(), eq(uri));
        doReturn((int) length, -1).when(is).read(any());

        assertThat(mServerSession.onPut(mOperation)).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
    }

    @Test
    public void onConnect_withNonNullTargetInHeader_returnsHttpNotAcceptable() {
        HeaderSet request = new HeaderSet();
        HeaderSet reply = new HeaderSet();
        byte[] target = new byte[10];
        request.setHeader(HeaderSet.TARGET, target);
        assertThat(mServerSession.onConnect(request, reply)).isEqualTo(
                ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE);
    }

    @Test
    public void onConnect_returnsObexHttpOk() {
        HeaderSet request = new HeaderSet();
        HeaderSet reply = new HeaderSet();
        request.setHeader(HeaderSet.TARGET, null);
        BluetoothOppManager bluetoothOppManager = spy(
                BluetoothOppManager.getInstance(mTargetContext));
        BluetoothOppManager.setInstance(bluetoothOppManager);
        doReturn(true).when(bluetoothOppManager).isAcceptlisted(any());
        doNothing().when(mTargetContext).sendBroadcast(any(),
                eq(Constants.HANDOVER_STATUS_PERMISSION), any());

        assertThat(mServerSession.onConnect(request, reply)).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
        BluetoothOppManager.setInstance(null);
    }

    @Test
    public void onDisconnect_repliesObexHttpOk() {
        HeaderSet request = new HeaderSet();
        HeaderSet reply = new HeaderSet();
        mServerSession.onDisconnect(request, reply);
        assertThat(reply.responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
    }

    @Test
    public void onClose_doesNotThrow() {
        Handler handler = mock(Handler.class);
        mServerSession.start(handler, 0);
        mServerSession.onClose();
    }
}