Android bluetooth介紹(三): 藍牙掃描(scan)設備分析


關鍵詞:藍牙blueZ  A2DP、SINK、sink_connect、sink_disconnect、sink_suspend、sink_resume、sink_is_connected、sink_get_properties、AUDIO、DBUS
版本號:基於android4.2之前版本號 bluez
內核:linux/linux3.08
系統:android/android4.1.3.4
作者:xubin341719(歡迎轉載。請注明作者。請尊重版權謝謝)
歡迎指正錯誤。共同學習、共同進步!!


參考站點:
http://blog.csdn.net/u011960402/article/details/17216563
http://www.cnblogs.com/fityme/archive/2013/04/13/3019471.html socket相關
http://hi.baidu.com/wwwkljoel/item/a35e5745d14e02e6bcf45170 setsockopt

Android bluetooth介紹(一):基本概念及硬件接口
Android bluetooth介紹(二): android 藍牙代碼架構及其uart 到rfcomm流程
Android bluetooth介紹(三): 藍牙掃描(scan)設備分析
Android bluetooth介紹(四): a2dp connect流程分析


一、藍牙掃描經常使用的方法:
         藍牙掃描的能夠通過兩種途徑實現:命令行hciitool掃描;Android界面觸發,通過JNI、DUBS下發命令。


1、  命令行hciitool掃描(這部分通過Linux命令操作,跟android沒有關系)
通過bluez的tool發送掃描命令,如:hcitoool scan
adb shell 下#hcitool  scan掃描結果

Hcitool掃描邏輯例如以下所看到的:

2、Android界面觸發,通過JNI、DUBS下發命令:通過android界面點擊搜索設備

應用掃描觸發邏輯流程:自上而下三種顏色,分別代表應用部分、JNI部分、linux blueZ部分。


二、Hcitool觸發邏輯分析
1、hcitool這部分代碼比較簡單,實現函數
idh.code\external\bluetooth\bluez\tools\hcitool.c代碼大致流程例如以下:

通過所帶的參數,找到cmd_scan,進入hci_inquriy。這個函數中創建一個BTPROTO_HCI的socket,通過ioctlHCINQUIRY向內核讀取數據,保存返回信息。

2、內核層邏輯:
當然IOCTL僅僅是當中一項。
idh.code\kernel\net\bluetooth\ hci_sock.c

static const struct proto_ops hci_sock_ops = {
…………
	.ioctl		= hci_sock_ioctl,
	.poll		= datagram_poll,
	.listen		= sock_no_listen,
…………
};


它的流程就是構造查詢命令,放入命令隊列,調度隊列來發送命令,當中hci_send_frame后面會解說,這里關鍵是命令的發送和數據的收集是分開的。所以它在里面會放棄2s的調度。以此來等待數據的收集。收集的數據放在hdev->inq_cache里面。我們來看看這個數據是怎樣取得的。例如以下圖所看到的:

入口點hci_rx_work前面已經具體分析過了,這里就不說了。它里面會依據不同的事件類型做不同的處理。通常情況下,掃描都是帶信號強度的掃描。所以走的hci_inquiry_result_with_rssi_evt路線,還有其他幾種掃描方式,比方:HCI_EV_INQUIRY_RESULT,HCI_EV_EXTENDED_INQUIRY_RESULT等。處理邏輯都幾乎相同的,里面會hci_inquiry_cache_update來把結果放到hdev->discovery鏈表里面去。供后面的查詢;比方前面調用的inquiry_cache_dump函數就能夠從這個鏈表里面把數據取出來。然后copy到用戶層;
三、Android界面觸發,通過JNI、DUBS下發命令
總體流程例如以下所看到的:

(一)、應用部分:
1、 idh.code\packages\apps\Settings\src\com\android\settings\bluetooth\DeviceListPreferenceFragment.java

  @Override
    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
        Preference preference) {
     	…………
        mLocalAdapter.startScanning(true);
        return true;
      }

