Browse Source

博声心电测量仪接入,设备搜索,连接,测量和本地数据生成,网络数据待调试

wzl 11 months ago
parent
commit
4f43613a1f

+ 25 - 0
android_bed/src/main/AndroidManifest.xml

@@ -1,4 +1,5 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="com.wdkl.app.ncs.callingbed">
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
@@ -106,5 +107,29 @@
             </intent-filter>
         </receiver>
 
+        <activity android:name=".ecg.EcgHrvUploadViewActivity"
+            android:turnScreenOn="true"
+            android:screenOrientation="nosensor"/>
+
+        <activity android:name=".ecg.ECGPdfHrvUploadViewer"
+            android:turnScreenOn="true"
+            android:screenOrientation="nosensor"/>
+
+        <activity android:name=".ecg.ECGPdfViewer"
+            android:turnScreenOn="true"
+            android:screenOrientation="nosensor"/>
+
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="com.wdkl.app.ncs.cdbsing.myfileprovider"
+            android:grantUriPermissions="true"
+            android:exported="false"
+            tools:replace="android:authorities">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_paths"
+                tools:replace="android:resource"/>
+        </provider>
+
     </application>
 </manifest>

+ 29 - 6
android_bed/src/main/java/com/wdkl/app/ncs/callingbed/activity/CallingbedActivity.kt

@@ -2,36 +2,35 @@ package com.wdkl.app.ncs.callingbed.activity
 
 
 import android.content.*
-import android.content.pm.PackageManager
 import android.graphics.Color
 import android.net.ConnectivityManager
 import android.os.*
 import android.provider.Settings
 import android.text.TextUtils
 import android.util.Log
-import android.view.KeyEvent
 import android.view.View
 import android.view.ViewTreeObserver
 import androidx.fragment.app.Fragment
 import com.alibaba.fastjson.JSON
 import com.alibaba.fastjson.JSONObject
+import com.borsam.network_lib.code.callback.BorsamHttpCallback
+import com.borsam.network_lib.code.response.BorsamHttpResponse
+import com.borsam.network_lib.manager.BorsamNetworkManager
+import com.borsam.network_lib.manager.UploadManager
 import com.enation.javashop.android.jrouter.external.annotation.Router
 import com.enation.javashop.net.engine.model.NetState
 import com.google.gson.Gson
-
 import com.wdkl.app.ncs.callingbed.BuildConfig
 import com.wdkl.app.ncs.callingbed.R
 import com.wdkl.app.ncs.callingbed.agreement.CallingbedAgreement
 import com.wdkl.app.ncs.callingbed.bt_gateway.*
 import com.wdkl.app.ncs.callingbed.databinding.CallingbedMainNewBinding
-
 import com.wdkl.app.ncs.callingbed.fragment.*
 import com.wdkl.app.ncs.callingbed.hardware.HardWareFactory
 import com.wdkl.app.ncs.callingbed.helper.*
 import com.wdkl.app.ncs.callingbed.launch.CallingbedLaunch
 import com.wdkl.app.ncs.callingbed.settings.SettingConfig
 import com.wdkl.app.ncs.callingbed.sleep.SleepService
-
 import com.wdkl.ncs.android.lib.base.BaseActivity
 import com.wdkl.ncs.android.lib.base.BaseApplication
 import com.wdkl.ncs.android.lib.core.locale.LocaleMangerUtils
@@ -69,7 +68,6 @@ import org.greenrobot.eventbus.Subscribe
 import org.greenrobot.eventbus.ThreadMode
 import java.io.File
 import java.io.FileOutputStream
-import java.io.InputStream
 import java.util.*
 import kotlin.collections.ArrayList
 
@@ -172,6 +170,10 @@ class CallingbedActivity :BaseActivity<BedCallingbedActivityPresenter, Callingbe
         Constant.DEVICE_HEIGHT = dm.heightPixels
         Constant.DEVICE_ORIENTATION = resources.configuration.orientation
 
+        //init BorsamNetwork
+        initBorsamNetwork()
+        //auth Info
+        borsamNetworkAuth()
     }
 
 
@@ -586,6 +588,27 @@ class CallingbedActivity :BaseActivity<BedCallingbedActivityPresenter, Callingbe
     }
 
 
+    /************************************************/
+    /*****************博声心电仪授权验证*****************/
+    /************************************************/
+    private val baseUrl = "http://app.bioxtime.com:28090"
+    private val appId = "50003"
+    private val appSecret = "123456"
+    private fun initBorsamNetwork() {
+        BorsamNetworkManager.getInstance().init(application, baseUrl, appId, appSecret)
+    }
+
+    private fun borsamNetworkAuth() {
+        BorsamNetworkManager.getInstance().authToken().requestAsync(object : BorsamHttpCallback {
+            override fun onResponse(response: BorsamHttpResponse) {
+                Log.d(TAG, "----------onResponse:   " + response.data)
+            }
+
+            override fun onFailure(response: BorsamHttpResponse, ex: Throwable) {
+                Log.d(TAG, "----------onFailure:   $ex")
+            }
+        })
+    }
 
 
 

+ 117 - 0
android_bed/src/main/java/com/wdkl/app/ncs/callingbed/ecg/BorsamUtils.java

