Android 蓝牙弹框接收传输的文件实现

Android 蓝牙弹框接收传输的文件实现

一、前言

Android 原生系统蓝牙接收是在通知栏显示和操作,但是部分定制设备比如大屏或者盒子设备是没有通知栏的。

如果要接收蓝牙文件就要自己接收蓝牙广播进行弹框提示,大概包括:确认接收,显示接收进度,确认取消/完成接收等弹框和实现,具体修改就要适配系统的蓝牙应用。

本文基于Android13 系统蓝牙应用Bluetooth文件传输Opp部分代码适配进行介绍。

也许你工作中不一定有这个需求,但是安卓屏显开发大概率是有这个需求的,

部分有兴趣的并且有系统源码编译运行条件的,可以尝试修改系统源码试试,

有需求的或者有兴趣的可以了解。

二、修改效果

1、确认接收

在这里插入图片描述

这里是中间弹框提示是否接收文件。背景是浏览器界面不用管。

2、接收过程

在这里插入图片描述

这里是中间弹框提示进度。

3、接收完成或者其他异常

目前方案是接收完成直接是隐藏了对话框,toast提示完成。

也有的方案要求在接收的列表下面添加一个完成按钮,点击完成隐藏传输框。

点击取消或者另外一边取消发送都是可以收到广播的,直接显示取消接收/接收异常即可。

在Android 的蓝牙传输中是无法跳过某个文件再接收后面的文件的,只能一个一个接收,

多个文件接收的情况,中途取消后,后面的文件都是无法再接收的,只能让另外一个设备重新发送。

三、代码修改

1、主要文件和目录

(1)目录

Android 文件传输协议是opp协议,蓝牙文件接收各种处理是在opp文件夹目录内。

具体目录:

package\modules\Bluetooth\android\app\src\com\android\bluetooth\opp

Android 11 或者更低的版本蓝牙相关代码是在 package\apps\Bluetooth\src\com\android\bluetooth\opp

(2)广播接收代码

package\modules\Bluetooth\android\app\src\com\android\bluetooth\opp\BluetoothOppReceiver.java

<receiver android:process="@string/process"     android:name="com.android.bluetooth.opp.BluetoothOppReceiver"     
android:exported="true"     
android:enabled="false">    
    <intent-filter>       
    <action android:name="android.btopp.intent.action.OPEN_RECEIVED_FILES"/>    
    </intent-filter>
</receiver>

上面那个静态广播接收只有一个Action?

但是Java 代码中有很多接收的Action?并且代码中确实有其他广播接收的,为啥可以这样,暂时不清楚!

有个猜测:本应用内之间可以指定某个广播接收类给它发送广播。

public class BluetoothOppReceiver extends BroadcastReceiver {
    private static final String TAG = "BluetoothOppReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (D) Log.d(TAG, " action :" + action);
        mContext = context;
        if (action == null) return;
        if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) {

    	} else if (action.equals(Constants.ACTION_ACCEPT)) { //点击接收触发,其实是自身发送的
            if (V) {
                Log.v(TAG, "Receiver ACTION_ACCEPT");
            }
    	} else if (action.equals(BluetoothShare.TRANSFER_COMPLETED_ACTION)) { //文件接收完成
            if (V) {
                Log.v(TAG, "Receiver Transfer Complete Intent for " + intent.getData());
            }
            //自定义添加的代码逻辑
            if (BluetoothShare.isStatusSuccess(transInfo.mStatus)) { //文件成功
      
      		//以前的逻辑
      		/**if (transInfo.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
                    toastMsg = context.getString(R.string.notification_sent, transInfo.mFileName);
                } else if (transInfo.mDirection == BluetoothShare.DIRECTION_INBOUND) {
                    toastMsg =
                            context.getString(R.string.notification_received, transInfo.mFileName);
                }**/
				//自定义弹框的逻辑
                Intent is = new Intent(NotifyDialogManager.ACTION_RECEIVER_SUCCEED);
                is.putExtra("fileName", transInfo.mFileName);
                is.putExtra("fileType", transInfo.mFileType);
                notifyDialogManager(is);

            } else if (BluetoothShare.isStatusError(transInfo.mStatus)) { //文件失败
            	//自定义弹框的逻辑
                Intent is = new Intent(NotifyDialogManager.ACTION_RECEIVER_FAILED);
                is.putExtra("fileName", transInfo.mFileName);
                is.putExtra("fileType", transInfo.mFileType);
                notifyDialogManager(is);
            }
        }//还有很多其他相关广播这里不一一罗列
    }
}

这里只是广播的一个过程,很多重要的过程是在BluetoothOppService.java 和 BluetoothOppNotification.java 发生的。

其实最关键的是: BluetoothShare.CONTENT_URI 变量,无论蓝牙的开始,传输过程,和结束都跟里面的数据相关。

本次自定义输入框的开发,数据交互用的是本地广播。

(3)传输服务类 BluetoothOppService

BluetoothOppService 是 文件能够传输的重要保障,如果该服务异常,或者未启动也是无法进行文件传输的。

主要代码如下:

public class BluetoothOppService extends ProfileService  { //父类是普通的Service类
    private static final boolean D = Constants.DEBUG; //日志文件,已经手动设置成true   
    private static final boolean V = Constants.VERBOSE;//日志文件,已经手动设置成true 
    
    private static final String TAG = "BtOppService"; //注意这才是本来的TAG打印

    @Override
    public boolean start() { //Service 生命周期方法
        if (V) {
            Log.v(TAG, "start()");
        }

		// BluetoothShare.CONTENT_URI 属性变化监听
        mObserver = new BluetoothShareContentObserver();
        getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);
        return true;
    }
    
    //属性变化监听,类似Settings.Global 那些属性的监听
    private class BluetoothShareContentObserver extends ContentObserver {

        BluetoothShareContentObserver() {
            super(new Handler());
        }

        @Override
        public void onChange(boolean selfChange) { //传输开始过程一直是有这个日志的
            if (V) {
                Log.v(TAG, "ContentObserver received notification");
            }
            updateFromProvider();
        }
    }

	//每接收一个文件,mUpdateThread 会置空一次,所以每个接收的文件是new一个thread
    private void updateFromProvider() {
        synchronized (BluetoothOppService.this) {
            mPendingUpdate = true;
            if (mUpdateThread == null) {
                mUpdateThread = new UpdateThread();
                mUpdateThread.start();
                mUpdateThreadRunning = true;
            }
        }
    }

}

在蓝牙打开的情况,查看opp服务,可以看到如下信息:

130|console:/ # dumpsys activity services | grep opp                           
  * ServiceRecord{e9e9e2e u0 com.android.bluetooth/.opp.BluetoothOppService}
    intent={cmp=com.android.bluetooth/.opp.BluetoothOppService}
    infoAllowStartForeground=[callingPackage: com.android.bluetooth; callingUid: 1002; uidState: PER ; intent: Intent { cmp=com.android.bluetooth/.opp.BluetoothOppService (has extras) }; code:PROC_STATE_PERSISTENT; tempAllowListReason:<broadcast:1000:android.bluetooth.adapter.action.STATE_CHANGED,reason:,reasonCode:BLUETOOTH_BROADCAST,duration:10000,callingUid:1000>; targetSdkVersion:33; callerTargetSdkVersion:33; startForegroundCount:0; bindFromPackage:null]
console:/ # 

蓝牙关闭的情况是无法获取到该信息的。所以如果蓝牙文件无法传输的情况,可以查看该服务是否正常运行。

BluetoothOppService 服务正常运行的情况,才能正常传输文件。

(3)接收数据重要过程

这里可以看到 从BluetoothOppNotification.java 的代码可以看到接收文件的确认,过程和结束方法,并且这三个方法是一直有回调打印的。

package\modules\Bluetooth\android\app\src\com\android\bluetooth\opp\BluetoothOppNotification.java

   private class NotificationUpdateThread extends Thread {

        NotificationUpdateThread() {
            super("Notification Update Thread");
        }

        @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            synchronized (BluetoothOppNotification.this) {
                if (mUpdateNotificationThread != this) {
                    throw new IllegalStateException(
                            "multiple UpdateThreads in BluetoothOppNotification");
                }
                mPendingUpdate = 0;
            }
            //重要的过程,三个
            updateActiveNotification();//更新文件接收过程,这里有判断总共接收的文件数,文件大小,文件名称和已接收问大小等基本数据
            updateCompletedNotification(); //文件是否接收完成通知?自定义没有用到
            updateIncomingFileConfirmNotification(); //确认接收文件
            synchronized (BluetoothOppNotification.this) {
                mUpdateNotificationThread = null;
            }
        }
    }

2、蓝牙文件接收最开始的地方到整个过程

这个具体过程要追寻估计有点麻烦,有兴趣的可以看看完整的蓝牙文件接收日志梳理梳理。

这里大概描述一下。

(1)蓝牙打开的情况,BluetoothOppService 会启动

(2)BluetoothOppService 调用 startSocketListener();

(3)蓝牙配对后,其他设备发送蓝牙文件,ObexServerSockets 接收到蓝牙文件接收任务,其实是底层会先接收

具体过程可以看日志打印了解

(4)BluetoothOppService 监听到 BluetoothShare.CONTENT_URI 变化,updateFromProvider()。

(5)BluetoothOppService 启动线程 一直遍历 BluetoothShare.CONTENT_URI 的值,并且一直调用 updateNotification 发送消息给 BluetoothOppNotification ,BluetoothOppNotification 判断 蓝牙文件确认,蓝牙过程显示,蓝牙结束等重要过程

3、蓝牙文件确认

BluetoothOppNotification 的 updateIncomingFileConfirmNotification() 方法,
在遍历到 BluetoothShare.CONTENT_URI 数据 数据的时候弹框提示,是否确认接收