2、 idh.code\packages\apps\Settings\src\com\android\settings\bluetooth\LocalBluetoothAdapter.java

 private final BluetoothAdapter mAdapter;
 void startScanning(boolean force) {
        // Only start if we're not already scanning
        if (!mAdapter.isDiscovering()) {
            if (!force) {
                // Don't scan more than frequently than SCAN_EXPIRATION_MS,
                // unless forced
                if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
                    return;
                }
                // If we are playing music, don't scan unless forced.
                A2dpProfile a2dp = mProfileManager.getA2dpProfile();
                if (a2dp != null && a2dp.isA2dpPlaying()) {
                    return;
                }
            }
            if (mAdapter.startDiscovery()) {
                mLastScan = System.currentTimeMillis();
            }
        }
} 

3、idh.code\frameworks\base\core\java\android\bluetooth\BluetoothAdapter.java

    public boolean startDiscovery() {
        if (getState() != STATE_ON) return false;
        try {
            return mService.startDiscovery();
        } catch (RemoteException e) {Log.e(TAG, "", e);}
        return false;
    }

4、JNI函數的調用idh.code\frameworks\base\core\java\android\server\BluetoothService.java

private native boolean startDiscoveryNative();//Native函數聲明
public class BluetoothService extends IBluetooth.Stub {
    private static final String TAG = "BluetoothService";
    private static final boolean DBG = true;
…………
public synchronized boolean startDiscovery() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
        "Need BLUETOOTH_ADMIN permission");
        if (!isEnabledInternal()) return false;
        return startDiscoveryNative();
}
………………
}

(二)、JNI部分:
1、android_server_BluetoothService.cpp中JNI函數的對比表
idh.code\frameworks\base\core\jni\android_server_BluetoothService.cpp

static JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
   ………………
    {"startDiscoveryNative", "()Z", (void*)startDiscoveryNative},
{"stopDiscoveryNative", "()Z", (void *)stopDiscoveryNative}, 
…………
}

2、相應Native函數的實現
這里面有個知識點DBUS,這個后面我們單獨去解說
idh.code\frameworks\base\core\jni\android_server_BluetoothService.cpp

#define BLUEZ_DBUS_BASE_IFC       "org.bluez"
#define DBUS_ADAPTER_IFACE BLUEZ_DBUS_BASE_IFC ".Adapter"//事實上DBUS_ADAPTER_IFACE 也就是 org.bluez.Adapter
static jboolean startDiscoveryNative(JNIEnv *env, jobject object) {

………………
    /* Compose the command */
    msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
                                       get_adapter_path(env, object),
                                       DBUS_ADAPTER_IFACE, "StartDiscovery");
…………
}
Native函數startDiscoveryNative和字符串StartDiscovery相應。

(三)、DBUS部分

1、DBUS相應方法的實現,這里跟JNI部分比較相似,也是用了函數結構體相應關系。
idh.code\external\bluetooth\bluez\src\adapter.c

#define ADAPTER_INTERFACE	"org.bluez.Adapter"
static GDBusMethodTable adapter_methods[] = {
………………
	{ "ReleaseSession",	"",	"",	release_session		},
	{ "StartDiscovery",	"",	"",	adapter_start_discovery },
	{ "StopDiscovery",	"",	"",	adapter_stop_discovery,
						G_DBUS_METHOD_FLAG_ASYNC},
………………
}

字符StartDiscovery又相應C中的實現函數adapter_start_discovery。
2、adapter_start_discovery的實現
idh.code\external\bluetooth\bluez\src\adapter.c

static DBusMessage *adapter_start_discovery(DBusConnection *conn,
						DBusMessage *msg, void *data)
{
…………
	err = start_discovery(adapter);
	if (err < 0 && err != -EINPROGRESS)
		return btd_error_failed(msg, strerror(-err));

done:
	req = create_session(adapter, conn, msg, 0,
				session_owner_exit);

	adapter->disc_sessions = g_slist_append(adapter->disc_sessions, req);

	return dbus_message_new_method_return(msg);
}

3、 start_discovery調用
idh.code\external\bluetooth\bluez\src\adapter.c

const struct btd_adapter_ops *adapter_ops = NULL;
static int start_discovery(struct btd_adapter *adapter)
{
…………
	pending_remote_name_cancel(adapter);
	return adapter_ops->start_discovery(adapter->dev_id);
}

adapter_ops相應結構體btd_adapter_ops中相應函數,例如以下:上面部分就相應到btd_adapter_ops中的hci_ops結構體。


