Android T 蓝牙传输文件流程(一)

代码使用的是Android T。首先我们知道蓝牙传输文件属于OPP,
代码位置:http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/

比如现在在图库中选中一张图片,点击“分享”然后选择蓝牙的方式,此时就会启动:BluetoothOppLauncherActivity.java
http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
BluetoothOppLauncherActivity.java#onCreate
这里面主要理清楚发送单个文件和多个文件即可,这里我们先看单个文件:

// 单个文件:Intent.ACTION_SEND
if (action.equals(Intent.ACTION_SEND)) {
    // type 就是类型,比如是图片,还是视频
    final String type = intent.getType();
    // 1. stream != null && type != null : 文件类型
	// 2. extraText != null && type != null : 文本类型
    final Uri stream = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
    CharSequence extraText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
    
    if (stream != null && type != null) {
    // Get ACTION_SEND intent: Uri = content://0@media/external/images/media/1125; mimetype = image/jpeg
     Log.v(TAG, "Get ACTION_SEND intent: Uri = " + stream + "; mimetype = " + type);
        // Save type/stream, will be used when adding transfer session to DB.
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                sendFileInfo(type, stream.toString(), false /* isHandover */, true /* fromExternal */);
            }
        });
        t.start();
        return;
    }
    
private void sendFileInfo(String mimeType, String uriString, boolean isHandover, boolean fromExternal) {
    BluetoothOppManager manager = BluetoothOppManager.getInstance(getApplicationContext());
    try {
        manager.saveSendingFileInfo(mimeType, uriString, isHandover, fromExternal);  ---> 先把信息存起来
        launchDevicePicker();   ---> 这里面判断一些蓝牙是否打开以及打开了处理的后续
        finish();
    } catch (IllegalArgumentException exception) {
        showToast(exception.getMessage());
        finish();
    }
}

接着上面的,先看下发送前的存储过程:
http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
BluetoothOppManager.java#saveSendingFileInfo

public void saveSendingFileInfo(String mimeType, String uriString, boolean isHandover, boolean fromExternal) throws 		    	    IllegalArgumentException {
    synchronized (BluetoothOppManager.this) {
        mMultipleFlag = false;    ---> 单个文件标志位
        mMimeTypeOfSendingFile = mimeType;   ---> 文件类型(图片、视频还是音乐类型)
        mIsHandoverInitiated = isHandover;   ---> 此次文件传输是否是由NFC发起,应为nfc传输文件实际上走的也是蓝牙opp传输
        Uri uri = Uri.parse(uriString);    ---> 路径转换成Uri 
        //根据信息作一些判断,最终变成BluetoothOppSendFileInfo
        BluetoothOppSendFileInfo sendFileInfo = BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType, fromExternal);
        // sendFileInfo 和 uri 对应起来
        uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
        // 放到ConcurrentHashMap sSendFileMap中
        BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
        mUriOfSendingFile = uri.toString();
        // 用SharedPreference存储
        storeApplicationData();
    }
}

信息存储看完了,在看launchDevicePicker()方法做了啥
http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
BluetoothOppLauncherActivity.java#launchDevicePicker

private void launchDevicePicker() {
    // TODO: In the future, we may send intent to DevicePickerActivity directly,
    // and let DevicePickerActivity to handle Bluetooth Enable.
    if (!BluetoothOppManager.getInstance(this).isEnabled()) {
        if (V) {
            Log.v(TAG, "Prepare Enable BT!! ");
        }
        // 蓝牙如果没有打开,就转到 BluetoothOppBtEnableActivity 类里面
        Intent in = new Intent(this, BluetoothOppBtEnableActivity.class);
        in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(in);
    } else {
        if (V) {
            Log.v(TAG, "BT already enabled!! ");
        }
        // 蓝牙打开就转到 BluetoothDevicePicker 类里面
        Intent in1 = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
        in1.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
        in1.putExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
        in1.putExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE, BluetoothDevicePicker.FILTER_TYPE_TRANSFER);
        in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE, getPackageName());
        in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS, BluetoothOppReceiver.class.getName());
        if (V) {
            Log.d(TAG, "Launching " + BluetoothDevicePicker.ACTION_LAUNCH);
        }
        startActivity(in1);
    }
}

