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 有体现。

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

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

相关推荐
阿巴斯甜18 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker19 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952720 小时前
Andorid Google 登录接入文档
android
黄林晴21 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android