@@ -0,0 +1,117 @@
+package com.wdkl.app.ncs.callingbed.ecg;
+
+import android.text.TextUtils;
+
+import com.borsam.device.BorsamDeviceType;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * @author Tiger
+ * @date 2024/7/27
+ * @description
+ */
+public class BorsamUtils {
+    /**
+     * 将记录数据保存为文件
+     *
+     * @return true 保存成功 false 保存失败
+     */
+    public static boolean saveToFile(byte[] data, File file) {
+        return saveToFile(data, file, false);
+    }
+
+    public static boolean saveToFile(byte[] data, File file, boolean append) {
+        if (data != null && file != null && !TextUtils.isEmpty(file.getPath())) {
+
+            if (!file.getParentFile().exists() && !file.mkdirs()) {
+                return false;
+            }
+            if (file.exists() && !file.delete()) {
+                return false;
+            }
+            FileOutputStream fos = null;
+            try {
+                fos = new FileOutputStream(file, append);
+                fos.write(data, 0, data.length);
+                fos.flush();
+                return true;
+            } catch (IOException e) {
+                e.printStackTrace();
+                return false;
+            } finally {
+                try {
+                    if (fos != null) {
+                        fos.close();
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        return false;
+    }
+
+    public static int getDeviceType(@BorsamDeviceType String name) {
+        int type = 0;
+        switch (name) {
+            case BorsamDeviceType.WECARDIO_STD:
+            case BorsamDeviceType.WECARDIO_TTM:
+            case BorsamDeviceType.WECARDIO_STDF:
+            case BorsamDeviceType.SIMPLE_BLE:
+                //200hz单导(STD、TTM、STDF)
+                type = 1;
+                break;
+            case BorsamDeviceType.WECARDIO_PLUS:
+                //160hz三导(PLUS)
+                type = 2;
+                break;
+            case BorsamDeviceType.WECARDIO_LTS:
+                //320hz三导(LTS)
+                type = 3;
+                break;
+            case BorsamDeviceType.WECARDIO_SIXL:
+                //320hz双导(6L)
+                type = 4;
+                break;
+            case BorsamDeviceType.WECARDIO_UNPLUS:
+                //320hz单导UN+)
+                type = 5;
+                break;
+            case BorsamDeviceType.WECARDIO_UN:
+                type = 13;
+                break;
+            case BorsamDeviceType.BLOOD_OXYGEN:
+                //血氧
+                type=102;
+                break;
+            case BorsamDeviceType.BLOOD_PRESSURE:
+                //血压
+                type=103;
+                break;
+            case BorsamDeviceType.BLOOD_SUGAR:
+                //血糖
+                type=104;
+                break;
+            case BorsamDeviceType.BODY_FAT_SCALE:
+                //血糖
+                type=101;
+                break;
+            case BorsamDeviceType.WECARDIO_X3:
+                type=12;
+                break;
+            case BorsamDeviceType.WECARDIO_TW:
+                type = 37;
+                break;
+            case BorsamDeviceType.META_COR:
+                //因为采样率 ad单位 与stdf std相同  故采用相同的device_type
+                type=1;
+                break;
+        }
+
+        return type;
+    }
+}

+ 326 - 0
android_bed/src/main/java/com/wdkl/app/ncs/callingbed/ecg/ECGPdfHrvUploadViewer.java

@@ -0,0 +1,326 @@
+package com.wdkl.app.ncs.callingbed.ecg;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.content.FileProvider;
+
+import com.borsam.device.BorsamDevice;
+import com.borsam.network_lib.code.response.BorsamHttpResponse;
+import com.borsam.network_lib.manager.UploadManager;
+import com.borsam.network_lib.upload.UploadTask;
+import com.borsam.network_lib.upload.callback.UploadRecordCallback;
+import com.borsam.pdf.ECGPdfCreator;
+import com.borsam.util.MD5Utils;
+import com.github.barteksc.pdfviewer.PDFView;
+import com.github.barteksc.pdfviewer.util.FitPolicy;
+import com.wdkl.app.ncs.callingbed.R;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * @author Borsam Medical
+ * @version 1.0
+ **/
+public class ECGPdfHrvUploadViewer extends AppCompatActivity {
+    private static final String TAG="ECGPdfHrvUploadViewer";
+
+    private static final String BORSAM_DEVICE = "borsam_device";
+    private static final String DEVICE_CODE_KEY = "device_code_key";
+    private PDFView mPDFView;
+    private BorsamDevice mBorsamDevice;
+    private String mPdfPath;
+    private String mCodeKey;
+    private AsyncTask<Void, Void, Boolean> mTask;
+
+
+    public static void start(Context context, @NonNull BorsamDevice borsamDevice, String codeKey) {
+        Intent starter = new Intent(context, ECGPdfHrvUploadViewer.class);
+        starter.putExtra(BORSAM_DEVICE, borsamDevice);
+        starter.putExtra(DEVICE_CODE_KEY, codeKey);
+        context.startActivity(starter);
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        //全屏显示
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
+                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
+                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
+                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+
+        setContentView(R.layout.activity_pdf);
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        Button back = findViewById(R.id.button_back);
+        back.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                finish();
+            }
+        });
+
+        mPDFView = findViewById(R.id.pdfView);
+        mBorsamDevice = getIntent().getParcelableExtra(BORSAM_DEVICE);
+        mCodeKey = getIntent().getStringExtra(DEVICE_CODE_KEY);
+        createPdf();
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+                | View.SYSTEM_UI_FLAG_FULLSCREEN);
+    }
+
+    /**
+     * createPdf
+     */
+    @SuppressLint("StaticFieldLeak")
+    private void createPdf() {
+        mTask = new AsyncTask<Void, Void, Boolean>() {
+            @Override
+            protected Boolean doInBackground(Void... voids) {
+                File pdfParentFile = getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
+                File pdfFile = new File(pdfParentFile, "test.pdf");
+                mPdfPath = pdfFile.getPath();
+                ECGPdfCreator ecgPdfCreator = new ECGPdfCreator(ECGPdfHrvUploadViewer.this, mPdfPath);
+                //BorsamDevice#getRecordData if you want to upload record data,
+                //use this method to save data as file and upload it
+                //set pdf config params
+                ecgPdfCreator.setConfig(createPdfConfig());
+                byte[] datas = null;
+                try {
+                    datas = mBorsamDevice.getRecordData();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+                ecgPdfCreator.setData(datas, mBorsamDevice.getSampling(),
+                        mBorsamDevice.getADUnit(), mBorsamDevice.getPassageNumbers(),
+                        mBorsamDevice.isReverse());
+                return ecgPdfCreator.createPdf();
+            }
+
+            @Override
+            protected void onPostExecute(Boolean result) {
+                if (result != null && result) {
+                    mPDFView.fromFile(new File(mPdfPath))
+                            .pageFitPolicy(FitPolicy.BOTH)
+                            .load();
+                    //load local pdf and upload
+                    prepareUpload();
+                }
+            }
+        };
+        mTask.execute();
+    }
+
+    /**
+     * set pdf config
+     * @return
+     */
+    private ECGPdfCreator.Config createPdfConfig(){
+        //TODO set config
+        ECGPdfCreator.Config pdfConfig = new ECGPdfCreator.Config();
+        /** method.1 */
+        //You can hide param if you don't need it
+//    pdfConfig.hideParams(PdfParamsType.PARAMS_AGE,PdfParamsType.PARAMS_ADDRESS);
+        //hide all params and subTitle
+        pdfConfig.hideAll();
+
+        /** method.2 */
+        // setCustomItem(true)  will draw customize text
+        // item is customize text ,column range 0-1
+//    pdfConfig.setCustomItem(true).addItem("cusName:borsam demo-1",0,0)
+//            .addItem("cusAges:100",1,0)
+//            .addItem("cusId:00001",2,0)
+//            .addItem("cusPhone:12345678901",2,1);
+        return pdfConfig;
+    }
+
+    /**
+     * Prepare to upload task
+     */
+    private void prepareUpload(){
+        if(TextUtils.isEmpty(mCodeKey)){
+            Log.e(TAG,"not exists code key can't upload");
+            return;
+        }
+        byte[] ecgDatas = mBorsamDevice.getRecordData();
+        //
+        File saveFile = getSaveFile();
+        boolean success  = BorsamUtils.saveToFile(ecgDatas,saveFile);
+        if(success){
+            //Important parameters for upload task
+            String deviceMac = mBorsamDevice.getAddress();
+            String deviceName = mBorsamDevice.getDeviceName();
+            int deviceType = BorsamUtils.getDeviceType(deviceName);
+            upload(saveFile,deviceMac,deviceType);
+        }else{
+            Log.e(TAG,"save file failed");
+        }
+
+    }
+
+    /**
+     * upload
+     * @param saveFile
+     * @param deviceMac
+     * @param deviceType
+     */
+    private void upload(File saveFile, String deviceMac, int deviceType){
+        //upload task
+        UploadTask uploadTask = new UploadTask();
+        //code Key from BorsamDevice, This mCodeKey is very important
+        uploadTask.setKey(mCodeKey);
+        uploadTask.setFilePath(saveFile.getPath());
+        //BorsamDevice type,  One device corresponds to one type, {@link BorsamUtils.getDeviceType}
+        uploadTask.setDeviceType(deviceType);
+        uploadTask.setDeviceMac(deviceMac);
+        UploadManager.getInstance().upload(uploadTask, new UploadRecordCallback() {
+            @Override
+            public void onUploadStart(@NonNull UploadTask uploadTask) {
+                Log.d(TAG,"--------------onUploadStart---------");
+            }
+
+            @Override
+            public void onUploadProgress(@NonNull UploadTask uploadTask, int progress) {
+                Log.d(TAG,"--------------onUploadStart---------"+progress);
+            }
+
+            @Override
+            public void onUploadSuccess(@NonNull UploadTask uploadTask) {
+                Log.d(TAG,"--------------onUploadSuccess---------");
+            }
+
+            @Override
+            public void onUploadFailure(@NonNull UploadTask uploadTask) {
+                Log.d(TAG,"--------------onUploadFailure---------");
+            }
+
+            @Override
+            public void onError(@NonNull UploadTask uploadTask, Throwable throwable) {
+                Log.d(TAG,"--------------onError---------");
+            }
+
+            @Override
+            public void onUploadTaskCancel(@NonNull UploadTask uploadTask) {
+                Log.d(TAG,"--------------onUploadTaskCancel---------");
+            }
+
+            @Override
+            public void onAddRecordSuccess(@NonNull UploadTask uploadTask, BorsamHttpResponse borsamHttpResponse) {
+                Log.d(TAG,"--------------onAddRecordSuccess---------"+borsamHttpResponse);
+                //file_report is remote pdf
+                //ext is analysis data
+            }
+        });
+    }
+
+    /**
+     * create ECG data files to be uploaded
+     * @return
+     */
+    private File getSaveFile(){
+        File file = new File(getExternalFilesDir(null).getAbsolutePath() + "/ecg_data/");
+        file.mkdirs();
+        return new File(file.getPath(), MD5Utils
+                .MD5(String.valueOf(System.currentTimeMillis())) + ".data");
+    }
+
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mTask != null) {
+            mTask.cancel(true);
+        }
+        //IMPORTANT BorsamDevice#close will clear all datas and listeners
+        mBorsamDevice.close();
+    }
+
+    /**
+     * file to bytes
+     */
+    public static byte[] fileToByteArray(String filePath) throws IOException {
+        FileInputStream fis = new FileInputStream(filePath);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024 * 4];
+        int n;
+        while ((n = fis.read(buffer)) != -1) {
+            out.write(buffer, 0, n);
+        }
+        fis.close();
+        return out.toByteArray();
+    }
+
+    /**
+     * Share pdf
+     */
+    public void share(String pdfPath){
+        File pdfFile = new File(pdfPath);
+        Uri uri = null;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            uri = FileProvider.getUriForFile(this,getPackageName()+".myfileprovider",new File(pdfPath));
+        }else{
+            uri = Uri.fromFile(pdfFile);
+        }
+        Intent intent = Intent.createChooser(getSharePdfIntent(uri,"subject","content"),"share");
+        startActivity(intent);
+    }
+
+    /**
+     * Create Share pdf Intent
+     */
+    public static Intent getSharePdfIntent(Uri pdfUri, CharSequence subject, CharSequence content) {
+        Intent intent = new Intent(Intent.ACTION_SEND);
+        intent.putExtra(Intent.EXTRA_STREAM, pdfUri);
+        intent.putExtra(Intent.EXTRA_SUBJECT, subject);
+        intent.putExtra(Intent.EXTRA_TEXT, content);
+        intent.setType("application/pdf");
+        return intent;
+    }
+
+/*    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.pdf_menu, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.share:
+                share(mPdfPath);
+                break;
+        }
+        return super.onOptionsItemSelected(item);
+    }*/
+}