继续看蓝牙未打开时候的场景
packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
    // Set up the "dialog"
    mOppManager = BluetoothOppManager.getInstance(this);
    mOppManager.mSendingFlag = false;

    mAlertBuilder.setIconAttribute(android.R.attr.alertDialogIcon);
    mAlertBuilder.setTitle(getString(R.string.bt_enable_title));
    mAlertBuilder.setView(createView());
    // 弹框询问是否打开 蓝牙,这里我们点击Ok
    mAlertBuilder.setPositiveButton(R.string.bt_enable_ok,   (dialog, which) -> onEnableBluetooth());
    mAlertBuilder.setNegativeButton(R.string.bt_enable_cancel, (dialog, which) -> finish());
    setupAlert();
}
// 点击Ok确认后,执行 onEnableBluetooth方法:
private void onEnableBluetooth {
	// 打开蓝牙的操作
    mOppManager.enableBluetooth(); // this is an asyn call
    mOppManager.mSendingFlag = true;   ---> 标志位,表明发送文件
    Toast.makeText(this, getString(R.string.enabling_progress_content), Toast.LENGTH_SHORT).show();
	// 启动 BluetoothOppBtEnablingActivity
    Intent in = new Intent(this, BluetoothOppBtEnablingActivity.class);
    in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    this.startActivity(in);
    finish();
}

继续往下看,启动BluetoothOppBtEnablingActivity
http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
BluetoothOppBtEnablingActivity.java

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
	// 表明窗口不被覆盖
    getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
    // If BT is already enabled jus return.
    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    if (adapter.isEnabled()) {   ---> 判断现在的状态是不是 BluetoothAdapter.STATE_ON
        finish();
        return;
    }
    //前面已经有打开蓝牙的操作了,这里注册一个蓝牙状态变化
    IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
    registerReceiver(mBluetoothReceiver, filter);
    mRegistered = true;
    mAlertBuilder.setTitle(R.string.enabling_progress_title);
    mAlertBuilder.setView(createView());
    setupAlert();
    // Add timeout for enabling progress 这里超时是20s
    mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(BT_ENABLING_TIMEOUT), BT_ENABLING_TIMEOUT_VALUE);
}

至此,我们就干了两件事:1. 把要发送的图片信息存下来,原因是如果蓝牙服务有问题,恢复了还可以继续;2. 打开蓝牙
上面我们看了蓝牙未打开的分支,现在看下打开后的分支,打开过就转到这里 BluetoothDevicePicker.ACTION_LAUNCH,注意,我们上面看到的都是转到什么class类里面,这里ACTION_LAUNCH是什么呢?我们需要看下代码里面的定义:http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/framework/java/android/bluetooth/BluetoothDevicePicker.java#ACTION_LAUNCHAndroid T 蓝牙传输文件流程(一)_第1张图片
ACTION_LAUNCH = “android.bluetooth.devicepicker.action.LAUNCH”; 对应的声明在这里:
Android T 蓝牙传输文件流程(一)_第2张图片
对应的 DevicePickerActivity 其实就是加载一个 Fragment,这个对应的就是:DevicePickerFragment.java
Android T 蓝牙传输文件流程(一)_第3张图片
DevicePickerFragment 类,有选择设备发送的操作,然后发送BluetoothDevicePicker.ACTION_DEVICE_SELECTED广播
具体的可以自己看下:http://aospxref.com/android-13.0.0_r3/xref/packages/apps/Settings/src/com/android/settings/bluetooth/
DevicePickerFragment.java#onDevicePreferenceClick

处理这个广播的逻辑在:http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android
/bluetooth/opp/BluetoothOppReceiver.java

public void onReceive(Context context, Intent intent) {
   String action = intent.getAction();
   if (D) Log.d(TAG, " action :" + action);
   if (action == null) return;
   if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) {   ---> 选中的蓝牙设备
       BluetoothOppManager mOppManager = BluetoothOppManager.getInstance(context);
       // 选中蓝牙的信息mac
       BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
	Log.d(TAG, "Received BT device selected intent, bt device: " + remoteDevice);
       if (remoteDevice == null) {
           mOppManager.cleanUpSendingFileInfo();
           return;
       }
       // Insert transfer session record to database
       // 这里面注意最大是3个同时发,多了就会报错,具体的看代码
       mOppManager.startTransfer(remoteDevice);

       // Display toast message
       String deviceName = mOppManager.getDeviceName(remoteDevice);
       String toastMsg;
       int batchSize = mOppManager.getBatchSize();
       if (mOppManager.mMultipleFlag) {
           toastMsg = context.getString(R.string.bt_toast_5, Integer.toString(batchSize), deviceName);
       } else {
           toastMsg = context.getString(R.string.bt_toast_4, deviceName);
       }
       Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show();
   }