//提示蓝牙文件确认方法
void updateIncomingFileConfirmNotification() {

		//注意这个的游标是关于 WHERE_CONFIRM_PENDING 的,确认的
        Cursor cursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mContentResolver,
                BluetoothShare.CONTENT_URI, null, WHERE_CONFIRM_PENDING,
                        null, BluetoothShare._ID);

        if (cursor == null) {
            return;
        }

        for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
            BluetoothOppTransferInfo info = new BluetoothOppTransferInfo();
            BluetoothOppUtility.fillRecord(mContext, cursor, info);
            Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mID);
            String fileNameSafe = info.mFileName.replaceAll("\\s", "_");
            Intent baseIntent = new Intent().setDataAndNormalize(contentUri)
                    .setClassName(mContext,
                            BluetoothOppReceiver.class.getName());

            //创建自定义的弹框对象
            NotifyDialogManager.Info sinfo = new NotifyDialogManager.Info();
            //接收的文件名
            sinfo.filename = info.mFileName;
           //点击取消接收按钮的操作
           sinfo.cancel = new View.OnClickListener() {
				@Override
				public void onClick(View v) {
					mContext.sendBroadcast(new Intent(baseIntent).setAction(Constants.ACTION_DECLINE));
					mNotifyDialogManager.cancel(NotifyDialogManager.RECEIVER_CONFIRM_DIALOG);
				}
            };

			//点击接收按钮,确认文件接收操作
            sinfo.confirm = new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mContext.sendBroadcast(new Intent(baseIntent).setAction(Constants.ACTION_ACCEPT));
                    mNotifyDialogManager.cancel(NotifyDialogManager.RECEIVER_CONFIRM_DIALOG);
                }
            };
            mNotifyDialogManager.setDeviceName(info.mDeviceName);
            mNotifyDialogManager.notify(NotifyDialogManager.RECEIVER_CONFIRM_DIALOG, sinfo);
            mInitialState = getCurrentState();
            break;
            。。。
}

这里可以看到,蓝牙文件接收实际操作是发送广播:

mContext.sendBroadcast(new Intent(baseIntent).setAction(Constants.ACTION_ACCEPT));

蓝牙文件拒绝是发送了广播:

mContext.sendBroadcast(new Intent(baseIntent).setAction(Constants.ACTION_DECLINE));

上面看到重要过程有三个,为啥在最后的 updateIncomingFileConfirmNotification 方法做接收文件处理?

具体原因不清楚,因为原生代码也是在这个方法里面发送通知消息给通知栏,让其确认和取消,所以自定义接收框同样写在了这里。

这里的发送广播确认和取消 都是在 BluetoothOppReceiver 的广播接收者里面有进行处理。

4、蓝牙文件进度更新

    @VisibleForTesting
    void updateActiveNotification() {
        // Active transfers
		//注意这个的游标是关于 WHERE_RUNNING 的,过程的,和其他的不同
        Cursor cursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mContentResolver,
                BluetoothShare.CONTENT_URI, null, WHERE_RUNNING, null, BluetoothShare._ID);
        if (cursor == null) {
            return;
        }

        // If there is active transfers, then no need to update completed transfer
        // notifications
        if (cursor.getCount() > 0) {
            mUpdateCompleteNotification = false;
        } else {
            mUpdateCompleteNotification = true;
        }
        if (V) {
            Log.v(TAG, "mUpdateCompleteNotification = " + mUpdateCompleteNotification);
        }
 
        // Collate the notifications
        final int timestampIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP);
        final int directionIndex = cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION);
        final int idIndex = cursor.getColumnIndexOrThrow(BluetoothShare._ID);
        final int totalBytesIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES);
        final int currentBytesIndex = cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES);
        final int dataIndex = cursor.getColumnIndexOrThrow(BluetoothShare._DATA);
        final int filenameHintIndex = cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT);
        final int confirmIndex = cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION);
        final int destinationIndex = cursor.getColumnIndexOrThrow(BluetoothShare.DESTINATION);

        mNotifications.clear();
        //从日志看,这里只取 cursor 一帧的数据,并不是这里for循环取完整个的蓝牙文件
        for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
            long timeStamp = cursor.getLong(timestampIndex);
            int dir = cursor.getInt(directionIndex);
            int id = cursor.getInt(idIndex);
            long total = cursor.getLong(totalBytesIndex);
            long current = cursor.getLong(currentBytesIndex);
            int confirmation = cursor.getInt(confirmIndex);

            String destination = cursor.getString(destinationIndex);
            String fileName = cursor.getString(dataIndex);
            if (fileName == null) {
                fileName = cursor.getString(filenameHintIndex);
            }
            if (fileName == null) {
                fileName = mContext.getString(R.string.unknown_file);
            }

            String batchID = Long.toString(timeStamp);

            // sending objects in one batch has same timeStamp
            if (mNotifications.containsKey(batchID)) {
                // NOTE: currently no such case
                // Batch sending case
            } else {

            //load file
            Log.v(TAG, "ACTION_INCOMING_FILE_CONFIRM " + current + "/" + total);
            //Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM);
            //mContext.sendBroadcast(intent);

                //显示自定义对话框,更新文件接收过程
                int[] state = getCurrentState();
                NotifyDialogManager.Info rinfo = new NotifyDialogManager.Info();
                rinfo.all = (int) total; //文件总共大小
                rinfo.done = (int) current;// 当前已经接收的文件大小
                rinfo.filename = fileName; //文件名称
                mNotifyDialogManager.notify(NotifyDialogManager.RECEIVER_PROGRESS_DIALOG, rinfo);

            }
        }
        cursor.close();
        LogUtil.debug("end");

    }

为啥在 updateActiveNotification 方法里面处理文件接收过程?原生也是在这里发消息给通知栏的!

5、蓝牙文件传输完成

文件接收完成处理比较特殊。

 
 /**
 * Receives and handles: system broadcasts; Intents from other applications;
 * Intents from OppService; Intents from modules in Opp application layer.
 */
public class BluetoothOppReceiver extends BroadcastReceiver {
 
	 @Override
    public void onReceive(Context context, Intent intent) {
    
    。。。//很多其他不是主要的广播,这里不一一罗列
	} else if (action.equals(BluetoothShare.TRANSFER_COMPLETED_ACTION)) { //文件接收完成
            if (V) {
                Log.v(TAG, "Receiver Transfer Complete Intent for " + intent.getData());
            }

            if (BluetoothShare.isStatusSuccess(transInfo.mStatus)) {
            	//自定义弹框,提示文件接收成功
                Intent is = new Intent(NotifyDialogManager.ACTION_RECEIVER_SUCCEED);
                is.putExtra("fileName", transInfo.mFileName);
                is.putExtra("fileType", transInfo.mFileType);
                notifyDialogManager(is);

            } else if (BluetoothShare.isStatusError(transInfo.mStatus)) {
            	//自定义弹框,提示文件接收失败
                Intent is = new Intent(NotifyDialogManager.ACTION_RECEIVER_FAILED);
                is.putExtra("fileName", transInfo.mFileName);
                is.putExtra("fileType", transInfo.mFileType);
                notifyDialogManager(is);
            }
    }
}

为啥接收完成不用 updateCompletedNotification 方法里面进行处理?

看了下原生的 updateCompletedNotification 方法里面逻辑还是比较多的,

有判断成功接收多少个,失败接收多少个;从日志上看接收成功也有可能显示失败,具体原因不清楚。

6、蓝牙文件传输异常

对方设备异常,比如关闭蓝牙或者取消发送,都是会有广播:BluetoothShare.TRANSFER_COMPLETED_ACTION

在里面进行处理即可。

另外本机设备如果要 中断接收文件可以发送下面的广播:

//取消接收
Intent intent = new Intent();
intent.setAction(BluetoothOppService.ACTION_BLUETOOTH_TRANSFER_STOP);
getContext().sendBroadcast(intent);

注意,多个文件接收的情况,取消接收后,后面的文件是无法继续接收的,

如果还有传输,只能让对方设备需要重新传输。

7、 整个opp文件Java部分修改的patch

代码修改还是比较多的,有需要的可以下载下来看看。