+ 231 - 0
android_bed/src/main/java/com/wdkl/app/ncs/callingbed/ecg/ECGPdfViewer.java

@@ -0,0 +1,231 @@
+package com.wdkl.app.ncs.callingbed.ecg;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.content.FileProvider;
+
+import com.borsam.device.BorsamDevice;
+import com.borsam.pdf.ECGPdfCreator;
+import com.github.barteksc.pdfviewer.PDFView;
+import com.github.barteksc.pdfviewer.util.FitPolicy;
+import com.wdkl.app.ncs.callingbed.R;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * @author Borsam Medical
+ * @version 1.0
+ **/
+public class ECGPdfViewer extends AppCompatActivity {
+
+    private static final String BORSAM_DEVICE = "borsam_device";
+    private static final String BORSAM_HOLTER_PATH = "ecg_holter_path";
+    private PDFView mPDFView;
+    private BorsamDevice mBorsamDevice;
+    private String mHolterPath;
+    private String mPdfPath;
+    private AsyncTask<Void, Void, Boolean> mTask;
+
+
+    public static void start(Context context, @NonNull BorsamDevice borsamDevice) {
+        Intent starter = new Intent(context, ECGPdfViewer.class);
+        starter.putExtra(BORSAM_DEVICE, borsamDevice);
+        context.startActivity(starter);
+    }
+
+    public static void start(Context context, @NonNull BorsamDevice borsamDevice,String holterPath) {
+        Intent starter = new Intent(context, ECGPdfViewer.class);
+        starter.putExtra(BORSAM_DEVICE, borsamDevice);
+        starter.putExtra(BORSAM_HOLTER_PATH, holterPath);
+        context.startActivity(starter);
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        //全屏显示
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
+                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
+                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
+                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+
+        setContentView(R.layout.activity_pdf);
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        mPDFView = findViewById(R.id.pdfView);
+        mBorsamDevice = getIntent().getParcelableExtra(BORSAM_DEVICE);
+        mHolterPath = getIntent().getStringExtra(BORSAM_HOLTER_PATH);
+
+        createPdf();
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+                | View.SYSTEM_UI_FLAG_FULLSCREEN);
+    }
+
+    /**
+     * createPdf
+     */
+    @SuppressLint("StaticFieldLeak")
+    private void createPdf() {
+        mTask = new AsyncTask<Void, Void, Boolean>() {
+            @Override
+            protected Boolean doInBackground(Void... voids) {
+                File pdfParentFile = getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
+                File pdfFile = new File(pdfParentFile, "test.pdf");
+                mPdfPath = pdfFile.getPath();
+                ECGPdfCreator ecgPdfCreator = new ECGPdfCreator(ECGPdfViewer.this, mPdfPath);
+                //BorsamDevice#getRecordData if you want to upload record data,
+                //use this method to save data as file and upload it
+                //set pdf config params
+                ecgPdfCreator.setConfig(createPdfConfig());
+                byte[] datas = null;
+                try {
+                    if(TextUtils.isEmpty(mHolterPath)){
+                        datas = mBorsamDevice.getRecordData();
+                    }else {
+                        //holter data
+                        datas = fileToByteArray(mHolterPath);
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+                ecgPdfCreator.setData(datas, mBorsamDevice.getSampling(),
+                        mBorsamDevice.getADUnit(), mBorsamDevice.getPassageNumbers(),
+                        mBorsamDevice.isReverse());
+                return ecgPdfCreator.createPdf();
+            }
+
+            @Override
+            protected void onPostExecute(Boolean result) {
+                if (result != null && result) {
+                    mPDFView
+                            .fromFile(new File(mPdfPath))
+                            .pageFitPolicy(FitPolicy.BOTH)
+                            .load();
+                }
+            }
+        };
+        mTask.execute();
+    }
+
+    /**
+     * set pdf config
+     * @return
+     */
+    private ECGPdfCreator.Config createPdfConfig(){
+        //TODO set config
+        ECGPdfCreator.Config pdfConfig = new ECGPdfCreator.Config();
+        /** method.1 */
+        //You can hide param if you don't need it
+//    pdfConfig.hideParams(PdfParamsType.PARAMS_AGE,PdfParamsType.PARAMS_ADDRESS);
+        //hide all params and subTitle
+//    pdfConfig.hideAll();
+
+        /** method.2 */
+        // setCustomItem(true)  will draw customize text
+        // item is customize text ,column range 0-1
+//    pdfConfig.setCustomItem(true).addItem("cusName:borsam demo-1",0,0)
+//            .addItem("cusAges:100",1,0)
+//            .addItem("cusId:00001",2,0)
+//            .addItem("cusPhone:12345678901",2,1);
+        return pdfConfig;
+    }
+
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mTask != null) {
+            mTask.cancel(true);
+        }
+        //IMPORTANT BorsamDevice#close will clear all datas and listeners
+        mBorsamDevice.close();
+    }
+
+    /**
+     * file to bytes
+     */
+    public static byte[] fileToByteArray(String filePath) throws IOException {
+        FileInputStream fis = new FileInputStream(filePath);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024 * 4];
+        int n;
+        while ((n = fis.read(buffer)) != -1) {
+            out.write(buffer, 0, n);
+        }
+        fis.close();
+        return out.toByteArray();
+    }
+
+    /**
+     * Share pdf
+     */
+    public void share(String pdfPath){
+        File pdfFile = new File(pdfPath);
+        Uri uri = null;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            uri = FileProvider.getUriForFile(this,getPackageName()+".myfileprovider",new File(pdfPath));
+        }else{
+            uri = Uri.fromFile(pdfFile);
+        }
+        Intent intent = Intent.createChooser(getSharePdfIntent(uri,"subject","content"),"share");
+        startActivity(intent);
+    }
+
+    /**
+     * Create Share pdf Intent
+     */
+    public static Intent getSharePdfIntent(Uri pdfUri, CharSequence subject, CharSequence content) {
+        Intent intent = new Intent(Intent.ACTION_SEND);
+        intent.putExtra(Intent.EXTRA_STREAM, pdfUri);
+        intent.putExtra(Intent.EXTRA_SUBJECT, subject);
+        intent.putExtra(Intent.EXTRA_TEXT, content);
+        intent.setType("application/pdf");
+        return intent;
+    }
+
+/*    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.pdf_menu, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.share:
+                share(mPdfPath);
+                break;
+        }
+        return super.onOptionsItemSelected(item);
+    }*/
+}