4、btd_adapter_ops中的hci_ops結構體
idh.code\external\bluetooth\bluez\plugins\hciops.c

static struct btd_adapter_ops hci_ops = {
…………
	.set_powered = hciops_set_powered,
	.set_discoverable = hciops_set_discoverable,
	.set_pairable = hciops_set_pairable,
	.set_limited_discoverable = hciops_set_limited_discoverable,
	.start_discovery = hciops_start_discovery,
	.stop_discovery = hciops_stop_discovery,
	………………
	.create_bonding = hciops_create_bonding,
	.cancel_bonding = hciops_cancel_bonding,
	.read_local_oob_data = hciops_read_local_oob_data,
	.add_remote_oob_data = hciops_add_remote_oob_data,
	.remove_remote_oob_data = hciops_remove_remote_oob_data,
	.set_link_timeout = hciops_set_link_timeout,
	.retry_authentication = hciops_retry_authentication,
};

5、hciops_start_discovery函數的實現
idh.code\external\bluetooth\bluez\plugins\hciops.c

static int hciops_start_discovery(int index)
{
	int adapter_type = get_adapter_type(index);

	switch (adapter_type) {
	case BR_EDR_LE:
		return hciops_start_inquiry(index, LENGTH_BR_LE_INQ);
	case BR_EDR: //藍牙芯片為2.1+EDR的
		return hciops_start_inquiry(index, LENGTH_BR_INQ);
	case LE_ONLY:
		return hciops_start_scanning(index, TIMEOUT_LE_SCAN);
	default:
		return -EINVAL;
	}
}

6、hciops_start_inquiry
idh.code\external\bluetooth\bluez\plugins\hciops.c

static int hciops_start_inquiry(int index, uint8_t length)
{
	struct dev_info *dev = &devs[index];
	uint8_t lap[3] = { 0x33, 0x8b, 0x9e };
	inquiry_cp inq_cp;

	DBG("hci%d length %u", index, length);

	memset(&inq_cp, 0, sizeof(inq_cp));
	memcpy(&inq_cp.lap, lap, 3);
	inq_cp.length = length;
	inq_cp.num_rsp = 0x00;

	if (hci_send_cmd(dev->sk, OGF_LINK_CTL,
			OCF_INQUIRY, INQUIRY_CP_SIZE, &inq_cp) < 0)
		return -errno;

	return 0;
}

7、idh.code\external\bluetooth\bluez\lib\hci.c

/* HCI functions that require open device
 * dd - Device descriptor returned by hci_open_dev. */
dd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param)
{
………………
	if (plen) {
		iv[2].iov_base = param;
		iv[2].iov_len  = plen;
		ivn = 3;
	}

	while (writev(dd, iv, ivn) < 0) {//writev這里把數據寫到socket里面。
		if (errno == EAGAIN || errno == EINTR)
			continue;
		return -1;
	}
	return 0;
}

(四)、內核部分:
1、HCI FILTER的設置
HCIsocket的類型為BTPROTO_HCI。上層調用setsockopt的時候。觸發了內核的hci_sock_setsockopt函數的運行。在這里面設置了socket的filter特性,包含包類型,包含事件類型

當上層調用setsockopt(sock, SOL_HCI, HCI_FILTER,&flt, sizeof(flt))時。觸發相應的內核路徑。
idh.code\kernel\net\bluetooth\hci_sock.c

static const struct proto_ops hci_sock_ops = {
	.family		= PF_BLUETOOTH,
	.owner		= THIS_MODULE,
…………
	.shutdown	= sock_no_shutdown,
	.setsockopt	= hci_sock_setsockopt,
	.getsockopt	= hci_sock_getsockopt,
	.connect	= sock_no_connect,
…………
};

idh.code\kernel\net\bluetooth\hci_sock.c

static int hci_sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int len)
{
………………
	case HCI_FILTER:
		{
			struct hci_filter *f = &hci_pi(sk)->filter;

			uf.type_mask = f->type_mask;
			uf.opcode    = f->opcode;
			uf.event_mask[0] = *((u32 *) f->event_mask + 0);
			uf.event_mask[1] = *((u32 *) f->event_mask + 1);
		}
	………………
}

內核這部分就比較統一的數據,通過hci_send_cmd把命令發出去,HCI_FILTER這個地方的處理還沒理解。后面補充