Change-Id: Ifef37f272a81b09d0707443c6bc09ee9786bd893
---
 .../modules/Bluetooth/android/app/Android.bp  |   6 +-
 .../Bluetooth/android/app/AndroidManifest.xml |  11 +-
 .../drawable/bluetooth_dialog_background.xml  |   5 +
 .../bluetooth_dialog_button_background.xml    |  15 +
 .../bluetooth_dialog_cancel_background.xml    |  15 +
 .../bluetooth_dialog_confirm_background.xml   |  15 +
 .../drawable/receiver_progress_background.xml |  23 ++
 .../app/res/drawable/selector_hide_narror.xml |   6 +
 .../res/drawable/shape_rectangle_cancel.xml   |  16 +
 .../res/drawable/shape_rectangle_confirm.xml  |   5 +
 .../res/drawable/shape_rectangle_count.xml    |  19 +
 .../res/drawable/shape_transfer_cancel.xml    |  16 +
 .../res/layout/bluetooth_progress_adapter.xml |  62 +++
 .../bluetooth_progress_dialog_header.xml      |  48 +++
 .../bluetooth_receiver_confirm_dialog.xml     |  63 ++++
 .../bluetooth_receiver_finish_dialog.xml      |  41 ++
 .../bluetooth_receiver_floating_button.xml    |  23 ++
 .../bluetooth_receiver_progress_dialog.xml    | 215 +++++++++++
 .../app/res/mipmap-xxxhdpi/bt_share.png       | Bin 0 -> 14942 bytes
 .../app/res/mipmap-xxxhdpi/delivery_nor.png   | Bin 0 -> 4878 bytes
 .../app/res/mipmap-xxxhdpi/delivery_pre.png   | Bin 0 -> 6785 bytes
 .../android/app/res/mipmap-xxxhdpi/fail.png   | Bin 0 -> 1100 bytes
 .../app/res/mipmap-xxxhdpi/ic_narrow_nor.png  | Bin 0 -> 950 bytes
 .../app/res/mipmap-xxxhdpi/ic_narrow_pre.png  | Bin 0 -> 895 bytes
 .../app/res/mipmap-xxxhdpi/success.png        | Bin 0 -> 1363 bytes
 .../android/app/res/values-zh-rCN/strings.xml |  25 ++
 .../android/app/res/values/strings.xml        |  28 ++
 .../bluetooth/opp/BluetoothBaseUI.java        |  43 +++
 .../opp/BluetoothOppNotification.java         | 176 +++++----
 .../opp/BluetoothOppObexServerSession.java    |  16 +-
 .../bluetooth/opp/BluetoothOppReceiver.java   |  27 +-
 .../bluetooth/opp/BluetoothOppService.java    |  54 +++
 .../opp/BluetoothOppTransferActivity.java     |   2 +-
 .../opp/BluetoothOppTransferEntity.java       |  31 ++
 .../opp/BluetoothOppTransferHistory.java      |   2 +
 .../BluetoothOppTransferProgressAdapter.java  |  87 +++++
 .../bluetooth/opp/BluetoothOppUtility.java    |   4 +
 .../opp/BluetoothReceiverConfirmDialog.java   |  71 ++++
 .../opp/BluetoothReceiverFinishDialog.java    |  61 +++
 .../opp/BluetoothReceiverProgressDialog.java  | 352 ++++++++++++++++++
 .../com/android/bluetooth/opp/Constants.java  |   2 +-
 .../bluetooth/opp/NotifyDialogManager.java    | 243 ++++++++++++
 42 files changed, 1741 insertions(+), 87 deletions(-)
 mode change 100644 => 100755 release/packages/modules/Bluetooth/android/app/Android.bp
 mode change 100644 => 100755 release/packages/modules/Bluetooth/android/app/AndroidManifest.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/drawable/bluetooth_dialog_background.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/drawable/bluetooth_dialog_button_background.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/drawable/bluetooth_dialog_cancel_background.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/drawable/bluetooth_dialog_confirm_background.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/drawable/receiver_progress_background.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/drawable/selector_hide_narror.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/drawable/shape_rectangle_cancel.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/drawable/shape_rectangle_confirm.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/drawable/shape_rectangle_count.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/drawable/shape_transfer_cancel.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/layout/bluetooth_progress_adapter.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/layout/bluetooth_progress_dialog_header.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/layout/bluetooth_receiver_confirm_dialog.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/layout/bluetooth_receiver_finish_dialog.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/layout/bluetooth_receiver_floating_button.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/layout/bluetooth_receiver_progress_dialog.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/mipmap-xxxhdpi/bt_share.png
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/mipmap-xxxhdpi/delivery_nor.png
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/mipmap-xxxhdpi/delivery_pre.png
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/mipmap-xxxhdpi/fail.png
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/mipmap-xxxhdpi/ic_narrow_nor.png
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/mipmap-xxxhdpi/ic_narrow_pre.png
 create mode 100755 release/packages/modules/Bluetooth/android/app/res/mipmap-xxxhdpi/success.png
 mode change 100644 => 100755 release/packages/modules/Bluetooth/android/app/res/values-zh-rCN/strings.xml
 mode change 100644 => 100755 release/packages/modules/Bluetooth/android/app/res/values/strings.xml
 create mode 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothBaseUI.java
 mode change 100644 => 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java
 mode change 100644 => 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
 mode change 100644 => 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
 mode change 100644 => 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
 mode change 100644 => 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
 create mode 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferEntity.java
 mode change 100644 => 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
 create mode 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferProgressAdapter.java
 mode change 100644 => 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppUtility.java
 create mode 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothReceiverConfirmDialog.java
 create mode 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothReceiverFinishDialog.java
 create mode 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothReceiverProgressDialog.java
 mode change 100644 => 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/Constants.java
 create mode 100755 release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/NotifyDialogManager.java



diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothBaseUI.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothBaseUI.java
new file mode 100755
index 0000000000..f377d11763
--- /dev/null
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothBaseUI.java
@@ -0,0 +1,43 @@
+package com.android.bluetooth.opp;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.view.ViewTreeObserver;
+
+
+public abstract class BluetoothBaseUI extends FrameLayout {
+
+	protected boolean hide = false;
+
+    public BluetoothBaseUI(Context context) {
+        super(context);
+        LayoutInflater.from(context).inflate(getLayoutId(), this, true);
+        customInit();   
+    }
+
+    abstract int getLayoutId();
+    abstract void customInit();
+
+	public void setInfo(final NotifyDialogManager ndm, NotifyDialogManager.Info info) {
+
+    }
+
+    public boolean isHide() {
+        return hide;
+    }
+
+    public void setHide(boolean _h) {
+        hide = _h;
+    }
+
+    public void update(NotifyDialogManager ndm, NotifyDialogManager.Info info) { 
+		if (hide && getVisibility() == VISIBLE) setVisibility(GONE);
+	}
+	
+    void onCancel() {}
+
+}
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java
old mode 100644
new mode 100755
index 44a9c1864b..25f15e5da3
--- a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java
@@ -47,12 +47,19 @@ import android.os.Message;
 import android.os.Process;
 import android.text.format.Formatter;
 import android.util.Log;
+import android.os.Build;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+import android.view.View;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
 
 import java.util.HashMap;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
 /**
  * This class handles the updating of the Notification Manager for the cases
  * where there is an ongoing transfer, incoming transfer need confirm and
@@ -147,6 +154,8 @@ class BluetoothOppNotification {
      * @param ctx The context to use to obtain access to the Notification
      *            Service
      */
+    NotifyDialogManager mNotifyDialogManager;
+
     BluetoothOppNotification(Context ctx) {
         mContext = ctx;
         mNotificationMgr = mContext.getSystemService(NotificationManager.class);
@@ -158,6 +167,18 @@ class BluetoothOppNotification {
         mNotifications = new HashMap<String, NotificationItem>();
         // Get Content Resolver object one time
         mContentResolver = mContext.getContentResolver();
+
+        //mNotifyDialogManager = new NotifyDialogManager(ctx);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+                DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+                Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+                Context windowContext =mContext.createDisplayContext(primaryDisplay)
+                    .createWindowContext(TYPE_APPLICATION_OVERLAY, null);
+                mNotifyDialogManager = new NotifyDialogManager(windowContext);
+        } else {
+                mNotifyDialogManager = new NotifyDialogManager(ctx);
+        }
+
     }
 
     /**
@@ -168,13 +189,13 @@ class BluetoothOppNotification {
             mPendingUpdate++;
             if (mPendingUpdate > 1) {
                 if (V) {
-                    Log.v(TAG, "update too frequent, put in queue");
+                    Log.v(TAG, "update too frequent, put in queue 22");
                 }
                 return;
             }
             if (!mHandler.hasMessages(NOTIFY)) {
                 if (V) {
-                    Log.v(TAG, "send message");
+                    Log.v(TAG, "send message 22");
                 }
                 mHandler.sendMessage(mHandler.obtainMessage(NOTIFY));
             }
@@ -298,6 +319,20 @@ class BluetoothOppNotification {
                 // NOTE: currently no such case
                 // Batch sending case
             } else {
+
+            //load file
+            Log.v(TAG, "ACTION_INCOMING_FILE_CONFIRM ");
+            //Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM);
+            //mContext.sendBroadcast(intent);
+
+                int[] state = getCurrentState();
+                NotifyDialogManager.Info rinfo = new NotifyDialogManager.Info();
+                rinfo.all = (int) total;
+                rinfo.done = (int) current;
+                rinfo.filename = fileName;
+                mNotifyDialogManager.notify(NotifyDialogManager.RECEIVER_PROGRESS_DIALOG, rinfo);
+
+                /**
                 NotificationItem item = new NotificationItem();
                 item.timeStamp = timeStamp;
                 item.id = id;
@@ -323,9 +358,12 @@ class BluetoothOppNotification {
                     Log.v(TAG, "ID=" + item.id + "; batchID=" + batchID + "; totoalCurrent"
                             + item.totalCurrent + "; totalTotal=" + item.totalTotal);
                 }
+                **/
+
             }
         }
         cursor.close();