+ 376 - 0
android_bed/src/main/java/com/wdkl/app/ncs/callingbed/ecg/EcgHrvUploadViewActivity.java

@@ -0,0 +1,376 @@
+package com.wdkl.app.ncs.callingbed.ecg;
+
+import android.bluetooth.BluetoothGatt;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.util.Log;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.borsam.ble.callback.BatteryCallback;
+import com.borsam.ble.callback.BorsamBleGattCallback;
+import com.borsam.ble.callback.SimpleBorsamBleDataCallback;
+import com.borsam.blecore.exception.BleException;
+import com.borsam.device.BorsamDevice;
+import com.borsam.device.callback.DataPartTwoListener;
+import com.borsam.device.callback.OnAddPointListener;
+import com.borsam.jni.callback.HeatRateListener;
+import com.borsam.widget.ECGAnimationType;
+import com.borsam.widget.ECGView;
+import com.wdkl.app.ncs.callingbed.R;
+
+/**
+ * @author Borsam Medical
+ * @version 1.0
+ **/
+public class EcgHrvUploadViewActivity extends AppCompatActivity implements BorsamDevice.BorsamDeviceCodeCallback {
+    private static final String TAG="EcgViewActivity";
+    //We only receive 120s data
+    private final int TOTAL_TIME = 120;
+    private static final String BORSAM_DEVICE = "borsam_device";
+    private BorsamDevice mBorsamDevice;
+    private ECGView mECGView;
+    private boolean goPdf;
+    private boolean mEnd;
+    private TextView tvBleState;
+    private TextView tvEcgTime;
+    private TextView tvHrValue;
+    private TextView tvBattery;
+
+    private CountDownTimer countDownTimer;
+    private long clickTime = 0;
+
+    public static void start(Context context, @NonNull BorsamDevice borsamDevice) {
+        Intent starter = new Intent(context, EcgHrvUploadViewActivity.class);
+        starter.putExtra(BORSAM_DEVICE, borsamDevice);
+        context.startActivity(starter);
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        //全屏显示
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
+                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
+                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
+                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+
+        setContentView(R.layout.activity_ecgview);
+
+        mBorsamDevice = getIntent().getParcelableExtra(BORSAM_DEVICE);
+        mECGView = findViewById(R.id.ecgView);
+        tvBleState = findViewById(R.id.tv_ble_state);
+        tvEcgTime = findViewById(R.id.tv_ecg_time);
+        tvHrValue = findViewById(R.id.tv_hr_value);
+        tvBattery = findViewById(R.id.tv_battery);
+
+        LinearLayout backLayout = findViewById(R.id.ll_ble_state);
+        backLayout.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                onBackPressed();
+            }
+        });
+
+        initEcgTimer();
+
+        //IMPORTANT:set ecg params
+        mECGView.setECGParams(mBorsamDevice);
+        mECGView.setECGAnimationType(ECGAnimationType.COVER);
+        //线条红色
+        mECGView.setLineColor(Color.parseColor("#FF0000"));
+        mECGView.setDotColor(Color.parseColor("#000000"));
+
+        mBorsamDevice.setHeatRateListener(new HeatRateListener() {
+            @Override
+            public void onReceiver(int hrValue) {
+                setHRValue(hrValue);
+            }
+        });
+        //IMPORTANT:set this listener to display data
+        mBorsamDevice.setOnAddPointListener(new OnAddPointListener() {
+            /**
+             * Callback this method on add point
+             * @param point ecg data
+             * @param passageIndex passage number start with 1 not 0
+             */
+            @Override
+            public void onAddPoint(int point, int passageIndex) {
+                //IMPORTANT:Call this method ECG to display data
+                mECGView.addPoint(point, passageIndex);
+            }
+        });
+        connect();
+    }
+
+    private void initEcgTimer() {
+        countDownTimer = new CountDownTimer(TOTAL_TIME*1000L, 1000) {
+            @Override
+            public void onTick(long l) {
+                tvEcgTime.setText("剩余时间: " + l/1000 + "秒");
+            }
+
+            @Override
+            public void onFinish() {
+                tvEcgTime.setText("剩余时间: 0秒");
+
+                getDataEnd();
+            }
+        };
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+                | View.SYSTEM_UI_FLAG_FULLSCREEN);
+    }
+
+    @Override
+    public void onBackPressed() {
+        long currentTime = System.currentTimeMillis();
+        // 3秒内连按两次后退按钮,退出应用
+        if (currentTime - clickTime < 3000) {
+            super.onBackPressed();
+        } else {
+            Toast.makeText(EcgHrvUploadViewActivity.this, "再按一次退出", Toast.LENGTH_SHORT).show();
+            clickTime = currentTime;
+        }
+    }
+
+    /**
+     * connect Borsam Device
+     */
+    private void connect() {
+        //BorsamDevice#connect will clear the previously connected data
+        //if you don't want,just use BorsamDevice#connect(BorsamBleGattCallback callback,boolean clearData)
+        //param:clearData if <code>true</code> will clear the previously connected data
+        //<code>false</code> otherwise
+        mBorsamDevice.connect(new BorsamBleGattCallback() {
+            /**
+             * Callback in the main thread
+             * Callback this method when the connection starts
+             */
+            @Override
+            public void onStartConnect() {
+                setBleState("开始连接");
+            }
+
+            /**
+             * Callback in the main thread
+             * Callback method when connection fails
+             */
+            @Override
+            public void onConnectFail(BorsamDevice borsamDevice, BleException e) {
+                setBleState("连接失败");
+            }
+
+            /**
+             * Callback in the main thread
+             * Callback this method when the connection is successful
+             */
+            @Override
+            public void onConnectSuccess(BorsamDevice borsamDevice, BluetoothGatt bluetoothGatt,
+                                         int status) {
+                setBleState("连接成功");
+
+                if (countDownTimer != null) {
+                    countDownTimer.start();
+                }
+                getEcgData();
+            }
+
+            /**
+             * Callback in the main thread
+             * Callback this method when the connection is broken
+             * @param isActiveDisConnected if <code>true</code> indicate disconnect by {@link BorsamDevice#disConnect()}
+             * <code>false</code> disconnect by device
+             */
+            @Override
+            public void onDisConnected(boolean isActiveDisConnected, BorsamDevice borsamDevice,
+                                       BluetoothGatt bluetoothGatt, int status) {
+                setBleState("连接断开");
+                if(isActiveDisConnected){
+                    connect();
+                }
+            }
+        });
+    }
+
+    /**
+     * must call method BorsamDevice.getData(...)
+     * getBattery
+     */
+    private void getBattery(){
+        mBorsamDevice.getBattery(new BatteryCallback() {
+            @Override
+            public void onError(BleException e) {
+                /**
+                 * must call method BorsamDevice.getData(...)
+                 * not receive battery!!!
+                 * because not received device callback data
+                 * try again
+                 */
+                getBattery();
+            }
+
+            @Override
+            public void onSuccess(int batteryPercent) {
+                //get battery Success
+                updateBattery(batteryPercent);
+            }
+        });
+    }
+
+    private void updateBattery(int batteryPercent){
+        tvBattery.setText("电量: " + batteryPercent + "%");
+    }
+
+    /**
+     * get ecg data
+     * Usually, we recommend to delay the acquisition of ECG data after a successful connection.
+     * But here, it’s not so particular.
+     */
+    private void getEcgData() {
+        //SimpleBorsamBleDataCallback extend BorsamBleDataCallback
+        //It implements onDataChanged and has empty body
+        //Generally, you will probably not pay attention onDataChanged
+        //BorsamDevice#getData default clear progress last ecg data
+        //If you want to progress consolidation,use BorsamDevice#getData(BorsamBleDataCallback callback,boolean keepProgress)
+        mBorsamDevice.getData(new SimpleBorsamBleDataCallback() {
+            /**
+             * data success
+             */
+            @Override
+            public void onDataSuccess() {
+                setBleState("获取数据成功");
+                getBattery();
+
+            }
+
+            /**
+             * data failure
+             */
+            @Override
+            public void onDataFailure(BleException e) {
+                setBleState("获取数据失败");
+            }
+
+            /**
+             * data progress
+             * @param second unit:s
+             */
+            @Override
+            public void onDataProgress(float second) {
+                /*if (mEnd){
+                    return;
+                }
+
+                if (second >= TOTAL_TIME) {
+                    getDataEnd();
+                }*/
+            }
+        });
+    }
+
+    /**
+     * get data end
+     * Some devices have the second part of the data
+     * So we need to judge here
+     */
+    private void getDataEnd() {
+        mEnd = true;
+        if (mBorsamDevice.hasDataPartTwo()) {
+            mBorsamDevice.setDataPartTwoListener(new DataPartTwoListener() {
+                @Override
+                public void onDataStart() {
+                    setBleState("开始获取第二部分数据");
+                }
+
+                /**
+                 * Second part progress
+                 * @param progress from 0~100
+                 */
+                @Override
+                public void onDataProgress(float progress) {
+                    setBleState("获取第二部分数据: " + progress);
+                }
+
+                @Override
+                public void onDataEnd(boolean success) {
+                    if (success) {
+                        setBleState("第二部分数据获取成功");
+                        mBorsamDevice.getCodeAsync(EcgHrvUploadViewActivity.this);
+                    } else {
+                        setBleState("第二部分数据获取失败");
+                    }
+                }
+            });
+        } else {
+            mBorsamDevice.getCodeAsync(this);
+        }
+    }
+
+    /**
+     * Jump Pdf Page
+     */
+    private void goPdf(String code) {
+        goPdf = true;
+        ECGPdfHrvUploadViewer.start(this,mBorsamDevice,code);
+        finish();
+    }
+
+    private void setBleState(String state) {
+        tvBleState.setText(state);
+    }
+
+    private void setHRValue(int value) {
+        tvHrValue.setText("心率: " + value);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (countDownTimer != null) {
+            countDownTimer.cancel();
+        }
+
+        //BorsamDevice#close will disconnect device and clear all datas and listeners
+        //if you don't want to clear datas,use BorsamDevice#disconnect
+        if (goPdf) {
+            mBorsamDevice.disConnect();
+        } else {
+            mBorsamDevice.close();
+        }
+    }
+
+
+    @Override
+    public void onSuccess(String code) {
+        Log.d(TAG,code);
+        goPdf(code);
+    }
+
+    @Override
+    public void onDataFailure(BleException e) {
+        Log.d(TAG,e.toString());
+        goPdf(null);
+    }
+}