Writev函數通過socket把數據寫下去,經過VFS層,調用到內核空間的sendmsg函數。

(五)、EVENT返回狀態

Controller收到查詢命令后。返回一個命令狀態
1、cmd_status
idh.code\external\bluetooth\bluez\plugins\hciops.c

	switch (eh->evt) {
	case EVT_CMD_STATUS:
		cmd_status(index, ptr);
		break;
static inline void cmd_status(int index, void *ptr)
{
	evt_cmd_status *evt = ptr;
	uint16_t opcode = btohs(evt->opcode);

	if (opcode == cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY))//假設是inquriy做特殊處理;
		cs_inquiry_evt(index, evt->status);
}

2、cs_inquiry_evt的實現 idh.code\external\bluetooth\bluez\plugins\hciops.c

static inline void cs_inquiry_evt(int index, uint8_t status)
{
	if (status) {//錯誤信息
		error("Inquiry Failed with status 0x%02x", status);
		return;
	}

	set_state(index, DISCOV_INQ);//設置狀態為INQ,向上層回復discoverying的property change
}

3、設置不同的DISCOV 狀態 idh.code\external\bluetooth\bluez\plugins\hciops.c

static void set_state(int index, int state)
{
	………………
	switch (dev->discov_state) {
	case DISCOV_HALTED://停止發現;
		if (adapter_get_state(adapter) == STATE_SUSPENDED)
			return;

		if (is_resolvname_enabled() &&
					adapter_has_discov_sessions(adapter))
			adapter_set_state(adapter, STATE_RESOLVNAME);
		else
			adapter_set_state(adapter, STATE_IDLE);
		break;
	case DISCOV_INQ:
	case DISCOV_SCAN://掃描發現;
		adapter_set_state(adapter, STATE_DISCOV);
		break;
	}
}

4、設置adapter的狀態 idh.code\external\bluetooth\bluez\src\adapter.c

idh.code\external\bluetooth\bluez\src\adapter.c
#define ADAPTER_INTERFACE	"org.bluez.Adapter"
void adapter_set_state(struct btd_adapter *adapter, int state)
{
…………
	case STATE_DISCOV:
		discov_active = TRUE;
//向上層回復discovering的property change
		emit_property_changed(connection, path,
					ADAPTER_INTERFACE, "Discovering",
					DBUS_TYPE_BOOLEAN, &discov_active);
		break;
…………
}

emit_property_changed發送PropertyChanged的消息,消息內容為Discovering。通知上層BluetoothEventLoop進行Discovering。


5、emit_property_changed發送Discovering消息的實現 idh.code\external\bluetooth\bluez\src\dbus-common.c
這部分涉及到DBUS內容

dbus_bool_t emit_property_changed(DBusConnection *conn,
					const char *path,
					const char *interface,
					const char *name,
					int type, void *value)
{
	DBusMessage *signal;
	DBusMessageIter iter;
	signal = dbus_message_new_signal(path, interface, "PropertyChanged"); // 創建消息對象並標識路徑 
	if (!signal) {
		error("Unable to allocate new %s.PropertyChanged signal",
				interface);
		return FALSE;
	}
	dbus_message_iter_init_append(signal, &iter);//把信號相相應的參數壓進去
	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name);//申請一個首地址。把前面壓入的參數傳入這個首地址	
append_variant(&iter, type, value);//
	return g_dbus_send_message(conn, signal);//啟動發送調用,並釋放發送相關消息信息
}

6、DBUS消息接收的實現 idh.code\frameworks\base\core\jni\android_server_BluetoothEventLoop.cpp