mOppManager.startTransfer(remoteDevice); 这个方法里,我们为了追流程,只看和流程相关的:
http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
BluetoothOppManager.java#414
这里是发送单个图片,直接看insertSingleShare方法:

private void insertSingleShare() {
    ContentValues values = new ContentValues();
    values.put(BluetoothShare.URI, mUri);
    values.put(BluetoothShare.MIMETYPE, mTypeOfSingleFile);
    values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress());
    if (mIsHandoverInitiated) {
        values.put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
    }
    // 主要看下这个往数据库写入的操作 CONTENT_URI = Uri.parse("content://com.android.bluetooth.opp/btopp")
    final Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
    Log.v(TAG, "Insert contentUri: " + contentUri + "  to device: " + getDeviceName(mRemoteDevice));
    }
}

mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values); 数据库的操作,最终操作的是BluetoothOppProvider,不清楚的可以去看下Android 数据库操作这块。
http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/
BluetoothOppProvider.java#insert

public Uri insert(Uri uri, ContentValues values) {
    SQLiteDatabase db = mOpenHelper.getWritableDatabase();

    if (sURIMatcher.match(uri) != SHARES) {
        throw new IllegalArgumentException("insert: Unknown/Invalid URI " + uri);
    }

    ContentValues filteredValues = new ContentValues();
    
    copyString(BluetoothShare.URI, values, filteredValues);
    copyString(BluetoothShare.FILENAME_HINT, values, filteredValues);
    copyString(BluetoothShare.MIMETYPE, values, filteredValues);
    copyString(BluetoothShare.DESTINATION, values, filteredValues);

    copyInteger(BluetoothShare.VISIBILITY, values, filteredValues);
    copyLong(BluetoothShare.TOTAL_BYTES, values, filteredValues);
    if (values.getAsInteger(BluetoothShare.VISIBILITY) == null) {
        filteredValues.put(BluetoothShare.VISIBILITY, BluetoothShare.VISIBILITY_VISIBLE);
    }
    Integer dir = values.getAsInteger(BluetoothShare.DIRECTION);
    Integer con = values.getAsInteger(BluetoothShare.USER_CONFIRMATION);

    if (dir == null) {
        dir = BluetoothShare.DIRECTION_OUTBOUND;
    }
    if (dir == BluetoothShare.DIRECTION_OUTBOUND && con == null) {
        con = BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED;
    }
    if (dir == BluetoothShare.DIRECTION_INBOUND && con == null) {
        con = BluetoothShare.USER_CONFIRMATION_PENDING;
    }
    filteredValues.put(BluetoothShare.USER_CONFIRMATION, con);
    filteredValues.put(BluetoothShare.DIRECTION, dir);

    filteredValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_PENDING);
    filteredValues.put(Constants.MEDIA_SCANNED, 0);

    Long ts = values.getAsLong(BluetoothShare.TIMESTAMP);
    if (ts == null) {
        ts = System.currentTimeMillis();
    }
    filteredValues.put(BluetoothShare.TIMESTAMP, ts);

    Context context = getContext();
    long rowID = db.insert(DB_TABLE, null, filteredValues);

    if (rowID == -1) {
        Log.w(TAG, "couldn't insert " + uri + "into btopp database");
        return null;
    }

    context.getContentResolver().notifyChange(uri, null);
    return Uri.parse(BluetoothShare.CONTENT_URI + "/" + rowID);
}

现在数据库发生了变化,那监听数据库的地方就会收到,此时我们可以知道 BluetoothOppService 类中有监听的地方,下面代码看下:

// 注册 ContentObserver
mObserver = new BluetoothShareContentObserver();
getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);