+		/**
 
         // Add the notifications
         for (NotificationItem item : mNotifications.values()) {
@@ -394,9 +432,31 @@ class BluetoothOppNotification {
                         PendingIntent.FLAG_IMMUTABLE));
             mNotificationMgr.notify(NOTIFICATION_ID_PROGRESS, b.build());
         }
+        **/
     }
 
     private void updateCompletedNotification() {
+		
+		if (mInitialState != null) {
+			int[] currentState = getCurrentState();
+			if (isStateChange(mInitialState, currentState)) {
+				
+				NotifyDialogManager.Info sinfo = new NotifyDialogManager.Info();
+				// mNotifyDialogManager.cancel(NotifyDialogManager.RECEIVER_PROGRESS_DIALOG);
+				// mNotifyDialogManager.notify(NotifyDialogManager.RECEIVER_FINISH_DIALOG, sinfo);
+			}
+		}
+    }
+
+    int[] mInitialState;
+
+    private boolean isStateChange(int[] state1, int[] state2) {
+        for (int i = 0; i < state1.length; i++) {
+            if (state1[i] != state2[i]) return false;
+        }
+        return true;
+    }
+    private int[] getCurrentState() {
         long timeStamp = 0;
         int outboundSuccNumber = 0;
         int outboundFailNumber = 0;
@@ -404,13 +464,14 @@ class BluetoothOppNotification {
         int inboundNum;
         int inboundSuccNumber = 0;
         int inboundFailNumber = 0;
+        int[] state = new int[6];
 
         // Creating outbound notification
         Cursor cursor =
                 mContentResolver.query(BluetoothShare.CONTENT_URI, null, WHERE_COMPLETED_OUTBOUND,
                         null, BluetoothShare.TIMESTAMP + " DESC");
         if (cursor == null) {
-            return;
+            return state;
         }
 
         final int timestampIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP);
@@ -435,49 +496,15 @@ class BluetoothOppNotification {
         cursor.close();
 
         outboundNum = outboundSuccNumber + outboundFailNumber;
-        // create the outbound notification
-        if (outboundNum > 0) {
-            String caption = BluetoothOppUtility.formatResultText(outboundSuccNumber,
-                    outboundFailNumber, mContext);
-            Intent contentIntent = new Intent(Constants.ACTION_OPEN_OUTBOUND_TRANSFER).setClassName(
-                    mContext, BluetoothOppReceiver.class.getName());
-            Intent deleteIntent = new Intent(Constants.ACTION_COMPLETE_HIDE).setClassName(
-                    mContext, BluetoothOppReceiver.class.getName());
-            Notification outNoti =
-                    new Notification.Builder(mContext, OPP_NOTIFICATION_CHANNEL).setOnlyAlertOnce(
-                            true)
-                            .setContentTitle(mContext.getString(R.string.outbound_noti_title))
-                            .setContentText(caption)
-                            .setSmallIcon(android.R.drawable.stat_sys_upload_done)
-                            .setColor(mContext.getResources()
-                                    .getColor(
-                                            android.R.color
-                                                    .system_notification_accent_color,
-                                            mContext.getTheme()))
-                            .setContentIntent(
-                                    PendingIntent.getBroadcast(mContext, 0, contentIntent,
-                                        PendingIntent.FLAG_IMMUTABLE))
-                            .setDeleteIntent(
-                                    PendingIntent.getBroadcast(mContext, 0, deleteIntent,
-                                        PendingIntent.FLAG_IMMUTABLE))
-                            .setWhen(timeStamp)
-                            .setLocalOnly(true)
-                            .build();
-            mNotificationMgr.notify(NOTIFICATION_ID_OUTBOUND_COMPLETE, outNoti);
-        } else {
-            if (mNotificationMgr != null) {
-                mNotificationMgr.cancel(NOTIFICATION_ID_OUTBOUND_COMPLETE);
-                if (V) {
-                    Log.v(TAG, "outbound notification was removed.");
-                }
-            }
-        }
+        state[0] = outboundSuccNumber;
+        state[1] = outboundFailNumber;
+        state[2] = outboundNum;
 
         // Creating inbound notification
         cursor = mContentResolver.query(BluetoothShare.CONTENT_URI, null, WHERE_COMPLETED_INBOUND,
                 null, BluetoothShare.TIMESTAMP + " DESC");
         if (cursor == null) {
-            return;
+            return state;
         }
 
         for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
@@ -499,44 +526,10 @@ class BluetoothOppNotification {
         cursor.close();
 
         inboundNum = inboundSuccNumber + inboundFailNumber;
-        // create the inbound notification
-        if (inboundNum > 0) {
-            String caption = BluetoothOppUtility.formatResultText(inboundSuccNumber,
-                    inboundFailNumber, mContext);
-            Intent contentIntent = new Intent(Constants.ACTION_OPEN_INBOUND_TRANSFER).setClassName(
-                    mContext, BluetoothOppReceiver.class.getName());
-            Intent deleteIntent = new Intent(Constants.ACTION_COMPLETE_HIDE).setClassName(
-                    mContext, BluetoothOppReceiver.class.getName());
-            Notification inNoti =
-                    new Notification.Builder(mContext, OPP_NOTIFICATION_CHANNEL).setOnlyAlertOnce(
-                            true)
-                            .setContentTitle(mContext.getString(R.string.inbound_noti_title))
-                            .setContentText(caption)
-                            .setSmallIcon(android.R.drawable.stat_sys_download_done)
-                            .setColor(mContext.getResources()
-                                    .getColor(
-                                            android.R.color
-                                                    .system_notification_accent_color,
-                                            mContext.getTheme()))
-
-                            .setContentIntent(
-                                    PendingIntent.getBroadcast(mContext, 0, contentIntent,
-                                        PendingIntent.FLAG_IMMUTABLE))
-                            .setDeleteIntent(
-                                    PendingIntent.getBroadcast(mContext, 0, deleteIntent,
-                                        PendingIntent.FLAG_IMMUTABLE))
-                            .setWhen(timeStamp)
-                            .setLocalOnly(true)
-                            .build();
-            mNotificationMgr.notify(NOTIFICATION_ID_INBOUND_COMPLETE, inNoti);
-        } else {
-            if (mNotificationMgr != null) {
-                mNotificationMgr.cancel(NOTIFICATION_ID_INBOUND_COMPLETE);
-                if (V) {
-                    Log.v(TAG, "inbound notification was removed.");
-                }
-            }
-        }
+        state[3] = inboundSuccNumber;
+        state[4] = inboundFailNumber;
+        state[5] = inboundNum;
+        return state;
     }
 
     private void updateIncomingFileConfirmNotification() {
@@ -556,6 +549,30 @@ class BluetoothOppNotification {
             Intent baseIntent = new Intent().setDataAndNormalize(contentUri)
                     .setClassName(mContext,
                             BluetoothOppReceiver.class.getName());
+
+            NotifyDialogManager.Info sinfo = new NotifyDialogManager.Info();
+            sinfo.filename = info.mFileName;
+            sinfo.cancel = new View.OnClickListener() {
+				@Override
+				public void onClick(View v) {
+					mContext.sendBroadcast(new Intent(baseIntent).setAction(Constants.ACTION_DECLINE));
+					mNotifyDialogManager.cancel(NotifyDialogManager.RECEIVER_CONFIRM_DIALOG);
+				}
+            };
+
+            sinfo.confirm = new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mContext.sendBroadcast(new Intent(baseIntent).setAction(Constants.ACTION_ACCEPT));
+                    mNotifyDialogManager.cancel(NotifyDialogManager.RECEIVER_CONFIRM_DIALOG);
+                }
+            };
+            mNotifyDialogManager.setDeviceName(info.mDeviceName);
+            mNotifyDialogManager.notify(NotifyDialogManager.RECEIVER_CONFIRM_DIALOG, sinfo);
+            mInitialState = getCurrentState();
+            break;
+
+            /**
             Notification.Action actionDecline =
                     new Notification.Action.Builder(Icon.createWithResource(mContext,
                             R.drawable.ic_decline),
@@ -628,6 +645,7 @@ class BluetoothOppNotification {
                             .setPublicVersion(public_n)
                             .build();
             mNotificationMgr.notify(NOTIFICATION_ID_PROGRESS, n);
+            **/
         }
         cursor.close();
     }
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
old mode 100644
new mode 100755
index 6520f512e3..4931632b2b
--- a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
@@ -60,6 +60,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Arrays;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 
 /**
  * This class runs as an OBEX server
@@ -302,6 +303,9 @@ public class BluetoothOppObexServerSession extends ServerRequestHandler
             try {
 
                 while (mServerBlocking) {
+                    if (V) {
+                        Log.v(TAG, "mServerBlocking wait");
+                    }
                     wait(1000);
                     if (mCallback != null && !mTimeoutMsgSent) {
                         mCallback.sendMessageDelayed(mCallback.obtainMessage(
@@ -408,9 +412,12 @@ public class BluetoothOppObexServerSession extends ServerRequestHandler
             if (mFileInfo.mInsertUri != null) {
                 mContext.getContentResolver().delete(mFileInfo.mInsertUri, null, null);
             }
+
+            Intent is = new Intent(NotifyDialogManager.ACTION_CANCEL_CONFIRM_DIALOG);
+            notifyDialogManager(is);
             // set status as local cancel
             status = BluetoothShare.STATUS_CANCELED;
-            Constants.updateShareStatus(mContext, mInfo.mId, status);
+            //Constants.updateShareStatus(mContext, mInfo.mId, status);
             obexResponse = ResponseCodes.OBEX_HTTP_FORBIDDEN;
 
             Message msg = Message.obtain(mCallback);
@@ -422,6 +429,10 @@ public class BluetoothOppObexServerSession extends ServerRequestHandler
         return obexResponse;
     }
 
+    private void notifyDialogManager(Intent intent) {
+        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
+    }
+
     private int receiveFile(BluetoothOppReceiveFileInfo fileInfo, Operation op) {
         /*
          * implement receive file
@@ -610,6 +621,9 @@ public class BluetoothOppObexServerSession extends ServerRequestHandler
         }
         mTimestamp = System.currentTimeMillis();
         mNumFilesAttemptedToReceive = 0;
+        BluetoothReceiverProgressDialog.globalSurplus = objectCount != null ? objectCount : 1L;
+        BluetoothReceiverProgressDialog.isSend = false;
+        Log.v(TAG, "onConnect(): objectCount =" + objectCount);
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppReceiver.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
old mode 100644
new mode 100755
index 3540c4d716..cae6b8784e
--- a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
@@ -44,6 +44,8 @@ import android.net.Uri;
 import android.util.Log;
 import android.widget.Toast;
 
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
 
@@ -56,12 +58,15 @@ public class BluetoothOppReceiver extends BroadcastReceiver {
     private static final boolean D = Constants.DEBUG;
     private static final boolean V = Constants.VERBOSE;
 
+    Context mContext;
+
     @Override
     public void onReceive(Context context, Intent intent) {
         String action = intent.getAction();
         if (D) Log.d(TAG, " action :" + action);
+        mContext = context;
         if (action == null) return;
-        if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) {
+        if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED) || action.equals("com.skg.filemanager.devicepicker.action.DEVICE_SELECTED")) {
             BluetoothOppManager mOppManager = BluetoothOppManager.getInstance(context);
 
             BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
@@ -255,27 +260,39 @@ public class BluetoothOppReceiver extends BroadcastReceiver {
             }
 
             if (BluetoothShare.isStatusSuccess(transInfo.mStatus)) {
+                Intent is = new Intent(NotifyDialogManager.ACTION_RECEIVER_SUCCEED);
+                is.putExtra("fileName", transInfo.mFileName);
+                is.putExtra("fileType", transInfo.mFileType);
+                notifyDialogManager(is);
+            /**
                 if (transInfo.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
                     toastMsg = context.getString(R.string.notification_sent, transInfo.mFileName);
                 } else if (transInfo.mDirection == BluetoothShare.DIRECTION_INBOUND) {
                     toastMsg =
                             context.getString(R.string.notification_received, transInfo.mFileName);
                 }
+            **/
 
             } else if (BluetoothShare.isStatusError(transInfo.mStatus)) {
+                Intent is = new Intent(NotifyDialogManager.ACTION_RECEIVER_FAILED);
+                is.putExtra("fileName", transInfo.mFileName);
+                is.putExtra("fileType", transInfo.mFileType);
+                notifyDialogManager(is);
+                /**
                 if (transInfo.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
                     toastMsg =
                             context.getString(R.string.notification_sent_fail, transInfo.mFileName);
                 } else if (transInfo.mDirection == BluetoothShare.DIRECTION_INBOUND) {
                     toastMsg = context.getString(R.string.download_fail_line1);
                 }
+                **/
             }
             if (V) {
                 Log.v(TAG, "Toast msg == " + toastMsg);
             }
-            if (toastMsg != null) {
+            /** if (toastMsg != null) {
                 Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show();
-            }
+            } **/
         }
     }
 
@@ -289,4 +306,8 @@ public class BluetoothOppReceiver extends BroadcastReceiver {
             Log.v(TAG, "notMgr.cancel called");
         }
     }
+
+    private void notifyDialogManager(Intent intent) {
+        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
+    }
 }
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
old mode 100644
new mode 100755
index 27bfe509c3..28bbc94342
--- a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -214,6 +214,26 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
         }
     }
 
