android Bluetooth FTP client创建流程分析

/ android / 没有评论 / 642浏览

用到的代码文件

packages/apps/Settings/src/:
com/android/settings/bluetooth/DeviceProfilesSettings.java

frameworks/base/packages/SettingsLib/src/:
com/android/settingslib/bluetooth/CachedBluetoothDevice.java
com/android/settingslib/bluetooth/FtpProfile.java

packages/apps/Bluetooth/src/:
com/android/bluetooth/ftp/BluetoothFtpClientService.java
com/android/bluetooth/ftp/BluetoothFtpClientActivity.java
com/android/bluetooth/ftp/BluetoothFtpClientLocal.java
com/android/bluetooth/ftp/BluetoothFtpRfcommTransport.java
com/android/bluetooth/ftp/BluetoothFtpObexClientSession.java

frameworks/base/obex/:
javax/obex/ObexSession.java
javax/obex/HeaderSet.java
javax/obex/ClientSession.java

代码执行流程图

android ftp profile

查看原图

蓝牙FTP协议Client端创建流程分析

下面的代码是以android N(api 24)为基础。

本文基于高通(Qualcomm)平台基线。

当两个蓝牙设备配对成功以后,在一方设备上会显示FTP协议选项,点击这个选项。

下面进入DeviceProfilesSettings类中的onProfileClicked()方法。

private void onProfileClicked(LocalBluetoothProfile profile, 
    CheckBox profilePref) {
    BluetoothDevice device = mCachedDevice.getDevice();

    if (!profilePref.isChecked()) {
        // Recheck it, until the dialog is done.
        profilePref.setChecked(true);
        askDisconnect(mManager.getForegroundActivity(), 
            profile);
    } else {
        ......
        if (profile.isPreferred(device)) {
            // profile is preferred but not connected: 
            // disable auto-connect
            if (profile instanceof PanProfile) {
                mCachedDevice.connectProfile(profile);
            } else {
                profile.setPreferred(device, false);
            }
        } else {
            profile.setPreferred(device, true);
            /**
              * 进入这里
              */
            mCachedDevice.connectProfile(profile);
        }
        refreshProfilePreference(profilePref, profile);
    }
}

下面进入CachedBluetoothDevice类中的connectProfile()方法。

public void connectProfile(LocalBluetoothProfile profile) {
    mConnectAttempted = SystemClock.elapsedRealtime();
    // Reset the only-show-one-error-dialog tracking variable
    mIsConnectingErrorPossible = true;
    /**
      * 进入这里
      */
    connectInt(profile);
    // Refresh the UI based on profile.connect() call
    refresh();
}

synchronized void connectInt(LocalBluetoothProfile profile) {
    if (!ensurePaired()) {
        return;
    }
    log("method called connectInt()");
    /**
      * 进入这里
      */
    if (profile.connect(mDevice)) {
        if (Utils.D) {
            Log.d(TAG, "Command sent successfully:CONNECT " 
                + describe(profile));
        }
        return;
    }
    Log.i(TAG, "Failed to connect " + profile.toString() 
        + " to " + mName);
}

下面进入FtpProfile类中的connect()方法。

private static BluetoothFtpClient mFtpClient;

public boolean connect(BluetoothDevice device) {
    if (mFtpClient != null) {
        return mFtpClient.connect(device);
    } else {
        return false;
    }
}

下面进入BluetoothFtpClient类中的connect()方法。
IBluetoothFtpCtrl是一个aidl文件,由此可以猜测即将进行跨进程调用。

private IBluetoothFtpCtrl mService;

public boolean connect(BluetoothDevice device) {
    if (mService != null) {
        try {
            /**
              * 跨进程调用
              */
            mService.connect(device);
        } catch (RemoteException e) {
            return false;
        }
    } else {
        return false;
    }
    return true;
}

下面是mService在BluetoothFtpClient类中的实现。

private final ServiceConnection mConnection = 
        new ServiceConnection() {
    public void onServiceConnected(ComponentName className, 
        IBinder service) {
        mService = IBluetoothFtpCtrl.Stub.asInterface(service);
    }

    public void onServiceDisconnected(ComponentName className) {
        mService = null;
    }
};

mConnection在doBind()方法中被调用。