// 变换后调用其 onChange 方法:
private class BluetoothShareContentObserver extends ContentObserver {
    BluetoothShareContentObserver() {
        super(new Handler());
    }
    @Override
    public void onChange(boolean selfChange) {
        if (V) {
            Log.v(TAG, "ContentObserver received notification");
        }
        updateFromProvider();  ---> 执行这个
    }
}

private void updateFromProvider() {
    synchronized (BluetoothOppService.this) {
        mPendingUpdate = true;
        if (mUpdateThread == null) {
            mUpdateThread = new UpdateThread();
            mUpdateThread.start();   ---> 启动
            mUpdateThreadRunning = true;
        }
    }
}
// 对应的线程run方法:
public void run() {
	// 设置线程优先级为后台线程
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    
 	// 检查是否存在多个 UpdateThread 实例,如果存在则抛出异常
    while (!mIsInterrupted) {
        synchronized (BluetoothOppService.this) {
            if (mUpdateThread != this) {
                mUpdateThreadRunning = false;
                throw new IllegalStateException("multiple UpdateThreads in BluetoothOppService");
            }
           Log.v(TAG, "pendingUpdate is " + mPendingUpdate + " sListenStarted is "
                        + mListenStarted + " isInterrupted :" + mIsInterrupted);
             
             // 如果没有待处理的更新任务,则将 UpdateThread 置为 null,结束线程的执行
            if (!mPendingUpdate) {
                mUpdateThread = null;
                mUpdateThreadRunning = false;
                return;
            }
            mPendingUpdate = false;
        }
   		 
   		// 从内容解析器查询蓝牙共享数据的游标,如果游标为空,则结束线程的执行
        Cursor cursor = getContentResolver().query(BluetoothShare.CONTENT_URI, null, null, null, BluetoothShare._ID);
        if (cursor == null) {
            mUpdateThreadRunning = false;
            return;
        }
		
		// 将游标移动到第一行
        cursor.moveToFirst();
        int arrayPos = 0;
        // 检查游标是否已经移动到最后一行之后
        boolean isAfterLast = cursor.isAfterLast();
       
		// 获取游标中 ID 列的索引
        int idColumn = cursor.getColumnIndexOrThrow(BluetoothShare._ID); 
        /*
         * 遍历游标和本地数组,使它们保持同步。算法的关键在于 ID 在游标和数组中都是唯一且排序的,
         * 因此可以同时按顺序处理这两个来源中的最小 ID:每一步,两个来源都指向尚未从该来源处理的最小 ID,
         * 算法会处理这两个可能性中的最小 ID。每一步操作如下:
         * - 如果数组中包含游标中不存在的条目,则删除该条目,移至数组的下一个条目。
         * - 如果数组中包含游标中存在的条目,则无需操作,移至下一个游标行和下一个数组条目。
         * - 如果游标中包含数组中不存在的条目,则在数组中插入一个新条目,移至下一个游标行和下一个数组条目。
         */
        while (!isAfterLast || arrayPos < mShares.size() && mListenStarted) {
            if (isAfterLast) {
                // 当游标已经超出范围但本地数组中仍有一些内容时,这些内容只能是无用的
                if (mShares.size() != 0) {
                    if (V) {
                        Log.v(TAG, "Array update: trimming " + mShares.get(arrayPos).mId
                                + " @ " + arrayPos);
                    }
                }
                deleteShare(arrayPos); // 删除该位置的共享条目,继续下一个位置的处理
            } else {
                int id = cursor.getInt(idColumn);
                if (arrayPos == mShares.size()) {
                    insertShare(cursor, arrayPos);   ---> 执行这个
                    if (V) {
                        Log.v(TAG, "Array update: inserting " + id + " @ " + arrayPos);
                    }
                    ++arrayPos;
                    cursor.moveToNext();
                    isAfterLast = cursor.isAfterLast();
                } else {
                    int arrayId = 0;
                    if (mShares.size() != 0) {
                        arrayId = mShares.get(arrayPos).mId;
                    }
                    if (arrayId < id) {
                        if (V) {
                            Log.v(TAG, "Array update: removing " + arrayId + " @ " + arrayPos);
                        }
                        deleteShare(arrayPos);
                    } else if (arrayId == id) {
                        // 该游标行已存在于存储的数组中
                        updateShare(cursor, arrayPos);
                        scanFileIfNeeded(arrayPos);
                        ++arrayPos;
                        cursor.moveToNext();
                        isAfterLast = cursor.isAfterLast();
                    } else {
                        // 该游标条目在存储的数组中不存在
                        if (V) {
                            Log.v(TAG, "Array update: appending " + id + " @ " + arrayPos);
                        }
                        insertShare(cursor, arrayPos);

                        ++arrayPos;
                        cursor.moveToNext();
                        isAfterLast = cursor.isAfterLast();
                    }
                }
            }
        }

        mNotifier.updateNotification();
        cursor.close();
    }
    mUpdateThreadRunning = false;
}

