Android 蓝牙开发——PBAP协议(十)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

SDK路径frameworks/base/core/java/android/bluetooth/

服务路径packages/apps/Bluetooth/src/com/android/bluetooth/

        在使用协议类的时候无法找到该类由于安卓源码中关于蓝牙协议的 Client 部分或相关接口都被 @hide 给隐藏掉了这样 android.jar 满足不了安卓源码 framework 层开发人员的需求可以使用反射机制或者引用 framework.jar 代替 android.jar。

位置out\target\common\obj\JAVA_LIBRARIES\framework_intermediates\classes.jar

一、SDK 接口

        BluetoothPbapClient.java蓝牙apk中的 PBAP 协议的代理类应用通过此代理类访问 PBAP 协议的方法。

接口名描述
connect连接指定设备
disconnect断开指定设备
getConnectedDevices获取已连接设备列表
getDevicesMatchingConnectionStates获取指定状态的设备列表
getConnectionState获得指定设备的状态
setPriority设置协议的优先级
getPriority获取协议的优先级

二、调用SDK接口

1、获取 BluetoothPbapClient

BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
btAdapter.getProfileProxy(mService, new PbapServiceListener(), BluetoothProfile.PBAP_CLIENT);

2、PbapServiceListener()

private BluetoothPbapClient pbapClient;

class PbapServiceListener implements BluetoothProfile.ServiceListener {
    @Override
    public void onServiceConnected(int profile, BluetoothProfile proxy) {
        synchronized (this) {
            pbapClient = (BluetoothPbapClient) proxy;
        }
    }

    @Override
    public void onServiceDisconnected(int profile) {
        synchronized (this) {
            pbapClient = null;
        }
    }
}

        这里通过回调方法拿到 BluetoothPbapClient 类。通过该类可以调用 SDK 中的各种方法。   

3、源码解析

        getProfileProxy() 在上一篇MAP协议中已经分析这里不再重复分析。

BluetoothPbapClient

private final BluetoothProfileConnector<IBluetoothPbapClient> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.A2DP_SINK, "BluetoothPbapClient", IBluetoothPbapClient.class.getName()) {
    @Override
    public IBluetoothPbapClient getServiceInterface(IBinder service) {
        return IBluetoothPbapClient.Stub.asInterface(Binder.allowBlocking(service));
    }
};

/* package */ BluetoothPbapClient(Context context, ServiceListener listener, BluetoothAdapter adapter) {
    mAdapter = adapter;
    mAttributionSource = adapter.getAttributionSource();
    mProfileConnector.connect(context, listener);
}

        这里同样调用 BluetoothProfileConnector 的 connect() 方法只是传入参数不同最后绑定的是 PbapClientService/packages/apps/Bluetooth/src/com/android/bluetooth/pbapclient/同样 BluetoothPbapClient 的 connect() 与上面 BluetoothMapClient 中的 connect() 差不多最后都是调用各自服务的 connect() 方法。 

PbapClientService.connect()

public boolean connect(BluetoothDevice device) {
    if (device == null) {
        throw new IllegalArgumentException("Null device");
    }
    enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
    if (DBG) Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress());
    if (getConnectionPolicy(device) <= BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
        return false;
    }
    synchronized (mPbapClientStateMachineMap) {
        PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
        if (pbapClientStateMachine == null && mPbapClientStateMachineMap.size() < MAXIMUM_DEVICES) {
            pbapClientStateMachine = new PbapClientStateMachine(this, device);
            pbapClientStateMachine.start();
            mPbapClientStateMachineMap.put(device, pbapClientStateMachine);
            return true;
        } else {
            Log.w(TAG, "Received connect request while already connecting/connected.");
            return false;
        }
    }
}

        这里调用PbapClientStateMachine 的 start() 方法启动状态机并将状态机存储到 mPbapClientStateMachineMap 中。

private class PbapBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
            for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) {
                stateMachine.resumeDownload();
            }
        }
    }
}

        可以看到当我们收到 ACTION_USER_UNLOCKED 广播时会对存储的状态机取出处理。

PbapClientStateMachine.resumeDownload()

static final int MSG_RESUME_DOWNLOAD = 8;

public void resumeDownload() {
    sendMessage(MSG_RESUME_DOWNLOAD);
}

class Connected extends State {
    @Override
    public void enter() {
        @Override
        public boolean processMessage(Message message) {
        switch (message.what) {
            case MSG_RESUME_DOWNLOAD:
                mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD).sendToTarget();
                break;
        }
        return HANDLED;
    }
}

 PbapClientConnectionHandler.handleMessage()

@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
        case MSG_DOWNLOAD:
            mAccountCreated = addAccount(mAccount);
            if (!mAccountCreated) {
                Log.e(TAG, "Account creation failed.");
                return;
            }
            if (isRepositorySupported(SUPPORTED_REPOSITORIES_FAVORITES)) {
                downloadContacts(FAV_PATH);
            }
            if (isRepositorySupported(SUPPORTED_REPOSITORIES_LOCALPHONEBOOK)) {
                downloadContacts(PB_PATH);
            }
            if (isRepositorySupported(SUPPORTED_REPOSITORIES_SIMCARD)) {
                downloadContacts(SIM_PB_PATH);
            }

            HashMap<String, Integer> callCounter = new HashMap<>();
            downloadCallLog(MCH_PATH, callCounter);
            downloadCallLog(ICH_PATH, callCounter);
            downloadCallLog(OCH_PATH, callCounter);
            break;

    }
    return;
}

