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

Commit 4167f335 authored by Hansong Zhang's avatar Hansong Zhang Committed by android-build-merger
Browse files

Merge "PBAP: Use PbapStateMachine to handle new connection"

am: 8de411ee

Change-Id: Ic0d0c07d01c3895bbedcdfecc1f1d896b3945d88
parents adbc868e 8de411ee
Loading
Loading
Loading
Loading
+7 −19
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@

package com.android.bluetooth.pbap;

import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
@@ -47,7 +48,6 @@ import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;

@@ -85,16 +85,14 @@ public class BluetoothPbapActivity extends AlertActivity

    private Button mOkButton;

    private CheckBox mAlwaysAllowed;

    private boolean mTimeout = false;

    private boolean mAlwaysAllowedValue = true;

    private static final int DISMISS_TIMEOUT_DIALOG = 0;

    private static final int DISMISS_TIMEOUT_DIALOG_VALUE = 2000;

    private BluetoothDevice mDevice;

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
@@ -110,6 +108,7 @@ public class BluetoothPbapActivity extends AlertActivity
        super.onCreate(savedInstanceState);
        Intent i = getIntent();
        String action = i.getAction();
        mDevice = i.getParcelableExtra(BluetoothPbapService.EXTRA_DEVICE);
        if (action.equals(BluetoothPbapService.AUTH_CHALL_ACTION)) {
            showPbapDialog(DIALOG_YES_NO_AUTH);
            mCurrentDialog = DIALOG_YES_NO_AUTH;
@@ -142,10 +141,9 @@ public class BluetoothPbapActivity extends AlertActivity
    }

    private String createDisplayText(final int id) {
        String mRemoteName = BluetoothPbapService.getRemoteDeviceName();
        switch (id) {
            case DIALOG_YES_NO_AUTH:
                String mMessage2 = getString(R.string.pbap_session_key_dialog_title, mRemoteName);
                String mMessage2 = getString(R.string.pbap_session_key_dialog_title, mDevice);
                return mMessage2;
            default:
                return null;
@@ -193,16 +191,7 @@ public class BluetoothPbapActivity extends AlertActivity
            final String extraValue) {
        Intent intent = new Intent(intentName);
        intent.setPackage(BluetoothPbapService.THIS_PACKAGE_NAME);
        if (extraName != null) {
            intent.putExtra(extraName, extraValue);
        }
        sendBroadcast(intent);
    }

    private void sendIntentToReceiver(final String intentName, final String extraName,
            final boolean extraValue) {
        Intent intent = new Intent(intentName);
        intent.setPackage(BluetoothPbapService.THIS_PACKAGE_NAME);
        intent.putExtra(BluetoothPbapService.EXTRA_DEVICE, mDevice);
        if (extraName != null) {
            intent.putExtra(extraName, extraValue);
        }
@@ -230,8 +219,7 @@ public class BluetoothPbapActivity extends AlertActivity
    private void onTimeout() {
        mTimeout = true;
        if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
            mMessageView.setText(getString(R.string.pbap_authentication_timeout_message,
                    BluetoothPbapService.getRemoteDeviceName()));
            mMessageView.setText(getString(R.string.pbap_authentication_timeout_message, mDevice));
            mKeyView.setVisibility(View.GONE);
            mKeyView.clearFocus();
            mKeyView.removeTextChangedListener(this);
+11 −17
Original line number Diff line number Diff line
@@ -32,8 +32,6 @@

package com.android.bluetooth.pbap;

import android.os.Handler;
import android.os.Message;
import android.util.Log;

import javax.obex.Authenticator;
@@ -44,39 +42,36 @@ import javax.obex.PasswordAuthentication;
 * authentication procedure.
 */
public class BluetoothPbapAuthenticator implements Authenticator {
    private static final String TAG = "BluetoothPbapAuthenticator";
    private static final String TAG = "PbapAuthenticator";

    private boolean mChallenged;

    private boolean mAuthCancelled;

    private String mSessionKey;
    private PbapStateMachine mPbapStateMachine;

    private Handler mCallback;

    public BluetoothPbapAuthenticator(final Handler callback) {
        mCallback = callback;
    BluetoothPbapAuthenticator(final PbapStateMachine stateMachine) {
        mPbapStateMachine = stateMachine;
        mChallenged = false;
        mAuthCancelled = false;
        mSessionKey = null;
    }

    public final synchronized void setChallenged(final boolean bool) {
    final synchronized void setChallenged(final boolean bool) {
        mChallenged = bool;
    }

    public final synchronized void setCancelled(final boolean bool) {
    final synchronized void setCancelled(final boolean bool) {
        mAuthCancelled = bool;
    }

    public final synchronized void setSessionKey(final String string) {
    final synchronized void setSessionKey(final String string) {
        mSessionKey = string;
    }

    private void waitUserConfirmation() {
        Message msg = Message.obtain(mCallback);
        msg.what = BluetoothPbapService.MSG_OBEX_AUTH_CHALL;
        msg.sendToTarget();
        mPbapStateMachine.sendMessage(PbapStateMachine.CREATE_NOTIFICATION);
        mPbapStateMachine.sendMessageDelayed(PbapStateMachine.REMOVE_NOTIFICATION,
                BluetoothPbapService.USER_CONFIRM_TIMEOUT_VALUE);
        synchronized (this) {
            while (!mChallenged && !mAuthCancelled) {
                try {
@@ -93,8 +88,7 @@ public class BluetoothPbapAuthenticator implements Authenticator {
            final boolean isUserIdRequired, final boolean isFullAccess) {
        waitUserConfirmation();
        if (mSessionKey.trim().length() != 0) {
            PasswordAuthentication pa = new PasswordAuthentication(null, mSessionKey.getBytes());
            return pa;
            return new PasswordAuthentication(null, mSessionKey.getBytes());
        }
        return null;
    }
+8 −19
Original line number Diff line number Diff line
@@ -171,8 +171,6 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {

    private boolean mVcardSelector = false;

    private int mMissedCallSize = 0;

    // record current path the client are browsing
    private String mCurrentPath = "";

@@ -180,8 +178,6 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {

    private Context mContext;

    private BluetoothPbapService mService;

    private BluetoothPbapVcardManager mVcardManager;

    private int mOrderBy = ORDER_BY_INDEXED;
@@ -204,6 +200,8 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {

    private AppParamValue mConnAppParamValue;

    private PbapStateMachine mStateMachine;

    public static class ContentType {
        public static final int PHONEBOOK = 1;

@@ -216,12 +214,13 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {
        public static final int COMBINED_CALL_HISTORY = 5;
    }

    public BluetoothPbapObexServer(Handler callback, BluetoothPbapService service) {
    public BluetoothPbapObexServer(Handler callback, Context context,
            PbapStateMachine stateMachine) {
        super();
        mCallback = callback;
        mService = service;
        mContext = service.getApplicationContext();
        mContext = context;
        mVcardManager = new BluetoothPbapVcardManager(mContext);
        mStateMachine = stateMachine;
    }

    @Override
@@ -284,9 +283,6 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {
            Log.v(TAG, "onConnect(): uuid is ok, will send out " + "MSG_SESSION_ESTABLISHED msg.");
        }

        Message msg = Message.obtain(mCallback);
        msg.what = BluetoothPbapService.MSG_SESSION_ESTABLISHED;
        msg.sendToTarget();
        return ResponseCodes.OBEX_HTTP_OK;
    }

@@ -383,14 +379,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {

    @Override
    public void onClose() {
        if (mCallback != null) {
            Message msg = Message.obtain(mCallback);
            msg.what = BluetoothPbapService.MSG_SERVERSESSION_CLOSE;
            msg.sendToTarget();
            if (D) {
                Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
            }
        }
        mStateMachine.sendMessage(PbapStateMachine.DISCONNECT);
    }

    @Override
@@ -1439,7 +1428,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {

    private byte[] getDatabaseIdentifier() {
        mDatabaseIdentifierHigh = 0;
        mDatabaseIdentifierLow = mService.getDbIdentifier();
        mDatabaseIdentifierLow = BluetoothPbapUtils.sDbIdentifier.get();
        if (mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER
                && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) {
            ByteBuffer ret = ByteBuffer.allocate(16);
+104 −355

File changed.

Preview size limit exceeded, changes collapsed.

+353 −0
Original line number Diff line number Diff line
/*
 * Copyright 2017 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.pbap;

import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.IObexConnectionHandler;
import com.android.bluetooth.ObexRejectServer;
import com.android.bluetooth.R;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;

import java.io.IOException;

import javax.obex.ResponseCodes;
import javax.obex.ServerSession;

/**
 * Bluetooth PBAP StateMachine
 *              (New connection socket)
 *                 WAITING FOR AUTH
 *                        |
 *                        |    (request permission from Settings UI)
 *                        |
 *           (Accept)    / \   (Reject)
 *                      /   \
 *                     v     v
 *          CONNECTED   ----->  FINISHED
 *                (OBEX Server done)
 */
class PbapStateMachine extends StateMachine {
    private static final String TAG = "PbapStateMachine";
    private static final boolean DEBUG = true;
    private static final boolean VERBOSE = true;

    private static final String PBAP_OBEX_NOTIFICATION_CHANNEL = "pbap_obex_notification_channel";
    private static final int NOTIFICATION_ID_AUTH = -1000002;
    // TODO: set a notification channel for each sm

    static final int AUTHORIZED = 1;
    static final int REJECTED = 2;
    static final int DISCONNECT = 3;
    static final int REQUEST_PERMISSION = 4;
    static final int CREATE_NOTIFICATION = 5;
    static final int REMOVE_NOTIFICATION = 6;
    static final int AUTH_KEY_INPUT = 7;
    static final int AUTH_CANCELLED = 8;

    BluetoothPbapService mService;
    IObexConnectionHandler mIObexConnectionHandler;

    private final WaitingForAuth mWaitingForAuth = new WaitingForAuth();
    private final Finished mFinished = new Finished();
    private final Connected mConnected = new Connected();
    private BluetoothDevice mRemoteDevice;
    private Handler mServiceHandler;
    private BluetoothSocket mConnSocket;
    private BluetoothPbapObexServer mPbapServer;
    private BluetoothPbapAuthenticator mObexAuth;
    private ServerSession mServerSession;

    private PbapStateMachine(@NonNull BluetoothPbapService service, Looper looper,
            @NonNull BluetoothDevice device, @NonNull BluetoothSocket connSocket,
            IObexConnectionHandler obexConnectionHandler, Handler pbapHandler) {
        super(TAG, looper);
        mService = service;
        mIObexConnectionHandler = obexConnectionHandler;
        mRemoteDevice = device;
        mServiceHandler = pbapHandler;
        mConnSocket = connSocket;

        addState(mFinished);
        addState(mWaitingForAuth);
        addState(mConnected);
        setInitialState(mWaitingForAuth);
    }

    static PbapStateMachine make(BluetoothPbapService service, Looper looper,
            BluetoothDevice device, BluetoothSocket connSocket,
            IObexConnectionHandler obexConnectionHandler, Handler pbapHandler) {
        PbapStateMachine stateMachine = new PbapStateMachine(service, looper, device, connSocket,
                obexConnectionHandler, pbapHandler);
        stateMachine.start();
        return stateMachine;
    }

    BluetoothDevice getRemoteDevice() {
        return mRemoteDevice;
    }

    private abstract class PbapStateBase extends State {
        /**
         * Get a state value from {@link BluetoothProfile} that represents the connection state of
         * this headset state
         *
         * @return a value in {@link BluetoothProfile#STATE_DISCONNECTED},
         * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or
         * {@link BluetoothProfile#STATE_DISCONNECTING}
         */
        abstract int getConnectionStateInt();
    }

    class WaitingForAuth extends PbapStateBase {
        @Override
        int getConnectionStateInt() {
            return BluetoothProfile.STATE_CONNECTING;
        }

        @Override
        public void enter() {
            mService.checkOrGetPhonebookPermission(PbapStateMachine.this);
        }

        @Override
        public boolean processMessage(Message message) {
            switch (message.what) {
                case AUTHORIZED:
                    transitionTo(mConnected);
                    break;
                case REJECTED:
                    rejectConnection();
                    transitionTo(mFinished);
                    break;
                case DISCONNECT:
                    mServiceHandler.removeMessages(BluetoothPbapService.USER_TIMEOUT);
                    Message msg = mServiceHandler.obtainMessage(
                            BluetoothPbapService.USER_TIMEOUT);
                    msg.obj = PbapStateMachine.this;
                    msg.sendToTarget();
                    transitionTo(mFinished);
                    break;
            }
            return HANDLED;
        }

        private void rejectConnection() {
            mPbapServer = new BluetoothPbapObexServer(mServiceHandler, mService,
                    PbapStateMachine.this);
            BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
            ObexRejectServer server = new ObexRejectServer(ResponseCodes.OBEX_HTTP_UNAVAILABLE,
                    mConnSocket);
            try {
                mServerSession = new ServerSession(transport, server, null);
            } catch (IOException ex) {
                Log.e(TAG, "Caught exception starting OBEX reject server session"
                        + ex.toString());
            }
        }
    }

    class Finished extends PbapStateBase {
        @Override
        int getConnectionStateInt() {
            return BluetoothProfile.STATE_DISCONNECTED;
        }

        @Override
        public void enter() {
            // Close OBEX server session
            if (mServerSession != null) {
                mServerSession.close();
                mServerSession = null;
            }

            // Close connection socket
            try {
                mConnSocket.close();
                mConnSocket = null;
            } catch (IOException e) {
                Log.e(TAG, "Close Connection Socket error: " + e.toString());
            }

            mServiceHandler.obtainMessage(BluetoothPbapService.MSG_STATE_MACHINE_DONE)
                    .sendToTarget();
        }

    }

    class Connected extends PbapStateBase {
        @Override
        int getConnectionStateInt() {
            return BluetoothProfile.STATE_CONNECTED;
        }

        @Override
        public void enter() {
            try {
                startObexServerSession();
            } catch (IOException ex) {
                Log.e(TAG, "Caught exception starting OBEX server session" + ex.toString());
            }
        }

        @Override
        public void exit() {
        }

        @Override
        public boolean processMessage(Message message) {
            switch (message.what) {
                case DISCONNECT:
                    stopObexServerSession();
                    break;
                case CREATE_NOTIFICATION:
                    createPbapNotification();
                    break;
                case REMOVE_NOTIFICATION:
                    Intent i = new Intent(BluetoothPbapService.USER_CONFIRM_TIMEOUT_ACTION);
                    mService.sendBroadcast(i);
                    notifyAuthCancelled();
                    removePbapNotification(NOTIFICATION_ID_AUTH);
                    break;
                case AUTH_KEY_INPUT:
                    String key = (String) message.obj;
                    notifyAuthKeyInput(key);
                    break;
                case AUTH_CANCELLED:
                    notifyAuthCancelled();
                    break;
            }
            return HANDLED;
        }

        private void startObexServerSession() throws IOException {
            if (VERBOSE) {
                Log.v(TAG, "Pbap Service startObexServerSession");
            }

            // acquire the wakeLock before start Obex transaction thread
            mServiceHandler.sendMessage(
                    mServiceHandler.obtainMessage(BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK));

            mPbapServer = new BluetoothPbapObexServer(mServiceHandler, mService,
                    PbapStateMachine.this);
            synchronized (this) {
                mObexAuth = new BluetoothPbapAuthenticator(PbapStateMachine.this);
                mObexAuth.setChallenged(false);
                mObexAuth.setCancelled(false);
            }
            BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
            mServerSession = new ServerSession(transport, mPbapServer, mObexAuth);
            // It's ok to just use one wake lock
            // Message MSG_ACQUIRE_WAKE_LOCK is always surrounded by RELEASE. safe.
        }

        private void stopObexServerSession() {
            if (VERBOSE) {
                Log.v(TAG, "Pbap Service stopObexServerSession");
            }
            transitionTo(mFinished);
        }

        private void createPbapNotification() {
            NotificationManager nm =
                    (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
            NotificationChannel notificationChannel = new NotificationChannel(
                    PBAP_OBEX_NOTIFICATION_CHANNEL,
                    mService.getString(R.string.pbap_notification_group),
                    NotificationManager.IMPORTANCE_HIGH);
            nm.createNotificationChannel(notificationChannel);

            // Create an intent triggered by clicking on the status icon.
            Intent clickIntent = new Intent();
            clickIntent.setClass(mService, BluetoothPbapActivity.class);
            clickIntent.putExtra(BluetoothPbapService.EXTRA_DEVICE, mRemoteDevice);
            clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            clickIntent.setAction(BluetoothPbapService.AUTH_CHALL_ACTION);

            // Create an intent triggered by clicking on the
            // "Clear All Notifications" button
            Intent deleteIntent = new Intent();
            deleteIntent.setClass(mService, BluetoothPbapService.class);
            deleteIntent.setAction(BluetoothPbapService.AUTH_CANCELLED_ACTION);

            String name = mRemoteDevice.getName();

            Notification notification =
                    new Notification.Builder(mService, PBAP_OBEX_NOTIFICATION_CHANNEL)
                            .setWhen(System.currentTimeMillis())
                            .setContentTitle(mService.getString(R.string.auth_notif_title))
                            .setContentText(mService.getString(R.string.auth_notif_message, name))
                            .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
                            .setTicker(mService.getString(R.string.auth_notif_ticker))
                            .setColor(mService.getResources().getColor(
                                    com.android.internal.R.color.system_notification_accent_color,
                                    mService.getTheme()))
                            .setFlag(Notification.FLAG_AUTO_CANCEL, true)
                            .setFlag(Notification.FLAG_ONLY_ALERT_ONCE, true)
                            .setContentIntent(PendingIntent.getActivity(mService, 0, clickIntent,
                                    0))
                            .setDeleteIntent(PendingIntent.getBroadcast(mService, 0, deleteIntent,
                                    0))
                            .setLocalOnly(true)
                            .build();
            nm.notify(NOTIFICATION_ID_AUTH, notification);
        }

        private void removePbapNotification(int id) {
            NotificationManager nm =
                    (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
            nm.cancel(id);
        }

        private void notifyAuthCancelled() {
            synchronized (this) {
                mObexAuth.setCancelled(true);
                mObexAuth.notify();
            }
        }

        private void notifyAuthKeyInput(final String key) {
            synchronized (this) {
                if (key != null) {
                    mObexAuth.setSessionKey(key);
                }
                mObexAuth.setChallenged(true);
                mObexAuth.notify();
            }
        }
    }

    synchronized int getConnectionState() {
        return ((PbapStateBase) getCurrentState()).getConnectionStateInt();
    }
}
Loading