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

Commit 2c8afeb9 authored by Sal Savage's avatar Sal Savage
Browse files

Clean up base request object and subclasses

This change adds a type metadata field to the base request object that
request implementations will use to indicate what type of request they
implemenet. This will allow future introspection on the objects to tell
what they are without instanceof.

This change also adds a helper getResponseCode() function to expose the
return code of a given request.

These together are used in a new toString() implementation to make
printing request objects more info rich.

Request subclasses now report a type. A few unnecessary log lines have
been removed.

Flag: EXEMPT, mechanical refactor, no logic change
Bug: 365626536
Test: atest com.android.bluetooth.pbapclient
Change-Id: Ia3a4905b5fb9607b0973da12541678b9f26a2560
parent 5a8e6dc6
Loading
Loading
Loading
Loading
+56 −50
Original line number Diff line number Diff line
@@ -29,91 +29,97 @@ import java.io.InputStream;
abstract class PbapClientRequest {
    static final String TAG = PbapClientRequest.class.getSimpleName();

    protected HeaderSet mHeaderSet;
    // Request Types
    public static final int TYPE_PULL_PHONEBOOK_METADATA = 0;
    public static final int TYPE_PULL_PHONEBOOK = 1;

    protected int mResponseCode;

    private boolean mAborted = false;

    private ClientOperation mOp = null;
    protected HeaderSet mHeaderSet = new HeaderSet();
    private int mResponseCode = -1;

    PbapClientRequest() {
        mHeaderSet = new HeaderSet();
        mResponseCode = -1;
    }

