原址
要想通过蓝牙接收文件,首先要打开蓝牙。所以先从打开蓝牙进行分析。
BluetoothOppReceiver在AndroidManifest.xml文件中进行了注册,其中action包括”Android.bluetooth.adapter.action.STATE_CHANGED”,也就是它会监听蓝牙状态的改变。
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)){
if (BluetoothAdapter.STATE_ON == intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
context.startService(new Intent(context, BluetoothOppService.class))
//......
}
}
监听到蓝牙开启,BluetoothOppReceiver会打开服务BluetoothOppService。接着看BluetoothOppService。
mAdapter = BluetoothAdapter.getDefaultAdapter();
mSocketListener = new BluetoothOppRfcommListener(mAdapter);
mObserver = new BluetoothShareContentObserver();
getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);
mNotifier = new BluetoothOppNotification(this);
mNotifier.mNotificationMgr.cancelAll();
mNotifier.updateNotification();
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothReceiver, filter);
synchronized (BluetoothOppService.this) {
if (mAdapter == null) {
Log.w(TAG, "Local BT device is not enabled");
} else {
startListener();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
在BluetoothOppService的onCreate()函数中,创建了BluetoothOppRfcommListener对象和BluetoothOppNotification对象。
BluetoothOppRfcommListener类主要用来创建BluetoothServerSocket,接收其他设备的连接。
BluetoothOppNotification则用来弹状态栏通知,显示发送、接收文件,及发送和接收进度等。
BluetoothOppService中还注册了observer,监测数据库的变化。
注册广播接受者,监听蓝牙状态的改变,开启蓝牙时,调用startSocketListener->mSocketListener.start,创建socket并开始监听其他设备的连接。关闭蓝牙时,调用mSocketListener.stop,关闭BluetoothServerSocket。
接着看startListener();
private void startListener() {
if (!mListenStarted) {
if (mAdapter.isEnabled()) {
mHandler.sendMessage(mHandler.obtainMessage(START_LISTENER));
mListenStarted = true;
}
}
}
handler接收到消息START_LISTENER,则调用
private void startSocketListener() {
mSocketListener.start(mHandler);
}
mSocketListener为创建的BluetoothOppRfcommListener对象,调用其start()方法,携带handler对象。在start()内将此handler存为全局变量,用于之后向BluetoothOppService发送消息。下一步创建线程mSocketAcceptThread ,并开始运行该线程。
public synchronized boolean start(Handler callback) {
if (mSocketAcceptThread == null) {
mCallback = callback;
mSocketAcceptThread = new Thread(TAG) {
public void run() {
if (Constants.USE_TCP_DEBUG) {
} else {
boolean serverOK = true;
for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
try {
mBtServerSocket = mAdapter.listenUsingInsecureRfcommWithServiceRecord("OBEX Object Push", BluetoothUuid.ObexObjectPush.getUuid());
} catch (IOException e1) {
serverOK = false;
}
if (!serverOK) {
synchronized (this) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
mInterrupted = true;
}
}
} else {
break;
}
}
if (!serverOK) {
mInterrupted = true;
}
BluetoothSocket clientSocket;
while (!mInterrupted) {
try {
BluetoothServerSocket sSocket = mBtServerSocket;
if (sSocket ==null) {
mInterrupted = true;
} else {
clientSocket = sSocket.accept();
BluetoothOppRfcommTransport transport = new BluetoothOppRfcommTransport(
clientSocket);
Message msg = Message.obtain();
msg.setTarget(mCallback);
msg.what = MSG_INCOMING_BTOPP_CONNECTION;
msg.obj = transport;
msg.sendToTarget();
}
} catch (IOException e) {
try {
Thread.sleep(500);
} catch (InterruptedException ie) {}
}
}
}
}
};
mInterrupted = false;
if(!Constants.USE_TCP_SIMPLE_SERVER) {
mSocketAcceptThread.start();
}
}
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
mSocketAcceptThread中创建BluetoothServerSocket,创建BluetoothServerSocket可能会失败,这里做的保护措施是进行10次尝试。listenUsingInsecureRfcommWithServiceRecord()函数表示此rfcomm连接是不安全的,所以连接时不会进行配对。
sSocket.accept()是阻塞的,等待远程设备的连接。
当与远程设备连接成功后,发送消息MSG_INCOMING_BTOPP_CONNECTION。mCallback对应BluetoothOppService的mHandler。所以在BluetoothOppService中看是如何处理此消息到。
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case BluetoothOppRfcommListener.MSG_INCOMING_BTOPP_CONNECTION:
ObexTransport transport = (ObexTransport)msg.obj;
if (mBatchs.size() == 0 && mPendingConnection == null) {
createServerSession(transport);
} else {
if (mPendingConnection != null) {
try {
transport.close();
} catch (IOException e) {
Log.e(TAG, "close tranport error");
}
} else if (Constants.USE_TCP_DEBUG && !Constants.USE_TCP_SIMPLE_SERVER) {
} else {
mIncomingRetries = mIncomingRetries + 1;
mPendingConnection = transport;
Message msg1 = Message.obtain(mHandler);
msg1.what = MSG_INCOMING_CONNECTION_RETRY;
mHandler.sendMessageDelayed(msg1, 1000);
}
}
break;
case MSG_INCOMING_CONNECTION_RETRY:
if (mBatchs.size() == 0) {
createServerSession(mPendingConnection);
mIncomingRetries = 0;
mPendingConnection = null;
} else {
if (mIncomingRetries == 20) {
try {
mPendingConnection.close();
} catch (IOException e) {
}
mIncomingRetries = 0;
mPendingConnection = null;
} else {
mIncomingRetries = mIncomingRetries + 1;
Message msg2 = Message.obtain(mHandler);
msg2.what = MSG_INCOMING_CONNECTION_RETRY;
mHandler.sendMessageDelayed(msg2, 1000);
}
}
break;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
createServerSession()函数中创建BluetoothOppObexServerSession,并准备接收文件,preStart()函数中创建serversession(new ServerSession(mTransport, this, null))。创建好serversession后就可以接收client的connect,put请求了。
private void createServerSession(ObexTransport transport) {
mServerSession = new BluetoothOppObexServerSession(this, transport);
mServerSession.preStart();
}
client端socket连接成功后会调用ClientSession.connect()进行obex层的连接。server端回调onConnect函数
public int onConnect(HeaderSet request, HeaderSet reply) {
Long objectCount = null;
try {
byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
if(uuid != null) {
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
}
objectCount = (Long) request.getHeader(HeaderSet.COUNT);
} catch (IOException e) {
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
}
。。。
return ResponseCodes.OBEX_HTTP_OK;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
onConnect函数中会对请求头中的target信息进行判断,没有才返回ok。
client端connect成功后,调用put()函数发送文件详细信息。server端回调onPut。
public int onPut(Operation op) {
HeaderSet request;
String name, mimeType;
Long length;
int obexResponse = ResponseCodes.OBEX_HTTP_OK;
if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED) {
return ResponseCodes.OBEX_HTTP_FORBIDDEN;
}
try {
boolean pre_reject = false;
request = op.getReceivedHeader();
name = (String)request.getHeader(HeaderSet.NAME);
length = (Long)request.getHeader(HeaderSet.LENGTH);
mimeType = (String)request.getHeader(HeaderSet.TYPE);
if (length == 0) {
pre_reject = true;
obexResponse = ResponseCodes.OBEX_HTTP_LENGTH_REQUIRED;
}
if (name == null || name.equals("")) {
pre_reject = true;
obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
if (!pre_reject) {
String extension, type;
int dotIndex = name.lastIndexOf(".");
if (dotIndex < 0 && mimeType == null) {
pre_reject = true;
obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
} else {
extension = name.substring(dotIndex + 1).toLowerCase();
MimeTypeMap map = MimeTypeMap.getSingleton();
type = map.getMimeTypeFromExtension(extension);
if (type != null) {
mimeType = type;
} else {
if (mimeType == null) {
pre_reject = true;
obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;
}
}
if (mimeType != null) {
mimeType = mimeType.toLowerCase();
}
}
}
if (!pre_reject
&& (mimeType == null
|| (!isWhitelisted && !Constants.mimeTypeMatches(mimeType,
Constants.ACCEPTABLE_SHARE_INBOUND_TYPES))
|| Constants.mimeTypeMatches(mimeType,
Constants.UNACCEPTABLE_SHARE_INBOUND_TYPES))) {
pre_reject = true;
obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;
}
if (pre_reject && obexResponse != ResponseCodes.OBEX_HTTP_OK) {
return obexResponse;
}
} catch (IOException e) {
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
ContentValues values = new ContentValues();
values.put(BluetoothShare.FILENAME_HINT, name);
values.put(BluetoothShare.TOTAL_BYTES, length.intValue());
values.put(BluetoothShare.MIMETYPE, mimeType);
values.put(BluetoothShare.DESTINATION, destination);
values.put(BluetoothShare.DIRECTION, BluetoothShare.DIRECTION_INBOUND);
values.put(BluetoothShare.TIMESTAMP, mTimestamp);
boolean needConfirm = true;
if (!mServerBlocking) {
values.put(BluetoothShare.USER_CONFIRMATION,
BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED);
needConfirm = false;
}
if (isWhitelisted) {
values.put(BluetoothShare.USER_CONFIRMATION,
BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
needConfirm = false;
}
Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
mLocalShareInfoId = Integer.parseInt(contentUri.getPathSegments().get(1));
if (needConfirm) {
Intent in = new Intent(BluetoothShare.INCOMING_FILE_CONFIRMATION_REQUEST_ACTION);
in.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
mContext.sendBroadcast(in);
}
synchronized (this) {
if (mWakeLock.isHeld()) {
mPartialWakeLock.acquire();
mWakeLock.release();
}
mServerBlocking = true;
try {
while (mServerBlocking) {
wait(1000);
if (mCallback != null && !mTimeoutMsgSent) {
mCallback.sendMessageDelayed(mCallback
.obtainMessage(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT),
BluetoothOppObexSession.SESSION_TIMEOUT);
mTimeoutMsgSent = true;
}
}
} catch (InterruptedException e) {
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
onPut中对请求的文件信息(文件名、文件长度、文件类型)进行检查。其中文件类型是否可接收在Constants中有定义(ACCEPTABLE_SHARE_INBOUND_TYPES和UNACCEPTABLE_SHARE_INBOUND_TYPES)。
符合规定则插入数据库。(插入数据库后,根据数据内容提示用户有文件接收请求。)
然后阻塞,等待用户的确认,发送延时消息(50s),避免用户没有处理该传入文件请求。
在BluetoothOppObexServerSession的onPut函数中会调用mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);将数据插入数据库。
BluetoothOppProvider.insert()-> startService()-> BluetoothOppService.onStartCommand()->updateFromProvider() ->保证mUpdateThread创建运行(有的话则不再创建)。
在mUpdateThread中会调用根据数据库进行不同的操作,包括:创建文件、显示传入文件确认通知等。在mUpdateThread线程中调用insertShare,插入共享文件到ArrayList mShares中。然后:
mServerTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch,
mServerSession);
mServerTransfer.start();
BluetoothOppTransfer.start() -> startObexSession() -> processCurrentShare() ->mSession.addShare(mCurrentShare);
接着看BluetoothOppObexServerSession中的addShare。
public void addShare(BluetoothOppShareInfo info) {
mInfo = info;
mFileInfo = processShareInfo();
}
private BluetoothOppReceiveFileInfo processShareInfo() {
BluetoothOppReceiveFileInfo fileInfo = BluetoothOppReceiveFileInfo.generateFileInfo(
mContext, mInfo.mId);
return fileInfo;
}
generateFileInfo()中判断是否有sd卡。判断/sdcard/blueooth是否是文件夹,及创建该文件夹是否成功。通过蓝牙接收的文件存储路径就是/sdcard/blueooth目录下。
检查存储空间是否满足。检查文件名和文件类型,生成唯一的文件名(因为可能接收到同名的文件多次则在文件名后,类型名前加“-1”,如:img-1.jpg,img-2.jpg。)
最后创建文件,返回BluetoothOppReceiveFileInfo对象。
public static BluetoothOppReceiveFileInfo generateFileInfo(Context context, int id) {
ContentResolver contentResolver = context.getContentResolver();
Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id);
String filename = null, hint = null, mimeType = null;
long length = 0;
Cursor metadataCursor = contentResolver.query(contentUri, new String[] {
BluetoothShare.FILENAME_HINT, BluetoothShare.TOTAL_BYTES, BluetoothShare.MIMETYPE
}, null, null, null);
if (metadataCursor != null) {
try {
if (metadataCursor.moveToFirst()) {
hint = metadataCursor.getString(0);
length = metadataCursor.getInt(1);
mimeType = metadataCursor.getString(2);
}
} finally {
metadataCursor.close();
}
}
File base = null;
StatFs stat = null;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String root = Environment.getExternalStorageDirectory().getPath();
base = new File(root + Constants.DEFAULT_STORE_SUBDIR);
if (!base.isDirectory() && !base.mkdir()) {
return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
}
stat = new StatFs(base.getPath());
} else {
return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_NO_SDCARD);
}
if (stat.getBlockSize() * ((long)stat.getAvailableBlocks() - 4) < length) {
return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_SDCARD_FULL);
}
filename = choosefilename(hint);
if (filename == null) {
return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
}
String extension = null;
int dotIndex = filename.lastIndexOf(".");
if (dotIndex < 0) {
if (mimeType == null) {
return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
} else {
extension = "";
}
} else {
extension = filename.substring(dotIndex);
filename = filename.substring(0, dotIndex);
}
filename = base.getPath() + File.separator + filename;
String fullfilename = chooseUniquefilename(filename, extension);
if (!safeCanonicalPath(fullfilename)) {
return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
}
if (fullfilename != null) {
try {
new FileOutputStream(fullfilename).close();
int index = fullfilename.lastIndexOf('/') + 1;
if (index > 0) {
String displayName = fullfilename.substring(index);
ContentValues updateValues = new ContentValues();
updateValues.put(BluetoothShare.FILENAME_HINT, displayName);
context.getContentResolver().update(contentUri, updateValues, null, null);
}
return new BluetoothOppReceiveFileInfo(fullfilename, length, new FileOutputStream(fullfilename), 0);
} catch (IOException e) {
return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
}
} else {
return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
BluetoothOppProvider.insert()-> startService()-> BluetoothOppService.onStartCommand()->updateFromProvider() ->保证mUpdateThread创建运行(有的话则不再创建)。mUpdateThread中会调用mNotifer.updateNotification()。
updateNotification()会创建线程NotificationUpdateThread(),并运行该线程。该线程会进行更新notification,包括:
- updateActiveNotification()表示正在传输的notification,用来更新传输或接收进度;
- updateCompletedNotification()用来显示已完成的notification;
- updateIncomingFileConfirmNotification()用来显示传入文件的通知。
主要看一下显示传入文件确认的通知
private void updateIncomingFileConfirmNotification() {
Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null,
WHERE_CONFIRM_PENDING, null, BluetoothShare._ID)
if (cursor == null) {
return
}
for (cursor.moveToFirst()
CharSequence title =
mContext.getText(R.string.incoming_file_confirm_Notification_title)
CharSequence caption = mContext
.getText(R.string.incoming_file_confirm_Notification_caption)
int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID))
long timeStamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP))
Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id)
//创建Notification,设置notification的图标、标志、标题等
Notification n = new Notification()
n.icon = R.drawable.bt_incomming_file_notification
n.flags |= Notification.FLAG_ONLY_ALERT_ONCE
n.flags |= Notification.FLAG_ONGOING_EVENT
n.defaults = Notification.DEFAULT_SOUND
n.tickerText = title
Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM)
intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName())
intent.setDataAndNormalize(contentUri)
n.when = timeStamp
//设置点击此notification时,发送Constants.ACTION_INCOMING_FILE_CONFIRM广播。
n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0,
intent, 0))
intent = new Intent(Constants.ACTION_HIDE)
intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName())
intent.setDataAndNormalize(contentUri)
//设置删除此notification时,发送Constants.ACTION_HIDE广播。
n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0)
mNotificationMgr.notify(id, n)
}
cursor.close()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
一般情况我们接收文件都会去点击该notification,点击该notification会发送Constants.ACTION_INCOMING_FILE_CONFIRM广播。现在使用的手机许多都改成直接弹对话框了,而是直接弹出对话框(直接发送该广播就可以了)。
if (action.equals(Constants.ACTION_INCOMING_FILE_CONFIRM)) {
Uri uri = intent.getData()
Intent in = new Intent(context, BluetoothOppIncomingFileConfirmActivity.class)
in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
in.setDataAndNormalize(uri)
context.startActivity(in)
NotificationManager notMgr = (NotificationManager)context
.getSystemService(Context.NOTIFICATION_SERVICE)
if (notMgr != null) { //清除该notification
notMgr.cancel((int)ContentUris.parseId(intent.getData()))
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
在BluetoothOppReceiver中接收到该广播Constants.ACTION_INCOMING_FILE_CONFIRM,打开BluetoothOppIncomingFileConfirmActivity界面。该界面样式是一个dialog,用来判断是否接收该文件。点击接收或取消后都更新数据库,BluetoothShare.USER_CONFIRMATION字段的值不同,分别为USER_CONFIRMATION_CONFIRMED和USER_CONFIRMATION_DENIED。
BluetoothProvider对应的update方法中调用notifyChange(uri, null)。通知监测数据库变化的observer。
BluetoothOppService检测到数据库的变化->updateFromProvider ->创建UpdateThread线程并运行 - >updateShare()。
如果用户点击了接收或取消,也就是对此请求进行了确认,调用mServerTransfer.setConfirmed()。
BluetoothOppTransfer setConfirmed()函数。在该函数中创建notifyThread 线程,使server session停止阻塞
public void setConfirmed() {
final Thread notifyThread = new Thread("Server Unblock thread") {
public void run() {
synchronized (mSession) {
mSession.unblock();
mSession.notify();
}
}
};
notifyThread.start();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
BluetoothOppObexServerSession的unblock()函数设置mServerBlocking为false。使onPut函数阻塞取消。
onPut阻塞时发送50s延时消息BluetoothOppObexSession.MSG_CONNECT_TIMEOUT。当用户超过时间并没有点击接收文件的notification,或者并没有点击dialog上的接收或取消,看一下是怎么处理的
// 删除传入文件确认通知
NotificationManager nm = (NotificationManager)mContext
.getSystemService(Context.NOTIFICATION_SERVICE)
nm.cancel(mCurrentShare.mId)
//用户确认的界面如果已开启,则dialog提示改为“接受来自‘’的文件时发生超时”,2s后关闭界面。
Intent in = new Intent(BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION)
mContext.sendBroadcast(in)
markShareTimeout(mCurrentShare)
超时后用户没有处理,则会先清除传入文件确认的通知,然后ui超时处理(dialog内容关闭,2s后关闭),如果没有显示用户确认的dialog则不用管。更新数据库,流程和上面点击接收或取消流程类似,都会调到mSession.unblock(),停止onPut函数的阻塞。
onPut函数取消阻塞后,会根据用户确认状态进行接收或拒绝接收处理。接着onPut函数取消阻塞后的内容分析。
mAccepted = mInfo.mConfirm
int status = BluetoothShare.STATUS_SUCCESS
//确认或自动确认(对于多个文件后面的文件)
if (mAccepted == BluetoothShare.USER_CONFIRMATION_CONFIRMED
|| mAccepted == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED
|| mAccepted == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED) {
if (mFileInfo.mFileName == null) { //检查文件名
status = mFileInfo.mStatus
mInfo.mStatus = mFileInfo.mStatus
Constants.updateShareStatus(mContext, mInfo.mId, status)
obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR
}
if (mFileInfo.mFileName != null) {
ContentValues updateValues = new ContentValues()
contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId)
updateValues.put(BluetoothShare._DATA, mFileInfo.mFileName)
updateValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_RUNNING)
mContext.getContentResolver().update(contentUri, updateValues, null, null)
//接收文件
status = receiveFile(mFileInfo, op)
if (status != BluetoothShare.STATUS_SUCCESS) {
obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR
}
Constants.updateShareStatus(mContext, mInfo.mId, status)
}
if (status == BluetoothShare.STATUS_SUCCESS) { //接收成功
Message msg = Message.obtain(mCallback, BluetoothOppObexSession.MSG_SHARE_COMPLETE)
msg.obj = mInfo
msg.sendToTarget()
} else {
if (mCallback != null) {
Message msg = Message.obtain(mCallback,
BluetoothOppObexSession.MSG_SESSION_ERROR)
mInfo.mStatus = status
msg.obj = mInfo
msg.sendToTarget()
}
}
} else if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED
|| mAccepted == BluetoothShare.USER_CONFIRMATION_TIMEOUT) {
//用户拒绝接收,或者超时未进行确认
if (mFileInfo.mFileName != null) {
try {
mFileInfo.mOutputStream.close()
} catch (IOException e) {
}
new File(mFileInfo.mFileName).delete()
}
status = BluetoothShare.STATUS_CANCELED
Constants.updateShareStatus(mContext, mInfo.mId, status)
obexResponse = ResponseCodes.OBEX_HTTP_FORBIDDEN
Message msg = Message.obtain(mCallback)
msg.what = BluetoothOppObexSession.MSG_SHARE_INTERRUPTED
mInfo.mStatus = status
msg.obj = mInfo
msg.sendToTarget()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
首先判断用户是否接收文件。
- 接收文件:检查文件名是否有问题,没有问题,则更新数据看接收状态,开始接收文件。接收文件完成后更新notification。
- 拒绝接收或超时未处理:关闭文件输出流,删除空文件。更新notification。
接着看接收文件的具体代码receiveFile
private int receiveFile(BluetoothOppReceiveFileInfo fileInfo, Operation op) {
int status = -1;
BufferedOutputStream bos = null;
InputStream is = null;
boolean error = false;
try {
is = op.openInputStream();
} catch (IOException e1) {
status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
error = true;
}
Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);
if (!error) {
ContentValues updateValues = new ContentValues();
updateValues.put(BluetoothShare._DATA, fileInfo.mFileName);
mContext.getContentResolver().update(contentUri, updateValues, null, null);
}
int position = 0;
if (!error) {
bos = new BufferedOutputStream(fileInfo.mOutputStream, 0x10000);
}
if (!error) {
int outputBufferSize = op.getMaxPacketSize();
byte[] b = new byte[outputBufferSize];
int readLength = 0;
long timestamp = 0;
try {
while ((!mInterrupted) && (position != fileInfo.mLength)) {
readLength = is.read(b);
if (readLength == -1) {
break;
}
bos.write(b, 0, readLength);
position += readLength;
ContentValues updateValues = new ContentValues();
updateValues.put(BluetoothShare.CURRENT_BYTES, position);
mContext.getContentResolver().update(contentUri, updateValues, null, null);
}
} catch (IOException e1) {
if ("Abort Received".equals(e1.getMessage())) {
status = BluetoothShare.STATUS_CANCELED;
} else {
status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
}
error = true;
}
}
if (mInterrupted) {
status = BluetoothShare.STATUS_CANCELED;
} else {
if (position == fileInfo.mLength) {
status = BluetoothShare.STATUS_SUCCESS;
} else {
if (status == -1) {
status = BluetoothShare.STATUS_UNKNOWN_ERROR;
}
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
}
}
return status;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
receiveFile函数通过流获取到蓝牙数据,并将数据写入本地文件中。更新接收到进度。到此接收文件的具体过程就分析完了。