+ 68 - 12
android_bed/src/main/java/com/wdkl/app/ncs/callingbed/fragment/MainFragment.kt

@@ -2,6 +2,11 @@ package com.wdkl.app.ncs.callingbed.fragment
 
 import android.util.Log
 import android.view.View
+import com.borsam.ble.BorsamBleManager
+import com.borsam.ble.callback.BorsamBleScanCallback
+import com.borsam.blecore.scan.BleScanRuleConfig
+import com.borsam.device.BorsamDevice
+import com.borsam.device.BorsamDeviceType
 import com.enation.javashop.net.engine.model.NetState
 import com.inuker.bluetooth.library.Constants
 import com.inuker.bluetooth.library.Constants.STATUS_CONNECTED
@@ -20,6 +25,7 @@ import com.wdkl.app.ncs.callingbed.R
 import com.wdkl.app.ncs.callingbed.bt_gateway.BluetoothUtil
 import com.wdkl.app.ncs.callingbed.databinding.MainViewLayoutBinding
 import com.wdkl.app.ncs.callingbed.dialog.WifiSetDialogHelper
+import com.wdkl.app.ncs.callingbed.ecg.EcgHrvUploadViewActivity
 import com.wdkl.app.ncs.callingbed.launch.CallingbedLaunch
 import com.wdkl.app.ncs.callingbed.utils.ClientManager
 import com.wdkl.ncs.android.lib.base.BaseFragment