// Called by dbus during WaitForAndDispatchEventNative()
static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg,
                                      void *data) {
…………
else if (dbus_message_is_signal(msg, "org.bluez.Adapter", "PropertyChanged")) {
        jobjectArray str_array = parse_adapter_property_change(env, msg);//(1)、對收到消息的解析
        if (str_array != NULL) {
            /* Check if bluetoothd has (re)started, if so update the path. */
            jstring property =(jstring) env->GetObjectArrayElement(str_array, 0);
            const char *c_property = env->GetStringUTFChars(property, NULL);
            if (!strncmp(c_property, "Powered", strlen("Powered"))) {
                jstring value =
                    (jstring) env->GetObjectArrayElement(str_array, 1);
                const char *c_value = env->GetStringUTFChars(value, NULL);
                if (!strncmp(c_value, "true", strlen("true")))
                    nat->adapter = get_adapter_path(nat->conn);
                env->ReleaseStringUTFChars(value, c_value);
            }
            env->ReleaseStringUTFChars(property, c_property);

            env->CallVoidMethod(nat->me,
                              method_onPropertyChanged,//(2)、
method_onPropertyChanged NATVIE函數的實現
                              str_array);
        } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
        goto success;
}

(1)、對收到消息的解析 idh.code\frameworks\base\core\jni\android_bluetooth_common.cpp 

jobjectArray parse_adapter_property_change(JNIEnv *env, DBusMessage *msg) {
    return parse_property_change(env, msg, (Properties *) &adapter_properties,
                    sizeof(adapter_properties) / sizeof(Properties));
}

針對org.bluez.Adapter不同的消息類型
idh.code\frameworks\base\core\jni\android_bluetooth_common.cpp

static Properties adapter_properties[] = {
    {"Address", DBUS_TYPE_STRING},
    {"Name", DBUS_TYPE_STRING},
    {"Class", DBUS_TYPE_UINT32},
    {"Powered", DBUS_TYPE_BOOLEAN},
    {"Discoverable", DBUS_TYPE_BOOLEAN},
    {"DiscoverableTimeout", DBUS_TYPE_UINT32},
    {"Pairable", DBUS_TYPE_BOOLEAN},
    {"PairableTimeout", DBUS_TYPE_UINT32},
    {"Discovering", DBUS_TYPE_BOOLEAN},
    {"Devices", DBUS_TYPE_ARRAY},
    {"UUIDs", DBUS_TYPE_ARRAY},
};

(2)、method_onPropertyChanged NATVIE函數的實現 idh.code\frameworks\base\core\jni\android_server_BluetoothEventLoop.cpp

static void classInitNative(JNIEnv* env, jclass clazz) {
    ALOGV("%s", __FUNCTION__);
#ifdef HAVE_BLUETOOTH
    method_onPropertyChanged = env->GetMethodID(clazz, "onPropertyChanged",
                                                "([Ljava/lang/String;)V");
method_onDevicePropertyChanged = env->GetMethodID(clazz,
 "onDevicePropertyChanged","(Ljava/lang/String;[Ljava/lang/String;)V");
…………
}

7、JNI調用onPropertyChanged相應JAVA的實現,在BluetoothEventLoop.java
idh.code\frameworks\base\core\java\android\server\BluetoothEventLoop.java中

   private static native void classInitNative();
/*package*/ void onPropertyChanged(String[] propValues) {
 ………………
        log("Property Changed: " + propValues[0] + " : " + propValues[1]);
        String name = propValues[0];
        if (name.equals("Name")) {//獲取藍牙名字;
   			…………
        } else if (name.equals("Pairable") || name.equals("Discoverable")) {//配對。
           ………………
        } else if (name.equals("Discovering")) {//掃描查詢;
            Intent intent;
            adapterProperties.setProperty(name, propValues[1]);
            if (propValues[1].equals("true")) {
                intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
            } else {
                // Stop the discovery.
                mBluetoothService.cancelDiscovery();
                intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
            }
            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
        } else if (name.equals("Devices") || name.equals("UUIDs")) {//Devices、UUID的獲取;
     	………………
        } else if (name.equals("Powered")) {//藍牙打開、關閉;
            mBluetoothState.sendMessage(BluetoothAdapterStateMachine.POWER_STATE_CHANGED,
                propValues[1].equals("true") ? new Boolean(true) : new Boolean(false));
        } else if (name.equals("DiscoverableTimeout")) {
            adapterProperties.setProperty(name, propValues[1]);
        }
    } 

(1)、看到這份log我們或許會更明確其他功能的由來:
D BluetoothEventLoop: Property Changed: Powered : true
D BluetoothEventLoop: Property Changed: Pairable : true
D BluetoothEventLoop: Property Changed: Class : 5898764
D BluetoothEventLoop: Property Changed: Pairable : true
D BluetoothEventLoop: Property Changed: Discoverable : false
D BluetoothEventLoop: Property Changed: Discovering : true
D BluetoothEventLoop: Property Changed: Discovering : false
D BluetoothEventLoop: Property Changed: Devices : 1
D BluetoothEventLoop: Device property changed: 94:20:53:01:15:90 property: Connected value: true
D BluetoothEventLoop: Device property changed: 94:20:53:01:15:90 property: Paired value: true
D BluetoothEventLoop: Device property changed: 94:20:53:01:15:90 property: UUIDs value: 4

(2)、以下我們重點分析Discovering這部分
idh.code\frameworks\base\core\java\android\server\BluetoothEventLoop.java

else if (name.equals("Discovering")) {
            Intent intent;
            adapterProperties.setProperty(name, propValues[1]);
            if (propValues[1].equals("true")) {//開始掃描
                intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//
            } else {
                // Stop the discovery. //停止掃描
                mBluetoothService.cancelDiscovery();
                intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
            }
            mContext.sendBroadcast(intent, BLUETOOTH_PERM);
        }
這樣就能夠通過broadcast發送ACTION_DISCOVERY_STARTED廣播,注冊的receiver來響應了。

8、ACTION_DISCOVERY_STARTED\ACTION_DISCOVERY_FINISHED的receiver分析
從代碼中我們能夠看到這個action一共同擁有兩個receiver,一個是靜態注冊的BluetoothDiscoveryReceiver,一個是動態注冊是ScanningStateChangedHandler。
(1)、BluetoothDiscoveryReceiver:
這個receiver是在settings中的Androidmanifest中靜態注冊的。用途:主要用於獲取掃描開始和終止的時間。
idh.code\packages\apps\Settings\AndroidManifest.xml

<receiver
      android:name=".bluetooth.BluetoothDiscoveryReceiver">
      <intent-filter>
        <action android:name="android.bluetooth.adapter.action.DISCOVERY_STARTED" />
        <action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED" />
        <category android:name="android.intent.category.DEFAULT" />
       </intent-filter>
</receiver>

1)、ACTION_DISCOVERY_STARTEDACTION_DISCOVERY_FINISHEDAndroidManifest.xml文件的聯系
idh.code\frameworks\base\core\java\android\bluetooth\BluetoothAdapter.java