+    static final String ACTION_BLUETOOTH_TRANSFER_STOP = "com.skg.bluetooth.transfer.stop";
+
+    private final BroadcastReceiver stopTransferReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            Log.i(TAG + "20210104", "receiver transfer stop" + action);
+            if (action.equals(ACTION_BLUETOOTH_TRANSFER_STOP)) {
+                if (mTransfer != null) {
+                    Log.i(TAG + "20210104", "stop operation");
+                    mTransfer.stop();
+                }
+                if (mServerTransfer != null) {
+                    Log.i(TAG + "20210104", "stop operation");
+                    mServerTransfer.stop();
+                }
+            }
+        }
+    };
+
     @Override
     protected void create() {
         if (V) {
@@ -226,6 +246,9 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
         registerReceiver(mBluetoothReceiver, filter);
 
+        IntentFilter stopFilter = new IntentFilter();
+        stopFilter.addAction(ACTION_BLUETOOTH_TRANSFER_STOP);
+        registerReceiver(stopTransferReceiver, stopFilter);
         if (V) {
             BluetoothOppPreference preference = BluetoothOppPreference.getInstance(this);
             if (preference != null) {
@@ -546,6 +569,7 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
                 mObserver = null;
             }
             unregisterReceiver(mBluetoothReceiver);
+            unregisterReceiver(stopTransferReceiver);
         } catch (IllegalArgumentException e) {
             Log.w(TAG, "unregisterReceivers " + e.toString());
         }
@@ -699,6 +723,9 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
                             }
                         }
 
+                        if (shouldScanFile(arrayPos)) {
+                            scanFile(arrayPos);
+                        }
                         deleteShare(arrayPos); // this advances in the array
                     } else {
                         int id = cursor.getInt(idColumn);
@@ -722,6 +749,9 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
                                     Log.v(TAG,
                                             "Array update: removing " + arrayId + " @ " + arrayPos);
                                 }
+                                if (shouldScanFile(arrayPos)) {
+                                    scanFile(arrayPos);
+                                }
                                 deleteShare(arrayPos);
                             } else if (arrayId == id) {
                                 // This cursor row already exists in the stored array.
@@ -1094,6 +1124,30 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
         }
     }
 