@@ -74,7 +80,7 @@ class  MainFragment: BaseFragment<BedMainFragmentPresenter, MainViewLayoutBindin
     //血压计设备发往手机
     val xya_mCharacter2: UUID = UUID.fromString("0000fff4-0000-1000-8000-00805f9b34fb")
 
-
+    var scanning = false
 
     override fun getLayId(): Int {
         return R.layout.main_view_layout
@@ -147,10 +153,11 @@ class  MainFragment: BaseFragment<BedMainFragmentPresenter, MainViewLayoutBindin
         //心电图
         sing_main_4_ll.setOnClickListener {
             bt_name= xya_bt_name
-            dv_type= 2
+            dv_type= 4
             //扫码蓝牙
             if (ClientManager.getClient().isBluetoothOpened) {
-                searchDevice()
+                //博声心电仪扫描
+                scanBSDevice()
             } else {
                 showMessage(getString(R.string.ble_device_5))
                 ClientManager.getClient().openBluetooth()
@@ -241,10 +248,13 @@ class  MainFragment: BaseFragment<BedMainFragmentPresenter, MainViewLayoutBindin
                             ClientManager.getClient().notify(bt_mac, xy_mService, xy_mCharacter2, xya_mNotifyRsp)
                         }else if (dv_type==3){
                             ClientManager.getClient().notify(bt_mac, xy_mService, xy_mCharacter2, xy_mNotifyRsp)
-                        }else if (dv_type==4){
-                            ClientManager.getClient().notify(bt_mac, xy_mService, xy_mCharacter2, xy_mNotifyRsp)
                         }
 
+                        //心电使用专用sdk扫描
+                        /*else if (dv_type==4){
+                            ClientManager.getClient().notify(bt_mac, xy_mService, xy_mCharacter2, xy_mNotifyRsp)
+                        }*/
+
                     }else{
                         //连接失败
                         WifiSetDialogHelper.showDialog(activity, 4)
@@ -305,7 +315,11 @@ class  MainFragment: BaseFragment<BedMainFragmentPresenter, MainViewLayoutBindin
     private val mBluetoothStateListener: BluetoothStateListener = object : BluetoothStateListener() {
         override fun onBluetoothStateChanged(openOrClosed: Boolean) {
             if (openOrClosed){
-                searchDevice()
+                if (dv_type == 4) {
+                    scanBSDevice()
+                } else {
+                    searchDevice()
+                }
             }
 
         }
@@ -335,12 +349,14 @@ class  MainFragment: BaseFragment<BedMainFragmentPresenter, MainViewLayoutBindin
     }
 
     fun updateConnectState(text: String, show: Boolean) {
-        if (show) {
-            tv_connect_state.text = text
-            ll_ble_connect.visibility = View.VISIBLE
-        } else {
-            tv_connect_state.text = ""
-            ll_ble_connect.visibility = View.GONE
+        activity.runOnUiThread {
+            if (show) {
+                tv_connect_state.text = text
+                ll_ble_connect.visibility = View.VISIBLE
+            } else {
+                tv_connect_state.text = ""
+                ll_ble_connect.visibility = View.GONE
+            }
         }
     }
 
@@ -405,4 +421,44 @@ class  MainFragment: BaseFragment<BedMainFragmentPresenter, MainViewLayoutBindin
             }
         }
     }