public final class BluetoothAdapter {
    private static final String TAG = "BluetoothAdapter";
private static final boolean DBG = false;
…………
    public static final String ACTION_DISCOVERY_STARTED =
            "android.bluetooth.adapter.action.DISCOVERY_STARTED";
    public static final String ACTION_DISCOVERY_FINISHED =
            "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
…………
}

2)、BluetoothAdapter,藍牙適配器。直到我們建立bluetoothSocket連接之前。都要不斷操作它。


BluetoothAdapter中的動作常量

ACTION_DISCOVERY_FINISHED

已完畢藍牙搜索

ACTION_DISCOVERY_STARTED

已經開始搜索藍牙設備

ACTION_LOCAL_NAME_CHANGED

更改藍牙的名字

ACTION_REQUEST_DISCOVERABLE

請求能夠被搜索

ACTION_REQUEST_ENABLE

請求啟動藍牙

ACTION_SCAN_MODE_CHANGED

掃描模式已經改變

ACTION_STATE_CHANGED

狀態已改變

ACTION_CONNECTION_STATE_CHANGED

 

3)、收到廣播后函數實現,開始掃描
Main log中顯示的log為DISCOVERY_STARTED
D BluetoothDiscoveryReceiver: Received:android.bluetooth.adapter.action.DISCOVERY_STARTED
HCI log 中:

idh.code\packages\apps\Settings\src\com\android\settings\bluetooth\BluetoothDiscoveryReceiver.java這個文件里就一個函數。還是比簡單

public final class BluetoothDiscoveryReceiver extends BroadcastReceiver {
    private static final String TAG = "BluetoothDiscoveryReceiver";
    private static final boolean DEBUG = Debug.isDebug();

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (DEBUG) Log.d(TAG, "Received: " + action);

        if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED) ||
                action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {    
//共享時間戳。掃描開始和結束的時間。 
   LocalBluetoothPreferences.persistDiscoveringTimestamp(context);
        }
    }
}

ScanningStateChangedHandler的注冊及用途,要用於開始掃描。和掃描顯示界面的控制。