downloadContacts(String path)下载联系人

downloadCallLog(String path, HashMap<String, Integer> callCounter)下载通话记录

相关路径

PB_PATH = "telecom/pb.vcf"; // 本地主电话簿路径
FAV_PATH = "telecom/fav.vcf"; //个人收藏电话簿路径
MCH_PATH = "telecom/mch.vcf"; // 本地未接来电路径
ICH_PATH = "telecom/ich.vcf"; // 本地来电路径
OCH_PATH = "telecom/och.vcf"; // 本地呼出路径
SIM_PB_PATH = "SIM1/telecom/pb.vcf"; // SIM卡主电话簿路径
SIM_MCH_PATH = "SIM1/telecom/mch.vcf"; // SIM卡本地未接来电路径
SIM_ICH_PATH = "SIM1/telecom/ich.vcf"; // SIM卡本地来电路径
SIM_OCH_PATH = "SIM1/telecom/och.vcf"; // SIM卡本地呼出路径

void downloadContacts(String path) {
    try {
        PhonebookPullRequest processor = new PhonebookPullRequest(mPbapClientStateMachine.getContext(), mAccount);
        //批量下载大小为DEFAULT_BATCH_SIZE的联系人
        BluetoothPbapRequestPullPhoneBookSize requestPbSize = new BluetoothPbapRequestPullPhoneBookSize(path, PBAP_REQUESTED_FIELDS);
        requestPbSize.execute(mObexSession);
        ......
        while ((numberOfContactsRemaining > 0) && (startOffset <= UPPER_LIMIT)) {
            int numberOfContactsToDownload = Math.min(Math.min(DEFAULT_BATCH_SIZE, numberOfContactsRemaining), UPPER_LIMIT - startOffset + 1);
            BluetoothPbapRequestPullPhoneBook request = new BluetoothPbapRequestPullPhoneBook(path, mAccount, PBAP_REQUESTED_FIELDS, VCARD_TYPE_30, numberOfContactsToDownload, startOffset);
            request.execute(mObexSession);
            ArrayList<VCardEntry> vcards = request.getList();
            ......
            processor.setResults(vcards);
            processor.onPullComplete();
            ......
        }
    } catch (IOException e) {
        Log.w(TAG, "Download contacts failure" + e.toString());
    }
}

void downloadCallLog(String path, HashMap<String, Integer> callCounter) {
    try {
        BluetoothPbapRequestPullPhoneBook request = new BluetoothPbapRequestPullPhoneBook(path, mAccount, 0, VCARD_TYPE_30, 0, 0);
        request.execute(mObexSession);
        CallLogPullRequest processor = new CallLogPullRequest(mPbapClientStateMachine.getContext(), path, callCounter, mAccount);
        processor.setResults(request.getList());
        processor.onPullComplete();
    } catch (IOException e) {
        Log.w(TAG, "Download call log failure");
    }
}

 PhonebookPullRequest.onPullComplete()

        通讯录和收藏联系人数据处理函数拿到数据后写入数据库。

@Override
public void onPullComplete() {
    try {
        ContentResolver contactsProvider = mContext.getContentResolver();
        ArrayList<ContentProviderOperation> insertOperations = new ArrayList<>();
        ArrayList<ContentProviderOperation> currentContactOperations;
        // 将插入操作分组在一起以最大限度地减少进程间通信并改善处理时间。
        for (VCardEntry e : mEntries) {
            if (Thread.currentThread().isInterrupted()) {
                Log.e(TAG, "Interrupted durring insert.");
                break;
            }
            int numberOfOperations = insertOperations.size();
            // 将当前vcard附加到插入操作列表。
            e.constructInsertOperations(contactsProvider, insertOperations);
            if (insertOperations.size() >= MAX_OPS) {
                // 如果超出了插入操作的限制则删除最新的vcard并提交。
                insertOperations.subList(numberOfOperations, insertOperations.size()).clear();
                contactsProvider.applyBatch(ContactsContract.AUTHORITY, insertOperations);
                insertOperations = e.constructInsertOperations(contactsProvider, null);
                if (insertOperations.size() >= MAX_OPS) {
                    // 当前VCard有超过500个属性丢卡。
                    insertOperations.clear();
                }
            }
        }
        if (insertOperations.size() > 0) {
            // Apply any unsubmitted vcards.
            contactsProvider.applyBatch(ContactsContract.AUTHORITY, insertOperations);
            insertOperations.clear();
        }
    } catch (OperationApplicationException | RemoteException | NumberFormatException e) {
        Log.e(TAG, "Got exception: ", e);
    } finally {
        complete = true;
    }
}

        同理CallLogPullRequest.onPullComplete() 对应通话记录的数据存储。这里直到最后我们只看到了一个完成状态的标志并没有找到下载状态的通知及回调函数。所以实际项目中我们可以在 PbapClientConnectionHandler 中增加 notifyDownloadStateChanged() 方法并在 download() 的不同状态通过该方法通知到 PbapClientStateMachine 中。最后通过广播通知到 PbapClientService。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: android