+
+    private boolean scanFile(int arrayPos) {
+        BluetoothOppShareInfo info = mShares.get(arrayPos);
+        synchronized (BluetoothOppService.this) {
+            if (D) {
+                Log.d(TAG, "Scanning file " + info.mFilename);
+            }
+            if (!mMediaScanInProgress) {
+                mMediaScanInProgress = true;
+                new MediaScannerNotifier(this, info, mHandler);
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    private boolean shouldScanFile(int arrayPos) {
+        BluetoothOppShareInfo info = mShares.get(arrayPos);
+        return BluetoothShare.isStatusSuccess(info.mStatus)
+                && info.mDirection == BluetoothShare.DIRECTION_INBOUND && !info.mMediaScanned
+                && info.mConfirm != BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED;
+    }
+
     private void scanFileIfNeeded(int arrayPos) {
         BluetoothOppShareInfo info = mShares.get(arrayPos);
         boolean isFileReceived = BluetoothShare.isStatusSuccess(info.mStatus)
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
old mode 100644
new mode 100755
index bba67586f2..aaab70a0bc
--- a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
@@ -135,7 +135,7 @@ public class BluetoothOppTransferActivity extends AlertActivity
         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
         Intent intent = getIntent();
         mUri = intent.getData();
-
+        Log.v(TAG, "onCreate lwz mUri = " + mUri);
         mTransInfo = new BluetoothOppTransferInfo();
         mTransInfo = BluetoothOppUtility.queryRecord(this, mUri);
         if (mTransInfo == null) {
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferEntity.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferEntity.java
new file mode 100755
index 0000000000..33866e5228
--- /dev/null
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferEntity.java
@@ -0,0 +1,31 @@
+package com.android.bluetooth.opp;
+
+public class BluetoothOppTransferEntity {
+    String name;
+    String size;
+    Boolean result;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getSize() {
+        return size;
+    }
+
+    public void setSize(String size) {
+        this.size = size;
+    }
+
+    public Boolean getResult() {
+        return result;
+    }
+
+    public void setResult(Boolean result) {
+        this.result = result;
+    }
+}
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
old mode 100644
new mode 100755
index e4f978d83b..fd37077826
--- a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
@@ -317,10 +317,12 @@ public class BluetoothOppTransferHistory extends Activity
         if (transInfo.mDirection == BluetoothShare.DIRECTION_INBOUND
                 && BluetoothShare.isStatusSuccess(transInfo.mStatus)) {
             // if received file successfully, open this file
+            Log.i(TAG, "lwz DIRECTION_INBOUND");
             BluetoothOppUtility.updateVisibilityToHidden(this, contentUri);
             BluetoothOppUtility.openReceivedFile(this, transInfo.mFileName, transInfo.mFileType,
                     transInfo.mTimeStamp, contentUri);
         } else {
+            Log.i(TAG, "lwz startActivity");
             Intent in = new Intent(this, BluetoothOppTransferActivity.class);
             in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             in.setDataAndNormalize(contentUri);
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferProgressAdapter.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferProgressAdapter.java
new file mode 100755
index 0000000000..43c2730e46
--- /dev/null
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferProgressAdapter.java
@@ -0,0 +1,87 @@
+package com.android.bluetooth.opp;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+
+import java.util.List;
+
+import com.android.bluetooth.R;
+
+public class BluetoothOppTransferProgressAdapter extends Adapter<BluetoothOppTransferProgressAdapter.ViewHolder> {
+    private List<BluetoothOppTransferEntity> bluetoothOppTransferEntityList;
+
+    public BluetoothOppTransferProgressAdapter(List<BluetoothOppTransferEntity> list) {
+        this.bluetoothOppTransferEntityList = list;
+    }
+    @Override
+    public int getItemViewType(int position) {
+        if (position == 0) {
+            return 0;
+        } else {
+            return position;
+        }
+    }
+
+    @NonNull
+    @Override
+    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        View view;
+//        if (viewType == 0) {
+//            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.bluetooth_progress_dialog_header, parent, false);
+//        } else {
+//            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.bluetooth_progress_adapter, parent, false);
+//        }
+        view = LayoutInflater.from(parent.getContext()).inflate(R.layout.bluetooth_progress_adapter, parent, false);
+        ViewHolder holder = new ViewHolder(view);
+        return holder;
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+        BluetoothOppTransferEntity entity = bluetoothOppTransferEntityList.get(position);
+        String[] names = entity.getName().split("/");
+        holder.name.setText(names[names.length - 1]);
+        holder.size.setText(entity.getSize());
+        if (entity.getResult()) {
+            if (BluetoothReceiverProgressDialog.isSend) {
+                holder.result.setText(R.string.bluetooth_opp_send_success);
+            } else {
+                holder.result.setText(R.string.bluetooth_opp_receiver_success);
+            }
+            holder.icon.setImageResource(R.mipmap.success);
+        } else {
+            if (BluetoothReceiverProgressDialog.isSend) {
+                holder.result.setText(R.string.bluetooth_opp_send_failed);
+            } else {
+                holder.result.setText(R.string.bluetooth_opp_receiver_failed);
+            }
+            holder.icon.setImageResource(R.mipmap.fail);
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        return bluetoothOppTransferEntityList.size();
+    }
+
+    class ViewHolder extends RecyclerView.ViewHolder {
+        TextView name;
+        TextView size;
+        ImageView icon;
+        TextView result;
+        public ViewHolder(View itemView) {
+            super(itemView);
+            name = itemView.findViewById(R.id.tvName);
+            size = itemView.findViewById(R.id.tvSize);
+            icon = itemView.findViewById(R.id.ivIcon);
+            result = itemView.findViewById(R.id.tvResult);
+        }
+    }
+}
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppUtility.java
old mode 100644
new mode 100755
index f1f1faa2c3..0395578ade
--- a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -189,6 +189,8 @@ public class BluetoothOppUtility {
      */
     public static void openReceivedFile(Context context, String fileName, String mimetype,
             Long timeStamp, Uri uri) {
+        Log.i(TAG, "lwz openReceivedFile fileName = " + fileName + " ,mimetype = " + mimetype );
+        Log.i(TAG, "lwz openReceivedFile uri = " + uri);
         if (fileName == null || mimetype == null) {
             Log.e(TAG, "ERROR: Para fileName ==null, or mimetype == null");
             return;
@@ -198,6 +200,8 @@ public class BluetoothOppUtility {
             Log.e(TAG, "Trying to open a file that wasn't transfered over Bluetooth");
             return;
         }
+        File f = new File(fileName);
+        Log.i(TAG, "lwz openReceivedFile f.exists() = " + f.exists());
 
         Uri path = null;
         Cursor metadataCursor = context.getContentResolver().query(uri, new String[]{
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothReceiverConfirmDialog.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothReceiverConfirmDialog.java
new file mode 100755
index 0000000000..05d6efbf8b
--- /dev/null
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothReceiverConfirmDialog.java
@@ -0,0 +1,71 @@
+package com.android.bluetooth.opp;
+
+import android.annotation.SuppressLint;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.bluetooth.R;
+
+@SuppressLint("ViewConstructor")
+public class BluetoothReceiverConfirmDialog extends BluetoothBaseUI {
+
+
+    public BluetoothReceiverConfirmDialog(Context context) {
+        super(context);
+    }
+
+    private String deviceName;
+
+    public void setDeviceName(String deviceName) {
+        this.deviceName = deviceName;
+    }
+
+    @Override
+    int getLayoutId() {
+        return R.layout.bluetooth_receiver_confirm_dialog;
+    }
+
+    TextView tvMessage, tvConfirm, tvCancel;
+
+    @Override
+    void customInit() {
+        tvMessage = findViewById(R.id.tvMessage);
+        tvConfirm = findViewById(R.id.tvConfirm);
+        tvCancel = findViewById(R.id.tvCancel);
+    }
+
+    @Override
+    public void setInfo(NotifyDialogManager ndm, NotifyDialogManager.Info info) {
+        super.setInfo(ndm, info);
+        tvMessage.setText(getResources().getString(R.string.bluetooth_receiver_confirm_dialog_accept) + deviceName
+                + getResources().getString(R.string.bluetooth_receiver_confirm_dialog_file) + info.filename + " ?");
+        tvCancel.setOnClickListener(info.cancel);
+        tvConfirm.setOnClickListener(info.confirm);
+    }
+
+
+    public static WindowManager.LayoutParams createLayoutParams() {
+        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+        layoutParams.gravity = Gravity.CENTER;
+        layoutParams.height = android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+        layoutParams.width = 800;
+        layoutParams.format = PixelFormat.TRANSLUCENT;
+        layoutParams.x = 50;
+        layoutParams.y = 50;
+		layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        return layoutParams;
+    }
+
+    @Override
+    public void update(NotifyDialogManager ndm, NotifyDialogManager.Info info) {
+
+    }
+
+
+}
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothReceiverFinishDialog.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothReceiverFinishDialog.java
new file mode 100755
index 0000000000..0c3d1bc026
--- /dev/null
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothReceiverFinishDialog.java
@@ -0,0 +1,61 @@
+package com.android.bluetooth.opp;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.bluetooth.R;
+
+public class BluetoothReceiverFinishDialog extends BluetoothBaseUI {
+
+    public BluetoothReceiverFinishDialog(Context context) {
+        super(context);
+    }
+
+    @Override
+    int getLayoutId() {
+        return R.layout.bluetooth_receiver_finish_dialog;
+    }
+
+    TextView tvReceiverResult, tvOperation;
+
+    @Override
+    void customInit() {
+        tvReceiverResult = findViewById(R.id.tvReceiverResult);
+        tvOperation = findViewById(R.id.tvOperation);
+    }
+
+	
+	@Override
+    public void setInfo(final NotifyDialogManager ndm, NotifyDialogManager.Info info) {
+        super.setInfo(ndm, info);
+        tvReceiverResult.setText(String.format("Receive (%s) %s", info.filename,
+                info.receiverSucceed ? " success": "failed"));
+        tvOperation.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                ndm.cancel(NotifyDialogManager.RECEIVER_FINISH_DIALOG);
+            }
+        });
+    }
+
+
+    public static WindowManager.LayoutParams createLayoutParams() {
+        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+        layoutParams.gravity = Gravity.CENTER | Gravity.TOP;
+        layoutParams.height = android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+        layoutParams.width = 500;
+        layoutParams.format = PixelFormat.TRANSLUCENT;
+        layoutParams.y = 30;
+		layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        return layoutParams;
+    }
+
+
+}
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothReceiverProgressDialog.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothReceiverProgressDialog.java
new file mode 100755
index 0000000000..83a36a03c5
--- /dev/null
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/BluetoothReceiverProgressDialog.java
@@ -0,0 +1,352 @@
+package com.android.bluetooth.opp;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.text.TextUtils;
+import android.text.Html;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.view.View;
+import com.android.bluetooth.R;
+import android.util.Log;
+import android.os.Build;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import com.android.bluetooth.R;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+public class BluetoothReceiverProgressDialog extends BluetoothBaseUI {
+    String TAG = "BluetoothReceiverProgressDialog";
+    public interface Callback {
+        void sendSuccess();
+        void sendFail();
+        void receiveSuccess();
+        void receiveFail();
+    }
+
+    private Callback callback;
+
+    public void setCallback(Callback callback) {
+        this.callback = callback;
+    }
+
+    private WindowManager wm;
+    public BluetoothReceiverProgressDialog(Context context) {
+        super(context);
+        transferComplete = false;
+    }
+
+    @Override
+    int getLayoutId() {
+        return R.layout.bluetooth_receiver_progress_dialog;
+    }
+
+    NotifyDialogManager.Info info;
+
+    TextView tvFileName, tvFileSize, tvCancelTransfer, tvTitle;
+    ImageView tvHide;
+    ProgressBar pbProgress;
+    private LinearLayout llCurrentFile, llLoadingCount;
+
+    private RecyclerView rvTransferFile;
+    private BluetoothOppTransferProgressAdapter bluetoothOppTransferProgressAdapter;
+    private List<BluetoothOppTransferEntity> bluetoothOppTransferEntityList;
+
+    @Override
+    void customInit() {
+        wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+        tvTitle = findViewById(R.id.tvTitle);
+        if (isSend) {
+            tvTitle.setText(R.string.bluetooth_opp_send_file);
+        } else {
+            tvTitle.setText(R.string.bluetooth_opp_receive_file);
+        }
+        tvFileName = findViewById(R.id.tvFileName);
+        tvFileSize = findViewById(R.id.tvFileSize);
+        tvHide = findViewById(R.id.tvHide);
+        pbProgress = findViewById(R.id.pbProgress);
+        llLoadingCount = findViewById(R.id.llLoadingCount);
+        llCurrentFile = findViewById(R.id.llCurrentFile);
+
+        btnReceiverFloating = LayoutInflater.from(getContext()).inflate(R.layout.bluetooth_receiver_floating_button, null);
+        tvFileCount = btnReceiverFloating.findViewById(R.id.tvCount);
+
+        //RecyclerView
+        rvTransferFile = findViewById(R.id.rvTransferFile);
+        bluetoothOppTransferEntityList = new ArrayList<>();
+//        bluetoothOppTransferEntityList.add(new BluetoothOppTransferEntity());
+        bluetoothOppTransferProgressAdapter = new BluetoothOppTransferProgressAdapter((bluetoothOppTransferEntityList));
+        rvTransferFile.setAdapter(bluetoothOppTransferProgressAdapter);
+        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
+        rvTransferFile.setLayoutManager(layoutManager);
+
+        updateTipCount();
+        setViewListener();
+    }
+
+    private void updateTipCount() {
+        View view = LayoutInflater.from(getContext()).inflate(R.layout.bluetooth_receiver_progress_dialog, null);
+        if (globalSurplus <= 1) {
+            llLoadingCount.setVisibility(View.GONE);
+        } else {
+            llLoadingCount.setVisibility(View.VISIBLE);
+            tvSurplus = llLoadingCount.findViewById(R.id.tvLoadingCount);
+            tvSurplus.setText(String.format("%d", globalSurplus - 1));
+        }
+        tvFileCount.setText(globalSurplus.toString());
+    }
+
+    private void setViewListener() {
+        btnDone = findViewById(R.id.btnDone);
+        tvCancelTransfer = findViewById(R.id.tvCancelTransfer);
+    }
+
+    private final View.OnClickListener receiverFloatingBtnListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            setVisibility(View.VISIBLE);
+            btnReceiverFloating.setVisibility(View.GONE);
+        }
+    };
+
+    private final View.OnClickListener cancelTransferListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            Log.i(TAG, "stop broadcast " + BluetoothOppService.ACTION_BLUETOOTH_TRANSFER_STOP);
+            //取消接收或发送
+            Intent intent = new Intent();
+            intent.setAction(BluetoothOppService.ACTION_BLUETOOTH_TRANSFER_STOP);
+            getContext().sendBroadcast(intent);
+        }
+    };
+
+    private View.OnClickListener doneListener;
+
+    public void setDoneListener(View.OnClickListener listener) {
+        if (doneListener == null) {
+            this.doneListener = listener;
+        }
+    }
+
+    private boolean isTouch;
+
+    private final View.OnTouchListener receiverFloatingBtnTouchListener = new View.OnTouchListener() {
+        private float rawX,rawY;
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            switch (event.getAction()){
+                case MotionEvent.ACTION_DOWN:
+                    isTouch = false;
+                    int[] viewLocation=new int[2];
+                    btnReceiverFloating.getLocationOnScreen(viewLocation);
+                    rawX=event.getRawX();
+                    rawY=event.getRawY();
+                    if (!(rawX>viewLocation[0]&&rawX<viewLocation[0]+btnReceiverFloating.getWidth()&&rawY>viewLocation[1]&&rawY<viewLocation[1])){
+                        return false;
+                    }
+                    break;
+                case MotionEvent.ACTION_UP:
+                    rawX=0;
+                    rawY=0;
+                    if (isTouch) return true;
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    float x=event.getRawX();
+                    float y=event.getRawY();
+                    if (Math.abs(x-rawX)>20||Math.abs(y-rawY)>20){
+                        WindowManager.LayoutParams params = (WindowManager.LayoutParams) btnReceiverFloating.getLayoutParams();
+                        params.x+= (int) (x-rawX);
+                        params.y+= (int) (y-rawY);
+                        wm.updateViewLayout(btnReceiverFloating,params);
+                        rawX=event.getRawX();
+                        rawY=event.getRawY();
+                        isTouch = true;
+                    }
+                    break;
+
+            }
+            return false;
+        }
+    };
+
+    private NotifyDialogManager.Info lastInfo;
+    @Override
+    public void update(NotifyDialogManager ndm, NotifyDialogManager.Info info) {
+        int progress = (int) (info.done * 1f / info.all * 100);
+        if (lastInfo == null) {
+            lastInfo = info;
+        } else {
+            if (!lastInfo.filename.equals(info.filename)) {
+                updateFileName(info.filename);
+                tvFileSize.setText(android.text.format.Formatter.formatFileSize(getContext(), info.all));
+                lastInfo = info;
+            }
+        }
+        updateReceiver(progress);
+    }
+
+    static Long globalSurplus = 0L;
+    static Boolean isSend = false;
+    static Boolean transferComplete = false;
+    private Boolean existFailed = false;
+    //finish single
+    public void finish(NotifyDialogManager.Info info) {
+        BluetoothOppTransferEntity entity = new BluetoothOppTransferEntity();
+        Log.i(TAG, "finish info = " + info);
+        if(info == null) {
+        Log.e(TAG, "-------------------------------->info  = " + info + ",lastInfo = " + lastInfo);
+            return;
+        }
+        entity.setName(info.filename.toString());
+        if (info.all == 0 && lastInfo != null && info.filename.equals(lastInfo.filename)) {
+            info.all = lastInfo.all;
+        }
+        entity.setSize(android.text.format.Formatter.formatFileSize(getContext(), info.all));
+        entity.setResult(info.receiverSucceed);
+        bluetoothOppTransferEntityList.add(entity);
+
+        ViewGroup.LayoutParams rvLayoutParams = rvTransferFile.getLayoutParams();
+        int singleHeight = (tvFileName.getHeight() + (int) (getContext().getResources().getDisplayMetrics().density * 10 + 0.5f));
+        if (bluetoothOppTransferEntityList.size() > 4) {
+            rvLayoutParams.height = singleHeight * 5;
+        } else {
+            rvLayoutParams.height = singleHeight * bluetoothOppTransferEntityList.size();
+        }
+        rvTransferFile.setLayoutParams(rvLayoutParams);
+
+        if (!info.receiverSucceed) {
+            existFailed = true;
+            if (!isSend) {
+                transferComplete = true;
+                globalSurplus = (long)0;
+                showDoneButton();
+                callback.receiveFail();
+            }
+        }
+
+        if (globalSurplus <= 1) {
+            transferComplete = true;
+            if (existFailed) {
+                showDoneButton();
+                if (isSend) {
+                    callback.sendFail();
+                } else {
+                    callback.receiveFail();
+                }
+            } else {
+                btnReceiverFloating.setVisibility(View.GONE);
+                if (isSend) {
+                    callback.sendSuccess();
+                } else {
+                    callback.receiveSuccess();
+                }
+            }
+        } else {
+            bluetoothOppTransferProgressAdapter.notifyDataSetChanged();
+            updateReceiver(0);
+            globalSurplus--;
+            updateTipCount();
+        }
+    }
+
+    private void showDoneButton() {
+        bluetoothOppTransferProgressAdapter.notifyDataSetChanged();
+        btnDone.setVisibility(View.VISIBLE);
+        btnDone.setOnClickListener(doneListener);
+        llLoadingCount.setVisibility(View.GONE);
+        tvFileCount.setVisibility(View.GONE);
+        btnReceiverFloating.setVisibility(View.GONE);
+        tvHide.setVisibility(View.GONE);
+        llCurrentFile.setVisibility(View.GONE);
+        setVisibility(View.VISIBLE);
+    }
+
+    public static WindowManager.LayoutParams createLayoutParams() {
+        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+        layoutParams.gravity = Gravity.CENTER;
+        layoutParams.height = android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+        layoutParams.width = 650;
+        layoutParams.format = PixelFormat.TRANSLUCENT;
+        layoutParams.y = 30;
+        layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        return layoutParams;
+    }
+
+    public void setInfo(final NotifyDialogManager ndm, NotifyDialogManager.Info info) {
+        this.info = info;
+        updateFileName(info.filename);
+        tvFileSize.setText(android.text.format.Formatter.formatFileSize(getContext(), info.all));
+        tvHide.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                setHide(true);
+            }
+        });
+        tvCancelTransfer.setOnClickListener(cancelTransferListener);
+        updateReceiver(0);
+    }
+
+
+    public void updateFileName(CharSequence fileName) {
+        String[] names = fileName.toString().split("/");
+        tvFileName.setText(names[names.length - 1]);
+    }
+
+    public void updateReceiver(int progress) {
+        pbProgress.setProgress(progress);
+
+    }
+
+    boolean addedReceiverFloatingBtn;
+    View btnReceiverFloating;
+    TextView tvFileCount, tvSurplus;
+    Button btnDone;
+
+    @Override
+    public void setHide(boolean _h) {
+        super.setHide(_h);
+        if (_h) setVisibility(GONE);
+        if (!addedReceiverFloatingBtn) {
+            addedReceiverFloatingBtn = true;
+            wm.addView(btnReceiverFloating, countLayoutParams());
+            btnReceiverFloating.setOnClickListener(receiverFloatingBtnListener);
+            btnReceiverFloating.setOnTouchListener(receiverFloatingBtnTouchListener);
+        } else {
+            btnReceiverFloating.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private WindowManager.LayoutParams countLayoutParams() {
+        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+        layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+        layoutParams.height = android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+        layoutParams.width = android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+        layoutParams.format = PixelFormat.TRANSLUCENT;
+        layoutParams.x = 50;
+        layoutParams.y = getContext().getResources().getDisplayMetrics().heightPixels - (int) (getContext().getResources().getDisplayMetrics().density * 80 + 10f);
+        layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        return layoutParams;
+    }
+}
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/Constants.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/Constants.java
old mode 100644
new mode 100755
index 84f735d0b1..9b1f650ff2
--- a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/Constants.java
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/Constants.java
@@ -207,7 +207,7 @@ public class Constants {
 
     static final boolean DEBUG = true;
 
-    static final boolean VERBOSE = false;
+    static final boolean VERBOSE = true;
 
     static final int MAX_RECORDS_IN_DATABASE = 50;
 
diff --git a/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/NotifyDialogManager.java b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/NotifyDialogManager.java
new file mode 100755
index 0000000000..4a3256f743
--- /dev/null
+++ b/release/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/opp/NotifyDialogManager.java
@@ -0,0 +1,243 @@
+package com.android.bluetooth.opp;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Message;
+import android.content.BroadcastReceiver;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Toast;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import com.android.bluetooth.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.core.widget.ListViewAutoScrollHelper;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+public class NotifyDialogManager {
+
+    //todo string extra
+    private String TAG = "NotifyDialogManager";
+    private Context context;
+    private WindowManager wm;
+    private Map<Integer, BluetoothBaseUI> dialogs = new HashMap<>();
+
+
+    public static final int RECEIVER_CONFIRM_DIALOG = 0;
+    public static final int RECEIVER_PROGRESS_DIALOG = 1;
+    public static final int RECEIVER_FINISH_DIALOG = 2;
+
+    Handler handler;
+
+    public NotifyDialogManager(Context context) {
+        this.context = context;
+       wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+        handler = new Handler() {
+         @Override
+         public void handleMessage(Message msg) {
+             super.handleMessage(msg);
+             handleMyMessage(msg);
+         }
+     };
+		
+		IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_CANCEL_CONFIRM_DIALOG);
+        filter.addAction(ACTION_RECEIVER_FAILED);
+        filter.addAction(ACTION_RECEIVER_SUCCEED);
+        LocalBroadcastManager.getInstance(context).registerReceiver(new NotifyDialogReceiver(), filter);
+    }
+
+    public boolean isCreated(int type) {
+        return dialogs.containsKey(type);
+    }
+
+
+    private final View.OnClickListener doneListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            cancel(RECEIVER_PROGRESS_DIALOG);
+        }
+    };
+
+    private Info lastInfo;
+    private String deviceName;
+
+    public void setDeviceName(String deviceName) {
+        this.deviceName = deviceName;
+    }
+
+    private  void handleMyMessage(Message msg) {
+        int type = msg.what;
+        Info info = (Info)msg.obj;
+        if (BluetoothReceiverProgressDialog.transferComplete) {
+            if (dialogs.get(RECEIVER_PROGRESS_DIALOG) != null) {
+                cancel(RECEIVER_PROGRESS_DIALOG);
+            }
+            if (dialogs.get(RECEIVER_CONFIRM_DIALOG) != null) {
+                cancel(RECEIVER_CONFIRM_DIALOG);
+            }
+        }
+        BluetoothBaseUI ui = dialogs.get(type);
+        if (lastInfo == null) lastInfo = info;
+        if (ui != null) {
+            ui.update(NotifyDialogManager.this, info);
+        } else {
+            switch (type) {
+                case RECEIVER_CONFIRM_DIALOG:
+                    createReceiverConfirmDialog(info);
+                break;
+                case RECEIVER_PROGRESS_DIALOG:
+                    createReceiverProgressDialog(info);
+                break;
+
+                case RECEIVER_FINISH_DIALOG:
+
+                    BluetoothReceiverProgressDialog dialog = (BluetoothReceiverProgressDialog) dialogs.get(RECEIVER_PROGRESS_DIALOG);
+                    if (dialog == null) {
+                        createReceiverProgressDialog(info);
+                    }
+                    dialog = (BluetoothReceiverProgressDialog) dialogs.get(RECEIVER_PROGRESS_DIALOG);
+                    info.all = lastInfo.all;
+                    dialog.setDoneListener(doneListener);
+                    dialog.finish(info);
+                    lastInfo = null;
+                break;
+        }
+
+	        }
+    }
+
+    public  void notify(final int type, final Info info) {
+        Log.i(TAG, "notify type = " + type + ",info = " + info);
+        Message message = Message.obtain();
+        message.what = type;
+        message.obj = info;
+        handler.sendMessage(message);
+    }
+
+    private BluetoothReceiverProgressDialog.Callback btProgressDialogCallback = new BluetoothReceiverProgressDialog.Callback() {
+        @Override
+        public void sendSuccess() {
+            cancel(RECEIVER_PROGRESS_DIALOG);
+            Toast.makeText(context, R.string.bluetooth_opp_send_success, Toast.LENGTH_LONG).show();
+        }
+
+        @Override
+        public void sendFail() {
+
+        }
+
+        @Override
+        public void receiveSuccess() {
+            cancel(RECEIVER_PROGRESS_DIALOG);
+            Toast.makeText(context, R.string.bluetooth_opp_receiver_success, Toast.LENGTH_LONG).show();
+        }
+
+        @Override
+        public void receiveFail() {
+
+        }
+    };
+
+    private void createReceiverConfirmDialog(Info info) {
+        BluetoothReceiverConfirmDialog view = new BluetoothReceiverConfirmDialog(context);
+        view.setDeviceName(deviceName);
+		view.setInfo(this, info);
+        WindowManager.LayoutParams layoutParams = BluetoothReceiverConfirmDialog.createLayoutParams();
+        //layoutParams.width = (int) (context.getResources().getDisplayMetrics().density * 106 + 0.5f);
+        dialogs.put(RECEIVER_CONFIRM_DIALOG, view);
+        wm.addView(view, layoutParams);
+    }
+
+    private void createReceiverProgressDialog(Info info) {
+        BluetoothReceiverProgressDialog view = new BluetoothReceiverProgressDialog(context);
+        view.setInfo(this, info);
+        view.setCallback(btProgressDialogCallback);
+        view.setHide(true);
+        WindowManager.LayoutParams layoutParams = BluetoothReceiverProgressDialog.createLayoutParams();
+        layoutParams.width = (int) (context.getResources().getDisplayMetrics().density * 291 + 0.5f);
+        dialogs.put(RECEIVER_PROGRESS_DIALOG, view);
+        wm.addView(view, layoutParams);
+    }
+
+    private void createReceiverResult(Info info) {
+        BluetoothReceiverFinishDialog view = new BluetoothReceiverFinishDialog(context);
+		view.setInfo(this, info);
+        WindowManager.LayoutParams layoutParams = BluetoothReceiverFinishDialog.createLayoutParams();
+        dialogs.put(RECEIVER_FINISH_DIALOG, view);
+        wm.addView(view, layoutParams);
+    }
+
+    public void cancel(final int type) {
+        boolean isCreated = isCreated(type);
+        BluetoothBaseUI ui = dialogs.get(type);
+        if (isCreated) {
+            wm.removeView(ui);
+            dialogs.remove(type);
+        }
+    }
+	
+    public void cancelAll() {
+        cancel(RECEIVER_CONFIRM_DIALOG);
+        cancel(RECEIVER_PROGRESS_DIALOG);
+        cancel(RECEIVER_FINISH_DIALOG);
+    }
+
+    public static class Info {
+        public CharSequence filename;
+        public int all;
+        public int done;
+        public boolean finish;
+        public View.OnClickListener cancel;
+        public View.OnClickListener confirm;
+        public boolean receiverSucceed;
+    }
+
+    class NotifyDialogReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction() == null) return;
+			String fileName = intent.getStringExtra("fileName");
+			Log.i(TAG, "fileName = " + fileName);
+//			cancel(RECEIVER_PROGRESS_DIALOG);
+			Info sinfo = new Info();
+			sinfo.filename = fileName;
+            switch (intent.getAction()) {
+                case ACTION_RECEIVER_FAILED: {
+					sinfo.receiverSucceed = false;
+                    break;
+                }
+                case ACTION_RECEIVER_SUCCEED: {
+					sinfo.receiverSucceed = true;
+                    break;
+                }
+                case ACTION_CANCEL_CONFIRM_DIALOG: {
+                    cancel(RECEIVER_CONFIRM_DIALOG);
+                    return;
+                }
+
+            }
+			NotifyDialogManager.this.notify(RECEIVER_FINISH_DIALOG, sinfo);
+        }
+    }
+
+    public static final String ACTION_RECEIVER_FAILED = "notify_manager_receiver_failed";
+    public static final String ACTION_RECEIVER_SUCCEED = "notify_manager_receiver_succeed";
+    public static final String ACTION_CANCEL_CONFIRM_DIALOG = "notify_manager_receiver_cancel_confirm_dialog";
+
+
+}