這個receiver是在idh.code\packages\apps\Settings\src\com\android\settings\bluetooth\BluetoothEventManager.java動態注冊的,例如以下:

BluetoothEventManager(LocalBluetoothAdapter adapter,
            CachedBluetoothDeviceManager deviceManager, Context context) {
mLocalAdapter = adapter;
…………
// Bluetooth on/off broadcasts
 addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());

// Discovery broadcastsaddHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
        addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
…………
}

(1)、ScanningStateChangedHandler函數實現例如以下:idh.code\packages\apps\Settings\src\com\android\settings\bluetooth\BluetoothEventManager.java

 private class ScanningStateChangedHandler implements Handler {
        private final boolean mStarted;

        ScanningStateChangedHandler(boolean started) {
            mStarted = started;
        }
        public void onReceive(Context context, Intent intent,
                BluetoothDevice device) {
            synchronized (mCallbacks) {//1)、調用注冊的callback
中的onScanningStateChanged函數。

for (BluetoothCallback callback : mCallbacks) { callback.onScanningStateChanged(mStarted); } } //2)、這個函數就是把上次掃描到設備、和之前的設備做相應處理; mDeviceManager.onScanningStateChanged(mStarted); LocalBluetoothPreferences.persistDiscoveringTimestamp(context); } }

1)、調用注冊的callback中的callback.onScanningStateChanged(mStarted)函數。
idh.code\packages\apps\Settings\src\com\android\settings\bluetooth\DeviceListPreferenceFragment.java

    public void onScanningStateChanged(boolean started) {
        if (started == false) {//《1》、假設掃描結束;
            removeOutOfRangeDevices();
        }
        updateProgressUi(started);// 《2》、UI顯示小圓圈掃描;
}

《1》、假設掃描結束;removeOutOfRangeDevices();
idh.code\packages\apps\Settings\src\com\android\settings\bluetooth\DeviceListPreferenceFragment.java

private void removeOutOfRangeDevices() {
    Collection<CachedBluetoothDevice> cachedDevices =
            mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
    for (CachedBluetoothDevice cachedDevice : cachedDevices) {
         if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE &&
             cachedDevice.isVisible() == false) {
             BluetoothDevicePreference preference = mDevicePreferenceMap.get(cachedDevice);
             if (preference != null) {
                 mDeviceListGroup.removePreference(preference);
             }
             mDevicePreferenceMap.remove(cachedDevice);
          }
     }
}

《2》、UI顯示小圓圈掃描,updateProgressUi(started);例如以下圖所看到的:

idh.code\packages\apps\Settings\src\com\android\settings\bluetooth\DeviceListPreferenceFragment.java

private void updateProgressUi(boolean start) {
    if (mDeviceListGroup instanceof ProgressCategory) {
        ((ProgressCategory) mDeviceListGroup).setProgress(start);
    }
}

2)、這部分的作用,開始掃描,不顯示列表中內容,或把之前列表中沒掃描到的設備清除
mDeviceManager.onScanningStateChanged(mStarted);
idh.code\packages\apps\Settings\src\com\android\settings\bluetooth\CachedBluetoothDevice.java

private void updateProgressUi(boolean start) {
    if (mDeviceListGroup instanceof ProgressCategory) {
        ((ProgressCategory) mDeviceListGroup).setProgress(start);
    }
}
2)、這部分的作用,開始掃描。不顯示列表中內容,或把之前列表中沒掃描到的設備清除
mDeviceManager.onScanningStateChanged(mStarted);
idh.code\packages\apps\Settings\src\com\android\settings\bluetooth\ CachedBluetoothDevice.java
    public synchronized void onScanningStateChanged(boolean started) {
        // If starting a new scan, clear old visibility
        // Iterate in reverse order since devices may be removed.
        //假設開始新的掃描,清除舊的能見設備。迭代反序由於有的設備可能被刪除
        for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
            CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
            if (started) {//假設掃描開始就不顯示;
                cachedDevice.setVisible(false);
            } else {//對掃描的結果作出推斷。假設之前掃描過,這次沒有掃描到,就移除列表。
                if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE &&
                        cachedDevice.isVisible() == false) {
                    mCachedDevices.remove(cachedDevice);
                }
            }
        }
    }
































免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM