昨天收到测试同事提的一个问题单,一个大文件,使用Android Beam分享失败。自己拿到之后,先进行了下测试,真是,找了个Nexus原生机对比一下,怪了,原生机是成功的。那就说明修改原生的问题哪里出错了,好了,开始干活吧。
再进行验证,先把现象搞清楚,首先找测试同事要到他测试的原视频,大小是843M,用这个反复测了多次,一直失败。好了,自己随便录制一段,再进行Beam分享,咦,好奇怪啊,我录制的视频分享成功!!不对,还不能确定,再试,拿测试同事的视频试一下,还是失败,难道是文件的问题?对比文件看了一下,哦,测试同事提供的文件命名中有中文字符。好,那我把自己录的名字改成中文的,再测试一下,也失败,OK了,看来不是文件差别引起的,可能跟命名当中带有的中文字符有关了。这就是一点眉目,再继续试,现在的分享是从文件管理器中进去的,录制的视频也可以直接从图库中找到,那么相同的文件,从图库中进去执行Beam分享,我靠,神奇了,从图库中进去执行分享,同一个文件,而且命名也有中文字符,是成功的!哇,有收获,不错,进一步明确了差异。现在的初步结论应该是容易确定了,如果从图库中进入,那么无论要分享的文件命名当中是否包含中文,都可以成功,而如果从文件管理器进入,执行分享,那么此时的文件名如果有中文,则必现失败。
有了补步的现象,接下来,我们就要对这个现象进一步分析了,差别是在哪里呢?首先来看一下NFC beam分享功能,beam的属于系统提供的功能,所以它的代码位于packages包下面,因为beam功能是对应有分享和接收两侧的,所以这里会对应有send和receive,当我们选中一个文件,点击beam分享时,就会执行BeamManager类的startBeamSend方法,大家从BeamManager类的方法当中也可以看到,既有发送,又有接收,整个类的代码如下:
/**
* Manager for starting and stopping Beam transfers. Prevents more than one transfer from
* happening at a time.
*/
public class BeamManager implements Handler.Callback {
private static final String TAG = "BeamManager";
private static final boolean DBG = false;
private static final String ACTION_WHITELIST_DEVICE =
"android.btopp.intent.action.WHITELIST_DEVICE";
public static final int MSG_BEAM_COMPLETE = 0;
private final Object mLock;
private boolean mBeamInProgress;
private final Handler mCallback;
private NfcService mNfcService;
private static final class Singleton {
public static final BeamManager INSTANCE = new BeamManager();
}
private BeamManager() {
mLock = new Object();
mBeamInProgress = false;
mCallback = new Handler(Looper.getMainLooper(), this);
mNfcService = NfcService.getInstance();
}
public static BeamManager getInstance() {
return Singleton.INSTANCE;
}
public boolean isBeamInProgress() {
synchronized (mLock) {
return mBeamInProgress;
}
}
public boolean startBeamReceive(Context context,
HandoverDataParser.BluetoothHandoverData handoverData) {
synchronized (mLock) {
if (mBeamInProgress) {
return false;
} else {
mBeamInProgress = true;
}
}
BeamTransferRecord transferRecord =
BeamTransferRecord.forBluetoothDevice(
handoverData.device, handoverData.carrierActivating, null);
Intent receiveIntent = new Intent(context.getApplicationContext(),
BeamReceiveService.class);
receiveIntent.putExtra(BeamReceiveService.EXTRA_BEAM_TRANSFER_RECORD, transferRecord);
receiveIntent.putExtra(BeamReceiveService.EXTRA_BEAM_COMPLETE_CALLBACK,
new Messenger(mCallback));
whitelistOppDevice(context, handoverData.device);
context.startServiceAsUser(receiveIntent, UserHandle.CURRENT);
return true;
}
public boolean startBeamSend(Context context,
HandoverDataParser.BluetoothHandoverData outgoingHandoverData,
Uri[] uris, UserHandle userHandle) {
synchronized (mLock) {
if (mBeamInProgress) {
return false;
} else {
mBeamInProgress = true;
}
}
BeamTransferRecord transferRecord = BeamTransferRecord.forBluetoothDevice(
outgoingHandoverData.device, outgoingHandoverData.carrierActivating,
uris);
Intent sendIntent = new Intent(context.getApplicationContext(),
BeamSendService.class);
sendIntent.putExtra(BeamSendService.EXTRA_BEAM_TRANSFER_RECORD, transferRecord);
sendIntent.putExtra(BeamSendService.EXTRA_BEAM_COMPLETE_CALLBACK,
new Messenger(mCallback));
context.startServiceAsUser(sendIntent, userHandle);
return true;
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what == MSG_BEAM_COMPLETE) {
synchronized (mLock) {
mBeamInProgress = false;
}
boolean success = msg.arg1 == 1;
if (success) {
mNfcService.playSound(NfcService.SOUND_END);
}
return true;
}
return false;
}
void whitelistOppDevice(Context context, BluetoothDevice device) {
if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP");
Intent intent = new Intent(ACTION_WHITELIST_DEVICE);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
context.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
}
public class BeamSendService extends Service implements BeamTransferManager.Callback {
private static String TAG = "BeamSendService";
private static boolean DBG = true;
public static String EXTRA_BEAM_TRANSFER_RECORD
= "com.android.nfc.beam.EXTRA_BEAM_TRANSFER_RECORD";
public static final String EXTRA_BEAM_COMPLETE_CALLBACK
= "com.android.nfc.beam.TRANSFER_COMPLETE_CALLBACK";
private BeamTransferManager mTransferManager;
private BeamStatusReceiver mBeamStatusReceiver;
private boolean mBluetoothEnabledByNfc;
private Messenger mCompleteCallback;
private int mStartId;
private final BluetoothAdapter mBluetoothAdapter;
private final BroadcastReceiver mBluetoothStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
handleBluetoothStateChanged(intent);
}
}
};
public BeamSendService() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
@Override
public void onCreate() {
super.onCreate();
// register BT state receiver
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothStateReceiver, filter);
}
@Override
public void onDestroy() {
super.onDestroy();
if (mBeamStatusReceiver != null) {
unregisterReceiver(mBeamStatusReceiver);
}
unregisterReceiver(mBluetoothStateReceiver);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mStartId = startId;
BeamTransferRecord transferRecord;
if (intent == null ||
(transferRecord = intent.getParcelableExtra(EXTRA_BEAM_TRANSFER_RECORD)) == null) {
if (DBG) Log.e(TAG, "No transfer record provided. Stopping.");
stopSelf(startId);
return START_NOT_STICKY;
}
mCompleteCallback = intent.getParcelableExtra(EXTRA_BEAM_COMPLETE_CALLBACK);
if (doTransfer(transferRecord)) {
if (DBG) Log.i(TAG, "Starting outgoing Beam transfer");
return START_STICKY;
} else {
invokeCompleteCallback(false);
stopSelf(startId);
return START_NOT_STICKY;
}
}
boolean doTransfer(BeamTransferRecord transferRecord) {
if (createBeamTransferManager(transferRecord)) {
// register Beam status receiver
mBeamStatusReceiver = new BeamStatusReceiver(this, mTransferManager);
registerReceiver(mBeamStatusReceiver, mBeamStatusReceiver.getIntentFilter(),
BeamStatusReceiver.BEAM_STATUS_PERMISSION, new Handler());
if (transferRecord.dataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) {
if (mBluetoothAdapter.isEnabled()) {
// Start the transfer
mTransferManager.start();
} else {
if (!mBluetoothAdapter.enableNoAutoConnect()) {
Log.e(TAG, "Error enabling Bluetooth.");
mTransferManager = null;
return false;
}
mBluetoothEnabledByNfc = true;
if (DBG) Log.d(TAG, "Queueing out transfer "
+ Integer.toString(transferRecord.id));
}
}
return true;
}
return false;
}
boolean createBeamTransferManager(BeamTransferRecord transferRecord) {
if (mTransferManager != null) {
return false;
}
if (transferRecord.dataLinkType != BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) {
// only support BT
return false;
}
mTransferManager = new BeamTransferManager(this, this, transferRecord, false);
mTransferManager.updateNotification();
return true;
}
private void handleBluetoothStateChanged(Intent intent) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
if (state == BluetoothAdapter.STATE_ON) {
if (mTransferManager != null &&
mTransferManager.mDataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) {
mTransferManager.start();
}
} else if (state == BluetoothAdapter.STATE_OFF) {
mBluetoothEnabledByNfc = false;
}
}
private void invokeCompleteCallback(boolean success) {
if (mCompleteCallback != null) {
try {
Message msg = Message.obtain(null, BeamManager.MSG_BEAM_COMPLETE);
msg.arg1 = success ? 1 : 0;
mCompleteCallback.send(msg);
} catch (RemoteException e) {
Log.e(TAG, "failed to invoke Beam complete callback", e);
}