    public final boolean isSuccess() {
        return (mResponseCode == ResponseCodes.OBEX_HTTP_OK);
    /**
     * A function that returns the type of the request.
     *
     * <p>Used to determine type instead of using 'instanceof'
     */
    public abstract int getType();

    /**
     * Get the actual response code associated with the request
     *
     * @return The response code as in integer
     */
    public final int getResponseCode() {
        return mResponseCode;
    }

    /**
     * A generica operation, providing overridable hooks to read response headers and content.
     *
     * <p>All PBAP Client operations are GET OBEX operations, so that is what this is.
     */
    public void execute(ClientSession session) throws IOException {
        Log.v(TAG, "execute");

        /* in case request is aborted before can be executed */
        if (mAborted) {
            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
            return;
        }

        ClientOperation operation = null;
        try {
            mOp = (ClientOperation) session.get(mHeaderSet);
            operation = (ClientOperation) session.get(mHeaderSet);

            /* make sure final flag for GET is used (PBAP spec 6.2.2) */
            mOp.setGetFinalFlag(true);
            operation.setGetFinalFlag(true);

            /*
             * this will trigger ClientOperation to use non-buffered stream so
             * we can abort operation
             */
            mOp.continueOperation(true, false);

            readResponseHeaders(mOp.getReceivedHeader());

            InputStream is = mOp.openInputStream();
            readResponse(is);
            is.close();

            mOp.close();
            operation.continueOperation(true, false);

            mResponseCode = mOp.getResponseCode();

            Log.d(TAG, "mResponseCode=" + mResponseCode);

            checkResponseCode(mResponseCode);
            readResponseHeaders(operation.getReceivedHeader());
            InputStream inputStream = operation.openInputStream();
            readResponse(inputStream);
            inputStream.close();
            mResponseCode = operation.getResponseCode();
        } catch (IOException e) {
            Log.e(TAG, "IOException occurred when processing request", e);
            mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;

            Log.e(TAG, "IOException occurred when processing request", e);
            throw e;
        }
    }

    public void abort() {
        mAborted = true;

        if (mOp != null) {
            try {
                mOp.abort();
            } catch (IOException e) {
                Log.e(TAG, "Exception occurred when trying to abort", e);
        } finally {
            // Always close the operation so the next operation can successfully complete
            if (operation != null) {
                operation.close();
            }
        }
    }

    protected void readResponse(InputStream stream) throws IOException {
        Log.v(TAG, "readResponse");

        /* nothing here by default */
    }

    protected void readResponseHeaders(HeaderSet headerset) {
        Log.v(TAG, "readResponseHeaders");

        /* nothing here by default */
    }

    protected void checkResponseCode(int responseCode) throws IOException {
        Log.v(TAG, "checkResponseCode");
    public static String typeToString(int type) {
        switch (type) {
            case TYPE_PULL_PHONEBOOK_METADATA:
                return "TYPE_PULL_PHONEBOOK_METADATA";
            case TYPE_PULL_PHONEBOOK:
                return "TYPE_PULL_PHONEBOOK";
            default:
                return "TYPE_RESERVED (" + type + ")";
        }
    }

        /* nothing here by default */
    @Override
    public String toString() {
        return "<"
                + TAG
                + (" type=" + typeToString(getType()))
                + (", responseCode=" + getResponseCode())
                + ">";
    }
}
+5 −4
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.bluetooth.pbapclient;

import android.accounts.Account;
import android.util.Log;

import com.android.bluetooth.ObexAppParameters;
import com.android.obex.HeaderSet;
@@ -40,6 +39,11 @@ final class RequestPullPhonebook extends PbapClientRequest {

    private PbapPhonebook mResponse;

    @Override
    public int getType() {
        return TYPE_PULL_PHONEBOOK;
    }

    RequestPullPhonebook(String phonebook, PbapApplicationParameters params, Account account) {
        mPhonebook = phonebook;
        mFormat = params.getVcardFormat();
@@ -80,10 +84,7 @@ final class RequestPullPhonebook extends PbapClientRequest {

    @Override
    protected void readResponse(InputStream stream) throws IOException {
        Log.v(TAG, "readResponse");

        mResponse = new PbapPhonebook(mPhonebook, mFormat, mListStartOffset, mAccount, stream);
        Log.d(TAG, "Read " + mResponse.getCount() + " entries");
    }

    public String getPhonebook() {
+5 −0
Original line number Diff line number Diff line
@@ -35,6 +35,11 @@ final class RequestPullPhonebookMetadata extends PbapClientRequest {
    private final String mPhonebook;
    private PbapPhonebookMetadata mResponse;

    @Override
    public int getType() {
        return TYPE_PULL_PHONEBOOK_METADATA;
    }

    RequestPullPhonebookMetadata(String phonebook, PbapApplicationParameters params) {
        mPhonebook = phonebook;
        mHeaderSet.setHeader(HeaderSet.NAME, phonebook);
+0 −93
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.pbapclient;

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

import static org.mockito.Mockito.mock;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.obex.ClientSession;
import com.android.obex.HeaderSet;
import com.android.obex.ObexTransport;
import com.android.obex.ResponseCodes;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.io.InputStream;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class PbapClientRequestTest {

    private PbapClientRequest mRequest = new PbapClientRequest() {};

    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock private ObexTransport mObexTransport;

    @Before
    public void setUp() throws Exception {
        mRequest = new PbapClientRequest() {};
    }

    @Test
    public void isSuccess_true() {
        mRequest.mResponseCode = ResponseCodes.OBEX_HTTP_OK;

        assertThat(mRequest.isSuccess()).isTrue();
    }

    @Test
    public void isSuccess_false() {
        mRequest.mResponseCode = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;

        assertThat(mRequest.isSuccess()).isFalse();
    }

    @Test
    public void execute_afterAbort() throws Exception {
        mRequest.abort();
        ClientSession session = new ClientSession(mObexTransport);
        mRequest.execute(session);

        assertThat(mRequest.mResponseCode).isEqualTo(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR);
    }

    // TODO: Add execute_success test case.

    @Test
    public void emptyMethods() {
        try {
            mRequest.readResponse(mock(InputStream.class));
            mRequest.readResponseHeaders(new HeaderSet());
            mRequest.checkResponseCode(ResponseCodes.OBEX_HTTP_OK);

        } catch (Exception e) {
            assertWithMessage("Exception should not happen.").fail();
        }
    }
}
+126 −7
Original line number Diff line number Diff line
@@ -19,30 +19,102 @@ package com.android.bluetooth.pbapclient;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import androidx.test.filters.SmallTest;
import static org.junit.Assert.assertThrows;

import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.FakeObexServer;
import com.android.obex.ApplicationParameter;
import com.android.obex.ClientSession;
import com.android.obex.HeaderSet;
import com.android.obex.Operation;
import com.android.obex.ResponseCodes;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@SmallTest
import java.io.IOException;
import java.nio.ByteBuffer;

@RunWith(AndroidJUnit4.class)
public class RequestPullPhonebookMetadataTest {
    RequestPullPhonebookMetadata mRequest;
    private static final String PHONEBOOK_NAME = "phonebook";
    private static final short PHONEBOOK_SIZE = 200;

    private FakePbapObexServer mServer;
    private ClientSession mSession;
    private RequestPullPhonebookMetadata mRequest;

    @Before
    public void setUp() {
    public void setUp() throws IOException {
        mServer = new FakePbapObexServer();
        mSession = mServer.getClientSession();

        PbapApplicationParameters params =
                new PbapApplicationParameters(
                        PbapApplicationParameters.PROPERTIES_ALL,
                        PbapPhonebook.FORMAT_VCARD_30,
                        PbapApplicationParameters.MAX_PHONEBOOK_SIZE,
                        /* startOffset= */ 0);
        mRequest =
                new RequestPullPhonebookMetadata(/* pbName= */ "phonebook", /* params= */ params);
        mRequest = new RequestPullPhonebookMetadata(PHONEBOOK_NAME, params);
    }

    @Test
    public void getType_returnsTypeMetadataRequest() {
        assertThat(mRequest.getType()).isEqualTo(PbapClientRequest.TYPE_PULL_PHONEBOOK_METADATA);
    }

    @Test
    public void getResponseCode_beforeExecutingRequest_returnsNegativeOne() {
        assertThat(mRequest.getResponseCode()).isEqualTo(-1);
    }

    @Test
    public void execute_sessionConnectedAndResponseOk_returnsMetadata() throws IOException {
        mSession.connect(null);
        mServer.setSize(PHONEBOOK_SIZE);

        mRequest.execute(mSession);

        assertThat(mRequest.getResponseCode()).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
        assertThat(mRequest.getPhonebook()).isEqualTo(PHONEBOOK_NAME);

        PbapPhonebookMetadata metadata = mRequest.getMetadata();
        assertThat(metadata.getPhonebook()).isEqualTo(PHONEBOOK_NAME);
        assertThat(metadata.getSize()).isEqualTo(200);
        assertThat(metadata.getDatabaseIdentifier())
                .isEqualTo(PbapPhonebookMetadata.INVALID_DATABASE_IDENTIFIER);
        assertThat(metadata.getPrimaryVersionCounter())
                .isEqualTo(PbapPhonebookMetadata.INVALID_VERSION_COUNTER);
        assertThat(metadata.getSecondaryVersionCounter())
                .isEqualTo(PbapPhonebookMetadata.INVALID_VERSION_COUNTER);
    }

    @Test
    public void execute_sessionConnectedAndResponseBad_returnsEmptyMetadata() throws IOException {
        mSession.connect(null);
        mServer.setResponseCode(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
        mRequest.execute(mSession);

        assertThat(mRequest.getResponseCode()).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
        assertThat(mRequest.getPhonebook()).isEqualTo(PHONEBOOK_NAME);

        PbapPhonebookMetadata metadata = mRequest.getMetadata();
        assertThat(metadata.getPhonebook()).isEqualTo(PHONEBOOK_NAME);
        assertThat(metadata.getSize()).isEqualTo(PbapPhonebookMetadata.INVALID_SIZE);
        assertThat(metadata.getDatabaseIdentifier())
                .isEqualTo(PbapPhonebookMetadata.INVALID_DATABASE_IDENTIFIER);
        assertThat(metadata.getPrimaryVersionCounter())
                .isEqualTo(PbapPhonebookMetadata.INVALID_VERSION_COUNTER);
        assertThat(metadata.getSecondaryVersionCounter())
                .isEqualTo(PbapPhonebookMetadata.INVALID_VERSION_COUNTER);
    }

    @Test
    public void execute_sessionNotConnected_throwsIOException() throws IOException {
        assertThrows(IOException.class, () -> mRequest.execute(mSession));
        assertThat(mRequest.getResponseCode()).isEqualTo(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR);
    }

    @Test
@@ -50,9 +122,56 @@ public class RequestPullPhonebookMetadataTest {
        try {
            HeaderSet headerSet = new HeaderSet();
            mRequest.readResponseHeaders(headerSet);
            assertThat(mRequest.getMetadata().getSize()).isEqualTo(-1);
            assertThat(mRequest.getMetadata().getSize())
                    .isEqualTo(PbapPhonebookMetadata.INVALID_SIZE);
        } catch (Exception e) {
            assertWithMessage("Exception should not happen.").fail();
        }
    }

    // *********************************************************************************************
    // * Fake PBAP Server
    // *********************************************************************************************

    private static class FakePbapObexServer extends FakeObexServer {
        private static final byte SIZE_BYTES = 2;

        private int mResponseCode = ResponseCodes.OBEX_HTTP_OK;
        private short mSize = 0;

        FakePbapObexServer() throws IOException {
            super();
        }

        public void setResponseCode(int responseCode) {
            mResponseCode = responseCode;
        }

        public void setSize(short size) {
            mSize = size;
        }

        @Override
        public int onGet(final Operation op) {
            if (mResponseCode != ResponseCodes.OBEX_HTTP_OK) {
                return mResponseCode;
            }

            ApplicationParameter params = new ApplicationParameter();
            params.addTriplet(
                    PbapApplicationParameters.OAP_PHONEBOOK_SIZE,
                    SIZE_BYTES,
                    shortToByteArray(mSize));

            HeaderSet replyHeaders = new HeaderSet();
            replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, params.getHeader());
            return sendResponse(op, replyHeaders, null);
        }

        public byte[] shortToByteArray(short s) {
            ByteBuffer ret = ByteBuffer.allocate(2);
            ret.putShort(s);
            return ret.array();
        }
    }
}
Loading