private void insertShare(Cursor cursor, int arrayPos) {
    String uriString = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.URI));
    Uri uri;
    if (uriString != null) {
        uri = Uri.parse(uriString);
        Log.d(TAG, "insertShare parsed URI: " + uri);
    } else {
        uri = null;
        Log.e(TAG, "insertShare found null URI at cursor!");
    }
    BluetoothOppShareInfo info = new BluetoothOppShareInfo(
            cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)), uri,
            cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)),
            cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare._DATA)),
            cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.MIMETYPE)),
            cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION)),
            cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.DESTINATION)),
            cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.VISIBILITY)),
            cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION)),
            cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS)),
            cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES)),
            cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES)),
            cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)),
            cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED))
                    != Constants.MEDIA_SCANNED_NOT_SCANNED);

    if (V) {
        Log.v(TAG, "Service adding new entry");
        Log.v(TAG, "ID      : " + info.mId);
        // Log.v(TAG, "URI     : " + ((info.mUri != null) ? "yes" : "no"));
        Log.v(TAG, "URI     : " + info.mUri);
        Log.v(TAG, "HINT    : " + info.mHint);
        Log.v(TAG, "FILENAME: " + info.mFilename);
        Log.v(TAG, "MIMETYPE: " + info.mMimetype);
        Log.v(TAG, "DIRECTION: " + info.mDirection);
        Log.v(TAG, "DESTINAT: " + info.mDestination);
        Log.v(TAG, "VISIBILI: " + info.mVisibility);
        Log.v(TAG, "CONFIRM : " + info.mConfirm);
        Log.v(TAG, "STATUS  : " + info.mStatus);
        Log.v(TAG, "TOTAL   : " + info.mTotalBytes);
        Log.v(TAG, "CURRENT : " + info.mCurrentBytes);
        Log.v(TAG, "TIMESTAMP : " + info.mTimestamp);
        Log.v(TAG, "SCANNED : " + info.mMediaScanned);
    }

    mShares.add(arrayPos, info);
    /* Mark the info as failed if it's in invalid status */
    if (info.isObsolete()) {
        Constants.updateShareStatus(this, info.mId, BluetoothShare.STATUS_UNKNOWN_ERROR);
    }
    /*
     * Add info into a batch. The logic is
     * 1) Only add valid and readyToStart info
     * 2) If there is no batch, create a batch and insert this transfer into batch,
     * then run the batch
     * 3) If there is existing batch and timestamp match, insert transfer into batch
     * 4) If there is existing batch and timestamp does not match, create a new batch and
     * put in queue
     */
	// BluetoothShare.DIRECTION_OUTBOUND 指的是发送
	//  BluetoothShare.DIRECTION_INBOUND 指的是接收
    if (info.isReadyToStart()) {
        if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
            /* check if the file exists */
            BluetoothOppSendFileInfo sendFileInfo = BluetoothOppUtility.getSendFileInfo(info.mUri);
            if (sendFileInfo == null || sendFileInfo.mInputStream == null) {
                Log.e(TAG, "Can't open file for OUTBOUND info " + info.mId);
                Constants.updateShareStatus(this, info.mId, BluetoothShare.STATUS_BAD_REQUEST);
                BluetoothOppUtility.closeSendFileInfo(info.mUri);
                return;
            }
        }
        if (mBatches.size() == 0) {
            BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info);
            newBatch.mId = mBatchId;
            mBatchId++;
            mBatches.add(newBatch);
            if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
                if (V) {
                    Log.v(TAG,
                            "Service create new Batch " + newBatch.mId + " for OUTBOUND info "
                                    + info.mId);
                }
                mTransfer = new BluetoothOppTransfer(this, newBatch);
            } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
                if (V) {
                    Log.v(TAG, "Service create new Batch " + newBatch.mId + " for INBOUND info "
                            + info.mId);
                }
                mServerTransfer = new BluetoothOppTransfer(this, newBatch, mServerSession);
            }

            if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND && mTransfer != null) {
                if (V) {
                    Log.v(TAG, "Service start transfer new Batch " + newBatch.mId + " for info "
                            + info.mId);
                }
                mTransfer.start();    ---> 主要看这个,处理连接对端设备,然后发送等操作
            } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND
                    && mServerTransfer != null) {
                if (V) {
                    Log.v(TAG, "Service start server transfer new Batch " + newBatch.mId
                            + " for info " + info.mId);
                }
                mServerTransfer.start();
            }

        } else {
            int i = findBatchWithTimeStamp(info.mTimestamp);
            if (i != -1) {
                if (V) {
                    Log.v(TAG, "Service add info " + info.mId + " to existing batch " + mBatches
                            .get(i).mId);
                }
                mBatches.get(i).addShare(info);
            } else {
                // There is ongoing batch
                BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info);
                newBatch.mId = mBatchId;
                mBatchId++;
                mBatches.add(newBatch);
                if (V) {
                    Log.v(TAG,
                            "Service add new Batch " + newBatch.mId + " for info " + info.mId);
                }
            }
        }
    }
}