+
+
+    //博声心电仪扫描
+    fun scanBSDevice() {
+        if (scanning) {
+            showMessage("正在搜索中...")
+            return
+        }
+
+        //You can change the scan configuration before scanning
+        //parameter boolean:fuzzy if true Will return the device whose device name contains the configured device name
+        //if false Will return the device whose device name matches the configured device name
+        //val bleScanRuleConfig = BleScanRuleConfig.Builder().setScanTimeOut(15 * 1000)
+        //    .setDeviceName(true, BorsamDeviceType.WECARDIO_STD, BorsamDeviceType.WECARDIO_LTS)
+        //    .build()
+        //BorsamBleManager.getInstance().initScanRule(bleScanRuleConfig)
+
+        var findBle = false
+        BorsamBleManager.getInstance().scan(object : BorsamBleScanCallback() {
+            override fun onScanStarted(success: Boolean) {
+                scanning = success
+                updateConnectState("正在搜索...", true)
+            }
+
+            override fun onScanning(borsamDevice: BorsamDevice) {
+                Log.i(TAG, "onScanning: $borsamDevice")
+                if (!findBle && BorsamDeviceType.SUMMARY.contains(borsamDevice.name)) {
+                    findBle = true
+                    EcgHrvUploadViewActivity.start(activity, borsamDevice)
+                }
+            }
+
+            override fun onScanFinished(list: List<BorsamDevice>) {
+                Log.d(TAG, "onScanFinished: $list")
+                scanning = false
+                updateConnectState("", false)
+                WifiSetDialogHelper.showDialog(activity, 3)
+            }
+        })
+    }
 }

+ 5 - 0
android_bed/src/main/res/drawable/ic_arrow_back.xml