boolean doBind() {
    Intent intent = new Intent(ACTION_BIND_CLIENT);
    ComponentName comp = intent.resolveSystemService(
        mContext.getPackageManager(), 0);
    intent.setComponent(comp);
    if (comp == null
            || !mContext.bindServiceAsUser(intent, 
                mConnection, 0,
                android.os.Process.myUserHandle())) {
        Log.e(TAG, "Could not bind to Bluetooth Ftp"
            + " Service with " + intent);
        return false;
    }
    return true;
}

上面doBind()方法最终绑定的IBluetoothFtpCtrl是在BluetoothFtpClientService类中实现。
BluetoothFtpClientService类定义在packages/apps/Bluetooth中,因此这就建立起了 Settings app与蓝牙进程通信的桥梁。

private final IBluetoothFtpCtrl.Stub mFtpClientCtrl = 
    new IBluetoothFtpCtrl.Stub() {
    ......
    /**
      * 上面BluetoothFtpClient类中的connect()方法进入这里
      */
    public void connect(BluetoothDevice device) {
        boolean launched = false;

        handleStateChanged(FTP_CLIENT, device,
                BluetoothFtpClient.BT_FTPC_STATE_ACTIVE,
                BluetoothFtpClient.BT_FTPC_STATE_AUTHORIZING);

            printLog("BluetoothFtpClient state: "+mClientState);
        /**
          * 启动FTP Client客户端界面
          *  这个界面中显示的就是服务端中文件和文件夹列表
          */
        if (mClientState == BluetoothFtpClient
                .BT_FTPC_STATE_ACTIVE) {
            Notification noti = 
                genFtpNotification(FTPC_ACTIVATED_NOTIFY, 
                    device.getName(), false);
            mNM.notify(FTPC_ACTIVATED_NOTIFY, noti);

            Intent intent = new Intent();

            mClientCurrentPath = DEFAULT_ROOT;
            mCurrentServer = device;
            intent.setClassName(getPackageName(),
                BluetoothFtpClientActivity.class.getName())
                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

            mDisconnectByUser = false;
            launched = true;
             /*
              * 启动
              */
            startActivity(intent);

        } else if (mClientState == BluetoothFtpClient
            .BT_FTPC_STATE_IDLE) {
            printErr("FTP client is not available.");
            // sendServiceMsg(FTP_SHOW_TOAST,
            // R.string.bluetooth_ftp_client_connection_failed);

        } else {
            // Try establishing the connection with some device, 
            // reject other attemptions.
            // add by liangfeng.xu for toast ftp service is busy
            sendServiceMsg(FTP_SHOW_TOAST,
                R.string.bluetooth_ftp_service_busy);
        }

        if (!launched) {
            handleStateChanged(FTP_CLIENT, device,
                    BluetoothFtpClient.BT_FTPC_STATE_AUTHORIZING,
                    BluetoothFtpClient.BT_FTPC_STATE_ACTIVE);
        }
    }
    ......
}

下面进入BluetoothFtpClientActivity类中的onCreate()方法。

@Override
protected void onCreate(Bundle savedInstanceState) {
    ......
    Intent intent = new Intent();
    intent.setClass(this, BluetoothFtpClientService.class);
    intent.setPackage("com.android.bluetooth.ftp");
    /**
      * 与BluetoothFtpClientService进行绑定
      */
    if (!bindService(intent, mFtpClientConn, 
        Context.BIND_AUTO_CREATE)) {
        Log.e(TAG, "[BT][FTP] Failed to bind service");
    }
    ......
}

下面是mFtpClientConn的具体实现。