mTransfer.start(); 其实就是调用BluetoothOppTransfer里面的strart方法,下篇文章在继续记录吧,这篇到此为止,我们总结下这篇,首先在BluetoothOppLauncherActivity里面会判断是发送单文件还是多文件,然后调用sendFileInfo方法,这个方法里面做了两件事,一是保存发送的信息,二是根据蓝牙是否打开来执行相应的逻辑:
若蓝牙未打开,执行BluetoothOppBtEnableActivity弹框询问是否打开,用户同意就在到BluetoothOppBtEnablingActivity处理一些逻辑。
若蓝牙已经打开,执行BluetoothDevicePicker.ACTION_LAUNCH(android.bluetooth.devicepicker.action.LAUNCH)加载DevicePickerFragment显示扫描到的蓝牙设备供用户选择要发送给哪个
用户选择某个设备后,在BluetoothOppReceiver类里就处理上面用户选择某设备后发送的BluetoothDevicePicker.ACTION_DEVICE_SELECTED广播事件,主要就是调用BluetoothOppManager里面的 mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);往数据库里面写入,根据Android数据库的知识,我们就知道实际上是操作BluetoothOppProvider.java#insert方法,当数据库uri发生变化是,就会回调相应的ContentObserver,这里是BluetoothShareContentObserver,然后执行onChange,然后起一个UpdateThread查询并insertShare。
那么我们根据几个关键点的log就可以验证上面的这段总结:

// 发送的是图片
BluetoothOppLauncherActivity: Get ACTION_SEND intent: Uri = content://0@media/external/images/media/1125; mimetype = image/jpeg

// saveSendingFileInfo 对应的log
BluetoothOppSendFileInfo: generateFileInfo ++ info.mFilePath = /storage/emulated/0/DCIM/Camera/IMG_20220810_153600.jpg
BluetoothOppUtility: generateUri: content://0@media/external/images/media/1125@897e0f7
BluetoothOppUtility: putSendFileInfo: uri= content://0@media/external/images/media/1125@897e0f7, sendFileInfo= com.android.bluetooth.opp.BluetoothOppSendFileInfo@897e0f7, path= /storage/emulated/0/DCIM/Camera/IMG_20220810_153600.jpg
BluetoothOppUtility: putSendFileInfo: uri=content://0@media/external/images/media/1125@897e0f7, is a new uri, create ArrayList
BluetoothOppManager: Application data stored to SharedPreference! 

// 处理发送的设备广播信息
BluetoothOppReceiver:  action :android.bluetooth.devicepicker.action.DEVICE_SELECTED

// 数据库操作成功后回调相应的 ContentObserver,这里是BluetoothShareContentObserver
BtOppService: ContentObserver received notification

Ok,先到此为止,下篇继续……

你可能感兴趣的:(Bluetooth,android)