@@ -0,0 +1,5 @@
+<vector android:height="48dp" android:tint="#2F9DF1"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#2F9DF1" android:pathData="M11.67,3.87L9.9,2.1 0,12l9.9,9.9 1.77,-1.77L3.54,12z"/>
+</vector>

+ 74 - 0
android_bed/src/main/res/layout/activity_ecgview.xml

@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/ll_ble_state"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="10dp">
+
+        <ImageView
+            android:layout_width="40dp"
+            android:layout_height="32dp"
+            android:src="@mipmap/back_press"
+            android:scaleType="centerInside"/>
+
+        <TextView
+            android:id="@+id/tv_ble_state"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="10dp"
+            android:gravity="center"
+            android:layout_gravity="center_vertical"
+            android:text="连接中"
+            android:textSize="24sp"
+            android:textStyle="bold"/>
+    </LinearLayout>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="40dp"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        android:paddingLeft="20dp"
+        android:paddingRight="20dp">
+
+        <TextView
+            android:id="@+id/tv_ecg_time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="24sp"
+            android:textColor="@color/main_color"
+            android:textStyle="bold"/>
+
+        <TextView
+            android:id="@+id/tv_hr_value"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:text="心率:"
+            android:textSize="24sp"
+            android:textColor="@color/main_color"
+            android:textStyle="bold"/>
+
+        <TextView
+            android:id="@+id/tv_battery"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:text="电量:"
+            android:textSize="24sp"
+            android:textColor="@color/main_color"
+            android:textStyle="bold"/>
+
+    </RelativeLayout>
+
+    <com.borsam.widget.ECGView
+        android:id="@+id/ecgView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</LinearLayout>

+ 38 - 0
android_bed/src/main/res/layout/activity_pdf.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:app="http://schemas.android.com/apk/res-auto"
+  android:layout_width="match_parent"
+  android:layout_height="match_parent"
+  android:orientation="vertical">
+
+  <androidx.appcompat.widget.Toolbar
+    android:id="@+id/toolbar"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/colorPrimary"
+    app:layout_constraintEnd_toEndOf="parent"
+    app:layout_constraintStart_toStartOf="parent"
+    app:layout_constraintTop_toTopOf="parent"
+    app:title="PDF预览"
+    app:titleTextColor="#ffffff" />
+
+  <com.github.barteksc.pdfviewer.PDFView
+    android:id="@+id/pdfView"
+    android:layout_width="0dp"
+    android:layout_height="0dp"
+    app:layout_constraintBottom_toBottomOf="parent"
+    app:layout_constraintEnd_toEndOf="parent"
+    app:layout_constraintStart_toStartOf="parent"
+    app:layout_constraintTop_toBottomOf="@+id/toolbar" >
+
+    <Button
+        android:id="@+id/button_back"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="10dp"
+        android:text="返回" />
+  </com.github.barteksc.pdfviewer.PDFView>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 6 - 0
android_bed/src/main/res/xml/file_paths.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <paths>
+        <external-path path="" name="pdf"/>
+    </paths>
+</resources>

+ 3 - 0
app/build.gradle

@@ -214,6 +214,9 @@ android {
     //解决冲突
     packagingOptions {
         pickFirst 'lib/armeabi-v7a/libc++_shared.so'
+        pickFirst 'lib/x86/libc++_shared.so'
+        pickFirst 'lib/arm64-v8a/libc++_shared.so'
+        pickFirst 'lib/x86_64/libc++_shared.so'
     }
 }
 

+ 4 - 1
app/src/main/code/com/wdkl/app/ncs/application/Application.kt

@@ -3,8 +3,8 @@ package com.wdkl.app.ncs.application
 import android.app.Activity
 import android.content.Context
 import android.content.res.Configuration
+import com.borsam.ble.BorsamBleManager
 import com.enation.javashop.android.jrouter.JRouter
-import com.enation.javashop.android.welcome.BuildConfig
 import com.wdkl.ncs.android.lib.base.BaseApplication
 import com.wdkl.ncs.android.lib.core.locale.LocaleMangerUtils
 import com.wdkl.ncs.android.lib.core.locale.SettingConfigNew
@@ -95,6 +95,9 @@ class Application : BaseApplication() {
      * @return rx观察者
      */
     private fun initFrame() {
+        //this method can be called anywhere,just before using the library
+        //Not necessarily here
+        BorsamBleManager.getInstance().init(this)
     }
 
     override fun attachBaseContext(base: Context) {

+ 22 - 0
common/build.gradle

@@ -53,6 +53,10 @@ android {
         targetCompatibility JavaVersion.VERSION_1_8
     }
 
+    configurations {
+        all*.exclude group: 'com.squareup.okhttp3', module: 'okhttp'
+        all*.exclude group: 'com.squareup.okhttp3', module: 'logging-interceptor'
+    }
 }
 
 dependencies {
@@ -270,6 +274,24 @@ dependencies {
 
     compile project(':resource')
 
+    //心电
+    api(name: 'borsam-ble-v1.3.09-release', ext: 'aar')
+    api(name: 'borsam-network-v1.0.0-release', ext: 'aar')
+
+    //implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
+    //implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
+    //implementation 'com.squareup.okhttp3:okhttp:3.12.0'
+    //implementation 'com.squareup.retrofit2:retrofit:2.3.0'
+    //implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
+    //implementation 'io.reactivex.rxjava2:rxjava:2.2.19'
+    //implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
+    //implementation 'com.google.code.gson:gson:2.8.6'
+    compile ('com.github.ihsanbal:LoggingInterceptor:3.0.0') {
+        exclude group: 'org.json', module: 'json'
+    }
+    compile ('com.github.barteksc:android-pdf-viewer:3.1.0-beta.1') {
+        exclude group: 'com.android.support'
+    }
 
 }
 

BIN
common/libs/borsam-ble-v1.3.09-release.aar


BIN
common/libs/borsam-network-v1.0.0-release.aar