private ServiceConnection mFtpClientConn = 
    new ServiceConnection() {

    public void onServiceConnected(ComponentName className, 
        IBinder service) {
        int state = -1;
        /**
          * 拿到binder对象,便于与BluetoothFtpClientService进行通信
          */
        mFtpClient = IBluetoothFtpClient.Stub.asInterface(service);
        if (mFtpClient != null) {
            try {
                mFtpClient.registerCallback(mFtpClientCallback);
                state = mFtpClient.getState();
                mCurrentPath = mFtpClient.getCurrentPath();
                //state: 201
                Log.d(TAG, "[BT][FTP] Client state: " + state);
                //mCurrentPath : /
                Log.d(STAG, "mCurrentPath : "+mCurrentPath);

            } catch (RemoteException e) {
                Log.e(TAG, "[BT][FTP] Exception occurred when "
                        + "registerCallback(), " 
                        + e);
            }
        } else {
            Log.e(TAG, "[BT][FTP] onServiceConnected(), 
                mFtpClient is null");
        }

        // When connected with FTP service, check state 
        // for different launch modes.
        switch (state) {
            /**
              * 进入这里 state=201
              */
            case BluetoothFtpClient.BT_FTPC_STATE_ACTIVE:
                Log.d(TAG, "[BT][FTP] Enable and "
                    + "connect to FTP server.");
                execOperation(OP_CONNECT, null);
                break;

            case BluetoothFtpClient.BT_FTPC_STATE_AUTHORIZING:
                Log.d(TAG, "[BT][FTP] Connected with FTP "
                    + "service, Authorizing.");
                showProgressDialog(OP_CONNECT);
                break;

            case BluetoothFtpClient.BT_FTPC_STATE_CONNECTED:
                Log.d(TAG, "[BT][FTP] Connected with FTP "
                    + "service, Connected.");
                if (mThread != null) {
                    showProgressDialog(OP_REFRESH);
                    if (mThread.isDone(
                            BluetoothFtpClientActivity.this)) {
                        mThread = null;
                        updateUI();
                    }
                } else {
                    updateUI();
                }
                break;

            case BluetoothFtpClient.BT_FTPC_STATE_SENDING:
                Log.d(TAG, "[BT][FTP] Connected with"
                    + " FTP service, Sending.");
                updateUI();
                prepareTransferringDialog(OP_START_PUSH, true);
                break;

            case BluetoothFtpClient.BT_FTPC_STATE_RECEIVING:
                Log.d(TAG, "[BT][FTP] Connected with"
                    + "FTP service, Receiving.");
                updateUI();
                prepareTransferringDialog(OP_START_PULL, true);
                break;

            case BluetoothFtpClient.BT_FTPC_STATE_TOBROWSE:
                Log.d(TAG, "[BT][FTP] Connected with "
                    + "FTP Service, To refresh");
                showProgressDialog(OP_REFRESH);
                execOperation(OP_REFRESH, null);
                break;

            case BluetoothFtpClient.BT_FTPC_STATE_BROWSING:
                Log.d(TAG, "[BT][FTP] Connected with "
                    + "FTP service, Refreshing.");
                showProgressDialog(OP_REFRESH);
                break;

            case BluetoothFtpClient.BT_FTPC_STATE_BROWSED:
                Log.d(TAG, "[BT][FTP] Connected with "
                    + "FTP service, Start parsing.");
                showProgressDialog(OP_REFRESH);
                parseFolderContent();
                break;

            case BluetoothFtpClient.BT_FTPC_STATE_ABORTING:
                Log.d(TAG, "[BT][FTP] Connected with "
                    + "FTP service, Aborting.");
                updateUI();
                showProgressDialog(OP_ABORT);
                break;

            default:
                break;
        }
    }

    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "[BT][FTP] FTP Service disconnected"
            + " unexpectedly. Finish this activity.");
        mFtpClient = null;
        finish();
    }
};

进入BluetoothFtpClientActivity类中的execOperation()方法。

private void execOperation(int op, String arg) {
    ......
    try {
        switch (op) {
            case OP_REGISTER_CB:
                mFtpClient.registerCallback(mFtpClientCallback);
                break;
            case OP_UNREGISTER_CB:
                mFtpClient.unregisterCallback(mFtpClientCallback);
                break;
            case OP_CONNECT:
                /**
                  * 进入这里
                  * 界面上就显示一个转圈进度条
                  */
                showProgressDialog(op);
                mFtpClient.connect();
                break;
            ......
         }
     }
 }

下面进入BluetoothFtpClientService类中mFtpClient这个变量声明的匿名内部类中。