可能跟不同厂商的代码会有差异,仅供参考。

opp相关修改代码下载地址:

https://download.csdn.net/download/wenzhi20102321/88601215

8、重要过程日志

其实整个蓝牙的接收过程,还是比较复杂的,估计只有开发那个工程师能够知道整个思路。

所以大部分人要进行分析研究,使用日志看出主要过程是最方便和有效的。

一次接收两个文件的部分日志:

//(1)接收socket过程
11-09 18:52:38.890 29667 29755 D BtOppService:  onConnect BluetoothSocket :android.bluetooth.BluetoothSocket@af26de5  
11-09 18:52:38.890 29667 29755 D BtOppService:  :device :18:87:40:11:16:0E
11-09 18:52:38.890 29667 29755 D ObexServerSockets0: Accepting socket connection...
11-09 18:52:38.891 29667 29667 D BtOppService: Get incoming connection
11-09 18:52:38.892 29667 29667 I BtOppService: Start Obex Server

//(2)文件名称
11-09 18:52:39.232 29667  3468 D BluetoothOpp:  File Name IMG_20230830_143647 

 //(3)确认接收第一个文件
11-09 18:52:46.668 29667 29667 D BluetoothOppReceiver:  action :android.btopp.intent.action.ACCEPT
11-09 18:52:46.668 29667 29667 V BluetoothOppReceiver: Receiver ACTION_ACCEPT
11-09 18:52:46.681 29667 29667 V BtOppService: ContentObserver received notification
11-09 18:52:46.701 29667  3554 V BluetoothOppNotification: mUpdateCompleteNotification = true