public void connect() {
    if (mCurrentServer != null 
        && mClientState == BluetoothFtpClient.BT_FTPC_STATE_ACTIVE) {
        printLog("Connect to: " + mCurrentServer.getAddress());
        mClientState = BluetoothFtpClient.BT_FTPC_STATE_AUTHORIZING;
        mBluetoothClient = 
            new BluetoothFtpClientLocal(mCurrentServer, 
                mServiceHandler);
        if (mBluetoothClient != null) {
            mBluetoothClient.connect();
        }
        mServiceHandler.sendMessageDelayed(
            mServiceHandler.obtainMessage(FTPC_CONNECT_TIMEOUT),
            FTPC_CONNECT_TIMEOUT_VALUE);
    } else {
        printErr("Invalid connect request: "
            + "no server device or connected already.");
        postClientEvent(BluetoothFtpClient.BT_FTPCUI_DISCONNECTED,
                BluetoothFtpClient.BT_FTPC_CONNECTION_FAILED);
    }
}

下面进入BluetoothFtpClientLocal类中connect()方法。

public void connect() {
    if (mSessionHandler == null) {
        mSessionHandler = new SessionHandler(this);
    }

    if (mConnectThread == null && mObexSession == null) {
        mConnectionState = ConnectionState.CONNECTING;

        mConnectThread = new SocketConnectThread();
        /**
          *SocketConnectThread类中的run方法。
          */
        mConnectThread.start();
    }
}

SocketConnectThread是定义在BluetoothFtpClientLocal类中的内部类。

public static final UUID FTP_UUID_SECURE = 
    UUID.fromString("00001106-0000-1000-8000-00805f9b34fb");

private class SocketConnectThread extends Thread {
    private BluetoothSocket socket = null;

    public SocketConnectThread() {
        super("SocketConnectThread");
    }

    /**
      * 核心是这里,进行链接。
      */
    @Override
    public void run() {
        try {
            Log.d(TAG, "SocketConnectThread run.");
            socket = mDevice
            .createInsecureRfcommSocketToServiceRecord(
                FTP_UUID_SECURE);
            socket.connect();

            BluetoothFtpRfcommTransport transport;
            transport = new BluetoothFtpRfcommTransport(socket);
             /*
              * SessionHandler是定义
              * 在BluetoothFtpClientLocal中的静态内部类
              */
            mSessionHandler.obtainMessage(SOCKET_CONNECTED,
                transport).sendToTarget();
        } catch (IOException e) {
            Log.e(TAG, "Error when creating/"
                + "connecting socket", e);

            closeSocket();
            mSessionHandler.obtainMessage(SOCKET_ERROR)
                .sendToTarget();
        }
    }

    @Override
    public void interrupt() {
        closeSocket();
    }

    private void closeSocket() {
        try {
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            Log.e(TAG, "Error when closing socket", e);
        }
    }
}

下面进入SessionHandler类中的处理流程。

private static class SessionHandler extends Handler {

    private final WeakReference<BluetoothFtpClientLocal> mClient;

    public SessionHandler(BluetoothFtpClientLocal client) {
        super();
        mClient = new WeakReference<BluetoothFtpClientLocal>(client);
    }

    @Override
    public void handleMessage(Message msg) {

        BluetoothFtpClientLocal client = mClient.get();
        if (client == null) {
            return;
        }

        switch (msg.what) {
            ......
            /**
              * handler收到消息
              */
            case SOCKET_CONNECTED:
                client.mConnectThread = null;

                client.mObexTransport = (ObexTransport) msg.obj;

                client.mObexSession = 
                    new BluetoothFtpObexClientSession(
                        client.mObexTransport,
                        client.mSessionHandler);
                /*
                  * 进入这里
                  */
                client.mObexSession.start();
                break;
            ......
        }
    }
}

下面进入BluetoothFtpObexClientSession类中的start()方法。

public void start() {
    if (mClientThread == null) {
         /*
          * ClientThread是定义
          * 在BluetoothFtpObexClientSession类中的内部类
          */
        mClientThread = new ClientThread(mTransport);
        mClientThread.start();
    }
}

下面进入ClientThread类中的run()方法。

private class ClientThread extends Thread {

    private ClientSession mSession;

    @Override
    public void run() {
        Process.setThreadPriority(
            Process.THREAD_PRIORITY_BACKGROUND);
        //先进行链接
        connect();

         /*
          * 链接成功失败与否都进行发消息通知
          * 这块儿消息的最终处理
          * 在BluetoothFtpClientService的mServiceCallback这个
          * 变量声明的匿名内部类中
          */
        if (mConnected) {
            mSessionHandler.obtainMessage(MSG_OBEX_CONNECTED)
                .sendToTarget();
        } else {
            mSessionHandler.obtainMessage(MSG_OBEX_DISCONNECTED)
                .sendToTarget();
            return;
        }

        while (!mInterrupted) {
            synchronized (this) {
                /**
                  * 如果当前没有请求,那么线程就等待
                  */
                if (mRequest == null) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        mInterrupted = true;
                    }
                }
            }

             /*
              * 如果notify后有请求那就执行具体的某个请求
              * BluetoothFtpRequest是一个抽象类,其具体的实现有:
              *         BluetoothFtpRequestGetList.java
              *         BluetoothFtpRequestPullFile.java
              *         BluetoothFtpRequestSetPath.java
              */
            if (!mInterrupted && mRequest != null) {
                try {
                    mRequest.execute(mSession);
                } catch (IOException e) {
                    // this will "disconnect" to cleanup
                    mInterrupted = true;
                }

                BluetoothFtpRequest oldReq = mRequest;
                mRequest = null;

                mSessionHandler.obtainMessage(
                    MSG_REQUEST_COMPLETED, oldReq).sendToTarget();
            }
        }

        disconnect();

        mSessionHandler.obtainMessage(MSG_OBEX_DISCONNECTED)
            .sendToTarget();
    }

}

下面进入ClientThread类中的connect()方法。

private void connect() {
    try {
        mSession = new ClientSession(mTransport);

        HeaderSet headerset = new HeaderSet();
        /*
          *这一步实质上就是将FTP_TARGET这个byte数组拷贝
          * 到headerset对象中的mTarget byte数组中。
          */
        headerset.setHeader(HeaderSet.TARGET, FTP_TARGET);

        headerset = mSession.connect(headerset);

        if (headerset.getResponseCode() == 
                ResponseCodes.OBEX_HTTP_OK) {
            mConnected = true;
        } else {
            disconnect();
        }
    } catch (IOException e) {
    }
}

至此Client端与Server端的socket通信就建立起来了......

后续请求的处理与执行就在ClientThread的run方法中执行。

每当一个BluetoothFtpRequest的execute()方法执行完以后后会发送一个消息:

mSessionHandler.obtainMessage(MSG_OBEX_DISCONNECTED)
    .sendToTarget(); 

/**
  * BluetoothFtpClientLocal类的SessionHandler中部分代码
  * 下面这些消息最终也还是
  * 被BluetoothFtpClientService中的mServiceCallback匿名
  * 内部类处理
  */
......
case BluetoothFtpObexClientSession.MSG_REQUEST_COMPLETED:
    BluetoothFtpRequest request = (BluetoothFtpRequest) msg.obj;
    int status = request.isSuccess() ? STATUS_OK : STATUS_FAILED;

    Log.v(TAG, "MSG_REQUEST_COMPLETED (" + status + ") for "
            + request.getClass().getName());
    if (request instanceof BluetoothFtpRequestGetList) {
        BluetoothFtpRequestGetList req = 
            (BluetoothFtpRequestGetList) request;
        // ArrayList<String> folders = req.getList();

        client.sendToClient(EVENT_GET_FOLDER_LISTING, 
            request.isSuccess(), null);

    } else if (request instanceof BluetoothFtpRequestSetPath) {
        if (request.isSuccess()) {
            BluetoothFtpRequestSetPath req = 
                (BluetoothFtpRequestSetPath) request;
            switch (req.mDir) {
                case UP:
                    if (client.mPath.size() > 0) {
                        client.mPath.removeLast();
                    }
                    break;
                case ROOT:
                    client.mPath.clear();
                    break;
                case DOWN:
                    client.mPath.addLast(req.mName);
                    break;
            }
        }
        client.sendToClient(EVENT_SET_PATH, request.isSuccess(),
                client.getCurrentPath());
    } else if (request instanceof BluetoothFtpRequestPullFile) {
        BluetoothFtpRequestPullFile req = 
            (BluetoothFtpRequestPullFile) request;
        client.sendToClient(EVENT_PULL_FILE_FINISHED, 
            request.isSuccess(), null);
    }
    break;
......

后续流程下次再分析......