11-09 18:52:47.706 29667  3567 V BluetoothOppNotification: inbound: succ-0  fail-0
11-09 18:52:47.708 29667  3567 I NotifyDialogManager: notify type = 1,info = com.android.bluetooth.opp.NotifyDialogManager$Info@7178928

//(4)文件接收过程

11-09 18:52:46.688 29667 29667 V BluetoothOppNotification: new notify threadi!
11-09 18:52:46.691 29667  3551 V BtOppService: pendingUpdate is false sListenStarted is true isInterrupted :false
11-09 18:52:46.694 29667 29667 V BluetoothOppNotification: send delay message
11-09 18:52:47.706 29667  3567 V BluetoothOppNotification: inbound: succ-0  fail-0
11-09 18:52:47.708 29667  3567 I NotifyDialogManager: notify type = 1,info = com.android.bluetooth.opp.NotifyDialogManager$Info@7178928


11-09 18:52:49.698 29667 29667 V BluetoothOppNotification: new notify threadi!
11-09 18:52:49.699 29667 29667 V BluetoothOppNotification: send delay message
11-09 18:52:49.701 29667  3589 V BluetoothOppNotification: mUpdateCompleteNotification = false
11-09 18:52:49.701 29667  3589 V BluetoothOppNotification: ACTION_INCOMING_FILE_CONFIRM 
11-09 18:52:49.706 29667  3589 V BluetoothOppNotification: inbound: succ-0  fail-0
11-09 18:52:49.707 29667  3589 I NotifyDialogManager: notify type = 1,info = com.android.bluetooth.opp.NotifyDialogManager$Info@8a6d146

//接收过程可能比较长。。。

//(5)第一个蓝牙文件接收完成
11-09 18:55:17.694 29667 29667 D BluetoothOppReceiver:  action :android.btopp.intent.action.TRANSFER_COMPLETE
11-09 18:55:17.694 29667 29667 V BluetoothOppReceiver: Receiver Transfer Complete Intent for content://com.android.bluetooth.opp/btopp/1

 //(6)接收第二个文件的socket日志
11-09 18:55:54.444 29667 29755 D BtOppService:  onConnect BluetoothSocket :android.bluetooth.BluetoothSocket@c44b63
11-09 18:55:54.444 29667 29755 D BtOppService:  :device :18:87:40:11:16:0E
11-09 18:55:54.445 29667 29755 D ObexServerSockets0: Accepting socket connection...
11-09 18:55:54.445 29667 29667 D BtOppService: Get incoming connection
11-09 18:55:54.445 29667 29667 I BtOppService: Start Obex Server

//(7)名称和信息
11-09 18:55:54.662 29667  5202 D BluetoothOpp: file crated, insertUri:content://media/external/downloads/1000000084
11-09 18:55:54.662 29667  5202 V BtOppObexServer: Generate BluetoothOppReceiveFileInfo:
11-09 18:55:54.662 29667  5202 V BtOppObexServer: filename  :Screenshot_2023-11-09-18-55-30-669_com.miui.gallery_065554.jpg
11-09 18:55:54.662 29667  5202 V BtOppObexServer: length    :752526
11-09 18:55:54.662 29667  5202 V BtOppObexServer: status    :0

 //(8)确认接收第二个文件
11-09 18:55:54.684 29667  5204 I NotifyDialogManager: notify type = 0,info = com.android.bluetooth.opp.NotifyDialogManager$Info@56b4e78
11-09 18:55:58.226 29667 29667 D BluetoothOppReceiver:  action :android.btopp.intent.action.ACCEPT
11-09 18:55:58.227 29667 29667 V BluetoothOppReceiver: Receiver ACTION_ACCEPT

 //(9)接收第二个文件过程
11-09 18:55:59.280 29667 29667 V BluetoothOppNotification: new notify threadi!
11-09 18:55:59.281 29667 29667 V BluetoothOppNotification: send delay message
11-09 18:55:59.286 29667  5261 V BluetoothOppNotification: mUpdateCompleteNotification = false
11-09 18:55:59.286 29667  5261 V BluetoothOppNotification: ACTION_INCOMING_FILE_CONFIRM 
11-09 18:55:59.292 29667  5261 I NotifyDialogManager: notify type = 1,info = com.android.bluetooth.opp.NotifyDialogManager$Info@b2951af
11-09 18:56:00.293 29667  5288 I NotifyDialogManager: notify type = 1,info = com.android.bluetooth.opp.NotifyDialogManager$Info@25e3a84

11-09 18:56:00.283 29667 29667 V BluetoothOppNotification: new notify threadi!
11-09 18:56:00.284 29667 29667 V BluetoothOppNotification: send delay message
11-09 18:56:00.288 29667  5288 V BluetoothOppNotification: ACTION_INCOMING_FILE_CONFIRM 
11-09 18:56:00.293 29667  5288 I NotifyDialogManager: notify type = 1,info = com.android.bluetooth.opp.NotifyDialogManager$Info@25e3a84

//如果是大文件,上面的接收文件过程就比较长。

 //(10)接收第二个文件结束
11-09 18:56:05.119 29667 29667 D BluetoothOppReceiver:  action :android.btopp.intent.action.TRANSFER_COMPLETE
11-09 18:56:05.119 29667 29667 V BluetoothOppReceiver: Receiver Transfer Complete Intent for content://com.android.bluetooth.opp/btopp/2
11-09 18:56:05.150 29667 29667 I NotifyDialogManager: fileName = Screenshot_2023-11-09-18-55-30-669_com.miui.gallery_065554.jpg
11-09 18:56:05.151 29667 29667 I NotifyDialogManager: notify type = 2,info = com.android.bluetooth.opp.NotifyDialogManager$Info@4796225
11-09 18:56:05.151 29667 29667 I BluetoothReceiverProgressDialog: finish info = com.android.bluetooth.opp.NotifyDialogManager$Info@4796225

从上面看关键日志就是进程 29667 相关的日志。

这里只贴了部分日志,有需要的可以看完整日志。

完整logcat日志下载:

https://download.csdn.net/download/wenzhi20102321/88601280

这的日志已经加入patch 的代码,是对话框提示接收和自定义进行对话框显示的日志。

四、总结

1、蓝牙文件接收设计到的广播

文件接收确认,需要自己发送广播:

mContext.sendBroadcast(new Intent(baseIntent).setAction(Constants.ACTION_DECLINE));

文件完成确认

BluetoothOppReceiver.java广播监听:
BluetoothShare.TRANSFER_COMPLETED_ACTION

2、蓝牙文件接收的过程

蓝牙文件确认接收和接收过程已经接收完成都是在 BluetoothOppNotification.java 有体现。

具体过程可以看日志进行确认。

确认接收后,后续的文件是默认自动接收的,如果中间断开接收,后续的文件是无法接收的。

  • 27
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

峥嵘life

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值