| ¶Ô±ÈÐÂÎļþ |
| | |
| | | *.iml |
| | | .gradle |
| | | /local.properties |
| | | /.idea/caches |
| | | /.idea/libraries |
| | | /.idea/modules.xml |
| | | /.idea/workspace.xml |
| | | /.idea/navEditor.xml |
| | | /.idea/assetWizardSettings.xml |
| | | .DS_Store |
| | | /build |
| | | /captures |
| | | .externalNativeBuild |
| | | .cxx |
| | | local.properties |
| | | .idea |
| | | app/release |
| | | app/src/androidTest |
| | | app/src/test |
| | | bluetooth-manager-lib/src/androidTest |
| | | bluetooth-manager-lib/src/test |
| | | gradle |
| | | gradlew |
| | | gradlew.bat |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | plugins { |
| | | id 'com.android.application' |
| | | } |
| | | |
| | | android { |
| | | namespace "com.shlb.comb" |
| | | |
| | | compileSdk 34 |
| | | |
| | | defaultConfig { |
| | | applicationId "com.shlb.comb" |
| | | minSdk 21 |
| | | targetSdk 34 |
| | | versionCode 1 |
| | | versionName "1.0" |
| | | |
| | | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
| | | } |
| | | |
| | | buildTypes { |
| | | release { |
| | | minifyEnabled false |
| | | minifyEnabled true |
| | | shrinkResources true |
| | | zipAlignEnabled true |
| | | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
| | | } |
| | | debug { |
| | | minifyEnabled false |
| | | minifyEnabled true |
| | | shrinkResources true |
| | | zipAlignEnabled true |
| | | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
| | | } |
| | | } |
| | | lintOptions { |
| | | abortOnError false |
| | | } |
| | | compileOptions { |
| | | sourceCompatibility JavaVersion.VERSION_1_8 |
| | | targetCompatibility JavaVersion.VERSION_1_8 |
| | | } |
| | | buildFeatures { |
| | | viewBinding true |
| | | } |
| | | |
| | | applicationVariants.all { variant -> |
| | | if (variant.buildType.name == "release") { |
| | | variant.outputs.all { |
| | | def date = new Date().format("yyyyMMddHHmmss") |
| | | outputFileName = "comb-release-${date}.apk" |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | dependencies { |
| | | |
| | | implementation 'androidx.appcompat:appcompat:1.3.0' |
| | | implementation 'com.google.android.material:material:1.4.0' |
| | | implementation project(path: ':bluetooth-manager-lib') |
| | | testImplementation 'junit:junit:4.13.2' |
| | | androidTestImplementation 'androidx.test.ext:junit:1.1.3' |
| | | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' |
| | | implementation 'com.qmuiteam:qmui:2.0.1' |
| | | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.0' |
| | | implementation 'org.greenrobot:eventbus:3.0.0' |
| | | implementation 'pub.devrel:easypermissions:3.0.0' |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | #-------------------------------------------å®å¶ååºå---------------------------------------------- |
| | | #---------------------------------1.å®ä½ç±»--------------------------------- |
| | | |
| | | -keep class com.shlb.comb.model.** { *; } |
| | | |
| | | #------------------------------------------------------------------------- |
| | | |
| | | #---------------------------------2.ç¬¬ä¸æ¹å
------------------------------- |
| | | |
| | | #eventbus |
| | | -keepattributes *Annotation* |
| | | -keepclassmembers class * { |
| | | @org.greenrobot.eventbus.Subscribe <methods>; |
| | | } |
| | | -keep enum org.greenrobot.eventbus.ThreadMode { *; } |
| | | |
| | | # If using AsyncExecutord, keep required constructor of default event used. |
| | | # Adjust the class name if a custom failure event type is used. |
| | | -keepclassmembers class org.greenrobot.eventbus.util.ThrowableFailureEvent { |
| | | <init>(java.lang.Throwable); |
| | | } |
| | | ##--------------- Androidx ---------- |
| | | -keep class com.google.android.material.** {*;} |
| | | -keep class androidx.** {*;} |
| | | -keep public class * extends androidx.** |
| | | -keep interface androidx.** {*;} |
| | | -dontwarn com.google.android.material.** |
| | | -dontnote com.google.android.material.** |
| | | -dontwarn androidx.** |
| | | ##--------------- Androidx ---------- |
| | | |
| | | ##--------------- QmUi ---------- |
| | | -keep class **_FragmentFinder { *; } |
| | | -keep class androidx.fragment.app.* { *; } |
| | | |
| | | -keep class com.qmuiteam.qmui.arch.record.RecordIdClassMap { *; } |
| | | -keep class com.qmuiteam.qmui.arch.record.RecordIdClassMapImpl { *; } |
| | | |
| | | -keep class com.qmuiteam.qmui.arch.scheme.SchemeMap {*;} |
| | | -keep class com.qmuiteam.qmui.arch.scheme.SchemeMapImpl {*;} |
| | | ##--------------- QmUi ---------- |
| | | |
| | | # Accessed via reflection, avoid renaming or removal |
| | | -keep class org.greenrobot.eventbus.android.AndroidComponentsImpl |
| | | |
| | | #fastjson |
| | | -keepattributes Signature |
| | | -dontwarn com.alibaba.fastjson.** |
| | | -keep class com.alibaba.fastjson.**{*; } |
| | | #picasso |
| | | -dontwarn com.squareup.okhttp.** |
| | | #okhttp #okio |
| | | -dontwarn okhttp3.** |
| | | -dontwarn okio.** |
| | | -dontwarn javax.annotation.** |
| | | #retrofit2 |
| | | -dontwarn retrofit2.** |
| | | #rx |
| | | -dontwarn rx.** |
| | | #alipay |
| | | -dontwarn com.alipay.** |
| | | #rxeasyhttp |
| | | -keep class com.zhouyou.http.model.** {*;} |
| | | -keep class com.zhouyou.http.cache.model.** {*;} |
| | | -keep class com.zhouyou.http.cache.stategy.**{*;} |
| | | |
| | | |
| | | # -keep class com.zhouyou.http.** { *;} |
| | | #MPAndroidchart |
| | | -keep class com.github.mikephil.** { *;} |
| | | #pgy |
| | | -dontwarn com.pgyersdk.** |
| | | -keep class com.pgyersdk.** { *; } |
| | | #litpal |
| | | -keep class org.litepal.** { |
| | | *; |
| | | } |
| | | |
| | | -keep class * extends org.litepal.crud.DataSupport { |
| | | *; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | #------------------------------------------------------------------------- |
| | | |
| | | #---------------------------------3.ä¸jsäºç¸è°ç¨çç±»------------------------ |
| | | |
| | | |
| | | |
| | | #------------------------------------------------------------------------- |
| | | |
| | | #---------------------------------4.åå°ç¸å
³çç±»åæ¹æ³----------------------- |
| | | #-keep class com.shlanbao.lbmonitoring.activity.LbSearchActivity |
| | | # |
| | | #-keep class com.shlanbao.lbmonitoring.view.LbSearchView |
| | | |
| | | |
| | | |
| | | #---------------------------------------------------------------------------- |
| | | #--------------------------------------------------------------------------------------------------- |
| | | |
| | | #-------------------------------------------åºæ¬ä¸ç¨å¨åºå-------------------------------------------- |
| | | #---------------------------------åºæ¬æä»¤åº---------------------------------- |
| | | # ä»£ç æ··æ·å缩æ¯ï¼å¨0~7ä¹é´ |
| | | -optimizationpasses 5 |
| | | |
| | | # æ··åæ¶ä¸ä½¿ç¨å¤§å°åæ··åï¼æ··ååçç±»å为å°å |
| | | -dontusemixedcaseclassnames |
| | | |
| | | # æå®ä¸å»å¿½ç¥éå
Œ
±åºçç±»æå |
| | | -dontskipnonpubliclibraryclasses |
| | | # ä¸å颿 ¡éªï¼preverifyæ¯proguardçå个æ¥éª¤ä¹ä¸ï¼Androidä¸éè¦preverifyï¼å»æè¿ä¸æ¥è½å¤å å¿«æ··æ·é度ã |
| | | -dontpreverify |
| | | |
| | | # æ··æ·æ¶æ¯å¦è®°å½æ¥å¿ï¼è¿å¥è¯è½å¤ä½¿æä»¬çé¡¹ç®æ··æ·åäº§çæ å°æä»¶ |
| | | # å
å«æç±»å->æ··æ·åç±»åçæ å°å
³ç³» |
| | | -verbose |
| | | |
| | | # é¿å
æ··æ·æ³å |
| | | -keepattributes Signature |
| | | |
| | | # ä¿çAnnotation䏿··æ· |
| | | -keepattributes *Annotation*,InnerClasses |
| | | |
| | | #googleæ¨èç®æ³ |
| | | -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* |
| | | |
| | | # é¿å
æ··æ·Annotationãå
é¨ç±»ãæ³åãå¿åç±» |
| | | -keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod |
| | | |
| | | # éå½åæåºå¼å¸¸æ¶çæä»¶åç§° |
| | | -renamesourcefileattribute SourceFile |
| | | |
| | | # æåºå¼å¸¸æ¶ä¿ç代ç è¡å· |
| | | -keepattributes SourceFile,LineNumberTable |
| | | |
| | | # 忽ç¥è¦å |
| | | -ignorewarnings |
| | | # ä¼åä¸ä¼åè¾å
¥çç±»æä»¶ |
| | | -dontoptimize |
| | | |
| | | # ä¿çRä¸é¢çèµæº |
| | | -keep class **.R$* {*;} |
| | | |
| | | # ä¿çæä¸¾ç±»ä¸è¢«æ··æ· |
| | | -keepclassmembers enum * { |
| | | public static **[] values(); |
| | | public static ** valueOf(java.lang.String); |
| | | } |
| | | #---------------------------------------------------------------------------- |
| | | |
| | | #---------------------------------é»è®¤ä¿çåº--------------------------------- |
| | | -keep public class * extends android.app.Activity |
| | | -keep public class * extends android.app.Application |
| | | -keep public class * extends android.app.Service |
| | | -keep public class * extends android.content.BroadcastReceiver |
| | | -keep public class * extends android.content.ContentProvider |
| | | -keep public class * extends android.app.backup.BackupAgentHelper |
| | | -keep public class * extends android.preference.Preference |
| | | -keep public class * extends android.view.View |
| | | -keep public class com.android.vending.licensing.ILicensingService |
| | | -keep class android.support.** {*;} |
| | | |
| | | -keep public class * extends android.view.View{ |
| | | *** get*(); |
| | | void set*(***); |
| | | public <init>(android.content.Context); |
| | | public <init>(android.content.Context, android.util.AttributeSet); |
| | | public <init>(android.content.Context, android.util.AttributeSet, int); |
| | | } |
| | | -keepclasseswithmembers class * { |
| | | public <init>(android.content.Context); |
| | | public <init>(android.content.Context, android.util.AttributeSet); |
| | | public <init>(android.content.Context, android.util.AttributeSet, int); |
| | | } |
| | | -keepclassmembers class * implements java.io.Serializable { |
| | | static final long serialVersionUID; |
| | | private static final java.io.ObjectStreamField[] serialPersistentFields; |
| | | private void writeObject(java.io.ObjectOutputStream); |
| | | private void readObject(java.io.ObjectInputStream); |
| | | java.lang.Object writeReplace(); |
| | | java.lang.Object readResolve(); |
| | | } |
| | | -keep class **.R$* { |
| | | *; |
| | | } |
| | | -keepclassmembers class * { |
| | | void *(**On*Event); |
| | | } |
| | | #---------------------------------------------------------------------------- |
| | | |
| | | #---------------------------------webview------------------------------------ |
| | | -keepclassmembers class fqcn.of.javascript.interface.for.Webview { |
| | | public *; |
| | | } |
| | | -keepclassmembers class * extends android.webkit.WebViewClient { |
| | | public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap); |
| | | public boolean *(android.webkit.WebView, java.lang.String); |
| | | } |
| | | -keepclassmembers class * extends android.webkit.WebViewClient { |
| | | public void *(android.webkit.WebView, jav.lang.String); |
| | | } |
| | | #---------------------------------------------------------------------------- |
| | | #--------------------------------------------------------------------------------------------------- |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:tools="http://schemas.android.com/tools" |
| | | package="com.shlb.comb"> |
| | | <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> |
| | | <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> |
| | | <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> |
| | | <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> |
| | | <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> |
| | | <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> |
| | | <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> |
| | | <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> |
| | | <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> |
| | | <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> |
| | | <uses-permission android:name="android.permission.INTERNET" /> |
| | | <uses-sdk |
| | | tools:overrideLibrary="com.qmuiteam.qmui" |
| | | /> |
| | | |
| | | |
| | | <application |
| | | android:allowBackup="true" |
| | | android:icon="@mipmap/ic_launcher" |
| | | android:label="@string/app_name" |
| | | android:supportsRtl="true" |
| | | android:theme="@style/AppTheme"> |
| | | |
| | | |
| | | |
| | | |
| | | <activity |
| | | android:name="com.shlb.comb.SplashActivity" |
| | | android:screenOrientation="portrait" |
| | | android:theme="@style/SplashTheme" |
| | | android:exported="true"> |
| | | <intent-filter> |
| | | <action android:name="android.intent.action.MAIN" /> |
| | | |
| | | <category android:name="android.intent.category.LAUNCHER" /> |
| | | </intent-filter> |
| | | </activity> |
| | | <activity |
| | | android:name=".activity.ParameterSettingActivity" |
| | | android:screenOrientation="portrait" |
| | | android:windowSoftInputMode="adjustPan" /> |
| | | <activity |
| | | android:name=".activity.AdvancedSettingActivity" |
| | | android:screenOrientation="portrait" |
| | | android:windowSoftInputMode="adjustPan" /> |
| | | <activity android:name=".activity.HomeActivity" |
| | | android:screenOrientation="portrait" |
| | | /> |
| | | <activity android:name=".activity.ConnActivity" |
| | | android:screenOrientation="portrait" |
| | | /> |
| | | <activity android:name=".activity.WriteActivity" |
| | | android:screenOrientation="portrait" |
| | | /> |
| | | <activity android:name=".activity.CurrentDeviceActivity" |
| | | android:screenOrientation="portrait" |
| | | /> |
| | | <activity android:name=".activity.TestActivity" |
| | | android:screenOrientation="portrait" /> |
| | | <activity android:name=".activity.NormalWriteActivity" /> |
| | | |
| | | |
| | | </application> |
| | | |
| | | </manifest> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb; |
| | | |
| | | import android.app.Activity; |
| | | import android.os.Bundle; |
| | | import android.util.Log; |
| | | |
| | | |
| | | |
| | | public class Application extends android.app.Application{ |
| | | private static final String TAG = "Application:"; |
| | | private static Application instance; |
| | | private int count = 0; |
| | | @Override |
| | | public void onCreate() { |
| | | super.onCreate(); |
| | | instance = this; |
| | | } |
| | | public Application getInstance() { |
| | | return instance; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | private void ActivityLifecycleCallbacks() { |
| | | registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { |
| | | @Override |
| | | public void onActivityStopped(Activity activity) { |
| | | // Logger.d(TAG + activity + "onActivityStopped"); |
| | | count--; |
| | | if (count == 0) { |
| | | Log.e(TAG , activity.getClass().getSimpleName()+">>>>>>>>>>>>>>>>>>>åå°åå° lifecycle"); |
| | | // if(activity.getClass().getSimpleName().contains("MainActivity")) { |
| | | // Intent tent = new Intent(getPackageName() + AppConfig.BACKSTAGE);// 广æçæ ç¾ï¼ä¸å®è¦åéè¦æ¥åçä¸è´ã |
| | | //// sendBroadcast(tent);// åé广æ |
| | | // LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(tent); |
| | | // } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityStarted(Activity activity) { |
| | | // Logger.e(TAG + activity + "onActivityStarted"); |
| | | if (count == 0) { |
| | | Log.e(TAG , ">>>>>>>>>>>>>>>>>>>åå°åå° lifecycle"); |
| | | // if(activity.getClass().getSimpleName().contains("MainActivity")){ |
| | | // Intent tent = new Intent(getPackageName() + AppConfig.RECEPTION);// 广æçæ ç¾ï¼ä¸å®è¦åéè¦æ¥åçä¸è´ã |
| | | //// sendBroadcast(tent);// åé广æ |
| | | // LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(tent); |
| | | // } |
| | | } |
| | | count++; |
| | | } |
| | | |
| | | @Override |
| | | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { |
| | | Log.e(TAG , activity + "onActivitySaveInstanceState"); |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityResumed(Activity activity) { |
| | | Log.e(TAG , activity + "onActivityResumed"); |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityPaused(Activity activity) { |
| | | Log.e(TAG , activity + "onActivityPaused"); |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityDestroyed(Activity activity) { |
| | | Log.e(TAG , activity + "onActivityDestroyed"); |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { |
| | | Log.e(TAG , activity + "onActivityCreated"); |
| | | } |
| | | }); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb; |
| | | |
| | | |
| | | import android.content.Intent; |
| | | import android.os.Build; |
| | | import android.os.Bundle; |
| | | import android.view.WindowManager; |
| | | |
| | | import androidx.annotation.Nullable; |
| | | import androidx.appcompat.app.AppCompatActivity; |
| | | |
| | | import com.shlb.comb.activity.HomeActivity; |
| | | import com.shlb.comb.view.ParticleView; |
| | | |
| | | |
| | | public class SplashActivity extends AppCompatActivity { |
| | | |
| | | ParticleView ptview; |
| | | |
| | | @Override |
| | | protected void onCreate(@Nullable Bundle savedInstanceState) { |
| | | super.onCreate(savedInstanceState); |
| | | if (Build.VERSION.SDK_INT >= 28) { |
| | | WindowManager.LayoutParams lp = getWindow().getAttributes(); |
| | | lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; |
| | | getWindow().setAttributes(lp); |
| | | } |
| | | setContentView(R.layout.activity_splash); |
| | | |
| | | |
| | | ptview = findViewById(R.id.ptview); |
| | | ptview.startAnim(); |
| | | ptview.setOnParticleAnimListener(new ParticleView.ParticleAnimListener() { |
| | | @Override |
| | | public void onAnimationEnd() { |
| | | startActivity(new Intent(SplashActivity.this, HomeActivity.class)); |
| | | finish(); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.activity; |
| | | |
| | | import android.view.View; |
| | | import android.widget.Button; |
| | | import android.widget.EditText; |
| | | import android.widget.TextView; |
| | | import android.text.Html; |
| | | import android.widget.Toast; |
| | | |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.base.BaseActivity; |
| | | import com.shlb.comb.event.UpdateEvent; |
| | | import com.shlb.comb.manager.BleGlobalManager; |
| | | import com.shlb.comb.util.CMD; |
| | | import com.shlb.comb.util.CRCutil; |
| | | |
| | | import org.greenrobot.eventbus.Subscribe; |
| | | import org.greenrobot.eventbus.ThreadMode; |
| | | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | import java.util.Locale; |
| | | |
| | | public class AdvancedSettingActivity extends BaseActivity { |
| | | |
| | | private EditText et_mainboard_version; |
| | | private Button btn_mainboard_read; |
| | | private EditText et_display_version; |
| | | private Button btn_display_read; |
| | | private Button btn_start_addressing; |
| | | private Button btn_end_addressing; |
| | | private Button btn_clear_log; |
| | | |
| | | private TextView tv_log; |
| | | private TextView tv_right_text; |
| | | private TextView tv_mainboard_status; |
| | | private TextView tv_sensor_status; |
| | | private StringBuilder logBuilder = new StringBuilder(); |
| | | private String currentExecutingCmd = ""; |
| | | |
| | | @Override |
| | | protected void contentView() { |
| | | setContentView(R.layout.activity_advanced_setting); |
| | | } |
| | | |
| | | @Override |
| | | protected void initView() { |
| | | super.initHead(); |
| | | if (tv_center != null) { |
| | | tv_center.setText("é«çº§è®¾ç½®"); |
| | | } |
| | | if (iv_left != null) { |
| | | iv_left.setImageResource(R.mipmap.icon_back); |
| | | } |
| | | |
| | | et_mainboard_version = findViewById(R.id.et_mainboard_version); |
| | | btn_mainboard_read = findViewById(R.id.btn_mainboard_read); |
| | | et_display_version = findViewById(R.id.et_display_version); |
| | | btn_display_read = findViewById(R.id.btn_display_read); |
| | | btn_start_addressing = findViewById(R.id.btn_start_addressing); |
| | | btn_end_addressing = findViewById(R.id.btn_end_addressing); |
| | | btn_clear_log = findViewById(R.id.btn_clear_log); |
| | | |
| | | tv_log = findViewById(R.id.tv_log); |
| | | tv_right_text = findViewById(R.id.tv_right_text); |
| | | tv_mainboard_status = findViewById(R.id.tv_mainboard_status); |
| | | tv_sensor_status = findViewById(R.id.tv_sensor_status); |
| | | |
| | | // Initialize log |
| | | logBuilder.append("æ¥å¿è®°å½:<br>"); |
| | | tv_log.setText(Html.fromHtml(logBuilder.toString())); |
| | | } |
| | | |
| | | @Override |
| | | protected void initData() { |
| | | updateBluetoothStatus(); |
| | | if (BleGlobalManager.getInstance().isConnected()) { |
| | | sendCmdWithCrc(CMD.ENTER_SETTING); |
| | | triggerAutoRead(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | protected void onDestroy() { |
| | | if (BleGlobalManager.getInstance().isConnected()) { |
| | | sendCmdWithCrc(CMD.EXIT_SETTING); |
| | | } |
| | | super.onDestroy(); |
| | | } |
| | | |
| | | @Override |
| | | protected void initEvent() { |
| | | View.OnClickListener listener = new View.OnClickListener() { |
| | | @Override |
| | | public void onClick(View v) { |
| | | int id = v.getId(); |
| | | String action = ""; |
| | | if (id == R.id.btn_mainboard_read) { |
| | | action = "读å主æ¿çæ¬"; |
| | | currentExecutingCmd = CMD.READ_BOARD_VERSION; |
| | | sendCmdWithCrc(CMD.READ_BOARD_VERSION); |
| | | } else if (id == R.id.btn_display_read) { |
| | | action = "读åä¼ æå¨çæ¬"; |
| | | currentExecutingCmd = CMD.READ_SENSOR_VERSION; |
| | | sendCmdWithCrc(CMD.READ_SENSOR_VERSION); |
| | | } else if (id == R.id.btn_start_addressing) { |
| | | action = "å¼å§ç¼å"; |
| | | currentExecutingCmd = CMD.WRITE_START_ADDRESS; |
| | | sendCmdWithCrc(CMD.WRITE_START_ADDRESS); |
| | | } else if (id == R.id.btn_end_addressing) { |
| | | action = "ç»æç¼å"; |
| | | currentExecutingCmd = CMD.WRITE_END_ADDRESS; |
| | | sendCmdWithCrc(CMD.WRITE_END_ADDRESS); |
| | | } else if (id == R.id.btn_clear_log) { |
| | | logBuilder.setLength(0); |
| | | logBuilder.append("æ¥å¿è®°å½:<br>"); |
| | | tv_log.setText(Html.fromHtml(logBuilder.toString())); |
| | | } |
| | | |
| | | if (!action.isEmpty() && id != R.id.btn_start_addressing && id != R.id.btn_end_addressing) { |
| | | // Toast.makeText(AdvancedSettingActivity.this, action, Toast.LENGTH_SHORT).show(); |
| | | // addLog(action); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | btn_mainboard_read.setOnClickListener(listener); |
| | | btn_display_read.setOnClickListener(listener); |
| | | btn_start_addressing.setOnClickListener(listener); |
| | | btn_end_addressing.setOnClickListener(listener); |
| | | btn_clear_log.setOnClickListener(listener); |
| | | } |
| | | |
| | | private void addLog(String message) { |
| | | appendLog(message, null); |
| | | } |
| | | |
| | | private String getCmdDescription(String hex) { |
| | | if (hex == null) return ""; |
| | | |
| | | // åéæä»¤å¹é
|
| | | if (hex.startsWith(CMD.ENTER_SETTING)) return "-è¿å
¥è®¾å®"; |
| | | if (hex.startsWith(CMD.EXIT_SETTING)) return "-éåºè®¾å®"; |
| | | if (hex.startsWith(CMD.READ_BOARD_VERSION)) return "-读å主æ¿çæ¬"; |
| | | if (hex.startsWith(CMD.READ_SENSOR_VERSION)) return "-读åä¼ æå¨çæ¬"; |
| | | if (hex.startsWith(CMD.WRITE_START_ADDRESS)) return "-å¼å§ç¼å"; |
| | | if (hex.startsWith(CMD.WRITE_END_ADDRESS)) return "-ç»æç¼å"; |
| | | |
| | | // æ¥æ¶æ°æ®å¹é
|
| | | // 读å主æ¿çæ¬è¿å (A55A03 0A ...) |
| | | if (hex.startsWith("A55A030A")) return "-读å主æ¿çæ¬"; |
| | | // 读åä¼ æå¨çæ¬è¿å (A55A03 0B ...) |
| | | if (hex.startsWith("A55A030B")) return "-读åä¼ æå¨çæ¬"; |
| | | |
| | | // åå
¥è¿å (A55A06å¼å¤´) |
| | | if (hex.startsWith("A55A06") && hex.length() >= 12) { |
| | | return "-åå
¥è¿å"; |
| | | } |
| | | |
| | | return ""; |
| | | } |
| | | |
| | | private void appendLog(String msg, Boolean isSent) { |
| | | String time = com.blankj.utilcode.util.TimeUtils.getNowString(new java.text.SimpleDateFormat("HH:mm:ss.SSS")); |
| | | |
| | | String displayMsg = msg; |
| | | String cmdDesc = ""; |
| | | |
| | | // å¦ææ¯ hex æä»¤ (纯 0-9 A-F a-f)ï¼å ç©ºæ ¼æ ¼å¼å |
| | | if (msg.matches("^[0-9A-Fa-f]+$")) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (int i = 0; i < msg.length(); i += 2) { |
| | | if (i + 2 <= msg.length()) { |
| | | sb.append(msg.substring(i, i + 2)).append(" "); |
| | | } else { |
| | | sb.append(msg.substring(i)); |
| | | } |
| | | } |
| | | displayMsg = sb.toString().trim(); |
| | | |
| | | // è·åæä»¤æè¿° |
| | | cmdDesc = getCmdDescription(msg); |
| | | } |
| | | |
| | | String logLine; |
| | | if (isSent != null) { |
| | | String color = isSent ? "#1890ff" : "#05aa87"; // Blue for sent, Green for received |
| | | // æ¼æ¥å°åç¼åé¢: "åé-读ç«å·: " æ "æ¶å°-读ç«å·: " |
| | | String prefix = (isSent ? "åé" : "æ¶å°") + cmdDesc + ": "; |
| | | logLine = time + " <font color='" + color + "'>" + prefix + displayMsg + "</font><br>"; |
| | | } else { |
| | | // System/Local log (Black) |
| | | logLine = time + " " + displayMsg + "<br>"; |
| | | } |
| | | |
| | | logBuilder.append(logLine); |
| | | if (tv_log != null) { |
| | | tv_log.setText(Html.fromHtml(logBuilder.toString())); |
| | | } |
| | | } |
| | | |
| | | private void updateBluetoothStatus() { |
| | | if (tv_right_text == null) return; |
| | | if (BleGlobalManager.getInstance().isConnected()) { |
| | | String name = "æªç¥è®¾å¤"; |
| | | if (BleGlobalManager.getInstance().getBluetoothLeDevice() != null) { |
| | | String deviceName = BleGlobalManager.getInstance().getBluetoothLeDevice().getName(); |
| | | if (deviceName != null && !deviceName.isEmpty()) { |
| | | name = deviceName; |
| | | } |
| | | } |
| | | tv_right_text.setText(name); |
| | | } else { |
| | | tv_right_text.setText("æªè¿æ¥"); |
| | | } |
| | | } |
| | | |
| | | private void triggerAutoRead() { |
| | | showLoading("æ£å¨åæ¥çæ¬ä¿¡æ¯..."); |
| | | // Reset status |
| | | setLabelStatus(tv_mainboard_status, "", R.color.base_text); |
| | | setLabelStatus(tv_sensor_status, "", R.color.base_text); |
| | | |
| | | // Read Mainboard |
| | | new android.os.Handler().postDelayed(() -> { |
| | | if (btn_mainboard_read != null) btn_mainboard_read.performClick(); |
| | | }, 200); |
| | | |
| | | // Read Sensor |
| | | new android.os.Handler().postDelayed(() -> { |
| | | if (btn_display_read != null) btn_display_read.performClick(); |
| | | }, 1200); |
| | | |
| | | // Hide loading |
| | | new android.os.Handler().postDelayed(() -> hiddeLoading(), 2500); |
| | | } |
| | | |
| | | private void setLabelStatus(TextView view, String text, int colorResId) { |
| | | if (view != null) { |
| | | view.setText(text); |
| | | view.setTextColor(getResources().getColor(colorResId)); |
| | | } |
| | | } |
| | | |
| | | private void sendCmdWithCrc(String cmd) { |
| | | if (!BleGlobalManager.getInstance().isConnected()) { |
| | | Toast.makeText(this, "请å
è¿æ¥èç", Toast.LENGTH_SHORT).show(); |
| | | addLog("é误: èçæªè¿æ¥"); |
| | | return; |
| | | } |
| | | |
| | | byte[] cmdBytes = BleGlobalManager.hexStringToBytes(cmd); |
| | | if (cmdBytes != null) { |
| | | String crc = CRCutil.getCRC(cmdBytes); |
| | | // Pad CRC to 4 chars if needed |
| | | while (crc.length() < 4) { |
| | | crc = "0" + crc; |
| | | } |
| | | String fullCmd = cmd + crc.toUpperCase(); |
| | | |
| | | appendLog(fullCmd, true); // true for sent (Blue) |
| | | BleGlobalManager.getInstance().sendCmd(fullCmd); |
| | | } else { |
| | | addLog("é误: æä»¤è½¬æ¢å¤±è´¥"); |
| | | } |
| | | } |
| | | |
| | | @Subscribe(threadMode = ThreadMode.MAIN) |
| | | public void onEvent(UpdateEvent event) { |
| | | if (event.getType() == UpdateEvent.Type.CONN_STATU) { |
| | | updateBluetoothStatus(); |
| | | if (event.getObj() instanceof Integer && (int) event.getObj() == android.bluetooth.BluetoothProfile.STATE_CONNECTED) { |
| | | triggerAutoRead(); |
| | | } |
| | | } else if (event.getType() == UpdateEvent.Type.DEVICE_INFO) { |
| | | String hex = event.getMsg(); |
| | | appendLog(hex, false); // false for received (Green) |
| | | parseAndRefresh(hex); |
| | | } |
| | | } |
| | | |
| | | private void parseAndRefresh(String hex) { |
| | | if (hex == null) return; |
| | | |
| | | // åå
¥æä»¤çè¿å (A55A06å¼å¤´) |
| | | if (hex.startsWith("A55A06") && hex.length() >= 12) { |
| | | parseWriteResponse(hex); |
| | | return; |
| | | } |
| | | |
| | | // 读åæä»¤çè¿å (A55A03å¼å¤´) |
| | | if (hex.startsWith("A55A03") && hex.length() >= 8) { |
| | | parseReadResponse(hex); |
| | | } |
| | | } |
| | | |
| | | private void parseReadResponse(String hex) { |
| | | try { |
| | | // å½ä»¤ç±»å: 第7-8ä½ (Index 6-8) |
| | | String cmdType = hex.substring(6, 8); |
| | | |
| | | // æ°æ®é¨åé常ä»ç¬¬11-12ä½å¼å§ (Index 10) |
| | | // åè®¾çæ¬å·æ°æ®å°±å¨åé¢ |
| | | String dataHex = ""; |
| | | if (hex.length() > 10) { |
| | | // 廿CRC (æå4ä½)? ä¸ç¡®å®é¿åº¦ï¼å
åææ |
| | | // ææ¶å substring(10) ç´å° length-4 (妿另CRC) |
| | | // ç®åèµ·è§ï¼å substring(10) |
| | | dataHex = hex.substring(10); |
| | | // 妿æå4使¯CRCï¼å¯è½éè¦å»æãé常 CRC æ¯æå4å符ã |
| | | if (dataHex.length() > 4) { |
| | | dataHex = dataHex.substring(0, dataHex.length() - 4); |
| | | } |
| | | } |
| | | |
| | | if ("0A".equals(cmdType)) { |
| | | // 主æ¿çæ¬ |
| | | String version = formatVersionHex(dataHex); |
| | | if (et_mainboard_version != null) et_mainboard_version.setText(version); |
| | | setLabelStatus(tv_mainboard_status, "(读åå¼)", R.color.base_color); |
| | | addLog("读å主æ¿çæ¬æå: " + version); |
| | | } else if ("0B".equals(cmdType)) { |
| | | // ä¼ æå¨çæ¬ |
| | | String version = formatVersionHex(dataHex); |
| | | if (et_display_version != null) et_display_version.setText(version); |
| | | setLabelStatus(tv_sensor_status, "(读åå¼)", R.color.base_color); |
| | | addLog("读åä¼ æå¨çæ¬æå: " + version); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | addLog("è§£æè¯»åè¿åå¼å¸¸: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private String formatVersionHex(String hex) { |
| | | if (hex == null || hex.isEmpty()) return ""; |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (int i = 0; i < hex.length(); i++) { |
| | | sb.append(hex.charAt(i)); |
| | | if (i % 2 == 1 && i < hex.length() - 1) { |
| | | sb.append("-"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | private void parseWriteResponse(String hex) { |
| | | try { |
| | | // åå
¥æä»¤çè¿å (A55A06å¼å¤´) |
| | | if (hex.startsWith("A55A06") && hex.length() >= 12) { |
| | | // ç»æç¶æ: 第11-12ä½ (Index 10-12) |
| | | String statusHex = hex.substring(10, 12); |
| | | boolean isSuccess = "01".equals(statusHex); |
| | | |
| | | if (!currentExecutingCmd.isEmpty()) { |
| | | if (isSuccess) { |
| | | if (currentExecutingCmd.startsWith(CMD.WRITE_START_ADDRESS)) { |
| | | addLog("å¼å§ç¼åæå"); |
| | | Toast.makeText(this, "å¼å§ç¼åæå", Toast.LENGTH_SHORT).show(); |
| | | } else if (currentExecutingCmd.startsWith(CMD.WRITE_END_ADDRESS)) { |
| | | addLog("ç»æç¼åæå"); |
| | | Toast.makeText(this, "ç»æç¼åæå", Toast.LENGTH_SHORT).show(); |
| | | } |
| | | } else { |
| | | addLog("æä½å¤±è´¥: " + hex); |
| | | Toast.makeText(this, "æä½å¤±è´¥", Toast.LENGTH_SHORT).show(); |
| | | } |
| | | // Reset current command after handling |
| | | currentExecutingCmd = ""; |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | addLog("è§£æå¼å¸¸: " + e.getMessage()); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.activity; |
| | | |
| | | import android.bluetooth.BluetoothGatt; |
| | | import android.bluetooth.BluetoothGattCallback; |
| | | import android.bluetooth.BluetoothGattCharacteristic; |
| | | import android.bluetooth.BluetoothGattService; |
| | | import android.bluetooth.BluetoothProfile; |
| | | import android.content.Intent; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.connect.BluetoothConnectManager; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectState; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectStateListener; |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | import com.blakequ.bluetooth_manager_lib.util.BluetoothUtils; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | import com.qmuiteam.qmui.util.QMUIDisplayHelper; |
| | | import com.qmuiteam.qmui.widget.QMUIEmptyView; |
| | | import com.qmuiteam.qmui.widget.dialog.QMUIDialog; |
| | | import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction; |
| | | import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView; |
| | | import com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.base.BaseActivity; |
| | | import com.shlb.comb.event.UpdateEvent; |
| | | import com.shlb.comb.util.Singletion; |
| | | |
| | | import org.greenrobot.eventbus.EventBus; |
| | | import org.greenrobot.eventbus.Subscribe; |
| | | import org.greenrobot.eventbus.ThreadMode; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | public class ConnActivity extends BaseActivity { |
| | | QMUIGroupListView mGroupListView; |
| | | QMUIEmptyView mEmptyView; |
| | | |
| | | //è¿æ¥ç®¡ç |
| | | private BluetoothConnectManager connectManager; |
| | | //è¿æ¥ç¶æ |
| | | private int connectState = 0; |
| | | //æ¯å¦è¿æ¥ |
| | | private boolean mConnected = false; |
| | | |
| | | |
| | | @Override |
| | | protected void contentView() { |
| | | setContentView(R.layout.activity_conn); |
| | | mGroupListView = findViewById(R.id.groupListView); |
| | | mEmptyView = findViewById(R.id.emptyView); |
| | | } |
| | | |
| | | @Override |
| | | protected void initView() { |
| | | super.initHead(); |
| | | initTitlebar(); |
| | | tv_center.setText("èçä¿¡æ¯"); |
| | | mEmptyView.show(true); |
| | | |
| | | |
| | | initGroupListView(); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void initData() { |
| | | BluetoothLeDevice mDevice = Singletion.getInstance().mDevice; |
| | | initConn(mDevice); |
| | | |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void initEvent() { |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public void onClick(View v) { |
| | | int id = v.getId(); |
| | | if (id == R.id.iv_left) { |
| | | this.onBackPressed(); |
| | | } else if (id == R.id.iv_right) { |
| | | } |
| | | } |
| | | |
| | | @Subscribe(threadMode = ThreadMode.MAIN) |
| | | public void onEventRefresh(UpdateEvent event) { |
| | | switch (event.getType()) { |
| | | case CONN_STATU: |
| | | |
| | | int statu = (int) event.getObj(); |
| | | //0-æå¼ 2-è¿æ¥ |
| | | switch (statu) { |
| | | case 0: |
| | | mConnected = false; |
| | | Singletion.getInstance().mConnected = false; |
| | | break; |
| | | case 2: |
| | | mConnected = true; |
| | | Singletion.getInstance().mConnected = true; |
| | | mEmptyView.hide(); |
| | | Toast("è¿æ¥æåï¼"); |
| | | break; |
| | | } |
| | | |
| | | break; |
| | | case CONN_SERVICE: |
| | | //è·åæå¡æååæ´æ°UI |
| | | updateUi(); |
| | | break; |
| | | |
| | | |
| | | case SCAN_UPDATE: |
| | | |
| | | |
| | | break; |
| | | case CONFIG_CHANGE: |
| | | |
| | | break; |
| | | } |
| | | } |
| | | |
| | | private ConnectStateListener stateListener = new ConnectStateListener() { |
| | | @Override |
| | | public void onConnectStateChanged(String address, ConnectState state) { |
| | | switch (state) { |
| | | case CONNECTED: |
| | | |
| | | |
| | | break; |
| | | case CONNECTING: |
| | | |
| | | |
| | | break; |
| | | case NORMAL: |
| | | |
| | | |
| | | break; |
| | | } |
| | | invalidateOptionsMenu(); |
| | | } |
| | | }; |
| | | |
| | | //è¿æ¥è®¾å¤ |
| | | private void initConn(BluetoothLeDevice device) { |
| | | if (connectManager == null) { |
| | | connectManager = BluetoothConnectManager.getInstance(this); |
| | | connectManager.addConnectStateListener(stateListener); |
| | | connectManager.setBluetoothGattCallback(new BluetoothGattCallback() { |
| | | |
| | | @Override |
| | | public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { |
| | | if (status == BluetoothGatt.GATT_SUCCESS) { |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.BLE_DATA, characteristic, "read")); |
| | | } else { |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.BLE_DATA, characteristic, "fail")); |
| | | LogUtils.e(TAG, "fail to read characteristic"); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { |
| | | super.onCharacteristicWrite(gatt, characteristic, status); |
| | | if (status == BluetoothGatt.GATT_SUCCESS) { |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.BLE_DATA, characteristic, "write")); |
| | | } else { |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.BLE_DATA, characteristic, "fail")); |
| | | LogUtils.e(TAG, "fail to write characteristic"); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { |
| | | super.onCharacteristicChanged(gatt, characteristic); |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.BLE_DATA, characteristic, "notify")); |
| | | } |
| | | |
| | | @Override |
| | | public void onConnectionStateChange(BluetoothGatt gatt, final int status, int newState) { |
| | | super.onConnectionStateChange(gatt, status, newState); |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.CONN_STATU, newState, "conn_statu")); |
| | | connectState = newState; |
| | | |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public void onServicesDiscovered(final BluetoothGatt gatt, int status) { |
| | | super.onServicesDiscovered(gatt, status); |
| | | if (status == BluetoothGatt.GATT_SUCCESS) { |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | displayGattServices(gatt.getServices()); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | } |
| | | }); |
| | | } |
| | | connectManager.connect(device.getAddress()); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * è·åå½åè¿æ¥èçå¯åæå¡ |
| | | * |
| | | * @param gattServices |
| | | */ |
| | | private void displayGattServices(List<BluetoothGattService> gattServices) { |
| | | Singletion.getInstance().mbluetoothServices.clear(); |
| | | List<Map<String, String>> res = new ArrayList<>(); |
| | | for (final BluetoothGattService gattService : gattServices) { |
| | | final List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics(); |
| | | for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { |
| | | Map<String, String> map = new HashMap<>(); |
| | | |
| | | String property = getPropertyString(gattCharacteristic.getProperties()); |
| | | //åªéè¦å¯åæå¡ |
| | | if (property.contains("Write")) { |
| | | map.put("name", property); |
| | | map.put("uuid", gattCharacteristic.getUuid().toString()); |
| | | res.add(map); |
| | | } |
| | | } |
| | | } |
| | | Singletion.getInstance().mbluetoothServices.addAll(res); |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.CONN_SERVICE)); |
| | | |
| | | } |
| | | |
| | | private String getPropertyString(int property) { |
| | | StringBuilder sb = new StringBuilder("("); |
| | | //Read |
| | | if ((property & BluetoothGattCharacteristic.PROPERTY_READ) > 0) { |
| | | sb.append("Read "); |
| | | } |
| | | //Write |
| | | if ((property & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0 |
| | | || (property & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) { |
| | | sb.append("Write "); |
| | | } |
| | | //Notify |
| | | if ((property & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { |
| | | sb.append("Notity "); |
| | | } |
| | | |
| | | if ((property & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) { |
| | | sb.append("Indicate "); |
| | | } |
| | | |
| | | //Broadcast |
| | | if ((property & BluetoothGattCharacteristic.PROPERTY_BROADCAST) > 0) { |
| | | sb.append("Broadcast "); |
| | | } |
| | | sb.deleteCharAt(sb.length() - 1); |
| | | sb.append(")"); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | public void initTitlebar() { |
| | | iv_right.setVisibility(View.VISIBLE); |
| | | iv_left.setImageDrawable(getResources().getDrawable(R.mipmap.icon_back)); |
| | | iv_right.setImageDrawable(getResources().getDrawable(R.mipmap.icon_topbar_overflow)); |
| | | } |
| | | |
| | | QMUICommonListItemView nameItem; |
| | | QMUICommonListItemView macItem; |
| | | QMUICommonListItemView statuItem; |
| | | View.OnClickListener onClickListener; |
| | | |
| | | private void initGroupListView() { |
| | | nameItem = mGroupListView.createItemView(null, |
| | | "èç", "æªè¿æ¥", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_NONE); |
| | | nameItem.setId(R.id.address_ble_name); |
| | | |
| | | macItem = mGroupListView.createItemView(null, |
| | | "mac", "", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_NONE); |
| | | macItem.setId(R.id.address_ble_mac); |
| | | |
| | | statuItem = mGroupListView.createItemView(null, |
| | | "ç¶æ", "æªè¿æ¥", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_NONE); |
| | | statuItem.setId(R.id.address_ble_statu); |
| | | |
| | | |
| | | onClickListener = new View.OnClickListener() { |
| | | @Override |
| | | public void onClick(View v) { |
| | | if (v instanceof QMUICommonListItemView) { |
| | | CharSequence text = ((QMUICommonListItemView) v).getDetailText(); |
| | | List<Map<String, String>> mbluetoothServices = Singletion.getInstance().mbluetoothServices; |
| | | |
| | | CharSequence title = ((QMUICommonListItemView) v).getText(); |
| | | if(title.equals("(ä¼ æå¨ç¼å)ç®ææ¨¡å¼")){ |
| | | if(mbluetoothServices == null || mbluetoothServices.size() < 1 ){ |
| | | Toast("æå¡ä¿¡æ¯é误ï¼è¯·éæ°è¿æ¥"); |
| | | return; |
| | | } |
| | | Map<String, String> map = mbluetoothServices.get(0); |
| | | Singletion.getInstance().uuid = map.get("uuid").toString(); |
| | | Intent intent = new Intent(ConnActivity.this,WriteActivity.class); |
| | | startActivity(intent); |
| | | }else if(title.equals("(ä¼ æå¨ç¼å)ä¸å®¶æ¨¡å¼")){ |
| | | if(mbluetoothServices == null || mbluetoothServices.size() < 2 ){ |
| | | Toast("æå¡ä¿¡æ¯é误ï¼è¯·éæ°è¿æ¥"); |
| | | return; |
| | | } |
| | | Map<String, String> map = mbluetoothServices.get(1); |
| | | Singletion.getInstance().uuid = map.get("uuid").toString(); |
| | | Intent intent = new Intent(ConnActivity.this,NormalWriteActivity.class); |
| | | startActivity(intent); |
| | | } |
| | | |
| | | } |
| | | } |
| | | }; |
| | | |
| | | int size = QMUIDisplayHelper.dp2px(this, 20); |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("Section 1:èçä¿¡æ¯") |
| | | .setDescription("") |
| | | .setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT) |
| | | .addItemView(nameItem, onClickListener) |
| | | .addItemView(statuItem, onClickListener) |
| | | .addItemView(macItem, onClickListener) |
| | | .addTo(mGroupListView); |
| | | |
| | | |
| | | } |
| | | |
| | | private void updateUi() { |
| | | //TODO å¨ææ´æ°UI |
| | | BluetoothLeDevice mDevice = Singletion.getInstance().mDevice; |
| | | List<Map<String, String>> mbluetoothServices = Singletion.getInstance().mbluetoothServices; |
| | | boolean mConnected = Singletion.getInstance().mConnected; |
| | | if (!mConnected || mbluetoothServices == null || mDevice == null) { |
| | | Toast("å¼å¸¸é误ï¼è¯·éåºéè¯ï¼"); |
| | | |
| | | } else { |
| | | if (nameItem != null) nameItem.setDetailText(mDevice.getName()); |
| | | if (statuItem != null) statuItem.setDetailText("å·²è¿æ¥"); |
| | | if (macItem != null) macItem.setDetailText(mDevice.getAddress()); |
| | | |
| | | //å建å¯åæå¡ä¿¡æ¯ |
| | | for (int i = 0; i < mbluetoothServices.size(); i++) { |
| | | Map<String, String> map = mbluetoothServices.get(i); |
| | | //map.get("uuid") |
| | | String title = i == 0 ? "(ä¼ æå¨ç¼å)ç®ææ¨¡å¼" : "(ä¼ æå¨ç¼å)ä¸å®¶æ¨¡å¼" ; |
| | | QMUICommonListItemView itemView = mGroupListView.createItemView(title); |
| | | itemView.setOrientation(QMUICommonListItemView.VERTICAL); |
| | | itemView.setDetailText(map.get("name")); |
| | | int size = QMUIDisplayHelper.dp2px(this, 20); |
| | | int section = i + 2; |
| | | int service = i + 1; |
| | | itemView.setAccessoryType(QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("Section " + section + ":ç¼åæå¡" + service) |
| | | .setDescription("") |
| | | .setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT) |
| | | .addItemView(itemView, onClickListener) |
| | | .addTo(mGroupListView); |
| | | |
| | | |
| | | } |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | | private void disconnect() { |
| | | if (connectManager != null) { |
| | | if (connectManager.isConnectDevice()) { |
| | | if (Singletion.getInstance().mDevice != null && Singletion.getInstance().mDevice.getAddress() != null) { |
| | | connectManager.disconnect(Singletion.getInstance().mDevice.getAddress()); |
| | | connectManager.removeConnectStateListener(stateListener); |
| | | } |
| | | |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void connect() { |
| | | if (connectManager != null) { |
| | | if (!connectManager.isConnectDevice()) { |
| | | if (Singletion.getInstance().mDevice != null && Singletion.getInstance().mDevice.getAddress() != null) |
| | | connectManager.connect(Singletion.getInstance().mDevice.getAddress()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void showDisconnectDialog() { |
| | | |
| | | new QMUIDialog.MessageDialogBuilder(ConnActivity.this) |
| | | .setTitle("æç¤º") |
| | | .setMessage("èçå·²è¿æ¥ï¼æ¯å¦æå¼è¿æ¥ï¼") |
| | | .addAction("åæ¶", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | dialog.dismiss(); |
| | | } |
| | | }) |
| | | .addAction("ç¡®å®", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | disconnect(); |
| | | dialog.dismiss(); |
| | | finish(); |
| | | |
| | | } |
| | | }) |
| | | .create(com.qmuiteam.qmui.R.style.QMUI_Dialog).show(); |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void onBackPressed() { |
| | | if (connectManager != null && connectManager.isConnectDevice()) { |
| | | showDisconnectDialog(); |
| | | } else { |
| | | super.onBackPressed(); |
| | | } |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void onDestroy() { |
| | | super.onDestroy(); |
| | | disconnect(); |
| | | |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.activity; |
| | | |
| | | import android.bluetooth.BluetoothAdapter; |
| | | import android.content.pm.PackageManager; |
| | | import android.os.Build; |
| | | import android.view.View; |
| | | |
| | | import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView; |
| | | import com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.base.BaseActivity; |
| | | import com.shlb.comb.event.UpdateEvent; |
| | | |
| | | import org.greenrobot.eventbus.Subscribe; |
| | | import org.greenrobot.eventbus.ThreadMode; |
| | | |
| | | public class CurrentDeviceActivity extends BaseActivity { |
| | | |
| | | private QMUIGroupListView mGroupHardware; |
| | | private QMUIGroupListView mGroupBle; |
| | | private QMUIGroupListView mGroupAudio; |
| | | private QMUIGroupListView mGroupScreen; |
| | | |
| | | @Override |
| | | protected void contentView() { |
| | | setContentView(R.layout.activity_current_device); |
| | | } |
| | | |
| | | @Override |
| | | protected void initView() { |
| | | super.initHead(); |
| | | tv_center.setText("设å¤ä¿¡æ¯"); |
| | | if (iv_right != null) iv_right.setVisibility(View.GONE); |
| | | if (iv_left != null) { |
| | | iv_left.setImageDrawable(getResources().getDrawable(R.mipmap.icon_back)); |
| | | iv_left.setOnClickListener(v -> finish()); |
| | | } |
| | | |
| | | mGroupHardware = findViewById(R.id.group_hardware); |
| | | mGroupBle = findViewById(R.id.group_ble); |
| | | mGroupAudio = findViewById(R.id.group_audio); |
| | | mGroupScreen = findViewById(R.id.group_screen); |
| | | } |
| | | |
| | | @Override |
| | | protected void initData() { |
| | | initHardwareInfo(); |
| | | initBleInfo(); |
| | | initAudioInfo(); |
| | | initScreenInfo(); |
| | | } |
| | | |
| | | @Override |
| | | protected void initEvent() { |
| | | } |
| | | |
| | | @Subscribe(threadMode = ThreadMode.MAIN) |
| | | public void onEvent(UpdateEvent event) { |
| | | // Required by BaseActivity's EventBus registration |
| | | } |
| | | |
| | | private void initHardwareInfo() { |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("硬件信æ¯") |
| | | .addItemView(createItem("设å¤åç§°", Build.DEVICE), null) |
| | | .addItemView(createItem("Android çæ¬", Build.VERSION.RELEASE), null) |
| | | .addItemView(createItem("å¶é å", Build.MANUFACTURER), null) |
| | | .addItemView(createItem("åå·", Build.MODEL), null) |
| | | .addItemView(createItem("æå»ºçæ¬", Build.DISPLAY), null) |
| | | .addItemView(createItem("主æ¿", Build.BOARD), null) |
| | | .addItemView(createItem("产åå", Build.PRODUCT), null) |
| | | .addTo(mGroupHardware); |
| | | } |
| | | |
| | | private void initBleInfo() { |
| | | PackageManager pm = getPackageManager(); |
| | | BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); |
| | | |
| | | boolean isBleSupported = pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); |
| | | |
| | | QMUIGroupListView.Section section = QMUIGroupListView.newSection(this) |
| | | .setTitle("ä½åèèç"); |
| | | |
| | | section.addItemView(createItem("æ¯æ BLE", isBleSupported ? "æ¯" : "å¦", isBleSupported), null); |
| | | |
| | | if (adapter != null) { |
| | | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
| | | boolean isOffloadedFilteringSupported = adapter.isOffloadedFilteringSupported(); |
| | | boolean isOffloadedScanBatchingSupported = adapter.isOffloadedScanBatchingSupported(); |
| | | boolean isMultipleAdvertisementSupported = adapter.isMultipleAdvertisementSupported(); |
| | | |
| | | section.addItemView(createItem("æ¯æè±æºè¿æ»¤", isOffloadedFilteringSupported ? "æ¯" : "å¦", isOffloadedFilteringSupported), null); |
| | | section.addItemView(createItem("æ¯æè±æºæ«ææ¹å¤ç", isOffloadedScanBatchingSupported ? "æ¯" : "å¦", isOffloadedScanBatchingSupported), null); |
| | | section.addItemView(createItem("æ¯æå¤é广æ", isMultipleAdvertisementSupported ? "æ¯" : "å¦", isMultipleAdvertisementSupported), null); |
| | | } |
| | | |
| | | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
| | | boolean isLe2MPhySupported = adapter.isLe2MPhySupported(); |
| | | boolean isLeCodedPhySupported = adapter.isLeCodedPhySupported(); |
| | | boolean isLeExtendedAdvertisingSupported = adapter.isLeExtendedAdvertisingSupported(); |
| | | boolean isLePeriodicAdvertisingSupported = adapter.isLePeriodicAdvertisingSupported(); |
| | | |
| | | section.addItemView(createItem("æ¯æé«é (PHY 2M)", isLe2MPhySupported ? "æ¯" : "å¦", isLe2MPhySupported), null); |
| | | section.addItemView(createItem("æ¯æè¿è·ç¦» (PHY Coded)", isLeCodedPhySupported ? "æ¯" : "å¦", isLeCodedPhySupported), null); |
| | | section.addItemView(createItem("æ¯ææ©å±å¹¿æ", isLeExtendedAdvertisingSupported ? "æ¯" : "å¦", isLeExtendedAdvertisingSupported), null); |
| | | section.addItemView(createItem("æ¯æå¨ææ§å¹¿æ", isLePeriodicAdvertisingSupported ? "æ¯" : "å¦", isLePeriodicAdvertisingSupported), null); |
| | | } |
| | | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
| | | int maxDataLength = adapter.isLeExtendedAdvertisingSupported() ? 1650 : 31; |
| | | section.addItemView(createItem("æå¤§å¹¿ææ°æ®é¿åº¦", String.valueOf(maxDataLength)), null); |
| | | } |
| | | } |
| | | |
| | | section.addTo(mGroupBle); |
| | | } |
| | | |
| | | private void initAudioInfo() { |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("èçé³é¢") |
| | | .addItemView(createItem("æ¯æ LE Audio", "å¦", false), null) |
| | | .addItemView(createItem("æ¯æ LE å¹¿ææº", "å¦", false), null) |
| | | .addItemView(createItem("æ¯æ LE 广æå©æ", "å¦", false), null) |
| | | .addItemView(createItem("æå¤§è¿æ¥é³é¢è®¾å¤æ°", "0"), null) |
| | | .addTo(mGroupAudio); |
| | | } |
| | | |
| | | private void initScreenInfo() { |
| | | android.util.DisplayMetrics dm = getResources().getDisplayMetrics(); |
| | | int screenWidth = dm.widthPixels; |
| | | int screenHeight = dm.heightPixels; |
| | | float density = dm.density; |
| | | int densityDpi = dm.densityDpi; |
| | | |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("å±å¹ä¿¡æ¯") |
| | | .addItemView(createItem("å辨ç", screenWidth + " x " + screenHeight), null) |
| | | .addItemView(createItem("å¯åº¦", density + " (" + densityDpi + " dpi)"), null) |
| | | .addTo(mGroupScreen); |
| | | } |
| | | |
| | | private QMUICommonListItemView createItem(String title, String detail) { |
| | | return createItem(title, detail, true); |
| | | } |
| | | |
| | | private QMUICommonListItemView createItem(String title, String detail, boolean isSupported) { |
| | | QMUICommonListItemView item = mGroupHardware.createItemView(title); |
| | | item.setOrientation(QMUICommonListItemView.HORIZONTAL); |
| | | item.setDetailText(detail); |
| | | return item; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.activity; |
| | | |
| | | import android.content.Intent; |
| | | import android.content.pm.PackageManager; |
| | | import android.graphics.Color; |
| | | import android.os.Build; |
| | | import android.view.View; |
| | | import android.view.Window; |
| | | import android.view.WindowManager; |
| | | import android.widget.AdapterView; |
| | | import android.widget.TextView; |
| | | import android.widget.Toast; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | import androidx.core.view.GravityCompat; |
| | | import androidx.drawerlayout.widget.DrawerLayout; |
| | | import androidx.fragment.app.Fragment; |
| | | import androidx.fragment.app.FragmentTransaction; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.scan.BluetoothScanManager; |
| | | import com.blakequ.bluetooth_manager_lib.util.BluetoothUtils; |
| | | import com.qmuiteam.qmui.widget.dialog.QMUIDialog; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.base.BaseActivity; |
| | | import com.shlb.comb.event.UpdateEvent; |
| | | import com.shlb.comb.fragment.DeviceListFragment; |
| | | import com.shlb.comb.fragment.SettingsFragment; |
| | | import com.shlb.comb.manager.BleGlobalManager; |
| | | import com.shlb.comb.model.BluetoothLeDeviceStore; |
| | | |
| | | import org.greenrobot.eventbus.Subscribe; |
| | | import org.greenrobot.eventbus.ThreadMode; |
| | | |
| | | public class HomeActivity extends BaseActivity implements AdapterView.OnItemClickListener { |
| | | |
| | | private DrawerLayout drawerLayout; |
| | | protected TextView tv_scan; |
| | | private long lastScanClickTime = 0; // ç¨äºèæµ |
| | | |
| | | // Bluetooth Variables |
| | | private BluetoothScanManager scanManager; |
| | | private BluetoothLeDeviceStore mDeviceStore; |
| | | private BluetoothUtils mBluetoothUtils; |
| | | |
| | | private SettingsFragment settingsFragment; |
| | | private DeviceListFragment deviceListFragment; |
| | | |
| | | @Override |
| | | protected void contentView() { |
| | | setContentView(R.layout.activity_home); |
| | | drawerLayout = findViewById(R.id.drawer_layout); |
| | | |
| | | // 设置å
¨å±ï¼ä½¿æ½å±å¯ä»¥è¦çç¶ææ åå¯¼èªæ |
| | | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
| | | Window window = getWindow(); |
| | | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); |
| | | window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); |
| | | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); |
| | | window.setStatusBarColor(Color.TRANSPARENT); |
| | | window.setNavigationBarColor(Color.TRANSPARENT); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | protected void initView() { |
| | | super.initHead(); |
| | | tv_scan = findViewById(R.id.tv_scan); |
| | | |
| | | try { |
| | | TextView tvVersion = findViewById(R.id.tv_version); |
| | | if (tvVersion != null) { |
| | | String versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; |
| | | tvVersion.setText("Version " + versionName); |
| | | } |
| | | } catch (PackageManager.NameNotFoundException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | |
| | | initTitlebar(); |
| | | |
| | | getSupportFragmentManager().addOnBackStackChangedListener(() -> { |
| | | Fragment current = getSupportFragmentManager().findFragmentById(R.id.fragment_container); |
| | | if (current instanceof SettingsFragment) { |
| | | tv_center.setText("宿¶çæ§"); |
| | | if (iv_left != null) { |
| | | iv_left.setImageResource(R.mipmap.icon_menu); |
| | | iv_left.setOnClickListener(v -> { |
| | | if (drawerLayout != null) { |
| | | drawerLayout.openDrawer(GravityCompat.START); |
| | | } |
| | | }); |
| | | } |
| | | if (tv_scan != null) { |
| | | tv_scan.setVisibility(View.VISIBLE); |
| | | if (BleGlobalManager.getInstance().isConnected()) { |
| | | updateConnectedUI(); |
| | | } else { |
| | | tv_scan.setText("è¿æ¥èç"); |
| | | tv_scan.setOnClickListener(v -> showDeviceListFragment()); |
| | | } |
| | | } |
| | | if (iv_right != null) iv_right.setVisibility(View.GONE); |
| | | } else if (current instanceof DeviceListFragment) { |
| | | tv_center.setText("èç设å¤"); |
| | | if (iv_left != null) { |
| | | iv_left.setImageResource(R.mipmap.icon_back); |
| | | iv_left.setOnClickListener(v -> onBackPressed()); |
| | | } |
| | | if (tv_scan != null) { |
| | | tv_scan.setVisibility(View.VISIBLE); |
| | | tv_scan.setText("SCAN"); |
| | | tv_scan.setOnClickListener(v -> { |
| | | long currentTime = System.currentTimeMillis(); |
| | | if (currentTime - lastScanClickTime < 2000) { |
| | | Toast("ç¹å»è¿å¿«ï¼è¯·ç¨åéè¯ï¼"); |
| | | return; |
| | | } |
| | | lastScanClickTime = currentTime; |
| | | if (deviceListFragment != null && deviceListFragment.isVisible()) { |
| | | deviceListFragment.startScan(); |
| | | } |
| | | }); |
| | | } |
| | | if (iv_right != null) iv_right.setVisibility(View.VISIBLE); |
| | | } |
| | | }); |
| | | |
| | | // Default show SettingsFragment |
| | | showSettingsFragment(); |
| | | } |
| | | |
| | | @Override |
| | | protected void initData() { |
| | | mBluetoothUtils = BluetoothUtils.getInstance(this); |
| | | mDeviceStore = new BluetoothLeDeviceStore(); |
| | | scanManager = BluetoothScanManager.getInstance(this); |
| | | } |
| | | |
| | | @Override |
| | | protected void initEvent() { |
| | | iv_left.setOnClickListener(new View.OnClickListener() { |
| | | @Override |
| | | public void onClick(View view) { |
| | | if (drawerLayout != null) { |
| | | drawerLayout.openDrawer(GravityCompat.START); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | // æ½å±èåç¹å»äºä»¶ |
| | | // 宿¶çæ§ |
| | | findViewById(R.id.menu_monitor).setOnClickListener(v -> { |
| | | updateDrawerSelection(R.id.menu_monitor); |
| | | Toast.makeText(this, "宿¶çæ§", Toast.LENGTH_SHORT).show(); |
| | | showSettingsFragment(); |
| | | drawerLayout.closeDrawer(GravityCompat.START); |
| | | }); |
| | | |
| | | // èçè®¾å¤ |
| | | findViewById(R.id.menu_devices).setOnClickListener(v -> { |
| | | updateDrawerSelection(R.id.menu_devices); |
| | | showDeviceListFragment(); |
| | | drawerLayout.closeDrawer(GravityCompat.START); |
| | | }); |
| | | |
| | | // å½åè®¾å¤ |
| | | findViewById(R.id.menu_current_device).setOnClickListener(v -> { |
| | | drawerLayout.closeDrawer(GravityCompat.START); |
| | | startActivity(new Intent(this, CurrentDeviceActivity.class)); |
| | | }); |
| | | |
| | | // åæ°è®¾å® |
| | | findViewById(R.id.menu_settings).setOnClickListener(v -> { |
| | | drawerLayout.closeDrawer(GravityCompat.START); |
| | | startActivity(new Intent(this, ParameterSettingActivity.class)); |
| | | }); |
| | | |
| | | // é«çº§è®¾ç½® |
| | | findViewById(R.id.menu_advanced_settings).setOnClickListener(v -> { |
| | | drawerLayout.closeDrawer(GravityCompat.START); |
| | | startActivity(new Intent(this, AdvancedSettingActivity.class)); |
| | | }); |
| | | } |
| | | |
| | | private void updateDrawerSelection(int selectedId) { |
| | | int[] menuIds = {R.id.menu_monitor, R.id.menu_devices, R.id.menu_current_device, R.id.menu_settings, R.id.menu_advanced_settings}; |
| | | for (int id : menuIds) { |
| | | View view = findViewById(id); |
| | | if (view != null) { |
| | | boolean isSelected = (id == selectedId); |
| | | view.setSelected(isSelected); |
| | | |
| | | // Manually set selected state for children because duplicateParentState="true" might be missing |
| | | if (view instanceof android.view.ViewGroup) { |
| | | android.view.ViewGroup group = (android.view.ViewGroup) view; |
| | | for (int i = 0; i < group.getChildCount(); i++) { |
| | | group.getChildAt(i).setSelected(isSelected); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | public void showSettingsFragment() { |
| | | tv_center.setText("宿¶çæ§"); |
| | | if (tv_scan != null) { |
| | | tv_scan.setVisibility(View.VISIBLE); |
| | | tv_scan.setText("è¿æ¥èç"); |
| | | tv_scan.setOnClickListener(v -> showDeviceListFragment()); |
| | | } |
| | | if (iv_right != null) iv_right.setVisibility(View.GONE); |
| | | |
| | | FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); |
| | | if (settingsFragment == null) { |
| | | settingsFragment = new SettingsFragment(); |
| | | } |
| | | ft.replace(R.id.fragment_container, settingsFragment); |
| | | ft.commit(); |
| | | } |
| | | |
| | | public void showDeviceListFragment() { |
| | | updateDrawerSelection(R.id.menu_devices); |
| | | tv_center.setText("èç设å¤"); |
| | | if (tv_scan != null) { |
| | | tv_scan.setVisibility(View.VISIBLE); |
| | | tv_scan.setText("SCAN"); |
| | | tv_scan.setOnClickListener(v -> { |
| | | // èæµé»è¾ï¼ä¸¤ç§å
åªè½ç¹ä¸æ¬¡ |
| | | long currentTime = System.currentTimeMillis(); |
| | | if (currentTime - lastScanClickTime < 2000) { |
| | | Toast("ç¹å»è¿å¿«ï¼è¯·ç¨åéè¯ï¼"); |
| | | return; |
| | | } |
| | | lastScanClickTime = currentTime; |
| | | if (deviceListFragment != null && deviceListFragment.isVisible()&&scanManager!=null&&!scanManager.isScanning()) { |
| | | deviceListFragment.startScan(); |
| | | } |
| | | }); |
| | | } |
| | | if (iv_right != null) iv_right.setVisibility(View.VISIBLE); |
| | | |
| | | FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); |
| | | if (deviceListFragment == null) { |
| | | deviceListFragment = new DeviceListFragment(); |
| | | } |
| | | ft.replace(R.id.fragment_container, deviceListFragment); |
| | | ft.addToBackStack(null); |
| | | ft.commit(); |
| | | } |
| | | |
| | | |
| | | public BluetoothScanManager getScanManager() { |
| | | return scanManager; |
| | | } |
| | | |
| | | public BluetoothLeDeviceStore getDeviceStore() { |
| | | return mDeviceStore; |
| | | } |
| | | |
| | | public BluetoothUtils getBluetoothUtils() { |
| | | return mBluetoothUtils; |
| | | } |
| | | |
| | | @Override |
| | | public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { |
| | | } |
| | | |
| | | @Subscribe(threadMode = ThreadMode.MAIN) |
| | | public void onEventRefresh(UpdateEvent event) { |
| | | if (event.getType() == UpdateEvent.Type.CONN_STATU) { |
| | | int status = 0; |
| | | if (event.getObj() instanceof Integer) { |
| | | status = (int) event.getObj(); |
| | | } |
| | | if (status == android.bluetooth.BluetoothProfile.STATE_CONNECTED) { |
| | | updateConnectedUI(); |
| | | } else if (status == android.bluetooth.BluetoothProfile.STATE_DISCONNECTED) { |
| | | if (tv_scan != null) { |
| | | tv_scan.setText("è¿æ¥èç"); |
| | | tv_scan.setOnClickListener(v -> showDeviceListFragment()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void updateConnectedUI() { |
| | | if (tv_scan == null) return; |
| | | com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice device = BleGlobalManager.getInstance().getBluetoothLeDevice(); |
| | | String name = (device != null) ? device.getName() : "Unknown"; |
| | | if (name == null) name = "Unknown"; |
| | | if (name.length() > 8) { |
| | | name = name.substring(0, 8) + "..."; |
| | | } |
| | | tv_scan.setText(name); |
| | | tv_scan.setOnClickListener(v -> { |
| | | new QMUIDialog.MessageDialogBuilder(this) |
| | | .setTitle("æç¤º") |
| | | .setMessage("æ¯å¦æå¼èçè¿æ¥ï¼") |
| | | .addAction("åæ¶", (dialog, index) -> dialog.dismiss()) |
| | | .addAction("ç¡®å®", (dialog, index) -> { |
| | | dialog.dismiss(); |
| | | BleGlobalManager.getInstance().disconnect(); |
| | | }) |
| | | .create(com.qmuiteam.qmui.R.style.QMUI_Dialog).show(); |
| | | }); |
| | | } |
| | | |
| | | public void initTitlebar() { |
| | | if (iv_right != null) { |
| | | iv_right.setVisibility(View.VISIBLE); |
| | | iv_right.setOnClickListener(v -> { |
| | | androidx.appcompat.widget.PopupMenu popup = new androidx.appcompat.widget.PopupMenu(this, v); |
| | | popup.getMenu().add(0, 0, 0, "çæ§é¡µ"); |
| | | popup.getMenu().add(0, 1, 1, "æµè¯é¡µ"); |
| | | popup.setOnMenuItemClickListener(item -> { |
| | | if (item.getItemId() == 0) { |
| | | DeviceListFragment.setNavigationMode(DeviceListFragment.MODE_RETURN_SETTINGS); |
| | | Toast.makeText(HomeActivity.this, "已忢ï¼è¿æ¥åè¿åçæ§é¡µ", Toast.LENGTH_SHORT).show(); |
| | | } else if (item.getItemId() == 1) { |
| | | DeviceListFragment.setNavigationMode(DeviceListFragment.MODE_GO_TO_TEST); |
| | | Toast.makeText(HomeActivity.this, "已忢ï¼è¿æ¥åè·³è½¬å°æµè¯é¡µ", Toast.LENGTH_SHORT).show(); |
| | | } |
| | | return true; |
| | | }); |
| | | popup.show(); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onBackPressed() { |
| | | if (drawerLayout != null && drawerLayout.isDrawerOpen(GravityCompat.START)) { |
| | | drawerLayout.closeDrawer(GravityCompat.START); |
| | | } else { |
| | | // Check if we are in DeviceListFragment and can go back |
| | | Fragment current = getSupportFragmentManager().findFragmentById(R.id.fragment_container); |
| | | if (current instanceof DeviceListFragment) { |
| | | super.onBackPressed(); |
| | | } else { |
| | | ExitApp(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private long exitTime = 0; |
| | | |
| | | public void ExitApp() { |
| | | if ((System.currentTimeMillis() - exitTime) > 2000) { |
| | | Toast.makeText(this, "åæä¸æ¬¡éåºç¨åºï¼", Toast.LENGTH_SHORT).show(); |
| | | exitTime = System.currentTimeMillis(); |
| | | } else { |
| | | finish(); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.activity; |
| | | |
| | | import android.bluetooth.BluetoothGatt; |
| | | import android.bluetooth.BluetoothGattCharacteristic; |
| | | import android.bluetooth.BluetoothGattService; |
| | | import android.text.InputType; |
| | | import android.util.Log; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.widget.Button; |
| | | import android.widget.LinearLayout; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.connect.BluetoothConnectManager; |
| | | import com.blakequ.bluetooth_manager_lib.connect.BluetoothSubScribeData; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectState; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectStateListener; |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | import com.blakequ.bluetooth_manager_lib.util.BluetoothUtils; |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | import com.blankj.utilcode.util.ThreadUtils; |
| | | import com.qmuiteam.qmui.util.QMUIDisplayHelper; |
| | | import com.qmuiteam.qmui.widget.dialog.QMUIDialog; |
| | | import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction; |
| | | import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView; |
| | | import com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView; |
| | | import com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.base.BaseActivity; |
| | | import com.shlb.comb.event.UpdateEvent; |
| | | import com.shlb.comb.util.CMD; |
| | | import com.shlb.comb.util.CRCutil; |
| | | import com.shlb.comb.util.Singletion; |
| | | |
| | | import org.greenrobot.eventbus.Subscribe; |
| | | import org.greenrobot.eventbus.ThreadMode; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.UUID; |
| | | |
| | | public class NormalWriteActivity extends BaseActivity { |
| | | |
| | | QMUIGroupListView mGroupListView; |
| | | |
| | | Button send; |
| | | |
| | | LinearLayout llRoot; |
| | | |
| | | |
| | | private BluetoothConnectManager connectManager; |
| | | private BluetoothGatt gatt; |
| | | |
| | | private BluetoothGattCharacteristic characteristic; |
| | | |
| | | |
| | | @Override |
| | | protected void contentView() { |
| | | setContentView(R.layout.normal_activity_write); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void initView() { |
| | | super.initHead(); |
| | | mGroupListView = findViewById(R.id.groupListView); |
| | | send = findViewById(R.id.send); |
| | | llRoot = findViewById(R.id.ll_root); |
| | | initTitlebar(); |
| | | tv_center.setText("ç¼å"); |
| | | initGroupListView(); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void initData() { |
| | | String uuid = Singletion.getInstance().uuid; |
| | | initWrite(uuid); |
| | | |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void initEvent() { |
| | | send.setOnClickListener(this); |
| | | int[] ids = {R.id.btnExit, R.id.btnInto, R.id.btnClean, R.id.btn1, R.id.btn2, R.id.btn3, R.id.btn4, R.id.btn5, R.id.btn6, R.id.btn7, R.id.btn8, R.id.btn9, R.id.btn10, R.id.btn11, R.id.btn12, R.id.btn13, R.id.btn14, R.id.btn15, R.id.btn16}; |
| | | for (int id : ids) { |
| | | View v = findViewById(id); |
| | | if (v != null) v.setOnClickListener(this); |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void onClick(View v) { |
| | | int id = v.getId(); |
| | | if (id == R.id.iv_left) { |
| | | finish(); |
| | | } else if (id == R.id.btnExit) { |
| | | writeCmd(CMD.EXIT); |
| | | } else if (id == R.id.btnInto) { |
| | | writeCmd(CMD.GETINTO); |
| | | } else if (id == R.id.btnClean) { |
| | | writeCmd(CMD.CLEAN); |
| | | } else if (id == R.id.btn1) { |
| | | sendYJCmd(1); |
| | | } else if (id == R.id.btn2) { |
| | | sendYJCmd(2); |
| | | } else if (id == R.id.btn3) { |
| | | sendYJCmd(3); |
| | | } else if (id == R.id.btn4) { |
| | | sendYJCmd(4); |
| | | } else if (id == R.id.btn5) { |
| | | sendYJCmd(5); |
| | | } else if (id == R.id.btn6) { |
| | | sendYJCmd(6); |
| | | } else if (id == R.id.btn7) { |
| | | sendYJCmd(7); |
| | | } else if (id == R.id.btn8) { |
| | | sendYJCmd(8); |
| | | } else if (id == R.id.btn9) { |
| | | sendYJCmd(9); |
| | | } else if (id == R.id.btn10) { |
| | | sendYJCmd(10); |
| | | } else if (id == R.id.btn11) { |
| | | sendYJCmd(11); |
| | | } else if (id == R.id.btn12) { |
| | | sendYJCmd(12); |
| | | } else if (id == R.id.btn13) { |
| | | sendYJCmd(13); |
| | | } else if (id == R.id.btn14) { |
| | | sendYJCmd(14); |
| | | } else if (id == R.id.btn15) { |
| | | sendYJCmd(15); |
| | | } else if (id == R.id.btn16) { |
| | | sendYJCmd(16); |
| | | } |
| | | } |
| | | |
| | | |
| | | @Subscribe(threadMode = ThreadMode.MAIN) |
| | | public void onEventReceiveNotify(UpdateEvent event) { |
| | | if (event != null && event.getType() == UpdateEvent.Type.BLE_DATA) { |
| | | BluetoothGattCharacteristic characteristic = (BluetoothGattCharacteristic) event.getObj(); |
| | | final byte[] dataArr = characteristic.getValue(); |
| | | final String flag = event.getMsg(); |
| | | if (dataArr != null && dataArr.length > 0) { |
| | | if (flag.equals("read")) { |
| | | } else if (flag.equals("write")) { |
| | | } else if (flag.equals("notify")) { |
| | | //è¿åç»æ |
| | | String crc = CRCutil.getCRC(dataArr); |
| | | LogUtils.e("resCRC", crc); |
| | | //CRC计ç®ç»æä¸º0æ¶å½ä»¤æ§è¡æåï¼èªå¨æ§è¡éåºå½ä»¤ |
| | | if (crc.equals("0")) { |
| | | writeCmd(CMD.EXIT); |
| | | Toast("æä½æåï¼èªå¨æ§è¡éåºç¼åæä»¤"); |
| | | curCmd = -1; |
| | | item6.setText("ç¼åæå"); |
| | | } |
| | | //计ç®è¿å为0æ¶ èªå¨éåº |
| | | |
| | | LogUtils.e("res", ByteUtils.byteArrayToHexString(dataArr)); |
| | | } else { |
| | | |
| | | Toast("è§£æè¿åä¿¡æ¯å¤±è´¥ï¼"); |
| | | } |
| | | } else { |
| | | Toast("è¿åä¿¡æ¯ä¸ºç©ºï¼"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | QMUICommonListItemView item1; |
| | | QMUICommonListItemView item2; |
| | | QMUICommonListItemView item3; |
| | | QMUICommonListItemView item4; |
| | | QMUICommonListItemView item5; |
| | | QMUICommonListItemView item6; |
| | | View.OnClickListener onClickListener; |
| | | int curCmd = -1; |
| | | |
| | | String[] cmdName = {"è¿å
¥ç¼å", "æ¸
é¤å°å", "ç¼è¾å°å", "éåºç¼å", "ä¸é®ç¼å"}; |
| | | |
| | | private void initGroupListView() { |
| | | item1 = mGroupListView.createItemView(null, |
| | | cmdName[0], "", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | |
| | | item2 = mGroupListView.createItemView(null, |
| | | cmdName[1], "", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | |
| | | item3 = mGroupListView.createItemView(null, |
| | | cmdName[2], "ç¹å»éæ©ç¼å·", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | item4 = mGroupListView.createItemView(null, |
| | | cmdName[3], "", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | |
| | | |
| | | onClickListener = new View.OnClickListener() { |
| | | @Override |
| | | public void onClick(View v) { |
| | | if (v instanceof QMUICommonListItemView) { |
| | | CharSequence text = ((QMUICommonListItemView) v).getText(); |
| | | //è¿å
¥ç¼å |
| | | if (text.equals(cmdName[0])) { |
| | | curCmd = 1; |
| | | item6.setText(cmdName[0]); |
| | | item6.setDetailText(""); |
| | | //æ¸
é¤å°å |
| | | } else if (text.equals(cmdName[1])) { |
| | | curCmd = 2; |
| | | item6.setText(cmdName[1]); |
| | | item6.setDetailText(""); |
| | | //ç¼è¾å°å |
| | | } else if (text.equals(cmdName[2])) { |
| | | showEditTextDialog(); |
| | | //éåºç¼å |
| | | } else if (text.equals(cmdName[3])) { |
| | | curCmd = 4; |
| | | item6.setText(cmdName[3]); |
| | | item6.setDetailText(""); |
| | | //ä¸é®ç¼å |
| | | } else if (text.equals(cmdName[4])) { |
| | | showQuickDialog(); |
| | | } |
| | | |
| | | |
| | | } |
| | | } |
| | | }; |
| | | |
| | | int size = QMUIDisplayHelper.dp2px(this, 20); |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("Section 1:æä½æä»¤") |
| | | .setDescription("") |
| | | .setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT) |
| | | .addItemView(item1, onClickListener) |
| | | .addItemView(item2, onClickListener) |
| | | .addItemView(item3, onClickListener) |
| | | .addItemView(item4, onClickListener) |
| | | .addTo(mGroupListView); |
| | | |
| | | item5 = mGroupListView.createItemView(cmdName[4]); |
| | | item5.setOrientation(QMUICommonListItemView.VERTICAL); |
| | | item5.setDetailText("è¿å
¥-æ¸
é¤-ç¼å-éåº"); |
| | | item5.setAccessoryType(QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | |
| | | item6 = mGroupListView.createItemView("å¾
éæ©"); |
| | | item6.setOrientation(QMUICommonListItemView.HORIZONTAL); |
| | | |
| | | |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("Section 2:å¿«æ·æä»¤") |
| | | .setDescription("") |
| | | .setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT) |
| | | .addItemView(item5, onClickListener) |
| | | .addTo(mGroupListView); |
| | | |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("Section 3:å½åæä»¤") |
| | | .setDescription("") |
| | | .setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT) |
| | | .addItemView(item6, onClickListener) |
| | | .addTo(mGroupListView); |
| | | |
| | | } |
| | | |
| | | /** |
| | | * åå§ååå
¥æå¡ |
| | | * |
| | | * @param uu |
| | | */ |
| | | private void initWrite(String uu) { |
| | | BluetoothLeDevice mDevice = Singletion.getInstance().mDevice; |
| | | connectManager = BluetoothConnectManager.getInstance(this); |
| | | connectManager.addConnectStateListener(listener); |
| | | String uuid = uu; |
| | | UUID serverUUid = null; |
| | | gatt = connectManager.getBluetoothGatt(mDevice.getAddress()); |
| | | if (gatt != null) { |
| | | List<BluetoothGattService> list = gatt.getServices(); |
| | | if (list != null) { |
| | | for (BluetoothGattService service : list) { |
| | | for (BluetoothGattCharacteristic characteristics : service.getCharacteristics()) { |
| | | if (characteristics.getUuid().toString().equals(uuid)) { |
| | | characteristic = characteristics; |
| | | serverUUid = service.getUuid(); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | if (serverUUid == null) { |
| | | Toast("serverUUidä¸è½ä¸ºç©º!"); |
| | | return; |
| | | } |
| | | //1.set service uuid |
| | | connectManager.setServiceUUID(serverUUid.toString()); |
| | | //2.clean history descriptor data |
| | | connectManager.cleanSubscribeData(); |
| | | //3.add subscribe params |
| | | if (BluetoothUtils.isCharacteristicRead(characteristic.getProperties())) { |
| | | connectManager.addBluetoothSubscribeData( |
| | | new BluetoothSubScribeData.Builder().setCharacteristicRead(characteristic.getUuid()).build()); |
| | | } |
| | | if (BluetoothUtils.isCharacteristicNotify(characteristic.getProperties())) { |
| | | connectManager.addBluetoothSubscribeData( |
| | | new BluetoothSubScribeData.Builder().setCharacteristicNotify(characteristic.getUuid()).build() |
| | | ); |
| | | } |
| | | |
| | | connectManager.startSubscribe(gatt); |
| | | } |
| | | |
| | | private void writeCmd(String cmd) { |
| | | int len = cmd.length(); |
| | | if (len > 0 && len % 2 == 0) { |
| | | byte[] bytes = invertStringToBytes(cmd); |
| | | if (bytes != null) { |
| | | characteristic.setValue(bytes); |
| | | gatt.writeCharacteristic(characteristic); |
| | | runOnUiThread(() -> { |
| | | item6.setDetailText(item6.getDetailText() + " å·²åé"); |
| | | }); |
| | | } else { |
| | | Log.e("tag", "write value fail"); |
| | | } |
| | | } else { |
| | | |
| | | runOnUiThread(() -> { |
| | | Toast("æä»¤é误ï¼"); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | private byte[] invertStringToBytes(String value) { |
| | | int len = value.length() / 2; |
| | | if (len > 0) { |
| | | byte[] bytes = new byte[len]; |
| | | for (int i = 0; i < len; i++) { |
| | | Integer val = Integer.valueOf(value.substring(i * 2, i * 2 + 2), 16); |
| | | bytes[i] = val.byteValue(); |
| | | } |
| | | return bytes; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private List<Map<String, Object>> getWriteData() { |
| | | List<Map<String, Object>> res = new ArrayList<>(); |
| | | Map<String, Object> map1 = new HashMap<>(); |
| | | map1.put("name", "è¿å
¥ç¼åç¶æ"); |
| | | map1.put("flag", "0"); |
| | | map1.put("value", "A55A060100000000002F35"); |
| | | res.add(map1); |
| | | Map<String, Object> map2 = new HashMap<>(); |
| | | map2.put("name", "éåºç¼åç¶æ"); |
| | | map2.put("flag", "0"); |
| | | map2.put("value", "A55A060400000000002F60"); |
| | | res.add(map2); |
| | | Map<String, Object> map3 = new HashMap<>(); |
| | | map3.put("name", "ç¼å"); |
| | | map3.put("flag", "0"); |
| | | map3.put("value", "A55A060300020000002F6F"); |
| | | res.add(map3); |
| | | Map<String, Object> map4 = new HashMap<>(); |
| | | map4.put("name", "æ¸
é¤å°å"); |
| | | map4.put("flag", "0"); |
| | | map4.put("value", "A55A060200000000002F06"); |
| | | res.add(map4); |
| | | return res; |
| | | |
| | | } |
| | | |
| | | private ConnectStateListener listener = new ConnectStateListener() { |
| | | @Override |
| | | public void onConnectStateChanged(String address, ConnectState state) { |
| | | switch (state) { |
| | | case CONNECTED: |
| | | break; |
| | | case CONNECTING: |
| | | break; |
| | | case NORMAL: |
| | | break; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * è¾å
¥ç¼ååºå·å æ§è¡ç¼åæä»¤ |
| | | */ |
| | | private void showEditTextDialog() { |
| | | final QMUIDialog.EditTextDialogBuilder builder = new QMUIDialog.EditTextDialogBuilder(this); |
| | | builder.setTitle("æç¤º") |
| | | .setPlaceholder("请è¾å
¥ç¼åå·ç (1-16)") |
| | | .setInputType(InputType.TYPE_CLASS_TEXT) |
| | | .addAction("åæ¶", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | dialog.dismiss(); |
| | | } |
| | | }) |
| | | .addAction("ç¡®å®", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | String text = builder.getEditText().getText().toString(); |
| | | Integer res = -1; |
| | | try { |
| | | res = Integer.parseInt(text); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | if (res == null || res < 0 || res > 16) { |
| | | Toast("请è¾å
¥1-16æ°å"); |
| | | } else { |
| | | item3.setDetailText(text); |
| | | curCmd = 3; |
| | | item6.setText(cmdName[2]); |
| | | item6.setDetailText(text); |
| | | dialog.dismiss(); |
| | | |
| | | } |
| | | |
| | | |
| | | } |
| | | }) |
| | | .create(com.qmuiteam.qmui.R.style.QMUI_Dialog).show(); |
| | | } |
| | | |
| | | /** |
| | | * è¾å
¥ç¼ååºå·å ä¸é®æ§è¡æææä»¤ |
| | | */ |
| | | private void showQuickDialog() { |
| | | final QMUIDialog.EditTextDialogBuilder builder = new QMUIDialog.EditTextDialogBuilder(this); |
| | | builder.setTitle("æç¤º") |
| | | .setPlaceholder("请è¾å
¥ç¼åå·ç (1-16)") |
| | | .setInputType(InputType.TYPE_CLASS_TEXT) |
| | | .addAction("åæ¶", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | dialog.dismiss(); |
| | | } |
| | | }) |
| | | .addAction("ç¡®å®", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | String text = builder.getEditText().getText().toString(); |
| | | Integer addrrNo = -1; |
| | | try { |
| | | addrrNo = Integer.parseInt(text); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | if (addrrNo == null || addrrNo < 0 || addrrNo > 16) { |
| | | Toast("请è¾å
¥1-16æ°å"); |
| | | } else { |
| | | curCmd = 5; |
| | | item5.setDetailText(text); |
| | | item6.setText(cmdName[4]); |
| | | item6.setDetailText(text); |
| | | dialog.dismiss(); |
| | | |
| | | |
| | | } |
| | | |
| | | |
| | | } |
| | | }) |
| | | .create(com.qmuiteam.qmui.R.style.QMUI_Dialog).show(); |
| | | } |
| | | |
| | | public void initTitlebar() { |
| | | iv_right.setVisibility(View.VISIBLE); |
| | | iv_left.setImageDrawable(getResources().getDrawable(R.mipmap.icon_back)); |
| | | iv_right.setImageDrawable(getResources().getDrawable(R.mipmap.icon_topbar_overflow)); |
| | | } |
| | | |
| | | /** |
| | | * åéä¸é®å½ä»¤ |
| | | * @param no2 |
| | | */ |
| | | private void sendYJCmd(int no2){ |
| | | |
| | | |
| | | if (no2 > 16|| no2<1 ) { |
| | | Toast("å°åæ°æ®å¼å¸¸"); |
| | | return; |
| | | |
| | | } |
| | | |
| | | ThreadUtils.executeBySingle(new ThreadUtils.SimpleTask<Object>() { |
| | | @Override |
| | | public Object doInBackground() throws Throwable { |
| | | writeCmd(CMD.GETINTO); |
| | | Thread.sleep(200); |
| | | |
| | | writeCmd(CMD.CLEAN); |
| | | Thread.sleep(200); |
| | | |
| | | String sNo2 = Integer.toHexString(no2); |
| | | // |
| | | if (sNo2.length() < 2) { |
| | | sNo2 = "0" + sNo2; |
| | | } |
| | | |
| | | String cmd = CMD.ADDR_PREFIX + sNo2 + CMD.ADDR_SUFFIX; |
| | | byte[] bytes = invertStringToBytes(cmd); |
| | | String crc = CRCutil.getCRC(bytes); |
| | | String zl = cmd + crc; |
| | | LogUtils.e("reszl", zl); |
| | | Thread.sleep(200); |
| | | writeCmd(zl); |
| | | |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | | public void onSuccess(Object result) { |
| | | |
| | | } |
| | | }); |
| | | Toast("ç¼åæä»¤å·²åéï¼"); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.activity; |
| | | |
| | | import android.app.AlertDialog; |
| | | import android.content.DialogInterface; |
| | | import android.text.Html; |
| | | import android.view.LayoutInflater; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.widget.Button; |
| | | import android.widget.EditText; |
| | | import android.widget.ScrollView; |
| | | import android.widget.Spinner; |
| | | import android.widget.TextView; |
| | | import android.widget.Toast; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | import androidx.recyclerview.widget.GridLayoutManager; |
| | | import androidx.recyclerview.widget.RecyclerView; |
| | | |
| | | import com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.base.BaseActivity; |
| | | import com.shlb.comb.event.UpdateEvent; |
| | | import com.shlb.comb.manager.BleGlobalManager; |
| | | import com.shlb.comb.util.CMD; |
| | | import com.shlb.comb.util.CRCutil; |
| | | |
| | | import org.greenrobot.eventbus.Subscribe; |
| | | import org.greenrobot.eventbus.ThreadMode; |
| | | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | |
| | | public class ParameterSettingActivity extends BaseActivity { |
| | | |
| | | private Spinner sp_threshold_mode; |
| | | private EditText et_threshold_value; |
| | | private Button btn_threshold_read; |
| | | private Button btn_threshold_write; |
| | | |
| | | private Spinner sp_sensitivity_mode; |
| | | private EditText et_sensitivity_value; |
| | | private Button btn_sensitivity_read; |
| | | private Button btn_sensitivity_write; |
| | | |
| | | private Button btn_start_check; |
| | | private Button btn_end_check; |
| | | private Button btn_reset_mainboard; |
| | | private Button btn_reset_sensor; |
| | | |
| | | private TextView tv_log; |
| | | private TextView tv_right_text; |
| | | |
| | | // New components |
| | | private RecyclerView rvGrid; |
| | | private QMUIRoundButton btnReadData; |
| | | private QMUIRoundButton btnClearLog; |
| | | private ScrollView svLog; |
| | | |
| | | private GridAdapter mAdapter; |
| | | private List<BoxStatus> boxStatusList = new ArrayList<>(); |
| | | private StringBuilder logBuilder = new StringBuilder(); |
| | | private boolean isSelfCheckMode = false; |
| | | private String pendingSettingCmd = ""; |
| | | |
| | | private SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); |
| | | |
| | | @Override |
| | | protected void contentView() { |
| | | setContentView(R.layout.activity_parameter_setting); |
| | | } |
| | | |
| | | @Override |
| | | protected void initView() { |
| | | super.initHead(); |
| | | if (tv_center != null) { |
| | | tv_center.setText("åæ°è®¾å®"); |
| | | } |
| | | if (iv_left != null) { |
| | | iv_left.setImageResource(R.mipmap.icon_back); |
| | | } |
| | | |
| | | sp_threshold_mode = findViewById(R.id.sp_threshold_mode); |
| | | et_threshold_value = findViewById(R.id.et_threshold_value); |
| | | btn_threshold_read = findViewById(R.id.btn_threshold_read); |
| | | btn_threshold_write = findViewById(R.id.btn_threshold_write); |
| | | |
| | | sp_sensitivity_mode = findViewById(R.id.sp_sensitivity_mode); |
| | | et_sensitivity_value = findViewById(R.id.et_sensitivity_value); |
| | | btn_sensitivity_read = findViewById(R.id.btn_sensitivity_read); |
| | | btn_sensitivity_write = findViewById(R.id.btn_sensitivity_write); |
| | | |
| | | btn_start_check = findViewById(R.id.btn_start_check); |
| | | btn_end_check = findViewById(R.id.btn_end_check); |
| | | btn_reset_mainboard = findViewById(R.id.btn_reset_mainboard); |
| | | btn_reset_sensor = findViewById(R.id.btn_reset_sensor); |
| | | |
| | | tv_log = findViewById(R.id.tv_log); |
| | | tv_right_text = findViewById(R.id.tv_right_text); |
| | | |
| | | // Init new components |
| | | rvGrid = findViewById(R.id.rv_grid); |
| | | // btnReadData was removed from layout |
| | | btnClearLog = findViewById(R.id.btn_clear_log); |
| | | svLog = findViewById(R.id.sv_log); |
| | | |
| | | // Log setup |
| | | if (svLog != null) { |
| | | svLog.setOnTouchListener((v, event) -> { |
| | | v.getParent().requestDisallowInterceptTouchEvent(true); |
| | | if ((event.getAction() & android.view.MotionEvent.ACTION_MASK) == android.view.MotionEvent.ACTION_UP) { |
| | | v.getParent().requestDisallowInterceptTouchEvent(false); |
| | | } |
| | | return false; |
| | | }); |
| | | } |
| | | |
| | | if (logBuilder.length() > 0) { |
| | | tv_log.setText(Html.fromHtml(logBuilder.toString())); |
| | | } else { |
| | | logBuilder.append("æ¥å¿è®°å½:<br>"); |
| | | tv_log.setText(Html.fromHtml(logBuilder.toString())); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | protected void initData() { |
| | | updateBluetoothStatus(); |
| | | |
| | | // Initialize box status list |
| | | boxStatusList.clear(); |
| | | for (int i = 1; i <= 30; i++) { |
| | | boxStatusList.add(new BoxStatus(i)); |
| | | } |
| | | |
| | | // Grid Setup |
| | | rvGrid.setLayoutManager(new GridLayoutManager(this, 10)); |
| | | mAdapter = new GridAdapter(); |
| | | rvGrid.setAdapter(mAdapter); |
| | | |
| | | if (BleGlobalManager.getInstance().isConnected()) { |
| | | sendCmdWithCrc(CMD.ENTER_SETTING); |
| | | // sendCmdWithCrc(CMD.READ_DATA); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | protected void onDestroy() { |
| | | if (BleGlobalManager.getInstance().isConnected()) { |
| | | sendCmdWithCrc(CMD.EXIT_SETTING); |
| | | } |
| | | super.onDestroy(); |
| | | } |
| | | |
| | | @Override |
| | | protected void initEvent() { |
| | | View.OnClickListener listener = new View.OnClickListener() { |
| | | @Override |
| | | public void onClick(View v) { |
| | | int id = v.getId(); |
| | | String action = ""; |
| | | if (id == R.id.btn_threshold_read) { |
| | | action = "读åéå¼"; |
| | | Toast.makeText(ParameterSettingActivity.this, "该åè½ææªå¼æ¾", Toast.LENGTH_SHORT).show(); |
| | | } else if (id == R.id.btn_threshold_write) { |
| | | handleThresholdWrite(); |
| | | } else if (id == R.id.btn_sensitivity_read) { |
| | | action = "读åçµæåº¦"; |
| | | Toast.makeText(ParameterSettingActivity.this, "该åè½ææªå¼æ¾", Toast.LENGTH_SHORT).show(); |
| | | } else if (id == R.id.btn_sensitivity_write) { |
| | | handleSensitivityWrite(); |
| | | } else if (id == R.id.btn_start_check) { |
| | | action = "å¼å§èªæ£"; |
| | | sendCmdWithCrc(CMD.WRITE_START_CHECK); |
| | | isSelfCheckMode = true; |
| | | } else if (id == R.id.btn_end_check) { |
| | | action = "ç»æèªæ£"; |
| | | sendCmdWithCrc(CMD.WRITE_END_CHECK); |
| | | isSelfCheckMode = false; |
| | | } else if (id == R.id.btn_reset_mainboard) { |
| | | showResetConfirmDialog("ç¡®å®è¦æ§è¡ä¸»æ¿å¤ä½åï¼", CMD.WRITE_BOARD_RESET); |
| | | } else if (id == R.id.btn_reset_sensor) { |
| | | showResetConfirmDialog("ç¡®å®è¦æ§è¡ä¼ æå¨å¤ä½åï¼", CMD.WRITE_SENSOR_RESET); |
| | | } |
| | | |
| | | if (!action.isEmpty()) { |
| | | // Toast.makeText(ParameterSettingActivity.this, action, Toast.LENGTH_SHORT).show(); |
| | | // appendLog(action); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | btn_threshold_read.setOnClickListener(listener); |
| | | btn_threshold_write.setOnClickListener(listener); |
| | | btn_sensitivity_read.setOnClickListener(listener); |
| | | btn_sensitivity_write.setOnClickListener(listener); |
| | | btn_start_check.setOnClickListener(listener); |
| | | btn_end_check.setOnClickListener(listener); |
| | | btn_reset_mainboard.setOnClickListener(listener); |
| | | btn_reset_sensor.setOnClickListener(listener); |
| | | |
| | | btnClearLog.setOnClickListener(v -> { |
| | | logBuilder.setLength(0); |
| | | logBuilder.append("æ¥å¿è®°å½:<br>"); |
| | | tv_log.setText(Html.fromHtml(logBuilder.toString())); |
| | | }); |
| | | } |
| | | |
| | | private String getCmdDescription(String hex, Boolean isSent) { |
| | | if (hex == null) return ""; |
| | | |
| | | // Special handling for 3B (Enter/Exit Setting) |
| | | if (hex.startsWith(CMD.ENTER_SETTING.substring(0, 8))) { |
| | | if (hex.startsWith(CMD.ENTER_SETTING)) return "-è¿å
¥è®¾å®"; |
| | | if (hex.startsWith(CMD.EXIT_SETTING)) return "-éåºè®¾å®"; |
| | | } |
| | | |
| | | // åéæä»¤å¹é
|
| | | if (hex.startsWith(CMD.WRITE_THRESHOLD_SETTING)) return "-éå¼è®¾å®"; |
| | | if (hex.startsWith(CMD.WRITE_SENSITIVITY_SETTING)) return "-çµæåº¦è®¾å®"; |
| | | if (hex.startsWith(CMD.WRITE_BOARD_RESET)) return "-主æ¿å¤ä½"; |
| | | if (hex.startsWith(CMD.WRITE_SENSOR_RESET)) return "-ä¼ æå¨å¤ä½"; |
| | | if (hex.startsWith(CMD.WRITE_START_CHECK)) return "-å¼å§èªæ£"; |
| | | if (hex.startsWith(CMD.WRITE_END_CHECK)) return "-ç»æèªæ£"; |
| | | if (hex.startsWith(CMD.READ_DATA.substring(0, 8))) return "-è¯»æ°æ®"; |
| | | |
| | | // æ¥æ¶æ°æ®å¹é
|
| | | if (hex.startsWith(CMD.WRITE_THRESHOLD_SETTING.substring(0, 8))) return "-éå¼è¿å"; |
| | | if (hex.startsWith(CMD.WRITE_SENSITIVITY_SETTING.substring(0, 8))) return "-çµæåº¦è¿å"; |
| | | if (hex.startsWith(CMD.WRITE_BOARD_RESET.substring(0, 8))) return "-主æ¿å¤ä½è¿å"; |
| | | if (hex.startsWith(CMD.WRITE_SENSOR_RESET.substring(0, 8))) return "-ä¼ æå¨å¤ä½è¿å"; |
| | | if (hex.startsWith(CMD.WRITE_START_CHECK.substring(0, 8))) return "-èªæ£è¿å"; |
| | | |
| | | if (hex.startsWith(CMD.WRITE_SUFFIX)) return "-åå
¥è¿å"; |
| | | |
| | | return ""; |
| | | } |
| | | |
| | | private void appendLog(String msg) { |
| | | appendLog(msg, null); |
| | | } |
| | | |
| | | private void appendLog(String msg, Boolean isSent) { |
| | | String time = new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()).format(new Date()); |
| | | |
| | | String displayMsg = msg; |
| | | String cmdDesc = ""; |
| | | |
| | | // å¦ææ¯ hex æä»¤ (纯 0-9 A-F a-f)ï¼å ç©ºæ ¼æ ¼å¼å |
| | | if (msg.matches("^[0-9A-Fa-f]+$")) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (int i = 0; i < msg.length(); i += 2) { |
| | | if (i + 2 <= msg.length()) { |
| | | sb.append(msg.substring(i, i + 2)).append(" "); |
| | | } else { |
| | | sb.append(msg.substring(i)); |
| | | } |
| | | } |
| | | displayMsg = sb.toString().trim(); |
| | | |
| | | // è·åæä»¤æè¿° |
| | | cmdDesc = getCmdDescription(msg, isSent); |
| | | } |
| | | |
| | | String logLine; |
| | | if (isSent != null) { |
| | | String color = isSent ? "#1890ff" : "#05aa87"; // Blue for sent, Green for received |
| | | // æ¼æ¥å°åç¼åé¢: "åé-读ç«å·: " æ "æ¶å°-读ç«å·: " |
| | | String prefix = (isSent ? "åé" : "æ¶å°") + cmdDesc + ": "; |
| | | logLine = time + " <font color='" + color + "'>" + prefix + displayMsg + "</font><br>"; |
| | | } else { |
| | | // æ®éæ¥å¿ |
| | | logLine = time + " " + displayMsg + "<br>"; |
| | | } |
| | | |
| | | logBuilder.append(logLine); |
| | | |
| | | // Update UI |
| | | if (tv_log != null) { |
| | | tv_log.setText(Html.fromHtml(logBuilder.toString())); |
| | | if (svLog != null) { |
| | | svLog.post(() -> svLog.fullScroll(View.FOCUS_DOWN)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Kept for backward compatibility if needed, but redirected to appendLog |
| | | private void addLog(String message) { |
| | | appendLog(message); |
| | | } |
| | | |
| | | @Subscribe(threadMode = ThreadMode.MAIN) |
| | | public void onEvent(UpdateEvent event) { |
| | | if (event.getType() == UpdateEvent.Type.CONN_STATU) { |
| | | updateBluetoothStatus(); |
| | | } else if (event.getType() == UpdateEvent.Type.DEVICE_INFO) { |
| | | String hex = event.getMsg(); |
| | | appendLog(hex, false); // false for received (Green) |
| | | parseAndRefresh(hex); |
| | | } |
| | | } |
| | | |
| | | private void parseAndRefresh(String hex) { |
| | | if (hex == null) return; |
| | | |
| | | // åå
¥æä»¤çè¿å (A55A06å¼å¤´) |
| | | if (hex.startsWith("A55A06") && hex.length() >= 12) { |
| | | String statusHex = hex.substring(10, 12); |
| | | boolean isSuccess = "01".equals(statusHex); |
| | | |
| | | if (hex.startsWith(CMD.ENTER_SETTING.substring(0, 8))) { |
| | | if (isSuccess) { |
| | | appendLog("è¿å
¥è®¾å®æ¨¡å¼æå"); |
| | | } else { |
| | | appendLog("è¿å
¥è®¾å®æ¨¡å¼å¤±è´¥"); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | if (hex.startsWith(CMD.EXIT_SETTING.substring(0, 8))) { |
| | | appendLog("éåºè®¾å®æ¨¡å¼æå"); |
| | | return; |
| | | } |
| | | |
| | | if (hex.startsWith(CMD.WRITE_THRESHOLD_SETTING.substring(0, 8))) { |
| | | if (isSuccess) { |
| | | appendLog("éå¼åå
¥æå"); |
| | | Toast.makeText(this, "éå¼åå
¥æå", Toast.LENGTH_SHORT).show(); |
| | | } else { |
| | | appendLog("éå¼åå
¥å¤±è´¥"); |
| | | Toast.makeText(this, "éå¼åå
¥å¤±è´¥", Toast.LENGTH_SHORT).show(); |
| | | } |
| | | } else if (hex.startsWith(CMD.WRITE_SENSITIVITY_SETTING.substring(0, 8))) { |
| | | if (isSuccess) { |
| | | appendLog("çµæåº¦åå
¥æå"); |
| | | Toast.makeText(this, "çµæåº¦åå
¥æå", Toast.LENGTH_SHORT).show(); |
| | | } else { |
| | | appendLog("çµæåº¦åå
¥å¤±è´¥"); |
| | | Toast.makeText(this, "çµæåº¦åå
¥å¤±è´¥", Toast.LENGTH_SHORT).show(); |
| | | } |
| | | } else if (hex.startsWith(CMD.WRITE_BOARD_RESET.substring(0, 8))) { |
| | | boolean isBoardReset = pendingSettingCmd.equals(CMD.WRITE_BOARD_RESET); |
| | | String name = isBoardReset ? "主æ¿å¤ä½" : "ä¼ æå¨å¤ä½"; |
| | | |
| | | if (isSuccess) { |
| | | appendLog(name + "æå"); |
| | | Toast.makeText(this, name + "æå", Toast.LENGTH_SHORT).show(); |
| | | } else { |
| | | appendLog(name + "失败"); |
| | | Toast.makeText(this, name + "失败", Toast.LENGTH_SHORT).show(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Check for Self Check command response |
| | | if (hex.startsWith(CMD.WRITE_START_CHECK.substring(0, 8)) && hex.length() >= 12) { |
| | | String statusHex = hex.substring(10, 12); |
| | | boolean isSuccess = "01".equals(statusHex); |
| | | String opName = isSelfCheckMode ? "å¼å§èªæ£" : "ç»æèªæ£"; |
| | | |
| | | if (isSuccess) { |
| | | appendLog(opName + "æå"); |
| | | Toast.makeText(this, opName + "æå", Toast.LENGTH_SHORT).show(); |
| | | |
| | | // If success and isSelfCheckMode, wait 5s then read data |
| | | if (isSelfCheckMode) { |
| | | appendLog("5ç§å读åèªæ£ç»æ..."); |
| | | new android.os.Handler().postDelayed(() -> { |
| | | sendCmdWithCrc(CMD.READ_DATA); |
| | | }, 5000); |
| | | } |
| | | } else { |
| | | appendLog(opName + "失败"); |
| | | Toast.makeText(this, opName + "失败", Toast.LENGTH_SHORT).show(); |
| | | } |
| | | } |
| | | |
| | | // Handle READ_DATA response for Self Check results |
| | | // A55A0301... |
| | | if (hex.startsWith(CMD.READ_DATA.substring(0, 8)) && hex.length() >= 26) { |
| | | parseSelfCheckData(hex); |
| | | } |
| | | } |
| | | |
| | | private void parseSelfCheckData(String hex) { |
| | | try { |
| | | // 11th hex -> index 10-18 (8 chars) |
| | | String part1Hex = hex.substring(10, 18); |
| | | long part1Bits = Long.parseLong(part1Hex, 16); |
| | | |
| | | // 18th hex -> index 18-26 (8 chars) |
| | | String part2Hex = hex.substring(18, 26); |
| | | long part2Bits = Long.parseLong(part2Hex, 16); |
| | | |
| | | appendLog("èªæ£æ°æ®è§£æ: " + part1Hex + ", " + part2Hex); |
| | | |
| | | boolean hasChange = false; |
| | | for (BoxStatus box : boxStatusList) { |
| | | int bitIndex = box.id - 1; |
| | | if (bitIndex >= 0 && bitIndex < 32) { |
| | | // Determine success based on both bits |
| | | // Assuming both need to be 1, or following specific logic. |
| | | // Based on user request "11th and 18th hex", we check both. |
| | | // Usually part1 is Glass (Detection), part2 is Online. |
| | | // We'll assume success means bit is set in both (Online and Detected?) |
| | | // OR maybe just Online? |
| | | // Let's stick to combining them to be safe as user mentioned both. |
| | | // But if Self Check is just checking if it's working, maybe Online is enough? |
| | | // Let's use: Success = (part1 & 1) && (part2 & 1) ? |
| | | // Let's use bitwise AND of the two parts for the result. |
| | | |
| | | boolean bit1 = ((part1Bits >> bitIndex) & 1) == 1; |
| | | boolean bit2 = ((part2Bits >> bitIndex) & 1) == 1; |
| | | |
| | | // If user meant "11th OR 18th", or "11th is this, 18th is that". |
| | | // Given "Success/Fail", I'll assume both must be valid. |
| | | // However, in normal operation: |
| | | // Glass=0, Online=1 -> Empty box, but sensor working. |
| | | // Glass=1, Online=1 -> Full box, sensor working. |
| | | // Online=0 -> Sensor broken/offline. |
| | | |
| | | // If "Self Check" puts the sensor in a state where it should report "1" for Glass? |
| | | // If so, then we expect Glass=1 AND Online=1. |
| | | // If "Self Check" just checks health, then Glass might be 0. |
| | | |
| | | // "ç¡®å®åªäºæ ¼åèªæ£æå" (Determine which grids passed self-check). |
| | | // If I use bit1 && bit2: |
| | | // If a sensor is working but empty (Glass=0), it fails self-check? |
| | | // This implies Self Check expects a "1" signal. |
| | | // This is common in self-checks (force a signal). |
| | | |
| | | if (bit1 && bit2) { |
| | | box.checkResult = 1; // Green |
| | | } else { |
| | | box.checkResult = 0; // Red |
| | | } |
| | | hasChange = true; |
| | | } |
| | | } |
| | | |
| | | if (hasChange && mAdapter != null) { |
| | | mAdapter.notifyDataSetChanged(); |
| | | appendLog("èªæ£ç»æå·²æ´æ°"); |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | appendLog("èªæ£æ°æ®è§£æå¼å¸¸: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | |
| | | private void sendCmdWithCrc(String cmd) { |
| | | if (!BleGlobalManager.getInstance().isConnected()) { |
| | | Toast.makeText(this, "请å
è¿æ¥èç", Toast.LENGTH_SHORT).show(); |
| | | appendLog("é误: èçæªè¿æ¥"); |
| | | return; |
| | | } |
| | | |
| | | byte[] cmdBytes = BleGlobalManager.hexStringToBytes(cmd); |
| | | if (cmdBytes != null) { |
| | | String crc = CRCutil.getCRC(cmdBytes); |
| | | // Pad CRC to 4 chars if needed |
| | | while (crc.length() < 4) { |
| | | crc = "0" + crc; |
| | | } |
| | | String fullCmd = cmd + crc.toUpperCase(); |
| | | |
| | | appendLog(fullCmd, true); // true for sent (Blue) |
| | | BleGlobalManager.getInstance().sendCmd(fullCmd); |
| | | } else { |
| | | appendLog("é误: æä»¤è½¬æ¢å¤±è´¥"); |
| | | } |
| | | } |
| | | |
| | | private void handleThresholdWrite() { |
| | | int position = sp_threshold_mode.getSelectedItemPosition(); |
| | | String inputValue = et_threshold_value.getText().toString().trim(); |
| | | String modeHex = ""; |
| | | String valueHex = ""; |
| | | |
| | | if (position == 0) { // ä¸é®è®¾å® |
| | | modeHex = "00"; |
| | | valueHex = "00"; |
| | | } else if (position == 1) { // åå±è®¾å® |
| | | modeHex = "01"; |
| | | if (inputValue.isEmpty()) { |
| | | Toast.makeText(this, "请è¾å
¥éå¼(1-30)", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | try { |
| | | int val = Integer.parseInt(inputValue); |
| | | if (val < 1 || val > 30) { |
| | | Toast.makeText(this, "éå¼èå´1-30", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | valueHex = String.format("%02X", val); |
| | | } catch (NumberFormatException e) { |
| | | Toast.makeText(this, "è¾å
¥æ ¼å¼é误", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | } else { |
| | | return; |
| | | } |
| | | |
| | | // Construct pending command: CMD (32) + Len (02) + Mode + Value |
| | | // CMD.WRITE_THRESHOLD_SETTING is "A55A063202" |
| | | pendingSettingCmd = CMD.WRITE_THRESHOLD_SETTING + modeHex + valueHex; |
| | | sendCmdWithCrc(pendingSettingCmd); |
| | | } |
| | | |
| | | private void handleSensitivityWrite() { |
| | | int position = sp_sensitivity_mode.getSelectedItemPosition(); |
| | | String inputValue = et_sensitivity_value.getText().toString().trim(); |
| | | String modeHex = ""; |
| | | String valueHex = ""; |
| | | |
| | | // 0: æ¹éå¢å (00), 1: æ¹éåå°(01), 2: åå±å¢å (02), 3: åå±åå°(03) |
| | | if (position == 0) { |
| | | modeHex = "00"; |
| | | valueHex = "00"; |
| | | } else if (position == 1) { |
| | | modeHex = "01"; |
| | | valueHex = "00"; |
| | | } else if (position == 2) { |
| | | modeHex = "02"; |
| | | } else if (position == 3) { |
| | | modeHex = "03"; |
| | | } else { |
| | | return; |
| | | } |
| | | |
| | | if (position == 2 || position == 3) { |
| | | if (inputValue.isEmpty()) { |
| | | Toast.makeText(this, "请è¾å
¥æ°å¼(1-30)", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | try { |
| | | int val = Integer.parseInt(inputValue); |
| | | if (val < 1 || val > 30) { |
| | | Toast.makeText(this, "æ°å¼èå´1-30", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | valueHex = String.format("%02X", val); |
| | | } catch (NumberFormatException e) { |
| | | Toast.makeText(this, "è¾å
¥æ ¼å¼é误", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // Construct pending command: CMD (33) + Len (02) + Mode + Value |
| | | // CMD.WRITE_SENSITIVITY_SETTING is "A55A063302" |
| | | pendingSettingCmd = CMD.WRITE_SENSITIVITY_SETTING + modeHex + valueHex; |
| | | sendCmdWithCrc(pendingSettingCmd); |
| | | } |
| | | |
| | | private void updateBluetoothStatus() { |
| | | if (tv_right_text == null) return; |
| | | if (BleGlobalManager.getInstance().isConnected()) { |
| | | String name = "æªç¥è®¾å¤"; |
| | | if (BleGlobalManager.getInstance().getBluetoothLeDevice() != null) { |
| | | String deviceName = BleGlobalManager.getInstance().getBluetoothLeDevice().getName(); |
| | | if (deviceName != null && !deviceName.isEmpty()) { |
| | | name = deviceName; |
| | | } |
| | | } |
| | | tv_right_text.setText(name); |
| | | } else { |
| | | tv_right_text.setText("æªè¿æ¥"); |
| | | } |
| | | } |
| | | |
| | | private void showResetConfirmDialog(String message, final String cmd) { |
| | | new AlertDialog.Builder(this) |
| | | .setTitle("æç¤º") |
| | | .setMessage(message) |
| | | .setPositiveButton("ç¡®å®", new DialogInterface.OnClickListener() { |
| | | @Override |
| | | public void onClick(DialogInterface dialog, int which) { |
| | | pendingSettingCmd = cmd; |
| | | sendCmdWithCrc(cmd); |
| | | } |
| | | }) |
| | | .setNegativeButton("åæ¶", null) |
| | | .show(); |
| | | } |
| | | |
| | | // Inner classes |
| | | |
| | | private static class BoxStatus { |
| | | int id; |
| | | // -1: Unknown/Initial (White), 0: Fail (Red), 1: Success (Green) |
| | | int checkResult = -1; |
| | | |
| | | public BoxStatus(int id) { |
| | | this.id = id; |
| | | this.checkResult = -1; |
| | | } |
| | | } |
| | | |
| | | private class GridAdapter extends RecyclerView.Adapter<GridAdapter.ViewHolder> { |
| | | |
| | | @NonNull |
| | | @Override |
| | | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { |
| | | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_grid_box, parent, false); |
| | | return new ViewHolder(view); |
| | | } |
| | | |
| | | @Override |
| | | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { |
| | | // Calculate Box ID based on 3 rows of 10, Right to Left logic as seen in image |
| | | // Row 1 (pos 0-9): 10 ... 1 |
| | | // Row 2 (pos 10-19): 20 ... 11 |
| | | // Row 3 (pos 20-29): 30 ... 21 |
| | | |
| | | int row = position / 10; |
| | | int col = position % 10; |
| | | int boxId = (row + 1) * 10 - col; |
| | | |
| | | holder.tvBoxNumber.setText(String.valueOf(boxId)); |
| | | |
| | | // Find status for this box |
| | | BoxStatus status = null; |
| | | for (BoxStatus s : boxStatusList) { |
| | | if (s.id == boxId) { |
| | | status = s; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (status != null) { |
| | | if (status.checkResult == 1) { |
| | | // Success |
| | | holder.viewBox.setBackgroundResource(R.drawable.bg_box_full); // Green |
| | | } else if (status.checkResult == 0) { |
| | | // Fail |
| | | holder.viewBox.setBackgroundResource(R.drawable.bg_box_error); // Red |
| | | } else { |
| | | // Unknown / Initial |
| | | holder.viewBox.setBackgroundResource(R.drawable.bg_box_empty); // White |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public int getItemCount() { |
| | | return 30; |
| | | } |
| | | |
| | | class ViewHolder extends RecyclerView.ViewHolder { |
| | | TextView tvBoxNumber; |
| | | View viewBox; |
| | | |
| | | public ViewHolder(@NonNull View itemView) { |
| | | super(itemView); |
| | | tvBoxNumber = itemView.findViewById(R.id.tv_box_number); |
| | | viewBox = itemView.findViewById(R.id.view_box); |
| | | } |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.activity; |
| | | |
| | | import android.bluetooth.BluetoothGatt; |
| | | import android.bluetooth.BluetoothGattCallback; |
| | | import android.bluetooth.BluetoothGattCharacteristic; |
| | | import android.bluetooth.BluetoothGattService; |
| | | import android.bluetooth.BluetoothGattDescriptor; |
| | | import android.bluetooth.BluetoothProfile; |
| | | import android.os.Bundle; |
| | | import android.text.TextUtils; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.widget.Button; |
| | | import android.widget.EditText; |
| | | import android.widget.TextView; |
| | | import android.widget.Toast; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.connect.BluetoothConnectManager; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectState; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectStateListener; |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | import com.qmuiteam.qmui.util.QMUIDisplayHelper; |
| | | import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView; |
| | | import com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.base.BaseActivity; |
| | | import com.shlb.comb.event.UpdateEvent; |
| | | import com.shlb.comb.util.Singletion; |
| | | |
| | | import org.greenrobot.eventbus.EventBus; |
| | | import org.greenrobot.eventbus.Subscribe; |
| | | import org.greenrobot.eventbus.ThreadMode; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.UUID; |
| | | |
| | | public class TestActivity extends BaseActivity { |
| | | |
| | | private TextView tvStatus; |
| | | private TextView tvSelectedService; |
| | | private QMUIGroupListView mGroupListView; |
| | | private EditText etCommand; |
| | | private Button btnSend; |
| | | private TextView tvLog; |
| | | |
| | | private BluetoothConnectManager connectManager; |
| | | private BluetoothLeDevice mDevice; |
| | | private BluetoothGatt mGatt; |
| | | private BluetoothGattCharacteristic selectedCharacteristic; |
| | | |
| | | // Store pairs of (Characteristic, UI Item) if needed, or just list |
| | | private List<BluetoothGattCharacteristic> writeableCharacteristics = new ArrayList<>(); |
| | | |
| | | @Override |
| | | protected void contentView() { |
| | | setContentView(R.layout.activity_test); |
| | | } |
| | | |
| | | @Override |
| | | protected void initView() { |
| | | super.initHead(); |
| | | tv_center.setText("èçæµè¯"); |
| | | if (iv_left != null) { |
| | | iv_left.setImageDrawable(getResources().getDrawable(R.mipmap.icon_back)); |
| | | iv_left.setOnClickListener(v -> finish()); |
| | | } |
| | | |
| | | tvStatus = findViewById(R.id.tv_status); |
| | | tvSelectedService = findViewById(R.id.tv_selected_service); |
| | | mGroupListView = findViewById(R.id.groupListView); |
| | | etCommand = findViewById(R.id.et_command); |
| | | btnSend = findViewById(R.id.btn_send); |
| | | tvLog = findViewById(R.id.tv_log); |
| | | } |
| | | |
| | | @Override |
| | | protected void initData() { |
| | | mDevice = Singletion.getInstance().mDevice; |
| | | if (mDevice == null) { |
| | | appendLog("æªéæ©è®¾å¤!"); |
| | | return; |
| | | } |
| | | initConn(mDevice); |
| | | } |
| | | |
| | | @Override |
| | | protected void initEvent() { |
| | | btnSend.setOnClickListener(v -> { |
| | | String cmd = etCommand.getText().toString().trim(); |
| | | if (TextUtils.isEmpty(cmd)) { |
| | | Toast("请è¾å
¥æä»¤"); |
| | | return; |
| | | } |
| | | if (selectedCharacteristic == null) { |
| | | Toast("è¯·éæ©ä¸ä¸ªæå¡/ç¹å¾å¼"); |
| | | return; |
| | | } |
| | | sendCmd(cmd); |
| | | }); |
| | | } |
| | | |
| | | private void initConn(BluetoothLeDevice device) { |
| | | appendLog("æ£å¨è¿æ¥ " + device.getAddress() + "..."); |
| | | connectManager = BluetoothConnectManager.getInstance(this); |
| | | connectManager.addConnectStateListener(stateListener); |
| | | connectManager.setBluetoothGattCallback(new BluetoothGattCallback() { |
| | | @Override |
| | | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { |
| | | super.onConnectionStateChange(gatt, status, newState); |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.CONN_STATU, newState, "conn_statu")); |
| | | if (newState == BluetoothProfile.STATE_CONNECTED) { |
| | | mGatt = gatt; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onServicesDiscovered(BluetoothGatt gatt, int status) { |
| | | super.onServicesDiscovered(gatt, status); |
| | | if (status == BluetoothGatt.GATT_SUCCESS) { |
| | | runOnUiThread(() -> processServices(gatt.getServices())); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { |
| | | super.onCharacteristicWrite(gatt, characteristic, status); |
| | | String msg = (status == BluetoothGatt.GATT_SUCCESS) ? "åå
¥æå" : "åå
¥å¤±è´¥"; |
| | | String uuid = characteristic.getUuid().toString().substring(0, 8); |
| | | runOnUiThread(() -> appendLog(msg + " (" + uuid + ")")); |
| | | } |
| | | |
| | | @Override |
| | | public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { |
| | | super.onCharacteristicChanged(gatt, characteristic); |
| | | byte[] value = characteristic.getValue(); |
| | | String hex = bytesToHexString(value); |
| | | String uuid = characteristic.getUuid().toString().substring(0, 8); |
| | | runOnUiThread(() -> appendLog("æ¶å°(" + uuid + "): " + hex)); |
| | | |
| | | } |
| | | }); |
| | | connectManager.connect(device.getAddress()); |
| | | } |
| | | |
| | | private ConnectStateListener stateListener = new ConnectStateListener() { |
| | | @Override |
| | | public void onConnectStateChanged(String address, ConnectState state) { |
| | | runOnUiThread(() -> { |
| | | String stateStr = ""; |
| | | switch (state) { |
| | | case CONNECTED: stateStr = "å·²è¿æ¥"; break; |
| | | case CONNECTING: stateStr = "è¿æ¥ä¸"; break; |
| | | case NORMAL: stateStr = "å·²æå¼"; break; |
| | | default: stateStr = state.toString(); |
| | | } |
| | | tvStatus.setText("ç¶æ: " + stateStr); |
| | | appendLog("ç¶æåæ´: " + stateStr); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | private void processServices(List<BluetoothGattService> services) { |
| | | writeableCharacteristics.clear(); |
| | | int size = QMUIDisplayHelper.dp2px(this, 20); |
| | | QMUIGroupListView.Section section = QMUIGroupListView.newSection(this) |
| | | .setTitle("å¯åæå¡å表") |
| | | .setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT); |
| | | |
| | | for (BluetoothGattService service : services) { |
| | | for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) { |
| | | int props = characteristic.getProperties(); |
| | | // Filter for Write properties |
| | | if ((props & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0 || |
| | | (props & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) { |
| | | |
| | | writeableCharacteristics.add(characteristic); |
| | | |
| | | String propertyStr = getPropertyString(props); |
| | | String uuidFull = characteristic.getUuid().toString(); |
| | | |
| | | QMUICommonListItemView item = mGroupListView.createItemView(null, |
| | | "UUID: " + uuidFull, |
| | | propertyStr, |
| | | QMUICommonListItemView.VERTICAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_NONE); |
| | | |
| | | item.setTag(characteristic); |
| | | |
| | | section.addItemView(item, v -> { |
| | | if (v.getTag() instanceof BluetoothGattCharacteristic) { |
| | | handleCharacteristicSelection((BluetoothGattCharacteristic) v.getTag()); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | |
| | | section.addTo(mGroupListView); |
| | | |
| | | if (!writeableCharacteristics.isEmpty()) { |
| | | // Default select first |
| | | handleCharacteristicSelection(writeableCharacteristics.get(0)); |
| | | } else { |
| | | appendLog("æªæ¾å°å¯åç¹å¾å¼."); |
| | | } |
| | | } |
| | | |
| | | private void handleCharacteristicSelection(BluetoothGattCharacteristic characteristic) { |
| | | selectedCharacteristic = characteristic; |
| | | tvSelectedService.setText("å½åéæ©æå¡: " + selectedCharacteristic.getUuid().toString()); |
| | | appendLog("已鿩ç¹å¾å¼: " + selectedCharacteristic.getUuid()); |
| | | Toast("å·²éæ©è¯¥æå¡"); |
| | | |
| | | // Attempt to find and enable notification on a characteristic in the same service |
| | | BluetoothGattService service = characteristic.getService(); |
| | | boolean foundNotify = false; |
| | | for (BluetoothGattCharacteristic c : service.getCharacteristics()) { |
| | | int props = c.getProperties(); |
| | | if ((props & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0 || |
| | | (props & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) { |
| | | appendLog("å°è¯å¯ç¨éç¥: " + c.getUuid()); |
| | | enableNotification(c); |
| | | foundNotify = true; |
| | | // Found one, stop searching to avoid concurrent descriptor writes |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (!foundNotify) { |
| | | appendLog("å½åæå¡æªæ¾å°å¯éç¥çç¹å¾å¼"); |
| | | } |
| | | } |
| | | |
| | | private String getPropertyString(int property) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | if ((property & BluetoothGattCharacteristic.PROPERTY_READ) > 0) { |
| | | sb.append("读 "); |
| | | } |
| | | if ((property & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0 |
| | | || (property & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) { |
| | | sb.append("å "); |
| | | } |
| | | if ((property & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { |
| | | sb.append("éç¥ "); |
| | | } |
| | | if ((property & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) { |
| | | sb.append("æç¤º "); |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | private void enableNotification(BluetoothGattCharacteristic characteristic) { |
| | | if (mGatt == null || characteristic == null) return; |
| | | |
| | | boolean success = mGatt.setCharacteristicNotification(characteristic, true); |
| | | if (success) { |
| | | appendLog("å¯ç¨éç¥ç嬿å"); |
| | | |
| | | // Write Descriptor for Notify/Indicate |
| | | BluetoothGattDescriptor descriptor = characteristic.getDescriptor( |
| | | UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); |
| | | |
| | | if (descriptor != null) { |
| | | int props = characteristic.getProperties(); |
| | | byte[] value = null; |
| | | if ((props & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { |
| | | value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; |
| | | } else if ((props & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) { |
| | | value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; |
| | | } |
| | | |
| | | if (value != null) { |
| | | descriptor.setValue(value); |
| | | boolean writeDesc = mGatt.writeDescriptor(descriptor); |
| | | appendLog("åå
¥Descriptor: " + writeDesc); |
| | | } |
| | | } |
| | | } else { |
| | | appendLog("å¯ç¨éç¥çå¬å¤±è´¥!"); |
| | | } |
| | | } |
| | | |
| | | private void sendCmd(String hexCmd) { |
| | | if (selectedCharacteristic == null || mGatt == null) return; |
| | | |
| | | byte[] data = hexStringToBytes(hexCmd); |
| | | if (data == null) { |
| | | Toast("Hexæä»¤æ ¼å¼é误"); |
| | | return; |
| | | } |
| | | |
| | | selectedCharacteristic.setValue(data); |
| | | boolean status = mGatt.writeCharacteristic(selectedCharacteristic); |
| | | appendLog("åé: " + hexCmd + ", ç»æ: " + status); |
| | | } |
| | | |
| | | private void appendLog(String msg) { |
| | | tvLog.append(msg + "\n"); |
| | | ((View)tvLog.getParent()).post(() -> ((View)tvLog.getParent()).scrollTo(0, tvLog.getBottom())); |
| | | } |
| | | |
| | | public static byte[] hexStringToBytes(String hexString) { |
| | | if (hexString == null || hexString.equals("")) { |
| | | return null; |
| | | } |
| | | hexString = hexString.toUpperCase(); |
| | | hexString = hexString.replace(" ", ""); |
| | | if (hexString.length() % 2 != 0) { |
| | | hexString = "0" + hexString; |
| | | } |
| | | |
| | | int length = hexString.length() / 2; |
| | | char[] hexChars = hexString.toCharArray(); |
| | | byte[] d = new byte[length]; |
| | | for (int i = 0; i < length; i++) { |
| | | int pos = i * 2; |
| | | d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); |
| | | } |
| | | return d; |
| | | } |
| | | |
| | | private static byte charToByte(char c) { |
| | | return (byte) "0123456789ABCDEF".indexOf(c); |
| | | } |
| | | |
| | | public static String bytesToHexString(byte[] src) { |
| | | StringBuilder stringBuilder = new StringBuilder(""); |
| | | if (src == null || src.length <= 0) { |
| | | return null; |
| | | } |
| | | for (int i = 0; i < src.length; i++) { |
| | | int v = src[i] & 0xFF; |
| | | String hv = Integer.toHexString(v); |
| | | if (hv.length() < 2) { |
| | | stringBuilder.append(0); |
| | | } |
| | | stringBuilder.append(hv); |
| | | } |
| | | return stringBuilder.toString().toUpperCase(); |
| | | } |
| | | |
| | | @Subscribe(threadMode = ThreadMode.MAIN) |
| | | public void onEventRefresh(UpdateEvent event) { |
| | | // Handle global events if necessary |
| | | } |
| | | |
| | | @Override |
| | | protected void onDestroy() { |
| | | super.onDestroy(); |
| | | if (connectManager != null && mDevice != null) { |
| | | connectManager.removeConnectStateListener(stateListener); |
| | | connectManager.disconnect(mDevice.getAddress()); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.activity; |
| | | |
| | | import android.bluetooth.BluetoothGatt; |
| | | import android.bluetooth.BluetoothGattCharacteristic; |
| | | import android.bluetooth.BluetoothGattService; |
| | | import android.text.InputType; |
| | | import android.util.Log; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.widget.LinearLayout; |
| | | import android.widget.TextView; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.connect.BluetoothConnectManager; |
| | | import com.blakequ.bluetooth_manager_lib.connect.BluetoothSubScribeData; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectState; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectStateListener; |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | import com.blakequ.bluetooth_manager_lib.util.BluetoothUtils; |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | import com.blankj.utilcode.util.ThreadUtils; |
| | | import com.qmuiteam.qmui.util.QMUIDisplayHelper; |
| | | import com.qmuiteam.qmui.widget.dialog.QMUIDialog; |
| | | import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction; |
| | | import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView; |
| | | import com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView; |
| | | import com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.base.BaseActivity; |
| | | import com.shlb.comb.event.UpdateEvent; |
| | | import com.shlb.comb.util.CMD; |
| | | import com.shlb.comb.util.CRCutil; |
| | | import com.shlb.comb.util.Singletion; |
| | | |
| | | import org.greenrobot.eventbus.Subscribe; |
| | | import org.greenrobot.eventbus.ThreadMode; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.UUID; |
| | | |
| | | public class WriteActivity extends BaseActivity { |
| | | |
| | | QMUIGroupListView mGroupListView; |
| | | |
| | | QMUIRoundButton send; |
| | | |
| | | LinearLayout llRoot; |
| | | |
| | | TextView sendNum; |
| | | |
| | | |
| | | private BluetoothConnectManager connectManager; |
| | | private BluetoothGatt gatt; |
| | | |
| | | private BluetoothGattCharacteristic characteristic; |
| | | |
| | | |
| | | @Override |
| | | protected void contentView() { |
| | | setContentView(R.layout.activity_write); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void initView() { |
| | | super.initHead(); |
| | | mGroupListView = findViewById(R.id.groupListView); |
| | | send = findViewById(R.id.send); |
| | | llRoot = findViewById(R.id.ll_root); |
| | | sendNum = findViewById(R.id.sendNum); |
| | | initTitlebar(); |
| | | tv_center.setText("ç¼å"); |
| | | initGroupListView(); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void initData() { |
| | | String uuid = Singletion.getInstance().uuid; |
| | | initWrite(uuid); |
| | | |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void initEvent() { |
| | | send.setOnClickListener(this); |
| | | |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void onClick(View v) { |
| | | int id = v.getId(); |
| | | if (id == R.id.iv_left) { |
| | | finish(); |
| | | } else if (id == R.id.btnExit) { |
| | | writeCmd(CMD.EXIT); |
| | | } else if (id == R.id.btnInto) { |
| | | writeCmd(CMD.GETINTO); |
| | | } else if (id == R.id.btnClean) { |
| | | writeCmd(CMD.CLEAN); |
| | | } else if (id == R.id.btn1) { |
| | | sendYJCmd(1); |
| | | } else if (id == R.id.btn2) { |
| | | sendYJCmd(2); |
| | | } else if (id == R.id.btn3) { |
| | | sendYJCmd(3); |
| | | } else if (id == R.id.btn4) { |
| | | sendYJCmd(4); |
| | | } else if (id == R.id.btn5) { |
| | | sendYJCmd(5); |
| | | } else if (id == R.id.btn6) { |
| | | sendYJCmd(6); |
| | | } else if (id == R.id.btn7) { |
| | | sendYJCmd(7); |
| | | } else if (id == R.id.btn8) { |
| | | sendYJCmd(8); |
| | | } else if (id == R.id.btn9) { |
| | | sendYJCmd(9); |
| | | } else if (id == R.id.btn10) { |
| | | sendYJCmd(10); |
| | | } else if (id == R.id.btn11) { |
| | | sendYJCmd(11); |
| | | } else if (id == R.id.btn12) { |
| | | sendYJCmd(12); |
| | | } else if (id == R.id.btn13) { |
| | | sendYJCmd(13); |
| | | } else if (id == R.id.btn14) { |
| | | sendYJCmd(14); |
| | | } else if (id == R.id.btn15) { |
| | | sendYJCmd(15); |
| | | } else if (id == R.id.btn16) { |
| | | sendYJCmd(16); |
| | | } else if (id == R.id.send) { |
| | | switch (curCmd) { |
| | | case -1: |
| | | Toast("è¯·éæ©æä»¤!"); |
| | | break; |
| | | case 1: |
| | | //è¿å
¥ç¼å |
| | | writeCmd(CMD.GETINTO); |
| | | break; |
| | | case 2: |
| | | //æ¸
é¤å°å |
| | | writeCmd(CMD.CLEAN); |
| | | break; |
| | | case 3: |
| | | String addrNos = item3.getDetailText().toString(); |
| | | Integer addrNo = -1; |
| | | try { |
| | | addrNo = Integer.parseInt(addrNos); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | if (addrNo == -1) { |
| | | Toast("å°åæ°æ®å¼å¸¸"); |
| | | return; |
| | | |
| | | } |
| | | String sNo = Integer.toHexString(addrNo); |
| | | if (sNo.length() < 2) { |
| | | sNo = "0" + sNo; |
| | | } |
| | | |
| | | String cmd = CMD.ADDR_PREFIX + sNo + CMD.ADDR_SUFFIX; |
| | | byte[] bytes = invertStringToBytes(cmd); |
| | | String crc = CRCutil.getCRC(bytes); |
| | | String zl = cmd + crc; |
| | | LogUtils.e("reszl", zl); |
| | | writeCmd(zl); |
| | | Toast("ç¼åæä»¤å·²åéï¼"); |
| | | break; |
| | | case 4: |
| | | //éåºç¼å |
| | | writeCmd(CMD.EXIT); |
| | | break; |
| | | case 5: |
| | | String addrNos2 = item5.getDetailText().toString(); |
| | | Integer addrNo2 = -1; |
| | | try { |
| | | addrNo2 = Integer.parseInt(addrNos2); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | final Integer no2 = addrNo2; |
| | | if (no2 == -1) { |
| | | Toast("å°åæ°æ®å¼å¸¸"); |
| | | return; |
| | | |
| | | } |
| | | |
| | | ThreadUtils.executeBySingle(new ThreadUtils.SimpleTask<Object>() { |
| | | @Override |
| | | public Object doInBackground() throws Throwable { |
| | | writeCmd(CMD.GETINTO); |
| | | Thread.sleep(200); |
| | | |
| | | writeCmd(CMD.CLEAN); |
| | | Thread.sleep(200); |
| | | |
| | | String sNo2 = Integer.toHexString(no2); |
| | | // |
| | | if (sNo2.length() < 2) { |
| | | sNo2 = "0" + sNo2; |
| | | } |
| | | |
| | | String cmd = CMD.ADDR_PREFIX + sNo2 + CMD.ADDR_SUFFIX; |
| | | byte[] bytes = invertStringToBytes(cmd); |
| | | String crc = CRCutil.getCRC(bytes); |
| | | String zl = cmd + crc; |
| | | LogUtils.e("reszl", zl); |
| | | Thread.sleep(200); |
| | | writeCmd(zl); |
| | | |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | | public void onSuccess(Object result) { |
| | | |
| | | } |
| | | }); |
| | | Toast("ç¼åæä»¤å·²åéï¼"); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | @Subscribe(threadMode = ThreadMode.MAIN) |
| | | public void onEventReceiveNotify(UpdateEvent event) { |
| | | if (event != null && event.getType() == UpdateEvent.Type.BLE_DATA) { |
| | | BluetoothGattCharacteristic characteristic = (BluetoothGattCharacteristic) event.getObj(); |
| | | final byte[] dataArr = characteristic.getValue(); |
| | | final String flag = event.getMsg(); |
| | | if (dataArr != null && dataArr.length > 0) { |
| | | if (flag.equals("read")) { |
| | | } else if (flag.equals("write")) { |
| | | } else if (flag.equals("notify")) { |
| | | //è¿åç»æ |
| | | String crc = CRCutil.getCRC(dataArr); |
| | | LogUtils.e("resCRC", crc); |
| | | //CRC计ç®ç»æä¸º0æ¶å½ä»¤æ§è¡æåï¼èªå¨æ§è¡éåºå½ä»¤ |
| | | if (crc.equals("0")) { |
| | | writeCmd(CMD.EXIT); |
| | | Toast("æä½æåï¼èªå¨æ§è¡éåºç¼åæä»¤"); |
| | | curCmd = -1; |
| | | item6.setText("ç¼åæå"); |
| | | } |
| | | //计ç®è¿å为0æ¶ èªå¨éåº |
| | | |
| | | LogUtils.e("res", ByteUtils.byteArrayToHexString(dataArr)); |
| | | } else { |
| | | |
| | | Toast("è§£æè¿åä¿¡æ¯å¤±è´¥ï¼"); |
| | | } |
| | | } else { |
| | | Toast("è¿åä¿¡æ¯ä¸ºç©ºï¼"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | QMUICommonListItemView item1; |
| | | QMUICommonListItemView item2; |
| | | QMUICommonListItemView item3; |
| | | QMUICommonListItemView item4; |
| | | QMUICommonListItemView item5; |
| | | QMUICommonListItemView item6; |
| | | View.OnClickListener onClickListener; |
| | | int curCmd = -1; |
| | | |
| | | String[] cmdName = {"è¿å
¥ç¼å", "æ¸
é¤å°å", "ç¼è¾å°å", "éåºç¼å", "ä¸é®ç¼å"}; |
| | | |
| | | private void initGroupListView() { |
| | | item1 = mGroupListView.createItemView(null, |
| | | cmdName[0], "", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | |
| | | item2 = mGroupListView.createItemView(null, |
| | | cmdName[1], "", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | |
| | | item3 = mGroupListView.createItemView(null, |
| | | cmdName[2], "ç¹å»éæ©ç¼å·", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | item4 = mGroupListView.createItemView(null, |
| | | cmdName[3], "", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | |
| | | |
| | | onClickListener = new View.OnClickListener() { |
| | | @Override |
| | | public void onClick(View v) { |
| | | if (v instanceof QMUICommonListItemView) { |
| | | CharSequence text = ((QMUICommonListItemView) v).getText(); |
| | | //è¿å
¥ç¼å |
| | | if (text.equals(cmdName[0])) { |
| | | curCmd = 1; |
| | | item6.setText(cmdName[0]); |
| | | item6.setDetailText(""); |
| | | //æ¸
é¤å°å |
| | | } else if (text.equals(cmdName[1])) { |
| | | curCmd = 2; |
| | | item6.setText(cmdName[1]); |
| | | item6.setDetailText(""); |
| | | //ç¼è¾å°å |
| | | } else if (text.equals(cmdName[2])) { |
| | | showEditTextDialog(); |
| | | //éåºç¼å |
| | | } else if (text.equals(cmdName[3])) { |
| | | curCmd = 4; |
| | | item6.setText(cmdName[3]); |
| | | item6.setDetailText(""); |
| | | //ä¸é®ç¼å |
| | | } else if (text.equals(cmdName[4])) { |
| | | showQuickDialog(); |
| | | } |
| | | |
| | | |
| | | } |
| | | } |
| | | }; |
| | | |
| | | int size = QMUIDisplayHelper.dp2px(this, 20); |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("Section 1:æä½æä»¤") |
| | | .setDescription("") |
| | | .setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT) |
| | | .addItemView(item1, onClickListener) |
| | | .addItemView(item2, onClickListener) |
| | | .addItemView(item3, onClickListener) |
| | | .addItemView(item4, onClickListener) |
| | | .addTo(mGroupListView); |
| | | |
| | | item5 = mGroupListView.createItemView(cmdName[4]); |
| | | item5.setOrientation(QMUICommonListItemView.VERTICAL); |
| | | item5.setDetailText("è¿å
¥-æ¸
é¤-ç¼å-éåº"); |
| | | item5.setAccessoryType(QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | |
| | | item6 = mGroupListView.createItemView("å¾
éæ©"); |
| | | item6.setOrientation(QMUICommonListItemView.HORIZONTAL); |
| | | |
| | | |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("Section 2:å¿«æ·æä»¤") |
| | | .setDescription("") |
| | | .setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT) |
| | | .addItemView(item5, onClickListener) |
| | | .addTo(mGroupListView); |
| | | |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("Section 3:å½åæä»¤") |
| | | .setDescription("") |
| | | .setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT) |
| | | .addItemView(item6, onClickListener) |
| | | .addTo(mGroupListView); |
| | | |
| | | } |
| | | |
| | | /** |
| | | * åå§ååå
¥æå¡ |
| | | * |
| | | * @param uu |
| | | */ |
| | | private void initWrite(String uu) { |
| | | BluetoothLeDevice mDevice = Singletion.getInstance().mDevice; |
| | | connectManager = BluetoothConnectManager.getInstance(this); |
| | | connectManager.addConnectStateListener(listener); |
| | | String uuid = uu; |
| | | UUID serverUUid = null; |
| | | gatt = connectManager.getBluetoothGatt(mDevice.getAddress()); |
| | | if (gatt != null) { |
| | | List<BluetoothGattService> list = gatt.getServices(); |
| | | if (list != null) { |
| | | for (BluetoothGattService service : list) { |
| | | for (BluetoothGattCharacteristic characteristics : service.getCharacteristics()) { |
| | | if (characteristics.getUuid().toString().equals(uuid)) { |
| | | characteristic = characteristics; |
| | | serverUUid = service.getUuid(); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | if (serverUUid == null) { |
| | | Toast("serverUUidä¸è½ä¸ºç©º!"); |
| | | return; |
| | | } |
| | | //1.set service uuid |
| | | connectManager.setServiceUUID(serverUUid.toString()); |
| | | //2.clean history descriptor data |
| | | connectManager.cleanSubscribeData(); |
| | | //3.add subscribe params |
| | | if (BluetoothUtils.isCharacteristicRead(characteristic.getProperties())) { |
| | | connectManager.addBluetoothSubscribeData( |
| | | new BluetoothSubScribeData.Builder().setCharacteristicRead(characteristic.getUuid()).build()); |
| | | } |
| | | if (BluetoothUtils.isCharacteristicNotify(characteristic.getProperties())) { |
| | | connectManager.addBluetoothSubscribeData( |
| | | new BluetoothSubScribeData.Builder().setCharacteristicNotify(characteristic.getUuid()).build() |
| | | ); |
| | | } |
| | | |
| | | connectManager.startSubscribe(gatt); |
| | | } |
| | | |
| | | private void writeCmd(String cmd) { |
| | | int len = cmd.length(); |
| | | if (len > 0 && len % 2 == 0) { |
| | | byte[] bytes = invertStringToBytes(cmd); |
| | | if (bytes != null) { |
| | | characteristic.setValue(bytes); |
| | | gatt.writeCharacteristic(characteristic); |
| | | runOnUiThread(() -> { |
| | | item6.setDetailText(item6.getDetailText() + " å·²åé"); |
| | | }); |
| | | } else { |
| | | Log.e("tag", "write value fail"); |
| | | } |
| | | } else { |
| | | |
| | | runOnUiThread(() -> { |
| | | Toast("æä»¤é误ï¼"); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | private byte[] invertStringToBytes(String value) { |
| | | int len = value.length() / 2; |
| | | if (len > 0) { |
| | | byte[] bytes = new byte[len]; |
| | | for (int i = 0; i < len; i++) { |
| | | Integer val = Integer.valueOf(value.substring(i * 2, i * 2 + 2), 16); |
| | | bytes[i] = val.byteValue(); |
| | | } |
| | | return bytes; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private List<Map<String, Object>> getWriteData() { |
| | | List<Map<String, Object>> res = new ArrayList<>(); |
| | | Map<String, Object> map1 = new HashMap<>(); |
| | | map1.put("name", "è¿å
¥ç¼åç¶æ"); |
| | | map1.put("flag", "0"); |
| | | map1.put("value", "A55A060100000000002F35"); |
| | | res.add(map1); |
| | | Map<String, Object> map2 = new HashMap<>(); |
| | | map2.put("name", "éåºç¼åç¶æ"); |
| | | map2.put("flag", "0"); |
| | | map2.put("value", "A55A060400000000002F60"); |
| | | res.add(map2); |
| | | Map<String, Object> map3 = new HashMap<>(); |
| | | map3.put("name", "ç¼å"); |
| | | map3.put("flag", "0"); |
| | | map3.put("value", "A55A060300020000002F6F"); |
| | | res.add(map3); |
| | | Map<String, Object> map4 = new HashMap<>(); |
| | | map4.put("name", "æ¸
é¤å°å"); |
| | | map4.put("flag", "0"); |
| | | map4.put("value", "A55A060200000000002F06"); |
| | | res.add(map4); |
| | | return res; |
| | | |
| | | } |
| | | |
| | | private ConnectStateListener listener = new ConnectStateListener() { |
| | | @Override |
| | | public void onConnectStateChanged(String address, ConnectState state) { |
| | | switch (state) { |
| | | case CONNECTED: |
| | | break; |
| | | case CONNECTING: |
| | | break; |
| | | case NORMAL: |
| | | break; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * è¾å
¥ç¼ååºå·å æ§è¡ç¼åæä»¤ |
| | | */ |
| | | private void showEditTextDialog() { |
| | | final QMUIDialog.EditTextDialogBuilder builder = new QMUIDialog.EditTextDialogBuilder(this); |
| | | builder.setTitle("æç¤º") |
| | | .setPlaceholder("请è¾å
¥ç¼åå·ç (1-16)") |
| | | .setInputType(InputType.TYPE_CLASS_TEXT) |
| | | .addAction("åæ¶", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | dialog.dismiss(); |
| | | } |
| | | }) |
| | | .addAction("ç¡®å®", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | String text = builder.getEditText().getText().toString(); |
| | | Integer res = -1; |
| | | try { |
| | | res = Integer.parseInt(text); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | if (res == null || res < 0 || res > 16) { |
| | | Toast("请è¾å
¥1-16æ°å"); |
| | | } else { |
| | | item3.setDetailText(text); |
| | | curCmd = 3; |
| | | item6.setText(cmdName[2]); |
| | | item6.setDetailText(text); |
| | | dialog.dismiss(); |
| | | |
| | | } |
| | | |
| | | |
| | | } |
| | | }) |
| | | .create(com.qmuiteam.qmui.R.style.QMUI_Dialog).show(); |
| | | } |
| | | |
| | | /** |
| | | * è¾å
¥ç¼ååºå·å ä¸é®æ§è¡æææä»¤ |
| | | */ |
| | | private void showQuickDialog() { |
| | | final QMUIDialog.EditTextDialogBuilder builder = new QMUIDialog.EditTextDialogBuilder(this); |
| | | builder.setTitle("æç¤º") |
| | | .setPlaceholder("请è¾å
¥ç¼åå·ç (1-16)") |
| | | .setInputType(InputType.TYPE_CLASS_TEXT) |
| | | .addAction("åæ¶", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | dialog.dismiss(); |
| | | } |
| | | }) |
| | | .addAction("ç¡®å®", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | String text = builder.getEditText().getText().toString(); |
| | | Integer addrrNo = -1; |
| | | try { |
| | | addrrNo = Integer.parseInt(text); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | if (addrrNo == null || addrrNo < 0 || addrrNo > 16) { |
| | | Toast("请è¾å
¥1-16æ°å"); |
| | | } else { |
| | | curCmd = 5; |
| | | item5.setDetailText(text); |
| | | item6.setText(cmdName[4]); |
| | | item6.setDetailText(text); |
| | | dialog.dismiss(); |
| | | |
| | | |
| | | } |
| | | |
| | | |
| | | } |
| | | }) |
| | | .create(com.qmuiteam.qmui.R.style.QMUI_Dialog).show(); |
| | | } |
| | | |
| | | public void initTitlebar() { |
| | | iv_right.setVisibility(View.VISIBLE); |
| | | iv_left.setImageDrawable(getResources().getDrawable(R.mipmap.icon_back)); |
| | | iv_right.setImageDrawable(getResources().getDrawable(R.mipmap.icon_topbar_overflow)); |
| | | } |
| | | |
| | | /** |
| | | * åéä¸é®å½ä»¤ |
| | | * @param no2 |
| | | */ |
| | | private void sendYJCmd(int no2){ |
| | | sendNum.setText(no2+""); |
| | | |
| | | |
| | | if (no2 > 16|| no2<1 ) { |
| | | Toast("å°åæ°æ®å¼å¸¸"); |
| | | return; |
| | | |
| | | } |
| | | |
| | | ThreadUtils.executeBySingle(new ThreadUtils.SimpleTask<Object>() { |
| | | @Override |
| | | public Object doInBackground() throws Throwable { |
| | | writeCmd(CMD.GETINTO); |
| | | Thread.sleep(200); |
| | | |
| | | writeCmd(CMD.CLEAN); |
| | | Thread.sleep(200); |
| | | |
| | | String sNo2 = Integer.toHexString(no2); |
| | | // |
| | | if (sNo2.length() < 2) { |
| | | sNo2 = "0" + sNo2; |
| | | } |
| | | |
| | | String cmd = CMD.ADDR_PREFIX + sNo2 + CMD.ADDR_SUFFIX; |
| | | byte[] bytes = invertStringToBytes(cmd); |
| | | String crc = CRCutil.getCRC(bytes); |
| | | String zl = cmd + crc; |
| | | LogUtils.e("reszl", zl); |
| | | Thread.sleep(200); |
| | | writeCmd(zl); |
| | | |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | | public void onSuccess(Object result) { |
| | | |
| | | } |
| | | }); |
| | | Toast("ç¼åæä»¤å·²åéï¼"); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.activity; |
| | | |
| | | import android.bluetooth.BluetoothGatt; |
| | | import android.bluetooth.BluetoothGattCharacteristic; |
| | | import android.bluetooth.BluetoothGattService; |
| | | import android.text.InputType; |
| | | import android.util.Log; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.connect.BluetoothConnectManager; |
| | | import com.blakequ.bluetooth_manager_lib.connect.BluetoothSubScribeData; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectState; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectStateListener; |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | import com.blakequ.bluetooth_manager_lib.util.BluetoothUtils; |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | import com.blankj.utilcode.util.ThreadUtils; |
| | | import com.qmuiteam.qmui.util.QMUIDisplayHelper; |
| | | import com.qmuiteam.qmui.widget.dialog.QMUIDialog; |
| | | import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction; |
| | | import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView; |
| | | import com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView; |
| | | import com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.base.BaseActivity; |
| | | import com.shlb.comb.event.UpdateEvent; |
| | | import com.shlb.comb.util.CMD; |
| | | import com.shlb.comb.util.CRCutil; |
| | | import com.shlb.comb.util.Singletion; |
| | | |
| | | import org.greenrobot.eventbus.Subscribe; |
| | | import org.greenrobot.eventbus.ThreadMode; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.UUID; |
| | | |
| | | public class WriteActivity2 extends BaseActivity { |
| | | |
| | | QMUIGroupListView mGroupListView; |
| | | |
| | | QMUIRoundButton send; |
| | | |
| | | private BluetoothConnectManager connectManager; |
| | | private BluetoothGatt gatt; |
| | | |
| | | private BluetoothGattCharacteristic characteristic; |
| | | |
| | | |
| | | @Override |
| | | protected void contentView() { |
| | | setContentView(R.layout.activity_write); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void initView() { |
| | | super.initHead(); |
| | | mGroupListView = findViewById(R.id.groupListView); |
| | | send = findViewById(R.id.send); |
| | | initTitlebar(); |
| | | tv_center.setText("ç¼å"); |
| | | initGroupListView(); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void initData() { |
| | | String uuid = Singletion.getInstance().uuid; |
| | | initWrite(uuid); |
| | | |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void initEvent() { |
| | | send.setOnClickListener(this); |
| | | |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void onClick(View v) { |
| | | int id = v.getId(); |
| | | if (id == R.id.iv_left) { |
| | | finish(); |
| | | } else if (id == R.id.iv_right) { |
| | | } else if (id == R.id.send) { |
| | | switch (curCmd){ |
| | | case -1: |
| | | Toast("è¯·éæ©æä»¤!"); |
| | | break; |
| | | case 1: |
| | | //è¿å
¥ç¼å |
| | | writeCmd(CMD.GETINTO); |
| | | break; |
| | | case 2: |
| | | //æ¸
é¤å°å |
| | | writeCmd(CMD.CLEAN); |
| | | break; |
| | | case 3: |
| | | String addrNos = item3.getDetailText().toString(); |
| | | Integer addrNo = -1; |
| | | try { |
| | | addrNo = Integer.parseInt(addrNos); |
| | | }catch (Exception e){ |
| | | e.printStackTrace(); |
| | | } |
| | | if(addrNo == -1 ){ |
| | | Toast("å°åæ°æ®å¼å¸¸"); |
| | | return; |
| | | |
| | | } |
| | | String sNo = Integer.toHexString(addrNo); |
| | | if(sNo.length()<2){ |
| | | sNo = "0" + sNo; |
| | | } |
| | | |
| | | String cmd = CMD.ADDR_PREFIX + sNo + CMD.ADDR_SUFFIX; |
| | | byte[] bytes = invertStringToBytes(cmd); |
| | | String crc = CRCutil.getCRC(bytes); |
| | | String zl = cmd + crc; |
| | | LogUtils.e("reszl", zl); |
| | | writeCmd(zl); |
| | | Toast("ç¼åæä»¤å·²åéï¼"); |
| | | break; |
| | | case 4: |
| | | //éåºç¼å |
| | | writeCmd(CMD.EXIT); |
| | | break; |
| | | case 5: |
| | | String addrNos2 = item5.getDetailText().toString(); |
| | | Integer addrNo2 = -1; |
| | | try { |
| | | addrNo2 = Integer.parseInt(addrNos2); |
| | | }catch (Exception e){ |
| | | e.printStackTrace(); |
| | | } |
| | | final Integer no2 = addrNo2; |
| | | if(no2 == -1 ){ |
| | | Toast("å°åæ°æ®å¼å¸¸"); |
| | | return; |
| | | |
| | | } |
| | | |
| | | ThreadUtils.executeBySingle(new ThreadUtils.SimpleTask<Object>() { |
| | | @Override |
| | | public Object doInBackground() throws Throwable { |
| | | writeCmd(CMD.GETINTO); |
| | | Thread.sleep(200); |
| | | |
| | | writeCmd(CMD.CLEAN); |
| | | Thread.sleep(200); |
| | | |
| | | String sNo2 = Integer.toHexString(no2); |
| | | // |
| | | if(sNo2.length()<2){ |
| | | sNo2 = "0" + sNo2; |
| | | } |
| | | |
| | | String cmd = CMD.ADDR_PREFIX + sNo2 + CMD.ADDR_SUFFIX; |
| | | byte[] bytes = invertStringToBytes(cmd); |
| | | String crc = CRCutil.getCRC(bytes); |
| | | String zl = cmd + crc; |
| | | LogUtils.e("reszl", zl); |
| | | Thread.sleep(200); |
| | | writeCmd(zl); |
| | | |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | | public void onSuccess(Object result) { |
| | | |
| | | } |
| | | }); |
| | | Toast("ç¼åæä»¤å·²åéï¼"); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | @Subscribe(threadMode = ThreadMode.MAIN) |
| | | public void onEventReceiveNotify(UpdateEvent event) { |
| | | if (event != null && event.getType() == UpdateEvent.Type.BLE_DATA) { |
| | | BluetoothGattCharacteristic characteristic = (BluetoothGattCharacteristic) event.getObj(); |
| | | final byte[] dataArr = characteristic.getValue(); |
| | | final String flag = event.getMsg(); |
| | | if (dataArr != null && dataArr.length > 0) { |
| | | if (flag.equals("read")) { |
| | | } else if (flag.equals("write")) { |
| | | } else if (flag.equals("notify")) { |
| | | //è¿åç»æ |
| | | String crc = CRCutil.getCRC(dataArr); |
| | | LogUtils.e("resCRC", crc); |
| | | //CRC计ç®ç»æä¸º0æ¶å½ä»¤æ§è¡æåï¼èªå¨æ§è¡éåºå½ä»¤ |
| | | if (crc.equals("0")) { |
| | | writeCmd(CMD.EXIT); |
| | | Toast("æä½æåï¼èªå¨æ§è¡éåºç¼åæä»¤"); |
| | | curCmd = -1; |
| | | item6.setText("ç¼åæå"); |
| | | } |
| | | //计ç®è¿å为0æ¶ èªå¨éåº |
| | | |
| | | LogUtils.e("res", ByteUtils.byteArrayToHexString(dataArr)); |
| | | } else { |
| | | |
| | | Toast("è§£æè¿åä¿¡æ¯å¤±è´¥ï¼"); |
| | | } |
| | | } else { |
| | | Toast("è¿åä¿¡æ¯ä¸ºç©ºï¼"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | QMUICommonListItemView item1; |
| | | QMUICommonListItemView item2; |
| | | QMUICommonListItemView item3; |
| | | QMUICommonListItemView item4; |
| | | QMUICommonListItemView item5; |
| | | QMUICommonListItemView item6; |
| | | View.OnClickListener onClickListener; |
| | | int curCmd = -1; |
| | | |
| | | String[] cmdName = {"è¿å
¥ç¼å", "æ¸
é¤å°å", "ç¼è¾å°å", "éåºç¼å", "ä¸é®ç¼å"}; |
| | | private void initGroupListView() { |
| | | item1 = mGroupListView.createItemView(null, |
| | | cmdName[0], "", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | |
| | | item2 = mGroupListView.createItemView(null, |
| | | cmdName[1], "", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | |
| | | item3 = mGroupListView.createItemView(null, |
| | | cmdName[2], "ç¹å»éæ©ç¼å·", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | item4 = mGroupListView.createItemView(null, |
| | | cmdName[3], "", |
| | | QMUICommonListItemView.HORIZONTAL, |
| | | QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | |
| | | |
| | | onClickListener = new View.OnClickListener() { |
| | | @Override |
| | | public void onClick(View v) { |
| | | if (v instanceof QMUICommonListItemView) { |
| | | CharSequence text = ((QMUICommonListItemView) v).getText(); |
| | | //è¿å
¥ç¼å |
| | | if (text.equals(cmdName[0])) { |
| | | curCmd = 1; |
| | | item6.setText(cmdName[0]); |
| | | item6.setDetailText(""); |
| | | //æ¸
é¤å°å |
| | | } else if (text.equals(cmdName[1])) { |
| | | curCmd = 2; |
| | | item6.setText(cmdName[1]); |
| | | item6.setDetailText(""); |
| | | //ç¼è¾å°å |
| | | } else if (text.equals(cmdName[2])) { |
| | | showEditTextDialog(); |
| | | //éåºç¼å |
| | | } else if (text.equals(cmdName[3])) { |
| | | curCmd = 4; |
| | | item6.setText(cmdName[3]); |
| | | item6.setDetailText(""); |
| | | //ä¸é®ç¼å |
| | | }else if (text.equals(cmdName[4])) { |
| | | showQuickDialog(); |
| | | } |
| | | |
| | | |
| | | } |
| | | } |
| | | }; |
| | | |
| | | int size = QMUIDisplayHelper.dp2px(this, 20); |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("Section 1:æä½æä»¤") |
| | | .setDescription("") |
| | | .setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT) |
| | | .addItemView(item1, onClickListener) |
| | | .addItemView(item2, onClickListener) |
| | | .addItemView(item3, onClickListener) |
| | | .addItemView(item4, onClickListener) |
| | | .addTo(mGroupListView); |
| | | |
| | | item5 = mGroupListView.createItemView(cmdName[4]); |
| | | item5.setOrientation(QMUICommonListItemView.VERTICAL); |
| | | item5.setDetailText("è¿å
¥-æ¸
é¤-ç¼å-éåº"); |
| | | item5.setAccessoryType(QMUICommonListItemView.ACCESSORY_TYPE_CHEVRON); |
| | | |
| | | item6 = mGroupListView.createItemView("å¾
éæ©"); |
| | | item6.setOrientation(QMUICommonListItemView.HORIZONTAL); |
| | | |
| | | |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("Section 2:å¿«æ·æä»¤") |
| | | .setDescription("") |
| | | .setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT) |
| | | .addItemView(item5, onClickListener) |
| | | .addTo(mGroupListView); |
| | | |
| | | QMUIGroupListView.newSection(this) |
| | | .setTitle("Section 3:å½åæä»¤") |
| | | .setDescription("") |
| | | .setLeftIconSize(size, ViewGroup.LayoutParams.WRAP_CONTENT) |
| | | .addItemView(item6, onClickListener) |
| | | .addTo(mGroupListView); |
| | | |
| | | } |
| | | |
| | | /** |
| | | * åå§ååå
¥æå¡ |
| | | * |
| | | * @param uu |
| | | */ |
| | | private void initWrite(String uu) { |
| | | BluetoothLeDevice mDevice = Singletion.getInstance().mDevice; |
| | | connectManager = BluetoothConnectManager.getInstance(this); |
| | | connectManager.addConnectStateListener(listener); |
| | | String uuid = uu; |
| | | UUID serverUUid = null; |
| | | gatt = connectManager.getBluetoothGatt(mDevice.getAddress()); |
| | | if (gatt != null) { |
| | | List<BluetoothGattService> list = gatt.getServices(); |
| | | if (list != null) { |
| | | for (BluetoothGattService service : list) { |
| | | for (BluetoothGattCharacteristic characteristics : service.getCharacteristics()) { |
| | | if (characteristics.getUuid().toString().equals(uuid)) { |
| | | characteristic = characteristics; |
| | | serverUUid = service.getUuid(); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | if (serverUUid == null) { |
| | | Toast("serverUUidä¸è½ä¸ºç©º!"); |
| | | return; |
| | | } |
| | | //1.set service uuid |
| | | connectManager.setServiceUUID(serverUUid.toString()); |
| | | //2.clean history descriptor data |
| | | connectManager.cleanSubscribeData(); |
| | | //3.add subscribe params |
| | | if (BluetoothUtils.isCharacteristicRead(characteristic.getProperties())) { |
| | | connectManager.addBluetoothSubscribeData( |
| | | new BluetoothSubScribeData.Builder().setCharacteristicRead(characteristic.getUuid()).build()); |
| | | } |
| | | if (BluetoothUtils.isCharacteristicNotify(characteristic.getProperties())) { |
| | | connectManager.addBluetoothSubscribeData( |
| | | new BluetoothSubScribeData.Builder().setCharacteristicNotify(characteristic.getUuid()).build() |
| | | ); |
| | | } |
| | | |
| | | connectManager.startSubscribe(gatt); |
| | | } |
| | | |
| | | private void writeCmd(String cmd) { |
| | | int len = cmd.length(); |
| | | if (len > 0 && len % 2 == 0) { |
| | | byte[] bytes = invertStringToBytes(cmd); |
| | | if (bytes != null) { |
| | | characteristic.setValue(bytes); |
| | | gatt.writeCharacteristic(characteristic); |
| | | runOnUiThread(()->{ |
| | | item6.setDetailText(item6.getDetailText()+" å·²åé"); |
| | | }); |
| | | } else { |
| | | Log.e("tag", "write value fail"); |
| | | } |
| | | } else { |
| | | |
| | | runOnUiThread(()->{ |
| | | Toast("æä»¤é误ï¼"); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | private byte[] invertStringToBytes(String value) { |
| | | int len = value.length() / 2; |
| | | if (len > 0) { |
| | | byte[] bytes = new byte[len]; |
| | | for (int i = 0; i < len; i++) { |
| | | Integer val = Integer.valueOf(value.substring(i * 2, i * 2 + 2), 16); |
| | | bytes[i] = val.byteValue(); |
| | | } |
| | | return bytes; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private List<Map<String, Object>> getWriteData() { |
| | | List<Map<String, Object>> res = new ArrayList<>(); |
| | | Map<String, Object> map1 = new HashMap<>(); |
| | | map1.put("name", "è¿å
¥ç¼åç¶æ"); |
| | | map1.put("flag", "0"); |
| | | map1.put("value", "A55A060100000000002F35"); |
| | | res.add(map1); |
| | | Map<String, Object> map2 = new HashMap<>(); |
| | | map2.put("name", "éåºç¼åç¶æ"); |
| | | map2.put("flag", "0"); |
| | | map2.put("value", "A55A060400000000002F60"); |
| | | res.add(map2); |
| | | Map<String, Object> map3 = new HashMap<>(); |
| | | map3.put("name", "ç¼å"); |
| | | map3.put("flag", "0"); |
| | | map3.put("value", "A55A060300020000002F6F"); |
| | | res.add(map3); |
| | | Map<String, Object> map4 = new HashMap<>(); |
| | | map4.put("name", "æ¸
é¤å°å"); |
| | | map4.put("flag", "0"); |
| | | map4.put("value", "A55A060200000000002F06"); |
| | | res.add(map4); |
| | | return res; |
| | | |
| | | } |
| | | |
| | | private ConnectStateListener listener = new ConnectStateListener() { |
| | | @Override |
| | | public void onConnectStateChanged(String address, ConnectState state) { |
| | | switch (state) { |
| | | case CONNECTED: |
| | | break; |
| | | case CONNECTING: |
| | | break; |
| | | case NORMAL: |
| | | break; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * è¾å
¥ç¼ååºå·å æ§è¡ç¼åæä»¤ |
| | | */ |
| | | private void showEditTextDialog() { |
| | | final QMUIDialog.EditTextDialogBuilder builder = new QMUIDialog.EditTextDialogBuilder(this); |
| | | builder.setTitle("æç¤º") |
| | | .setPlaceholder("请è¾å
¥ç¼åå·ç (1-16)") |
| | | .setInputType(InputType.TYPE_CLASS_TEXT) |
| | | .addAction("åæ¶", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | dialog.dismiss(); |
| | | } |
| | | }) |
| | | .addAction("ç¡®å®", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | String text = builder.getEditText().getText().toString(); |
| | | Integer res = -1; |
| | | try { |
| | | res = Integer.parseInt(text); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | if (res == null || res < 0 || res > 16) { |
| | | Toast("请è¾å
¥1-16æ°å"); |
| | | } else { |
| | | item3.setDetailText(text); |
| | | curCmd = 3; |
| | | item6.setText(cmdName[2]); |
| | | item6.setDetailText(text); |
| | | dialog.dismiss(); |
| | | |
| | | } |
| | | |
| | | |
| | | } |
| | | }) |
| | | .create(com.qmuiteam.qmui.R.style.QMUI_Dialog).show(); |
| | | } |
| | | |
| | | /** |
| | | * è¾å
¥ç¼ååºå·å ä¸é®æ§è¡æææä»¤ |
| | | */ |
| | | private void showQuickDialog() { |
| | | final QMUIDialog.EditTextDialogBuilder builder = new QMUIDialog.EditTextDialogBuilder(this); |
| | | builder.setTitle("æç¤º") |
| | | .setPlaceholder("请è¾å
¥ç¼åå·ç (1-16)") |
| | | .setInputType(InputType.TYPE_CLASS_TEXT) |
| | | .addAction("åæ¶", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | dialog.dismiss(); |
| | | } |
| | | }) |
| | | .addAction("ç¡®å®", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | String text = builder.getEditText().getText().toString(); |
| | | Integer addrrNo = -1; |
| | | try { |
| | | addrrNo = Integer.parseInt(text); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | if (addrrNo == null || addrrNo < 0 || addrrNo > 16) { |
| | | Toast("请è¾å
¥1-16æ°å"); |
| | | } else { |
| | | curCmd = 5; |
| | | item5.setDetailText(text); |
| | | item6.setText(cmdName[4]); |
| | | item6.setDetailText(text); |
| | | dialog.dismiss(); |
| | | |
| | | |
| | | } |
| | | |
| | | |
| | | } |
| | | }) |
| | | .create(com.qmuiteam.qmui.R.style.QMUI_Dialog).show(); |
| | | } |
| | | |
| | | public void initTitlebar() { |
| | | iv_right.setVisibility(View.VISIBLE); |
| | | iv_left.setImageDrawable(getResources().getDrawable(R.mipmap.icon_back)); |
| | | iv_right.setImageDrawable(getResources().getDrawable(R.mipmap.icon_topbar_overflow)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /* |
| | | * FileName: BaseArrayListAdapter.java |
| | | * Copyright (C) 2014 Plusub Tech. Co. Ltd. All Rights Reserved <admin@plusub.com> |
| | | * |
| | | * Licensed under the Plusub License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * |
| | | * author : quhao <blakequ@gmail.com> |
| | | * date : 2014-6-7 ä¸å9:50:52 |
| | | * last modify author : |
| | | * version : 1.0 |
| | | */ |
| | | package com.shlb.comb.adapter; |
| | | |
| | | import android.content.Context; |
| | | import android.util.DisplayMetrics; |
| | | import android.view.LayoutInflater; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.view.WindowManager; |
| | | import android.widget.BaseAdapter; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * æ°ç»éé
å¨ï¼åªéè¦å®ç° {@link #getView(int, View, ViewGroup)} |
| | | * @author blakequ Blakequ@gmail.com |
| | | * |
| | | * @param <T> |
| | | */ |
| | | public abstract class BaseArrayListAdapter<T> extends BaseAdapter { |
| | | |
| | | protected Context mContext; |
| | | protected LayoutInflater mInflater; |
| | | private List<T> mDatas; |
| | | /**å±å¹ç宽度*/ |
| | | protected int mScreenWidth; |
| | | /**å±å¹é«åº¦*/ |
| | | protected int mScreenHeight; |
| | | /**å±å¹å¯åº¦*/ |
| | | protected float mDensity; |
| | | |
| | | public BaseArrayListAdapter(Context context){ |
| | | mContext = context; |
| | | mInflater = LayoutInflater.from(context); |
| | | mDatas = new ArrayList<T>(); |
| | | DisplayMetrics metric = new DisplayMetrics(); |
| | | WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); |
| | | wm.getDefaultDisplay().getMetrics(metric); |
| | | mScreenWidth = metric.widthPixels; |
| | | mScreenHeight = metric.heightPixels; |
| | | mDensity = metric.density; |
| | | } |
| | | |
| | | public BaseArrayListAdapter(Context context, List<T> datas){ |
| | | mContext = context; |
| | | mInflater = LayoutInflater.from(context); |
| | | if (datas != null && datas.size() > 0) { |
| | | mDatas = datas; |
| | | }else{ |
| | | mDatas = new ArrayList<T>(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * get context |
| | | * <p>Title: getContext |
| | | * <p>Description: |
| | | * @return |
| | | */ |
| | | public Context getContext() { |
| | | return mContext; |
| | | } |
| | | |
| | | public LayoutInflater getInflater() { |
| | | return mInflater; |
| | | } |
| | | |
| | | /** |
| | | * get screen width |
| | | * <p>Title: getScreenWidth |
| | | * <p>Description: |
| | | * @return |
| | | */ |
| | | public int getScreenWidth() { |
| | | return mScreenWidth; |
| | | } |
| | | |
| | | /** |
| | | * get screen height |
| | | * <p>Title: getScreenHeight |
| | | * <p>Description: |
| | | * @return |
| | | */ |
| | | public int getScreenHeight() { |
| | | return mScreenHeight; |
| | | } |
| | | |
| | | /** |
| | | * return all data |
| | | * <p>Title: getAllData |
| | | * <p>Description: |
| | | * @return |
| | | */ |
| | | public List<T> getAllData(){ |
| | | return mDatas; |
| | | } |
| | | |
| | | /** |
| | | * add data to head |
| | | * <p>Title: addHead |
| | | * <p>Description: |
| | | * @param datas |
| | | */ |
| | | public void addHead(T datas){ |
| | | if (datas != null) { |
| | | mDatas.add(0, datas); |
| | | notifyDataSetChanged(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * add data list to head |
| | | * <p>Title: addHead |
| | | * <p>Description: |
| | | * @param datas |
| | | */ |
| | | public void addHead(List<T> datas){ |
| | | if (datas != null) { |
| | | for (int i = 0; i < datas.size(); i++) { |
| | | mDatas.add(i, datas.get(i)); |
| | | } |
| | | notifyDataSetChanged(); |
| | | } |
| | | } |
| | | |
| | | |
| | | public void add(T datas){ |
| | | if (datas != null) { |
| | | mDatas.add(datas); |
| | | notifyDataSetChanged(); |
| | | } |
| | | } |
| | | |
| | | public void add(int position, T datas){ |
| | | if (position < 0 || position >= mDatas.size() || datas == null) { |
| | | return; |
| | | } |
| | | mDatas.add(position, datas); |
| | | notifyDataSetChanged(); |
| | | } |
| | | |
| | | /** |
| | | * æ´æ°æå®ä½ç½®æ°æ® |
| | | * <p>Title: update |
| | | * <p>Description: |
| | | * @param position |
| | | * @param datas |
| | | */ |
| | | public void update(int position, T datas){ |
| | | if (position < 0 || position >= mDatas.size() || datas == null) { |
| | | return; |
| | | } |
| | | mDatas.remove(position); |
| | | mDatas.add(position, datas); |
| | | notifyDataSetChanged(); |
| | | } |
| | | |
| | | /** |
| | | * add data to adapter |
| | | * <p>Title: addAll |
| | | * <p>Description: |
| | | * @param datas |
| | | */ |
| | | public void addAll(List<T> datas){ |
| | | if (datas != null) { |
| | | mDatas.addAll(datas); |
| | | notifyDataSetChanged(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * refresh data to adapter, history data will be remove |
| | | * <p>Title: refreshData |
| | | * <p>Description: |
| | | * @param datas |
| | | */ |
| | | public void refreshData(List<T> datas){ |
| | | if (datas != null) { |
| | | mDatas.clear(); |
| | | mDatas.addAll(datas); |
| | | notifyDataSetChanged(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * clear all data |
| | | * <p>Title: clear |
| | | * <p>Description: |
| | | */ |
| | | public void clear(){ |
| | | mDatas.clear(); |
| | | notifyDataSetChanged(); |
| | | } |
| | | |
| | | /** |
| | | * å 餿å®ä½ç½®æ°æ® |
| | | * <p>Title: delete |
| | | * <p>Description: |
| | | * @param position |
| | | */ |
| | | public void delete(int position){ |
| | | if (position < 0 || position >= mDatas.size()) { |
| | | return; |
| | | } |
| | | mDatas.remove(position); |
| | | notifyDataSetChanged(); |
| | | } |
| | | |
| | | @Override |
| | | public int getCount() { |
| | | // TODO Auto-generated method stub |
| | | return mDatas.size(); |
| | | } |
| | | |
| | | @Override |
| | | public Object getItem(int position) { |
| | | // TODO Auto-generated method stub |
| | | if (mDatas == null || mDatas.size() <= 0 || position < 0 || position >= mDatas.size()) { |
| | | return null; |
| | | } |
| | | return mDatas.get(position); |
| | | } |
| | | |
| | | @Override |
| | | public long getItemId(int position) { |
| | | // TODO Auto-generated method stub |
| | | return position; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.adapter; |
| | | |
| | | import android.content.Context; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.widget.ImageView; |
| | | import android.widget.TextView; |
| | | |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.device.BeaconType; |
| | | import com.blakequ.bluetooth_manager_lib.device.BeaconUtils; |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | import com.blakequ.bluetooth_manager_lib.device.adrecord.AdRecord; |
| | | import com.blakequ.bluetooth_manager_lib.device.ibeacon.IBeaconDevice; |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.util.Constants; |
| | | |
| | | |
| | | public class DeviceListAdapter extends BaseArrayListAdapter<BluetoothLeDevice>{ |
| | | |
| | | private int scrodKey; |
| | | public DeviceListAdapter(Context context) { |
| | | super(context); |
| | | } |
| | | |
| | | public void updateConfig(int scrodKey){ |
| | | this.scrodKey = scrodKey; |
| | | } |
| | | |
| | | @Override |
| | | public View getView(int position, View view, ViewGroup parent) { |
| | | final ViewHolder viewHolder; |
| | | // General ListView optimization code. |
| | | if (view == null) { |
| | | view = mInflater.inflate(R.layout.dialog_layout_bluetooth_item, null); |
| | | viewHolder = new ViewHolder(); |
| | | viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address); |
| | | viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name); |
| | | viewHolder.deviceIcon = (ImageView) view.findViewById(R.id.device_icon); |
| | | |
| | | view.setTag(viewHolder); |
| | | } else { |
| | | viewHolder = (ViewHolder) view.getTag(); |
| | | } |
| | | |
| | | final BluetoothLeDevice device = (BluetoothLeDevice) getItem(position); |
| | | final String deviceName = device.getName(); |
| | | final double rssi = device.getRssi(); |
| | | |
| | | if (deviceName != null && deviceName.length() > 0) { |
| | | viewHolder.deviceName.setText(deviceName); |
| | | } else { |
| | | viewHolder.deviceName.setText(R.string.unknown_device); |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | return view; |
| | | } |
| | | |
| | | public double calculateAccuracy(final int txPower, final double rssi) { |
| | | if (rssi == 0) { |
| | | return 0; // if we cannot determine accuracy, return -1. |
| | | } |
| | | |
| | | final double ratio = rssi * 1.0 / txPower; |
| | | if (ratio < 1.0) { |
| | | return Math.pow(ratio, 10); |
| | | } else { |
| | | return (0.89976) * Math.pow(ratio, 7.7095) + 0.111; |
| | | } |
| | | } |
| | | |
| | | static class ViewHolder { |
| | | TextView deviceName; |
| | | TextView deviceAddress; |
| | | ImageView deviceIcon; |
| | | |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.adapter; |
| | | |
| | | import android.content.Context; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.widget.ImageView; |
| | | import android.widget.TextView; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.device.BeaconType; |
| | | import com.blakequ.bluetooth_manager_lib.device.BeaconUtils; |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | import com.blakequ.bluetooth_manager_lib.device.adrecord.AdRecord; |
| | | import com.blakequ.bluetooth_manager_lib.device.ibeacon.IBeaconDevice; |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.util.Constants; |
| | | |
| | | |
| | | public class DeviceListAdapter2 extends BaseArrayListAdapter<BluetoothLeDevice>{ |
| | | |
| | | private int scrodKey; |
| | | public DeviceListAdapter2(Context context) { |
| | | super(context); |
| | | } |
| | | |
| | | public void updateConfig(int scrodKey){ |
| | | this.scrodKey = scrodKey; |
| | | } |
| | | |
| | | @Override |
| | | public View getView(int position, View view, ViewGroup parent) { |
| | | final ViewHolder viewHolder; |
| | | // General ListView optimization code. |
| | | if (view == null) { |
| | | view = mInflater.inflate(R.layout.list_item_device, null); |
| | | viewHolder = new ViewHolder(); |
| | | viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address); |
| | | viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name); |
| | | viewHolder.deviceRssi = (TextView) view.findViewById(R.id.device_rssi); |
| | | viewHolder.deviceIcon = (ImageView) view.findViewById(R.id.device_icon); |
| | | viewHolder.deviceLastUpdated = (TextView) view.findViewById(R.id.device_last_update); |
| | | viewHolder.ibeaconMajor = (TextView) view.findViewById(R.id.ibeacon_major); |
| | | viewHolder.ibeaconMinor = (TextView) view.findViewById(R.id.ibeacon_minor); |
| | | viewHolder.ibeaconDistance = (TextView) view.findViewById(R.id.ibeacon_distance); |
| | | viewHolder.ibeaconUUID = (TextView) view.findViewById(R.id.ibeacon_uuid); |
| | | viewHolder.ibeaconTxPower = (TextView) view.findViewById(R.id.ibeacon_tx_power); |
| | | viewHolder.ibeaconSection = view.findViewById(R.id.ibeacon_section); |
| | | viewHolder.ibeaconDistanceDescriptor = (TextView) view.findViewById(R.id.ibeacon_distance_descriptor); |
| | | viewHolder.deviceRecord = (TextView) view.findViewById(R.id.ibeacon_scan_record); |
| | | view.setTag(viewHolder); |
| | | } else { |
| | | viewHolder = (ViewHolder) view.getTag(); |
| | | } |
| | | |
| | | final BluetoothLeDevice device = (BluetoothLeDevice) getItem(position); |
| | | final String deviceName = device.getName(); |
| | | final double rssi = device.getRssi(); |
| | | |
| | | if (deviceName != null && deviceName.length() > 0) { |
| | | viewHolder.deviceName.setText(deviceName); |
| | | } else { |
| | | viewHolder.deviceName.setText(R.string.unknown_device); |
| | | } |
| | | |
| | | if (BeaconUtils.getBeaconType(device) == BeaconType.IBEACON) { |
| | | final IBeaconDevice iBeacon = new IBeaconDevice(device); |
| | | final String accuracy = Constants.DOUBLE_TWO_DIGIT_ACCURACY.format(iBeacon.getAccuracy()); |
| | | |
| | | viewHolder.deviceIcon.setImageResource(R.mipmap.ic_device_ibeacon); |
| | | viewHolder.ibeaconSection.setVisibility(View.VISIBLE); |
| | | viewHolder.ibeaconMajor.setText(String.valueOf(iBeacon.getMajor())); |
| | | viewHolder.ibeaconMinor.setText(String.valueOf(iBeacon.getMinor())); |
| | | viewHolder.ibeaconTxPower.setText(String.valueOf(iBeacon.getCalibratedTxPower())); |
| | | viewHolder.ibeaconUUID.setText(iBeacon.getUUID()); |
| | | viewHolder.ibeaconDistance.setText( |
| | | mContext.getString(R.string.formatter_meters, accuracy)); |
| | | viewHolder.ibeaconDistanceDescriptor.setText(iBeacon.getDistanceDescriptor().toString()); |
| | | } else { |
| | | viewHolder.deviceIcon.setImageResource(R.mipmap.ic_bluetooth); |
| | | viewHolder.ibeaconSection.setVisibility(View.GONE); |
| | | } |
| | | |
| | | final String rssiString = |
| | | mContext.getString(R.string.formatter_db, String.valueOf(rssi)); |
| | | final String runningAverageRssiString = |
| | | mContext.getString(R.string.formatter_db, String.valueOf(device.getRunningAverageRssi())); |
| | | |
| | | viewHolder.deviceLastUpdated.setText( |
| | | android.text.format.DateFormat.format( |
| | | Constants.TIME_FORMAT, new java.util.Date(device.getTimestamp()))); |
| | | viewHolder.deviceAddress.setText(device.getAddress()); |
| | | viewHolder.deviceRssi.setText(rssiString + " / " + runningAverageRssiString); |
| | | |
| | | //add for test |
| | | if (scrodKey != -1){ |
| | | AdRecord record = device.getAdRecordStore().getRecord(scrodKey); |
| | | if (record != null) { |
| | | viewHolder.deviceRecord.setVisibility(View.VISIBLE); |
| | | viewHolder.deviceRecord.setText(record.getHumanReadableType()+"ï¼"+ByteUtils.byteArrayToHexString(record.getData())); |
| | | }else { |
| | | viewHolder.deviceRecord.setVisibility(View.GONE); |
| | | } |
| | | }else { |
| | | viewHolder.deviceRecord.setVisibility(View.GONE); |
| | | } |
| | | return view; |
| | | } |
| | | |
| | | public double calculateAccuracy(final int txPower, final double rssi) { |
| | | if (rssi == 0) { |
| | | return 0; // if we cannot determine accuracy, return -1. |
| | | } |
| | | |
| | | final double ratio = rssi * 1.0 / txPower; |
| | | if (ratio < 1.0) { |
| | | return Math.pow(ratio, 10); |
| | | } else { |
| | | return (0.89976) * Math.pow(ratio, 7.7095) + 0.111; |
| | | } |
| | | } |
| | | |
| | | static class ViewHolder { |
| | | TextView deviceName; |
| | | TextView deviceAddress; |
| | | TextView deviceRssi; |
| | | TextView ibeaconUUID; |
| | | TextView ibeaconMajor; |
| | | TextView ibeaconMinor; |
| | | TextView ibeaconTxPower; |
| | | TextView ibeaconDistance; |
| | | TextView ibeaconDistanceDescriptor; |
| | | TextView deviceLastUpdated; |
| | | View ibeaconSection; |
| | | ImageView deviceIcon; |
| | | TextView deviceRecord; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.adapter; |
| | | |
| | | |
| | | import androidx.annotation.NonNull; |
| | | |
| | | |
| | | import com.chad.library.adapter.base.BaseQuickAdapter; |
| | | import com.chad.library.adapter.base.viewholder.BaseViewHolder; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.model.HomeItem; |
| | | |
| | | import java.util.List; |
| | | |
| | | |
| | | public class HomeAdapter extends BaseQuickAdapter<HomeItem, BaseViewHolder> { |
| | | public HomeAdapter(int layoutResId, List data) { |
| | | super(layoutResId, data); |
| | | } |
| | | |
| | | @Override |
| | | protected void convert(@NonNull BaseViewHolder helper, HomeItem item) { |
| | | helper.setText(R.id.tweetName, item.getTitle()); |
| | | //helper.setImageResource(R.id.icon, item.getImageResource()); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.adapter; |
| | | |
| | | import android.bluetooth.BluetoothDevice; |
| | | import android.content.Context; |
| | | import android.os.ParcelUuid; |
| | | import android.util.SparseArray; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.widget.ImageView; |
| | | import android.widget.LinearLayout; |
| | | import android.widget.TextView; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanRecordCompat; |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | import com.shlb.comb.R; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | |
| | | public class HomeDeviceListAdapter extends BaseArrayListAdapter<BluetoothLeDevice> { |
| | | private setOnItemButtonClick onItemButtonClick; |
| | | |
| | | |
| | | private int scrodKey; |
| | | private int expandedPosition = -1; |
| | | |
| | | public HomeDeviceListAdapter(Context context, setOnItemButtonClick onItemButtonClick) { |
| | | super(context); |
| | | this.onItemButtonClick = onItemButtonClick; |
| | | } |
| | | |
| | | public void updateConfig(int scrodKey) { |
| | | this.scrodKey = scrodKey; |
| | | } |
| | | |
| | | @Override |
| | | public View getView(int position, View view, ViewGroup parent) { |
| | | final ViewHolder viewHolder; |
| | | // General ListView optimization code. |
| | | if (view == null) { |
| | | view = mInflater.inflate(R.layout.home_list_bluetooth_item, null); |
| | | viewHolder = new ViewHolder(); |
| | | viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address); |
| | | viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name); |
| | | viewHolder.rssi = (TextView) view.findViewById(R.id.rssi); |
| | | viewHolder.deviceIcon = (ImageView) view.findViewById(R.id.device_icon); |
| | | viewHolder.ivDown = (ImageView) view.findViewById(R.id.iv_down); |
| | | viewHolder.llMainContent = (LinearLayout) view.findViewById(R.id.ll_main_content); |
| | | viewHolder.llMoreInfo = (LinearLayout) view.findViewById(R.id.ll_more_info); |
| | | viewHolder.tvDeviceType = (TextView) view.findViewById(R.id.tv_device_type); |
| | | viewHolder.tvAdvType = (TextView) view.findViewById(R.id.tv_adv_type); |
| | | viewHolder.tvFlags = (TextView) view.findViewById(R.id.tv_flags); |
| | | viewHolder.tvLocalName = (TextView) view.findViewById(R.id.tv_local_name); |
| | | viewHolder.tvTxPower = (TextView) view.findViewById(R.id.tv_tx_power); |
| | | viewHolder.tvServiceUuids = (TextView) view.findViewById(R.id.tv_service_uuids); |
| | | viewHolder.tvServiceData = (TextView) view.findViewById(R.id.tv_service_data); |
| | | viewHolder.tvManufacturerData = (TextView) view.findViewById(R.id.tv_manufacturer_data); |
| | | |
| | | view.findViewById(R.id.conn_btn).setOnClickListener(new View.OnClickListener() { |
| | | @Override |
| | | public void onClick(View view) { |
| | | onItemButtonClick.buttonClick(view, position); |
| | | } |
| | | }); |
| | | view.setTag(viewHolder); |
| | | } else { |
| | | viewHolder = (ViewHolder) view.getTag(); |
| | | } |
| | | |
| | | final BluetoothLeDevice device = (BluetoothLeDevice) getItem(position); |
| | | final String deviceName = device.getName(); |
| | | final String deviceAddress = device.getAddress(); |
| | | final double rssi = device.getRssi(); |
| | | |
| | | |
| | | if (deviceName != null && deviceName.length() > 0) { |
| | | viewHolder.deviceName.setText(deviceName); |
| | | } else { |
| | | viewHolder.deviceName.setText(R.string.unknown_device); |
| | | } |
| | | if (deviceAddress != null && deviceAddress.length() > 0) { |
| | | viewHolder.deviceAddress.setText(deviceAddress); |
| | | } |
| | | if (Math.abs(rssi) > 0) { |
| | | viewHolder.rssi.setText("-" + rssi + "dBm"); |
| | | } |
| | | |
| | | if (expandedPosition == position) { |
| | | viewHolder.llMoreInfo.setVisibility(View.VISIBLE); |
| | | viewHolder.ivDown.setRotation(180); |
| | | populateMoreInfo(viewHolder, device); |
| | | } else { |
| | | viewHolder.llMoreInfo.setVisibility(View.GONE); |
| | | viewHolder.ivDown.setRotation(0); |
| | | } |
| | | |
| | | viewHolder.llMainContent.setOnClickListener(new View.OnClickListener() { |
| | | @Override |
| | | public void onClick(View v) { |
| | | if (expandedPosition == position) { |
| | | expandedPosition = -1; |
| | | } else { |
| | | expandedPosition = position; |
| | | } |
| | | notifyDataSetChanged(); |
| | | } |
| | | }); |
| | | |
| | | |
| | | return view; |
| | | } |
| | | |
| | | private void populateMoreInfo(ViewHolder holder, BluetoothLeDevice device) { |
| | | holder.tvDeviceType.setText("设å¤ç±»å: " + getDeviceType(device)); |
| | | |
| | | ScanRecordCompat record = ScanRecordCompat.parseFromBytes(device.getScanRecord()); |
| | | if (record != null) { |
| | | holder.tvAdvType.setText("广æç±»å: Legacy"); |
| | | holder.tvFlags.setText("æ å¿ (Flags): " + getFlags(record)); |
| | | holder.tvLocalName.setText("宿´æ¬å°åç§°: " + (record.getDeviceName() != null ? record.getDeviceName() : "æ ")); |
| | | |
| | | int txPower = record.getTxPowerLevel(); |
| | | holder.tvTxPower.setText("åå°åç: " + (txPower == Integer.MIN_VALUE ? "æ " : txPower + " dBm")); |
| | | |
| | | List<ParcelUuid> uuids = record.getServiceUuids(); |
| | | if (uuids != null && !uuids.isEmpty()) { |
| | | StringBuilder sb = new StringBuilder("æå¡ UUID: "); |
| | | for (ParcelUuid uuid : uuids) { |
| | | sb.append(uuid.toString()).append("\n"); |
| | | } |
| | | holder.tvServiceUuids.setVisibility(View.VISIBLE); |
| | | holder.tvServiceUuids.setText(sb.toString().trim()); |
| | | } else { |
| | | holder.tvServiceUuids.setVisibility(View.GONE); |
| | | } |
| | | |
| | | Map<ParcelUuid, byte[]> serviceData = record.getServiceData(); |
| | | if (serviceData != null && !serviceData.isEmpty()) { |
| | | StringBuilder sb = new StringBuilder("æå¡æ°æ®: "); |
| | | for (Map.Entry<ParcelUuid, byte[]> entry : serviceData.entrySet()) { |
| | | sb.append(entry.getKey().toString()).append(": ") |
| | | .append(ByteUtils.byteArrayToHexString(entry.getValue())).append("\n"); |
| | | } |
| | | holder.tvServiceData.setVisibility(View.VISIBLE); |
| | | holder.tvServiceData.setText(sb.toString().trim()); |
| | | } else { |
| | | holder.tvServiceData.setVisibility(View.GONE); |
| | | } |
| | | |
| | | holder.tvManufacturerData.setText("ååæ°æ®: " + getManufacturerData(record)); |
| | | } else { |
| | | holder.tvAdvType.setText("广æç±»å: æ "); |
| | | holder.tvFlags.setText("æ å¿ (Flags): æ "); |
| | | holder.tvLocalName.setText("宿´æ¬å°åç§°: æ "); |
| | | holder.tvTxPower.setText("åå°åç: æ "); |
| | | holder.tvServiceUuids.setVisibility(View.GONE); |
| | | holder.tvServiceData.setVisibility(View.GONE); |
| | | holder.tvManufacturerData.setText("ååæ°æ®: æ "); |
| | | } |
| | | } |
| | | |
| | | private String getDeviceType(BluetoothLeDevice device) { |
| | | int type = device.getDevice().getType(); |
| | | switch (type) { |
| | | case BluetoothDevice.DEVICE_TYPE_CLASSIC: |
| | | return "ä»
ç»å
¸èç (Classic)"; |
| | | case BluetoothDevice.DEVICE_TYPE_LE: |
| | | return "ä»
ä½åèèç (LE)"; |
| | | case BluetoothDevice.DEVICE_TYPE_DUAL: |
| | | return "忍¡ (Dual Mode)"; |
| | | default: |
| | | return "æªç¥"; |
| | | } |
| | | } |
| | | |
| | | private String getFlags(ScanRecordCompat record) { |
| | | int flags = record.getAdvertiseFlags(); |
| | | if (flags == -1) return "æ "; |
| | | StringBuilder sb = new StringBuilder(); |
| | | if ((flags & 0x01) != 0) sb.append("LE æéå¯åç°æ¨¡å¼, "); |
| | | if ((flags & 0x02) != 0) sb.append("LE æ®éå¯åç°æ¨¡å¼, "); |
| | | if ((flags & 0x04) != 0) sb.append("䏿¯æ BR/EDR, "); |
| | | if ((flags & 0x08) != 0) sb.append("æ§å¶å¨æ¯æ LE å BR/EDR, "); |
| | | if ((flags & 0x10) != 0) sb.append("ä¸»æºæ¯æ LE å BR/EDR, "); |
| | | if (sb.length() > 2) sb.setLength(sb.length() - 2); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | private String getManufacturerData(ScanRecordCompat record) { |
| | | SparseArray<byte[]> data = record.getManufacturerSpecificData(); |
| | | if (data == null || data.size() == 0) return "æ "; |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (int i = 0; i < data.size(); i++) { |
| | | int id = data.keyAt(i); |
| | | byte[] bytes = data.valueAt(i); |
| | | sb.append(String.format("ID: 0x%04X, æ°æ®: ", id)); |
| | | sb.append(ByteUtils.byteArrayToHexString(bytes)); |
| | | if (i < data.size() - 1) sb.append("\n"); |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | public double calculateAccuracy(final int txPower, final double rssi) { |
| | | if (rssi == 0) { |
| | | return 0; // if we cannot determine accuracy, return -1. |
| | | } |
| | | |
| | | final double ratio = rssi * 1.0 / txPower; |
| | | if (ratio < 1.0) { |
| | | return Math.pow(ratio, 10); |
| | | } else { |
| | | return (0.89976) * Math.pow(ratio, 7.7095) + 0.111; |
| | | } |
| | | } |
| | | |
| | | static class ViewHolder { |
| | | TextView deviceName; |
| | | TextView deviceAddress; |
| | | TextView rssi; |
| | | ImageView deviceIcon; |
| | | ImageView ivDown; |
| | | LinearLayout llMainContent; |
| | | LinearLayout llMoreInfo; |
| | | TextView tvDeviceType; |
| | | TextView tvAdvType; |
| | | TextView tvFlags; |
| | | TextView tvLocalName; |
| | | TextView tvTxPower; |
| | | TextView tvServiceUuids; |
| | | TextView tvServiceData; |
| | | TextView tvManufacturerData; |
| | | |
| | | } |
| | | |
| | | public interface setOnItemButtonClick { |
| | | void buttonClick(View view, int position); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.adapter; |
| | | |
| | | |
| | | import android.view.View; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | |
| | | import com.chad.library.adapter.base.BaseQuickAdapter; |
| | | import com.chad.library.adapter.base.viewholder.BaseViewHolder; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.model.HomeItem; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | |
| | | public class WriteAdapter extends BaseQuickAdapter<Map<String,Object>, BaseViewHolder> { |
| | | public WriteAdapter(int layoutResId, List data) { |
| | | super(layoutResId, data); |
| | | } |
| | | |
| | | @Override |
| | | protected void convert(@NonNull BaseViewHolder helper, Map<String,Object> item) { |
| | | helper.setText(R.id.name, item.get("name").toString()); |
| | | if("1" == item.get("flag").toString()){ |
| | | helper.getView(R.id.logo).setVisibility(View.VISIBLE); |
| | | }else { |
| | | helper.getView(R.id.logo).setVisibility(View.GONE); |
| | | } |
| | | |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.base; |
| | | |
| | | import android.app.Activity; |
| | | import android.content.Context; |
| | | import android.content.Intent; |
| | | import android.os.Bundle; |
| | | import android.view.View; |
| | | import android.view.inputmethod.InputMethodManager; |
| | | import android.widget.ImageView; |
| | | import android.widget.TextView; |
| | | |
| | | import androidx.annotation.Nullable; |
| | | import androidx.appcompat.app.AppCompatActivity; |
| | | |
| | | import com.blankj.utilcode.util.SnackbarUtils; |
| | | import com.blankj.utilcode.util.ToastUtils; |
| | | import com.qmuiteam.qmui.widget.dialog.QMUITipDialog; |
| | | import com.shlb.comb.R; |
| | | |
| | | import org.greenrobot.eventbus.EventBus; |
| | | |
| | | import java.util.List; |
| | | import java.util.Timer; |
| | | import java.util.TimerTask; |
| | | |
| | | import pub.devrel.easypermissions.EasyPermissions; |
| | | |
| | | public abstract class BaseActivity extends AppCompatActivity implements View.OnClickListener, EasyPermissions.PermissionCallbacks { |
| | | public String TAG = this.getClass().getSimpleName(); |
| | | //title左边æé® |
| | | protected ImageView iv_left; |
| | | //titleæ é¢ |
| | | protected TextView tv_center; |
| | | //titleå³è¾¹æé® |
| | | protected ImageView iv_right; |
| | | |
| | | |
| | | @Override |
| | | protected void onCreate(@Nullable Bundle savedInstanceState) { |
| | | super.onCreate(savedInstanceState); |
| | | contentView(); |
| | | EventBus.getDefault().register(this); |
| | | init(); |
| | | } |
| | | |
| | | private void init() { |
| | | initView(); |
| | | initData(); |
| | | initEvent(); |
| | | } |
| | | |
| | | protected void initHead() { |
| | | iv_left = findViewById(R.id.iv_left); |
| | | tv_center = findViewById(R.id.tv_center); |
| | | iv_right = findViewById(R.id.iv_right); |
| | | initHeadEvent(); |
| | | } |
| | | |
| | | |
| | | private void initHeadEvent() { |
| | | iv_left.setOnClickListener(this); |
| | | iv_right.setOnClickListener(this); |
| | | } |
| | | |
| | | protected abstract void contentView(); |
| | | |
| | | protected abstract void initView(); |
| | | |
| | | protected abstract void initData(); |
| | | |
| | | protected abstract void initEvent(); |
| | | |
| | | |
| | | @Override |
| | | public void setTitle(CharSequence title) { |
| | | tv_center.setText(title); |
| | | } |
| | | |
| | | @Override |
| | | public void onClick(View v) { |
| | | int id = v.getId(); |
| | | if (id == R.id.iv_left) { |
| | | finish(); |
| | | } else if (id == R.id.iv_right) { |
| | | } |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void onDestroy() { |
| | | super.onDestroy(); |
| | | EventBus.getDefault().unregister(this); |
| | | } |
| | | |
| | | QMUITipDialog tipDialog; |
| | | |
| | | //å¼¹åºæç¤º |
| | | public void showTips(String tips) { |
| | | tipDialog = new QMUITipDialog.CustomBuilder(this) |
| | | .setContent(R.layout.tipdialog_custom) |
| | | .create(); |
| | | tipDialog.setCanceledOnTouchOutside(true); |
| | | ((TextView) tipDialog.findViewById(R.id.tv_tip)).setText(tips); |
| | | tipDialog.show(); |
| | | |
| | | new Timer().schedule(new TimerTask() { |
| | | @Override |
| | | public void run() { |
| | | if (tipDialog != null)tipDialog.dismiss(); |
| | | } |
| | | }, 1500); |
| | | // LogUtils.e("TAG","åå¤"); |
| | | // handler.postDelayed(new Runnable() { |
| | | // @Override |
| | | // public void run() { |
| | | // LogUtils.e("TAG","è¿å
¥"); |
| | | // if (tipDialog != null) { |
| | | // LogUtils.e("TAG","å
³é"); |
| | | // tipDialog.dismiss(); |
| | | // } |
| | | // } |
| | | // }, 1500); |
| | | } |
| | | QMUITipDialog loading; |
| | | public void showLoading(){ |
| | | showLoading("æ£å¨å è½½"); |
| | | } |
| | | |
| | | public void showLoading(String msg){ |
| | | if(loading != null && loading.isShowing()){ |
| | | loading.dismiss(); |
| | | } |
| | | loading = new QMUITipDialog.Builder(this) |
| | | .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) |
| | | .setTipWord(msg) |
| | | .create(); |
| | | loading.show(); |
| | | } |
| | | |
| | | public void hiddeLoading(){ |
| | | if (loading != null) { |
| | | loading.dismiss(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 强å¶éèé®ç |
| | | */ |
| | | protected void hideKeyborad() { |
| | | InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); |
| | | imm.hideSoftInputFromWindow(iv_left.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); |
| | | } |
| | | |
| | | /** |
| | | * æå¼è³æå®çActivity |
| | | * |
| | | * @param pClass |
| | | */ |
| | | public void openActivity(Class<?> pClass) { |
| | | openActivity(pClass, null); |
| | | } |
| | | |
| | | /** |
| | | * æå¼è³æå®çActivity |
| | | * |
| | | * @param pClass |
| | | * @param pBundle ä¼ å¼ |
| | | */ |
| | | public void openActivity(Class<?> pClass, Bundle pBundle) { |
| | | Intent intent = new Intent(this, pClass); |
| | | if (pBundle != null) { |
| | | intent.putExtras(pBundle); |
| | | } |
| | | startActivity(intent); |
| | | } |
| | | |
| | | /** |
| | | * æå¼activity |
| | | * |
| | | * @param pAction activityå¨ä½ |
| | | */ |
| | | public void openActivity(String pAction) { |
| | | openActivity(pAction, null); |
| | | } |
| | | |
| | | /** |
| | | * æå¼activity |
| | | * |
| | | * @param pAction activityå¨ä½ |
| | | * @param pBundle æ°æ® |
| | | */ |
| | | public void openActivity(String pAction, Bundle pBundle) { |
| | | Intent intent = new Intent(pAction); |
| | | if (pBundle != null) { |
| | | intent.putExtras(pBundle); |
| | | } |
| | | startActivity(intent); |
| | | } |
| | | |
| | | /** |
| | | * è¿åactivity ï¼ç»å®æ°æ® |
| | | * |
| | | * @param pClass |
| | | */ |
| | | public void returnActivity(Class<?> pClass) { |
| | | returnActivity(pClass, null); |
| | | } |
| | | |
| | | /** |
| | | * è¿åactivity ï¼ç»å®æ°æ® |
| | | * |
| | | * @param pClass |
| | | * @param pBundle |
| | | */ |
| | | public void returnActivity(Class<?> pClass, Bundle pBundle) { |
| | | Intent intent = new Intent(this, pClass); |
| | | if (pBundle != null) { |
| | | intent.putExtras(pBundle); |
| | | } |
| | | intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); |
| | | startActivity(intent); |
| | | } |
| | | |
| | | /** |
| | | * è¿åactivity |
| | | * |
| | | * @param pAction activityå¨ä½ |
| | | */ |
| | | public void returnActivity(String pAction) { |
| | | returnActivity(pAction, null); |
| | | } |
| | | |
| | | /** |
| | | * è¿åactivity |
| | | * |
| | | * @param pAction activityå¨ä½ |
| | | * @param pBundle æ°æ® |
| | | */ |
| | | public void returnActivity(String pAction, Bundle pBundle) { |
| | | Intent intent = new Intent(pAction); |
| | | if (pBundle != null) { |
| | | intent.putExtras(pBundle); |
| | | } |
| | | intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); |
| | | startActivity(intent); |
| | | } |
| | | |
| | | public void Toast(String text) { |
| | | ToastUtils.showShort(text); |
| | | // SnackbarUtils.with(tv_center).setDuration(SnackbarUtils.LENGTH_LONG).setMessage(text).show(); |
| | | } |
| | | @Override |
| | | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { |
| | | super.onRequestPermissionsResult(requestCode, permissions, grantResults); |
| | | |
| | | // Forward results to EasyPermissions |
| | | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | public void onPermissionsGranted(int requestCode, List<String> list) { |
| | | // Some permissions have been granted |
| | | Toast("æéæ£æµéè¿ï¼"); |
| | | } |
| | | |
| | | @Override |
| | | public void onPermissionsDenied(int requestCode, List<String> list) { |
| | | // Some permissions have been denied |
| | | Toast("æªæå¼æéæéï¼ç¨åºæ æ³æ£å¸¸è¿è¡"+list.toString()); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.event; |
| | | |
| | | /** |
| | | */ |
| | | public class UpdateEvent { |
| | | |
| | | private Type type; |
| | | private Object obj; |
| | | private String msg; |
| | | private int arg1; |
| | | |
| | | public UpdateEvent(Type type) { |
| | | this.type = type; |
| | | } |
| | | |
| | | public UpdateEvent(Type type, Object obj) { |
| | | this.obj = obj; |
| | | this.type = type; |
| | | } |
| | | |
| | | public UpdateEvent(Type type, Object obj, String msg) { |
| | | this.obj = obj; |
| | | this.type = type; |
| | | this.msg = msg; |
| | | } |
| | | |
| | | public UpdateEvent(Type type, int arg1) { |
| | | this.type = type; |
| | | this.arg1 = arg1; |
| | | } |
| | | |
| | | public int getArg1() { |
| | | return arg1; |
| | | } |
| | | |
| | | public void setArg1(int arg1) { |
| | | this.arg1 = arg1; |
| | | } |
| | | |
| | | public String getMsg() { |
| | | return msg; |
| | | } |
| | | |
| | | public void setMsg(String msg) { |
| | | this.msg = msg; |
| | | } |
| | | |
| | | public Type getType() { |
| | | return type; |
| | | } |
| | | |
| | | public void setType(Type type) { |
| | | this.type = type; |
| | | } |
| | | |
| | | public Object getObj() { |
| | | return obj; |
| | | } |
| | | |
| | | public void setObj(Object obj) { |
| | | this.obj = obj; |
| | | } |
| | | |
| | | public static enum Type{ |
| | | SCAN_UPDATE, |
| | | BLE_DATA, |
| | | POP_SHOW, |
| | | TAB_SWITCH, |
| | | CONFIG_CHANGE, |
| | | |
| | | CONN_STATU, |
| | | CONN_SERVICE, |
| | | ONLINE, |
| | | OFFLINE, |
| | | DEVICE_INFO |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.fragment; |
| | | |
| | | import android.Manifest; |
| | | import android.content.Intent; |
| | | import android.os.Bundle; |
| | | import android.text.TextUtils; |
| | | import android.view.LayoutInflater; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.widget.AdapterView; |
| | | import android.widget.ListView; |
| | | import android.widget.Toast; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | import androidx.annotation.Nullable; |
| | | import androidx.fragment.app.Fragment; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | import com.blakequ.bluetooth_manager_lib.scan.BluetoothScanManager; |
| | | import com.blakequ.bluetooth_manager_lib.scan.ScanOverListener; |
| | | import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanCallbackCompat; |
| | | import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanResultCompat; |
| | | import com.blakequ.bluetooth_manager_lib.util.BluetoothUtils; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | import com.qmuiteam.qmui.widget.QMUIEmptyView; |
| | | import com.qmuiteam.qmui.widget.dialog.QMUIDialog; |
| | | import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction; |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.activity.ConnActivity; |
| | | import com.shlb.comb.activity.TestActivity; |
| | | import com.shlb.comb.activity.HomeActivity; |
| | | import com.shlb.comb.adapter.HomeDeviceListAdapter; |
| | | import com.shlb.comb.base.BaseActivity; |
| | | import com.shlb.comb.event.UpdateEvent; |
| | | import com.shlb.comb.model.BluetoothLeDeviceStore; |
| | | import com.shlb.comb.util.Singletion; |
| | | |
| | | import org.greenrobot.eventbus.EventBus; |
| | | import org.greenrobot.eventbus.Subscribe; |
| | | import org.greenrobot.eventbus.ThreadMode; |
| | | |
| | | import java.util.List; |
| | | |
| | | import pub.devrel.easypermissions.AfterPermissionGranted; |
| | | import pub.devrel.easypermissions.EasyPermissions; |
| | | |
| | | public class DeviceListFragment extends Fragment implements AdapterView.OnItemClickListener, EasyPermissions.PermissionCallbacks { |
| | | private static final int BLUETOOTH_NEDD_PERMISSIONS = 1011; |
| | | |
| | | public static final int MODE_RETURN_SETTINGS = 0; |
| | | public static final int MODE_GO_TO_TEST = 1; |
| | | private static int navigationMode = MODE_RETURN_SETTINGS; |
| | | private boolean isSwitchingDevice = false; |
| | | |
| | | public static void setNavigationMode(int mode) { |
| | | navigationMode = mode; |
| | | } |
| | | |
| | | private QMUIEmptyView mEmptyView; |
| | | private ListView homeList; |
| | | private BluetoothScanManager scanManager; |
| | | private BluetoothLeDeviceStore mDeviceStore; |
| | | private BluetoothUtils mBluetoothUtils; |
| | | private HomeDeviceListAdapter mLeDeviceListAdapter; |
| | | private com.qmuiteam.qmui.widget.dialog.QMUITipDialog mLoadingDialog; |
| | | |
| | | private void showLoading(String message) { |
| | | if (mLoadingDialog != null && mLoadingDialog.isShowing()) { |
| | | mLoadingDialog.dismiss(); |
| | | } |
| | | mLoadingDialog = new com.qmuiteam.qmui.widget.dialog.QMUITipDialog.Builder(getContext()) |
| | | .setIconType(com.qmuiteam.qmui.widget.dialog.QMUITipDialog.Builder.ICON_TYPE_LOADING) |
| | | .setTipWord(message) |
| | | .create(); |
| | | mLoadingDialog.show(); |
| | | } |
| | | |
| | | private void dismissLoading() { |
| | | if (mLoadingDialog != null && mLoadingDialog.isShowing()) { |
| | | mLoadingDialog.dismiss(); |
| | | mLoadingDialog = null; |
| | | } |
| | | } |
| | | |
| | | @Nullable |
| | | @Override |
| | | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { |
| | | View view = inflater.inflate(R.layout.fragment_device_list, container, false); |
| | | mEmptyView = view.findViewById(R.id.emptyView); |
| | | homeList = view.findViewById(R.id.homeList); |
| | | return view; |
| | | } |
| | | |
| | | @Override |
| | | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { |
| | | super.onViewCreated(view, savedInstanceState); |
| | | EventBus.getDefault().register(this); |
| | | initView(); |
| | | initData(); |
| | | } |
| | | |
| | | private void initView() { |
| | | mEmptyView.show(true, "æç´¢èç", "", "éæ°æ«æ", new View.OnClickListener() { |
| | | @Override |
| | | public void onClick(View view) { |
| | | if (scanManager != null && !scanManager.isScanning()) { |
| | | startScan(); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | |
| | | private void initData() { |
| | | if (getActivity() instanceof HomeActivity) { |
| | | HomeActivity activity = (HomeActivity) getActivity(); |
| | | mBluetoothUtils = activity.getBluetoothUtils(); |
| | | mDeviceStore = activity.getDeviceStore(); |
| | | scanManager = activity.getScanManager(); |
| | | } |
| | | |
| | | mLeDeviceListAdapter = new HomeDeviceListAdapter(getContext(), new HomeDeviceListAdapter.setOnItemButtonClick() { |
| | | @Override |
| | | public void buttonClick(View view, int position) { |
| | | final BluetoothLeDevice device = (BluetoothLeDevice) mLeDeviceListAdapter.getItem(position); |
| | | if (device == null) { |
| | | Toast.makeText(getContext(), "è¿æ¥è®¾å¤ä¸è½ä¸ºç©ºï¼", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | Singletion.getInstance().mDevice = device; |
| | | if (device.getAddress() == null) { |
| | | Toast.makeText(getContext(), "è¿æ¥è®¾å¤å°åä¸è½ä¸ºç©ºï¼", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | |
| | | if (com.shlb.comb.manager.BleGlobalManager.getInstance().isConnected()) { |
| | | new QMUIDialog.MessageDialogBuilder(getContext()) |
| | | .setTitle("æç¤º") |
| | | .setMessage("å½åå·²è¿æ¥è®¾å¤ï¼æ¯å¦æå¼å¹¶è¿æ¥æ°è®¾å¤ï¼") |
| | | .addAction("åæ¶", (dialog, index) -> dialog.dismiss()) |
| | | .addAction("ç¡®å®", (dialog, index) -> { |
| | | dialog.dismiss(); |
| | | isSwitchingDevice = true; |
| | | com.shlb.comb.manager.BleGlobalManager.getInstance().disconnect(); |
| | | connectToDevice(device); |
| | | }) |
| | | .create(com.qmuiteam.qmui.R.style.QMUI_Dialog).show(); |
| | | } else { |
| | | connectToDevice(device); |
| | | } |
| | | } |
| | | }); |
| | | homeList.setOnItemClickListener(this); |
| | | homeList.setAdapter(mLeDeviceListAdapter); |
| | | scanManager.setScanOverListener(new ScanOverListener() { |
| | | @Override |
| | | public void onScanOver() { |
| | | } |
| | | }); |
| | | |
| | | scanManager.setScanCallbackCompat(new ScanCallbackCompat() { |
| | | @Override |
| | | public void onScanFailed(final int errorCode) { |
| | | super.onScanFailed(errorCode); |
| | | Toast.makeText(getContext(), "æ«æå¤±è´¥ï¼" + errorCode, Toast.LENGTH_SHORT).show(); |
| | | } |
| | | |
| | | @Override |
| | | public void onScanResult(int callbackType, ScanResultCompat result) { |
| | | super.onScanResult(callbackType, result); |
| | | String deviceName = result.getScanRecord().getDeviceName(); |
| | | LogUtils.i("scan device ", result.getLeDevice().getAddress() + " " + deviceName); |
| | | if (deviceName != null) deviceName = deviceName.toLowerCase(); |
| | | |
| | | if (!TextUtils.isEmpty(deviceName)) { |
| | | mDeviceStore.addDevice(result.getLeDevice()); |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.SCAN_UPDATE)); |
| | | } |
| | | } |
| | | }); |
| | | startScan(); |
| | | } |
| | | |
| | | private void connectToDevice(BluetoothLeDevice device) { |
| | | if (navigationMode == MODE_RETURN_SETTINGS) { |
| | | com.shlb.comb.manager.BleGlobalManager.getInstance().connect(getContext(), device); |
| | | showLoading("æ£å¨è¿æ¥ " + device.getName() + "..."); |
| | | } else { |
| | | Intent intent = new Intent(getContext(), TestActivity.class); |
| | | startActivity(intent); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { |
| | | } |
| | | |
| | | public void startScan() { |
| | | if (checkPermission()) { |
| | | if (checkIsBleState()) { |
| | | if (mDeviceStore != null && scanManager != null && scanManager.isScanning()) { |
| | | if (getActivity() instanceof BaseActivity) { |
| | | ((BaseActivity) getActivity()).Toast("æ£å¨æ«æä¸..."); |
| | | } |
| | | return; |
| | | } |
| | | mDeviceStore.clear(); |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.SCAN_UPDATE)); |
| | | ((BaseActivity) getActivity()).Toast("å¼å§æ«æ..."); |
| | | scanManager.startScanNow(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | public void startScanOnce() { |
| | | if (checkPermission()) { |
| | | if (checkIsBleState()) { |
| | | if (mDeviceStore != null && scanManager != null && scanManager.isScanning()) return; |
| | | mDeviceStore.clear(); |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.SCAN_UPDATE)); |
| | | scanManager.startScanOnce(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Subscribe(threadMode = ThreadMode.MAIN) |
| | | public void onEventRefresh(UpdateEvent event) { |
| | | if (event.getType() == UpdateEvent.Type.CONN_STATU) { |
| | | int status = 0; |
| | | if (event.getObj() instanceof Integer) { |
| | | status = (int) event.getObj(); |
| | | } |
| | | if (status == android.bluetooth.BluetoothProfile.STATE_CONNECTED) { |
| | | dismissLoading(); |
| | | Toast.makeText(getContext(), "è¿æ¥æå", Toast.LENGTH_SHORT).show(); |
| | | if (getFragmentManager() != null) { |
| | | getFragmentManager().popBackStack(); |
| | | } |
| | | } else if (status == android.bluetooth.BluetoothProfile.STATE_DISCONNECTED) { |
| | | if (isSwitchingDevice) { |
| | | isSwitchingDevice = false; |
| | | return; |
| | | } |
| | | dismissLoading(); |
| | | Toast.makeText(getContext(), "è¿æ¥æå¼", Toast.LENGTH_SHORT).show(); |
| | | } |
| | | } |
| | | if (event.getType() == UpdateEvent.Type.SCAN_UPDATE) { |
| | | if (mLeDeviceListAdapter != null && mDeviceStore != null && mDeviceStore.getDeviceList() != null) { |
| | | mLeDeviceListAdapter.refreshData(mDeviceStore.getDeviceList()); |
| | | } |
| | | if (mDeviceStore != null && mDeviceStore.getDeviceList() != null && mDeviceStore.getDeviceList().size() > 0) { |
| | | mEmptyView.hide(); |
| | | homeList.setVisibility(View.VISIBLE); |
| | | } else { |
| | | homeList.setVisibility(View.GONE); |
| | | mEmptyView.show(true, "æç´¢èç", "", "éæ°æ«æ", new View.OnClickListener() { |
| | | @Override |
| | | public void onClick(View view) { |
| | | if (scanManager != null && !scanManager.isScanning()) { |
| | | startScan(); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |
| | | super.onRequestPermissionsResult(requestCode, permissions, grantResults); |
| | | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); |
| | | } |
| | | |
| | | @Override |
| | | public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) { |
| | | startScan(); |
| | | } |
| | | |
| | | @Override |
| | | public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) { |
| | | Toast.makeText(getContext(), "æªè·å¾å¿
è¦æéï¼æ æ³æ«æ", Toast.LENGTH_SHORT).show(); |
| | | } |
| | | |
| | | private boolean checkIsBleState() { |
| | | if (!mBluetoothUtils.isBluetoothLeSupported(getContext())) { |
| | | Toast.makeText(getContext(), "å½åææºä¸æ¯æBLEä½åèèçï¼æ æ³ä½¿ç¨ï¼", Toast.LENGTH_SHORT).show(); |
| | | } else if (!mBluetoothUtils.isBluetoothIsEnable()) { |
| | | new QMUIDialog.MessageDialogBuilder(getContext()) |
| | | .setTitle("æç¤º") |
| | | .setMessage("èçæªæå¼ï¼ç¡®å®æå¼èçï¼") |
| | | .addAction("åæ¶", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | dialog.dismiss(); |
| | | } |
| | | }) |
| | | .addAction("ç¡®å®", new QMUIDialogAction.ActionListener() { |
| | | @Override |
| | | public void onClick(QMUIDialog dialog, int index) { |
| | | mBluetoothUtils.askUserToEnableBluetoothIfNeeded(getActivity()); |
| | | dialog.dismiss(); |
| | | } |
| | | }) |
| | | .create(com.qmuiteam.qmui.R.style.QMUI_Dialog).show(); |
| | | } else { |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | @AfterPermissionGranted(BLUETOOTH_NEDD_PERMISSIONS) |
| | | private boolean checkPermission() { |
| | | String[] permissionList; |
| | | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { |
| | | permissionList = new String[]{ |
| | | Manifest.permission.BLUETOOTH_SCAN, |
| | | Manifest.permission.BLUETOOTH_CONNECT, |
| | | Manifest.permission.ACCESS_FINE_LOCATION |
| | | }; |
| | | } else { |
| | | permissionList = new String[]{ |
| | | Manifest.permission.BLUETOOTH, |
| | | Manifest.permission.BLUETOOTH_ADMIN, |
| | | Manifest.permission.ACCESS_COARSE_LOCATION, |
| | | Manifest.permission.ACCESS_FINE_LOCATION, |
| | | Manifest.permission.READ_EXTERNAL_STORAGE, |
| | | Manifest.permission.WRITE_EXTERNAL_STORAGE |
| | | }; |
| | | } |
| | | |
| | | if (EasyPermissions.hasPermissions(getContext(), permissionList)) { |
| | | return true; |
| | | } else { |
| | | EasyPermissions.requestPermissions(this, "ç¨åºè¿è¡éè¦æé!", |
| | | BLUETOOTH_NEDD_PERMISSIONS, permissionList); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onPause() { |
| | | super.onPause(); |
| | | if (scanManager != null && scanManager.isScanning()) { |
| | | scanManager.stopCycleScan(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onDestroyView() { |
| | | super.onDestroyView(); |
| | | dismissLoading(); |
| | | EventBus.getDefault().unregister(this); |
| | | if (scanManager != null) { |
| | | scanManager.setScanCallbackCompat(null); |
| | | scanManager.setScanOverListener(null); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.fragment; |
| | | |
| | | import android.os.Bundle; |
| | | import android.view.LayoutInflater; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | import android.widget.ArrayAdapter; |
| | | import android.widget.EditText; |
| | | import android.widget.Spinner; |
| | | import android.widget.TextView; |
| | | import android.widget.Toast; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | import androidx.annotation.Nullable; |
| | | import androidx.fragment.app.Fragment; |
| | | import androidx.recyclerview.widget.GridLayoutManager; |
| | | import androidx.recyclerview.widget.RecyclerView; |
| | | |
| | | import com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton; |
| | | import com.shlb.comb.manager.BleGlobalManager; |
| | | import com.shlb.comb.event.UpdateEvent; |
| | | import org.greenrobot.eventbus.EventBus; |
| | | import org.greenrobot.eventbus.Subscribe; |
| | | import org.greenrobot.eventbus.ThreadMode; |
| | | import android.bluetooth.BluetoothProfile; |
| | | import com.shlb.comb.R; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import com.shlb.comb.util.CMD; |
| | | import com.shlb.comb.util.CRCutil; |
| | | |
| | | import com.qmuiteam.qmui.widget.dialog.QMUITipDialog; |
| | | import android.text.Html; |
| | | import android.content.DialogInterface; |
| | | |
| | | public class SettingsFragment extends Fragment { |
| | | |
| | | private RecyclerView rvGrid; |
| | | private EditText etLayer; |
| | | private EditText etStation; |
| | | private Spinner spinnerBaud; |
| | | private QMUIRoundButton btnWriteAll; |
| | | private QMUIRoundButton btnReadData; |
| | | private QMUIRoundButton btnReadParam; |
| | | private QMUIRoundButton btnClearLog; |
| | | private TextView tvStatus; |
| | | private TextView tvLog; |
| | | private TextView tvLayerStatus; |
| | | private TextView tvStationStatus; |
| | | private TextView tvBaudStatus; |
| | | private android.widget.ScrollView svLog; |
| | | |
| | | private QMUITipDialog mLoadingDialog; |
| | | |
| | | private GridAdapter mAdapter; |
| | | private List<BoxStatus> boxStatusList = new ArrayList<>(); |
| | | private StringBuilder logBuilder = new StringBuilder(); |
| | | |
| | | private java.util.Queue<String> cmdQueue = new java.util.LinkedList<>(); |
| | | private String currentExecutingCmd = ""; // å½åæ£å¨æ§è¡çæä»¤ï¼ç¨äºæ ¡éªååº |
| | | |
| | | private static class BoxStatus { |
| | | int id; |
| | | boolean isOnline; |
| | | boolean hasGlass; |
| | | |
| | | public BoxStatus(int id) { |
| | | this.id = id; |
| | | this.isOnline = false; |
| | | this.hasGlass = false; |
| | | } |
| | | } |
| | | |
| | | private Runnable autoReadRunnable = new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | // èçè¿æ¥æåå èªå¨è§¦åçæ§è¯¦æ
ç è¯»åæ°æ® å åæ°è®¾å®è¿éç 读ååæ° |
| | | if (BleGlobalManager.getInstance().isConnected()) { |
| | | showLoading("æ£å¨åæ¥æ°æ®..."); |
| | | |
| | | if (btnReadData != null) btnReadData.performClick(); |
| | | |
| | | new android.os.Handler().postDelayed(() -> { |
| | | if (btnReadParam != null) btnReadParam.performClick(); |
| | | |
| | | // åè®¾åæ°è¯»å触åå 1.5ç§ å
³é loadingï¼æè
å¨è§£æå®ææåæ°åå
³é |
| | | // è¿éç®åå¤çï¼å»¶æ¶å
³é |
| | | new android.os.Handler().postDelayed(() -> { |
| | | dismissLoading(); |
| | | }, 1500); |
| | | }, 1000); // é´é1ç§ï¼é¿å
æä»¤å²çª |
| | | } |
| | | } |
| | | }; |
| | | private android.os.Handler debounceHandler = new android.os.Handler(); |
| | | private static final long DEBOUNCE_DELAY_MS = 1500; // 1.5 seconds debounce |
| | | |
| | | @Override |
| | | public void onStart() { |
| | | super.onStart(); |
| | | if (!EventBus.getDefault().isRegistered(this)) { |
| | | EventBus.getDefault().register(this); |
| | | } |
| | | updateConnectionStatus(); |
| | | } |
| | | |
| | | @Override |
| | | public void onStop() { |
| | | super.onStop(); |
| | | if (EventBus.getDefault().isRegistered(this)) { |
| | | EventBus.getDefault().unregister(this); |
| | | } |
| | | } |
| | | |
| | | private void updateConnectionStatus() { |
| | | if (BleGlobalManager.getInstance().isConnected()) { |
| | | tvStatus.setText("ç¶æï¼å·²è¿æ¥"); |
| | | } else { |
| | | tvStatus.setText("ç¶æï¼æªè¿æ¥"); |
| | | } |
| | | } |
| | | |
| | | @Subscribe(threadMode = ThreadMode.MAIN) |
| | | public void onEventRefresh(UpdateEvent event) { |
| | | if (event.getType() == UpdateEvent.Type.CONN_STATU) { |
| | | // Check if obj is integer |
| | | if (event.getObj() instanceof Integer) { |
| | | int status = (int) event.getObj(); |
| | | if (status == BluetoothProfile.STATE_CONNECTED) { |
| | | tvStatus.setText("ç¶æï¼å·²è¿æ¥"); |
| | | Toast.makeText(getContext(), "èçå·²è¿æ¥", Toast.LENGTH_SHORT).show(); |
| | | |
| | | // èçè¿æ¥æåå èªå¨è§¦å |
| | | triggerAutoRead(); |
| | | } else { |
| | | tvStatus.setText("ç¶æï¼å·²æå¼"); |
| | | } |
| | | } |
| | | } else if (event.getType() == UpdateEvent.Type.DEVICE_INFO) { |
| | | // Received data |
| | | String hex = event.getMsg(); |
| | | tvStatus.setText("æ¶å°æ°æ®: " + hex); |
| | | appendLog(hex, false); // false for received (Green) |
| | | parseAndRefresh(hex); |
| | | } |
| | | } |
| | | |
| | | private void parseAndRefresh(String hex) { |
| | | if (hex == null) return; |
| | | |
| | | // æ ¹æ®CMDå8ä½å¤ææä»¤ç±»å |
| | | if (hex.length() >= 8) { |
| | | // åæ°è¯»åè¿å (é¿åº¦è³å°12ä½) |
| | | // è¯»å±æ°: A55A0308... |
| | | // 读ç«å·: A55A0304... |
| | | // 读波ç¹ç: A55A0305... |
| | | if (hex.length() >= 12 && ( |
| | | hex.startsWith(CMD.READ_FLOORS.substring(0, 8)) || |
| | | hex.startsWith(CMD.READ_STATION_NUM.substring(0, 8)) || |
| | | hex.startsWith(CMD.READ_BAUD_RATE.substring(0, 8)))) { |
| | | parseParamResponse(hex); |
| | | return; |
| | | } |
| | | |
| | | // æ°æ®è¯»åè¿å (é¿åº¦è³å°32ä½) |
| | | // è¯»æ°æ®: A55A0301... |
| | | if (hex.length() >= 32 && hex.startsWith(CMD.READ_DATA.substring(0, 8))) { |
| | | parseDataResponse(hex); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // åå
¥æä»¤çè¿å (A55A06å¼å¤´) |
| | | if (hex.startsWith("A55A06") && hex.length() >= 12) { |
| | | parseWriteResponse(hex); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | private void setLabelStatus(TextView view, String text, int colorResId) { |
| | | if (view != null && getContext() != null) { |
| | | view.setText(text); |
| | | view.setTextColor(androidx.core.content.ContextCompat.getColor(getContext(), colorResId)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è§£æåæ°è¯»åè¿å (ç«å·ãæ³¢ç¹çã屿°) |
| | | */ |
| | | private void parseParamResponse(String hex) { |
| | | try { |
| | | // å½ä»¤ç±»å: 第7-8ä½ |
| | | String cmdType = hex.substring(6, 8); |
| | | // æ°æ®å
容: 第11-12ä½ |
| | | String dataHex = hex.substring(10, 12); |
| | | int value = Integer.parseInt(dataHex, 16); |
| | | |
| | | if ("04".equals(cmdType)) { |
| | | // ç«å· |
| | | if (etStation != null) etStation.setText(String.valueOf(value)); |
| | | setLabelStatus(tvStationStatus, "(读åå¼)", R.color.base_color); |
| | | appendLog("读åç«å·: " + value); |
| | | } else if ("05".equals(cmdType)) { |
| | | // æ³¢ç¹ç |
| | | if (spinnerBaud != null && value >= 0 && value < spinnerBaud.getAdapter().getCount()) { |
| | | spinnerBaud.setSelection(value); |
| | | } |
| | | setLabelStatus(tvBaudStatus, "(读åå¼)", R.color.base_color); |
| | | appendLog("è¯»åæ³¢ç¹ç: " + value); |
| | | } else if ("08".equals(cmdType)) { |
| | | // 屿° |
| | | if (etLayer != null) etLayer.setText(String.valueOf(value)); |
| | | setLabelStatus(tvLayerStatus, "(读åå¼)", R.color.base_color); |
| | | appendLog("读å屿°: " + value); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | appendLog("åæ°è§£æå¼å¸¸: " + e.getMessage()); |
| | | setLabelStatus(tvStationStatus, "(读å失败)", R.color.orange); |
| | | setLabelStatus(tvBaudStatus, "(读å失败)", R.color.orange); |
| | | setLabelStatus(tvLayerStatus, "(读å失败)", R.color.orange); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è§£æåå
¥æä»¤è¿å |
| | | */ |
| | | private void parseWriteResponse(String hex) { |
| | | try { |
| | | // ç»æç¶æ: 第11-12ä½ |
| | | String statusHex = hex.substring(10, 12); |
| | | boolean isSuccess = "01".equals(statusHex); |
| | | |
| | | // åªè¦å½åææ£å¨æ§è¡çæä»¤ï¼ä¸æ¶å°äºåå
¥åå¤ï¼A55A06å¼å¤´ï¼ï¼æä»¬å°±è®¤ä¸ºæ¯å½åæä»¤çåå¤ |
| | | // å 为æä»¬éç¨éåæºå¶ï¼å¿
é¡»å䏿¡ç䏿¡ï¼æä»¥ä¸ä¼æä¹±åºé®é¢ã |
| | | // è¿ç§æ¹å¼è§é¿äº ENTER å EXIT å8ä½ç¸åå¯¼è´æ æ³åºåçé®é¢ã |
| | | |
| | | if (!currentExecutingCmd.isEmpty()) { |
| | | if (isSuccess) { |
| | | // è®°å½æ¥å¿ï¼æ ¹æ®å½åæå¾
çæä»¤æ¥è®°å½ï¼è䏿¯æ ¹æ®è¿åç hex |
| | | // 使ç¨å®æ´æä»¤åç¼å¹é
ï¼é¿å
ENTER (3B0101) å EXIT (3B0100) å8ä½ç¸å导è´çè¯¯å¤ |
| | | if (currentExecutingCmd.startsWith(CMD.ENTER_SETTING)) { |
| | | appendLog("è¿å
¥è®¾å®æ¨¡å¼æå"); |
| | | } else if (currentExecutingCmd.startsWith(CMD.WRITE_STATION_NUM)) { |
| | | appendLog("åå
¥ç«å·æå"); |
| | | setLabelStatus(tvStationStatus, "(åå
¥å¼)", R.color.base_color_s); |
| | | } else if (currentExecutingCmd.startsWith(CMD.WRITE_BAUD_RATE)) { |
| | | appendLog("åå
¥æ³¢ç¹çæå"); |
| | | setLabelStatus(tvBaudStatus, "(åå
¥å¼)", R.color.base_color_s); |
| | | } else if (currentExecutingCmd.startsWith(CMD.EXIT_SETTING)) { |
| | | appendLog("éåºè®¾å®æ¨¡å¼æå"); |
| | | } |
| | | |
| | | // åªææåæç»§ç»æ§è¡ä¸ä¸æ¡ |
| | | processNextCmd(); |
| | | } else { |
| | | // æ§è¡å¤±è´¥ |
| | | cmdQueue.clear(); |
| | | currentExecutingCmd = ""; |
| | | tvStatus.setText("åå
¥å¤±è´¥"); |
| | | |
| | | if (currentExecutingCmd.startsWith(CMD.WRITE_STATION_NUM)) { |
| | | setLabelStatus(tvStationStatus, "(åå
¥å¤±è´¥)", R.color.orange); |
| | | } else if (currentExecutingCmd.startsWith(CMD.WRITE_BAUD_RATE)) { |
| | | setLabelStatus(tvBaudStatus, "(åå
¥å¤±è´¥)", R.color.orange); |
| | | } |
| | | |
| | | appendLog("åå
¥å¤±è´¥: " + hex); |
| | | Toast.makeText(getContext(), "åå
¥å¤±è´¥", Toast.LENGTH_SHORT).show(); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | cmdQueue.clear(); |
| | | currentExecutingCmd = ""; |
| | | appendLog("åå
¥ååºè§£æå¼å¸¸: " + e.getMessage()); |
| | | // å¼å¸¸ä¹è§ä¸ºå¤±è´¥ |
| | | setLabelStatus(tvStationStatus, "(åå
¥å¤±è´¥)", R.color.orange); |
| | | setLabelStatus(tvBaudStatus, "(åå
¥å¤±è´¥)", R.color.orange); |
| | | } |
| | | } |
| | | |
| | | private void processNextCmd() { |
| | | if (cmdQueue != null && !cmdQueue.isEmpty()) { |
| | | String nextCmd = cmdQueue.poll(); |
| | | currentExecutingCmd = nextCmd; |
| | | |
| | | // æ´æ°ç¶ææ¾ç¤º |
| | | if (nextCmd.startsWith(CMD.ENTER_SETTING.substring(0, 10))) { |
| | | tvStatus.setText("æ£å¨è¿å
¥è®¾å®æ¨¡å¼..."); |
| | | } else if (nextCmd.startsWith(CMD.WRITE_STATION_NUM.substring(0, 10))) { |
| | | tvStatus.setText("æ£å¨åå
¥ç«å·..."); |
| | | } else if (nextCmd.startsWith(CMD.WRITE_BAUD_RATE.substring(0, 10))) { |
| | | tvStatus.setText("æ£å¨åå
¥æ³¢ç¹ç..."); |
| | | } else if (nextCmd.startsWith(CMD.EXIT_SETTING.substring(0, 10))) { |
| | | tvStatus.setText("æ£å¨éåºè®¾å®æ¨¡å¼..."); |
| | | } |
| | | |
| | | sendCmdWithCrc(nextCmd); |
| | | } else { |
| | | // éå为空ï¼å
¨é¨å®æ |
| | | currentExecutingCmd = ""; |
| | | tvStatus.setText("åæ°åå
¥å®æ"); |
| | | Toast.makeText(getContext(), "åæ°åå
¥æå", Toast.LENGTH_SHORT).show(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è§£æçæ§æ°æ®è¿å (æ¯å¦æç»çãå¨çº¿ç¶æ) |
| | | */ |
| | | private void parseDataResponse(String hex) { |
| | | try { |
| | | // è§£ææ¯å¦æç»ç: 第11-18ä½ (8个å符 = 32ä½) |
| | | // ç´¢å¼ 10-17 |
| | | String glassHex = hex.substring(10, 18); |
| | | appendLog("è§£æç»çæ°æ®: " + glassHex); |
| | | |
| | | // ç´æ¥è§£æ hex 为 long (Big Endian) |
| | | // "00000004" -> 4 -> ...00100 -> Bit 2 -> Box 3 |
| | | long glassBits = Long.parseLong(glassHex, 16); |
| | | |
| | | String onlineHex = hex.substring(18, 26); |
| | | appendLog("è§£æå¨çº¿æ°æ®: " + onlineHex); |
| | | long onlineBits = Long.parseLong(onlineHex, 16); |
| | | |
| | | // æ´æ°30ä¸ªæ ¼åçç¶æ |
| | | for (BoxStatus box : boxStatusList) { |
| | | int bitIndex = box.id - 1; // 对åºä½ç´¢å¼ 0-29 |
| | | if (bitIndex >= 0 && bitIndex < 32) { |
| | | // æ£æ¥å¯¹åºä½æ¯å¦ä¸º1 |
| | | box.hasGlass = ((glassBits >> bitIndex) & 1) == 1; |
| | | box.isOnline = ((onlineBits >> bitIndex) & 1) == 1; |
| | | } |
| | | } |
| | | |
| | | // å·æ°å表æ¾ç¤º |
| | | if (mAdapter != null) { |
| | | mAdapter.notifyDataSetChanged(); |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | tvStatus.setText("æ°æ®è§£æé误: " + e.getMessage()); |
| | | appendLog("æ°æ®è§£æé误: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | @Nullable |
| | | @Override |
| | | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { |
| | | return inflater.inflate(R.layout.fragment_settings, container, false); |
| | | } |
| | | |
| | | @Override |
| | | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { |
| | | super.onViewCreated(view, savedInstanceState); |
| | | |
| | | // Initialize box status list |
| | | boxStatusList.clear(); |
| | | for (int i = 1; i <= 30; i++) { |
| | | boxStatusList.add(new BoxStatus(i)); |
| | | } |
| | | |
| | | initView(view); |
| | | |
| | | // 妿已ç»è¿æ¥ï¼èªå¨è§¦å读å |
| | | if (BleGlobalManager.getInstance().isConnected()) { |
| | | triggerAutoRead(); |
| | | } |
| | | } |
| | | |
| | | private void triggerAutoRead() { |
| | | // 使ç¨é²ææºå¶ï¼é¿å
çæ¶é´å
夿¬¡è§¦å |
| | | debounceHandler.removeCallbacks(autoReadRunnable); |
| | | debounceHandler.postDelayed(autoReadRunnable, DEBOUNCE_DELAY_MS); |
| | | } |
| | | |
| | | private void showLoading(String msg) { |
| | | if (mLoadingDialog != null && mLoadingDialog.isShowing()) { |
| | | mLoadingDialog.dismiss(); |
| | | } |
| | | mLoadingDialog = new QMUITipDialog.Builder(getContext()) |
| | | .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) |
| | | .setTipWord(msg) |
| | | .create(); |
| | | // å
许ç¹å»å¤é¨æè¿åé®åæ¶ loadingï¼åªæ¯å
³é dialogï¼ |
| | | mLoadingDialog.setCancelable(true); |
| | | mLoadingDialog.setCanceledOnTouchOutside(true); |
| | | mLoadingDialog.show(); |
| | | } |
| | | |
| | | private void dismissLoading() { |
| | | if (mLoadingDialog != null && mLoadingDialog.isShowing()) { |
| | | mLoadingDialog.dismiss(); |
| | | } |
| | | } |
| | | |
| | | private void initView(View view) { |
| | | rvGrid = view.findViewById(R.id.rv_grid); |
| | | etLayer = view.findViewById(R.id.et_layer); |
| | | etStation = view.findViewById(R.id.et_station); |
| | | spinnerBaud = view.findViewById(R.id.spinner_baud); |
| | | btnWriteAll = view.findViewById(R.id.btn_write_all); |
| | | btnReadData = view.findViewById(R.id.btn_read_data); |
| | | btnReadParam = view.findViewById(R.id.btn_read_param); |
| | | btnClearLog = view.findViewById(R.id.btn_clear_log); |
| | | tvStatus = view.findViewById(R.id.tv_status); |
| | | tvLog = view.findViewById(R.id.tv_log); |
| | | svLog = view.findViewById(R.id.sv_log); |
| | | tvLayerStatus = view.findViewById(R.id.tv_layer_status); |
| | | tvStationStatus = view.findViewById(R.id.tv_station_status); |
| | | tvBaudStatus = view.findViewById(R.id.tv_baud_status); |
| | | |
| | | // è§£å³æ¥å¿åºåæ»å¨å²çª |
| | | svLog.setOnTouchListener((v, event) -> { |
| | | v.getParent().requestDisallowInterceptTouchEvent(true); |
| | | if ((event.getAction() & android.view.MotionEvent.ACTION_MASK) == android.view.MotionEvent.ACTION_UP) { |
| | | v.getParent().requestDisallowInterceptTouchEvent(false); |
| | | } |
| | | return false; |
| | | }); |
| | | |
| | | // Restore logs if any |
| | | if (logBuilder.length() > 0) { |
| | | tvLog.setText(Html.fromHtml(logBuilder.toString())); |
| | | } else { |
| | | logBuilder.append("æ¥å¿è®°å½:<br>"); |
| | | tvLog.setText(Html.fromHtml(logBuilder.toString())); |
| | | } |
| | | |
| | | // Grid Setup |
| | | // 10 columns to match the image (10 boxes per row) |
| | | // Since we are in a horizontal scroll view, this will layout correctly |
| | | rvGrid.setLayoutManager(new GridLayoutManager(getContext(), 10)); |
| | | mAdapter = new GridAdapter(); |
| | | rvGrid.setAdapter(mAdapter); |
| | | |
| | | // Spinner Setup |
| | | String[] baudRates = new String[]{"156Kbps", "625Kbps", "2.5Mbps", "5Mbps", "10Mbps"}; |
| | | ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, baudRates); |
| | | spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); |
| | | spinnerBaud.setAdapter(spinnerAdapter); |
| | | spinnerBaud.setSelection(0); // Select 156Kbps by default |
| | | |
| | | // Button Listeners |
| | | btnWriteAll.setOnClickListener(v -> { |
| | | if (!BleGlobalManager.getInstance().isConnected()) { |
| | | Toast.makeText(getContext(), "请å
è¿æ¥èç", Toast.LENGTH_SHORT).show(); |
| | | appendLog("é误: èçæªè¿æ¥"); |
| | | return; |
| | | } |
| | | |
| | | String stationStr = etStation.getText().toString().trim(); |
| | | if (stationStr.isEmpty()) { |
| | | Toast.makeText(getContext(), "请è¾å
¥ç«å·", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | int station = Integer.parseInt(stationStr); |
| | | if (station < 1 || station > 64) { |
| | | Toast.makeText(getContext(), "ç«å·èå´æ æ(1-64)", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | Toast.makeText(getContext(), "请è¾å
¥ææçæ°å", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | |
| | | // æé æä»¤éå |
| | | cmdQueue.clear(); |
| | | |
| | | // 1. è¿å
¥è®¾å® |
| | | cmdQueue.offer(CMD.ENTER_SETTING); |
| | | |
| | | // 2. åå
¥ç«å· |
| | | int station = Integer.parseInt(stationStr); |
| | | String stationHex = String.format("%02X", station); |
| | | cmdQueue.offer(CMD.WRITE_STATION_NUM + stationHex); |
| | | |
| | | // 3. åå
¥æ³¢ç¹ç |
| | | int baudIndex = spinnerBaud.getSelectedItemPosition(); |
| | | String baudHex = String.format("%02X", baudIndex); |
| | | cmdQueue.offer(CMD.WRITE_BAUD_RATE + baudHex); |
| | | |
| | | // 4. éåºè®¾å® |
| | | cmdQueue.offer(CMD.EXIT_SETTING); |
| | | |
| | | // å¼å§æ§è¡ |
| | | processNextCmd(); |
| | | }); |
| | | |
| | | btnReadData.setOnClickListener(v -> { |
| | | if (BleGlobalManager.getInstance().isConnected()) { |
| | | tvStatus.setText("ç¶æï¼æ£å¨è¯»åæ°æ®..."); |
| | | sendCmdWithCrc(CMD.READ_DATA); |
| | | } else { |
| | | Toast.makeText(getContext(), "请å
è¿æ¥èç", Toast.LENGTH_SHORT).show(); |
| | | appendLog("é误: èçæªè¿æ¥"); |
| | | } |
| | | }); |
| | | |
| | | btnReadParam.setOnClickListener(v -> { |
| | | if (BleGlobalManager.getInstance().isConnected()) { |
| | | tvStatus.setText("ç¶æï¼æ£å¨è¯»ååæ°..."); |
| | | sendCmdWithCrc(CMD.READ_FLOORS); |
| | | new android.os.Handler().postDelayed(() -> { |
| | | sendCmdWithCrc(CMD.READ_STATION_NUM); |
| | | }, 200); |
| | | new android.os.Handler().postDelayed(() -> { |
| | | sendCmdWithCrc(CMD.READ_BAUD_RATE); |
| | | }, 400); |
| | | } else { |
| | | Toast.makeText(getContext(), "请å
è¿æ¥èç", Toast.LENGTH_SHORT).show(); |
| | | appendLog("é误: èçæªè¿æ¥"); |
| | | } |
| | | }); |
| | | |
| | | btnClearLog.setOnClickListener(v -> { |
| | | logBuilder.setLength(0); |
| | | logBuilder.append("æ¥å¿è®°å½:<br>"); |
| | | tvLog.setText(Html.fromHtml(logBuilder.toString())); |
| | | }); |
| | | } |
| | | |
| | | private void appendLog(String msg) { |
| | | appendLog(msg, null); |
| | | } |
| | | |
| | | private String getCmdDescription(String hex) { |
| | | if (hex == null) return ""; |
| | | |
| | | // åéæä»¤å¹é
|
| | | if (hex.startsWith(CMD.READ_DATA)) return "-è¯»æ°æ®"; |
| | | if (hex.startsWith(CMD.READ_FLOORS)) return "-è¯»å±æ°"; |
| | | if (hex.startsWith(CMD.READ_STATION_NUM)) return "-读ç«å·"; |
| | | if (hex.startsWith(CMD.READ_BAUD_RATE)) return "-读波ç¹ç"; |
| | | |
| | | // 3Bæä»¤ (Enter/Exit) ç¹æ®å¤ç |
| | | if (hex.startsWith(CMD.ENTER_SETTING.substring(0, 8))) { |
| | | // 1. ä¸ä¸æä¼å
: 妿å½åææ£å¨æ§è¡çæä»¤ï¼ä»¥å½åæä»¤ä¸ºå |
| | | if (!currentExecutingCmd.isEmpty()) { |
| | | if (currentExecutingCmd.startsWith(CMD.ENTER_SETTING)) return "-è¿å
¥è®¾å®"; |
| | | if (currentExecutingCmd.startsWith(CMD.EXIT_SETTING)) return "-éåºè®¾å®"; |
| | | } |
| | | |
| | | // 2. 精确å¹é
éåºæä»¤ |
| | | if (hex.startsWith(CMD.EXIT_SETTING)) return "-éåºè®¾å®"; |
| | | |
| | | // 3. é»è®¤æ
åµ: ä¸ä¸æä¸ºç©ºä¸éæç¡®éåºæä»¤ï¼ä¼å
å¤å®ä¸ºéåºè®¾å® |
| | | // (ä¿®å¤: ä»åæ°è®¾å®é¡µè¿åæ¶ï¼æ¶å°çéåºååºä¼è¢«è¯¯å¤ä¸ºè¿å
¥è®¾å®) |
| | | return "-éåºè®¾å®"; |
| | | } |
| | | |
| | | if (hex.startsWith(CMD.ENTER_SETTING)) return "-è¿å
¥è®¾å®"; |
| | | if (hex.startsWith(CMD.EXIT_SETTING)) return "-éåºè®¾å®"; |
| | | if (hex.startsWith(CMD.WRITE_STATION_NUM)) return "-åç«å·"; |
| | | if (hex.startsWith(CMD.WRITE_BAUD_RATE)) return "-åæ³¢ç¹ç"; |
| | | |
| | | // æ¥æ¶æ°æ®å¹é
|
| | | // è¯»æ°æ®è¿å: A55A0301... |
| | | if (hex.startsWith(CMD.READ_DATA.substring(0, 8))) return "-è¯»æ°æ®"; |
| | | |
| | | // è¯»åæ°è¿å |
| | | if (hex.length() >= 8) { |
| | | if (hex.startsWith(CMD.READ_FLOORS.substring(0, 8))) return "-è¯»å±æ°"; |
| | | if (hex.startsWith(CMD.READ_STATION_NUM.substring(0, 8))) return "-读ç«å·"; |
| | | if (hex.startsWith(CMD.READ_BAUD_RATE.substring(0, 8))) return "-读波ç¹ç"; |
| | | } |
| | | |
| | | // åå
¥è¿å (A55A06å¼å¤´) |
| | | if (hex.startsWith("A55A06") && hex.length() >= 12) { |
| | | return "-åå
¥è¿å"; |
| | | } |
| | | |
| | | return ""; |
| | | } |
| | | |
| | | private void appendLog(String msg, Boolean isSent) { |
| | | String time = com.blankj.utilcode.util.TimeUtils.getNowString(new java.text.SimpleDateFormat("HH:mm:ss.SSS")); |
| | | |
| | | String displayMsg = msg; |
| | | String cmdDesc = ""; |
| | | |
| | | // å¦ææ¯ hex æä»¤ (纯 0-9 A-F a-f)ï¼å ç©ºæ ¼æ ¼å¼å |
| | | if (msg.matches("^[0-9A-Fa-f]+$")) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (int i = 0; i < msg.length(); i += 2) { |
| | | if (i + 2 <= msg.length()) { |
| | | sb.append(msg.substring(i, i + 2)).append(" "); |
| | | } else { |
| | | sb.append(msg.substring(i)); |
| | | } |
| | | } |
| | | displayMsg = sb.toString().trim(); |
| | | |
| | | // è·åæä»¤æè¿° |
| | | cmdDesc = getCmdDescription(msg); |
| | | } |
| | | |
| | | String logLine; |
| | | if (isSent != null) { |
| | | String color = isSent ? "#1890ff" : "#05aa87"; // Blue for sent, Green for received |
| | | // æ¼æ¥å°åç¼åé¢: "åé-读ç«å·: " æ "æ¶å°-读ç«å·: " |
| | | String prefix = (isSent ? "åé" : "æ¶å°") + cmdDesc + ": "; |
| | | logLine = time + " <font color='" + color + "'>" + prefix + displayMsg + "</font><br>"; |
| | | } else { |
| | | // æ®éæ¥å¿ |
| | | logLine = time + " " + displayMsg + "<br>"; |
| | | } |
| | | |
| | | logBuilder.append(logLine); |
| | | if (tvLog != null) { |
| | | tvLog.setText(Html.fromHtml(logBuilder.toString())); |
| | | if (svLog != null) { |
| | | svLog.post(() -> svLog.fullScroll(View.FOCUS_DOWN)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void sendCmdWithCrc(String cmd) { |
| | | if (!BleGlobalManager.getInstance().isConnected()) { |
| | | Toast.makeText(getContext(), "请å
è¿æ¥èç", Toast.LENGTH_SHORT).show(); |
| | | appendLog("é误: èçæªè¿æ¥"); |
| | | return; |
| | | } |
| | | |
| | | byte[] cmdBytes = BleGlobalManager.hexStringToBytes(cmd); |
| | | if (cmdBytes != null) { |
| | | String crc = CRCutil.getCRC(cmdBytes); |
| | | // Pad CRC to 4 chars if needed |
| | | while (crc.length() < 4) { |
| | | crc = "0" + crc; |
| | | } |
| | | String fullCmd = cmd + crc.toUpperCase(); |
| | | |
| | | appendLog(fullCmd, true); // true for sent (Blue) |
| | | BleGlobalManager.getInstance().sendCmd(fullCmd); |
| | | } else { |
| | | appendLog("é误: æä»¤è½¬æ¢å¤±è´¥"); |
| | | } |
| | | } |
| | | |
| | | private class GridAdapter extends RecyclerView.Adapter<GridAdapter.ViewHolder> { |
| | | |
| | | @NonNull |
| | | @Override |
| | | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { |
| | | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_grid_box, parent, false); |
| | | return new ViewHolder(view); |
| | | } |
| | | |
| | | @Override |
| | | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { |
| | | // Calculate Box ID based on 3 rows of 10, Right to Left logic as seen in image |
| | | // Row 1 (pos 0-9): 10 ... 1 |
| | | // Row 2 (pos 10-19): 20 ... 11 |
| | | // Row 3 (pos 20-29): 30 ... 21 |
| | | |
| | | int row = position / 10; |
| | | int col = position % 10; |
| | | int boxId = (row + 1) * 10 - col; |
| | | |
| | | holder.tvBoxNumber.setText(String.valueOf(boxId)); |
| | | |
| | | // Find status for this box |
| | | BoxStatus status = null; |
| | | for (BoxStatus s : boxStatusList) { |
| | | if (s.id == boxId) { |
| | | status = s; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (status != null) { |
| | | // Priority: Online > Glass |
| | | // User requirement: "ä¼å
æ¾ç¤ºæ¯å¦å¨çº¿" (Prioritize displaying online status) |
| | | // "19-26ä¸ºä¹æ¯16è¿å¶ï¼è§£ææäºè¿å¶32ä»£ç æè¿30ä¸ªæ ¼åæ¯å¦å¨çº¿" |
| | | // Usually this means if offline, show offline color. If online, show state (glass/no glass). |
| | | |
| | | if (!status.isOnline) { |
| | | holder.viewBox.setBackgroundResource(R.drawable.bg_box_offline); // Offline (Grey) |
| | | } else { |
| | | if (status.hasGlass) { |
| | | holder.viewBox.setBackgroundResource(R.drawable.bg_box_full); // Green |
| | | } else { |
| | | holder.viewBox.setBackgroundResource(R.drawable.bg_box_empty); // Online but empty (White) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public int getItemCount() { |
| | | return 30; |
| | | } |
| | | |
| | | class ViewHolder extends RecyclerView.ViewHolder { |
| | | TextView tvBoxNumber; |
| | | View viewBox; |
| | | |
| | | public ViewHolder(@NonNull View itemView) { |
| | | super(itemView); |
| | | tvBoxNumber = itemView.findViewById(R.id.tv_box_number); |
| | | viewBox = itemView.findViewById(R.id.view_box); |
| | | } |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.manager; |
| | | |
| | | import android.bluetooth.BluetoothGatt; |
| | | import android.bluetooth.BluetoothGattCallback; |
| | | import android.bluetooth.BluetoothGattCharacteristic; |
| | | import android.bluetooth.BluetoothGattDescriptor; |
| | | import android.bluetooth.BluetoothGattService; |
| | | import android.bluetooth.BluetoothProfile; |
| | | import android.content.Context; |
| | | import android.os.Handler; |
| | | import android.os.Looper; |
| | | import android.widget.Toast; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.connect.BluetoothConnectManager; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectState; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectStateListener; |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | import com.shlb.comb.event.UpdateEvent; |
| | | |
| | | import org.greenrobot.eventbus.EventBus; |
| | | |
| | | import java.util.List; |
| | | import java.util.UUID; |
| | | |
| | | public class BleGlobalManager { |
| | | private static volatile BleGlobalManager instance; |
| | | private BluetoothConnectManager connectManager; |
| | | private BluetoothLeDevice mDevice; |
| | | private BluetoothGatt mGatt; |
| | | |
| | | private BluetoothGattCharacteristic writeCharacteristic; |
| | | private BluetoothGattCharacteristic notifyCharacteristic; |
| | | |
| | | private Context mContext; |
| | | private Handler mHandler = new Handler(Looper.getMainLooper()); |
| | | |
| | | private BleGlobalManager() { |
| | | } |
| | | |
| | | public static BleGlobalManager getInstance() { |
| | | if (instance == null) { |
| | | synchronized (BleGlobalManager.class) { |
| | | if (instance == null) { |
| | | instance = new BleGlobalManager(); |
| | | } |
| | | } |
| | | } |
| | | return instance; |
| | | } |
| | | |
| | | public BluetoothLeDevice getBluetoothLeDevice() { |
| | | return mDevice; |
| | | } |
| | | |
| | | public void connect(Context context, BluetoothLeDevice device) { |
| | | this.mContext = context; |
| | | if (connectManager == null) { |
| | | connectManager = BluetoothConnectManager.getInstance(mContext); |
| | | connectManager.addConnectStateListener(stateListener); |
| | | } |
| | | // æ¯æ¬¡è¿æ¥é½éæ°è®¾ç½®GattCallbackï¼é²æ¢è¢«å
¶ä»Activity(å¦TestActivity)è¦ç |
| | | connectManager.setBluetoothGattCallback(gattCallback); |
| | | |
| | | this.mDevice = device; |
| | | connectManager.connect(device.getAddress()); |
| | | } |
| | | |
| | | public void disconnect() { |
| | | if (connectManager != null && mDevice != null) { |
| | | connectManager.disconnect(mDevice.getAddress()); |
| | | } |
| | | resetCharacteristics(); |
| | | } |
| | | |
| | | public boolean isConnected() { |
| | | return connectManager != null && connectManager.isConnectDevice(); |
| | | } |
| | | |
| | | private void resetCharacteristics() { |
| | | writeCharacteristic = null; |
| | | notifyCharacteristic = null; |
| | | mGatt = null; |
| | | } |
| | | |
| | | private ConnectStateListener stateListener = new ConnectStateListener() { |
| | | @Override |
| | | public void onConnectStateChanged(String address, ConnectState state) { |
| | | if (state == ConnectState.CONNECTED) { |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.CONN_STATU, BluetoothProfile.STATE_CONNECTED, "conn_statu")); |
| | | } else if (state == ConnectState.NORMAL) { |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.CONN_STATU, BluetoothProfile.STATE_DISCONNECTED, "conn_statu")); |
| | | } |
| | | |
| | | if (state == ConnectState.NORMAL) { |
| | | resetCharacteristics(); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | private BluetoothGattCallback gattCallback = new BluetoothGattCallback() { |
| | | @Override |
| | | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { |
| | | super.onConnectionStateChange(gatt, status, newState); |
| | | if (newState == BluetoothProfile.STATE_CONNECTED) { |
| | | mGatt = gatt; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onServicesDiscovered(BluetoothGatt gatt, int status) { |
| | | super.onServicesDiscovered(gatt, status); |
| | | if (status == BluetoothGatt.GATT_SUCCESS) { |
| | | mHandler.post(() -> findAndEnableCharacteristics(gatt.getServices())); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { |
| | | super.onCharacteristicWrite(gatt, characteristic, status); |
| | | // Optional: Broadcast write status |
| | | } |
| | | |
| | | @Override |
| | | public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { |
| | | super.onCharacteristicChanged(gatt, characteristic); |
| | | byte[] value = characteristic.getValue(); |
| | | String hex = bytesToHexString(value); |
| | | // Broadcast received data |
| | | // You can define a custom event or interface for this |
| | | EventBus.getDefault().post(new UpdateEvent(UpdateEvent.Type.DEVICE_INFO, 0, hex)); |
| | | } |
| | | }; |
| | | |
| | | private void findAndEnableCharacteristics(List<BluetoothGattService> services) { |
| | | // Simple logic: Find first Write and first Notify in the same service if possible |
| | | // Or just find any Write and any Notify |
| | | |
| | | for (BluetoothGattService service : services) { |
| | | for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) { |
| | | int props = characteristic.getProperties(); |
| | | |
| | | // Find Write Characteristic |
| | | if ((props & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0 || |
| | | (props & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) { |
| | | // TODO å¾
确认 |
| | | if (writeCharacteristic == null && characteristic.getUuid().toString().startsWith("0000ff") ) { |
| | | writeCharacteristic = characteristic; |
| | | |
| | | // Once we found a write char, let's look for a notify char in the SAME service |
| | | findNotifyInService(service); |
| | | |
| | | if (notifyCharacteristic != null) { |
| | | // Found both in same service, we are good |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (writeCharacteristic != null && notifyCharacteristic == null) { |
| | | // If we didn't find notify in the same service, look everywhere |
| | | for (BluetoothGattService service : services) { |
| | | findNotifyInService(service); |
| | | if (notifyCharacteristic != null) break; |
| | | } |
| | | } |
| | | |
| | | if (writeCharacteristic != null) { |
| | | Toast.makeText(mContext, "æå¡å·²å°±ç»ª (Write: " + writeCharacteristic.getUuid().toString().substring(0,8) + ")", Toast.LENGTH_SHORT).show(); |
| | | } else { |
| | | Toast.makeText(mContext, "æªæ¾å°å¯åç¹å¾å¼", Toast.LENGTH_SHORT).show(); |
| | | } |
| | | } |
| | | |
| | | private void findNotifyInService(BluetoothGattService service) { |
| | | for (BluetoothGattCharacteristic c : service.getCharacteristics()) { |
| | | int props = c.getProperties(); |
| | | if ((props & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0 || |
| | | (props & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) { |
| | | notifyCharacteristic = c; |
| | | enableNotification(c); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void enableNotification(BluetoothGattCharacteristic characteristic) { |
| | | if (mGatt == null || characteristic == null) return; |
| | | |
| | | boolean success = mGatt.setCharacteristicNotification(characteristic, true); |
| | | if (success) { |
| | | BluetoothGattDescriptor descriptor = characteristic.getDescriptor( |
| | | UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); |
| | | |
| | | if (descriptor != null) { |
| | | int props = characteristic.getProperties(); |
| | | byte[] value = null; |
| | | if ((props & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { |
| | | value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; |
| | | } else if ((props & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) { |
| | | value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; |
| | | } |
| | | |
| | | if (value != null) { |
| | | descriptor.setValue(value); |
| | | mGatt.writeDescriptor(descriptor); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | public void sendCmd(String hexCmd) { |
| | | if (writeCharacteristic == null || mGatt == null) { |
| | | Toast.makeText(mContext, "èçæªè¿æ¥ææå¡æªæ¾å°", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | |
| | | byte[] data = hexStringToBytes(hexCmd); |
| | | if (data == null) { |
| | | Toast.makeText(mContext, "æä»¤æ ¼å¼é误", Toast.LENGTH_SHORT).show(); |
| | | return; |
| | | } |
| | | |
| | | writeCharacteristic.setValue(data); |
| | | mGatt.writeCharacteristic(writeCharacteristic); |
| | | } |
| | | |
| | | public static byte[] hexStringToBytes(String hexString) { |
| | | if (hexString == null || hexString.equals("")) { |
| | | return null; |
| | | } |
| | | hexString = hexString.toUpperCase(); |
| | | hexString = hexString.replace(" ", ""); |
| | | if (hexString.length() % 2 != 0) { |
| | | hexString = "0" + hexString; |
| | | } |
| | | |
| | | int length = hexString.length() / 2; |
| | | char[] hexChars = hexString.toCharArray(); |
| | | byte[] d = new byte[length]; |
| | | for (int i = 0; i < length; i++) { |
| | | int pos = i * 2; |
| | | d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); |
| | | } |
| | | return d; |
| | | } |
| | | |
| | | private static byte charToByte(char c) { |
| | | return (byte) "0123456789ABCDEF".indexOf(c); |
| | | } |
| | | |
| | | public static String bytesToHexString(byte[] src) { |
| | | StringBuilder stringBuilder = new StringBuilder(""); |
| | | if (src == null || src.length <= 0) { |
| | | return null; |
| | | } |
| | | for (int i = 0; i < src.length; i++) { |
| | | int v = src[i] & 0xFF; |
| | | String hv = Integer.toHexString(v); |
| | | if (hv.length() < 2) { |
| | | stringBuilder.append(0); |
| | | } |
| | | stringBuilder.append(hv); |
| | | } |
| | | return stringBuilder.toString().toUpperCase(); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.model; |
| | | |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.device.BeaconType; |
| | | import com.blakequ.bluetooth_manager_lib.device.BeaconUtils; |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | import com.blakequ.bluetooth_manager_lib.device.ibeacon.IBeaconDevice; |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | import com.shlb.comb.util.CsvWriterHelper; |
| | | import com.shlb.comb.util.TimeFormatter; |
| | | |
| | | import java.io.File; |
| | | import java.io.FileWriter; |
| | | import java.io.IOException; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Comparator; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | |
| | | |
| | | public class BluetoothLeDeviceStore { |
| | | private final Map<String, BluetoothLeDevice> mDeviceMap; |
| | | |
| | | |
| | | public BluetoothLeDeviceStore() { |
| | | mDeviceMap = new HashMap<>(); |
| | | } |
| | | |
| | | public void addDevice(final BluetoothLeDevice device) { |
| | | //if (mDeviceMap.containsKey(device.getAddress())) { |
| | | // mDeviceMap.get(device.getAddress()).updateRssiReading(device.getTimestamp(), device.getRssi()); |
| | | //} else { |
| | | // mDeviceMap.put(device.getAddress(), device); |
| | | //} |
| | | mDeviceMap.put(device.getAddress(), device); |
| | | } |
| | | |
| | | public void clear() { |
| | | mDeviceMap.clear(); |
| | | } |
| | | |
| | | |
| | | public List<BluetoothLeDevice> getDeviceList() { |
| | | final List<BluetoothLeDevice> methodResult = new ArrayList<>(mDeviceMap.values()); |
| | | if(methodResult==null || methodResult.size()< 1) return methodResult; |
| | | |
| | | Collections.sort(methodResult, new Comparator<BluetoothLeDevice>() { |
| | | |
| | | @Override |
| | | public int compare(final BluetoothLeDevice arg0, final BluetoothLeDevice arg1) { |
| | | return arg0.getAddress().compareToIgnoreCase(arg1.getAddress()); |
| | | } |
| | | }); |
| | | |
| | | return methodResult; |
| | | } |
| | | |
| | | public int size(){ |
| | | return mDeviceMap.size(); |
| | | } |
| | | |
| | | private String getListAsCsv() { |
| | | final List<BluetoothLeDevice> list = getDeviceList(); |
| | | final StringBuilder sb = new StringBuilder(); |
| | | sb.append(CsvWriterHelper.addStuff("mac")); |
| | | sb.append(CsvWriterHelper.addStuff("name")); |
| | | sb.append(CsvWriterHelper.addStuff("firstTimestamp")); |
| | | sb.append(CsvWriterHelper.addStuff("firstRssi")); |
| | | sb.append(CsvWriterHelper.addStuff("currentTimestamp")); |
| | | sb.append(CsvWriterHelper.addStuff("currentRssi")); |
| | | sb.append(CsvWriterHelper.addStuff("adRecord")); |
| | | sb.append(CsvWriterHelper.addStuff("iBeacon")); |
| | | sb.append(CsvWriterHelper.addStuff("uuid")); |
| | | sb.append(CsvWriterHelper.addStuff("major")); |
| | | sb.append(CsvWriterHelper.addStuff("minor")); |
| | | sb.append(CsvWriterHelper.addStuff("txPower")); |
| | | sb.append(CsvWriterHelper.addStuff("distance")); |
| | | sb.append(CsvWriterHelper.addStuff("accuracy")); |
| | | sb.append('\n'); |
| | | |
| | | for (final BluetoothLeDevice device : list) { |
| | | sb.append(CsvWriterHelper.addStuff(device.getAddress())); |
| | | sb.append(CsvWriterHelper.addStuff(device.getName())); |
| | | sb.append(CsvWriterHelper.addStuff(TimeFormatter.getIsoDateTime(device.getFirstTimestamp()))); |
| | | sb.append(CsvWriterHelper.addStuff(device.getFirstRssi())); |
| | | sb.append(CsvWriterHelper.addStuff(TimeFormatter.getIsoDateTime(device.getTimestamp()))); |
| | | sb.append(CsvWriterHelper.addStuff(device.getRssi())); |
| | | sb.append(CsvWriterHelper.addStuff(ByteUtils.byteArrayToHexString(device.getScanRecord()))); |
| | | final boolean isIBeacon = BeaconUtils.getBeaconType(device) == BeaconType.IBEACON; |
| | | final String uuid; |
| | | final String minor; |
| | | final String major; |
| | | final String txPower; |
| | | final String distance; |
| | | final String accuracy; |
| | | |
| | | if (isIBeacon) { |
| | | final IBeaconDevice beacon = new IBeaconDevice(device); |
| | | uuid = String.valueOf(beacon.getUUID()); |
| | | minor = String.valueOf(beacon.getMinor()); |
| | | major = String.valueOf(beacon.getMajor()); |
| | | txPower = String.valueOf(beacon.getCalibratedTxPower()); |
| | | distance = beacon.getDistanceDescriptor().toString().toLowerCase(Locale.US); |
| | | accuracy = String.valueOf(beacon.getAccuracy()); |
| | | } else { |
| | | uuid = ""; |
| | | minor = ""; |
| | | major = ""; |
| | | txPower = ""; |
| | | distance = ""; |
| | | accuracy = ""; |
| | | } |
| | | |
| | | sb.append(CsvWriterHelper.addStuff(isIBeacon)); |
| | | sb.append(CsvWriterHelper.addStuff(uuid)); |
| | | sb.append(CsvWriterHelper.addStuff(minor)); |
| | | sb.append(CsvWriterHelper.addStuff(major)); |
| | | sb.append(CsvWriterHelper.addStuff(txPower)); |
| | | sb.append(CsvWriterHelper.addStuff(distance)); |
| | | sb.append(CsvWriterHelper.addStuff(accuracy)); |
| | | |
| | | sb.append('\n'); |
| | | } |
| | | |
| | | return sb.toString(); |
| | | } |
| | | |
| | | |
| | | private static FileWriter generateFile(final File file, final String contents) { |
| | | FileWriter writer = null; |
| | | try { |
| | | writer = new FileWriter(file); |
| | | writer.append(contents); |
| | | writer.flush(); |
| | | |
| | | } catch (final IOException e) { |
| | | e.printStackTrace(); |
| | | } finally { |
| | | try { |
| | | writer.close(); |
| | | } catch (final IOException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | return writer; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.model; |
| | | |
| | | public class HomeItem { |
| | | private String title; |
| | | private Class<?> activity; |
| | | private int imageResource; |
| | | |
| | | public HomeItem() { |
| | | } |
| | | |
| | | public HomeItem(String title, Class<?> activity, int imageResource) { |
| | | this.title = title; |
| | | this.activity = activity; |
| | | this.imageResource = imageResource; |
| | | } |
| | | |
| | | public int getImageResource() { |
| | | return imageResource; |
| | | } |
| | | |
| | | public void setImageResource(int imageResource) { |
| | | this.imageResource = imageResource; |
| | | } |
| | | |
| | | public String getTitle() { |
| | | return title; |
| | | } |
| | | |
| | | public void setTitle(String title) { |
| | | this.title = title; |
| | | } |
| | | |
| | | public Class<?> getActivity() { |
| | | return activity; |
| | | } |
| | | |
| | | public void setActivity(Class<?> activity) { |
| | | this.activity = activity; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.util; |
| | | |
| | | public class CMD { |
| | | //è¿å
¥ |
| | | public final static String GETINTO = "A55A060100000000002F35"; |
| | | //æ¸
é¤ |
| | | public final static String CLEAN = "A55A060200000000002F06"; |
| | | //éåº |
| | | public final static String EXIT = "A55A060400000000002F60"; |
| | | |
| | | |
| | | |
| | | public final static String ADDR_PREFIX = "A55A060300"; |
| | | public final static String ADDR_SUFFIX = "000000"; |
| | | |
| | | |
| | | //读ååç¼ |
| | | public final static String READ_PREFIX = "A55A03"; |
| | | //åå
¥åç¼ |
| | | public final static String WRITE_SUFFIX = "A55A06"; |
| | | |
| | | |
| | | //è¯»æ°æ® |
| | | public final static String READ_DATA = READ_PREFIX + "0100"; |
| | | //è¿å
¥è®¾å® |
| | | public final static String ENTER_SETTING = WRITE_SUFFIX + "3B0101"; |
| | | //éåºè®¾å® |
| | | public final static String EXIT_SETTING = WRITE_SUFFIX + "3B0100"; |
| | | |
| | | |
| | | //è¯»å±æ° |
| | | public final static String READ_FLOORS = READ_PREFIX + "080100"; |
| | | //读ç«å· |
| | | public final static String READ_STATION_NUM = READ_PREFIX + "040100"; |
| | | //读波ç¹ç |
| | | public final static String READ_BAUD_RATE = READ_PREFIX + "050100"; |
| | | //读主æ¿çæ¬ |
| | | public final static String READ_BOARD_VERSION = READ_PREFIX + "0A0100"; |
| | | //è¯»ä¼ æå¨çæ¬ |
| | | public final static String READ_SENSOR_VERSION = READ_PREFIX + "0B0100"; |
| | | |
| | | |
| | | |
| | | //åç«å· |
| | | public final static String WRITE_STATION_NUM = WRITE_SUFFIX + "3401"; |
| | | //åæ³¢ç¹ç |
| | | public final static String WRITE_BAUD_RATE = WRITE_SUFFIX + "3501"; |
| | | |
| | | //å¼å§ç¼å |
| | | public final static String WRITE_START_ADDRESS = WRITE_SUFFIX + "3A0100"; |
| | | //ç»æç¼å |
| | | public final static String WRITE_END_ADDRESS = WRITE_SUFFIX + "3A0101"; |
| | | //å¼å§èªæ£ |
| | | public final static String WRITE_START_CHECK = WRITE_SUFFIX + "390100"; |
| | | //ç»æèªæ£ |
| | | public final static String WRITE_END_CHECK = WRITE_SUFFIX + "390101"; |
| | | // éå¼è®¾å® |
| | | public final static String WRITE_THRESHOLD_SETTING = WRITE_SUFFIX + "3202"; |
| | | //çµæè®¾å® |
| | | public final static String WRITE_SENSITIVITY_SETTING = WRITE_SUFFIX + "3302"; |
| | | |
| | | |
| | | //主æ¿å¤ä½ |
| | | public final static String WRITE_BOARD_RESET = WRITE_SUFFIX + "360100"; |
| | | //ä¼ æå¨å¤ä½ |
| | | public final static String WRITE_SENSOR_RESET = WRITE_SUFFIX + "360101"; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.util; |
| | | |
| | | public class CRCutil { |
| | | /** |
| | | * 计ç®CRC16æ ¡éªç |
| | | * |
| | | * @param bytes |
| | | * @return |
| | | */ |
| | | public static String getCRC(byte[] bytes) { |
| | | int CRC = 0xFFFF; |
| | | int POLYNOMIAL = 0xA001; |
| | | |
| | | int i, j,m,n; |
| | | for (i = 0; i < bytes.length; i++) { |
| | | CRC ^= ((int) bytes[i] & 0x000000ff); |
| | | for (j = 0; j < 8; j++) { |
| | | if ((CRC & 0x00000001) != 0) { |
| | | CRC >>= 1; |
| | | CRC ^= POLYNOMIAL; |
| | | } else { |
| | | CRC >>= 1; |
| | | } |
| | | } |
| | | } |
| | | m = CRC /256; |
| | | n = CRC%256; |
| | | CRC = n * 256 +m; |
| | | return Integer.toHexString(CRC); |
| | | |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.util; |
| | | |
| | | import java.text.DecimalFormat; |
| | | |
| | | public class Constants { |
| | | public static final DecimalFormat DOUBLE_TWO_DIGIT_ACCURACY = new DecimalFormat("#.##"); |
| | | public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; |
| | | |
| | | public static final String FILTER_SWITCH = "switch"; |
| | | public static final String FILTER_NAME = "name"; |
| | | public static final String FILTER_RSSI = "rssi"; |
| | | public static final String SCAN_PERIOD = "scan"; |
| | | public static final String PAUSE_PERIOD = "pause"; |
| | | public static final String SHOW_SPINNER = "spinner"; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.util; |
| | | |
| | | public class CsvWriterHelper { |
| | | private static final String QUOTE = "\""; |
| | | |
| | | public static String addStuff(final Integer text) { |
| | | return QUOTE + text + QUOTE + ","; |
| | | } |
| | | |
| | | public static String addStuff(final Long text) { |
| | | return QUOTE + text + QUOTE + ","; |
| | | } |
| | | |
| | | public static String addStuff(final boolean value) { |
| | | return QUOTE + value + QUOTE + ","; |
| | | } |
| | | |
| | | public static String addStuff(String text) { |
| | | if (text == null) { |
| | | text = "<blank>"; |
| | | } |
| | | text = text.replace(QUOTE, "'"); |
| | | |
| | | return QUOTE + text.trim() + QUOTE + ","; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.util; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * ç¨äºåå¨å·²è¿æ¥èçæ°æ® |
| | | */ |
| | | public class Singletion { |
| | | private static final Singletion mInstance = new Singletion(); |
| | | |
| | | private Singletion() { |
| | | |
| | | } |
| | | public static Singletion getInstance() { |
| | | return mInstance; |
| | | } |
| | | //å½åè¿æ¥èçççå¯åæå¡ |
| | | public List<Map<String,String>> mbluetoothServices = new ArrayList<>(); |
| | | //å½åè¿æ¥èç |
| | | public BluetoothLeDevice mDevice; |
| | | |
| | | public boolean mConnected; |
| | | |
| | | public String uuid; |
| | | |
| | | public void clean(){ |
| | | mbluetoothServices.clear(); |
| | | mDevice = null; |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.util; |
| | | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | import java.util.Locale; |
| | | |
| | | public class TimeFormatter { |
| | | private final static String FORMAT = "yyyy-MM-dd HH:mm:ss zzz"; |
| | | private final static SimpleDateFormat FORMATTER = new SimpleDateFormat(FORMAT, Locale.CHINA); |
| | | |
| | | public static String getIsoDateTime(final Date date) { |
| | | return FORMATTER.format(date); |
| | | } |
| | | |
| | | public static String getIsoDateTime(final long millis) { |
| | | return getIsoDateTime(new Date(millis)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.view; |
| | | |
| | | |
| | | import android.animation.Animator; |
| | | import android.animation.AnimatorSet; |
| | | import android.animation.ValueAnimator; |
| | | import android.content.Context; |
| | | import android.content.res.TypedArray; |
| | | import android.graphics.Canvas; |
| | | import android.graphics.Color; |
| | | import android.graphics.LinearGradient; |
| | | import android.graphics.Paint; |
| | | import android.graphics.PointF; |
| | | import android.graphics.Rect; |
| | | import android.graphics.RectF; |
| | | import android.graphics.Shader; |
| | | import android.util.AttributeSet; |
| | | import android.view.View; |
| | | |
| | | import com.shlb.comb.R; |
| | | import com.shlb.comb.view.particleview.LineEvaluator; |
| | | import com.shlb.comb.view.particleview.Particle; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | | |
| | | |
| | | /** |
| | | * ä½è
ï¼ å·´æ on 16/8/27 11:29 |
| | | * Github: https://github.com/JeasonWong |
| | | */ |
| | | public class ParticleView extends View { |
| | | |
| | | private final int STATUS_MOTIONLESS = 0; |
| | | private final int STATUS_PARTICLE_GATHER = 1; |
| | | private final int STATUS_TEXT_MOVING = 2; |
| | | |
| | | private final int ROW_NUM = 10; |
| | | private final int COLUMN_NUM = 10; |
| | | |
| | | private final int DEFAULT_MAX_TEXT_SIZE = sp2px(80); |
| | | private final int DEFAULT_MIN_TEXT_SIZE = sp2px(30); |
| | | |
| | | public final int DEFAULT_TEXT_ANIM_TIME = 1000; |
| | | public final int DEFAULT_SPREAD_ANIM_TIME = 300; |
| | | public final int DEFAULT_HOST_TEXT_ANIM_TIME = 800; |
| | | |
| | | private Paint mHostTextPaint; |
| | | private Paint mParticleTextPaint; |
| | | private Paint mCirclePaint; |
| | | private Paint mHostBgPaint; |
| | | private int mWidth, mHeight; |
| | | |
| | | private Particle[][] mParticles = new Particle[ROW_NUM][COLUMN_NUM]; |
| | | private Particle[][] mMinParticles = new Particle[ROW_NUM][COLUMN_NUM]; |
| | | |
| | | //èæ¯è² |
| | | private int mBgColor; |
| | | //ç²åè² |
| | | private int mParticleColor; |
| | | //é»è®¤ç²åææ¡å¤§å° |
| | | private int mParticleTextSize = DEFAULT_MIN_TEXT_SIZE; |
| | | |
| | | private int mStatus = STATUS_MOTIONLESS; |
| | | |
| | | private ParticleAnimListener mParticleAnimListener; |
| | | |
| | | //ç²åææ¡ |
| | | private String mParticleText; |
| | | //ä¸»ææ¡ |
| | | private String mHostText; |
| | | //æ©æ£å®½åº¦ |
| | | private float mSpreadWidth; |
| | | //Hostæåå±ç°å®½åº¦ |
| | | private float mHostRectWidth; |
| | | //ç²åææ¡çxåæ |
| | | private float mParticleTextX; |
| | | //Hostæåçxåæ |
| | | private float mHostTextX; |
| | | |
| | | //Text anim time in milliseconds |
| | | private int mTextAnimTime; |
| | | //Spread anim time in milliseconds |
| | | private int mSpreadAnimTime; |
| | | //HostText anim time in milliseconds |
| | | private int mHostTextAnimTime; |
| | | |
| | | private PointF mStartMaxP, mEndMaxP; |
| | | private PointF mStartMinP, mEndMinP; |
| | | |
| | | public ParticleView(Context context, AttributeSet attrs) { |
| | | this(context, attrs, 0); |
| | | } |
| | | |
| | | public ParticleView(Context context, AttributeSet attrs, int defStyleAttr) { |
| | | super(context, attrs, defStyleAttr); |
| | | initView(attrs); |
| | | } |
| | | |
| | | private void initView(AttributeSet attrs) { |
| | | |
| | | TypedArray typeArray = getContext().obtainStyledAttributes(attrs, R.styleable.ParticleView); |
| | | mHostText = null == typeArray.getString(R.styleable.ParticleView_pv_host_text) ? "" : typeArray.getString(R.styleable.ParticleView_pv_host_text); |
| | | mParticleText = null == typeArray.getString(R.styleable.ParticleView_pv_particle_text) ? "" : typeArray.getString(R.styleable.ParticleView_pv_particle_text); |
| | | mParticleTextSize = (int) typeArray.getDimension(R.styleable.ParticleView_pv_particle_text_size, DEFAULT_MIN_TEXT_SIZE); |
| | | int hostTextSize = (int) typeArray.getDimension(R.styleable.ParticleView_pv_host_text_size, DEFAULT_MIN_TEXT_SIZE); |
| | | mBgColor = typeArray.getColor(R.styleable.ParticleView_pv_background_color, 0xFF0867AB); |
| | | mParticleColor = typeArray.getColor(R.styleable.ParticleView_pv_text_color, 0xFFCEF4FD); |
| | | mTextAnimTime = typeArray.getInt(R.styleable.ParticleView_pv_text_anim_time, DEFAULT_TEXT_ANIM_TIME); |
| | | mSpreadAnimTime = typeArray.getInt(R.styleable.ParticleView_pv_text_anim_time, DEFAULT_SPREAD_ANIM_TIME); |
| | | mHostTextAnimTime = typeArray.getInt(R.styleable.ParticleView_pv_text_anim_time, DEFAULT_HOST_TEXT_ANIM_TIME); |
| | | typeArray.recycle(); |
| | | |
| | | mHostTextPaint = new Paint(); |
| | | mHostTextPaint.setAntiAlias(true); |
| | | mHostTextPaint.setTextSize(hostTextSize); |
| | | mParticleTextPaint = new Paint(); |
| | | mParticleTextPaint.setAntiAlias(true); |
| | | mCirclePaint = new Paint(); |
| | | mCirclePaint.setAntiAlias(true); |
| | | mHostBgPaint = new Paint(); |
| | | mHostBgPaint.setAntiAlias(true); |
| | | mHostBgPaint.setTextSize(hostTextSize); |
| | | |
| | | mParticleTextPaint.setTextSize(mParticleTextSize); |
| | | mCirclePaint.setTextSize(mParticleTextSize); |
| | | |
| | | mParticleTextPaint.setColor(mBgColor); |
| | | mHostTextPaint.setColor(mBgColor); |
| | | mCirclePaint.setColor(mParticleColor); |
| | | mHostBgPaint.setColor(mParticleColor); |
| | | |
| | | } |
| | | |
| | | @Override |
| | | protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| | | super.onSizeChanged(w, h, oldw, oldh); |
| | | mWidth = w; |
| | | mHeight = h; |
| | | |
| | | mStartMinP = new PointF(mWidth / 2 - getTextWidth(mParticleText, mParticleTextPaint) / 2f - dip2px(4), mHeight / 2 + getTextHeight(mHostText, mHostTextPaint) / 2 - getTextHeight(mParticleText, mParticleTextPaint) / 0.7f); |
| | | mEndMinP = new PointF(mWidth / 2 + getTextWidth(mParticleText, mParticleTextPaint) / 2f + dip2px(10), mHeight / 2 + getTextHeight(mHostText, mHostTextPaint) / 2); |
| | | |
| | | for (int i = 0; i < ROW_NUM; i++) { |
| | | for (int j = 0; j < COLUMN_NUM; j++) { |
| | | mMinParticles[i][j] = new Particle(mStartMinP.x + (mEndMinP.x - mStartMinP.x) / COLUMN_NUM * j, mStartMinP.y + (mEndMinP.y - mStartMinP.y) / ROW_NUM * i, dip2px(0.8f)); |
| | | } |
| | | } |
| | | |
| | | mStartMaxP = new PointF(mWidth / 2 - DEFAULT_MAX_TEXT_SIZE, mHeight / 2 - DEFAULT_MAX_TEXT_SIZE); |
| | | mEndMaxP = new PointF(mWidth / 2 + DEFAULT_MAX_TEXT_SIZE, mHeight / 2 + DEFAULT_MAX_TEXT_SIZE); |
| | | |
| | | for (int i = 0; i < ROW_NUM; i++) { |
| | | for (int j = 0; j < COLUMN_NUM; j++) { |
| | | mParticles[i][j] = new Particle(mStartMaxP.x + (mEndMaxP.x - mStartMaxP.x) / COLUMN_NUM * j, mStartMaxP.y + (mEndMaxP.y - mStartMaxP.y) / ROW_NUM * i, getTextWidth(mHostText + mParticleText, mParticleTextPaint) / (COLUMN_NUM * 1.8f)); |
| | | } |
| | | } |
| | | |
| | | Shader linearGradient = new LinearGradient(mWidth / 2 - getTextWidth(mParticleText, mCirclePaint) / 2f, |
| | | mHeight / 2 - getTextHeight(mParticleText, mCirclePaint) / 2, |
| | | mWidth / 2 - getTextWidth(mParticleText, mCirclePaint) / 2, |
| | | mHeight / 2 + getTextHeight(mParticleText, mCirclePaint) / 2, |
| | | new int[]{mParticleColor, Color.argb(120, getR(mParticleColor), getG(mParticleColor), getB(mParticleColor))}, null, Shader.TileMode.CLAMP); |
| | | mCirclePaint.setShader(linearGradient); |
| | | } |
| | | |
| | | @Override |
| | | protected void onDraw(Canvas canvas) { |
| | | super.onDraw(canvas); |
| | | |
| | | if (mStatus == STATUS_PARTICLE_GATHER) { |
| | | for (int i = 0; i < ROW_NUM; i++) { |
| | | for (int j = 0; j < COLUMN_NUM; j++) { |
| | | canvas.drawCircle(mParticles[i][j].x, mParticles[i][j].y, mParticles[i][j].radius, mCirclePaint); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (mStatus == STATUS_TEXT_MOVING) { |
| | | canvas.drawText(mHostText, mHostTextX, mHeight / 2 + getTextHeight(mHostText, mHostBgPaint) / 2, mHostBgPaint); |
| | | canvas.drawRect(mHostTextX + mHostRectWidth, mHeight / 2 - getTextHeight(mHostText, mHostBgPaint) / 1.2f, mHostTextX + getTextWidth(mHostText, mHostTextPaint), mHeight / 2 + getTextHeight(mHostText, mHostBgPaint) / 1.2f, mHostTextPaint); |
| | | } |
| | | |
| | | if (mStatus == STATUS_PARTICLE_GATHER) { |
| | | canvas.drawRoundRect(new RectF(mWidth / 2 - mSpreadWidth, mStartMinP.y, mWidth / 2 + mSpreadWidth, mEndMinP.y), dip2px(2), dip2px(2), mHostBgPaint); |
| | | canvas.drawText(mParticleText, mWidth / 2 - getTextWidth(mParticleText, mParticleTextPaint) / 2, mStartMinP.y + (mEndMinP.y - mStartMinP.y) / 2 + getTextHeight(mParticleText, mParticleTextPaint) / 2, mParticleTextPaint); |
| | | } else if (mStatus == STATUS_TEXT_MOVING) { |
| | | canvas.drawRoundRect(new RectF(mParticleTextX - dip2px(4), mStartMinP.y, mParticleTextX + getTextWidth(mParticleText, mParticleTextPaint) + dip2px(4), mEndMinP.y), dip2px(2), dip2px(2), mHostBgPaint); |
| | | canvas.drawText(mParticleText, mParticleTextX, mStartMinP.y + (mEndMinP.y - mStartMinP.y) / 2 + getTextHeight(mParticleText, mParticleTextPaint) / 2, mParticleTextPaint); |
| | | } |
| | | |
| | | } |
| | | |
| | | private void startParticleAnim() { |
| | | |
| | | mStatus = STATUS_PARTICLE_GATHER; |
| | | |
| | | Collection<Animator> animList = new ArrayList<>(); |
| | | |
| | | ValueAnimator textAnim = ValueAnimator.ofInt(DEFAULT_MAX_TEXT_SIZE, mParticleTextSize); |
| | | textAnim.setDuration((int) (mTextAnimTime * 0.8f)); |
| | | textAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| | | @Override |
| | | public void onAnimationUpdate(ValueAnimator valueAnimator) { |
| | | int textSize = (int) valueAnimator.getAnimatedValue(); |
| | | mParticleTextPaint.setTextSize(textSize); |
| | | } |
| | | }); |
| | | animList.add(textAnim); |
| | | |
| | | for (int i = 0; i < ROW_NUM; i++) { |
| | | for (int j = 0; j < COLUMN_NUM; j++) { |
| | | final int tempI = i; |
| | | final int tempJ = j; |
| | | ValueAnimator animator = ValueAnimator.ofObject(new LineEvaluator(), mParticles[i][j], mMinParticles[i][j]); |
| | | animator.setDuration(mTextAnimTime + ((int) (mTextAnimTime * 0.02f)) * i + ((int) (mTextAnimTime * 0.03f)) * j); |
| | | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| | | @Override |
| | | public void onAnimationUpdate(ValueAnimator animation) { |
| | | mParticles[tempI][tempJ] = (Particle) animation.getAnimatedValue(); |
| | | if (tempI == ROW_NUM - 1 && tempJ == COLUMN_NUM - 1) { |
| | | invalidate(); |
| | | } |
| | | } |
| | | }); |
| | | animList.add(animator); |
| | | } |
| | | } |
| | | |
| | | AnimatorSet set = new AnimatorSet(); |
| | | set.playTogether(animList); |
| | | set.start(); |
| | | |
| | | set.addListener(new AnimListener() { |
| | | @Override |
| | | public void onAnimationEnd(Animator animation) { |
| | | startSpreadAnim(); |
| | | } |
| | | }); |
| | | |
| | | } |
| | | |
| | | private void startSpreadAnim() { |
| | | ValueAnimator animator = ValueAnimator.ofFloat(0, getTextWidth(mParticleText, mParticleTextPaint) / 2 + dip2px(4)); |
| | | animator.setDuration(mSpreadAnimTime); |
| | | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| | | @Override |
| | | public void onAnimationUpdate(ValueAnimator animation) { |
| | | mSpreadWidth = (float) animation.getAnimatedValue(); |
| | | invalidate(); |
| | | } |
| | | }); |
| | | animator.addListener(new AnimListener() { |
| | | @Override |
| | | public void onAnimationEnd(Animator animation) { |
| | | startHostTextAnim(); |
| | | } |
| | | }); |
| | | animator.start(); |
| | | } |
| | | |
| | | private void startHostTextAnim() { |
| | | mStatus = STATUS_TEXT_MOVING; |
| | | |
| | | Collection<Animator> animList = new ArrayList<>(); |
| | | |
| | | ValueAnimator particleTextXAnim = ValueAnimator.ofFloat(mStartMinP.x + dip2px(4), mWidth / 2 - (getTextWidth(mHostText, mHostTextPaint) + getTextWidth(mParticleText, mParticleTextPaint)) / 2 + getTextWidth(mHostText, mHostTextPaint)); |
| | | particleTextXAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| | | @Override |
| | | public void onAnimationUpdate(ValueAnimator animation) { |
| | | mParticleTextX = (float) animation.getAnimatedValue(); |
| | | } |
| | | }); |
| | | animList.add(particleTextXAnim); |
| | | |
| | | ValueAnimator animator = ValueAnimator.ofFloat(0, getTextWidth(mHostText, mHostTextPaint)); |
| | | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| | | @Override |
| | | public void onAnimationUpdate(ValueAnimator animation) { |
| | | mHostRectWidth = (float) animation.getAnimatedValue(); |
| | | } |
| | | }); |
| | | animList.add(animator); |
| | | |
| | | ValueAnimator hostTextXAnim = ValueAnimator.ofFloat(mStartMinP.x, mWidth / 2 - (getTextWidth(mHostText, mHostTextPaint) + getTextWidth(mParticleText, mParticleTextPaint) + dip2px(20)) / 2); |
| | | hostTextXAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| | | @Override |
| | | public void onAnimationUpdate(ValueAnimator animation) { |
| | | mHostTextX = (float) animation.getAnimatedValue(); |
| | | invalidate(); |
| | | } |
| | | }); |
| | | animList.add(hostTextXAnim); |
| | | |
| | | AnimatorSet set = new AnimatorSet(); |
| | | set.playTogether(animList); |
| | | set.setDuration(mHostTextAnimTime); |
| | | set.addListener(new AnimListener() { |
| | | @Override |
| | | public void onAnimationEnd(Animator animation) { |
| | | if (null != mParticleAnimListener) { |
| | | mParticleAnimListener.onAnimationEnd(); |
| | | } |
| | | } |
| | | }); |
| | | set.start(); |
| | | |
| | | } |
| | | |
| | | public void startAnim() { |
| | | post(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | startParticleAnim(); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | private abstract class AnimListener implements Animator.AnimatorListener { |
| | | @Override |
| | | public void onAnimationStart(Animator animation) { |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public void onAnimationCancel(Animator animation) { |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public void onAnimationRepeat(Animator animation) { |
| | | |
| | | } |
| | | } |
| | | |
| | | public void setOnParticleAnimListener(ParticleAnimListener particleAnimListener) { |
| | | mParticleAnimListener = particleAnimListener; |
| | | } |
| | | |
| | | public interface ParticleAnimListener { |
| | | void onAnimationEnd(); |
| | | } |
| | | |
| | | private int dip2px(float dipValue) { |
| | | final float scale = getContext().getResources().getDisplayMetrics().density; |
| | | return (int) (dipValue * scale + 0.5f); |
| | | } |
| | | |
| | | private int sp2px(float spValue) { |
| | | final float fontScale = getContext().getResources().getDisplayMetrics().scaledDensity; |
| | | return (int) (spValue * fontScale + 0.5f); |
| | | } |
| | | |
| | | private float getTextHeight(String text, Paint paint) { |
| | | Rect rect = new Rect(); |
| | | paint.getTextBounds(text, 0, text.length(), rect); |
| | | return rect.height() / 1.1f; |
| | | } |
| | | |
| | | private float getTextWidth(String text, Paint paint) { |
| | | return paint.measureText(text); |
| | | } |
| | | |
| | | private int getR(int color) { |
| | | int r = (color >> 16) & 0xFF; |
| | | return r; |
| | | } |
| | | |
| | | private int getG(int color) { |
| | | int g = (color >> 8) & 0xFF; |
| | | return g; |
| | | } |
| | | |
| | | private int getB(int color) { |
| | | int b = color & 0xFF; |
| | | return b; |
| | | } |
| | | } |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.view.particleview; |
| | | |
| | | |
| | | import android.animation.TypeEvaluator; |
| | | |
| | | public class LineEvaluator implements TypeEvaluator<Particle> { |
| | | |
| | | @Override |
| | | public Particle evaluate(float fraction, Particle startValue, Particle endValue) { |
| | | Particle particle = new Particle(); |
| | | particle.x = startValue.x + (endValue.x - startValue.x) * fraction; |
| | | particle.y = startValue.y + (endValue.y - startValue.y) * fraction; |
| | | particle.radius = startValue.radius + (endValue.radius - startValue.radius) * fraction; |
| | | return particle; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.view.particleview; |
| | | |
| | | public class Particle { |
| | | |
| | | public float x; |
| | | public float y; |
| | | public float radius; |
| | | |
| | | public Particle() { |
| | | } |
| | | |
| | | public Particle(float x, float y, float radius) { |
| | | this.x = x; |
| | | this.y = y; |
| | | this.radius = radius; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.shlb.comb.view; |
| | | |
| | | public class t { |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <!-- |
| | | Tencent is pleased to support the open source community by making QMUI_Android available. |
| | | |
| | | Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. |
| | | |
| | | Licensed under the MIT License (the "License"); you may not use this file except in |
| | | compliance with the License. You may obtain a copy of the License at |
| | | |
| | | http://opensource.org/licenses/MIT |
| | | |
| | | Unless required by applicable law or agreed to in writing, software distributed under the License is |
| | | distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, |
| | | either express or implied. See the License for the specific language governing permissions and |
| | | limitations under the License. |
| | | --> |
| | | |
| | | <selector xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <item android:color="@color/app_color_blue_2_pressed" android:state_pressed="true"/> |
| | | <item android:color="@color/app_color_blue_2"/> |
| | | </selector> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <selector xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <item android:color="#1890ff" android:state_selected="true" /> |
| | | <item android:color="#1890ff" android:state_pressed="true" /> |
| | | <item android:color="#757575" /> |
| | | </selector> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <selector xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <item android:color="#1890ff" android:state_selected="true" /> |
| | | <item android:color="#1890ff" android:state_pressed="true" /> |
| | | <item android:color="#333333" /> |
| | | </selector> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <vector xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:aapt="http://schemas.android.com/aapt" |
| | | android:width="108dp" |
| | | android:height="108dp" |
| | | android:viewportWidth="108" |
| | | android:viewportHeight="108"> |
| | | <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> |
| | | <aapt:attr name="android:fillColor"> |
| | | <gradient |
| | | android:endX="85.84757" |
| | | android:endY="92.4963" |
| | | android:startX="42.9492" |
| | | android:startY="49.59793" |
| | | android:type="linear"> |
| | | <item |
| | | android:color="#44000000" |
| | | android:offset="0.0" /> |
| | | <item |
| | | android:color="#00000000" |
| | | android:offset="1.0" /> |
| | | </gradient> |
| | | </aapt:attr> |
| | | </path> |
| | | <path |
| | | android:fillColor="#FFFFFF" |
| | | android:fillType="nonZero" |
| | | android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" |
| | | android:strokeWidth="1" |
| | | android:strokeColor="#00000000" /> |
| | | </vector> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <shape xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <solid android:color="#FFFFFF"/> |
| | | <corners android:radius="6dp"/> |
| | | <stroke android:width="1dp" android:color="#E0E0E0"/> |
| | | </shape> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <shape xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <solid android:color="#F44336"/> |
| | | <corners android:radius="6dp"/> |
| | | </shape> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <shape xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <solid android:color="#4CAF50"/> |
| | | <corners android:radius="6dp"/> |
| | | </shape> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <shape xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <solid android:color="#9E9E9E"/> |
| | | <corners android:radius="6dp"/> |
| | | </shape> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <shape xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <solid android:color="@color/white"/> |
| | | <corners android:radius="12dp"/> |
| | | </shape> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <shape xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:shape="rectangle"> |
| | | <gradient |
| | | android:angle="135" |
| | | android:startColor="#1890ff" |
| | | android:endColor="#0050b3" |
| | | android:type="linear" /> |
| | | </shape> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <ripple xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:color="#201890ff"> |
| | | <item> |
| | | <selector> |
| | | <item android:state_selected="true"> |
| | | <shape android:shape="rectangle"> |
| | | <solid android:color="#E6F7FF" /> |
| | | <corners android:radius="8dp" /> |
| | | </shape> |
| | | </item> |
| | | <item android:state_pressed="true"> |
| | | <shape android:shape="rectangle"> |
| | | <solid android:color="#F5F5F5" /> |
| | | <corners android:radius="8dp" /> |
| | | </shape> |
| | | </item> |
| | | <item> |
| | | <shape android:shape="rectangle"> |
| | | <solid android:color="@android:color/transparent" /> |
| | | <corners android:radius="8dp" /> |
| | | </shape> |
| | | </item> |
| | | </selector> |
| | | </item> |
| | | </ripple> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <shape xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <solid android:color="@color/white"/> |
| | | <corners android:radius="4dp"/> |
| | | <stroke android:width="1dp" android:color="@color/gray_line"/> |
| | | </shape> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <shape xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <solid android:color="#F5F6FA"/> |
| | | <corners android:radius="8dp"/> |
| | | <stroke android:width="0dp" android:color="@color/gray_line"/> |
| | | </shape> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <shape xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:shape="rectangle" |
| | | > |
| | | <solid |
| | | android:color="@color/white" |
| | | /> |
| | | <corners |
| | | android:radius="3dp" |
| | | /> |
| | | |
| | | </shape> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <selector xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <item android:state_pressed="true"> |
| | | <shape> |
| | | <solid android:color="#D0D0D0"/> |
| | | <corners android:radius="4dp"/> |
| | | <stroke android:width="1dp" android:color="#A0A0A0"/> |
| | | </shape> |
| | | </item> |
| | | <item> |
| | | <shape> |
| | | <solid android:color="#E0E0E0"/> |
| | | <corners android:radius="4dp"/> |
| | | <stroke android:width="1dp" android:color="#B0B0B0"/> |
| | | </shape> |
| | | </item> |
| | | </selector> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <vector xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:width="108dp" |
| | | android:height="108dp" |
| | | android:viewportWidth="108" |
| | | android:viewportHeight="108"> |
| | | <path |
| | | android:fillColor="#3DDC84" |
| | | android:pathData="M0,0h108v108h-108z" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M9,0L9,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,0L19,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M29,0L29,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M39,0L39,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M49,0L49,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M59,0L59,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M69,0L69,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M79,0L79,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M89,0L89,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M99,0L99,108" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,9L108,9" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,19L108,19" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,29L108,29" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,39L108,39" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,49L108,49" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,59L108,59" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,69L108,69" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,79L108,79" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,89L108,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M0,99L108,99" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,29L89,29" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,39L89,39" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,49L89,49" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,59L89,59" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,69L89,69" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M19,79L89,79" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M29,19L29,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M39,19L39,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M49,19L49,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M59,19L59,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M69,19L69,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | <path |
| | | android:fillColor="#00000000" |
| | | android:pathData="M79,19L79,89" |
| | | android:strokeWidth="0.8" |
| | | android:strokeColor="#33FFFFFF" /> |
| | | </vector> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <shape xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:shape="rectangle" |
| | | > |
| | | |
| | | <solid |
| | | android:color="@color/colorPrimary" |
| | | /> |
| | | <corners |
| | | android:radius="1dp" |
| | | /> |
| | | |
| | | </shape> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <!-- |
| | | Tencent is pleased to support the open source community by making QMUI_Android available. |
| | | |
| | | Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. |
| | | |
| | | Licensed under the MIT License (the "License"); you may not use this file except in |
| | | compliance with the License. You may obtain a copy of the License at |
| | | |
| | | http://opensource.org/licenses/MIT |
| | | |
| | | Unless required by applicable law or agreed to in writing, software distributed under the License is |
| | | distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, |
| | | either express or implied. See the License for the specific language governing permissions and |
| | | limitations under the License. |
| | | --> |
| | | |
| | | <shape xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:shape="rectangle"> |
| | | <!-- rectangle 表示为ç©å½¢ --> |
| | | |
| | | <!-- å¡«å
çé¢è² --> |
| | | <solid android:color="#FFEBF9FF"/> |
| | | |
| | | <!-- è¾¹æ¡çé¢è²åç²ç» --> |
| | | <stroke |
| | | android:width="1px" |
| | | android:color="@color/app_color_blue_2"/> |
| | | |
| | | <!-- android:radius åè§çåå¾ --> |
| | | <corners android:radius="4dp"/> |
| | | |
| | | </shape> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <!-- |
| | | Tencent is pleased to support the open source community by making QMUI_Android available. |
| | | |
| | | Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. |
| | | |
| | | Licensed under the MIT License (the "License"); you may not use this file except in |
| | | compliance with the License. You may obtain a copy of the License at |
| | | |
| | | http://opensource.org/licenses/MIT |
| | | |
| | | Unless required by applicable law or agreed to in writing, software distributed under the License is |
| | | distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, |
| | | either express or implied. See the License for the specific language governing permissions and |
| | | limitations under the License. |
| | | --> |
| | | |
| | | <shape xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:shape="rectangle"> |
| | | <!-- rectangle 表示为ç©å½¢ --> |
| | | |
| | | <!-- å¡«å
çé¢è² --> |
| | | <solid android:color="#7FEBF9FF"/> |
| | | |
| | | <!-- è¾¹æ¡çé¢è²åç²ç» --> |
| | | <stroke |
| | | android:width="1px" |
| | | android:color="@color/app_color_blue_2_pressed"/> |
| | | |
| | | <!-- android:radius åè§çåå¾ --> |
| | | <corners android:radius="4dp"/> |
| | | |
| | | </shape> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <!-- |
| | | Tencent is pleased to support the open source community by making QMUI_Android available. |
| | | |
| | | Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. |
| | | |
| | | Licensed under the MIT License (the "License"); you may not use this file except in |
| | | compliance with the License. You may obtain a copy of the License at |
| | | |
| | | http://opensource.org/licenses/MIT |
| | | |
| | | Unless required by applicable law or agreed to in writing, software distributed under the License is |
| | | distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, |
| | | either express or implied. See the License for the specific language governing permissions and |
| | | limitations under the License. |
| | | --> |
| | | |
| | | <selector xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <item android:drawable="@drawable/radius_button_bg_pressed" android:state_pressed="true"/> |
| | | <item android:drawable="@drawable/radius_button_bg"/> |
| | | </selector> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <LinearLayout 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:background="@color/gray_bg" |
| | | android:orientation="vertical"> |
| | | |
| | | <include layout="@layout/include_head" /> |
| | | |
| | | <ScrollView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:padding="15dp"> |
| | | |
| | | <!-- Advanced Settings Section --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:background="@drawable/bg_card" |
| | | android:orientation="vertical" |
| | | android:padding="20dp"> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginBottom="20dp" |
| | | android:text="é«çº§è®¾ç½®" |
| | | android:textColor="@color/base_text" |
| | | android:textSize="18sp" |
| | | android:textStyle="bold" /> |
| | | |
| | | <!-- Mainboard Version Row --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:layout_marginBottom="25dp"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:layout_marginBottom="10dp"> |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="主æ¿çæ¬" |
| | | android:textColor="@color/base_text" |
| | | android:textSize="15sp" |
| | | android:textStyle="bold"/> |
| | | <TextView |
| | | android:id="@+id/tv_mainboard_status" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="" |
| | | android:textSize="14sp" |
| | | android:layout_marginLeft="10dp"/> |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal"> |
| | | |
| | | <EditText |
| | | android:id="@+id/et_mainboard_version" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| | | android:background="@drawable/bg_input_field" |
| | | android:paddingStart="10dp" |
| | | android:paddingEnd="10dp" |
| | | android:hint="10-10-1961-01" |
| | | android:enabled="false" |
| | | android:textColor="@color/base_text" |
| | | android:textSize="14sp" |
| | | android:layout_marginRight="10dp"/> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_mainboard_read" |
| | | android:layout_width="80dp" |
| | | android:layout_height="45dp" |
| | | android:text="读å" |
| | | android:textColor="@color/white" |
| | | android:textSize="14sp" |
| | | app:qmui_backgroundColor="@color/base_color" |
| | | app:qmui_radius="8dp"/> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:background="@color/gray_line" |
| | | android:layout_marginBottom="20dp"/> |
| | | |
| | | <!-- Display Version Row --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:layout_marginBottom="25dp"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:layout_marginBottom="10dp"> |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="æ¾ç¤ºå±çæ¬" |
| | | android:textColor="@color/base_text" |
| | | android:textSize="15sp" |
| | | android:textStyle="bold"/> |
| | | <TextView |
| | | android:id="@+id/tv_sensor_status" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="" |
| | | android:textSize="14sp" |
| | | android:layout_marginLeft="10dp"/> |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal"> |
| | | |
| | | <EditText |
| | | android:id="@+id/et_display_version" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| | | android:background="@drawable/bg_input_field" |
| | | android:paddingStart="10dp" |
| | | android:paddingEnd="10dp" |
| | | android:hint="10-10-1962-01" |
| | | android:enabled="false" |
| | | android:textColor="@android:color/black" |
| | | android:textSize="14sp" |
| | | android:layout_marginRight="10dp"/> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_display_read" |
| | | android:layout_width="80dp" |
| | | android:layout_height="45dp" |
| | | android:text="读å" |
| | | android:textColor="@color/white" |
| | | android:textSize="14sp" |
| | | app:qmui_backgroundColor="@color/base_color" |
| | | app:qmui_radius="8dp"/> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:background="@color/gray_line" |
| | | android:layout_marginBottom="20dp"/> |
| | | |
| | | <!-- Addressing Buttons --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center"> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_start_addressing" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_weight="1" |
| | | android:text="å¼å§ç¼å" |
| | | android:textColor="@color/white" |
| | | android:textSize="14sp" |
| | | app:qmui_backgroundColor="@color/base_color" |
| | | app:qmui_radius="8dp" |
| | | android:layout_marginRight="15dp"/> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_end_addressing" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_weight="1" |
| | | android:text="ç»æç¼å" |
| | | android:textColor="@color/white" |
| | | android:textSize="14sp" |
| | | app:qmui_backgroundColor="@color/base_color" |
| | | app:qmui_radius="8dp"/> |
| | | </LinearLayout> |
| | | |
| | | </LinearLayout> |
| | | |
| | | <!-- Log Display --> |
| | | <androidx.cardview.widget.CardView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | app:cardCornerRadius="12dp" |
| | | app:cardElevation="2dp" |
| | | app:cardBackgroundColor="@color/white" |
| | | android:layout_marginTop="16dp" |
| | | android:layout_marginBottom="24dp"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical"> |
| | | |
| | | <!-- Card Header --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center_vertical" |
| | | android:padding="16dp" |
| | | android:background="#FAFAFA"> |
| | | |
| | | <View |
| | | android:layout_width="4dp" |
| | | android:layout_height="16dp" |
| | | android:background="@color/base_color" |
| | | android:layout_marginEnd="8dp"/> |
| | | |
| | | <TextView |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_weight="1" |
| | | android:text="æä½è®°å½" |
| | | android:textSize="16sp" |
| | | android:textStyle="bold" |
| | | android:textColor="#333333" /> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_clear_log" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="32dp" |
| | | android:text="æ¸
空" |
| | | android:textColor="@color/white" |
| | | android:textSize="12sp" |
| | | android:paddingStart="12dp" |
| | | android:paddingEnd="12dp" |
| | | app:qmui_backgroundColor="#FF5252" |
| | | app:qmui_radius="4dp"/> |
| | | </LinearLayout> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:background="#EEEEEE"/> |
| | | |
| | | <ScrollView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="200dp" |
| | | android:padding="12dp" |
| | | android:background="#FAFAFA"> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_log" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:text="çå¾
æä½..." |
| | | android:textColor="#333333" |
| | | android:textSize="12sp" |
| | | android:fontFamily="monospace" |
| | | android:lineSpacingExtra="4dp"/> |
| | | </ScrollView> |
| | | |
| | | </LinearLayout> |
| | | </androidx.cardview.widget.CardView> |
| | | |
| | | </LinearLayout> |
| | | </ScrollView> |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?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" |
| | | xmlns:titlebar="http://schemas.android.com/apk/res-auto" |
| | | android:id="@+id/ll_root" |
| | | android:orientation="vertical"> |
| | | |
| | | <include |
| | | android:id="@+id/include_head" |
| | | layout="@layout/include_head"/> |
| | | |
| | | <RelativeLayout |
| | | android:id="@+id/container" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent"> |
| | | |
| | | |
| | | <!--å 载失败--> |
| | | <LinearLayout |
| | | android:id="@+id/ll_error_refresh" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:gravity="center" |
| | | android:orientation="vertical" |
| | | android:visibility="gone"> |
| | | |
| | | <ImageView |
| | | android:id="@+id/img_err" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:src="@mipmap/load_err" /> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginTop="15dp" |
| | | android:text="å 载失败,ç¹å»éè¯" |
| | | android:textSize="15sp" /> |
| | | </LinearLayout> |
| | | |
| | | <!--å è½½ä¸..--> |
| | | <ViewStub |
| | | android:id="@+id/vs_loading" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_centerInParent="true" |
| | | android:inflatedId="@+id/panel_import" |
| | | android:layout="@layout/layout_loading_view" /> |
| | | |
| | | </RelativeLayout> |
| | | |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:app="http://schemas.android.com/apk/res-auto" |
| | | xmlns:tools="http://schemas.android.com/tools" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:background="@color/qmui_config_color_background" |
| | | android:orientation="vertical" |
| | | tools:context=".activity.HomeActivity"> |
| | | <include layout="@layout/include_head"/> |
| | | <com.qmuiteam.qmui.widget.QMUIEmptyView |
| | | android:id="@+id/emptyView" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | /> |
| | | |
| | | <com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView |
| | | android:id="@+id/groupListView" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content"/> |
| | | </LinearLayout > |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?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"> |
| | | |
| | | <include layout="@layout/include_head" /> |
| | | |
| | | <ScrollView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:background="#F5F5F5" |
| | | android:scrollbars="none"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:paddingBottom="24dp"> |
| | | |
| | | <!-- ç¡¬ä»¶ä¿¡æ¯ --> |
| | | <com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView |
| | | android:id="@+id/group_hardware" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginTop="16dp" /> |
| | | |
| | | <!-- ä½åèèç --> |
| | | <com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView |
| | | android:id="@+id/group_ble" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginTop="8dp" /> |
| | | |
| | | <!-- èçé³é¢ --> |
| | | <com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView |
| | | android:id="@+id/group_audio" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginTop="8dp" /> |
| | | |
| | | <!-- å±å¹ä¿¡æ¯ --> |
| | | <com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView |
| | | android:id="@+id/group_screen" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginTop="8dp" /> |
| | | |
| | | </LinearLayout> |
| | | </ScrollView> |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:app="http://schemas.android.com/apk/res-auto" |
| | | xmlns:tools="http://schemas.android.com/tools" |
| | | android:id="@+id/drawer_layout" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:fitsSystemWindows="false" |
| | | tools:context=".activity.HomeActivity"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:fitsSystemWindows="false" |
| | | android:orientation="vertical"> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="25dp" |
| | | android:background="@color/base_color" /> |
| | | |
| | | <include layout="@layout/include_head_home" /> |
| | | |
| | | <FrameLayout |
| | | android:id="@+id/fragment_container" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="0dp" |
| | | android:layout_weight="1" /> |
| | | |
| | | <TextView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginTop="@dimen/dimension_10" |
| | | android:layout_marginBottom="@dimen/dimension_20" |
| | | android:gravity="center" |
| | | android:text="䏿µ·å
°å®ä¼ æç§æè¡ä»½æéå
¬å¸" |
| | | android:textColor="@color/dark_hint" /> |
| | | |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:id="@+id/nav_view" |
| | | android:layout_width="300dp" |
| | | android:layout_height="match_parent" |
| | | android:layout_gravity="start" |
| | | android:background="@color/white" |
| | | android:clickable="true" |
| | | android:focusable="true" |
| | | android:orientation="vertical"> |
| | | |
| | | <!-- æ½å±å¤´é¨ (å·¦å³ç»æï¼ä½¿ç¨ä¸»é¢è²) --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="160dp" |
| | | android:background="@color/base_color" |
| | | android:gravity="bottom" |
| | | android:orientation="horizontal" |
| | | android:padding="20dp"> |
| | | |
| | | <ImageView |
| | | android:layout_width="60dp" |
| | | android:layout_height="60dp" |
| | | android:src="@mipmap/ic_launcher" /> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="60dp" |
| | | android:layout_marginLeft="15dp" |
| | | android:gravity="center_vertical" |
| | | android:orientation="vertical"> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="COMB" |
| | | android:textColor="@color/white" |
| | | android:textSize="22sp" |
| | | android:textStyle="bold" /> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="Shanghai Lanbao Sensing Technology Co., Ltd." |
| | | android:textColor="@color/white" |
| | | android:textSize="11sp" |
| | | android:letterSpacing="0.1" /> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | |
| | | <!-- èåå表 --> |
| | | <ScrollView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="0dp" |
| | | android:layout_weight="1"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:paddingTop="5dp"> |
| | | |
| | | <!-- 宿¶çæ§ --> |
| | | <LinearLayout |
| | | android:id="@+id/menu_monitor" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="48dp" |
| | | android:gravity="center_vertical" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="20dp" |
| | | android:paddingRight="20dp"> |
| | | |
| | | <ImageView |
| | | android:layout_width="22dp" |
| | | android:layout_height="22dp" |
| | | android:src="@mipmap/icon_record" |
| | | app:tint="@color/drawer_item_text_gray" /> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginLeft="25dp" |
| | | android:text="宿¶çæ§" |
| | | android:textColor="@color/drawer_item_text_gray" |
| | | android:textSize="15sp" /> |
| | | </LinearLayout> |
| | | |
| | | <!-- èçè®¾å¤ --> |
| | | <LinearLayout |
| | | android:id="@+id/menu_devices" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="48dp" |
| | | android:gravity="center_vertical" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="20dp" |
| | | android:paddingRight="20dp"> |
| | | |
| | | <ImageView |
| | | android:layout_width="22dp" |
| | | android:layout_height="22dp" |
| | | android:src="@mipmap/ic_bluetooth" |
| | | app:tint="@color/drawer_item_text_gray" /> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginLeft="25dp" |
| | | android:text="èç设å¤" |
| | | android:textColor="@color/drawer_item_text_gray" |
| | | android:textSize="15sp" /> |
| | | </LinearLayout> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:layout_marginTop="5dp" |
| | | android:layout_marginBottom="5dp" |
| | | android:background="@color/drawer_divider" /> |
| | | |
| | | <!-- åæ°è®¾å® --> |
| | | <LinearLayout |
| | | android:id="@+id/menu_settings" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="48dp" |
| | | android:gravity="center_vertical" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="20dp" |
| | | android:paddingRight="20dp"> |
| | | |
| | | <ImageView |
| | | android:layout_width="22dp" |
| | | android:layout_height="22dp" |
| | | android:src="@mipmap/icon_topbar_overflow" |
| | | app:tint="@color/drawer_item_text_gray" /> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginLeft="25dp" |
| | | android:text="åæ°è®¾å®" |
| | | android:textColor="@color/drawer_item_text_gray" |
| | | android:textSize="15sp" /> |
| | | </LinearLayout> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:layout_marginTop="5dp" |
| | | android:layout_marginBottom="5dp" |
| | | android:background="@color/drawer_divider" /> |
| | | |
| | | <!-- å½åè®¾å¤ --> |
| | | <LinearLayout |
| | | android:id="@+id/menu_current_device" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="48dp" |
| | | android:gravity="center_vertical" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="20dp" |
| | | android:paddingRight="20dp"> |
| | | |
| | | <ImageView |
| | | android:layout_width="22dp" |
| | | android:layout_height="22dp" |
| | | android:src="@mipmap/icon_topbar_about" |
| | | app:tint="@color/drawer_item_text_gray" /> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginLeft="25dp" |
| | | android:text="å½å设å¤" |
| | | android:textColor="@color/drawer_item_text_gray" |
| | | android:textSize="15sp" /> |
| | | </LinearLayout> |
| | | |
| | | <!-- é«çº§è®¾ç½® --> |
| | | <LinearLayout |
| | | android:id="@+id/menu_advanced_settings" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="48dp" |
| | | android:gravity="center_vertical" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="20dp" |
| | | android:paddingRight="20dp"> |
| | | |
| | | <ImageView |
| | | android:layout_width="22dp" |
| | | android:layout_height="22dp" |
| | | android:src="@mipmap/icon_topbar_overflow" |
| | | app:tint="@color/drawer_item_text_gray" /> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginLeft="25dp" |
| | | android:text="é«çº§è®¾ç½®" |
| | | android:textColor="@color/drawer_item_text_gray" |
| | | android:textSize="15sp" /> |
| | | </LinearLayout> |
| | | |
| | | </LinearLayout> |
| | | </ScrollView> |
| | | |
| | | <TextView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginBottom="20dp" |
| | | android:gravity="center" |
| | | android:id="@+id/tv_version" |
| | | android:text="1.0.0" |
| | | android:textColor="@color/dark_hint" |
| | | android:textSize="11sp" |
| | | android:letterSpacing="0.1" /> |
| | | |
| | | </LinearLayout> |
| | | |
| | | </androidx.drawerlayout.widget.DrawerLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <LinearLayout 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:background="@color/gray_bg" |
| | | android:orientation="vertical"> |
| | | |
| | | <include layout="@layout/include_head" /> |
| | | |
| | | <ScrollView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:padding="15dp"> |
| | | |
| | | <!-- Monitoring Details Section --> |
| | | <androidx.cardview.widget.CardView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | app:cardCornerRadius="12dp" |
| | | app:cardElevation="2dp" |
| | | app:cardBackgroundColor="@color/white" |
| | | android:layout_marginBottom="24dp"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical"> |
| | | |
| | | <!-- Card Header --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center_vertical" |
| | | android:padding="16dp" |
| | | android:background="#FAFAFA"> |
| | | |
| | | <View |
| | | android:layout_width="4dp" |
| | | android:layout_height="16dp" |
| | | android:background="@color/base_color" |
| | | android:layout_marginEnd="8dp"/> |
| | | |
| | | <TextView |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_weight="1" |
| | | android:text="èªæ£çæµ" |
| | | android:textSize="16sp" |
| | | android:textStyle="bold" |
| | | android:textColor="#333333" /> |
| | | </LinearLayout> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:background="#EEEEEE"/> |
| | | |
| | | <!-- Card Content --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:padding="16dp"> |
| | | |
| | | <HorizontalScrollView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:scrollbars="none"> |
| | | |
| | | <androidx.recyclerview.widget.RecyclerView |
| | | android:id="@+id/rv_grid" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" /> |
| | | </HorizontalScrollView> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:background="#EEEEEE" |
| | | android:layout_marginTop="16dp" |
| | | android:layout_marginBottom="12dp"/> |
| | | |
| | | <!-- Legend --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center_vertical" |
| | | android:paddingStart="4dp"> |
| | | |
| | | <View |
| | | android:layout_width="12dp" |
| | | android:layout_height="12dp" |
| | | android:background="@drawable/bg_box_full" |
| | | android:layout_marginEnd="6dp"/> |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="èªæ£æå" |
| | | android:textSize="12sp" |
| | | android:textColor="#666666" |
| | | android:layout_marginEnd="16dp"/> |
| | | |
| | | <View |
| | | android:layout_width="12dp" |
| | | android:layout_height="12dp" |
| | | android:background="@drawable/bg_box_error" |
| | | android:layout_marginEnd="6dp"/> |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="èªæ£å¤±è´¥" |
| | | android:textSize="12sp" |
| | | android:textColor="#666666" |
| | | android:layout_marginEnd="16dp"/> |
| | | </LinearLayout> |
| | | |
| | | <!-- Start/End Check Buttons --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:layout_marginTop="20dp"> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_start_check" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| | | android:text="å¼å§èªæ£" |
| | | android:textColor="@color/white" |
| | | android:textSize="14sp" |
| | | app:qmui_backgroundColor="@color/base_color" |
| | | app:qmui_radius="8dp" |
| | | android:layout_marginRight="10dp"/> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_end_check" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| | | android:text="ç»æèªæ£" |
| | | android:textColor="@color/base_color" |
| | | android:textSize="14sp" |
| | | app:qmui_backgroundColor="@color/white" |
| | | app:qmui_borderColor="@color/base_color" |
| | | app:qmui_borderWidth="1dp" |
| | | app:qmui_radius="8dp" |
| | | android:layout_marginLeft="10dp"/> |
| | | </LinearLayout> |
| | | |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | </androidx.cardview.widget.CardView> |
| | | |
| | | <!-- Customer Use Section --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:background="@drawable/bg_card" |
| | | android:orientation="vertical" |
| | | android:padding="20dp"> |
| | | |
| | | <!-- Threshold Row --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:layout_marginBottom="25dp"> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="éå¼" |
| | | android:textColor="@color/base_text" |
| | | android:textSize="15sp" |
| | | android:textStyle="bold" |
| | | android:layout_marginBottom="10dp"/> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal"> |
| | | |
| | | <Spinner |
| | | android:id="@+id/sp_threshold_mode" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1.5" |
| | | android:entries="@array/threshold_modes" |
| | | android:background="@drawable/bg_input_field" |
| | | android:paddingStart="10dp" |
| | | android:layout_marginRight="10dp" /> |
| | | |
| | | <EditText |
| | | android:id="@+id/et_threshold_value" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| | | android:background="@drawable/bg_input_field" |
| | | android:gravity="center" |
| | | android:hint="1-30" |
| | | android:inputType="number" |
| | | android:textSize="14sp"/> |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginTop="10dp" |
| | | android:orientation="horizontal"> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_threshold_read" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_weight="1" |
| | | android:text="读å" |
| | | android:textColor="@color/white" |
| | | android:textSize="14sp" |
| | | app:qmui_backgroundColor="@color/base_color" |
| | | app:qmui_radius="8dp" |
| | | android:layout_marginRight="10dp"/> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_threshold_write" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_weight="1" |
| | | android:text="åå
¥" |
| | | android:textColor="@color/white" |
| | | android:textSize="14sp" |
| | | app:qmui_backgroundColor="@color/base_color_s" |
| | | app:qmui_radius="8dp"/> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:background="@color/gray_line" |
| | | android:layout_marginBottom="20dp"/> |
| | | |
| | | <!-- Sensitivity Row --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:layout_marginBottom="25dp"> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="çµæåº¦" |
| | | android:textColor="@color/base_text" |
| | | android:textSize="15sp" |
| | | android:textStyle="bold" |
| | | android:layout_marginBottom="10dp"/> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal"> |
| | | |
| | | <Spinner |
| | | android:id="@+id/sp_sensitivity_mode" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1.5" |
| | | android:entries="@array/sensitivity_modes" |
| | | android:background="@drawable/bg_input_field" |
| | | android:paddingStart="10dp" |
| | | android:layout_marginRight="10dp" /> |
| | | |
| | | <EditText |
| | | android:id="@+id/et_sensitivity_value" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| | | android:background="@drawable/bg_input_field" |
| | | android:gravity="center" |
| | | android:hint="1-30" |
| | | android:inputType="number" |
| | | android:textSize="14sp"/> |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginTop="10dp" |
| | | android:orientation="horizontal"> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_sensitivity_read" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_weight="1" |
| | | android:text="读å" |
| | | android:textColor="@color/white" |
| | | android:textSize="14sp" |
| | | app:qmui_backgroundColor="@color/base_color" |
| | | app:qmui_radius="8dp" |
| | | android:layout_marginRight="10dp"/> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_sensitivity_write" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_weight="1" |
| | | android:text="åå
¥" |
| | | android:textColor="@color/white" |
| | | android:textSize="14sp" |
| | | app:qmui_backgroundColor="@color/base_color_s" |
| | | app:qmui_radius="8dp"/> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:background="@color/gray_line" |
| | | android:layout_marginBottom="20dp"/> |
| | | |
| | | <!-- Control Buttons --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical"> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="æä½" |
| | | android:textColor="@color/base_text" |
| | | android:textSize="15sp" |
| | | android:textStyle="bold" |
| | | android:layout_marginBottom="10dp"/> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal"> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_reset_mainboard" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| | | android:text="主æ¿å¤ä½" |
| | | android:textColor="@color/white" |
| | | android:textSize="14sp" |
| | | app:qmui_backgroundColor="@color/orange" |
| | | app:qmui_radius="8dp" |
| | | android:layout_marginRight="10dp"/> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_reset_sensor" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| | | android:text="ä¼ æå¤ä½" |
| | | android:textColor="@color/white" |
| | | android:textSize="14sp" |
| | | app:qmui_backgroundColor="@color/orange" |
| | | app:qmui_radius="8dp" |
| | | android:layout_marginLeft="10dp"/> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | |
| | | <!-- Log Display --> |
| | | <androidx.cardview.widget.CardView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | app:cardCornerRadius="12dp" |
| | | app:cardElevation="2dp" |
| | | app:cardBackgroundColor="@color/white" |
| | | android:layout_marginTop="16dp" |
| | | android:layout_marginBottom="24dp"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical"> |
| | | |
| | | <!-- Card Header --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center_vertical" |
| | | android:padding="16dp" |
| | | android:background="#FAFAFA"> |
| | | |
| | | <View |
| | | android:layout_width="4dp" |
| | | android:layout_height="16dp" |
| | | android:background="@color/base_color" |
| | | android:layout_marginEnd="8dp"/> |
| | | |
| | | <TextView |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_weight="1" |
| | | android:text="æ¥å¿" |
| | | android:textSize="16sp" |
| | | android:textStyle="bold" |
| | | android:textColor="#333333" /> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_clear_log" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="28dp" |
| | | android:text="æ¸
空" |
| | | android:textColor="@color/white" |
| | | android:textSize="12sp" |
| | | android:paddingStart="12dp" |
| | | android:paddingEnd="12dp" |
| | | app:qmui_backgroundColor="#FF5252" |
| | | app:qmui_radius="4dp"/> |
| | | </LinearLayout> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:background="#EEEEEE"/> |
| | | |
| | | <ScrollView |
| | | android:id="@+id/sv_log" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="200dp" |
| | | android:padding="12dp" |
| | | android:scrollbars="vertical" |
| | | android:background="#FAFAFA"> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_log" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:text="æ¥å¿è®°å½:\n" |
| | | android:textSize="12sp" |
| | | android:fontFamily="monospace" |
| | | android:textColor="#333333" |
| | | android:lineSpacingExtra="4dp"/> |
| | | </ScrollView> |
| | | </LinearLayout> |
| | | </androidx.cardview.widget.CardView> |
| | | |
| | | </LinearLayout> |
| | | </ScrollView> |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:pv="http://schemas.android.com/apk/res-auto" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:orientation="vertical"> |
| | | |
| | | <com.shlb.comb.view.ParticleView |
| | | android:id="@+id/ptview" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:background="@color/colorPrimary" |
| | | pv:pv_background_color="@color/colorPrimary" |
| | | pv:pv_host_text="COMB" |
| | | pv:pv_host_text_anim_time="600" |
| | | pv:pv_particle_text="ç§»å¨å¹³å°" |
| | | pv:pv_particle_text_size="14sp" |
| | | pv:pv_spread_anim_time="1000" |
| | | pv:pv_text_anim_time="600" |
| | | pv:pv_text_color="#FFF" /> |
| | | <TextView |
| | | android:textColor="@color/white" |
| | | android:layout_marginBottom="@dimen/dimension_20" |
| | | android:layout_centerHorizontal="true" |
| | | android:layout_alignParentBottom="true" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="䏿µ·å
°å®ä¼ æç§æè¡ä»½æéå
¬å¸" |
| | | /> |
| | | |
| | | </RelativeLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?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:background="@color/qmui_config_color_background" |
| | | android:orientation="vertical"> |
| | | |
| | | <include layout="@layout/include_head"/> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:orientation="vertical" |
| | | android:padding="16dp"> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_status" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="ç¶æ: æªè¿æ¥" |
| | | android:textSize="16sp" |
| | | android:textStyle="bold" |
| | | android:textColor="@android:color/black"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_selected_service" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="å½åéæ©æå¡: æ " |
| | | android:layout_marginTop="10dp" |
| | | android:textColor="@color/base_color"/> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="å¯ç¨æå¡å表:" |
| | | android:layout_marginTop="10dp"/> |
| | | |
| | | <ScrollView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="250dp" |
| | | android:layout_marginTop="5dp" |
| | | android:background="@drawable/bg_round"> |
| | | <com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView |
| | | android:id="@+id/groupListView" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content"/> |
| | | </ScrollView> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center_vertical" |
| | | android:layout_marginTop="10dp"> |
| | | <EditText |
| | | android:id="@+id/et_command" |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_weight="1" |
| | | android:background="@drawable/bg_edittext" |
| | | android:padding="10dp" |
| | | android:textSize="14sp" |
| | | android:hint="请è¾å
¥Hexæä»¤ (å¦ A55A...)"/> |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_send" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:padding="10dp" |
| | | android:text="åé" |
| | | android:textColor="@color/white" |
| | | android:layout_marginLeft="10dp"/> |
| | | </LinearLayout> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="æ¥å¿:" |
| | | android:layout_marginTop="10dp"/> |
| | | |
| | | <ScrollView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="150dp" |
| | | android:background="#FAFAFA" |
| | | android:layout_marginTop="5dp"> |
| | | <TextView |
| | | android:id="@+id/tv_log" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:padding="5dp" |
| | | android:textSize="12sp" |
| | | android:fontFamily="monospace"/> |
| | | </ScrollView> |
| | | </LinearLayout> |
| | | |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:app="http://schemas.android.com/apk/res-auto" |
| | | xmlns:tools="http://schemas.android.com/tools" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:background="@color/qmui_config_color_background" |
| | | android:orientation="vertical" |
| | | tools:context=".activity.HomeActivity"> |
| | | |
| | | <include layout="@layout/include_head" /> |
| | | |
| | | <com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView |
| | | android:id="@+id/groupListView" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:visibility="gone" /> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/send" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_gravity="center" |
| | | android:layout_marginTop="@dimen/dimension_15" |
| | | android:clickable="true" |
| | | android:padding="10dp" |
| | | android:text="åéæä»¤" |
| | | android:visibility="gone" |
| | | app:qmui_radius="4dp" /> |
| | | |
| | | <TextView |
| | | android:textSize="@dimen/dimension_40" |
| | | android:gravity="center" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="80dp" |
| | | android:id="@+id/sendNum" |
| | | /> |
| | | |
| | | <LinearLayout |
| | | android:id="@+id/ll_root" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical"> |
| | | |
| | | |
| | | <Button |
| | | android:visibility="gone" |
| | | android:id="@+id/btnInto" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_10" |
| | | android:layout_marginTop="@dimen/dimension_10" |
| | | android:layout_marginRight="10dp" |
| | | android:gravity="center" |
| | | android:text="è¿å
¥ç¼å" |
| | | android:textStyle="bold" /> |
| | | |
| | | |
| | | <Button |
| | | android:visibility="gone" |
| | | android:id="@+id/btnClean" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_10" |
| | | android:layout_marginTop="@dimen/dimension_10" |
| | | android:layout_marginRight="10dp" |
| | | |
| | | android:gravity="center" |
| | | android:text="æ¸
é¤å°å" |
| | | android:textStyle="bold" /> |
| | | |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="@dimen/dimension_10" |
| | | android:paddingTop="@dimen/dimension_10" |
| | | android:paddingRight="@dimen/dimension_10"> |
| | | |
| | | <Button |
| | | android:id="@+id/btn1" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="1" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn2" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="2" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn3" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="3" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn4" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="4" |
| | | android:textStyle="bold" /> |
| | | |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="@dimen/dimension_10" |
| | | android:paddingTop="@dimen/dimension_10" |
| | | android:paddingRight="@dimen/dimension_10" |
| | | |
| | | > |
| | | |
| | | <Button |
| | | android:id="@+id/btn5" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="5" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn6" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="6" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn7" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="7" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn8" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="8" |
| | | android:textStyle="bold" /> |
| | | |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="@dimen/dimension_10" |
| | | android:paddingTop="@dimen/dimension_10" |
| | | android:paddingRight="@dimen/dimension_10" |
| | | |
| | | > |
| | | |
| | | <Button |
| | | android:id="@+id/btn9" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="9" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn10" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="10" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn11" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="11" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn12" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="12" |
| | | android:textStyle="bold" /> |
| | | |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="@dimen/dimension_10" |
| | | android:paddingTop="@dimen/dimension_10" |
| | | android:paddingRight="@dimen/dimension_10" |
| | | |
| | | > |
| | | |
| | | <Button |
| | | android:id="@+id/btn13" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="13" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn14" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="14" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn15" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="15" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn16" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="16" |
| | | android:textStyle="bold" /> |
| | | |
| | | </LinearLayout> |
| | | |
| | | <Button |
| | | android:visibility="gone" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_10" |
| | | android:layout_marginTop="@dimen/dimension_10" |
| | | android:layout_marginRight="10dp" |
| | | android:id="@+id/btnExit" |
| | | android:gravity="center" |
| | | android:text="éåºç¼å" |
| | | android:textStyle="bold" /> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:app="http://schemas.android.com/apk/res-auto" |
| | | xmlns:tools="http://schemas.android.com/tools" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:background="@color/qmui_config_color_background" |
| | | android:orientation="vertical" |
| | | tools:context=".activity.HomeActivity"> |
| | | <include layout="@layout/include_head"/> |
| | | |
| | | <com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView |
| | | android:id="@+id/groupListView" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content"/> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:layout_marginTop="@dimen/dimension_15" |
| | | android:id="@+id/send" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:clickable="true" |
| | | android:layout_gravity="center" |
| | | android:padding="10dp" |
| | | android:text="åéæä»¤" |
| | | app:qmui_radius="4dp"/> |
| | | |
| | | </LinearLayout > |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?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="wrap_content" |
| | | android:orientation="horizontal"> |
| | | |
| | | <RelativeLayout |
| | | android:layout_width="@dimen/dimension_60" |
| | | android:layout_height="50dp" |
| | | > |
| | | |
| | | <LinearLayout |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_centerInParent="true" |
| | | android:orientation="vertical"> |
| | | |
| | | <ImageView |
| | | android:id="@+id/device_icon" |
| | | android:layout_width="30dp" |
| | | android:layout_height="30dp" |
| | | android:src="@mipmap/ic_sanjiao" /> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="-dBm" |
| | | android:layout_gravity="center" |
| | | android:textColor="@color/qmui_config_color_gray_6" |
| | | android:textSize="@dimen/dimension_10" /> |
| | | |
| | | </LinearLayout> |
| | | |
| | | </RelativeLayout> |
| | | |
| | | <RelativeLayout |
| | | android:layout_width="0dp" |
| | | android:layout_weight="1" |
| | | android:layout_height="50dp" |
| | | > |
| | | <LinearLayout |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_centerVertical="true" |
| | | android:orientation="vertical" |
| | | > |
| | | <TextView |
| | | android:id="@+id/device_name" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="JDY-123-456" |
| | | /> |
| | | <TextView |
| | | android:id="@+id/device_address" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="24:6d:12:dd:ad:df:cd" |
| | | android:textSize="@dimen/dimension_10" |
| | | android:textColor="@color/qmui_config_color_gray_6" |
| | | /> |
| | | |
| | | |
| | | </LinearLayout> |
| | | |
| | | </RelativeLayout> |
| | | |
| | | <RelativeLayout |
| | | android:layout_width="130dp" |
| | | android:layout_height="50dp" |
| | | > |
| | | |
| | | <Button |
| | | android:layout_width="@dimen/dimension_80" |
| | | android:layout_height="40dp" |
| | | android:layout_centerVertical="true" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_toLeftOf="@id/iv_down" |
| | | android:focusable="false" |
| | | android:text="CONNECT" |
| | | android:textSize="@dimen/dimension_10" /> |
| | | <ImageView |
| | | android:layout_marginRight="20dp" |
| | | android:id="@+id/iv_down" |
| | | android:layout_alignParentRight="true" |
| | | android:src="@mipmap/ic_down" |
| | | android:layout_centerVertical="true" |
| | | android:layout_width="20dp" |
| | | android:layout_height="20dp"/> |
| | | </RelativeLayout> |
| | | |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?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:background="@drawable/bg_round" |
| | | android:orientation="vertical" |
| | | > |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="400dp" |
| | | android:orientation="vertical"> |
| | | <ListView |
| | | android:id="@android:id/list" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent"/> |
| | | </LinearLayout> |
| | | |
| | | |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="@dimen/dimension_30" |
| | | android:orientation="vertical" |
| | | > |
| | | <TextView |
| | | android:layout_marginLeft="@dimen/dimension_15" |
| | | android:text="æµè¯" |
| | | android:textSize="@dimen/dimension_12" |
| | | android:layout_centerVertical="true" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:id="@+id/name" |
| | | /> |
| | | |
| | | <ImageView |
| | | android:layout_marginRight="@dimen/dimension_15" |
| | | android:layout_alignParentRight="true" |
| | | android:id="@+id/logo" |
| | | android:layout_centerVertical="true" |
| | | android:layout_width="@dimen/dimension_20" |
| | | android:layout_height="@dimen/dimension_20" |
| | | android:src="@mipmap/about_logo" |
| | | /> |
| | | |
| | | |
| | | </RelativeLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?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:background="@drawable/bg_round" |
| | | android:orientation="vertical" |
| | | > |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="400dp" |
| | | android:orientation="vertical"> |
| | | <RelativeLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="@dimen/dimension_40"> |
| | | <TextView |
| | | android:layout_marginLeft="15dp" |
| | | android:text="éæ©å½ä»¤" |
| | | android:textSize="@dimen/dimension_12" |
| | | android:layout_centerVertical="true" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content"/> |
| | | |
| | | <ImageView |
| | | android:id="@+id/iv_close" |
| | | android:layout_width="@dimen/dimension_20" |
| | | android:layout_height="@dimen/dimension_20" |
| | | android:layout_alignParentRight="true" |
| | | android:layout_centerVertical="true" |
| | | android:layout_marginRight="@dimen/dimension_20" |
| | | android:src="@mipmap/icon_colse" /> |
| | | </RelativeLayout> |
| | | <androidx.recyclerview.widget.RecyclerView |
| | | android:id="@+id/write" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="200dp"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/selectedtv" |
| | | android:layout_marginLeft="@dimen/dimension_15" |
| | | android:layout_marginTop="@dimen/dimension_10" |
| | | android:textSize="@dimen/dimension_12" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="@dimen/dimension_20" |
| | | android:text="å½åéæ©ï¼" |
| | | /> |
| | | |
| | | <TextView |
| | | android:id="@+id/restv" |
| | | android:layout_marginLeft="@dimen/dimension_15" |
| | | android:layout_marginTop="@dimen/dimension_10" |
| | | android:textSize="@dimen/dimension_12" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="@dimen/dimension_20" |
| | | android:text="è¿åä¿¡æ¯ï¼" |
| | | /> |
| | | <EditText |
| | | android:id="@+id/writeed" |
| | | android:layout_marginLeft="@dimen/dimension_15" |
| | | android:layout_marginRight="@dimen/dimension_15" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginTop="@dimen/dimension_10" |
| | | /> |
| | | <Button |
| | | android:id="@+id/writebtn" |
| | | android:layout_gravity="center" |
| | | android:textSize="@dimen/dimension_12" |
| | | android:layout_width="@dimen/dimension_90" |
| | | android:layout_height="@dimen/dimension_40" |
| | | android:text="send" |
| | | /> |
| | | </LinearLayout> |
| | | |
| | | |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?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"> |
| | | |
| | | <com.qmuiteam.qmui.widget.QMUIEmptyView |
| | | android:id="@+id/emptyView" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" /> |
| | | |
| | | <ListView |
| | | android:id="@+id/homeList" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:divider="@color/gray_line" |
| | | android:dividerHeight="1dp" |
| | | android:visibility="gone" /> |
| | | |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <androidx.core.widget.NestedScrollView 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:background="#F5F6FA" |
| | | android:fillViewport="true"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:padding="16dp"> |
| | | |
| | | <!-- Card 1: Grid Status --> |
| | | <androidx.cardview.widget.CardView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | app:cardCornerRadius="12dp" |
| | | app:cardElevation="2dp" |
| | | app:cardBackgroundColor="@color/white" |
| | | android:layout_marginBottom="24dp"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical"> |
| | | |
| | | <!-- Card Header --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center_vertical" |
| | | android:padding="16dp" |
| | | android:background="#FAFAFA"> |
| | | |
| | | <View |
| | | android:layout_width="4dp" |
| | | android:layout_height="16dp" |
| | | android:background="@color/base_color" |
| | | android:layout_marginEnd="8dp"/> |
| | | |
| | | <TextView |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_weight="1" |
| | | android:text="çæ§è¯¦æ
" |
| | | android:textSize="16sp" |
| | | android:textStyle="bold" |
| | | android:textColor="#333333" /> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_read_data" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="32dp" |
| | | android:text="è¯»åæ°æ®" |
| | | android:textColor="@color/white" |
| | | android:textSize="12sp" |
| | | android:paddingStart="12dp" |
| | | android:paddingEnd="12dp" |
| | | app:qmui_backgroundColor="@color/base_color" |
| | | app:qmui_radius="4dp"/> |
| | | </LinearLayout> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:background="#EEEEEE"/> |
| | | |
| | | <!-- Card Content --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:padding="16dp"> |
| | | |
| | | <HorizontalScrollView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:scrollbars="none"> |
| | | |
| | | <androidx.recyclerview.widget.RecyclerView |
| | | android:id="@+id/rv_grid" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" /> |
| | | </HorizontalScrollView> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:background="#EEEEEE" |
| | | android:layout_marginTop="16dp" |
| | | android:layout_marginBottom="12dp"/> |
| | | |
| | | <!-- Legend --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center_vertical" |
| | | android:paddingStart="4dp"> |
| | | |
| | | <View |
| | | android:layout_width="12dp" |
| | | android:layout_height="12dp" |
| | | android:background="@drawable/bg_box_full" |
| | | android:layout_marginEnd="6dp"/> |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="æç»ç" |
| | | android:textSize="12sp" |
| | | android:textColor="#666666" |
| | | android:layout_marginEnd="16dp"/> |
| | | |
| | | <View |
| | | android:layout_width="12dp" |
| | | android:layout_height="12dp" |
| | | android:background="@drawable/bg_box_empty" |
| | | android:layout_marginEnd="6dp"/> |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="æ ç»ç" |
| | | android:textSize="12sp" |
| | | android:textColor="#666666" |
| | | android:layout_marginEnd="16dp"/> |
| | | |
| | | <View |
| | | android:layout_width="12dp" |
| | | android:layout_height="12dp" |
| | | android:background="@drawable/bg_box_offline" |
| | | android:layout_marginEnd="6dp"/> |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="离线" |
| | | android:textSize="12sp" |
| | | android:textColor="#666666"/> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | </androidx.cardview.widget.CardView> |
| | | |
| | | <!-- Card 2: Controls --> |
| | | <androidx.cardview.widget.CardView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | app:cardCornerRadius="12dp" |
| | | app:cardElevation="2dp" |
| | | app:cardBackgroundColor="@color/white"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical"> |
| | | |
| | | <!-- Card Header --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center_vertical" |
| | | android:padding="16dp" |
| | | android:background="#FAFAFA"> |
| | | |
| | | <View |
| | | android:layout_width="4dp" |
| | | android:layout_height="16dp" |
| | | android:background="@color/base_color" |
| | | android:layout_marginEnd="8dp"/> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="åæ°" |
| | | android:textSize="16sp" |
| | | android:textStyle="bold" |
| | | android:textColor="#333333" /> |
| | | </LinearLayout> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:background="#EEEEEE"/> |
| | | |
| | | <!-- Card Content --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:padding="16dp"> |
| | | |
| | | <!-- Layer Input --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:layout_marginBottom="16dp"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center_vertical" |
| | | android:layout_marginBottom="8dp"> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="屿°" |
| | | android:textSize="14sp" |
| | | android:textColor="#666666"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_layer_status" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textSize="10sp" |
| | | android:layout_marginStart="8dp"/> |
| | | </LinearLayout> |
| | | |
| | | <EditText |
| | | android:id="@+id/et_layer" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="48dp" |
| | | android:background="@drawable/bg_input_field" |
| | | android:paddingStart="16dp" |
| | | android:paddingEnd="16dp" |
| | | android:hint="屿° (1-30)" |
| | | android:textColorHint="#CCCCCC" |
| | | android:textSize="14sp" |
| | | android:focusable="false" |
| | | android:focusableInTouchMode="false" |
| | | android:inputType="none"/> |
| | | </LinearLayout> |
| | | |
| | | <!-- Station Input --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:layout_marginBottom="16dp"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center_vertical" |
| | | android:layout_marginBottom="8dp"> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="ç«å·" |
| | | android:textSize="14sp" |
| | | android:textColor="#666666"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_station_status" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textSize="10sp" |
| | | android:layout_marginStart="8dp"/> |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal"> |
| | | |
| | | <EditText |
| | | android:id="@+id/et_station" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| | | android:background="@drawable/bg_input_field" |
| | | android:paddingStart="16dp" |
| | | android:paddingEnd="16dp" |
| | | android:hint="请è¾å
¥ç«å· (1-64)" |
| | | android:textColorHint="#CCCCCC" |
| | | android:textSize="14sp" |
| | | android:inputType="number"/> |
| | | |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | |
| | | <!-- Baud Rate --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center_vertical" |
| | | android:layout_marginBottom="8dp"> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="æ³¢ç¹ç" |
| | | android:textSize="14sp" |
| | | android:textColor="#666666"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_baud_status" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textSize="10sp" |
| | | android:layout_marginStart="8dp"/> |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal"> |
| | | |
| | | <FrameLayout |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| | | android:background="@drawable/bg_input_field"> |
| | | |
| | | <Spinner |
| | | android:id="@+id/spinner_baud" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:background="@null" |
| | | android:paddingStart="8dp"/> |
| | | </FrameLayout> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:layout_marginTop="24dp"> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_read_param" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| | | android:layout_marginEnd="8dp" |
| | | android:text="读ååæ°" |
| | | android:textColor="@color/white" |
| | | android:textSize="16sp" |
| | | android:textStyle="bold" |
| | | app:qmui_backgroundColor="@color/base_color" |
| | | app:qmui_radius="8dp"/> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_write_all" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| | | android:layout_marginStart="8dp" |
| | | android:text="åå
¥åæ°" |
| | | android:textColor="@color/white" |
| | | android:textSize="16sp" |
| | | android:textStyle="bold" |
| | | app:qmui_backgroundColor="@color/base_color_s" |
| | | app:qmui_radius="8dp"/> |
| | | </LinearLayout> |
| | | |
| | | </LinearLayout> |
| | | </LinearLayout> |
| | | </androidx.cardview.widget.CardView> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_status" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:text="å½åç¶æï¼çå¾
æä½..." |
| | | android:gravity="center" |
| | | android:textSize="12sp" |
| | | android:textColor="#999999" |
| | | android:layout_marginTop="16dp"/> |
| | | |
| | | <!-- Log Display --> |
| | | <androidx.cardview.widget.CardView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | app:cardCornerRadius="12dp" |
| | | app:cardElevation="2dp" |
| | | app:cardBackgroundColor="@color/white" |
| | | android:layout_marginTop="16dp" |
| | | android:layout_marginBottom="24dp"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical"> |
| | | |
| | | <!-- Card Header --> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:gravity="center_vertical" |
| | | android:padding="16dp" |
| | | android:background="#FAFAFA"> |
| | | |
| | | <View |
| | | android:layout_width="4dp" |
| | | android:layout_height="16dp" |
| | | android:background="@color/base_color" |
| | | android:layout_marginEnd="8dp"/> |
| | | |
| | | <TextView |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_weight="1" |
| | | android:text="æ¥å¿" |
| | | android:textSize="16sp" |
| | | android:textStyle="bold" |
| | | android:textColor="#333333" /> |
| | | |
| | | <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton |
| | | android:id="@+id/btn_clear_log" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="32dp" |
| | | android:text="æ¸
空" |
| | | android:textColor="@color/white" |
| | | android:textSize="12sp" |
| | | android:paddingStart="12dp" |
| | | android:paddingEnd="12dp" |
| | | app:qmui_backgroundColor="#FF5252" |
| | | app:qmui_radius="4dp"/> |
| | | </LinearLayout> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1dp" |
| | | android:background="#EEEEEE"/> |
| | | |
| | | <ScrollView |
| | | android:id="@+id/sv_log" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="200dp" |
| | | android:padding="12dp" |
| | | android:scrollbars="vertical" |
| | | android:background="#FAFAFA"> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_log" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:text="æ¥å¿è®°å½:\n" |
| | | android:textSize="12sp" |
| | | android:fontFamily="monospace" |
| | | android:textColor="#333333" |
| | | android:lineSpacingExtra="4dp"/> |
| | | </ScrollView> |
| | | </LinearLayout> |
| | | </androidx.cardview.widget.CardView> |
| | | |
| | | </LinearLayout> |
| | | |
| | | </androidx.core.widget.NestedScrollView> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <androidx.cardview.widget.CardView |
| | | android:id="@+id/card_view" |
| | | xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:card_view="http://schemas.android.com/apk/res-auto" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_gravity="center" |
| | | android:layout_marginLeft="5dp" |
| | | android:layout_marginRight="5dp" |
| | | android:foreground="?android:attr/selectableItemBackground" |
| | | card_view:cardBackgroundColor="@color/white" |
| | | card_view:cardCornerRadius="4dp" |
| | | card_view:cardUseCompatPadding="true"> |
| | | |
| | | <RelativeLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_gravity="center" |
| | | > |
| | | |
| | | |
| | | <ImageView |
| | | android:id="@+id/img" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_alignParentLeft="true" |
| | | android:layout_alignParentStart="true" |
| | | android:layout_alignParentTop="true" |
| | | android:scaleType="centerCrop" |
| | | android:src="@mipmap/animation_img1"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tweetName" |
| | | android:paddingLeft="@dimen/dp_10" |
| | | style="@style/tweetName" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="Chad" |
| | | android:layout_marginTop="@dimen/dp_10" |
| | | android:layout_below="@+id/img" |
| | | android:layout_alignParentLeft="true" |
| | | android:layout_alignParentStart="true"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tweetText" |
| | | android:paddingLeft="@dimen/dp_10" |
| | | style="@style/tweetText" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="ä¼ æå¨ç¼å" |
| | | android:textColor="@android:color/darker_gray" |
| | | android:layout_below="@+id/tweetName" |
| | | android:layout_alignParentLeft="true" |
| | | android:layout_alignParentStart="true" |
| | | android:paddingRight="@dimen/dp_10" |
| | | android:paddingBottom="@dimen/dp_10" |
| | | android:layout_alignParentRight="true" |
| | | android:layout_alignParentEnd="true"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tweetDate" |
| | | style="@style/tweetDate" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="" |
| | | android:paddingRight="@dimen/dp_10" |
| | | android:layout_alignBaseline="@+id/tweetName" |
| | | android:layout_alignBottom="@+id/tweetName" |
| | | android:layout_alignParentRight="true" |
| | | android:layout_alignParentEnd="true"/> |
| | | |
| | | </RelativeLayout> |
| | | </androidx.cardview.widget.CardView> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?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="wrap_content" |
| | | android:background="@color/white" |
| | | android:orientation="vertical"> |
| | | |
| | | <LinearLayout |
| | | android:id="@+id/ll_main_content" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="@dimen/dimension_70" |
| | | android:orientation="horizontal"> |
| | | |
| | | <RelativeLayout |
| | | android:layout_width="@dimen/dimension_80" |
| | | android:layout_height="@dimen/dimension_70"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_centerInParent="true" |
| | | android:orientation="vertical"> |
| | | |
| | | <ImageView |
| | | android:id="@+id/device_icon" |
| | | android:layout_width="30dp" |
| | | android:layout_height="30dp" |
| | | android:src="@mipmap/ic_sanjiao" /> |
| | | |
| | | <TextView |
| | | android:id="@+id/rssi" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_gravity="center" |
| | | android:layout_marginTop="@dimen/dimension_5" |
| | | android:text="-dBm" |
| | | android:textColor="@color/qmui_config_color_gray_6" |
| | | android:textSize="@dimen/dimension_10" /> |
| | | |
| | | </LinearLayout> |
| | | |
| | | </RelativeLayout> |
| | | |
| | | <RelativeLayout |
| | | android:layout_width="0dp" |
| | | android:layout_height="@dimen/dimension_70" |
| | | android:layout_weight="1"> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_centerVertical="true" |
| | | android:orientation="vertical"> |
| | | |
| | | <TextView |
| | | android:id="@+id/device_name" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:maxLines="1" /> |
| | | |
| | | <TextView |
| | | android:id="@+id/device_address" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginTop="@dimen/dimension_5" |
| | | android:maxLines="1" |
| | | android:textColor="@color/qmui_config_color_gray_6" |
| | | android:textSize="@dimen/dimension_10" /> |
| | | |
| | | |
| | | </LinearLayout> |
| | | |
| | | </RelativeLayout> |
| | | |
| | | <RelativeLayout |
| | | android:layout_width="130dp" |
| | | android:layout_height="@dimen/dimension_70"> |
| | | |
| | | <Button |
| | | android:id="@+id/conn_btn" |
| | | android:layout_width="@dimen/dimension_80" |
| | | android:layout_height="40dp" |
| | | android:layout_centerVertical="true" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_toLeftOf="@id/iv_down" |
| | | android:focusable="false" |
| | | android:text="CONNECT" |
| | | android:textSize="@dimen/dimension_10" /> |
| | | |
| | | <ImageView |
| | | android:id="@+id/iv_down" |
| | | android:layout_width="20dp" |
| | | android:layout_height="20dp" |
| | | android:layout_alignParentRight="true" |
| | | android:layout_centerVertical="true" |
| | | android:layout_marginRight="20dp" |
| | | android:src="@mipmap/ic_down" /> |
| | | </RelativeLayout> |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:id="@+id/ll_more_info" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:paddingLeft="20dp" |
| | | android:paddingRight="20dp" |
| | | android:paddingBottom="10dp" |
| | | android:visibility="gone"> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_device_type" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textColor="@color/qmui_config_color_gray_6" |
| | | android:textSize="12sp" |
| | | android:layout_marginTop="2dp"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_adv_type" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textColor="@color/qmui_config_color_gray_6" |
| | | android:textSize="12sp" |
| | | android:layout_marginTop="2dp"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_flags" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textColor="@color/qmui_config_color_gray_6" |
| | | android:textSize="12sp" |
| | | android:layout_marginTop="2dp"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_local_name" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textColor="@color/qmui_config_color_gray_6" |
| | | android:textSize="12sp" |
| | | android:layout_marginTop="2dp"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_tx_power" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textColor="@color/qmui_config_color_gray_6" |
| | | android:textSize="12sp" |
| | | android:layout_marginTop="2dp"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_service_uuids" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textColor="@color/qmui_config_color_gray_6" |
| | | android:textSize="12sp" |
| | | android:layout_marginTop="2dp"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_service_data" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textColor="@color/qmui_config_color_gray_6" |
| | | android:textSize="12sp" |
| | | android:layout_marginTop="2dp"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_manufacturer_data" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textColor="@color/qmui_config_color_gray_6" |
| | | android:textSize="12sp" |
| | | android:layout_marginTop="2dp"/> |
| | | </LinearLayout> |
| | | |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:app="http://schemas.android.com/apk/res-auto" |
| | | android:id="@+id/head" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="@dimen/dimension_50" |
| | | android:background="@color/base_color" |
| | | android:gravity="center_vertical"> |
| | | |
| | | |
| | | <ImageView |
| | | android:id="@+id/iv_left" |
| | | android:layout_width="@dimen/dimension_40" |
| | | android:layout_height="@dimen/dimension_40" |
| | | android:layout_centerVertical="true" |
| | | android:padding="@dimen/dimension_10" |
| | | android:src="@mipmap/icon_menu" |
| | | /> |
| | | |
| | | <TextView |
| | | android:textColor="@color/white" |
| | | android:textSize="@dimen/dimension_15" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_centerInParent="true" |
| | | android:id="@+id/tv_center" |
| | | /> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_right_text" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_alignParentRight="true" |
| | | android:layout_centerVertical="true" |
| | | android:paddingRight="16dp" |
| | | android:text="æªè¿æ¥" |
| | | android:textColor="@color/white" |
| | | android:textSize="14sp"/> |
| | | |
| | | <ImageView |
| | | android:visibility="gone" |
| | | android:padding="@dimen/dimension_10" |
| | | android:layout_alignParentRight="true" |
| | | android:id="@+id/iv_right" |
| | | android:layout_centerVertical="true" |
| | | android:layout_width="@dimen/dimension_40" |
| | | android:layout_height="@dimen/dimension_40" |
| | | android:src="@mipmap/icon_topbar_right" |
| | | /> |
| | | |
| | | </RelativeLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:id="@+id/head" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="@dimen/dimension_60" |
| | | android:background="@color/base_color" |
| | | android:paddingTop="@dimen/dimension_10" |
| | | android:gravity="center_vertical"> |
| | | |
| | | <ImageView |
| | | android:id="@+id/iv_left" |
| | | android:layout_width="@dimen/dimension_40" |
| | | android:layout_height="@dimen/dimension_40" |
| | | android:layout_centerVertical="true" |
| | | android:padding="@dimen/dimension_10" |
| | | android:src="@mipmap/icon_menu" /> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_center" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_centerVertical="true" |
| | | android:layout_toEndOf="@id/iv_left" |
| | | android:layout_toRightOf="@id/iv_left" |
| | | android:textColor="@color/white" |
| | | android:textSize="@dimen/fontsize_18" |
| | | android:textStyle="bold" |
| | | android:layout_marginLeft="@dimen/dimension_10" |
| | | android:layout_marginStart="@dimen/dimension_10" /> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_alignParentEnd="true" |
| | | android:layout_alignParentRight="true" |
| | | android:layout_centerVertical="true" |
| | | android:gravity="center_vertical" |
| | | android:orientation="horizontal"> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_scan" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:padding="@dimen/dimension_10" |
| | | android:text="SCAN" |
| | | android:textColor="@color/white" |
| | | android:textSize="@dimen/fontsize_15" /> |
| | | |
| | | <ImageView |
| | | android:id="@+id/iv_right" |
| | | android:layout_width="@dimen/dimension_40" |
| | | android:layout_height="@dimen/dimension_40" |
| | | android:padding="@dimen/dimension_10" |
| | | android:src="@mipmap/icon_topbar_right" |
| | | android:visibility="visible" /> |
| | | </LinearLayout> |
| | | |
| | | </RelativeLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?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="wrap_content" |
| | | android:gravity="center" |
| | | android:orientation="vertical" |
| | | android:padding="2dp"> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_box_number" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textSize="9sp" |
| | | android:textColor="#999999" |
| | | android:layout_marginBottom="2dp"/> |
| | | |
| | | <FrameLayout |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content"> |
| | | |
| | | <View |
| | | android:id="@+id/view_box" |
| | | android:layout_width="28dp" |
| | | android:layout_height="28dp" |
| | | android:background="@drawable/bg_box_empty" /> |
| | | |
| | | <!-- Add a subtle shadow or overlay effect if needed in future --> |
| | | </FrameLayout> |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:id="@+id/ll_progress_bar" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content"> |
| | | |
| | | <ImageView |
| | | android:id="@+id/img_progress" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | /> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginLeft="10dp" |
| | | android:layout_toRightOf="@+id/img_progress" |
| | | android:text="åªåå è½½ä¸..." |
| | | android:textColor="#585858" |
| | | android:textSize="14sp" /> |
| | | |
| | | </RelativeLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:app="http://schemas.android.com/apk/res-auto" |
| | | xmlns:tools="http://schemas.android.com/tools" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:gravity="top" |
| | | android:orientation="horizontal" |
| | | tools:ignore="MissingDefaultResource"> |
| | | |
| | | <ImageView |
| | | android:id="@+id/device_icon" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:paddingTop="5dp" |
| | | android:src="@mipmap/ic_bluetooth"/> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical"> |
| | | |
| | | <TextView |
| | | android:id="@+id/device_name" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:textSize="24sp"/> |
| | | |
| | | <GridLayout |
| | | android:layout_width="fill_parent" |
| | | android:layout_height="wrap_content" |
| | | android:columnCount="2"> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:text="@string/label_mac" |
| | | android:textSize="12sp" |
| | | android:textStyle="bold"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/device_address" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textSize="12sp" |
| | | android:typeface="monospace"/> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:text="@string/label_updated" |
| | | android:textSize="12sp" |
| | | android:textStyle="bold"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/device_last_update" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:textSize="12sp"/> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:text="@string/label_rssi" |
| | | android:textSize="12sp" |
| | | android:textStyle="bold"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/device_rssi" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:textSize="12sp"/> |
| | | </GridLayout> |
| | | |
| | | <TextView |
| | | android:id="@+id/ibeacon_scan_record" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:visibility="gone" |
| | | android:textSize="12sp"/> |
| | | |
| | | <GridLayout |
| | | android:id="@+id/ibeacon_section" |
| | | android:layout_width="fill_parent" |
| | | android:layout_height="wrap_content" |
| | | android:background="#66e0e0e0" |
| | | android:columnCount="4"> |
| | | |
| | | <!-- ROW 1 --> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:text="@string/label_uuid" |
| | | android:textSize="12sp" |
| | | android:textStyle="bold"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/ibeacon_uuid" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_columnSpan="3" |
| | | android:paddingRight="5dp" |
| | | android:textSize="12sp"/> |
| | | |
| | | <!-- ROW 2 --> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:text="@string/label_major" |
| | | android:textSize="12sp" |
| | | android:textStyle="bold"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/ibeacon_major" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:textSize="12sp"/> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingLeft="5dp" |
| | | android:paddingRight="5dp" |
| | | android:text="@string/label_minor" |
| | | android:textSize="12sp" |
| | | android:textStyle="bold"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/ibeacon_minor" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:textSize="12sp"/> |
| | | |
| | | <!-- ROW 3 --> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:text="@string/label_tx_power" |
| | | android:textSize="12sp" |
| | | android:textStyle="bold"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/ibeacon_tx_power" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:textSize="12sp"/> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingLeft="5dp" |
| | | android:paddingRight="5dp" |
| | | android:text="@string/label_distance" |
| | | android:textSize="12sp" |
| | | android:textStyle="bold"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/ibeacon_distance" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:textSize="12sp"/> |
| | | |
| | | <!-- ROW 4 --> |
| | | |
| | | <TextView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:paddingRight="5dp" |
| | | android:text="@string/label_decriptor" |
| | | android:textSize="12sp" |
| | | android:textStyle="bold"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/ibeacon_distance_descriptor" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_columnSpan="3" |
| | | android:paddingRight="5dp" |
| | | android:textSize="12sp"/> |
| | | |
| | | </GridLayout> |
| | | </LinearLayout> |
| | | |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:app="http://schemas.android.com/apk/res-auto" |
| | | xmlns:tools="http://schemas.android.com/tools" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:background="@color/qmui_config_color_background" |
| | | android:orientation="vertical" |
| | | tools:context=".activity.HomeActivity"> |
| | | |
| | | <include layout="@layout/include_head" /> |
| | | |
| | | <com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView |
| | | android:id="@+id/groupListView" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | /> |
| | | |
| | | <Button |
| | | android:id="@+id/send" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_gravity="center" |
| | | android:layout_marginTop="@dimen/dimension_15" |
| | | android:clickable="true" |
| | | android:padding="10dp" |
| | | android:text="åéæä»¤" |
| | | /> |
| | | |
| | | <LinearLayout |
| | | android:visibility="gone" |
| | | android:id="@+id/ll_root" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical"> |
| | | |
| | | |
| | | <Button |
| | | android:id="@+id/btnInto" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_10" |
| | | android:layout_marginTop="@dimen/dimension_10" |
| | | android:layout_marginRight="10dp" |
| | | android:gravity="center" |
| | | android:text="è¿å
¥ç¼å" |
| | | android:textStyle="bold" /> |
| | | |
| | | |
| | | <Button |
| | | android:id="@+id/btnClean" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_10" |
| | | android:layout_marginTop="@dimen/dimension_10" |
| | | android:layout_marginRight="10dp" |
| | | |
| | | android:gravity="center" |
| | | android:text="æ¸
é¤å°å" |
| | | android:textStyle="bold" /> |
| | | |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="@dimen/dimension_10" |
| | | android:paddingTop="@dimen/dimension_10" |
| | | android:paddingRight="@dimen/dimension_10"> |
| | | |
| | | <Button |
| | | android:id="@+id/btn1" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="1" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn2" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="2" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn3" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="3" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn4" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="4" |
| | | android:textStyle="bold" /> |
| | | |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="@dimen/dimension_10" |
| | | android:paddingTop="@dimen/dimension_10" |
| | | android:paddingRight="@dimen/dimension_10" |
| | | |
| | | > |
| | | |
| | | <Button |
| | | android:id="@+id/btn5" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="5" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn6" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="6" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn7" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="7" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn8" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="8" |
| | | android:textStyle="bold" /> |
| | | |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="@dimen/dimension_10" |
| | | android:paddingTop="@dimen/dimension_10" |
| | | android:paddingRight="@dimen/dimension_10" |
| | | |
| | | > |
| | | |
| | | <Button |
| | | android:id="@+id/btn9" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="9" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn10" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="10" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn11" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="11" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn12" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="12" |
| | | android:textStyle="bold" /> |
| | | |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:paddingLeft="@dimen/dimension_10" |
| | | android:paddingTop="@dimen/dimension_10" |
| | | android:paddingRight="@dimen/dimension_10" |
| | | |
| | | > |
| | | |
| | | <Button |
| | | android:id="@+id/btn13" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="13" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn14" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="14" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn15" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_marginRight="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="15" |
| | | android:textStyle="bold" /> |
| | | |
| | | <Button |
| | | android:id="@+id/btn16" |
| | | android:layout_width="0dp" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_5" |
| | | android:layout_weight="1" |
| | | |
| | | android:gravity="center" |
| | | android:text="16" |
| | | android:textStyle="bold" /> |
| | | |
| | | </LinearLayout> |
| | | |
| | | <Button |
| | | android:layout_width="match_parent" |
| | | android:layout_height="45dp" |
| | | android:layout_marginLeft="@dimen/dimension_10" |
| | | android:layout_marginTop="@dimen/dimension_10" |
| | | android:layout_marginRight="10dp" |
| | | android:id="@+id/btnExit" |
| | | android:gravity="center" |
| | | android:text="éåºç¼å" |
| | | android:textStyle="bold" /> |
| | | </LinearLayout> |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:tools="http://schemas.android.com/tools" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:gravity="center" |
| | | android:orientation="horizontal" |
| | | tools:ignore="UseCompoundDrawables"> |
| | | |
| | | <ImageView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:contentDescription="èªå®ä¹ icon" |
| | | android:src="@drawable/qmui_icon_notify_info"/> |
| | | |
| | | <TextView |
| | | android:id="@+id/tv_tip" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_marginLeft="10dp" |
| | | android:text="èªå®ä¹ç»æ" |
| | | android:textColor="@color/qmui_config_color_white" |
| | | android:textSize="16sp"/> |
| | | |
| | | </LinearLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content"> |
| | | |
| | | <ImageView |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:src="@mipmap/top_background"/> |
| | | |
| | | <ImageView |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_gravity="right|bottom" |
| | | android:src="@mipmap/monkey"/> |
| | | </FrameLayout> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <menu xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:app="http://schemas.android.com/apk/res-auto" |
| | | xmlns:tools="http://schemas.android.com/tools" |
| | | tools:context="com.shlb.comb.MainActivity"> |
| | | <item |
| | | android:id="@+id/action_settings" |
| | | android:orderInCategory="100" |
| | | android:title="@string/action_settings" |
| | | app:showAsAction="never" /> |
| | | </menu> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <resources> |
| | | <dimen name="fab_margin">48dp</dimen> |
| | | </resources> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <resources> |
| | | <dimen name="fab_margin">200dp</dimen> |
| | | </resources> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <resources> |
| | | <dimen name="fab_margin">48dp</dimen> |
| | | </resources> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <resources> |
| | | <string-array name="threshold_modes"> |
| | | <item>ä¸é®è®¾å®</item> |
| | | <item>åå±è®¾å®</item> |
| | | </string-array> |
| | | <string-array name="sensitivity_modes"> |
| | | <item>æ¹éå¢å </item> |
| | | <item>æ¹éåå°</item> |
| | | <item>åå±å¢å </item> |
| | | <item>åå±åå°</item> |
| | | </string-array> |
| | | </resources> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <resources> |
| | | <color name="colorPrimary">#1890ff</color> |
| | | <color name="colorPrimaryDark">#1890ff</color> |
| | | <color name="colorAccent">#1890ff</color> |
| | | |
| | | <color name="white">#ffffff</color> |
| | | <color name="base_color">#1890ff</color> |
| | | <color name="base_color_s">#05aa87</color> |
| | | <color name="gray_line">#e5e5e5</color> |
| | | <color name="gray_bg">#f7f7f7</color> |
| | | <color name="dark_line">#C2C2C2</color> |
| | | <color name="dark_hint">#898989</color> |
| | | <color name="dark_border">#979797</color> |
| | | <color name="dark_big_line">#727272</color> |
| | | <color name="dark_line2">#9E9E9E</color> |
| | | <color name="bg_green">#2AD9A4</color> |
| | | <color name="bg_title">#EFEEF5</color> |
| | | |
| | | |
| | | <color name="base_line">#F4F5F7</color> |
| | | <color name="app_color_blue_2">#31BDF3</color> |
| | | <color name="app_color_blue_2_disabled">#7F31BDF3</color> |
| | | <color name="app_color_blue_2_pressed">#7F31BDF3</color> |
| | | |
| | | <color name="app_color_theme_1">#EF5362</color> <!-- Grapefruit --> |
| | | <color name="app_color_theme_2">#FE6D4B</color> <!-- Bittersweet --> |
| | | <color name="app_color_theme_3">#FFCF47</color> <!-- Sunflower --> |
| | | <color name="app_color_theme_4">#9FD661</color> <!-- Grass --> |
| | | <color name="app_color_theme_5">#3FD0AD</color> <!-- Mint --> |
| | | <color name="app_color_theme_6">#2BBDF3</color> <!-- Aqua --> |
| | | <color name="app_color_theme_7">#5A9AEF</color> <!-- Blue Jeans --> |
| | | <color name="app_color_theme_8">#AC8FEF</color> <!-- Lavender --> |
| | | <color name="app_color_theme_9">#EE85C1</color> <!-- Pink Rose --> |
| | | |
| | | <color name="e5">#F7F7F7</color> |
| | | <color name="base_text">#000</color> |
| | | <color name="bg">#10858C96</color> |
| | | <color name="bg_table">#F0F0F0</color> |
| | | <color name="blue_">#196CA0</color> |
| | | <color name="orange">#FAA02A</color> |
| | | |
| | | <color name="green">#00FF00</color> |
| | | <color name="blue">#EE9A00</color> |
| | | <color name="red">#EE0000</color> |
| | | |
| | | <color name="content_background">#f2f2f2</color> |
| | | |
| | | <color name="red_normal">#FF0000</color> |
| | | <color name="red_pressed">#FF6666</color> |
| | | |
| | | <color name="shape1">#5d8df7</color> |
| | | <color name="shape2">#5EB9F8</color> |
| | | <color name="shape3">#5CC2F8</color> |
| | | |
| | | <color name="title1">#1890ff</color> |
| | | <color name="title2">#1890ff</color> |
| | | <color name="title3">#1890ff</color> |
| | | <color name="title4">#1890ff</color> |
| | | |
| | | |
| | | <color name="bar1">#C0FF8C</color> |
| | | <color name="bar2">#FFF78C</color> |
| | | <color name="bar3">#FFD08C</color> |
| | | <color name="bar4">#8CEAFF</color> |
| | | <color name="bar5">#FF8C9D</color> |
| | | |
| | | <color name="drawer_header_teal">#008299</color> |
| | | <color name="drawer_item_selected_blue">#00B2D4</color> |
| | | <color name="drawer_item_text_gray">#666666</color> |
| | | <color name="drawer_divider">#EEEEEE</color> |
| | | <color name="drawer_badge_red">#EE0000</color> |
| | | |
| | | |
| | | |
| | | |
| | | <integer-array name="gradient_arc_color"> |
| | | <item>@color/green</item> |
| | | <item>@color/blue</item> |
| | | <item>@color/red</item> |
| | | </integer-array> |
| | | |
| | | <integer-array name="titlecolor"> |
| | | <item>@color/title1</item> |
| | | <item>@color/title2</item> |
| | | <item>@color/title3</item> |
| | | </integer-array> |
| | | </resources> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <resources> |
| | | <!-- Default screen margins, per the Android Design guidelines. --> |
| | | <dimen name="activity_horizontal_margin">16dp</dimen> |
| | | <dimen name="activity_vertical_margin">16dp</dimen> |
| | | |
| | | <dimen name="padding_top">0dp</dimen> |
| | | |
| | | <!-- åä½å¤§å° --> |
| | | <dimen name="fontsize_8">8sp</dimen> |
| | | <dimen name="fontsize_10">10sp</dimen> |
| | | <dimen name="fontsize_12">12sp</dimen> |
| | | <dimen name="fontsize_13">13sp</dimen> |
| | | <dimen name="fontsize_14">14sp</dimen> |
| | | <dimen name="fontsize_15">15sp</dimen> |
| | | <dimen name="fontsize_16">16sp</dimen> |
| | | <dimen name="fontsize_17">17sp</dimen> |
| | | <dimen name="fontsize_18">18sp</dimen> |
| | | <dimen name="fontsize_20">20sp</dimen> |
| | | <dimen name="fontsize_22">22sp</dimen> |
| | | <dimen name="fontsize_24">24sp</dimen> |
| | | <dimen name="fontsize_26">26sp</dimen> |
| | | <dimen name="fontsize_28">28sp</dimen> |
| | | <dimen name="fontsize_34">34sp</dimen> |
| | | <dimen name="fontsize_40">40sp</dimen> |
| | | |
| | | <!-- 尺寸 --> |
| | | <dimen name="dimension_0">0dp</dimen> |
| | | <dimen name="dimension_dp_litter">0.25dp</dimen> |
| | | <dimen name="dimension_dp_half">0.5dp</dimen> |
| | | <dimen name="dimension_1">1dp</dimen> |
| | | <dimen name="dimension_2">2dp</dimen> |
| | | <dimen name="dimension_3">3dp</dimen> |
| | | <dimen name="dimension_4">4dp</dimen> |
| | | <dimen name="dimension_5">5dp</dimen> |
| | | <dimen name="dimension_6">6dp</dimen> |
| | | <dimen name="dimension_7">7dp</dimen> |
| | | <dimen name="dimension_8">8dp</dimen> |
| | | <dimen name="dimension_10">10dp</dimen> |
| | | <dimen name="dimension_11">11dp</dimen> |
| | | <dimen name="dimension_12">12dp</dimen> |
| | | <dimen name="dimension_13">13dp</dimen> |
| | | <dimen name="dimension_14">14dp</dimen> |
| | | <dimen name="dimension_15">15dp</dimen> |
| | | <dimen name="dimension_16">16dp</dimen> |
| | | <dimen name="dimension_17">17dp</dimen> |
| | | <dimen name="dimension_18">18dp</dimen> |
| | | <dimen name="dimension_20">20dp</dimen> |
| | | <dimen name="dimension_21">21dp</dimen> |
| | | <dimen name="dimension_23">23dp</dimen> |
| | | <dimen name="dimension_24">24dp</dimen> |
| | | <dimen name="dimension_25">25dp</dimen> |
| | | <dimen name="dimension_28">28dp</dimen> |
| | | <dimen name="dimension_30">30dp</dimen> |
| | | <dimen name="dimension_32">32dp</dimen> |
| | | <dimen name="dimension_34">34dp</dimen> |
| | | <dimen name="dimension_35">35dp</dimen> |
| | | <dimen name="dimension_36">36dp</dimen> |
| | | <dimen name="dimension_38">38dp</dimen> |
| | | <dimen name="dimension_40">40dp</dimen> |
| | | <dimen name="dimension_41">41dp</dimen> |
| | | <dimen name="dimension_43">43dp</dimen> |
| | | <dimen name="dimension_39">39dp</dimen> |
| | | <dimen name="dimension_42">42dp</dimen> |
| | | <dimen name="dimension_44">44dp</dimen> |
| | | <dimen name="dimension_45">45dp</dimen> |
| | | <dimen name="dimension_47">47dp</dimen> |
| | | <dimen name="dimension_48">48dp</dimen> |
| | | <dimen name="dimension_50">50dp</dimen> |
| | | <dimen name="dimension_52">52dp</dimen> |
| | | <dimen name="dimension_55">55dp</dimen> |
| | | <dimen name="dimension_60">60dp</dimen> |
| | | <dimen name="dimension_65">65dp</dimen> |
| | | <dimen name="dimension_70">70dp</dimen> |
| | | <dimen name="dimension_75">75dp</dimen> |
| | | <dimen name="dimension_77">77dp</dimen> |
| | | <dimen name="dimension_80">80dp</dimen> |
| | | <dimen name="dimension_85">85dp</dimen> |
| | | <dimen name="dimension_90">90dp</dimen> |
| | | <dimen name="dimension_95">95dp</dimen> |
| | | <dimen name="dimension_100">100dp</dimen> |
| | | <dimen name="dimension_120">120dp</dimen> |
| | | <dimen name="dimension_135">135dp</dimen> |
| | | <dimen name="dimension_140">140dp</dimen> |
| | | <dimen name="dimension_160">160dp</dimen> |
| | | <dimen name="dimension_165">165dp</dimen> |
| | | <dimen name="dimension_170">170dp</dimen> |
| | | <dimen name="dimension_150">150dp</dimen> |
| | | <dimen name="dimension_180">180dp</dimen> |
| | | <dimen name="dimension_200">200dp</dimen> |
| | | <dimen name="dimension_220">220dp</dimen> |
| | | <dimen name="dimension_250">250dp</dimen> |
| | | <dimen name="dimension_260">260dp</dimen> |
| | | <dimen name="dimension_290">290dp</dimen> |
| | | <dimen name="dimension_300">300dp</dimen> |
| | | <dimen name="dimension_px_half">0.5px</dimen> |
| | | <dimen name="dimension_px_1">1px</dimen> |
| | | <dimen name="dimension_dp_15">15dp</dimen> |
| | | <dimen name="dimension_px_30">30px</dimen> |
| | | <dimen name="dimension_px_100">100px</dimen> |
| | | |
| | | <dimen name="status_bar_height">0dp</dimen> |
| | | </resources> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <!-- |
| | | Tencent is pleased to support the open source community by making QMUI_Android available. |
| | | |
| | | Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. |
| | | |
| | | Licensed under the MIT License (the "License"); you may not use this file except in |
| | | compliance with the License. You may obtain a copy of the License at |
| | | |
| | | http://opensource.org/licenses/MIT |
| | | |
| | | Unless required by applicable law or agreed to in writing, software distributed under the License is |
| | | distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, |
| | | either express or implied. See the License for the specific language governing permissions and |
| | | limitations under the License. |
| | | --> |
| | | |
| | | <resources> |
| | | <item name="qmuidemo" type="id"/> |
| | | <item name="main_container" type="id"/> |
| | | |
| | | <item name="empty_button" type="id"/> |
| | | |
| | | <item name="topbar_right_about_button" type="id"/> |
| | | <item name="topbar_right_change_button" type="id"/> |
| | | |
| | | <item name="layout_for_test" type="id"/> |
| | | <!--main--> |
| | | <item name="ble_connec" type="id"/> |
| | | <item name="ble_address" type="id"/> |
| | | |
| | | <!--address--> |
| | | <item name="address_write_list" type="id"/> |
| | | <item name="address_ble_name" type="id"/> |
| | | <item name="address_ble_statu" type="id"/> |
| | | <item name="address_ble_mac" type="id"/> |
| | | |
| | | |
| | | </resources> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <resources> |
| | | <string name="app_name">comb</string> |
| | | <string name="action_settings">Settings</string> |
| | | <!-- Strings used for fragments for navigation --> |
| | | <string name="first_fragment_label">First Fragment</string> |
| | | <string name="second_fragment_label">Second Fragment</string> |
| | | <string name="next">Next</string> |
| | | <string name="previous">Previous</string> |
| | | |
| | | <string name="hello_first_fragment">Hello first fragment</string> |
| | | <string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string> |
| | | |
| | | |
| | | |
| | | |
| | | <!-- Labels --> |
| | | <string name="about_dialog_text">This is a sample application using the Bluetooth LE Library.\n\nGithub: https://github.com/alt236/Bluetooth-LE-Library---Android\n\nCopyright: Alexandros Schillings</string> |
| | | <string name="header_device_info">Device Info</string> |
| | | <string name="header_ibeacon_data">iBeacon Data</string> |
| | | <string name="header_raw_ad_records">Raw Ad Records</string> |
| | | <string name="header_rssi_info">RSSI Info</string> |
| | | <string name="header_scan_record">Scan Record</string> |
| | | <string name="label_advertisement">Advertisement:</string> |
| | | <string name="label_as_array">As Array:</string> |
| | | <string name="label_as_string">As String:</string> |
| | | <string name="label_bluetooth_le_status">Bluetooth LE:</string> |
| | | <string name="label_bluetooth_status">Bluetooth:</string> |
| | | <string name="label_bluetooth_filter">Filter:</string> |
| | | <string name="label_bluetooth_period">Period:</string> |
| | | <string name="label_bonding_state">Bonding State:</string> |
| | | <string name="label_company_id">Company ID:</string> |
| | | <string name="label_data">Data:</string> |
| | | <string name="label_desc">Desc:</string> |
| | | <string name="label_device_address">Device address:</string> |
| | | <string name="label_device_class">Device Class:</string> |
| | | <string name="label_device_major_class">Major Class:</string> |
| | | <string name="label_device_services">Services:</string> |
| | | <string name="label_device_name">Device Name:</string> |
| | | <string name="label_distance">Distance:</string> |
| | | <string name="label_first_rssi">First RSSI:</string> |
| | | <string name="label_first_timestamp">First Timestamp:</string> |
| | | <string name="label_last_rssi">Last RSSI:</string> |
| | | <string name="label_last_timestamp">Last Timestamp:</string> |
| | | <string name="label_mac">MAC:</string> |
| | | <string name="label_major">Major:</string> |
| | | <string name="label_minor">Minor:</string> |
| | | <string name="label_rssi">RSSI:</string> |
| | | <string name="label_running_average_rssi">Running Average RSSI:</string> |
| | | <string name="label_state">State:</string> |
| | | <string name="label_tx_power">TX Power:</string> |
| | | <string name="label_uuid">UUID:</string> |
| | | <string name="label_updated">Updated:</string> |
| | | <string name="label_decriptor">Descriptor:</string> |
| | | <string name="label_record">Record:</string> |
| | | |
| | | <string name="connected">Connected</string> |
| | | <string name="disconnected">Disconnected</string> |
| | | <string name="invalid_device_data">Invalid Device Data!</string> |
| | | <string name="no_data">No data</string> |
| | | <string name="not_supported">Not supported</string> |
| | | <string name="off">Off</string> |
| | | <string name="period">scan:10s, pause:5s</string> |
| | | <string name="on">On</string> |
| | | <string name="supported">Supported</string> |
| | | <string name="unknown">unknown</string> |
| | | <string name="unknown_characteristic">Unknown characteristic</string> |
| | | <string name="unknown_device">Unknown device</string> |
| | | <string name="unknown_service">Unknown service</string> |
| | | <string name="no_known_services">No known services</string> |
| | | <!-- Formatters --> |
| | | <string name="formatter_meters">%sm</string> |
| | | <string name="formatter_db">%sdb</string> |
| | | <string name="formatter_item_count">Items: %s</string> |
| | | |
| | | </resources> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <resources xmlns:tools="http://schemas.android.com/tools"> |
| | | |
| | | |
| | | <!-- Base application theme. --> |
| | | <style name="AppTheme" parent="QMUI.Compat.NoActionBar"> |
| | | <!-- Customize your theme here. --> |
| | | <item name="colorPrimary">@color/colorPrimary</item> |
| | | <item name="colorPrimaryDark">@color/colorPrimaryDark</item> |
| | | <item name="colorAccent">@color/colorAccent</item> |
| | | <item name="android:windowBackground">@color/gray_bg</item> |
| | | |
| | | <item name="QMUITopBarStyle">@style/QDTopBar</item> |
| | | <item name="android:navigationBarColor">@android:color/transparent</item> |
| | | </style> |
| | | |
| | | <!--SplashActivity Theme--> |
| | | <style name="SplashTheme" parent="AppTheme"> |
| | | <item name="android:windowIsTranslucent">false</item> |
| | | <item name="android:windowNoTitle">true</item> |
| | | <item name="android:windowContentOverlay">@null</item> |
| | | <item name="android:windowBackground">@color/colorPrimary</item> |
| | | <item name="android:windowFullscreen">true</item> |
| | | <item name="android:navigationBarColor">@color/colorPrimary</item> |
| | | <item name="android:statusBarColor">@color/colorPrimary</item> |
| | | </style> |
| | | |
| | | |
| | | <style name="QDTopBar" parent="QMUI.TopBar"> |
| | | <item name="qmui_topbar_title_color">@color/qmui_config_color_white</item> |
| | | <item name="qmui_topbar_subtitle_color">@color/qmui_config_color_white</item> |
| | | <item name="qmui_topbar_height">48dp</item> |
| | | <item name="qmui_topbar_title_text_size">15dp</item> |
| | | <item name="qmui_topbar_image_btn_height">48dp</item> |
| | | </style> |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | <style name="tweetName"> |
| | | <item name="android:textColor">#48495f</item> |
| | | <item name="android:textStyle">bold</item> |
| | | <item name="android:textSize">18sp</item> |
| | | <!--<item name="android:textStyle">bold</item>--> |
| | | <item name="android:shadowColor">#44000000</item> |
| | | <item name="android:shadowDx">1</item> |
| | | <item name="android:shadowDy">1</item> |
| | | <item name="android:shadowRadius">1</item> |
| | | </style> |
| | | |
| | | <style name="tweetText"> |
| | | <item name="android:textSize">13sp</item> |
| | | <item name="android:textColorLink">#888888</item> |
| | | <item name="android:lineSpacingExtra">4dp</item> |
| | | </style> |
| | | |
| | | <style name="tweetDate"> |
| | | <item name="android:textSize">10sp</item> |
| | | <item name="android:textColor">#FFCCCCCC</item> |
| | | </style> |
| | | <style name="ButtonBase" parent="android:Widget.Holo.Light.Button"/> |
| | | <style name="Button" parent="ButtonBase"/> |
| | | <style name="CustomButton" parent="@style/Button"> |
| | | <item name="android:layout_height">40dp</item> |
| | | <item name="android:layout_width">wrap_content</item> |
| | | <item name="android:layout_marginTop">10dp</item> |
| | | <item name="android:layout_gravity">center</item> |
| | | <item name="android:paddingLeft">?attr/qmui_content_spacing_horizontal</item> |
| | | <item name="android:paddingRight">?attr/qmui_content_spacing_horizontal</item> |
| | | <item name="android:background">@drawable/s_radius_button_bg</item> |
| | | <item name="android:textColor">@color/s_app_color_blue_2</item> |
| | | <item name="android:textSize">12sp</item> |
| | | <item name="android:textStyle">bold</item> |
| | | <item name="android:gravity">center</item> |
| | | </style> |
| | | |
| | | </resources> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <resources> |
| | | <declare-styleable name="ParticleView"><attr format="string" name="pv_host_text"/><attr format="dimension" name="pv_host_text_size"/><attr format="string" name="pv_particle_text"/><attr format="dimension" name="pv_particle_text_size"/><attr format="color" name="pv_text_color"/><attr format="color" name="pv_background_color"/><attr format="integer" name="pv_text_anim_time"/><attr format="integer" name="pv_spread_anim_time"/><attr format="integer" name="pv_host_text_anim_time"/></declare-styleable> |
| | | </resources> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?><!-- |
| | | Sample backup rules file; uncomment and customize as necessary. |
| | | See https://developer.android.com/guide/topics/data/autobackup |
| | | for details. |
| | | Note: This file is ignored for devices older that API 31 |
| | | See https://developer.android.com/about/versions/12/backup-restore |
| | | --> |
| | | <full-backup-content> |
| | | <!-- |
| | | <include domain="sharedpref" path="."/> |
| | | <exclude domain="sharedpref" path="device.xml"/> |
| | | --> |
| | | </full-backup-content> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="utf-8"?><!-- |
| | | Sample data extraction rules file; uncomment and customize as necessary. |
| | | See https://developer.android.com/about/versions/12/backup-restore#xml-changes |
| | | for details. |
| | | --> |
| | | <data-extraction-rules> |
| | | <cloud-backup> |
| | | <!-- TODO: Use <include> and <exclude> to control what is backed up. |
| | | <include .../> |
| | | <exclude .../> |
| | | --> |
| | | </cloud-backup> |
| | | <!-- |
| | | <device-transfer> |
| | | <include .../> |
| | | <exclude .../> |
| | | </device-transfer> |
| | | --> |
| | | </data-extraction-rules> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | apply plugin: 'com.android.library' |
| | | |
| | | android { |
| | | namespace "com.blakequ.bluetooth_manager_lib" |
| | | compileSdkVersion 28 |
| | | defaultConfig { |
| | | minSdk 21 |
| | | targetSdk 28 |
| | | versionCode 1 |
| | | versionName "1.0" |
| | | |
| | | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
| | | } |
| | | buildTypes { |
| | | release { |
| | | minifyEnabled false |
| | | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' |
| | | } |
| | | } |
| | | |
| | | lintOptions { |
| | | abortOnError false |
| | | } |
| | | } |
| | | |
| | | tasks.withType(JavaCompile) { |
| | | options.encoding = "UTF-8" |
| | | } |
| | | |
| | | |
| | | dependencies { |
| | | implementation fileTree(dir: 'libs', include: ['*.jar']) |
| | | testImplementation 'junit:junit:4.12' |
| | | implementation 'androidx.appcompat:appcompat:1.3.0' |
| | | api 'com.blankj:utilcodex:1.31.0' |
| | | } |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # Add project specific ProGuard rules here. |
| | | # By default, the flags in this file are appended to flags specified |
| | | # in D:\studio_sdk/tools/proguard/proguard-android.txt |
| | | # You can edit the include path and order by changing the proguardFiles |
| | | # directive in build.gradle. |
| | | # |
| | | # For more details, see |
| | | # http://developer.android.com/guide/developing/tools/proguard.html |
| | | |
| | | # Add any project specific keep options here: |
| | | |
| | | # If your project uses WebView with JS, uncomment the following |
| | | # and specify the fully qualified class name to the JavaScript interface |
| | | # class: |
| | | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
| | | # public *; |
| | | #} |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| | | package="com.blakequ.bluetooth_manager_lib"> |
| | | |
| | | <uses-permission android:name="android.permission.BLUETOOTH" /> |
| | | <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> |
| | | <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> |
| | | <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> |
| | | <application |
| | | android:allowBackup="true"> |
| | | |
| | | </application> |
| | | |
| | | </manifest> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib; |
| | | |
| | | import android.annotation.TargetApi; |
| | | import android.bluetooth.BluetoothManager; |
| | | import android.content.Context; |
| | | import android.content.pm.PackageManager; |
| | | import androidx.annotation.NonNull; |
| | | import android.util.Log; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.connect.BluetoothConnectManager; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectConfig; |
| | | import com.blakequ.bluetooth_manager_lib.connect.multiple.MultiConnectManager; |
| | | import com.blakequ.bluetooth_manager_lib.scan.BluetoothScanManager; |
| | | |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/17 11:02 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description: scan and connect ble device |
| | | */ |
| | | @TargetApi(18) |
| | | public final class BleManager { |
| | | private static BleParamsOptions configOptions; |
| | | private BleManager(){ |
| | | } |
| | | |
| | | public static BleParamsOptions getBleParamsOptions(){ |
| | | if (configOptions == null){ |
| | | setBleParamsOptions(BleParamsOptions.createDefault()); |
| | | } |
| | | return configOptions; |
| | | } |
| | | |
| | | /** |
| | | * set config of ble connect and scan |
| | | * @param options |
| | | */ |
| | | public static void setBleParamsOptions(@NonNull BleParamsOptions options){ |
| | | if (options != null){ |
| | | configOptions = options; |
| | | ConnectConfig.maxConnectDeviceNum = options.getMaxConnectDeviceNum(); |
| | | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * get connect manager, only connect one device |
| | | * @param context |
| | | * @return |
| | | * @see #getMultiConnectManager(Context) |
| | | */ |
| | | public static BluetoothConnectManager getConnectManager(@NonNull Context context){ |
| | | return BluetoothConnectManager.getInstance(context); |
| | | } |
| | | |
| | | /** |
| | | * get scan bluetooth manager |
| | | * @param context |
| | | * @return |
| | | */ |
| | | public static BluetoothScanManager getScanManager(@NonNull Context context){ |
| | | return BluetoothScanManager.getInstance(context); |
| | | } |
| | | |
| | | /** |
| | | * get multi bluetooth device connect manager |
| | | * @param context |
| | | * @return |
| | | * @see #getConnectManager(Context) |
| | | */ |
| | | public static MultiConnectManager getMultiConnectManager(@NonNull Context context){ |
| | | return MultiConnectManager.getInstance(context); |
| | | } |
| | | |
| | | /** |
| | | * Check if Bluetooth LE is supported by this Android device, and if so, make sure it is enabled. |
| | | * |
| | | * @return false if it is supported and not enabled |
| | | * @throws BleNotAvailableException if Bluetooth LE is not supported. (Note: The Android emulator will do this) |
| | | */ |
| | | public static boolean checkAvailability(Context context) throws BleNotAvailableException { |
| | | if (isSDKAvailable()) { |
| | | if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { |
| | | throw new BleNotAvailableException("Bluetooth LE not supported by this device"); |
| | | } else { |
| | | if (((BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter().isEnabled()) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | public static boolean isSDKAvailable(){ |
| | | if (android.os.Build.VERSION.SDK_INT < 18) { |
| | | throw new BleNotAvailableException("Bluetooth LE not supported by this device"); |
| | | } |
| | | return true; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /** |
| | | * Radius Networks, Inc. |
| | | * http://www.radiusnetworks.com |
| | | * |
| | | * @author David G. Young |
| | | * |
| | | * Licensed to the Apache Software Foundation (ASF) under one |
| | | * or more contributor license agreements. See the NOTICE file |
| | | * distributed with this work for additional information |
| | | * regarding copyright ownership. The ASF licenses this file |
| | | * to you under the Apache License, Version 2.0 (the |
| | | * "License"); you may not use this file except in compliance |
| | | * with the License. You may obtain a copy of the License at |
| | | * |
| | | * http://www.apache.org/licenses/LICENSE-2.0 |
| | | * |
| | | * Unless required by applicable law or agreed to in writing, |
| | | * software distributed under the License is distributed on an |
| | | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| | | * KIND, either express or implied. See the License for the |
| | | * specific language governing permissions and limitations |
| | | * under the License. |
| | | */ |
| | | package com.blakequ.bluetooth_manager_lib; |
| | | |
| | | /** |
| | | * Indicates that Bluetooth Low Energy is not available on this device |
| | | * @see ScanManager#checkAvailability() |
| | | * @author David G. Young |
| | | * |
| | | */ |
| | | public class BleNotAvailableException extends RuntimeException { |
| | | |
| | | private static final long serialVersionUID = 2242747823097637729L; |
| | | |
| | | public BleNotAvailableException(String message) { |
| | | super(message); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib; |
| | | |
| | | |
| | | import androidx.annotation.IntRange; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectConfig; |
| | | |
| | | import static com.blakequ.bluetooth_manager_lib.scan.BackgroundPowerSaver.DEFAULT_BACKGROUND_BETWEEN_SCAN_PERIOD; |
| | | import static com.blakequ.bluetooth_manager_lib.scan.BackgroundPowerSaver.DEFAULT_BACKGROUND_SCAN_PERIOD; |
| | | import static com.blakequ.bluetooth_manager_lib.scan.BackgroundPowerSaver.DEFAULT_FOREGROUND_BETWEEN_SCAN_PERIOD; |
| | | import static com.blakequ.bluetooth_manager_lib.scan.BackgroundPowerSaver.DEFAULT_FOREGROUND_SCAN_PERIOD; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/12/1 14:28 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description: config of ble connect and scan |
| | | */ |
| | | |
| | | public class BleParamsOptions { |
| | | |
| | | private final boolean isDebugMode; |
| | | |
| | | //using for scan |
| | | private final long foregroundScanPeriod; |
| | | private final long foregroundBetweenScanPeriod; |
| | | private final long backgroundScanPeriod; |
| | | private final long backgroundBetweenScanPeriod; |
| | | |
| | | //using for connect |
| | | private final int maxConnectDeviceNum; //䏿¬¡æå¤§è¿æ¥è®¾å¤ä¸ªæ° |
| | | private final int reconnectStrategy; //éè¿çç¥ |
| | | private final int reconnectMaxTimes; //æå¤§éè¿æ¬¡æ° |
| | | private final long reconnectBaseSpaceTime; //éè¿åºç¡æ¶é´é´éms |
| | | private final int reconnectedLineToExponentTimes; //å¿«ééè¿ç次æ°(线æ§å°ææ°) |
| | | private final int connectTimeOutTimes; //è¿æ¥è¶
æ¶æ¶é´ |
| | | |
| | | public boolean isDebugMode() { |
| | | return isDebugMode; |
| | | } |
| | | |
| | | public long getForegroundScanPeriod() { |
| | | return foregroundScanPeriod; |
| | | } |
| | | |
| | | public long getForegroundBetweenScanPeriod() { |
| | | return foregroundBetweenScanPeriod; |
| | | } |
| | | |
| | | public long getBackgroundScanPeriod() { |
| | | return backgroundScanPeriod; |
| | | } |
| | | |
| | | public long getBackgroundBetweenScanPeriod() { |
| | | return backgroundBetweenScanPeriod; |
| | | } |
| | | |
| | | public int getMaxConnectDeviceNum() { |
| | | return maxConnectDeviceNum; |
| | | } |
| | | |
| | | public int getReconnectStrategy() { |
| | | return reconnectStrategy; |
| | | } |
| | | |
| | | public int getReconnectMaxTimes() { |
| | | return reconnectMaxTimes; |
| | | } |
| | | |
| | | public long getReconnectBaseSpaceTime() { |
| | | return reconnectBaseSpaceTime; |
| | | } |
| | | |
| | | public int getReconnectedLineToExponentTimes() { |
| | | return reconnectedLineToExponentTimes; |
| | | } |
| | | |
| | | public int getConnectTimeOutTimes() { |
| | | return connectTimeOutTimes; |
| | | } |
| | | |
| | | private BleParamsOptions(Builder builder){ |
| | | this.isDebugMode = builder.isDebugMode; |
| | | this.foregroundScanPeriod = builder.foregroundScanPeriod; |
| | | this.foregroundBetweenScanPeriod = builder.foregroundBetweenScanPeriod; |
| | | this.backgroundScanPeriod = builder.backgroundScanPeriod; |
| | | this.backgroundBetweenScanPeriod = builder.backgroundBetweenScanPeriod; |
| | | this.maxConnectDeviceNum = builder.maxConnectDeviceNum; |
| | | this.reconnectStrategy = builder.reconnectStrategy; |
| | | this.reconnectMaxTimes = builder.reconnectMaxTimes; |
| | | this.reconnectBaseSpaceTime = builder.reconnectBaseSpaceTime; |
| | | this.reconnectedLineToExponentTimes = builder.reconnectedLineToExponentTimes; |
| | | this.connectTimeOutTimes = builder.connectTimeOutTimes; |
| | | } |
| | | |
| | | public static class Builder { |
| | | |
| | | private boolean isDebugMode = true; |
| | | private long foregroundScanPeriod = DEFAULT_FOREGROUND_SCAN_PERIOD; |
| | | private long foregroundBetweenScanPeriod = DEFAULT_FOREGROUND_BETWEEN_SCAN_PERIOD; |
| | | private long backgroundScanPeriod = DEFAULT_BACKGROUND_SCAN_PERIOD; |
| | | private long backgroundBetweenScanPeriod = DEFAULT_BACKGROUND_BETWEEN_SCAN_PERIOD; |
| | | private int maxConnectDeviceNum = 5;//䏿¬¡æå¤§è¿æ¥è®¾å¤ä¸ªæ° |
| | | private int reconnectStrategy = ConnectConfig.RECONNECT_LINE_EXPONENT; //éè¿çç¥ |
| | | private int reconnectMaxTimes = Integer.MAX_VALUE; //æå¤§éè¿æ¬¡æ° |
| | | private long reconnectBaseSpaceTime = 8000; //éè¿åºç¡æ¶é´é´éms |
| | | private int reconnectedLineToExponentTimes = 5; //å¿«ééè¿ç次æ°(线æ§å°ææ°) |
| | | private int connectTimeOutTimes = 15000; //è¿æ¥è¶
æ¶æ¶é´15s |
| | | |
| | | /** |
| | | * setting is debug mode, if false then the log will disable |
| | | * @param isDebugMode you can set by BuildConfig.DEBUG, default is true |
| | | * @return |
| | | */ |
| | | public Builder setDebugMode(boolean isDebugMode){ |
| | | this.isDebugMode = isDebugMode; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Sets the duration in milliseconds of each Bluetooth LE scan cycle to look for beacons. |
| | | * This function is used to setup the period when switching |
| | | * between background/foreground. To have it effect on an already running scan (when the next |
| | | * cycle starts) |
| | | * |
| | | * @param foregroundScanPeriod defalut is 10 seconds, you should using milliseconds |
| | | * @return |
| | | */ |
| | | public Builder setForegroundScanPeriod(@IntRange(from = 0) long foregroundScanPeriod) { |
| | | if (foregroundScanPeriod < 0){ |
| | | throw new IllegalArgumentException("Period time must > 0, now is "+foregroundScanPeriod); |
| | | } |
| | | this.foregroundScanPeriod = foregroundScanPeriod; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Sets the duration in milliseconds between each Bluetooth LE scan cycle to look for beacons. |
| | | * This function is used to setup the period when switching |
| | | * between background/foreground. To have it effect on an already running scan (when the next |
| | | * cycle starts) |
| | | * @param foregroundBetweenScanPeriod defalut is 5 seconds, you should using milliseconds |
| | | * @return |
| | | */ |
| | | public Builder setForegroundBetweenScanPeriod(@IntRange(from = 0) long foregroundBetweenScanPeriod) { |
| | | if (foregroundBetweenScanPeriod < 0){ |
| | | throw new IllegalArgumentException("Period time must > 0, now is "+foregroundBetweenScanPeriod); |
| | | } |
| | | this.foregroundBetweenScanPeriod = foregroundBetweenScanPeriod; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Sets the duration in milliseconds of each Bluetooth LE scan cycle to look for beacons. |
| | | * This function is used to setup the period when switching |
| | | * between background/foreground. To have it effect on an already running scan (when the next |
| | | * cycle starts) |
| | | * @param backgroundScanPeriod default is 10 seconds, you should using milliseconds |
| | | * @return |
| | | */ |
| | | public Builder setBackgroundScanPeriod(@IntRange(from = 0) long backgroundScanPeriod) { |
| | | if (backgroundScanPeriod < 0){ |
| | | throw new IllegalArgumentException("Period time must > 0, now is "+backgroundScanPeriod); |
| | | } |
| | | this.backgroundScanPeriod = backgroundScanPeriod; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Sets the duration in milliseconds spent not scanning between each Bluetooth LE scan cycle when no ranging/monitoring clients are in the foreground |
| | | * @param backgroundBetweenScanPeriod default is 5 minutes, you should using milliseconds |
| | | * @return |
| | | */ |
| | | public Builder setBackgroundBetweenScanPeriod(@IntRange(from = 0) long backgroundBetweenScanPeriod) { |
| | | if (backgroundBetweenScanPeriod < 0){ |
| | | throw new IllegalArgumentException("Period time must > 0, now is "+backgroundBetweenScanPeriod); |
| | | } |
| | | this.backgroundBetweenScanPeriod = backgroundBetweenScanPeriod; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * max number of connect ble device |
| | | * @param maxConnectDeviceNum default is 5 |
| | | * @return |
| | | */ |
| | | public Builder setMaxConnectDeviceNum(@IntRange(from = 1) int maxConnectDeviceNum) { |
| | | if (maxConnectDeviceNum < 1){ |
| | | throw new IllegalArgumentException("Connect device number must > 1, now is "+maxConnectDeviceNum); |
| | | } |
| | | this.maxConnectDeviceNum = maxConnectDeviceNum; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * how to reconnect, you have choose reconnectStrategy: |
| | | * <ol> |
| | | * <li>1. ConnectConfig.RECONNECT_LINEAR, each reconnection interval time is the same</li> |
| | | * <li>2. ConnectConfig.RECONNECT_EXPONENT, the reconnect time interval is exponential growth</li> |
| | | * <li>3. ConnectConfig.RECONNECT_LINE_EXPONENT, Start time interval is the same, after reconnectedLineToExponentTimes times then use exponential growth<li/> |
| | | * </ol> |
| | | * @param reconnectStrategy |
| | | * @return |
| | | */ |
| | | public Builder setReconnectStrategy(@IntRange(from = 1, to = 4) int reconnectStrategy) { |
| | | if (reconnectStrategy < 1 || reconnectStrategy > 4){ |
| | | throw new IllegalArgumentException("reconnectStrategy range is 1 to 4"); |
| | | } |
| | | this.reconnectStrategy = reconnectStrategy; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * usable only the reconnect strategy is ConnectConfig.RECONNECT_LINE_EXPONENT |
| | | * @param reconnectedLineToExponentTimes the times from linear to exponential, default is 5 times |
| | | * @return |
| | | */ |
| | | public Builder setReconnectedLineToExponentTimes(@IntRange(from = 1) int reconnectedLineToExponentTimes) { |
| | | if (reconnectedLineToExponentTimes < 1){ |
| | | throw new IllegalArgumentException("reconnectedLineToExponentTimes value must >= 1, now is "+reconnectedLineToExponentTimes); |
| | | } |
| | | this.reconnectedLineToExponentTimes = reconnectedLineToExponentTimes; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * max reconnect times, if you set 0 will not reconnect |
| | | * @param reconnectMaxTimes default is Integer.MAX_VALUE |
| | | * @return |
| | | */ |
| | | public Builder setReconnectMaxTimes(@IntRange(from = 0) int reconnectMaxTimes) { |
| | | if (reconnectMaxTimes < 0){ |
| | | throw new IllegalArgumentException("Reconnect max times must > 0, now is "+reconnectMaxTimes); |
| | | } |
| | | this.reconnectMaxTimes = reconnectMaxTimes; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * reconnect interval time(>=1000ms) |
| | | * @param reconnectBaseSpaceTime default is 8 seconds |
| | | * @return |
| | | */ |
| | | public Builder setReconnectBaseSpaceTime(@IntRange(from = 1000) long reconnectBaseSpaceTime) { |
| | | if (reconnectBaseSpaceTime < 1000){ |
| | | throw new IllegalArgumentException("reconnectBaseSpaceTime must >= 1000ms, now is "+reconnectBaseSpaceTime); |
| | | } |
| | | this.reconnectBaseSpaceTime = reconnectBaseSpaceTime; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * time out of connect device(after this time will check bluetooth state, if bluetooth not available will close all connect) |
| | | * @param connectTimeOutTimes default is 15 seconds(must > 1 seconds) |
| | | * @return |
| | | */ |
| | | public Builder setConnectTimeOutTimes(@IntRange(from = 1000) int connectTimeOutTimes){ |
| | | if (connectTimeOutTimes < 1000){ |
| | | throw new IllegalArgumentException("connectTimeOutTimes must >= 1000ms, now is "+connectTimeOutTimes); |
| | | } |
| | | this.connectTimeOutTimes = connectTimeOutTimes; |
| | | return this; |
| | | } |
| | | |
| | | /** Builds configured {@link BleParamsOptions} object */ |
| | | public BleParamsOptions build() { |
| | | return new BleParamsOptions(this); |
| | | } |
| | | } |
| | | |
| | | public static BleParamsOptions createDefault() { |
| | | return new Builder().build(); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.connect; |
| | | |
| | | import android.annotation.SuppressLint; |
| | | import android.annotation.TargetApi; |
| | | import android.bluetooth.BluetoothGatt; |
| | | import android.bluetooth.BluetoothGattCallback; |
| | | import android.bluetooth.BluetoothGattCharacteristic; |
| | | import android.bluetooth.BluetoothGattDescriptor; |
| | | import android.bluetooth.BluetoothGattService; |
| | | import android.bluetooth.BluetoothProfile; |
| | | import android.content.Context; |
| | | import android.os.Build; |
| | | import android.os.Handler; |
| | | import android.os.Looper; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.util.BluetoothUtils; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | |
| | | import java.lang.reflect.Field; |
| | | import java.util.List; |
| | | import java.util.Queue; |
| | | import java.util.UUID; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/19 11:16 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description:bluetooth connect interface |
| | | */ |
| | | public abstract class BluetoothConnectInterface { |
| | | protected static final String TAG = "BluetoothConnectInterface"; |
| | | protected Context context; |
| | | private BluetoothOperatorQueue mOpratorQueue; |
| | | private Handler mHandler = new Handler(Looper.getMainLooper()); |
| | | |
| | | public BluetoothConnectInterface(Context context){ |
| | | this.context = context; |
| | | mOpratorQueue = new BluetoothOperatorQueue(); |
| | | } |
| | | |
| | | /** |
| | | * release resource |
| | | */ |
| | | public abstract void release(); |
| | | |
| | | /** |
| | | * get gatt connect callback |
| | | * @return |
| | | */ |
| | | protected abstract BluetoothGattCallback getBluetoothGattCallback(); |
| | | |
| | | protected abstract String getServiceUUID(); |
| | | |
| | | /** |
| | | * get device gatt service, if not will return null |
| | | * @param address |
| | | * @return null if not find gatt service |
| | | */ |
| | | public abstract BluetoothGatt getBluetoothGatt(String address); |
| | | |
| | | /** |
| | | * get the list of subscribe |
| | | * @return |
| | | */ |
| | | protected abstract Queue<BluetoothSubScribeData> getSubscribeDataQueue(); |
| | | |
| | | /** |
| | | * invoke when bluetooth disconnect |
| | | * @param gatt |
| | | */ |
| | | protected abstract void onDeviceDisconnect(BluetoothGatt gatt, int errorState); |
| | | |
| | | /** |
| | | * invoke when bluetooth connected |
| | | * @param gatt |
| | | */ |
| | | protected abstract void onDeviceConnected(BluetoothGatt gatt); |
| | | |
| | | /** |
| | | * invoke when fail to discover service |
| | | * @param gatt |
| | | */ |
| | | protected abstract void onDiscoverServicesFail(BluetoothGatt gatt); |
| | | |
| | | /** |
| | | * invoke when success to discover service |
| | | * @param gatt |
| | | */ |
| | | protected abstract void onDiscoverServicesSuccess(BluetoothGatt gatt); |
| | | |
| | | /** |
| | | * Runs the specified action on the UI thread. If the current thread is the UI |
| | | * thread, then the action is executed immediately. If the current thread is |
| | | * not the UI thread, the action is posted to the event queue of the UI thread. |
| | | * |
| | | * @param action the action to run on the UI thread |
| | | */ |
| | | public final void runOnUiThread(Runnable action) { |
| | | if (Thread.currentThread() != Looper.getMainLooper().getThread()) { |
| | | mHandler.post(action); |
| | | } else { |
| | | action.run(); |
| | | } |
| | | } |
| | | |
| | | protected Handler getMainLooperHandler(){ |
| | | return mHandler; |
| | | } |
| | | |
| | | protected BluetoothGattCallback gattCallback = new BluetoothGattCallback() { |
| | | @Override |
| | | public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { |
| | | super.onCharacteristicChanged(gatt, characteristic); |
| | | if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onCharacteristicChanged(gatt, characteristic); |
| | | } |
| | | |
| | | @Override |
| | | public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { |
| | | super.onCharacteristicRead(gatt, characteristic, status); |
| | | LogUtils.i("onCharacteristicRead data status:" + GattError.parseConnectionError(status) + " " + characteristic.getUuid().toString()); |
| | | mOpratorQueue.nextOperator(); |
| | | if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onCharacteristicRead(gatt, characteristic, status); |
| | | } |
| | | |
| | | @Override |
| | | public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { |
| | | super.onCharacteristicWrite(gatt, characteristic, status); |
| | | LogUtils.i("onCharacteristicWrite write status:" + GattError.parseConnectionError(status)); |
| | | mOpratorQueue.nextOperator(); |
| | | if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onCharacteristicWrite(gatt, characteristic, status); |
| | | } |
| | | |
| | | @Override |
| | | public void onConnectionStateChange(final BluetoothGatt gatt, int status, final int newState) { |
| | | super.onConnectionStateChange(gatt, status, newState); |
| | | //status=133æ¯GATT_ERRORé误http://stackoverflow.com/questions/25330938/android-bluetoothgatt-status-133-register-callback |
| | | //http://www.loverobots.cn/android-ble-connection-solution-bluetoothgatt-status-133.html |
| | | LogUtils.i("onConnectionStateChange gattStatus=" + GattError.parseConnectionError(status) + " newStatus=" |
| | | + (newState == BluetoothProfile.STATE_CONNECTED ? "CONNECTED" : "DISCONNECTED")); |
| | | |
| | | //ä¸åçææºå½èçå
³éï¼è®¾å¤æå¼ï¼éå¯ï¼è¿ç¦»ï¼è¿åçç¶æä¸ä¸æ ·ï¼newStateé½ä¸æ ·æ¯DISCONNECTEDï¼è®¾å¤åæ¢ä¸ä¼äº§çå½±å |
| | | if (status == BluetoothGatt.GATT_SUCCESS) { |
| | | if (newState == BluetoothProfile.STATE_CONNECTED) {//è°ç¨connectä¼è°ç¨ |
| | | LogUtils.i("Connected to GATT server"); |
| | | // Attempts to discover services after successful connection. |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | onDeviceConnected(gatt); |
| | | if (gatt != null && !gatt.discoverServices()) { |
| | | LogUtils.e("onConnectionStateChange start service discovery fail! Thread:" + Thread.currentThread()); |
| | | } |
| | | } |
| | | }); |
| | | } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {//è°ç¨disconnectä¼è°ç¨ï¼è®¾å¤æå¼æèçå
³éä¼è¿å
¥ |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | onDeviceDisconnect(gatt, newState); |
| | | } |
| | | }); |
| | | } |
| | | } else{ //è°ç¨connectådisconnectåºéåä¼è¿å
¥,è®¾å¤æå¼æèçå
³éä¼è¿å
¥ |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | onDeviceDisconnect(gatt, newState); |
| | | } |
| | | }); |
| | | } |
| | | if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onConnectionStateChange(gatt, status, newState); |
| | | } |
| | | |
| | | @Override |
| | | public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { |
| | | super.onDescriptorRead(gatt, descriptor, status); |
| | | LogUtils.i("onDescriptorRead status=" + GattError.parseConnectionError(status)); |
| | | mOpratorQueue.nextOperator(); |
| | | if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onDescriptorRead(gatt, descriptor, status); |
| | | } |
| | | |
| | | @Override |
| | | public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { |
| | | super.onDescriptorWrite(gatt, descriptor, status); |
| | | LogUtils.i("onDescriptorWrite status=" + GattError.parseConnectionError(status)); |
| | | mOpratorQueue.nextOperator(); |
| | | if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onDescriptorWrite(gatt, descriptor, status); |
| | | } |
| | | |
| | | @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
| | | @Override |
| | | public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { |
| | | super.onMtuChanged(gatt, mtu, status); |
| | | if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onMtuChanged(gatt, mtu, status); |
| | | } |
| | | |
| | | @Override |
| | | public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { |
| | | super.onReadRemoteRssi(gatt, rssi, status); |
| | | if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onReadRemoteRssi(gatt, rssi, status); |
| | | } |
| | | |
| | | @Override |
| | | public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { |
| | | super.onReliableWriteCompleted(gatt, status); |
| | | if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onReliableWriteCompleted(gatt, status); |
| | | } |
| | | |
| | | @Override |
| | | public void onServicesDiscovered(final BluetoothGatt gatt, int status) { |
| | | LogUtils.i("onServicesDiscovered status=" + GattError.parseConnectionError(status)); |
| | | if (status == BluetoothGatt.GATT_SUCCESS) { |
| | | //start subscribe data |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | onDiscoverServicesSuccess(gatt); |
| | | if (gatt != null){ |
| | | startSubscribe(gatt); |
| | | } |
| | | } |
| | | }); |
| | | }else { |
| | | LogUtils.e("onServicesDiscovered fail!"); |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | onDiscoverServicesFail(gatt); |
| | | } |
| | | }); |
| | | } |
| | | if (getBluetoothGattCallback() != null) getBluetoothGattCallback().onServicesDiscovered(gatt, status); |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * start subscribe data, add data to subscribe list before invoke this method |
| | | * <p>You should invoke this method after onServicesDiscovered, otherwise can not find service<p/> |
| | | * @param bluetoothGatt |
| | | * @return boolean is success start read or write character |
| | | */ |
| | | public boolean startSubscribe(BluetoothGatt bluetoothGatt){ |
| | | if (bluetoothGatt == null){ |
| | | LogUtils.e("Fail to subscribe, BluetoothGatt is null"); |
| | | return false; |
| | | } |
| | | boolean isSuccess = subscribe(bluetoothGatt.getDevice().getAddress()); |
| | | mOpratorQueue.start(bluetoothGatt); |
| | | return isSuccess; |
| | | } |
| | | |
| | | /** |
| | | * 订é
èç设å¤éç¥åè¯»åæ°æ® |
| | | * @return |
| | | */ |
| | | protected boolean subscribe(String address){ |
| | | BluetoothGatt mBluetoothGatt = getBluetoothGatt(address); |
| | | if (mBluetoothGatt == null){ |
| | | LogUtils.e("can not subscribe to ble device info "+address); |
| | | return false; |
| | | } |
| | | mOpratorQueue.clean(); |
| | | |
| | | if (isEmpty(getServiceUUID())){ |
| | | LogUtils.e("Service UUID is null"); |
| | | return false; |
| | | } |
| | | |
| | | //check subscribe list |
| | | if (getSubscribeDataQueue() == null && getSubscribeDataQueue().size() > 0){ |
| | | LogUtils.e("Subscribe BLE data is null, you must invoke addBluetoothSubscribeData to add data"); |
| | | return false; |
| | | } |
| | | |
| | | BluetoothGattService gattService = mBluetoothGatt.getService(UUID.fromString(getServiceUUID())); |
| | | if (gattService != null){ |
| | | for (BluetoothSubScribeData data:getSubscribeDataQueue()){ |
| | | final BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(data.getCharacteristicUUID()); |
| | | if (characteristic != null){ |
| | | switch (data.getOperatorType()){ |
| | | case CHAR_WIRTE: |
| | | if (BluetoothUtils.isCharacteristicWrite(characteristic.getProperties())){ |
| | | characteristic.setValue(data.getCharacteristicValue()); |
| | | mOpratorQueue.addOperator(characteristic, true); |
| | | }else{ |
| | | LogUtils.e("Fail to write characteristic, not have write property , uuid:"+characteristic.getUuid()+" ,property:"+characteristic.getProperties()); |
| | | } |
| | | break; |
| | | case CHAR_READ: |
| | | //bug fix:samsung phone bug, can not read value |
| | | if (checkIsSamsung()){ |
| | | setProperty(characteristic); |
| | | } |
| | | if(BluetoothUtils.isCharacteristicRead(characteristic.getProperties())){ |
| | | mOpratorQueue.addOperator(characteristic, false); |
| | | }else{ |
| | | LogUtils.e("Fail to read characteristic, not have read property , uuid:" + characteristic.getUuid() + " ,property:" + characteristic.getProperties()); |
| | | } |
| | | break; |
| | | case DESC_READ: |
| | | BluetoothGattDescriptor descriptor = characteristic.getDescriptor(data.getDescriptorUUID()); |
| | | if (descriptor != null){ |
| | | mOpratorQueue.addOperator(descriptor, false); |
| | | }else { |
| | | LogUtils.e("Fail to get descriptor read uuid:"+data.getDescriptorUUID()); |
| | | } |
| | | break; |
| | | case DESC_WRITE: |
| | | BluetoothGattDescriptor descriptor2 = characteristic.getDescriptor(data.getDescriptorUUID()); |
| | | if (descriptor2 != null){ |
| | | descriptor2.setValue(data.getDescriptorValue()); |
| | | mOpratorQueue.addOperator(descriptor2, true); |
| | | }else { |
| | | LogUtils.e("Fail to get descriptor write uuid:"+data.getDescriptorUUID()); |
| | | } |
| | | break; |
| | | case NOTIFY: |
| | | if(BluetoothUtils.isCharacteristicNotify(characteristic.getProperties())){ |
| | | mBluetoothGatt.setCharacteristicNotification(characteristic, true); |
| | | BluetoothGattDescriptor descriptor3 = characteristic.getDescriptor(data.getDescriptorUUID()); |
| | | if (descriptor3 != null){ |
| | | if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0){ |
| | | descriptor3.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); |
| | | }else if((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0){ |
| | | //ä¸¤ä¸ªé½æ¯éç¥çææï¼notifyåindicationçåºå«å¨äºï¼notifyåªæ¯å°ä½ è¦åçæ°æ®åéç»ææºï¼æ²¡æç¡®è®¤æºå¶ï¼ |
| | | //ä¸ä¼ä¿è¯æ°æ®å鿝å¦å°è¾¾ãèindicationçæ¹å¼å¨ææºæ¶å°æ°æ®æ¶ä¼ä¸»å¨åä¸ä¸ªack忥ãå³æç¡®è®¤æºå¶ï¼åªææ¶ |
| | | //å°è¿ä¸ªackä½ æè½ç»§ç»åéä¸ä¸ä¸ªæ°æ®ãè¿ä¿è¯äºæ°æ®çæ£ç¡®å°è¾¾ï¼ä¹èµ·å°äºæµæ§çä½ç¨ãæä»¥å¨æå¼éç¥çæ¶åï¼éè¦è®¾ç½®ä¸ä¸ã |
| | | descriptor3.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); |
| | | } |
| | | mOpratorQueue.addOperator(descriptor3, true); |
| | | }else { |
| | | LogUtils.e("Fail to get notify descriptor uuid:"+data.getDescriptorUUID()); |
| | | } |
| | | }else{ |
| | | LogUtils.e("Fail to notify characteristic, not have notify property , uuid:" + characteristic.getUuid() + " ,property:" + characteristic.getProperties()); |
| | | } |
| | | break; |
| | | } |
| | | }else { |
| | | LogUtils.e("Fail to get characteristic service uuid:"+data.getCharacteristicUUID()); |
| | | } |
| | | } |
| | | }else { |
| | | LogUtils.e("Can not get gatt service uuid:"+getServiceUUID()); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * è®¾ç½®å±æ§,设置读æé |
| | | * @param flagReadChar |
| | | */ |
| | | @SuppressLint("SoonBlockedPrivateApi") |
| | | private void setProperty(BluetoothGattCharacteristic flagReadChar){ |
| | | Field properField = null; |
| | | try { |
| | | properField = flagReadChar.getClass().getDeclaredField("mProperties"); |
| | | properField.setAccessible(true); |
| | | properField.set(flagReadChar, 10); |
| | | } catch (NoSuchFieldException e) { |
| | | e.printStackTrace(); |
| | | } catch (IllegalAccessException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å¤æææºç±»å |
| | | * @return |
| | | */ |
| | | protected boolean checkIsSamsung() { |
| | | String brand = android.os.Build.BRAND; |
| | | if (brand.toLowerCase().equals("samsung")) { |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | public boolean isEmpty(String str) { |
| | | return str == null || str.length() == 0; |
| | | } |
| | | |
| | | public <V> boolean isEmpty(List<V> sourceList) { |
| | | return sourceList == null || sourceList.size() == 0; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.connect; |
| | | |
| | | import android.annotation.TargetApi; |
| | | import android.bluetooth.BluetoothAdapter; |
| | | import android.bluetooth.BluetoothDevice; |
| | | import android.bluetooth.BluetoothGatt; |
| | | import android.bluetooth.BluetoothGattCallback; |
| | | import android.bluetooth.BluetoothManager; |
| | | import android.bluetooth.BluetoothProfile; |
| | | import android.content.Context; |
| | | import android.os.Looper; |
| | | import android.os.SystemClock; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.BleManager; |
| | | import com.blakequ.bluetooth_manager_lib.BleParamsOptions; |
| | | import com.blakequ.bluetooth_manager_lib.util.BluetoothUtils; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Queue; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.ConcurrentLinkedQueue; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/18 11:29 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description: ç¨äºå½åèççè¿æ¥ç®¡çï¼è´è´£è¿æ¥çå»ºç«æå¼ï¼ä½¿ç¨æ¶éè¦è®¾ç½®åè°{@link #setBluetoothGattCallback(BluetoothGattCallback)}, |
| | | * è¿æ¥{@link #connect(String)},æå¼è¿æ¥{@link #disconnect(String)}, å
³éè¿æ¥{@link #close(String)}. |
| | | * 注æï¼<br> |
| | | * 1.å¨è¿è¡èçæå¼åè¿æ¥è°ç¨çæ¶åï¼éè¦å¨ä¸»çº¿ç¨æ§è¡ï¼å¦åå¨ä¸æææºä¼åºç°è®¸å¤å¼å¸¸éè¯¯ææ æ³è¿æ¥çæ
åµ<br> |
| | | * 2.è¯¥è¿æ¥ç®¡çåªè½è¿æ¥ä¸ä¸ªè®¾å¤ï¼ä¸æ¯æåæ¶è¿æ¥å¤ä¸ªè®¾å¤<br> |
| | | * 3.å¯ä»¥èªå®ä¹æå¼åéè¿æ¬¡æ°åéè¿çé´éæ¶é´<br> |
| | | * 4.妿è¦è®¢é
æå¡æ°æ®ï¼read,write,notifyï¼Service UUID {@link #setServiceUUID(String)}ï¼ å¦åä¸è½èªå¨çè¿è¡éç¥åcharådescç读åæä½ï¼è¿éè¦{@link #addBluetoothSubscribeData(BluetoothSubScribeData)}ï¼<br> |
| | | * 5.åç¬è®¢é
æ°æ®ï¼éè¦è°ç¨{@link #cleanSubscribeData()}æ¸
é¤è®¢é
åå²å表, {@link #addBluetoothSubscribeData(BluetoothSubScribeData)}æ·»å åæ°, {@link #startSubscribe(BluetoothGatt)}å¯å¨è®¢é
ï¼ä¼èªå¨åè°{@link #setBluetoothGattCallback(BluetoothGattCallback)}订é
ç»æ |
| | | */ |
| | | @TargetApi(18) |
| | | public final class BluetoothConnectManager extends BluetoothConnectInterface{ |
| | | private static final String TAG = "BluetoothConnectManager"; |
| | | |
| | | private static BluetoothConnectManager INSTANCE = null; |
| | | private final BluetoothUtils mBluetoothUtils; |
| | | private BluetoothGattCallback mBluetoothGattCallback; |
| | | private BluetoothManager bluetoothManager; |
| | | private final Map<String, BluetoothGatt> gattMap; //ä¿åè¿æ¥è¿çgatt |
| | | private final Queue<BluetoothSubScribeData> subscribeQueue; |
| | | private static String serviceUUID; |
| | | private ReconnectParamsBean reconnectParamsBean; |
| | | private List<ConnectStateListener> connectStateListeners; |
| | | private ConnectState currentState = ConnectState.NORMAL; |
| | | private static Object obj = new Object(); |
| | | |
| | | public BluetoothConnectManager(Context context) { |
| | | super(context); |
| | | subscribeQueue = new ConcurrentLinkedQueue<BluetoothSubScribeData>(); |
| | | mBluetoothUtils = BluetoothUtils.getInstance(context); |
| | | bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); |
| | | gattMap = new ConcurrentHashMap<String, BluetoothGatt>(); //伿并åçæå¼åè¿æ¥ï¼æ
èå¿
须使ç¨å¹¶åConcurrentHashMapæè¡ï¼å¦å伿ConcurrentModificationException |
| | | connectStateListeners = new ArrayList<>(); |
| | | BleManager.getBleParamsOptions(); |
| | | } |
| | | |
| | | @Override |
| | | public void release() { |
| | | closeAll(); |
| | | gattMap.clear(); |
| | | reconnectParamsBean = null; |
| | | } |
| | | |
| | | |
| | | public static BluetoothConnectManager getInstance(Context context){ |
| | | if (INSTANCE == null){ |
| | | synchronized (obj){ |
| | | if (INSTANCE == null){ |
| | | INSTANCE = new BluetoothConnectManager(context); |
| | | } |
| | | } |
| | | } |
| | | return INSTANCE; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * add callback of gatt connect, notice:<br> |
| | | * 1. can not do any task which need a lot of time<br> |
| | | * 2. you should update UI in the main thread, in callback method use {@link #runOnUiThread(Runnable)} |
| | | * @param callback |
| | | * @see #runOnUiThread(Runnable) |
| | | */ |
| | | public void setBluetoothGattCallback(BluetoothGattCallback callback){ |
| | | this.mBluetoothGattCallback = callback; |
| | | } |
| | | |
| | | /** |
| | | * add listener of connect state |
| | | * @param listener |
| | | * @see #removeConnectStateListener(ConnectStateListener) |
| | | */ |
| | | public void addConnectStateListener(ConnectStateListener listener){ |
| | | synchronized(connectStateListeners){ |
| | | connectStateListeners.add(listener); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * remove listener |
| | | * @param listener |
| | | * @see #addBluetoothSubscribeData(BluetoothSubScribeData) |
| | | */ |
| | | public void removeConnectStateListener(ConnectStateListener listener){ |
| | | synchronized(connectStateListeners){ |
| | | connectStateListeners.remove(listener); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * add subscribe data while auto read or write characteristic(or descriptor) after discover service, you can clean subscribe list by {@link #cleanSubscribeData()} |
| | | * @param data |
| | | * @see #cleanSubscribeData() |
| | | * @see #startSubscribe(BluetoothGatt) |
| | | * @see #setServiceUUID(String) |
| | | */ |
| | | public void addBluetoothSubscribeData(BluetoothSubScribeData data){ |
| | | subscribeQueue.add(data); |
| | | } |
| | | |
| | | /** |
| | | * clean subscribe list |
| | | * @see #addBluetoothSubscribeData(BluetoothSubScribeData) |
| | | */ |
| | | public void cleanSubscribeData(){ |
| | | subscribeQueue.clear(); |
| | | } |
| | | |
| | | /** |
| | | * set bluetooth service uuid, can not be null |
| | | * @see #addBluetoothSubscribeData(BluetoothSubScribeData) |
| | | * @param serviceUUID |
| | | */ |
| | | public void setServiceUUID(String serviceUUID){ |
| | | this.serviceUUID = serviceUUID; |
| | | } |
| | | |
| | | @Override |
| | | public BluetoothGatt getBluetoothGatt(String address){ |
| | | if (!isEmpty(address) && gattMap.containsKey(address)){ |
| | | return gattMap.get(address); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | | protected BluetoothGattCallback getBluetoothGattCallback() { |
| | | return mBluetoothGattCallback; |
| | | } |
| | | |
| | | @Override |
| | | protected String getServiceUUID() { |
| | | return serviceUUID; |
| | | } |
| | | |
| | | @Override |
| | | protected void onDeviceDisconnect(final BluetoothGatt gatt, int errorState) { |
| | | //is bluetooth enable |
| | | //å¯ä»¥ä¸å
³éï¼ä»¥ä¾¿éç¨ï¼å 为å¨è¿æ¥connectçæ¶åå¯ä»¥å¿«éè¿æ¥ |
| | | if (!checkIsSamsung() || !mBluetoothUtils.isBluetoothIsEnable()){//ä¸æææºæå¼åç´æ¥è¿æ¥ |
| | | LogUtils.e( "Disconnected from GATT server address:"+gatt.getDevice().getAddress()); |
| | | close(gatt.getDevice().getAddress()); //鲿¢åºç°status 133 |
| | | }else { |
| | | updateConnectStateListener(gatt.getDevice().getAddress(), ConnectState.NORMAL); |
| | | } |
| | | |
| | | //if disconnect by hand, so not run reconnect device |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | reconnectDevice(gatt.getDevice().getAddress()); //å¦æè®¾å¤æå¼åæå®æ¶é´åå°è¯éæ°è¿æ¥,éæ°è¿æ¥ |
| | | } |
| | | }); |
| | | } |
| | | |
| | | @Override |
| | | protected void onDeviceConnected(BluetoothGatt gatt) { |
| | | updateConnectStateListener(gatt.getDevice().getAddress(), ConnectState.CONNECTED); |
| | | reconnectParamsBean = null; |
| | | } |
| | | |
| | | @Override |
| | | protected void onDiscoverServicesFail(final BluetoothGatt gatt) { |
| | | if (!checkIsSamsung() || !mBluetoothUtils.isBluetoothIsEnable()){//ä¸æææºæå¼åç´æ¥è¿æ¥ |
| | | LogUtils.e( "Disconnected from GATT server address:"+gatt.getDevice().getAddress()); |
| | | close(gatt.getDevice().getAddress()); //鲿¢åºç°status 133 |
| | | }else { |
| | | updateConnectStateListener(gatt.getDevice().getAddress(), ConnectState.NORMAL); |
| | | } |
| | | |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | reconnectDevice(gatt.getDevice().getAddress()); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | public void updateReconnectParams(){ |
| | | reconnectParamsBean = null; |
| | | } |
| | | |
| | | @Override |
| | | protected void onDiscoverServicesSuccess(BluetoothGatt gatt) { |
| | | } |
| | | |
| | | @Override |
| | | protected Queue<BluetoothSubScribeData> getSubscribeDataQueue() { |
| | | return subscribeQueue; |
| | | } |
| | | |
| | | /** |
| | | * has device is connected or connecting |
| | | * @return |
| | | */ |
| | | public boolean isConnectDevice(){ |
| | | if (gattMap.size() == 0) return false; |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * è·åå·²ç»è¿æ¥ç设å¤,注æï¼è¿åç设å¤ä¸ä¸å®å
¨é¨æ¯å½åAPPæè¿æ¥ç设å¤ï¼éè¦éè¿UUIDæè®¾å¤ååçåºå |
| | | * @return |
| | | */ |
| | | public List<BluetoothDevice> getConnectedDevice(){ |
| | | if (isConnectDevice()){ |
| | | return bluetoothManager.getConnectedDevices(BluetoothProfile.GATT); |
| | | } |
| | | return Collections.EMPTY_LIST; |
| | | } |
| | | |
| | | public ConnectState getCurrentState(){ |
| | | return currentState; |
| | | } |
| | | |
| | | /** |
| | | * Connects to the GATT server hosted on the Bluetooth LE device. 为ä¿è¯åªæä¸ä¸ªè¿æ¥ï¼å½è¿æ¥å建æåå§åæååä¼å¼ºå¶å
³éå
¶ä»è¿æ¥ |
| | | * |
| | | * @param address The device address of the destination device. |
| | | * @return Return true if the connection is initiated successfully. The connection result |
| | | * is reported asynchronously through the |
| | | * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} |
| | | * callback. |
| | | */ |
| | | public boolean connect(final String address) { |
| | | BluetoothAdapter mAdapter = mBluetoothUtils.getBluetoothAdapter(); |
| | | if (mAdapter == null || address == null) { |
| | | LogUtils.w( "BluetoothAdapter not initialized or unspecified address."); |
| | | updateConnectStateListener(address, ConnectState.NORMAL); |
| | | return false; |
| | | } |
| | | |
| | | if (!mBluetoothUtils.isBluetoothIsEnable()){ |
| | | LogUtils.e( "bluetooth is not enable."); |
| | | updateConnectStateListener(address, ConnectState.NORMAL); |
| | | return false; |
| | | } |
| | | |
| | | // Previously connected device. Try to reconnect. |
| | | if (gattMap.containsKey(address)){ |
| | | BluetoothGatt mBluetoothGatt = gattMap.get(address); |
| | | LogUtils.i( "Trying to use an existing gatt and reconnection device " + address + " thread:" + (Thread.currentThread() == Looper.getMainLooper().getThread())); |
| | | if (mBluetoothGatt.connect()) { |
| | | closeOtherDevice(address); |
| | | updateConnectStateListener(address, ConnectState.CONNECTING); |
| | | return true; |
| | | } else { |
| | | close(address); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | BluetoothDevice device = mAdapter.getRemoteDevice(address); |
| | | if (device != null){ |
| | | /*if We want to directly connect to the device, we can setting the autoConnect |
| | | parameter to false.*/ |
| | | BluetoothGatt mBluetoothGatt = device.connectGatt(context, false, gattCallback); |
| | | if (mBluetoothGatt != null){ |
| | | LogUtils.d( "create a new connection address=" + address + " thread:" + (Thread.currentThread() == Looper.getMainLooper().getThread())); |
| | | gattMap.put(address, mBluetoothGatt); |
| | | closeOtherDevice(address); |
| | | updateConnectStateListener(address, ConnectState.CONNECTING); |
| | | return true; |
| | | }else{ |
| | | LogUtils.e( "Get Gatt fail!, address=" + address + " thread:" + (Thread.currentThread() == Looper.getMainLooper().getThread())); |
| | | } |
| | | }else{ |
| | | LogUtils.e( "Device not found, address=" + address); |
| | | } |
| | | updateConnectStateListener(address, ConnectState.NORMAL); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * close bluetooth, release resource |
| | | * @param address |
| | | */ |
| | | public boolean close(String address) { |
| | | if (!isEmpty(address) && gattMap.containsKey(address)){ |
| | | LogUtils.w("close gatt server " + address); |
| | | BluetoothGatt mBluetoothGatt = gattMap.get(address); |
| | | mBluetoothGatt.close(); |
| | | gattMap.remove(address); |
| | | updateConnectStateListener(address, ConnectState.NORMAL); |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * close all bluetooth connect, release all resource |
| | | */ |
| | | public void closeAll(){ |
| | | for (String address:gattMap.keySet()) { |
| | | close(address); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æå¼èçè¿æ¥ï¼ä¸ä¼éæ¾BluetoothGattææçææèµæºï¼å¯ä»¥è°ç¨mBluetoothGatt.connect()å¾å¿«éæ°è¿æ¥ä¸ |
| | | * 妿ä¸åæ¶éæ¾èµæºï¼å¯è½åºç°133é误ï¼http://www.loverobots.cn/android-ble-connection-solution-bluetoothgatt-status-133.html |
| | | * @param address |
| | | */ |
| | | public void disconnect(String address){ |
| | | if (!isEmpty(address) && gattMap.containsKey(address)){ |
| | | reconnectParamsBean = new ReconnectParamsBean(address); |
| | | reconnectParamsBean.setNumber(1000); |
| | | LogUtils.w("disconnect gatt server " + address); |
| | | BluetoothGatt mBluetoothGatt = gattMap.get(address); |
| | | mBluetoothGatt.disconnect(); |
| | | updateConnectStateListener(address, ConnectState.NORMAL); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * éæ°è¿æ¥æå¼çè®¾å¤ |
| | | * @param address |
| | | */ |
| | | private void reconnectDevice(final String address){ |
| | | if (reconnectParamsBean != null){ |
| | | if (!reconnectParamsBean.getAddress().equals(address)){ |
| | | reconnectParamsBean.updateAddress(address); |
| | | }else { |
| | | if (reconnectParamsBean.getNumber() == 0){//same device |
| | | reconnectParamsBean.updateAddress(address); |
| | | }else if(reconnectParamsBean.getNumber() == 1000){//disconnect by hand |
| | | LogUtils.i("reconnect fail! disconnect by hand"); |
| | | reconnectParamsBean.setNumber(0); |
| | | return; |
| | | } |
| | | } |
| | | reconnectParamsBean.addNumber(); |
| | | }else{ |
| | | reconnectParamsBean = new ReconnectParamsBean(address); |
| | | } |
| | | |
| | | //计ç®ä¸ä¸æ¬¡éè¿çæ¶é´ |
| | | long nextReconnectTime = reconnectParamsBean.getNextReconnectTime() - SystemClock.elapsedRealtime(); |
| | | if (nextReconnectTime < 0){ |
| | | nextReconnectTime = 0; |
| | | } |
| | | LogUtils.i("next reconnect time " + reconnectParamsBean.toString()+" after:"+nextReconnectTime/1000+"seconds"); |
| | | |
| | | getMainLooperHandler().postDelayed(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | //éæ°è¿æ¥è¦æ±æ²¡æå·²ç»è¿æ¥ç设å¤ï¼æ²¡ææ£å¨è¿æ¥çï¼èçå¯ç¨ |
| | | if (mBluetoothUtils.isBluetoothIsEnable()) { |
| | | boolean isReconncted = false; |
| | | if (gattMap.containsKey(address)) { |
| | | if (gattMap.size() == 1) isReconncted = true; |
| | | } else if (gattMap.size() == 0) { |
| | | isReconncted = true; |
| | | } |
| | | |
| | | //妿已ç»è¿æ¥ä¸ï¼ä¹ä¸éè¿ |
| | | if (!isEmpty(getConnectedDevice())) { |
| | | isReconncted = false; |
| | | reconnectParamsBean = null; |
| | | } |
| | | |
| | | if (isReconncted && getConnectedDevice().size() == 0) { |
| | | LogUtils.d("reconnecting! will reconnect " + address); |
| | | if (reconnectParamsBean != null){ |
| | | //éè¿å¿
é¡»å¨ä¸»çº¿ç¨è¿è¡ |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | connect(address); |
| | | } |
| | | }); |
| | | }else { |
| | | LogUtils.w("Fail to reconnect, ReconnectParams is null"); |
| | | } |
| | | } else { |
| | | LogUtils.w("Fail to reconnect, refuse! " + address + " flag:" + isReconncted); |
| | | } |
| | | }else{ |
| | | LogUtils.w("Fail to reconnect, the bluetooth is disable!"); |
| | | } |
| | | } |
| | | }, nextReconnectTime); |
| | | } |
| | | |
| | | /** |
| | | * å
³éé¤äºå½åå°åç设å¤å¤çå
¶ä»è¿æ¥ |
| | | * @param address |
| | | */ |
| | | private void closeOtherDevice(String address){ |
| | | if (!isEmpty(address)){ |
| | | //å
³éæ£å¨å·²ç»è¿æ¥çè®¾å¤ |
| | | List<BluetoothDevice> list = getConnectedDevice(); |
| | | for (BluetoothDevice device:list) { |
| | | if (!device.getAddress().equals(address)){ |
| | | close(device.getAddress()); |
| | | } |
| | | } |
| | | |
| | | //å
³éå
¶ä»å·²ç»æå¼çè®¾å¤ |
| | | for (String ads:gattMap.keySet()) { |
| | | BluetoothGatt mBluetoothGatt = gattMap.get(ads); |
| | | if (!ads.equals(address)){ |
| | | close(ads); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void updateConnectStateListener(String address, ConnectState state){ |
| | | synchronized (connectStateListeners){ |
| | | currentState = state; |
| | | if (state == ConnectState.CONNECTING){ |
| | | //start check time out connect |
| | | BleParamsOptions options = BleManager.getBleParamsOptions(); |
| | | getMainLooperHandler().postDelayed(timeOutTask, options.getConnectTimeOutTimes()); |
| | | } |
| | | for (ConnectStateListener listener:connectStateListeners){ |
| | | if (listener != null) listener.onConnectStateChanged(address, state); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * connect time out task |
| | | */ |
| | | private Runnable timeOutTask = new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | if (!mBluetoothUtils.isBluetoothIsEnable()){ |
| | | LogUtils.w("Fail to connect device! Bluetooth is not enable!"); |
| | | closeAll(); |
| | | } |
| | | } |
| | | }; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.connect; |
| | | |
| | | import android.bluetooth.BluetoothGatt; |
| | | import android.bluetooth.BluetoothGattCharacteristic; |
| | | import android.bluetooth.BluetoothGattDescriptor; |
| | | |
| | | |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | |
| | | import java.util.Queue; |
| | | import java.util.concurrent.ConcurrentLinkedQueue; |
| | | |
| | | /** |
| | | * bluetooth write and read queue oprator |
| | | * Created by PLUSUB on 2015/11/16. |
| | | */ |
| | | public class BluetoothOperatorQueue { |
| | | |
| | | private String uuid; |
| | | private final Queue<SimpleEntity> sWriteQueue = new ConcurrentLinkedQueue<SimpleEntity>(); |
| | | private boolean sIsWriting = false; |
| | | private BluetoothGatt mBluetoothGatt; |
| | | |
| | | public BluetoothOperatorQueue(){ |
| | | } |
| | | |
| | | public synchronized void clean(){ |
| | | sIsWriting = false; |
| | | uuid = null; |
| | | sWriteQueue.clear(); |
| | | } |
| | | |
| | | /** |
| | | * start operator to write or read BluetoothGatt Service |
| | | * @param mBluetoothGatt |
| | | * @exception IllegalArgumentException if mBluetoothGatt is null |
| | | */ |
| | | public void start(BluetoothGatt mBluetoothGatt){ |
| | | if (mBluetoothGatt == null){ |
| | | throw new IllegalArgumentException("BluetoothGatt is null, can not write or read BluetoothGatt Service"); |
| | | } |
| | | this.mBluetoothGatt = mBluetoothGatt; |
| | | nextOperator(); |
| | | } |
| | | |
| | | /** |
| | | * add write or read characteristic operator by order,In order execution |
| | | * @param gattCharacteristic |
| | | * @param isWrite true is write operator, false is read |
| | | */ |
| | | public synchronized void addOperator(BluetoothGattCharacteristic gattCharacteristic, boolean isWrite) { |
| | | SimpleEntity entity = new SimpleEntity(isWrite, gattCharacteristic); |
| | | sWriteQueue.add(entity); |
| | | } |
| | | |
| | | /** |
| | | * add write or read descriptor operator by order,In order execution |
| | | * @param gattDescriptor |
| | | * @param isWrite true is write operator, false is read |
| | | */ |
| | | public synchronized void addOperator(BluetoothGattDescriptor gattDescriptor, boolean isWrite) { |
| | | SimpleEntity entity = new SimpleEntity(isWrite, gattDescriptor); |
| | | sWriteQueue.add(entity); |
| | | } |
| | | |
| | | /** |
| | | * next operator, should invoke by hand |
| | | */ |
| | | public synchronized void nextOperator() { |
| | | sIsWriting = false; |
| | | if (!sWriteQueue.isEmpty() && !sIsWriting) { |
| | | doOperator(sWriteQueue.poll()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * do operator of read or write |
| | | * @param entity |
| | | */ |
| | | private synchronized boolean doOperator(SimpleEntity entity) { |
| | | if (mBluetoothGatt == null){ |
| | | LogUtils.e("do operator fail, bluetoothgatt is null"); |
| | | return false; |
| | | } |
| | | boolean result = true; |
| | | if (entity.obj instanceof BluetoothGattCharacteristic) { |
| | | sIsWriting = true; |
| | | BluetoothGattCharacteristic character = (BluetoothGattCharacteristic) entity.obj; |
| | | uuid = character.getUuid().toString(); |
| | | if (entity.isWrite){ |
| | | result = mBluetoothGatt.writeCharacteristic(character); |
| | | }else{ |
| | | // test(character); |
| | | result = mBluetoothGatt.readCharacteristic(character); |
| | | } |
| | | } else if (entity.obj instanceof BluetoothGattDescriptor) { |
| | | sIsWriting = true; |
| | | BluetoothGattDescriptor desc = (BluetoothGattDescriptor) entity.obj; |
| | | uuid = desc.getUuid().toString(); |
| | | if (entity.isWrite){ |
| | | result = mBluetoothGatt.writeDescriptor(desc); |
| | | }else { |
| | | result = mBluetoothGatt.readDescriptor(desc); |
| | | } |
| | | } else { |
| | | LogUtils.d("do operator next"); |
| | | nextOperator(); |
| | | } |
| | | LogUtils.d("do operator result:"+result+" "+uuid); |
| | | return result; |
| | | } |
| | | |
| | | // private void test(BluetoothGattCharacteristic characteristic){ |
| | | // boolean result = (characteristic.getProperties() & |
| | | // BluetoothGattCharacteristic.PROPERTY_READ) == 0; |
| | | // System.out.println("result:"+result+ " "+characteristic.getProperties()+" "+BluetoothGattCharacteristic.PROPERTY_READ); |
| | | // } |
| | | |
| | | private static class SimpleEntity { |
| | | public boolean isWrite; |
| | | public Object obj; |
| | | public String info; |
| | | |
| | | public SimpleEntity(boolean isWrite, Object obj) { |
| | | this.isWrite = isWrite; |
| | | this.obj = obj; |
| | | } |
| | | |
| | | public SimpleEntity(boolean isWrite, Object obj, String info) { |
| | | this.isWrite = isWrite; |
| | | this.obj = obj; |
| | | this.info = info; |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.connect; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.device.resolvers.GattAttributeResolver; |
| | | |
| | | import java.util.UUID; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/18 17:48 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description: |
| | | */ |
| | | public class BluetoothSubScribeData { |
| | | |
| | | private UUID characteristicUUID; |
| | | private byte[] characteristicValue; |
| | | private UUID descriptorUUID; |
| | | private byte[] descriptorValue; |
| | | //the notification uuid for Characteristic |
| | | private Type operatorType; |
| | | |
| | | private BluetoothSubScribeData(UUID characteristicUUID, byte[] characteristicValue, UUID descriptorUUID |
| | | ,byte[] descriptorValue, Type operatorType){ |
| | | this.characteristicUUID = characteristicUUID; |
| | | this.characteristicValue = characteristicValue; |
| | | this.descriptorUUID = descriptorUUID; |
| | | this.descriptorValue = descriptorValue; |
| | | this.operatorType = operatorType; |
| | | } |
| | | |
| | | private BluetoothSubScribeData(UUID characteristicUUID, Type operatorType){ |
| | | this.characteristicUUID = characteristicUUID; |
| | | this.operatorType = operatorType; |
| | | } |
| | | |
| | | |
| | | public UUID getCharacteristicUUID() { |
| | | return characteristicUUID; |
| | | } |
| | | |
| | | public byte[] getCharacteristicValue() { |
| | | return characteristicValue; |
| | | } |
| | | |
| | | public UUID getDescriptorUUID() { |
| | | return descriptorUUID; |
| | | } |
| | | |
| | | public byte[] getDescriptorValue() { |
| | | return descriptorValue; |
| | | } |
| | | |
| | | public Type getOperatorType() { |
| | | return operatorType; |
| | | } |
| | | |
| | | public static final class Builder { |
| | | private UUID characteristicUUID; |
| | | private byte[] characteristicValue; |
| | | private UUID descriptorUUID; |
| | | private byte[] descriptorValue; |
| | | //the notification uuid for Characteristic |
| | | private Type operatorType; |
| | | |
| | | /** |
| | | * read Characteristic |
| | | * @param characteristicUUID |
| | | * @return |
| | | */ |
| | | public Builder setCharacteristicRead(UUID characteristicUUID){ |
| | | this.operatorType = Type.CHAR_READ; |
| | | this.characteristicUUID = characteristicUUID; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * write Characteristic |
| | | * @param characteristicUUID |
| | | * @param characteristicValue |
| | | * @return |
| | | */ |
| | | public Builder setCharacteristicWrite(UUID characteristicUUID, byte[] characteristicValue){ |
| | | this.operatorType = Type.CHAR_WIRTE; |
| | | this.characteristicUUID = characteristicUUID; |
| | | this.characteristicValue = characteristicValue; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * read Descriptor |
| | | * @param characteristicUUID |
| | | * @param descriptorUUID |
| | | * @return |
| | | */ |
| | | public Builder setDescriptorRead(UUID characteristicUUID, UUID descriptorUUID){ |
| | | this.operatorType = Type.DESC_READ; |
| | | this.characteristicUUID = characteristicUUID; |
| | | this.descriptorUUID = descriptorUUID; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * write Descriptor |
| | | * @param characteristicUUID |
| | | * @param descriptorUUID |
| | | * @param descriptorValue |
| | | * @return |
| | | */ |
| | | public Builder setDescriptorWrite(UUID characteristicUUID, UUID descriptorUUID, byte[] descriptorValue){ |
| | | this.operatorType = Type.DESC_WRITE; |
| | | this.characteristicUUID = characteristicUUID; |
| | | this.descriptorUUID = descriptorUUID; |
| | | this.descriptorValue = descriptorValue; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * get notify |
| | | * @param characteristicNotificationUUID notify characteristic uuid |
| | | * @return |
| | | */ |
| | | public Builder setCharacteristicNotify(UUID characteristicNotificationUUID){ |
| | | this.operatorType = Type.NOTIFY; |
| | | this.characteristicUUID = characteristicNotificationUUID; |
| | | this.descriptorUUID = UUID.fromString(GattAttributeResolver.CLIENT_CHARACTERISTIC_CONFIG); |
| | | return this; |
| | | } |
| | | |
| | | public BluetoothSubScribeData build(){ |
| | | if (characteristicUUID == null){ |
| | | throw new IllegalArgumentException("invalid characteristic, and characteristic can not be null"); |
| | | } |
| | | BluetoothSubScribeData data = null; |
| | | switch (operatorType){ |
| | | case CHAR_READ: |
| | | data = new BluetoothSubScribeData(characteristicUUID, operatorType); |
| | | break; |
| | | case CHAR_WIRTE: |
| | | if (characteristicValue == null){ |
| | | throw new IllegalArgumentException("invalid null characteristic value"); |
| | | } |
| | | data = new BluetoothSubScribeData(characteristicUUID, characteristicValue, null,null, operatorType); |
| | | break; |
| | | case DESC_READ: |
| | | if (descriptorUUID == null){ |
| | | throw new IllegalArgumentException("invalid null descriptor UUID"); |
| | | } |
| | | data = new BluetoothSubScribeData(characteristicUUID, null, descriptorUUID, null, operatorType); |
| | | break; |
| | | case DESC_WRITE: |
| | | if (descriptorUUID == null || descriptorValue == null){ |
| | | throw new IllegalArgumentException("invalid null descriptor UUID or value"); |
| | | } |
| | | data = new BluetoothSubScribeData(characteristicUUID, null, descriptorUUID, descriptorValue, operatorType); |
| | | break; |
| | | case NOTIFY: |
| | | if (descriptorUUID == null){ |
| | | throw new IllegalArgumentException("invalid null descriptor UUID"); |
| | | } |
| | | data = new BluetoothSubScribeData(characteristicUUID, null, descriptorUUID, null, operatorType); |
| | | break; |
| | | } |
| | | return data; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * bluetooth subscribe operator type |
| | | */ |
| | | public static enum Type{ |
| | | CHAR_WIRTE, |
| | | CHAR_READ, |
| | | DESC_WRITE, |
| | | DESC_READ, |
| | | NOTIFY |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.connect; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/29 14:04 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description: |
| | | */ |
| | | public class ConnectConfig { |
| | | public static int maxConnectDeviceNum = 5;//䏿¬¡æå¤§è¿æ¥ä¸ªæ° |
| | | |
| | | /**线æ§é´ééè¿ï¼æ¯æ¬¡æå¼åéè¿æ¶é´æ¯çº¿æ§å¢é¿*/ |
| | | public static final int RECONNECT_LINEAR = 1; |
| | | /**ææ°é´ééæ°*/ |
| | | public static final int RECONNECT_EXPONENT = 2; |
| | | /**å
线æ§åææ°éæ°*/ |
| | | public static final int RECONNECT_LINE_EXPONENT = 3; |
| | | /**åºå®æ¶é´éè¿,æ¯æ¬¡æå¼å齿¯ç¸åæ¶é´ä¹ååèµ·éè¿*/ |
| | | public static final int RECONNECT_FIXED_TIME = 4; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.connect; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/19 15:04 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description: |
| | | */ |
| | | public enum ConnectState { |
| | | /**disconnect or not connect*/ |
| | | NORMAL, |
| | | /**connected*/ |
| | | CONNECTED, |
| | | CONNECTING |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.connect; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/23 16:51 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description: |
| | | */ |
| | | public interface ConnectStateListener { |
| | | |
| | | /** |
| | | * invoke when bluetooth connect state changed |
| | | * @param address bluetooth device address |
| | | * @param state current state |
| | | */ |
| | | void onConnectStateChanged(String address, ConnectState state); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.connect; |
| | | |
| | | /** |
| | | * Copyright (C) quhao All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the Plusub License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * |
| | | * Parses the error numbers according to the <b>gatt_api.h</b> file from bluedroid stack. |
| | | * See: https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/android-5.1.0_r1/stack/include/gatt_api.h (and other versions) for details. |
| | | * See:https://github.com/NordicSemiconductor/Android-DFU-Library/blob/release/dfu/src/main/java/no/nordicsemi/android/error/GattError.java |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> |
| | | * date : 2016/3/29 10:24 |
| | | * last modify author : |
| | | * version : 1.0 |
| | | * description: |
| | | */ |
| | | public class GattError { |
| | | |
| | | // Starts at line 106 of gatt_api.h file |
| | | /** |
| | | * Converts the connection status given by the {@link android.bluetooth.BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} to error name. |
| | | * @param error the status number |
| | | * @return the error name as stated in the gatt_api.h file |
| | | */ |
| | | public static String parseConnectionError(final int error) { |
| | | switch (error) { |
| | | case 0x01: |
| | | return "GATT CONN L2C FAILURE"; |
| | | case 0x08: |
| | | return "GATT CONN TIMEOUT"; |
| | | case 0x13: |
| | | return "GATT CONN TERMINATE PEER USER"; |
| | | case 0x16: |
| | | return "GATT CONN TERMINATE LOCAL HOST"; |
| | | case 0x3E: |
| | | return "GATT CONN FAIL ESTABLISH"; |
| | | case 0x22: |
| | | return "GATT CONN LMP TIMEOUT"; |
| | | case 0x0100: |
| | | return "GATT CONN CANCEL "; |
| | | case 0x0085: //133é误 |
| | | return "GATT ERROR"; // Device not reachable |
| | | default: |
| | | return parse(error); |
| | | } |
| | | } |
| | | |
| | | // Starts at line 29 of the gatt_api.h file |
| | | /** |
| | | * Converts the bluetooth communication status given by other BluetoothGattCallbacks to error name. It also parses the DFU errors. |
| | | * @param error the status number |
| | | * @return the error name as stated in the gatt_api.h file |
| | | */ |
| | | private static String parse(final int error) { |
| | | switch (error) { |
| | | case 0: |
| | | return "GATT SUCCESS"; |
| | | case 0x0001: |
| | | return "GATT INVALID HANDLE"; |
| | | case 0x0002: |
| | | return "GATT READ NOT PERMIT"; |
| | | case 0x0003: |
| | | return "GATT WRITE NOT PERMIT"; |
| | | case 0x0004: |
| | | return "GATT INVALID PDU"; |
| | | case 0x0005: |
| | | return "GATT INSUF AUTHENTICATION"; |
| | | case 0x0006: |
| | | return "GATT REQ NOT SUPPORTED"; |
| | | case 0x0007: |
| | | return "GATT INVALID OFFSET"; |
| | | case 0x0008: |
| | | return "GATT INSUF AUTHORIZATION"; |
| | | case 0x0009: |
| | | return "GATT PREPARE Q FULL"; |
| | | case 0x000a: |
| | | return "GATT NOT FOUND"; |
| | | case 0x000b: |
| | | return "GATT NOT LONG"; |
| | | case 0x000c: |
| | | return "GATT INSUF KEY SIZE"; |
| | | case 0x000d: |
| | | return "GATT INVALID ATTR LEN"; |
| | | case 0x000e: |
| | | return "GATT ERR UNLIKELY"; |
| | | case 0x000f: |
| | | return "GATT INSUF ENCRYPTION"; |
| | | case 0x0010: |
| | | return "GATT UNSUPPORT GRP TYPE"; |
| | | case 0x0011: |
| | | return "GATT INSUF RESOURCE"; |
| | | case 0x0087: |
| | | return "GATT ILLEGAL PARAMETER"; |
| | | case 0x0080: |
| | | return "GATT NO RESOURCES"; |
| | | case 0x0081: |
| | | return "GATT INTERNAL ERROR"; |
| | | case 0x0082: |
| | | return "GATT WRONG STATE"; |
| | | case 0x0083: |
| | | return "GATT DB FULL"; |
| | | case 0x0084: |
| | | return "GATT BUSY"; |
| | | case 0x0085: |
| | | return "GATT ERROR"; |
| | | case 0x0086: |
| | | return "GATT CMD STARTED"; |
| | | case 0x0088: |
| | | return "GATT PENDING"; |
| | | case 0x0089: |
| | | return "GATT AUTH FAIL"; |
| | | case 0x008a: |
| | | return "GATT MORE"; |
| | | case 0x008b: |
| | | return "GATT INVALID CFG"; |
| | | case 0x008c: |
| | | return "GATT SERVICE STARTED"; |
| | | case 0x008d: |
| | | return "GATT ENCRYPTED NO MITM"; |
| | | case 0x008e: |
| | | return "GATT NOT ENCRYPTED"; |
| | | case 0x008f: |
| | | return "GATT CONGESTED"; |
| | | case 0x00FD: |
| | | return "GATT CCCD CFG ERROR"; |
| | | case 0x00FE: |
| | | return "GATT PROCEDURE IN PROGRESS"; |
| | | case 0x00FF: |
| | | return "GATT VALUE OUT OF RANGE"; |
| | | case 0x0101: |
| | | return "GATT FAILURE, TOO MANY OPEN CONNECTIONS"; |
| | | default: |
| | | return "UNKNOWN (" + error + ")"; |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.connect; |
| | | |
| | | import android.os.SystemClock; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.BleManager; |
| | | import com.blakequ.bluetooth_manager_lib.BleParamsOptions; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/19 18:23 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description: |
| | | */ |
| | | public class ReconnectParamsBean { |
| | | private String address; |
| | | private int number;//reconnect times number |
| | | private long nextReconnectTime;//next reconnect time |
| | | private long startDisconnectTime; //the bluetooth disconnected time |
| | | private boolean isReconnectNow = false; |
| | | |
| | | public ReconnectParamsBean(String address) { |
| | | this.address = address; |
| | | this.startDisconnectTime = SystemClock.elapsedRealtime(); |
| | | this.number = 0; |
| | | } |
| | | |
| | | public String getAddress() { |
| | | return address; |
| | | } |
| | | |
| | | public void updateAddress(String address){ |
| | | this.address = address; |
| | | this.startDisconnectTime = SystemClock.elapsedRealtime(); |
| | | this.number = 0; |
| | | } |
| | | |
| | | /** |
| | | * get next reconnect time |
| | | * @return |
| | | */ |
| | | public long getNextReconnectTime() { |
| | | BleParamsOptions options = BleManager.getBleParamsOptions(); |
| | | switch (options.getReconnectStrategy()){ |
| | | case ConnectConfig.RECONNECT_EXPONENT: |
| | | nextReconnectTime = (long) (startDisconnectTime + options.getReconnectBaseSpaceTime() * Math.pow(2, number)); |
| | | break; |
| | | case ConnectConfig.RECONNECT_LINE_EXPONENT: |
| | | if (number <= options.getReconnectedLineToExponentTimes()){ |
| | | nextReconnectTime = startDisconnectTime + options.getReconnectBaseSpaceTime()*number; |
| | | }else { |
| | | nextReconnectTime = (long) (startDisconnectTime + options.getReconnectBaseSpaceTime() * Math.pow(2, number)); |
| | | } |
| | | break; |
| | | case ConnectConfig.RECONNECT_LINEAR: |
| | | nextReconnectTime = startDisconnectTime + options.getReconnectBaseSpaceTime()*number; |
| | | break; |
| | | case ConnectConfig.RECONNECT_FIXED_TIME: |
| | | nextReconnectTime = startDisconnectTime + options.getReconnectBaseSpaceTime(); |
| | | break; |
| | | } |
| | | |
| | | if (isReconnectNow){ |
| | | nextReconnectTime = SystemClock.elapsedRealtime(); |
| | | } |
| | | |
| | | //max reconnect times, not reconnect |
| | | if (number >= options.getReconnectMaxTimes()){ |
| | | LogUtils.d("reconnect number="+number+" more than max times "+options.getReconnectMaxTimes()); |
| | | //å°æ¶é´è®¾ç½®é常大 |
| | | nextReconnectTime = SystemClock.elapsedRealtime() + 10*24*60*60*1000; |
| | | } |
| | | return nextReconnectTime; |
| | | } |
| | | |
| | | public boolean isReconnectNow() { |
| | | return isReconnectNow; |
| | | } |
| | | |
| | | public void setReconnectNow(boolean reconnectNow) { |
| | | number = 0; |
| | | startDisconnectTime = SystemClock.elapsedRealtime(); |
| | | isReconnectNow = reconnectNow; |
| | | } |
| | | |
| | | public int getNumber() { |
| | | return number; |
| | | } |
| | | |
| | | /** |
| | | * invoke after bluetooth disconnected |
| | | */ |
| | | public void addNumber() { |
| | | isReconnectNow = false; |
| | | this.startDisconnectTime = SystemClock.elapsedRealtime(); |
| | | this.number++; |
| | | } |
| | | |
| | | /** |
| | | * if you can not want to reconnect by auto, you can set a max value |
| | | * @param num |
| | | */ |
| | | public void setNumber(int num){ |
| | | this.number = num; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "ReconnectParamsBean{" + |
| | | "address='" + address + '\'' + |
| | | ", number=" + number + |
| | | ", next reconnect after " + (getNextReconnectTime()-startDisconnectTime)/1000 + "seconds"+ |
| | | ", startDisconnectTime=" + startDisconnectTime + |
| | | '}'; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.connect.multiple; |
| | | |
| | | import android.bluetooth.BluetoothAdapter; |
| | | import android.bluetooth.BluetoothDevice; |
| | | import android.bluetooth.BluetoothGatt; |
| | | import android.content.Context; |
| | | import android.os.Looper; |
| | | import android.os.SystemClock; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.BleManager; |
| | | import com.blakequ.bluetooth_manager_lib.BleParamsOptions; |
| | | import com.blakequ.bluetooth_manager_lib.connect.BluetoothConnectInterface; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectConfig; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectState; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectStateListener; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ReconnectParamsBean; |
| | | import com.blakequ.bluetooth_manager_lib.util.BluetoothUtils; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Queue; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.ConcurrentLinkedQueue; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/19 9:48 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description:the queue using for manager connect request |
| | | */ |
| | | public abstract class ConnectRequestQueue extends BluetoothConnectInterface{ |
| | | private static final String TAG = "ConnectRequestQueue"; |
| | | private Map<String, ReconnectParamsBean> reconnectMap; //reconnect device list and reconnect times number |
| | | private Map<String, ConnectState> macMap;//<mac address, is connected> |
| | | private Map<String, BluetoothGatt> gattMap;//notice:ArrayMap is not support concurrent, so can not use ArrayMap |
| | | private Queue<String> deviceQueue; |
| | | private final BluetoothUtils mBluetoothUtils; |
| | | private List<ConnectStateListener> connectStateListeners; |
| | | |
| | | public ConnectRequestQueue(Context context){ |
| | | super(context); |
| | | macMap = new ConcurrentHashMap<String, ConnectState>();//if not consider concurrent, should use ArrayMap |
| | | gattMap = new ConcurrentHashMap<String, BluetoothGatt>(); |
| | | reconnectMap = new ConcurrentHashMap<String, ReconnectParamsBean>(); |
| | | deviceQueue = new ConcurrentLinkedQueue<>(); |
| | | mBluetoothUtils = BluetoothUtils.getInstance(context); |
| | | connectStateListeners = new ArrayList<>(); |
| | | } |
| | | |
| | | public void addConnectStateListener(ConnectStateListener listener){ |
| | | synchronized (connectStateListeners){ |
| | | connectStateListeners.add(listener); |
| | | } |
| | | } |
| | | |
| | | public void removeConnectStateListener(ConnectStateListener listener){ |
| | | synchronized (connectStateListeners){ |
| | | connectStateListeners.remove(listener); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | protected void onDeviceConnected(BluetoothGatt gatt) { |
| | | if (gatt != null){ |
| | | updateConnectState(gatt.getDevice().getAddress(), ConnectState.CONNECTED); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | protected void onDeviceDisconnect(BluetoothGatt gatt, int errorState) { |
| | | LogUtils.e( "Disconnected from GATT server address:" + gatt.getDevice().getAddress()); |
| | | //å¯ä»¥ä¸å
³éï¼ä»¥ä¾¿éç¨ï¼å 为å¨è¿æ¥connectçæ¶åå¯ä»¥å¿«éè¿æ¥ |
| | | if (!mBluetoothUtils.isBluetoothIsEnable()){ |
| | | //å
³éææçè®¾å¤ |
| | | closeAll(); |
| | | }else { |
| | | close(gatt.getDevice().getAddress());//鲿¢åºç°status 133 |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | protected void onDiscoverServicesFail(BluetoothGatt gatt) { |
| | | if (gatt != null){ |
| | | updateConnectState(gatt.getDevice().getAddress(), ConnectState.NORMAL); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | protected void onDiscoverServicesSuccess(BluetoothGatt gatt){ |
| | | if (gatt != null){ |
| | | updateConnectState(gatt.getDevice().getAddress(), ConnectState.CONNECTED); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * start connect device one by one |
| | | */ |
| | | public void startConnect(){ |
| | | if (deviceQueue.size() > 0 && mBluetoothUtils.isBluetoothIsEnable()){ |
| | | triggerConnectNextDevice(); |
| | | }else { |
| | | triggerReconnect(""); |
| | | LogUtils.e( "startConnect--Fail to from connect queue, and start reconnect task. ble state:" + mBluetoothUtils.isBluetoothIsEnable()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * start connect device(will trigger reconnect) |
| | | * @param macAddress |
| | | */ |
| | | public void startConnect(String macAddress){ |
| | | if (macAddress != null && macAddress.length() > 0){ |
| | | if (macMap.containsKey(macAddress)){ |
| | | ConnectState state = macMap.get(macAddress); |
| | | //å¦ææ¯æªè¿æ¥ç¶æï¼åå¼å¯éè¿ï¼éç½®éè¿æ¬¡æ°ï¼å¹¶ç«å³è¿æ¥ |
| | | if (macMap.get(macAddress) == ConnectState.NORMAL){ |
| | | ReconnectParamsBean bean; |
| | | if (!reconnectMap.containsKey(macAddress)){ |
| | | bean = new ReconnectParamsBean(macAddress); |
| | | reconnectMap.put(macAddress, bean); |
| | | }else{ |
| | | bean = reconnectMap.get(macAddress); |
| | | } |
| | | bean.setReconnectNow(true); |
| | | startReconnectTask(); |
| | | }else{ |
| | | LogUtils.i( "Device is " + state + " state"); |
| | | } |
| | | }else{ |
| | | LogUtils.e( "Fail to connect device, device can not found in queue, you must invoke addDeviceToQueue(Stirng)"); |
| | | } |
| | | }else{ |
| | | LogUtils.e( "Fail to connect device, mac address is null"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * connect bluetooth device one by one |
| | | * @return the next connect device |
| | | */ |
| | | private void triggerConnectNextDevice(){ |
| | | String mac = deviceQueue.peek(); |
| | | if (!isEmpty(mac)){ |
| | | LogUtils.i( "Start trigger connect device "+mac); |
| | | connect(mac); |
| | | } |
| | | } |
| | | |
| | | private void updateConnectState(String address, ConnectState state) { |
| | | //bug:Can not remove device from queue, this position just update connect state |
| | | if (macMap.containsKey(address)) { |
| | | macMap.put(address, state); |
| | | updateConnectStateListener(address, state); |
| | | } |
| | | switch (state){ |
| | | case NORMAL: //disconnect or close |
| | | String mac = deviceQueue.peek(); |
| | | if (!isEmpty(mac)){ |
| | | if (address.equals(mac)){ |
| | | deviceQueue.poll(); |
| | | } |
| | | triggerConnectNextDevice(); |
| | | } |
| | | triggerReconnect(address); |
| | | break; |
| | | case CONNECTED: |
| | | reconnectMap.remove(address); |
| | | String mac1 = deviceQueue.peek(); |
| | | if (!isEmpty(mac1)){ |
| | | if (address.equals(mac1)){ |
| | | deviceQueue.poll(); |
| | | } |
| | | triggerConnectNextDevice(); |
| | | } |
| | | triggerReconnect(address); |
| | | break; |
| | | case CONNECTING: |
| | | //start check time out connect |
| | | BleParamsOptions options = BleManager.getBleParamsOptions(); |
| | | getMainLooperHandler().postDelayed(timeOutTask, options.getConnectTimeOutTimes()); |
| | | break; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * connect time out task |
| | | */ |
| | | private Runnable timeOutTask = new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | if (!mBluetoothUtils.isBluetoothIsEnable()){ |
| | | LogUtils.w( "Fail to connect device! Bluetooth is not enable!"); |
| | | closeAll(); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * release resource |
| | | */ |
| | | @Override |
| | | public void release(){ |
| | | macMap.clear(); |
| | | closeAll(); |
| | | gattMap.clear(); |
| | | reconnectMap.clear(); |
| | | deviceQueue.clear(); |
| | | getMainLooperHandler().removeCallbacks(reconnectTask); |
| | | } |
| | | |
| | | /** |
| | | * get the size of current queue |
| | | * <p>Notice:this len maybe is not equal of maxLen(connect device num<=maxLen), is dynamic length by sensor physical truth</> |
| | | * @see #getMaxLen() |
| | | */ |
| | | public int getQueueSize(){ |
| | | return macMap.size(); |
| | | } |
| | | |
| | | /** |
| | | * add device to connect queue, if the number out of range will discard. |
| | | * @param macAddress |
| | | * @see #startConnect() |
| | | */ |
| | | public void addDeviceToQueue(String macAddress){ |
| | | if (!macMap.containsKey(macAddress)){ |
| | | if (macMap.size() >= getMaxLen()){ |
| | | String address = deviceQueue.poll(); |
| | | if (isEmpty(address)){ |
| | | address = getFirstDevice(); |
| | | } |
| | | removeDeviceFromQueue(address); |
| | | } |
| | | deviceQueue.add(macAddress); |
| | | macMap.put(macAddress, ConnectState.NORMAL); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * add device to connect queue |
| | | * @param devices |
| | | * @see #startConnect() |
| | | */ |
| | | public void addDeviceToQueue(String[] devices){ |
| | | if (devices != null && devices.length > 0){ |
| | | for (int i=0; i<devices.length; i++){ |
| | | addDeviceToQueue(devices[i]); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * remove device from queue, disconnected device if in connection state |
| | | * @param macAddress |
| | | */ |
| | | public void removeDeviceFromQueue(String macAddress){ |
| | | if (isEmpty(macAddress)) return; |
| | | macMap.remove(macAddress); |
| | | deviceQueue.remove(macAddress); |
| | | reconnectMap.remove(macAddress); |
| | | if (gattMap.containsKey(macAddress)){ |
| | | close(macAddress); |
| | | } |
| | | } |
| | | |
| | | public List<String> getAllDevice(){ |
| | | if (macMap.size() <= 0) return Collections.EMPTY_LIST; |
| | | List<String> list = new ArrayList<>(); |
| | | for (String key:macMap.keySet()){ |
| | | list.add(key); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | private String getFirstDevice(){ |
| | | if (macMap.size() <= 0) return null; |
| | | for (String key:macMap.keySet()){ |
| | | return key; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * get all connected device |
| | | * @return |
| | | */ |
| | | public List<String> getAllConnectedDevice(){ |
| | | if (macMap.size() <= 0) return Collections.EMPTY_LIST; |
| | | List<String> list = new ArrayList<>(); |
| | | for (String key:macMap.keySet()){ |
| | | if (macMap.get(key) == ConnectState.CONNECTED){ |
| | | list.add(key); |
| | | } |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | /** |
| | | * is contain device |
| | | * @param address |
| | | * @return |
| | | */ |
| | | public boolean containsDevice(String address){ |
| | | return macMap.containsKey(address); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * get bluetooth state of connect |
| | | * @param address |
| | | * @return |
| | | */ |
| | | public ConnectState getDeviceState(String address){ |
| | | return macMap.get(address); |
| | | } |
| | | |
| | | public List<String> getAllConnectingDevice(){ |
| | | if (macMap.size() <= 0) return Collections.EMPTY_LIST; |
| | | List<String> list = new ArrayList<>(); |
| | | for (String key:macMap.keySet()){ |
| | | if (macMap.get(key) == ConnectState.CONNECTING){ |
| | | list.add(key); |
| | | } |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | /** |
| | | * has device is not connected |
| | | * @return |
| | | */ |
| | | public boolean isDisconnectDevice(){ |
| | | for (ConnectState value:macMap.values()) { |
| | | if (value == ConnectState.NORMAL){ |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * is have device is connecting |
| | | * @return |
| | | */ |
| | | public boolean isConnectingDevice(){ |
| | | for (ConnectState value:macMap.values()){ |
| | | if (value == ConnectState.CONNECTING){ |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * is have device is connected |
| | | * @return |
| | | */ |
| | | public boolean isConnectedDevice(){ |
| | | for (ConnectState value:macMap.values()){ |
| | | if (value == ConnectState.CONNECTED){ |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * è·åè¿æ¥è®¾å¤çBluetoothGatt对象ï¼å¦ææ²¡æè¿ånull |
| | | * @param address |
| | | * @return |
| | | */ |
| | | public BluetoothGatt getBluetoothGatt(String address){ |
| | | if (!isEmpty(address) && gattMap.containsKey(address)){ |
| | | return gattMap.get(address); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * trigger reconnect task |
| | | */ |
| | | private void triggerReconnect(String mac){ |
| | | //if deviceQueue is null, start reconnect |
| | | if (deviceQueue.size() == 0){ |
| | | //å°éè¿ç设å¤å
¨é¨æ¾å
¥éè¿éå |
| | | for (String key:macMap.keySet()){ |
| | | if (macMap.get(key) == ConnectState.NORMAL){ |
| | | ReconnectParamsBean bean; |
| | | if (!reconnectMap.containsKey(key)){ |
| | | bean = new ReconnectParamsBean(key); |
| | | reconnectMap.put(key, bean); |
| | | }else if(key.equals(mac)){ |
| | | bean = reconnectMap.get(key); |
| | | bean.addNumber(); |
| | | LogUtils.d( "trigger reconnect, reconnect after "+(bean.getNextReconnectTime() - SystemClock.elapsedRealtime())/1000+" seconds"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | startReconnectTask(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * can not reconnect all the time |
| | | */ |
| | | private synchronized void startReconnectTask(){ |
| | | if (reconnectMap.size() <= 0) return; |
| | | long nextTime = SystemClock.elapsedRealtime()*2; |
| | | String address = ""; |
| | | //select minimum time of list |
| | | for (String addr:reconnectMap.keySet()){ |
| | | ReconnectParamsBean bean = reconnectMap.get(addr); |
| | | if (bean.getNextReconnectTime() < nextTime){ |
| | | nextTime = bean.getNextReconnectTime(); |
| | | address = addr; |
| | | } |
| | | } |
| | | |
| | | //start reconnect task |
| | | if (!isEmpty(address)){ |
| | | if (nextTime <= SystemClock.elapsedRealtime()){ |
| | | LogUtils.d( "start reconnect device:"+address); |
| | | reconnectDevice(address); |
| | | }else{ |
| | | LogUtils.d( "start reconnect device "+address+" after "+(nextTime - SystemClock.elapsedRealtime())/1000+" seconds"); |
| | | getMainLooperHandler().removeCallbacks(reconnectTask); |
| | | getMainLooperHandler().postDelayed(reconnectTask, nextTime - SystemClock.elapsedRealtime()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * reconnect runnable |
| | | */ |
| | | private Runnable reconnectTask = new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | LogUtils.d( "Start reconnect task by handler"); |
| | | startReconnectTask(); |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * reconnect device |
| | | * @param address |
| | | */ |
| | | private synchronized void reconnectDevice(final String address){ |
| | | if (macMap.containsKey(address)){ |
| | | ReconnectParamsBean bean = reconnectMap.get(address); |
| | | if (mBluetoothUtils.isBluetoothIsEnable()) { |
| | | if (bean == null){ |
| | | reconnectMap.put(address, new ReconnectParamsBean(address)); |
| | | } |
| | | |
| | | //check is connected or connectting |
| | | ConnectState state = macMap.get(address); |
| | | if (state == ConnectState.NORMAL){ |
| | | LogUtils.d( "Start reconnect device "+address+" reconnect number is "+bean.getNumber()); |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | connect(address); |
| | | } |
| | | }); |
| | | }else{ |
| | | LogUtils.w( "Fail to reconnect device! "+address+" state is "+state); |
| | | } |
| | | }else { |
| | | closeAll(); |
| | | LogUtils.w( "Fail to reconnect device! Bluetooth is not enable!"); |
| | | } |
| | | }else{ |
| | | LogUtils.w("Fail to reconnect device! "+address+" is remove from reconnectMap"); |
| | | reconnectMap.remove(address); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * You should invoke {@link #startConnect()} to begin to connect device. Not recommended for direct use this method |
| | | * @see #startConnect() |
| | | * @param address |
| | | * @return |
| | | */ |
| | | protected boolean connect(final String address) { |
| | | BluetoothAdapter mAdapter = mBluetoothUtils.getBluetoothAdapter(); |
| | | if (mAdapter == null || address == null) { |
| | | LogUtils.e("BluetoothAdapter not initialized or unspecified address "+address); |
| | | updateConnectStateListener(address, ConnectState.NORMAL); |
| | | return false; |
| | | } |
| | | |
| | | if (!mBluetoothUtils.isBluetoothIsEnable()){ |
| | | LogUtils.e("bluetooth is not enable."); |
| | | closeAll(); |
| | | // updateConnectStateListener(address, ConnectState.NORMAL); |
| | | return false; |
| | | } |
| | | |
| | | if (isEmpty(getServiceUUID())){ |
| | | LogUtils.w("Service uuid is null"); |
| | | } |
| | | |
| | | // Previously connected device. Try to reconnect. |
| | | if (gattMap.containsKey(address)){ |
| | | BluetoothGatt mBluetoothGatt = gattMap.get(address); |
| | | LogUtils.i("Trying to use an existing gatt and reconnection device " + address + " thread:" + (Thread.currentThread() == Looper.getMainLooper().getThread())); |
| | | if (mBluetoothGatt.connect()) { |
| | | updateConnectState(address, ConnectState.CONNECTING); |
| | | return true; |
| | | } else { |
| | | close(address); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | BluetoothDevice device = mAdapter.getRemoteDevice(address); |
| | | if (device != null){ |
| | | /*if We want to directly connect to the device, we can setting the autoConnect |
| | | parameter to false.*/ |
| | | BluetoothGatt mBluetoothGatt = device.connectGatt(context, false, gattCallback); |
| | | if (mBluetoothGatt != null){ |
| | | LogUtils.i("create a new connection address=" + address + " thread:" + (Thread.currentThread() == Looper.getMainLooper().getThread())); |
| | | gattMap.put(address, mBluetoothGatt); |
| | | updateConnectState(address, ConnectState.CONNECTING); |
| | | return true; |
| | | } else { |
| | | LogUtils.e("Get Gatt fail!, address=" + address + " thread:" + (Thread.currentThread() == Looper.getMainLooper().getThread())); |
| | | } |
| | | } else { |
| | | LogUtils.e("Device not found, address=" + address); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * å
³éèçè¿æ¥,ä¼éæ¾BluetoothGattææçææèµæº |
| | | * @param address |
| | | */ |
| | | public boolean close(String address) { |
| | | if (!isEmpty(address) && gattMap.containsKey(address)){ |
| | | LogUtils.w( "close gatt server " + address); |
| | | BluetoothGatt mBluetoothGatt = gattMap.get(address); |
| | | mBluetoothGatt.close(); |
| | | gattMap.remove(address); |
| | | updateConnectState(address, ConnectState.NORMAL); |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * å
³éææèçè®¾å¤ |
| | | */ |
| | | public void closeAll(){ |
| | | for (String address:gattMap.keySet()) { |
| | | close(address); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æå¼èçè¿æ¥ï¼ä¸ä¼éæ¾BluetoothGattææçææèµæºï¼å¯ä»¥è°ç¨mBluetoothGatt.connect()å¾å¿«éæ°è¿æ¥ä¸ |
| | | * 妿ä¸åæ¶éæ¾èµæºï¼å¯è½åºç°133é误ï¼http://www.loverobots.cn/android-ble-connection-solution-bluetoothgatt-status-133.html |
| | | * @param address |
| | | */ |
| | | public void disconnect(String address){ |
| | | if (!isEmpty(address) && gattMap.containsKey(address)){ |
| | | LogUtils.w("disconnect gatt server " + address); |
| | | BluetoothGatt mBluetoothGatt = gattMap.get(address); |
| | | mBluetoothGatt.disconnect(); |
| | | updateConnectState(address, ConnectState.NORMAL); |
| | | } |
| | | } |
| | | |
| | | private void updateConnectStateListener(String address, ConnectState state){ |
| | | synchronized (connectStateListeners){ |
| | | for (ConnectStateListener listener:connectStateListeners){ |
| | | if (listener != null) listener.onConnectStateChanged(address, state); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * max connected number of bluetooth queue |
| | | * @return |
| | | */ |
| | | public int getMaxLen(){ |
| | | return ConnectConfig.maxConnectDeviceNum; |
| | | } |
| | | |
| | | public boolean isEmpty(String str) { |
| | | return str == null || str.length() == 0; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.connect.multiple; |
| | | |
| | | import android.annotation.TargetApi; |
| | | import android.bluetooth.BluetoothDevice; |
| | | import android.bluetooth.BluetoothGatt; |
| | | import android.bluetooth.BluetoothGattCallback; |
| | | import android.bluetooth.BluetoothManager; |
| | | import android.bluetooth.BluetoothProfile; |
| | | import android.content.Context; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.BleManager; |
| | | import com.blakequ.bluetooth_manager_lib.connect.BluetoothSubScribeData; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectConfig; |
| | | import com.blakequ.bluetooth_manager_lib.connect.ConnectState; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Queue; |
| | | import java.util.concurrent.ConcurrentLinkedQueue; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/19 9:49 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description:manager multiple bluetooth device connect |
| | | * <p> |
| | | * 1.must set uuid for subscribe bluetooth device data, {@link #setServiceUUID(String)}, {@link #addBluetoothSubscribeData(BluetoothSubScribeData)}<br> |
| | | * 2.register callback of bluetooth notify by {@link #setBluetoothGattCallback(BluetoothGattCallback)}<br> |
| | | * 3.add device to connect queue, {@link #addDeviceToQueue(String)} or {@link #addDeviceToQueue(String[])}, {@link #removeDeviceFromQueue(String)}<br> |
| | | * 4.start auto connect one by one, {@link #startConnect()}<br> |
| | | * 5.close all connect, {@link #close(String)}, {@link #closeAll()}<br> |
| | | * <p/> |
| | | */ |
| | | @TargetApi(18) |
| | | public final class MultiConnectManager extends ConnectRequestQueue { |
| | | private static final String TAG = "MultiConnectManager"; |
| | | private static MultiConnectManager INSTANCE; |
| | | private BluetoothManager bluetoothManager; |
| | | private static String serviceUUID; |
| | | private BluetoothGattCallback mBluetoothGattCallback; |
| | | private final Queue<BluetoothSubScribeData> subscribeQueue; |
| | | private static Object obj = new Object(); |
| | | |
| | | private MultiConnectManager(Context context){ |
| | | super(context); |
| | | bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); |
| | | subscribeQueue = new ConcurrentLinkedQueue<BluetoothSubScribeData>(); |
| | | BleManager.getBleParamsOptions(); |
| | | } |
| | | |
| | | public static MultiConnectManager getInstance(Context context){ |
| | | //åéé |
| | | if (INSTANCE == null){ |
| | | synchronized (obj){ |
| | | if (INSTANCE == null){ |
| | | INSTANCE = new MultiConnectManager(context); |
| | | } |
| | | } |
| | | } |
| | | return INSTANCE; |
| | | } |
| | | |
| | | public List<BluetoothDevice> getConnectedDevice() { |
| | | List<BluetoothDevice> devices = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT); |
| | | if (!isEmpty(devices)){ |
| | | List<BluetoothDevice> newDevices = new ArrayList<BluetoothDevice>(); |
| | | for (BluetoothDevice device: devices){ |
| | | if (getDeviceState(device.getAddress()) == ConnectState.CONNECTED) { |
| | | newDevices.add(device); |
| | | }else { |
| | | LogUtils.i("Not exist connected device in queue "+device.getAddress()); |
| | | } |
| | | } |
| | | return newDevices; |
| | | } |
| | | return Collections.EMPTY_LIST; |
| | | } |
| | | |
| | | /** |
| | | * register callback of bluetooth notify |
| | | * @param callback |
| | | */ |
| | | public void setBluetoothGattCallback(BluetoothGattCallback callback){ |
| | | this.mBluetoothGattCallback = callback; |
| | | } |
| | | |
| | | /** |
| | | * add subscribe data while read or write characteristic(or descriptor) after discover service. |
| | | * if each device using different config, you should invoke {@link #cleanSubscribeData()} to clean queue before using {@link #startSubscribe(BluetoothGatt)} |
| | | * @param data |
| | | * @see #cleanSubscribeData() |
| | | * @see #startSubscribe(BluetoothGatt) |
| | | * @see #setServiceUUID(String) |
| | | */ |
| | | public void addBluetoothSubscribeData(BluetoothSubScribeData data){ |
| | | subscribeQueue.add(data); |
| | | } |
| | | |
| | | /** |
| | | * clean subscribe list |
| | | * @see #addBluetoothSubscribeData(BluetoothSubScribeData) |
| | | */ |
| | | public void cleanSubscribeData(){ |
| | | subscribeQueue.clear(); |
| | | } |
| | | |
| | | /** |
| | | * set bluetooth service uuid, can not be null |
| | | * @see #addBluetoothSubscribeData(BluetoothSubScribeData) |
| | | * @param serviceUUID |
| | | */ |
| | | public void setServiceUUID(String serviceUUID){ |
| | | this.serviceUUID = serviceUUID; |
| | | } |
| | | |
| | | @Override |
| | | protected BluetoothGattCallback getBluetoothGattCallback() { |
| | | return mBluetoothGattCallback; |
| | | } |
| | | |
| | | @Override |
| | | protected String getServiceUUID() { |
| | | return serviceUUID; |
| | | } |
| | | |
| | | @Override |
| | | protected Queue<BluetoothSubScribeData> getSubscribeDataQueue() { |
| | | return subscribeQueue; |
| | | } |
| | | |
| | | @Deprecated |
| | | public void setMaxConnectDeviceNum(int number){ |
| | | ConnectConfig.maxConnectDeviceNum = number; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device; |
| | | |
| | | /** |
| | | * |
| | | */ |
| | | public interface BeaconDevice { |
| | | BeaconType getBeaconType(); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * |
| | | */ |
| | | public abstract class BeaconManufacturerData { |
| | | private final BeaconType mBeaconType; |
| | | private final byte[] mData; |
| | | |
| | | protected BeaconManufacturerData(final BeaconType expectedType, final byte[] data){ |
| | | if (BeaconUtils.getBeaconType(data) != expectedType) { |
| | | throw new IllegalArgumentException( |
| | | "Manufacturer record '" |
| | | + Arrays.toString(data) |
| | | + "' is not from a " + expectedType); |
| | | } |
| | | |
| | | this.mData = data; |
| | | this.mBeaconType = expectedType; |
| | | } |
| | | |
| | | public BeaconType getBeaconType(){ |
| | | return mBeaconType; |
| | | } |
| | | |
| | | public byte[] getData(){ |
| | | return mData; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device; |
| | | |
| | | /** |
| | | * ble device type |
| | | */ |
| | | public enum BeaconType { |
| | | /** |
| | | * not ibeacon device |
| | | */ |
| | | NOT_A_BEACON, |
| | | /** |
| | | * ibeacon device |
| | | * */ |
| | | IBEACON, |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.device.adrecord.AdRecord; |
| | | import com.blakequ.bluetooth_manager_lib.device.ibeacon.IBeaconConstants; |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | |
| | | /** |
| | | * |
| | | */ |
| | | public final class BeaconUtils { |
| | | |
| | | private BeaconUtils(){ |
| | | // TO AVOID INSTANTIATION |
| | | } |
| | | |
| | | /** |
| | | * Ascertains whether a Manufacturer Data byte array belongs to a known Beacon type; |
| | | * |
| | | * @param manufacturerData a Bluetooth LE device's raw manufacturerData. |
| | | * @return the {@link BeaconType} |
| | | */ |
| | | public static BeaconType getBeaconType(final byte[] manufacturerData) { |
| | | if (manufacturerData == null || manufacturerData.length == 0) { |
| | | return BeaconType.NOT_A_BEACON; |
| | | } |
| | | |
| | | if(isIBeacon(manufacturerData)){ |
| | | return BeaconType.IBEACON; |
| | | } else { |
| | | return BeaconType.NOT_A_BEACON; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Ascertains whether a {@link com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice} is an iBeacon; |
| | | * |
| | | * @param device a {@link com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice} device. |
| | | * @return the {@link BeaconType} |
| | | */ |
| | | public static BeaconType getBeaconType(final BluetoothLeDevice device) { |
| | | final int key = AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA; |
| | | return getBeaconType(device.getAdRecordStore().getRecordDataAsString(key).getBytes()); |
| | | } |
| | | |
| | | private static boolean isIBeacon(final byte[] manufacturerData){ |
| | | // An iBeacon record must be at least 25 chars long |
| | | if (!(manufacturerData.length >= 25)) { |
| | | return false; |
| | | } |
| | | |
| | | if (ByteUtils.doesArrayBeginWith(manufacturerData, IBeaconConstants.MANUFACTURER_DATA_IBEACON_PREFIX)) { |
| | | return true; |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device; |
| | | |
| | | import android.bluetooth.BluetoothDevice; |
| | | import android.os.Bundle; |
| | | import android.os.Parcel; |
| | | import android.os.Parcelable; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.device.adrecord.AdRecordStore; |
| | | import com.blakequ.bluetooth_manager_lib.device.adrecord.AdRecordUtils; |
| | | import com.blakequ.bluetooth_manager_lib.device.resolvers.BluetoothClassResolver; |
| | | import com.blakequ.bluetooth_manager_lib.device.ibeacon.IBeaconDevice; |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | import com.blakequ.bluetooth_manager_lib.util.LimitedLinkHashMap; |
| | | |
| | | import java.io.Serializable; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.HashSet; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | |
| | | // TODO: Auto-generated Javadoc |
| | | |
| | | /** |
| | | * This is a wrapper around the default BluetoothDevice object |
| | | * As BluetoothDevice is final it cannot be extended, so to get it you |
| | | * need to call {@link #getDevice()} method. |
| | | * |
| | | * @author Alexandros Schillings |
| | | */ |
| | | public class BluetoothLeDevice implements Parcelable ,BeaconDevice{ |
| | | /** |
| | | * The Constant CREATOR. |
| | | */ |
| | | public static final Creator<BluetoothLeDevice> CREATOR = new Creator<BluetoothLeDevice>() { |
| | | public BluetoothLeDevice createFromParcel(final Parcel in) { |
| | | return new BluetoothLeDevice(in); |
| | | } |
| | | |
| | | public BluetoothLeDevice[] newArray(final int size) { |
| | | return new BluetoothLeDevice[size]; |
| | | } |
| | | }; |
| | | protected static final int MAX_RSSI_LOG_SIZE = 10; |
| | | private static final String PARCEL_EXTRA_BLUETOOTH_DEVICE = "bluetooth_device"; |
| | | private static final String PARCEL_EXTRA_CURRENT_RSSI = "current_rssi"; |
| | | private static final String PARCEL_EXTRA_CURRENT_TIMESTAMP = "current_timestamp"; |
| | | private static final String PARCEL_EXTRA_DEVICE_RSSI_LOG = "device_rssi_log"; |
| | | private static final String PARCEL_EXTRA_DEVICE_SCANRECORD = "device_scanrecord"; |
| | | private static final String PARCEL_EXTRA_DEVICE_SCANRECORD_STORE = "device_scanrecord_store"; |
| | | private static final String PARCEL_EXTRA_FIRST_RSSI = "device_first_rssi"; |
| | | private static final String PARCEL_EXTRA_FIRST_TIMESTAMP = "first_timestamp"; |
| | | private static final long LOG_INVALIDATION_THRESHOLD = 10 * 1000; |
| | | private final AdRecordStore mRecordStore; |
| | | private final BluetoothDevice mDevice; |
| | | private final Map<Long, Integer> mRssiLog; |
| | | private final byte[] mScanRecord; |
| | | private final int mFirstRssi; |
| | | private final long mFirstTimestamp; |
| | | private int mCurrentRssi; |
| | | private long mCurrentTimestamp; |
| | | private transient Set<BluetoothService> mServiceSet; |
| | | |
| | | /** |
| | | * Instantiates a new Bluetooth LE device. |
| | | * |
| | | * @param device a standard android Bluetooth device |
| | | * @param rssi the RSSI value of the Bluetooth device |
| | | * @param scanRecord the scan record of the device |
| | | * @param timestamp the timestamp of the RSSI reading |
| | | */ |
| | | public BluetoothLeDevice(final BluetoothDevice device, final int rssi, final byte[] scanRecord, final long timestamp) { |
| | | mDevice = device; |
| | | mFirstRssi = rssi; |
| | | mFirstTimestamp = timestamp; |
| | | mRecordStore = new AdRecordStore(AdRecordUtils.parseScanRecordAsSparseArray(scanRecord)); |
| | | mScanRecord = scanRecord; |
| | | mRssiLog = new LimitedLinkHashMap<>(MAX_RSSI_LOG_SIZE); |
| | | updateRssiReading(timestamp, rssi); |
| | | } |
| | | |
| | | /** |
| | | * Instantiates a new Bluetooth LE device. |
| | | * |
| | | * @param device the device |
| | | */ |
| | | public BluetoothLeDevice(final BluetoothLeDevice device) { |
| | | mCurrentRssi = device.getRssi(); |
| | | mCurrentTimestamp = device.getTimestamp(); |
| | | mDevice = device.getDevice(); |
| | | mFirstRssi = device.getFirstRssi(); |
| | | mFirstTimestamp = device.getFirstTimestamp(); |
| | | mRecordStore = new AdRecordStore( |
| | | AdRecordUtils.parseScanRecordAsSparseArray(device.getScanRecord())); |
| | | mRssiLog = device.getRssiLog(); |
| | | mScanRecord = device.getScanRecord(); |
| | | } |
| | | |
| | | /** |
| | | * Instantiates a new bluetooth le device. |
| | | * |
| | | * @param in the in |
| | | */ |
| | | @SuppressWarnings("unchecked") |
| | | protected BluetoothLeDevice(final Parcel in) { |
| | | final Bundle b = in.readBundle(getClass().getClassLoader()); |
| | | |
| | | mCurrentRssi = b.getInt(PARCEL_EXTRA_CURRENT_RSSI, 0); |
| | | mCurrentTimestamp = b.getLong(PARCEL_EXTRA_CURRENT_TIMESTAMP, 0); |
| | | mDevice = b.getParcelable(PARCEL_EXTRA_BLUETOOTH_DEVICE); |
| | | mFirstRssi = b.getInt(PARCEL_EXTRA_FIRST_RSSI, 0); |
| | | mFirstTimestamp = b.getLong(PARCEL_EXTRA_FIRST_TIMESTAMP, 0); |
| | | mRecordStore = b.getParcelable(PARCEL_EXTRA_DEVICE_SCANRECORD_STORE); |
| | | mRssiLog = (Map<Long, Integer>) b.getSerializable(PARCEL_EXTRA_DEVICE_RSSI_LOG); |
| | | mScanRecord = b.getByteArray(PARCEL_EXTRA_DEVICE_SCANRECORD); |
| | | } |
| | | |
| | | @Override |
| | | public BeaconType getBeaconType() { |
| | | return BeaconUtils.getBeaconType(this); |
| | | } |
| | | |
| | | /** |
| | | * if ble device is ibeacon will return Ibeacon device, else return null |
| | | * @return |
| | | */ |
| | | public IBeaconDevice getIBeaconDevice(){ |
| | | if (getBeaconType() == BeaconType.IBEACON){ |
| | | return new IBeaconDevice(this); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * Adds the to rssi log. |
| | | * |
| | | * @param timestamp the timestamp |
| | | * @param rssiReading the rssi reading |
| | | */ |
| | | private void addToRssiLog(final long timestamp, final int rssiReading) { |
| | | synchronized (mRssiLog) { |
| | | if (timestamp - mCurrentTimestamp > LOG_INVALIDATION_THRESHOLD) { |
| | | mRssiLog.clear(); |
| | | } |
| | | |
| | | mCurrentRssi = rssiReading; |
| | | mCurrentTimestamp = timestamp; |
| | | mRssiLog.put(timestamp, rssiReading); |
| | | } |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see android.os.Parcelable#describeContents() |
| | | */ |
| | | @Override |
| | | public int describeContents() { |
| | | return 0; |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see java.lang.Object#equals(java.lang.Object) |
| | | */ |
| | | @Override |
| | | public boolean equals(final Object obj) { |
| | | if (this == obj) |
| | | return true; |
| | | if (obj == null) |
| | | return false; |
| | | if (getClass() != obj.getClass()) |
| | | return false; |
| | | final BluetoothLeDevice other = (BluetoothLeDevice) obj; |
| | | if (mCurrentRssi != other.mCurrentRssi) |
| | | return false; |
| | | if (mCurrentTimestamp != other.mCurrentTimestamp) |
| | | return false; |
| | | if (mDevice == null) { |
| | | if (other.mDevice != null) |
| | | return false; |
| | | } else if (!mDevice.equals(other.mDevice)) |
| | | return false; |
| | | if (mFirstRssi != other.mFirstRssi) |
| | | return false; |
| | | if (mFirstTimestamp != other.mFirstTimestamp) |
| | | return false; |
| | | if (mRecordStore == null) { |
| | | if (other.mRecordStore != null) |
| | | return false; |
| | | } else if (!mRecordStore.equals(other.mRecordStore)) |
| | | return false; |
| | | if (mRssiLog == null) { |
| | | if (other.mRssiLog != null) |
| | | return false; |
| | | } else if (!mRssiLog.equals(other.mRssiLog)) |
| | | return false; |
| | | if (!Arrays.equals(mScanRecord, other.mScanRecord)) |
| | | return false; |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Gets the ad record store. |
| | | * |
| | | * @return the ad record store |
| | | */ |
| | | public AdRecordStore getAdRecordStore() { |
| | | return mRecordStore; |
| | | } |
| | | |
| | | /** |
| | | * Gets the address. |
| | | * |
| | | * @return the address |
| | | */ |
| | | public String getAddress() { |
| | | return mDevice.getAddress(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the bluetooth device bond state. |
| | | * |
| | | * @return the bluetooth device bond state |
| | | */ |
| | | public String getBluetoothDeviceBondState() { |
| | | return resolveBondingState(mDevice.getBondState()); |
| | | } |
| | | |
| | | /** |
| | | * Gets the bluetooth device class name. |
| | | * |
| | | * @return the bluetooth device class name |
| | | */ |
| | | public String getBluetoothDeviceClassName() { |
| | | return BluetoothClassResolver.resolveDeviceClass(mDevice.getBluetoothClass().getDeviceClass()); |
| | | } |
| | | |
| | | public Set<BluetoothService> getBluetoothDeviceKnownSupportedServices() { |
| | | if (mServiceSet == null) { |
| | | synchronized (this) { |
| | | if (mServiceSet == null) { |
| | | final Set<BluetoothService> serviceSet = new HashSet<>(); |
| | | for (final BluetoothService service : BluetoothService.values()) { |
| | | |
| | | if (mDevice.getBluetoothClass().hasService(service.getAndroidConstant())) { |
| | | serviceSet.add(service); |
| | | } |
| | | } |
| | | mServiceSet = Collections.unmodifiableSet(serviceSet); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return mServiceSet; |
| | | } |
| | | |
| | | /** |
| | | * Gets the bluetooth device major class name. |
| | | * |
| | | * @return the bluetooth device major class name |
| | | */ |
| | | public String getBluetoothDeviceMajorClassName() { |
| | | return BluetoothClassResolver.resolveMajorDeviceClass(mDevice.getBluetoothClass().getMajorDeviceClass()); |
| | | } |
| | | |
| | | /** |
| | | * Gets the device. |
| | | * |
| | | * @return the device |
| | | */ |
| | | public BluetoothDevice getDevice() { |
| | | return mDevice; |
| | | } |
| | | |
| | | /** |
| | | * Gets the first rssi. |
| | | * |
| | | * @return the first rssi |
| | | */ |
| | | public int getFirstRssi() { |
| | | return mFirstRssi; |
| | | } |
| | | |
| | | /** |
| | | * Gets the first timestamp. |
| | | * |
| | | * @return the first timestamp |
| | | */ |
| | | public long getFirstTimestamp() { |
| | | return mFirstTimestamp; |
| | | } |
| | | |
| | | /** |
| | | * Gets the name. |
| | | * |
| | | * @return the name |
| | | */ |
| | | public String getName() { |
| | | return mDevice.getName(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the rssi. |
| | | * |
| | | * @return the rssi |
| | | */ |
| | | public int getRssi() { |
| | | return mCurrentRssi; |
| | | } |
| | | |
| | | /** |
| | | * Gets the rssi log. |
| | | * |
| | | * @return the rssi log |
| | | */ |
| | | protected Map<Long, Integer> getRssiLog() { |
| | | synchronized (mRssiLog) { |
| | | return mRssiLog; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Gets the running average rssi. |
| | | * |
| | | * @return the running average rssi |
| | | */ |
| | | public double getRunningAverageRssi() { |
| | | int sum = 0; |
| | | int count = 0; |
| | | |
| | | synchronized (mRssiLog) { |
| | | |
| | | for (final Long aLong : mRssiLog.keySet()) { |
| | | count++; |
| | | sum += mRssiLog.get(aLong); |
| | | } |
| | | } |
| | | |
| | | if (count > 0) { |
| | | return sum / count; |
| | | } else { |
| | | return 0; |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * Gets the scan record. |
| | | * |
| | | * @return the scan record |
| | | */ |
| | | public byte[] getScanRecord() { |
| | | return mScanRecord; |
| | | } |
| | | |
| | | /** |
| | | * Gets the timestamp. |
| | | * |
| | | * @return the timestamp |
| | | */ |
| | | public long getTimestamp() { |
| | | return mCurrentTimestamp; |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see java.lang.Object#hashCode() |
| | | */ |
| | | @Override |
| | | public int hashCode() { |
| | | final int prime = 31; |
| | | int result = 1; |
| | | result = prime * result + mCurrentRssi; |
| | | result = prime * result + (int) (mCurrentTimestamp ^ (mCurrentTimestamp >>> 32)); |
| | | result = prime * result + ((mDevice == null) ? 0 : mDevice.hashCode()); |
| | | result = prime * result + mFirstRssi; |
| | | result = prime * result + (int) (mFirstTimestamp ^ (mFirstTimestamp >>> 32)); |
| | | result = prime * result + ((mRecordStore == null) ? 0 : mRecordStore.hashCode()); |
| | | result = prime * result + ((mRssiLog == null) ? 0 : mRssiLog.hashCode()); |
| | | result = prime * result + Arrays.hashCode(mScanRecord); |
| | | return result; |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see java.lang.Object#toString() |
| | | */ |
| | | @Override |
| | | public String toString() { |
| | | return "BluetoothLeDevice [mDevice=" + mDevice + ", mRssi=" + mFirstRssi + ", mScanRecord=" + ByteUtils.byteArrayToHexString(mScanRecord) + ", mRecordStore=" + mRecordStore + ", getBluetoothDeviceBondState()=" + getBluetoothDeviceBondState() + ", getBluetoothDeviceClassName()=" + getBluetoothDeviceClassName() + "]"; |
| | | } |
| | | |
| | | /** |
| | | * Update rssi reading. |
| | | * |
| | | * @param timestamp the timestamp |
| | | * @param rssiReading the rssi reading |
| | | */ |
| | | public void updateRssiReading(final long timestamp, final int rssiReading) { |
| | | addToRssiLog(timestamp, rssiReading); |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int) |
| | | */ |
| | | @Override |
| | | public void writeToParcel(final Parcel parcel, final int arg1) { |
| | | final Bundle b = new Bundle(getClass().getClassLoader()); |
| | | |
| | | b.putByteArray(PARCEL_EXTRA_DEVICE_SCANRECORD, mScanRecord); |
| | | |
| | | b.putInt(PARCEL_EXTRA_FIRST_RSSI, mFirstRssi); |
| | | b.putInt(PARCEL_EXTRA_CURRENT_RSSI, mCurrentRssi); |
| | | |
| | | b.putLong(PARCEL_EXTRA_FIRST_TIMESTAMP, mFirstTimestamp); |
| | | b.putLong(PARCEL_EXTRA_CURRENT_TIMESTAMP, mCurrentTimestamp); |
| | | |
| | | b.putParcelable(PARCEL_EXTRA_BLUETOOTH_DEVICE, mDevice); |
| | | b.putParcelable(PARCEL_EXTRA_DEVICE_SCANRECORD_STORE, mRecordStore); |
| | | b.putSerializable(PARCEL_EXTRA_DEVICE_RSSI_LOG, (Serializable) mRssiLog); |
| | | |
| | | parcel.writeBundle(b); |
| | | } |
| | | |
| | | /** |
| | | * Resolve bonding state. |
| | | * |
| | | * @param bondState the bond state |
| | | * @return the string |
| | | */ |
| | | private static String resolveBondingState(final int bondState) { |
| | | switch (bondState) { |
| | | case BluetoothDevice.BOND_BONDED: |
| | | return "Paired"; |
| | | case BluetoothDevice.BOND_BONDING: |
| | | return "Pairing"; |
| | | case BluetoothDevice.BOND_NONE: |
| | | return "Unbonded"; |
| | | default: |
| | | return "Unknown"; |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device; |
| | | |
| | | import android.bluetooth.BluetoothClass; |
| | | |
| | | /** |
| | | * |
| | | */ |
| | | public enum BluetoothService { |
| | | AUDIO(BluetoothClass.Service.AUDIO), |
| | | CAPTURE(BluetoothClass.Service.CAPTURE), |
| | | INFORMATION(BluetoothClass.Service.INFORMATION), |
| | | LIMITED_DISCOVERABILITY(BluetoothClass.Service.LIMITED_DISCOVERABILITY), |
| | | NETWORKING(BluetoothClass.Service.NETWORKING), |
| | | OBJECT_TRANSFER(BluetoothClass.Service.OBJECT_TRANSFER), |
| | | POSITIONING(BluetoothClass.Service.POSITIONING), |
| | | RENDER(BluetoothClass.Service.RENDER), |
| | | TELEPHONY(BluetoothClass.Service.TELEPHONY); |
| | | |
| | | private final int mAndroidConstant; |
| | | |
| | | BluetoothService(final int androidCode){ |
| | | mAndroidConstant = androidCode; |
| | | } |
| | | |
| | | public int getAndroidConstant(){ |
| | | return mAndroidConstant; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device.adrecord; |
| | | |
| | | import android.os.Bundle; |
| | | import android.os.Parcel; |
| | | import android.os.Parcelable; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * Created by Dave Smith |
| | | * Double Encore, Inc. |
| | | * <p> |
| | | * Expanded by Alexandros Schillings |
| | | */ |
| | | public final class AdRecord implements Parcelable { |
| | | // 02 # Number of bytes that follow in first AD structure |
| | | // 01 # Flags AD type |
| | | // 1A # Flags value 0x1A = 000011010 |
| | | // bit 0 (OFF) LE Limited Discoverable Mode |
| | | // bit 1 (ON) LE General Discoverable Mode |
| | | // bit 2 (OFF) BR/EDR Not Supported |
| | | // bit 3 (ON) Simultaneous LE and BR/EDR to Same Device Capable (controller) |
| | | // bit 4 (ON) Simultaneous LE and BR/EDR to Same Device Capable (Host) |
| | | // 1A # Number of bytes that follow in second (and last) AD structure |
| | | // FF # Manufacturer specific data AD type |
| | | // 4C 00 # Company identifier code (0x004C == Apple) |
| | | // 02 # Byte 0 of iBeacon advertisement indicator |
| | | // 15 # Byte 1 of iBeacon advertisement indicator |
| | | // e2 c5 6d b5 df fb 48 d2 b0 60 d0 f5 a7 10 96 e0 # iBeacon proximity uuid |
| | | // 00 00 # major |
| | | // 00 00 # minor |
| | | // c5 # The 2's complement of the calibrated Tx Power |
| | | |
| | | |
| | | /** |
| | | * General FLAGS |
| | | * <p> |
| | | * Description: Flags |
| | | * <p> |
| | | * Information: |
| | | * Bit 0: LE Limited Discoverable Mode |
| | | * Bit 1: LE General Discoverable Mode |
| | | * Bit 2: BR/EDR Not Supported (i.e. bit 37 of LMP Extended Feature bits Page 0) |
| | | * Bit 3: Simultaneous LE and BR/EDR to Same Device Capable (Controller) (i.e. bit 49 of LMP Extended Feature bits Page 0) |
| | | * Bit 4: Simultaneous LE and BR/EDR to Same Device Capable (Host) (i.e. bit 66 of LMP Extended Feature bits Page 1) |
| | | * Bits 5-7 Reserved |
| | | */ |
| | | public static final int TYPE_FLAGS = 0x01; |
| | | // SERVICE |
| | | public static final int TYPE_UUID16_INC = 0x02; |
| | | public static final int TYPE_UUID16 = 0x03; |
| | | public static final int TYPE_UUID32_INC = 0x04; |
| | | public static final int TYPE_UUID32 = 0x05; |
| | | public static final int TYPE_UUID128_INC = 0x06; |
| | | public static final int TYPE_UUID128 = 0x07; |
| | | // Local name |
| | | public static final int TYPE_LOCAL_NAME_SHORT = 0x08; |
| | | public static final int TYPE_LOCAL_NAME_COMPLETE = 0x09; |
| | | // TX Power Level |
| | | public static final int TYPE_TX_POWER_LEVEL = 0x0A; |
| | | // SIMPLE PAIRING OPTIONAL OOB TAGS |
| | | public static final int TYPE_DEVICE_CLASS = 0x0D; |
| | | public static final int TYPE_SIMPLE_PAIRING_HASH_C = 0x0E; |
| | | public static final int TYPE_SIMPLE_PAIRING_RANDOMIZER_R = 0x0F; |
| | | // SECURITY MANAGER TK VALUE |
| | | public static final int TYPE_TK_VALUE = 0x10; |
| | | /* SECURITY MANAGER OOB FLAGS |
| | | * |
| | | * Description: Flag (1 octet) |
| | | * |
| | | * Information: |
| | | * Bit 0: OOB Flags Field: (0 = OOB data not present, 1 = OOB data present) |
| | | * Bit 1: LE supported (Host) (i.e. bit 65 of LMP Extended Feature bits Page 1 |
| | | * Bit 2: Simultaneous LE and BR/EDR to Same Device Capable (Host) (i.e. bit 66 of LMP Extended Feature bits Page 1) |
| | | * Bit 3: Address type (0 = Public Address, 1 = Random Address) |
| | | * Bits 4-7 Reserved |
| | | */ |
| | | public static final int TYPE_SECURITY_MANAGER_OOB_FLAGS = 0x11; |
| | | /* SLAVE CONNECTION INTERVAL RANGE |
| | | * |
| | | * Description: Slave Connection Interval Range |
| | | * |
| | | * Information: |
| | | * The first 2 octets defines the minimum value for the connection interval in the following manner: |
| | | * connInterval min = Conn_Interval_Min * 1.25 ms |
| | | * Conn_Interval_Min range: 0x0006 to 0x0C80 |
| | | * Value of 0xFFFF indicates no specific minimum. |
| | | * Values outside the range are reserved. (excluding 0xFFFF) |
| | | * |
| | | * The second 2 octets defines the maximum value for the connection interval in the following manner: |
| | | * connInterval max = Conn_Interval_Max * 1.25 ms |
| | | * Conn_Interval_Max range: 0x0006 to 0x0C80 |
| | | * Conn_Interval_Max shall be equal to or greater |
| | | * than the Conn_Interval_Min. |
| | | * Value of 0xFFFF indicates no specific maximum. |
| | | * Values outside the range are reserved (excluding 0xFFFF) |
| | | */ |
| | | public static final int TYPE_CONNECTION_INTERVAL_RANGE = 0x12; |
| | | // SERVICE SOLICITATION |
| | | public static final int TYPE_SERVICE_UUIDS_LIST_16BIT = 0x14; |
| | | public static final int TYPE_SERVICE_UUIDS_LIST_128BIT = 0x15; |
| | | /* SERVICE DATA |
| | | * |
| | | * Description: Service Data (2 or more octets) |
| | | * Information: The first 2 octets contain the 16 bit Service UUID followed by additional service data |
| | | */ |
| | | public static final int TYPE_SERVICE_DATA = 0x16; |
| | | /* MANUFACTURER SPECIFIC DATA |
| | | * |
| | | * Description: Manufacturer Specific Data (2 or more octets) |
| | | * Information: The first 2 octets contain the Company Identifier Code followed by additional manufacturer specific data |
| | | */ |
| | | public static final int TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; |
| | | public static final Creator<AdRecord> CREATOR = new Creator<AdRecord>() { |
| | | public AdRecord createFromParcel(final Parcel in) { |
| | | return new AdRecord(in); |
| | | } |
| | | |
| | | public AdRecord[] newArray(final int size) { |
| | | return new AdRecord[size]; |
| | | } |
| | | }; |
| | | private static final String PARCEL_RECORD_DATA = "record_data"; |
| | | private static final String PARCEL_RECORD_TYPE = "record_type"; |
| | | private static final String PARCEL_RECORD_LENGTH = "record_length"; |
| | | /* Model Object Definition */ |
| | | private final int mLength; |
| | | private final int mType; |
| | | private final byte[] mData; |
| | | |
| | | public AdRecord(final int length, final int type, final byte[] data) { |
| | | mLength = length; |
| | | mType = type; |
| | | mData = data; |
| | | } |
| | | |
| | | public AdRecord(final Parcel in) { |
| | | final Bundle b = in.readBundle(getClass().getClassLoader()); |
| | | mLength = b.getInt(PARCEL_RECORD_LENGTH); |
| | | mType = b.getInt(PARCEL_RECORD_TYPE); |
| | | mData = b.getByteArray(PARCEL_RECORD_DATA); |
| | | } |
| | | |
| | | @Override |
| | | public int describeContents() { |
| | | return 0; |
| | | } |
| | | |
| | | public byte[] getData() { |
| | | return mData; |
| | | } |
| | | |
| | | public String getHumanReadableType() { |
| | | return getHumanReadableAdType(mType); |
| | | } |
| | | |
| | | public int getLength() { |
| | | return mLength; |
| | | } |
| | | |
| | | public int getType() { |
| | | return mType; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "AdRecord [mLength=" + mLength + ", mType=" + mType + ", mData=" + Arrays.toString(mData) + ", getHumanReadableType()=" + getHumanReadableType() + "]"; |
| | | } |
| | | |
| | | @Override |
| | | public void writeToParcel(final Parcel parcel, final int arg1) { |
| | | final Bundle b = new Bundle(getClass().getClassLoader()); |
| | | |
| | | b.putInt(PARCEL_RECORD_LENGTH, mLength); |
| | | b.putInt(PARCEL_RECORD_TYPE, mType); |
| | | b.putByteArray(PARCEL_RECORD_DATA, mData); |
| | | |
| | | parcel.writeBundle(b); |
| | | } |
| | | |
| | | private static String getHumanReadableAdType(final int type) { |
| | | switch (type) { |
| | | case TYPE_CONNECTION_INTERVAL_RANGE: |
| | | return "Slave Connection Interval Range"; |
| | | case TYPE_DEVICE_CLASS: |
| | | return "Class of device"; |
| | | case TYPE_FLAGS: |
| | | return "Flags"; |
| | | case TYPE_MANUFACTURER_SPECIFIC_DATA: |
| | | return "Manufacturer Specific Data"; |
| | | case TYPE_LOCAL_NAME_COMPLETE: |
| | | return "Name (Complete)"; |
| | | case TYPE_LOCAL_NAME_SHORT: |
| | | return "Name (Short)"; |
| | | case TYPE_SECURITY_MANAGER_OOB_FLAGS: |
| | | return "Security Manager OOB Flags"; |
| | | case TYPE_SERVICE_UUIDS_LIST_128BIT: |
| | | return "Service UUIDs (128bit)"; |
| | | case TYPE_SERVICE_UUIDS_LIST_16BIT: |
| | | return "Service UUIDs (16bit)"; |
| | | case TYPE_SERVICE_DATA: |
| | | return "Service Data"; |
| | | case TYPE_SIMPLE_PAIRING_HASH_C: |
| | | return "Simple Pairing Hash C"; |
| | | case TYPE_SIMPLE_PAIRING_RANDOMIZER_R: |
| | | return "Simple Pairing Randomizer R"; |
| | | case TYPE_TK_VALUE: |
| | | return "TK Value"; |
| | | case TYPE_TX_POWER_LEVEL: |
| | | return "Transmission Power Level"; |
| | | case TYPE_UUID128: |
| | | return "Complete list of 128-bit UUIDs available"; |
| | | case TYPE_UUID128_INC: |
| | | return "More 128-bit UUIDs available"; |
| | | case TYPE_UUID16: |
| | | return "Complete list of 16-bit UUIDs available"; |
| | | case TYPE_UUID16_INC: |
| | | return "More 16-bit UUIDs available"; |
| | | case TYPE_UUID32: |
| | | return "Complete list of 32-bit UUIDs available"; |
| | | case TYPE_UUID32_INC: |
| | | return "More 32-bit UUIDs available"; |
| | | default: |
| | | return "Unknown AdRecord Structure: " + type; |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device.adrecord; |
| | | |
| | | import android.os.Bundle; |
| | | import android.os.Parcel; |
| | | import android.os.Parcelable; |
| | | import android.util.SparseArray; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | |
| | | |
| | | /** |
| | | * The Class AdRecordStore. |
| | | */ |
| | | public class AdRecordStore implements Parcelable { |
| | | public static final Creator<AdRecordStore> CREATOR = new Creator<AdRecordStore>() { |
| | | public AdRecordStore createFromParcel(final Parcel in) { |
| | | return new AdRecordStore(in); |
| | | } |
| | | |
| | | public AdRecordStore[] newArray(final int size) { |
| | | return new AdRecordStore[size]; |
| | | } |
| | | }; |
| | | private final SparseArray<AdRecord> mAdRecords; |
| | | private final String mLocalNameComplete; |
| | | private final String mLocalNameShort; |
| | | |
| | | public AdRecordStore(final Parcel in) { |
| | | final Bundle b = in.readBundle(getClass().getClassLoader()); |
| | | mAdRecords = b.getSparseParcelableArray("records_array"); |
| | | mLocalNameComplete = b.getString("local_name_complete"); |
| | | mLocalNameShort = b.getString("local_name_short"); |
| | | } |
| | | |
| | | /** |
| | | * Instantiates a new Bluetooth LE device Ad Record Store. |
| | | * |
| | | * @param adRecords the ad records |
| | | */ |
| | | public AdRecordStore(final SparseArray<AdRecord> adRecords) { |
| | | mAdRecords = adRecords; |
| | | |
| | | mLocalNameComplete = AdRecordUtils.getRecordDataAsString( |
| | | mAdRecords.get(AdRecord.TYPE_LOCAL_NAME_COMPLETE)); |
| | | |
| | | mLocalNameShort = AdRecordUtils.getRecordDataAsString( |
| | | mAdRecords.get(AdRecord.TYPE_LOCAL_NAME_SHORT)); |
| | | |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see android.os.Parcelable#describeContents() |
| | | */ |
| | | @Override |
| | | public int describeContents() { |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * Gets the short local device name. |
| | | * |
| | | * @return the local name complete |
| | | */ |
| | | public String getLocalNameComplete() { |
| | | return mLocalNameComplete; |
| | | } |
| | | |
| | | /** |
| | | * Gets the complete local device name. |
| | | * |
| | | * @return the local name short |
| | | */ |
| | | public String getLocalNameShort() { |
| | | return mLocalNameShort; |
| | | } |
| | | |
| | | /** |
| | | * retrieves an individual record. |
| | | * |
| | | * @param record the record |
| | | * @return the record |
| | | */ |
| | | public AdRecord getRecord(final int record) { |
| | | return mAdRecords.get(record); |
| | | } |
| | | |
| | | /** |
| | | * Gets the record data as string. |
| | | * |
| | | * @param record the record |
| | | * @return the record data as string |
| | | */ |
| | | public String getRecordDataAsString(final int record) { |
| | | return AdRecordUtils.getRecordDataAsString( |
| | | mAdRecords.get(record)); |
| | | } |
| | | |
| | | /** |
| | | * Gets the record as collection. |
| | | * |
| | | * @return the records as collection |
| | | */ |
| | | public Collection<AdRecord> getRecordsAsCollection() { |
| | | return Collections.unmodifiableCollection(asList(mAdRecords)); |
| | | } |
| | | |
| | | /** |
| | | * Checks if is record present. |
| | | * |
| | | * @param record the record |
| | | * @return true, if is record present |
| | | */ |
| | | public boolean isRecordPresent(final int record) { |
| | | return mAdRecords.indexOfKey(record) >= 0; |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see java.lang.Object#toString() |
| | | */ |
| | | @Override |
| | | public String toString() { |
| | | return "AdRecordStore [mLocalNameComplete=" + mLocalNameComplete + ", mLocalNameShort=" + mLocalNameShort + "]"; |
| | | } |
| | | |
| | | /* (non-Javadoc) |
| | | * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int) |
| | | */ |
| | | @Override |
| | | public void writeToParcel(final Parcel parcel, final int arg1) { |
| | | final Bundle b = new Bundle(); |
| | | b.putString("local_name_complete", mLocalNameComplete); |
| | | b.putString("local_name_short", mLocalNameShort); |
| | | b.putSparseParcelableArray("records_array", mAdRecords); |
| | | |
| | | parcel.writeBundle(b); |
| | | } |
| | | |
| | | /** |
| | | * As list. |
| | | * |
| | | * @param <C> the generic type |
| | | * @param sparseArray the sparse array |
| | | * @return the collection |
| | | */ |
| | | public static <C> Collection<C> asList(final SparseArray<C> sparseArray) { |
| | | if (sparseArray == null) return null; |
| | | |
| | | final Collection<C> arrayList = new ArrayList<>(sparseArray.size()); |
| | | for (int i = 0; i < sparseArray.size(); i++) { |
| | | arrayList.add(sparseArray.valueAt(i)); |
| | | } |
| | | |
| | | return arrayList; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device.adrecord; |
| | | |
| | | import android.annotation.SuppressLint; |
| | | import android.util.SparseArray; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | |
| | | public final class AdRecordUtils { |
| | | |
| | | private AdRecordUtils(){ |
| | | // TO AVOID INSTANTIATION |
| | | } |
| | | |
| | | public static String getRecordDataAsString(final AdRecord nameRecord) { |
| | | if (nameRecord == null) { |
| | | return ""; |
| | | } |
| | | return new String(nameRecord.getData()); |
| | | } |
| | | |
| | | public static byte[] getServiceData(final AdRecord serviceData) { |
| | | if (serviceData == null) { |
| | | return null; |
| | | } |
| | | if (serviceData.getType() != AdRecord.TYPE_SERVICE_DATA) return null; |
| | | |
| | | final byte[] raw = serviceData.getData(); |
| | | //Chop out the uuid |
| | | return Arrays.copyOfRange(raw, 2, raw.length); |
| | | } |
| | | |
| | | public static int getServiceDataUuid(final AdRecord serviceData) { |
| | | if (serviceData == null) { |
| | | return -1; |
| | | } |
| | | if (serviceData.getType() != AdRecord.TYPE_SERVICE_DATA) return -1; |
| | | |
| | | final byte[] raw = serviceData.getData(); |
| | | //Find UUID data in byte array |
| | | int uuid = (raw[1] & 0xFF) << 8; |
| | | uuid += (raw[0] & 0xFF); |
| | | |
| | | return uuid; |
| | | } |
| | | |
| | | /* |
| | | * Read out all the AD structures from the raw scan record |
| | | */ |
| | | public static List<AdRecord> parseScanRecordAsList(final byte[] scanRecord) { |
| | | final List<AdRecord> records = new ArrayList<>(); |
| | | |
| | | int index = 0; |
| | | while (index < scanRecord.length) { |
| | | final int length = scanRecord[index++]; |
| | | //Done once we run out of records |
| | | if (length == 0) break; |
| | | |
| | | final int type = ByteUtils.getIntFromByte(scanRecord[index]); |
| | | |
| | | //Done if our record isn't a valid type |
| | | if (type == 0) break; |
| | | |
| | | final byte[] data = Arrays.copyOfRange(scanRecord, index + 1, index + length); |
| | | |
| | | records.add(new AdRecord(length, type, data)); |
| | | |
| | | //Advance |
| | | index += length; |
| | | } |
| | | |
| | | return Collections.unmodifiableList(records); |
| | | } |
| | | |
| | | @SuppressLint("UseSparseArrays") |
| | | public static Map<Integer, AdRecord> parseScanRecordAsMap(final byte[] scanRecord) { |
| | | final Map<Integer, AdRecord> records = new HashMap<>(); |
| | | |
| | | int index = 0; |
| | | while (index < scanRecord.length) { |
| | | final int length = scanRecord[index++]; |
| | | //Done once we run out of records |
| | | if (length == 0) break; |
| | | |
| | | final int type = ByteUtils.getIntFromByte(scanRecord[index]); |
| | | |
| | | //Done if our record isn't a valid type |
| | | if (type == 0) break; |
| | | |
| | | final byte[] data = Arrays.copyOfRange(scanRecord, index + 1, index + length); |
| | | |
| | | records.put(type, new AdRecord(length, type, data)); |
| | | |
| | | //Advance |
| | | index += length; |
| | | } |
| | | |
| | | return Collections.unmodifiableMap(records); |
| | | } |
| | | |
| | | public static SparseArray<AdRecord> parseScanRecordAsSparseArray(final byte[] scanRecord) { |
| | | final SparseArray<AdRecord> records = new SparseArray<>(); |
| | | |
| | | int index = 0; |
| | | while (index < scanRecord.length) { |
| | | final int length = scanRecord[index++]; |
| | | //Done once we run out of records |
| | | if (length == 0) break; |
| | | |
| | | final int type = ByteUtils.getIntFromByte(scanRecord[index]); |
| | | |
| | | //Done if our record isn't a valid type |
| | | if (type == 0) break; |
| | | |
| | | final byte[] data = Arrays.copyOfRange(scanRecord, index + 1, index + length); |
| | | |
| | | records.put(type, new AdRecord(length, type, data)); |
| | | |
| | | //Advance |
| | | index += length; |
| | | } |
| | | |
| | | return records; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device.ibeacon; |
| | | |
| | | /** |
| | | * |
| | | */ |
| | | public class IBeaconConstants { |
| | | public static final byte[] MANUFACTURER_DATA_IBEACON_PREFIX = {0x4C, 0x00, 0x02, 0x15}; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device.ibeacon; |
| | | |
| | | import android.annotation.SuppressLint; |
| | | import android.bluetooth.BluetoothDevice; |
| | | import android.os.Parcel; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.device.BeaconDevice; |
| | | import com.blakequ.bluetooth_manager_lib.device.BeaconType; |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | |
| | | |
| | | @SuppressLint("ParcelCreator") |
| | | public class IBeaconDevice extends BluetoothLeDevice implements BeaconDevice { |
| | | |
| | | /** |
| | | * The m iBeacon data. |
| | | */ |
| | | private final IBeaconManufacturerData mIBeaconData; |
| | | |
| | | /** |
| | | * Instantiates a new iBeacon device. |
| | | * |
| | | * @param device the device |
| | | * @param rssi the RSSI value |
| | | * @param scanRecord the scanRecord |
| | | * @throws IllegalArgumentException if the passed device is not an iBeacon |
| | | */ |
| | | public IBeaconDevice(final BluetoothDevice device, final int rssi, final byte[] scanRecord) { |
| | | super(device, rssi, scanRecord, 0); |
| | | mIBeaconData = new IBeaconManufacturerData(this); |
| | | } |
| | | |
| | | /** |
| | | * Instantiates a new iBeacon device. |
| | | * |
| | | * @param device the device |
| | | * @param rssi the RSSI value of the RSSI measurement |
| | | * @param scanRecord the scan record |
| | | * @param timestamp the timestamp of the RSSI measurement |
| | | * @throws IllegalArgumentException if the passed device is not an iBeacon |
| | | */ |
| | | public IBeaconDevice(final BluetoothDevice device, final int rssi, final byte[] scanRecord, final long timestamp) { |
| | | super(device, rssi, scanRecord, timestamp); |
| | | mIBeaconData = new IBeaconManufacturerData(this); |
| | | } |
| | | |
| | | /** |
| | | * Will try to convert a {@link BluetoothLeDevice} into an |
| | | * iBeacon Device. |
| | | * |
| | | * @param device the device |
| | | * @throws IllegalArgumentException if the passed device is not an iBeacon |
| | | */ |
| | | public IBeaconDevice(final BluetoothLeDevice device) { |
| | | super(device); |
| | | mIBeaconData = new IBeaconManufacturerData(this); |
| | | } |
| | | |
| | | private IBeaconDevice(final Parcel in) { |
| | | super(in); |
| | | mIBeaconData = new IBeaconManufacturerData(this); |
| | | } |
| | | |
| | | /** |
| | | * Gets the estimated Accuracy of the reading in meters based on |
| | | * a simple running average of the last {@link #MAX_RSSI_LOG_SIZE} |
| | | * samples. |
| | | * |
| | | * @return the accuracy in meters |
| | | */ |
| | | public double getAccuracy() { |
| | | return IBeaconUtils.calculateAccuracy( |
| | | getCalibratedTxPower(), |
| | | getRunningAverageRssi()); |
| | | } |
| | | |
| | | @Override |
| | | public BeaconType getBeaconType() { |
| | | return BeaconType.IBEACON; |
| | | } |
| | | |
| | | /** |
| | | * Gets the calibrated TX power of the iBeacon device as reported. |
| | | * |
| | | * @return the calibrated TX power |
| | | */ |
| | | public int getCalibratedTxPower() { |
| | | return getIBeaconData().getCalibratedTxPower(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the iBeacon company identifier. |
| | | * |
| | | * @return the company identifier |
| | | */ |
| | | public int getCompanyIdentifier() { |
| | | return getIBeaconData().getCompanyIdentifier(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the estimated Distance descriptor. |
| | | * |
| | | * @return the distance descriptor |
| | | */ |
| | | public IBeaconDistanceDescriptor getDistanceDescriptor() { |
| | | return IBeaconUtils.getDistanceDescriptor(getAccuracy()); |
| | | } |
| | | |
| | | /** |
| | | * Gets the iBeacon manufacturing data. |
| | | * |
| | | * @return the iBeacon data |
| | | */ |
| | | public IBeaconManufacturerData getIBeaconData() { |
| | | return mIBeaconData; |
| | | } |
| | | |
| | | /** |
| | | * Gets the iBeacon Major value. |
| | | * |
| | | * @return the Major value |
| | | */ |
| | | public int getMajor() { |
| | | return getIBeaconData().getMajor(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the iBeacon Minor value. |
| | | * |
| | | * @return the Minor value |
| | | */ |
| | | public int getMinor() { |
| | | return getIBeaconData().getMinor(); |
| | | } |
| | | |
| | | /** |
| | | * Gets the iBeacon UUID. |
| | | * |
| | | * @return the UUID |
| | | */ |
| | | public String getUUID() { |
| | | return getIBeaconData().getUUID(); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device.ibeacon; |
| | | |
| | | public enum IBeaconDistanceDescriptor { |
| | | IMMEDIATE, |
| | | NEAR, |
| | | FAR, |
| | | UNKNOWN, |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device.ibeacon; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.device.BeaconManufacturerData; |
| | | import com.blakequ.bluetooth_manager_lib.device.BeaconType; |
| | | import com.blakequ.bluetooth_manager_lib.device.BluetoothLeDevice; |
| | | import com.blakequ.bluetooth_manager_lib.device.adrecord.AdRecord; |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | |
| | | /** |
| | | * Parses the Manufactured Data field of an iBeacon |
| | | * <p> |
| | | * The parsing is based on the following schema: |
| | | * <pre> |
| | | * Byte|Value |
| | | * ------------------------------------------------- |
| | | * 0 4C - Byte 1 (LSB) of Company identifier code |
| | | * 1 00 - Byte 0 (MSB) of Company identifier code (0x004C == Apple) |
| | | * 2 02 - Byte 0 of iBeacon advertisement indicator |
| | | * 3 15 - Byte 1 of iBeacon advertisement indicator |
| | | * 4 e2 |\ |
| | | * 5 c5 |\\ |
| | | * 6 6d |#\\ |
| | | * 7 b5 |##\\ |
| | | * 8 df |###\\ |
| | | * 9 fb |####\\ |
| | | * 10 48 |#####\\ |
| | | * 11 d2 |#####|| iBeacon |
| | | * 12 b0 |#####|| Proximity UUID |
| | | * 13 60 |#####// |
| | | * 14 d0 |####// |
| | | * 15 f5 |###// |
| | | * 16 a7 |##// |
| | | * 17 10 |#// |
| | | * 18 96 |// |
| | | * 19 e0 |/ |
| | | * 20 00 - major |
| | | * 21 00 |
| | | * 22 00 - minor |
| | | * 23 00 |
| | | * 24 c5 - The 2's complement of the calibrated Tx Power |
| | | * </pre> |
| | | * @author Alexandros Schillings |
| | | */ |
| | | |
| | | public final class IBeaconManufacturerData extends BeaconManufacturerData { |
| | | private final int mCalibratedTxPower; |
| | | private final int mCompanyIdentidier; |
| | | private final int mIBeaconAdvertisment; |
| | | private final int mMajor; |
| | | private final int mMinor; |
| | | private final String mUUID; |
| | | |
| | | /** |
| | | * Instantiates a new iBeacon manufacturer data object. |
| | | * |
| | | * @param device a {@link BluetoothLeDevice} |
| | | * @throws IllegalArgumentException if the data is not from an iBeacon. |
| | | */ |
| | | public IBeaconManufacturerData(final BluetoothLeDevice device) { |
| | | this(device.getAdRecordStore().getRecord(AdRecord.TYPE_MANUFACTURER_SPECIFIC_DATA).getData()); |
| | | } |
| | | |
| | | /** |
| | | * Instantiates a new iBeacon manufacturer data object. |
| | | * |
| | | * @param manufacturerData the {@link AdRecord#TYPE_MANUFACTURER_SPECIFIC_DATA} data array |
| | | * @throws IllegalArgumentException if the data is not from an iBeacon. |
| | | */ |
| | | public IBeaconManufacturerData(final byte[] manufacturerData) { |
| | | super(BeaconType.IBEACON, manufacturerData); |
| | | |
| | | final byte[] intArray = Arrays.copyOfRange(manufacturerData, 0, 2); |
| | | ByteUtils.invertArray(intArray); |
| | | |
| | | mCompanyIdentidier = ByteUtils.getIntFrom2ByteArray(intArray); |
| | | mIBeaconAdvertisment = ByteUtils.getIntFrom2ByteArray(Arrays.copyOfRange(manufacturerData, 2, 4)); |
| | | mUUID = IBeaconUtils.calculateUuidString(Arrays.copyOfRange(manufacturerData, 4, 20)); |
| | | mMajor = ByteUtils.getIntFrom2ByteArray(Arrays.copyOfRange(manufacturerData, 20, 22)); |
| | | mMinor = ByteUtils.getIntFrom2ByteArray(Arrays.copyOfRange(manufacturerData, 22, 24)); |
| | | mCalibratedTxPower = manufacturerData[24]; |
| | | } |
| | | |
| | | /** |
| | | * Gets the calibrated TX power of the iBeacon device as reported. |
| | | * |
| | | * @return the calibrated TX power |
| | | */ |
| | | public int getCalibratedTxPower() { |
| | | return mCalibratedTxPower; |
| | | } |
| | | |
| | | /** |
| | | * Gets the iBeacon company identifier. |
| | | * |
| | | * @return the company identifier |
| | | */ |
| | | public int getCompanyIdentifier() { |
| | | return mCompanyIdentidier; |
| | | } |
| | | |
| | | public int getIBeaconAdvertisement() { |
| | | return mIBeaconAdvertisment; |
| | | } |
| | | |
| | | /** |
| | | * Gets the iBeacon Major value. |
| | | * |
| | | * @return the Major value |
| | | */ |
| | | public int getMajor() { |
| | | return mMajor; |
| | | } |
| | | |
| | | /** |
| | | * Gets the iBeacon Minor value. |
| | | * |
| | | * @return the Minor value |
| | | */ |
| | | public int getMinor() { |
| | | return mMinor; |
| | | } |
| | | |
| | | /** |
| | | * Gets the iBeacon UUID. |
| | | * |
| | | * @return the UUID |
| | | */ |
| | | public String getUUID() { |
| | | return mUUID; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device.ibeacon; |
| | | |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.util.ByteUtils; |
| | | |
| | | final class IBeaconUtils { |
| | | private static final double DISTANCE_THRESHOLD_WTF = 0.0; |
| | | private static final double DISTANCE_THRESHOLD_IMMEDIATE = 0.5; |
| | | private static final double DISTANCE_THRESHOLD_NEAR = 3.0; |
| | | |
| | | private IBeaconUtils(){ |
| | | // TO AVOID INSTANTIATION |
| | | } |
| | | |
| | | /** |
| | | * Calculates the accuracy of an RSSI reading. |
| | | * <p/> |
| | | * The code was taken from <a href="http://stackoverflow.com/questions/20416218/understanding-ibeacon-distancing" /a> |
| | | * |
| | | * @param txPower the calibrated TX power of an iBeacon |
| | | * @param rssi the RSSI value of the iBeacon |
| | | * @return the calculated Accuracy |
| | | */ |
| | | public static double calculateAccuracy(final int txPower, final double rssi) { |
| | | if (rssi == 0) { |
| | | return -1.0; // if we cannot determine accuracy, return -1. |
| | | } |
| | | |
| | | final double ratio = rssi * 1.0 / txPower; |
| | | if (ratio < 1.0) { |
| | | return Math.pow(ratio, 10); |
| | | } else { |
| | | return (0.89976) * Math.pow(ratio, 7.7095) + 0.111; |
| | | } |
| | | } |
| | | |
| | | public static String calculateUuidString(final byte[] uuid) { |
| | | final StringBuilder sb = new StringBuilder(); |
| | | |
| | | for (int i = 0; i < uuid.length; i++) { |
| | | if (i == 4) { |
| | | sb.append('-'); |
| | | } |
| | | if (i == 6) { |
| | | sb.append('-'); |
| | | } |
| | | if (i == 8) { |
| | | sb.append('-'); |
| | | } |
| | | if (i == 10) { |
| | | sb.append('-'); |
| | | } |
| | | |
| | | final int intFromByte = ByteUtils.getIntFromByte(uuid[i]); |
| | | if(intFromByte <= 0xF){ |
| | | sb.append('0'); |
| | | } |
| | | sb.append(Integer.toHexString(intFromByte)); |
| | | } |
| | | |
| | | |
| | | return sb.toString(); |
| | | } |
| | | |
| | | public static IBeaconDistanceDescriptor getDistanceDescriptor(final double accuracy) { |
| | | if (accuracy < DISTANCE_THRESHOLD_WTF) { |
| | | return IBeaconDistanceDescriptor.UNKNOWN; |
| | | } |
| | | |
| | | if (accuracy < DISTANCE_THRESHOLD_IMMEDIATE) { |
| | | return IBeaconDistanceDescriptor.IMMEDIATE; |
| | | } |
| | | |
| | | if (accuracy < DISTANCE_THRESHOLD_NEAR) { |
| | | return IBeaconDistanceDescriptor.NEAR; |
| | | } |
| | | |
| | | return IBeaconDistanceDescriptor.FAR; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device.resolvers; |
| | | |
| | | import android.bluetooth.BluetoothClass; |
| | | |
| | | public class BluetoothClassResolver { |
| | | |
| | | public static String resolveDeviceClass(final int btClass) { |
| | | switch (btClass) { |
| | | case BluetoothClass.Device.AUDIO_VIDEO_CAMCORDER: |
| | | return "A/V, Camcorder"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: |
| | | return "A/V, Car Audio"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: |
| | | return "A/V, Handsfree"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: |
| | | return "A/V, Headphones"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: |
| | | return "A/V, HiFi Audio"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER: |
| | | return "A/V, Loudspeaker"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_MICROPHONE: |
| | | return "A/V, Microphone"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: |
| | | return "A/V, Portable Audio"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_SET_TOP_BOX: |
| | | return "A/V, Set Top Box"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_UNCATEGORIZED: |
| | | return "A/V, Uncategorized"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_VCR: |
| | | return "A/V, VCR"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CAMERA: |
| | | return "A/V, Video Camera"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CONFERENCING: |
| | | return "A/V, Video Conferencing"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER: |
| | | return "A/V, Video Display and Loudspeaker"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_GAMING_TOY: |
| | | return "A/V, Video Gaming Toy"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_MONITOR: |
| | | return "A/V, Video Monitor"; |
| | | case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: |
| | | return "A/V, Video Wearable Headset"; |
| | | case BluetoothClass.Device.COMPUTER_DESKTOP: |
| | | return "Computer, Desktop"; |
| | | case BluetoothClass.Device.COMPUTER_HANDHELD_PC_PDA: |
| | | return "Computer, Handheld PC/PDA"; |
| | | case BluetoothClass.Device.COMPUTER_LAPTOP: |
| | | return "Computer, Laptop"; |
| | | case BluetoothClass.Device.COMPUTER_PALM_SIZE_PC_PDA: |
| | | return "Computer, Palm Size PC/PDA"; |
| | | case BluetoothClass.Device.COMPUTER_SERVER: |
| | | return "Computer, Server"; |
| | | case BluetoothClass.Device.COMPUTER_UNCATEGORIZED: |
| | | return "Computer, Uncategorized"; |
| | | case BluetoothClass.Device.COMPUTER_WEARABLE: |
| | | return "Computer, Wearable"; |
| | | case BluetoothClass.Device.HEALTH_BLOOD_PRESSURE: |
| | | return "Health, Blood Pressure"; |
| | | case BluetoothClass.Device.HEALTH_DATA_DISPLAY: |
| | | return "Health, Data Display"; |
| | | case BluetoothClass.Device.HEALTH_GLUCOSE: |
| | | return "Health, Glucose"; |
| | | case BluetoothClass.Device.HEALTH_PULSE_OXIMETER: |
| | | return "Health, Pulse Oximeter"; |
| | | case BluetoothClass.Device.HEALTH_PULSE_RATE: |
| | | return "Health, Pulse Rate"; |
| | | case BluetoothClass.Device.HEALTH_THERMOMETER: |
| | | return "Health, Thermometer"; |
| | | case BluetoothClass.Device.HEALTH_UNCATEGORIZED: |
| | | return "Health, Uncategorized"; |
| | | case BluetoothClass.Device.HEALTH_WEIGHING: |
| | | return "Health, Weighting"; |
| | | case BluetoothClass.Device.PHONE_CELLULAR: |
| | | return "Phone, Cellular"; |
| | | case BluetoothClass.Device.PHONE_CORDLESS: |
| | | return "Phone, Cordless"; |
| | | case BluetoothClass.Device.PHONE_ISDN: |
| | | return "Phone, ISDN"; |
| | | case BluetoothClass.Device.PHONE_MODEM_OR_GATEWAY: |
| | | return "Phone, Modem or Gateway"; |
| | | case BluetoothClass.Device.PHONE_SMART: |
| | | return "Phone, Smart"; |
| | | case BluetoothClass.Device.PHONE_UNCATEGORIZED: |
| | | return "Phone, Uncategorized"; |
| | | case BluetoothClass.Device.TOY_CONTROLLER: |
| | | return "Toy, Controller"; |
| | | case BluetoothClass.Device.TOY_DOLL_ACTION_FIGURE: |
| | | return "Toy, Doll/Action Figure"; |
| | | case BluetoothClass.Device.TOY_GAME: |
| | | return "Toy, Game"; |
| | | case BluetoothClass.Device.TOY_ROBOT: |
| | | return "Toy, Robot"; |
| | | case BluetoothClass.Device.TOY_UNCATEGORIZED: |
| | | return "Toy, Uncategorized"; |
| | | case BluetoothClass.Device.TOY_VEHICLE: |
| | | return "Toy, Vehicle"; |
| | | case BluetoothClass.Device.WEARABLE_GLASSES: |
| | | return "Wearable, Glasses"; |
| | | case BluetoothClass.Device.WEARABLE_HELMET: |
| | | return "Wearable, Helmet"; |
| | | case BluetoothClass.Device.WEARABLE_JACKET: |
| | | return "Wearable, Jacket"; |
| | | case BluetoothClass.Device.WEARABLE_PAGER: |
| | | return "Wearable, Pager"; |
| | | case BluetoothClass.Device.WEARABLE_UNCATEGORIZED: |
| | | return "Wearable, Uncategorized"; |
| | | case BluetoothClass.Device.WEARABLE_WRIST_WATCH: |
| | | return "Wearable, Wrist Watch"; |
| | | default: |
| | | return "Unknown, Unknown (class=" + btClass + ")"; |
| | | } |
| | | } |
| | | |
| | | public static String resolveMajorDeviceClass(final int majorBtClass) { |
| | | switch (majorBtClass) { |
| | | case BluetoothClass.Device.Major.AUDIO_VIDEO: |
| | | return "Audio/ Video"; |
| | | case BluetoothClass.Device.Major.COMPUTER: |
| | | return "Computer"; |
| | | case BluetoothClass.Device.Major.HEALTH: |
| | | return "Health"; |
| | | case BluetoothClass.Device.Major.IMAGING: |
| | | return "Imaging"; |
| | | case BluetoothClass.Device.Major.MISC: |
| | | return "Misc"; |
| | | case BluetoothClass.Device.Major.NETWORKING: |
| | | return "Networking"; |
| | | case BluetoothClass.Device.Major.PERIPHERAL: |
| | | return "Peripheral"; |
| | | case BluetoothClass.Device.Major.PHONE: |
| | | return "Phone"; |
| | | case BluetoothClass.Device.Major.TOY: |
| | | return "Toy"; |
| | | case BluetoothClass.Device.Major.UNCATEGORIZED: |
| | | return "Uncategorized"; |
| | | case BluetoothClass.Device.Major.WEARABLE: |
| | | return "Wearable"; |
| | | default: |
| | | return "Unknown (" +majorBtClass+ ")"; |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device.resolvers; |
| | | |
| | | import android.util.SparseArray; |
| | | |
| | | public class CompanyIdentifierResolver { |
| | | public static final int ERICSSON_TECHNOLOGY_LICENSING = 0x0000; |
| | | public static final int NOKIA_MOBILE_PHONES = 0x0001; |
| | | public static final int INTEL_CORP = 0x0002; |
| | | public static final int IBM_CORP = 0x0003; |
| | | public static final int TOSHIBA_CORP = 0x0004; |
| | | public static final int THREE_COM = 0x0005; |
| | | public static final int MICROSOFT = 0x0006; |
| | | public static final int LUCENT = 0x0007; |
| | | public static final int MOTOROLA = 0x0008; |
| | | public static final int INFINEON_TECHNOLOGIES_AG = 0x0009; |
| | | public static final int CAMBRIDGE_SILICON_RADIO = 0x000A; |
| | | public static final int SILICON_WAVE = 0x000B; |
| | | public static final int DIGIANSWER_A_S = 0x000C; |
| | | public static final int TEXAS_INSTRUMENTS_INC = 0x000D; |
| | | public static final int CEVA_INC_FORMERLY_PARTHUS_TECHNOLOGIES_INC = 0x000E; |
| | | public static final int BROADCOM_CORPORATION = 0x000F; |
| | | public static final int MITEL_SEMICONDUCTOR = 0x0010; |
| | | public static final int WIDCOMM_INC = 0x0011; |
| | | public static final int ZEEVO_INC = 0x0012; |
| | | public static final int ATMEL_CORPORATION = 0x0013; |
| | | public static final int MITSUBISHI_ELECTRIC_CORPORATION = 0x0014; |
| | | public static final int RTX_TELECOM_A_S = 0x0015; |
| | | public static final int KC_TECHNOLOGY_INC = 0x0016; |
| | | public static final int NEWLOGIC = 0x0017; |
| | | public static final int TRANSILICA_INC = 0x0018; |
| | | public static final int ROHDE_SCHWARZ_GMBH_CO_KG = 0x0019; |
| | | public static final int TTPCOM_LIMITED = 0x001A; |
| | | public static final int SIGNIA_TECHNOLOGIES_INC = 0x001B; |
| | | public static final int CONEXANT_SYSTEMS_INC = 0x001C; |
| | | public static final int QUALCOMM = 0x001D; |
| | | public static final int INVENTEL = 0x001E; |
| | | public static final int AVM_BERLIN = 0x001F; |
| | | public static final int BANDSPEED_INC = 0x0020; |
| | | public static final int MANSELLA_LTD = 0x0021; |
| | | public static final int NEC_CORPORATION = 0x0022; |
| | | public static final int WAVEPLUS_TECHNOLOGY_CO_LTD = 0x0023; |
| | | public static final int ALCATEL = 0x0024; |
| | | public static final int PHILIPS_SEMICONDUCTORS = 0x0025; |
| | | public static final int C_TECHNOLOGIES = 0x0026; |
| | | public static final int OPEN_INTERFACE = 0x0027; |
| | | public static final int R_F_MICRO_DEVICES = 0x0028; |
| | | public static final int HITACHI_LTD = 0x0029; |
| | | public static final int SYMBOL_TECHNOLOGIES_INC = 0x002A; |
| | | public static final int TENOVIS = 0x002B; |
| | | public static final int MACRONIX_INTERNATIONAL_CO_LTD = 0x002C; |
| | | public static final int GCT_SEMICONDUCTOR = 0x002D; |
| | | public static final int NORWOOD_SYSTEMS = 0x002E; |
| | | public static final int MEWTEL_TECHNOLOGY_INC = 0x002F; |
| | | public static final int ST_MICROELECTRONICS = 0x0030; |
| | | public static final int SYNOPSIS = 0x0031; |
| | | public static final int REDM_COMMUNICATIONS_LTD = 0x0032; |
| | | public static final int COMMIL_LTD = 0x0033; |
| | | public static final int COMPUTER_ACCESS_TECHNOLOGY_CORPORATION_CATC = 0x0034; |
| | | public static final int ECLIPSE_HQ_ESPANA_SL = 0x0035; |
| | | public static final int RENESAS_TECHNOLOGY_CORP = 0x0036; |
| | | public static final int MOBILIAN_CORPORATION = 0x0037; |
| | | public static final int TERAX = 0x0038; |
| | | public static final int INTEGRATED_SYSTEM_SOLUTION_CORP = 0x0039; |
| | | public static final int MATSUSHITA_ELECTRIC_INDUSTRIAL_CO_LTD = 0x003A; |
| | | public static final int GENNUM_CORPORATION = 0x003B; |
| | | public static final int RESEARCH_IN_MOTION = 0x003C; |
| | | public static final int IPEXTREME_INC = 0x003D; |
| | | public static final int SYSTEMS_AND_CHIPS_INC = 0x003E; |
| | | public static final int BLUETOOTH_SIG_INC = 0x003F; |
| | | public static final int SEIKO_EPSON_CORPORATION = 0x0040; |
| | | public static final int INTEGRATED_SILICON_SOLUTION_TAIWAN_INC = 0x0041; |
| | | public static final int CONWISE_TECHNOLOGY_CORPORATION_LTD = 0x0042; |
| | | public static final int PARROT_SA = 0x0043; |
| | | public static final int SOCKET_MOBILE = 0x0044; |
| | | public static final int ATHEROS_COMMUNICATIONS_INC = 0x0045; |
| | | public static final int MEDIATEK_INC = 0x0046; |
| | | public static final int BLUEGIGA = 0x0047; |
| | | public static final int MARVELL_TECHNOLOGY_GROUP_LTD = 0x0048; |
| | | public static final int THREE_DSP_CORPORATION = 0x0049; |
| | | public static final int ACCEL_SEMICONDUCTOR_LTD = 0x004A; |
| | | public static final int CONTINENTAL_AUTOMOTIVE_SYSTEMS = 0x004B; |
| | | public static final int APPLE_INC = 0x004C; |
| | | public static final int STACCATO_COMMUNICATIONS_INC = 0x004D; |
| | | public static final int AVAGO_TECHNOLOGIES = 0x004E; |
| | | public static final int APT_LICENSING_LTD = 0x004F; |
| | | public static final int SIRF_TECHNOLOGY = 0x0050; |
| | | public static final int TZERO_TECHNOLOGIES_INC = 0x0051; |
| | | public static final int JM_CORPORATION = 0x0052; |
| | | public static final int FREE2MOVE_AB = 0x0053; |
| | | public static final int THREE_DIJOY_CORPORATION = 0x0054; |
| | | public static final int PLANTRONICS_INC = 0x0055; |
| | | public static final int SONY_ERICSSON_MOBILE_COMMUNICATIONS = 0x0056; |
| | | public static final int HARMAN_INTERNATIONAL_INDUSTRIES_INC = 0x0057; |
| | | public static final int VIZIO_INC = 0x0058; |
| | | public static final int NORDIC_SEMICONDUCTOR_ASA = 0x0059; |
| | | public static final int EM_MICROELECTRONICMARIN_SA = 0x005A; |
| | | public static final int RALINK_TECHNOLOGY_CORPORATION = 0x005B; |
| | | public static final int BELKIN_INTERNATIONAL_INC = 0x005C; |
| | | public static final int REALTEK_SEMICONDUCTOR_CORPORATION = 0x005D; |
| | | public static final int STONESTREET_ONE_LLC = 0x005E; |
| | | public static final int WICENTRIC_INC = 0x005F; |
| | | public static final int RIVIERAWAVES_SAS = 0x0060; |
| | | public static final int RDA_MICROELECTRONICS = 0x0061; |
| | | public static final int GIBSON_GUITARS = 0x0062; |
| | | public static final int MICOMMAND_INC = 0x0063; |
| | | public static final int BAND_XI_INTERNATIONAL_LLC = 0x0064; |
| | | public static final int HEWLETTPACKARD_COMPANY = 0x0065; |
| | | public static final int NINE_SOLUTIONS_OY = 0x0066; |
| | | public static final int GN_NETCOM_A_S = 0x0067; |
| | | public static final int GENERAL_MOTORS = 0x0068; |
| | | public static final int AD_ENGINEERING_INC = 0x0069; |
| | | public static final int MINDTREE_LTD = 0x006A; |
| | | public static final int POLAR_ELECTRO_OY = 0x006B; |
| | | public static final int BEAUTIFUL_ENTERPRISE_CO_LTD = 0x006C; |
| | | public static final int BRIARTEK_INC = 0x006D; |
| | | public static final int SUMMIT_DATA_COMMUNICATIONS_INC = 0x006E; |
| | | public static final int SOUND_ID = 0x006F; |
| | | public static final int MONSTER_LLC = 0x0070; |
| | | public static final int CONNECTBLUE_AB = 0x0071; |
| | | public static final int SHANGHAI_SUPER_SMART_ELECTRONICS_CO_LTD = 0x0072; |
| | | public static final int GROUP_SENSE_LTD = 0x0073; |
| | | public static final int ZOMM_LLC = 0x0074; |
| | | public static final int SAMSUNG_ELECTRONICS_CO_LTD = 0x0075; |
| | | public static final int CREATIVE_TECHNOLOGY_LTD = 0x0076; |
| | | public static final int LAIRD_TECHNOLOGIES = 0x0077; |
| | | public static final int NIKE_INC = 0x0078; |
| | | public static final int LESSWIRE_AG = 0x0079; |
| | | public static final int MSTAR_SEMICONDUCTOR_INC = 0x007A; |
| | | public static final int HANLYNN_TECHNOLOGIES = 0x007B; |
| | | public static final int A_R_CAMBRIDGE = 0x007C; |
| | | public static final int SEERS_TECHNOLOGY_CO_LTD = 0x007D; |
| | | public static final int SPORTS_TRACKING_TECHNOLOGIES_LTD = 0x007E; |
| | | public static final int AUTONET_MOBILE = 0x007F; |
| | | public static final int DELORME_PUBLISHING_COMPANY_INC = 0x0080; |
| | | public static final int WUXI_VIMICRO = 0x0081; |
| | | public static final int SENNHEISER_COMMUNICATIONS_A_S = 0x0082; |
| | | public static final int TIMEKEEPING_SYSTEMS_INC = 0x0083; |
| | | public static final int LUDUS_HELSINKI_LTD = 0x0084; |
| | | public static final int BLUERADIOS_INC = 0x0085; |
| | | public static final int EQUINOX_AG = 0x0086; |
| | | public static final int GARMIN_INTERNATIONAL_INC = 0x0087; |
| | | public static final int ECOTEST = 0x0088; |
| | | public static final int GN_RESOUND_A_S = 0x0089; |
| | | public static final int JAWBONE = 0x008A; |
| | | public static final int TOPCORN_POSITIONING_SYSTEMS_LLC = 0x008B; |
| | | public static final int QUALCOMM_RETAIL_SOLUTIONS_INC_FORMERLY_QUALCOMM_LABS_INC = 0x008C; |
| | | public static final int ZSCAN_SOFTWARE = 0x008D; |
| | | public static final int QUINTIC_CORP = 0x008E; |
| | | public static final int STOLLMAN_EV_GMBH = 0x008F; |
| | | public static final int FUNAI_ELECTRIC_CO_LTD = 0x0090; |
| | | public static final int ADVANCED_PANMOBIL_SYSTEMS_GMBH_CO_KG = 0x0091; |
| | | public static final int THINKOPTICS_INC = 0x0092; |
| | | public static final int UNIVERSAL_ELECTRONICS_INC = 0x0093; |
| | | public static final int AIROHA_TECHNOLOGY_CORP = 0x0094; |
| | | public static final int NEC_LIGHTING_LTD = 0x0095; |
| | | public static final int ODM_TECHNOLOGY_INC = 0x0096; |
| | | public static final int CONNECTEDEVICE_LTD = 0x0097; |
| | | public static final int ZER01TV_GMBH = 0x0098; |
| | | public static final int ITECH_DYNAMIC_GLOBAL_DISTRIBUTION_LTD = 0x0099; |
| | | public static final int ALPWISE = 0x009A; |
| | | public static final int JIANGSU_TOPPOWER_AUTOMOTIVE_ELECTRONICS_CO_LTD = 0x009B; |
| | | public static final int COLORFY_INC = 0x009C; |
| | | public static final int GEOFORCE_INC = 0x009D; |
| | | public static final int BOSE_CORPORATION = 0x009E; |
| | | public static final int SUUNTO_OY = 0x009F; |
| | | public static final int KENSINGTON_COMPUTER_PRODUCTS_GROUP = 0x00A0; |
| | | public static final int SRMEDIZINELEKTRONIK = 0x00A1; |
| | | public static final int VERTU_CORPORATION_LIMITED = 0x00A2; |
| | | public static final int META_WATCH_LTD = 0x00A3; |
| | | public static final int LINAK_A_S = 0x00A4; |
| | | public static final int OTL_DYNAMICS_LLC = 0x00A5; |
| | | public static final int PANDA_OCEAN_INC = 0x00A6; |
| | | public static final int VISTEON_CORPORATION = 0x00A7; |
| | | public static final int ARP_DEVICES_LIMITED = 0x00A8; |
| | | public static final int MAGNETI_MARELLI_SPA = 0x00A9; |
| | | public static final int CAEN_RFID_SRL = 0x00AA; |
| | | public static final int INGENIEURSYSTEMGRUPPE_ZAHN_GMBH = 0x00AB; |
| | | public static final int GREEN_THROTTLE_GAMES = 0x00AC; |
| | | public static final int PETER_SYSTEMTECHNIK_GMBH = 0x00AD; |
| | | public static final int OMEGAWAVE_OY = 0x00AE; |
| | | public static final int CINETIX = 0x00AF; |
| | | public static final int PASSIF_SEMICONDUCTOR_CORP = 0x00B0; |
| | | public static final int SARIS_CYCLING_GROUP_INC = 0x00B1; |
| | | public static final int BEKEY_A_S = 0x00B2; |
| | | public static final int CLARINOX_TECHNOLOGIES_PTY_LTD = 0x00B3; |
| | | public static final int BDE_TECHNOLOGY_CO_LTD = 0x00B4; |
| | | public static final int SWIRL_NETWORKS = 0x00B5; |
| | | public static final int MESO_INTERNATIONAL = 0x00B6; |
| | | public static final int TRELAB_LTD = 0x00B7; |
| | | public static final int QUALCOMM_INNOVATION_CENTER_INC_QUIC = 0x00B8; |
| | | public static final int JOHNSON_CONTROLS_INC = 0x00B9; |
| | | public static final int STARKEY_LABORATORIES_INC = 0x00BA; |
| | | public static final int SPOWER_ELECTRONICS_LIMITED = 0x00BB; |
| | | public static final int ACE_SENSOR_INC = 0x00BC; |
| | | public static final int APLIX_CORPORATION = 0x00BD; |
| | | public static final int AAMP_OF_AMERICA = 0x00BE; |
| | | public static final int STALMART_TECHNOLOGY_LIMITED = 0x00BF; |
| | | public static final int AMICCOM_ELECTRONICS_CORPORATION = 0x00C0; |
| | | public static final int SHENZHEN_EXCELSECU_DATA_TECHNOLOGY_COLTD = 0x00C1; |
| | | public static final int GENEQ_INC = 0x00C2; |
| | | public static final int ADIDAS_AG = 0x00C3; |
| | | public static final int LG_ELECTRONICS = 0x00C4; |
| | | public static final int ONSET_COMPUTER_CORPORATION = 0x00C5; |
| | | public static final int SELFLY_BV = 0x00C6; |
| | | public static final int QUUPPA_OY = 0x00C7; |
| | | public static final int GELO_INC = 0x00C8; |
| | | public static final int EVLUMA = 0x00C9; |
| | | public static final int MC10 = 0x00CA; |
| | | public static final int BINAURIC_SE = 0x00CB; |
| | | public static final int BEATS_ELECTRONICS = 0x00CC; |
| | | public static final int MICROCHIP_TECHNOLOGY_INC = 0x00CD; |
| | | public static final int ELGATO_SYSTEMS_GMBH = 0x00CE; |
| | | public static final int ARCHOS_SA = 0x00CF; |
| | | public static final int DEXCOM_INC = 0x00D0; |
| | | public static final int POLAR_ELECTRO_EUROPE_BV = 0x00D1; |
| | | public static final int DIALOG_SEMICONDUCTOR_BV = 0x00D2; |
| | | public static final int TAIXINGBANG_TECHNOLOGY_HK_CO_LTD = 0x00D3; |
| | | public static final int KAWANTECH = 0x00D4; |
| | | public static final int AUSTCO_COMMUNICATION_SYSTEMS = 0x00D5; |
| | | public static final int TIMEX_GROUP_USA_INC = 0x00D6; |
| | | public static final int QUALCOMM_TECHNOLOGIES_INC = 0x00D7; |
| | | public static final int QUALCOMM_CONNECTED_EXPERIENCES_INC = 0x00D8; |
| | | public static final int VOYETRA_TURTLE_BEACH = 0x00D9; |
| | | public static final int TXTR_GMBH = 0x00DA; |
| | | public static final int BIOSENTRONICS = 0x00DB; |
| | | public static final int PROCTER_GAMBLE = 0x00DC; |
| | | public static final int HOSIDEN_CORPORATION = 0x00DD; |
| | | public static final int MUZIK_LLC = 0x00DE; |
| | | public static final int MISFIT_WEARABLES_CORP = 0x00DF; |
| | | public static final int GOOGLE = 0x00E0; |
| | | public static final int DANLERS_LTD = 0x00E1; |
| | | public static final int SEMILINK_INC = 0x00E2; |
| | | public static final int INMUSIC_BRANDS_INC = 0x00E3; |
| | | public static final int LS_RESEARCH_INC = 0x00E4; |
| | | public static final int EDEN_SOFTWARE_CONSULTANTS_LTD = 0x00E5; |
| | | public static final int FRESHTEMP = 0x00E6; |
| | | public static final int KS_TECHNOLOGIES = 0x00E7; |
| | | public static final int ACTS_TECHNOLOGIES = 0x00E8; |
| | | public static final int VTRACK_SYSTEMS = 0x00E9; |
| | | public static final int NIELSENKELLERMAN_COMPANY = 0x00EA; |
| | | public static final int SERVER_TECHNOLOGY_INC = 0x00EB; |
| | | public static final int BIORESEARCH_ASSOCIATES = 0x00EC; |
| | | public static final int JOLLY_LOGIC_LLC = 0x00ED; |
| | | public static final int ABOVE_AVERAGE_OUTCOMES_INC = 0x00EE; |
| | | public static final int BITSPLITTERS_GMBH = 0x00EF; |
| | | public static final int PAYPAL_INC = 0x00F0; |
| | | public static final int WITRON_TECHNOLOGY_LIMITED = 0x00F1; |
| | | public static final int MORSE_PROJECT_INC = 0x00F2; |
| | | public static final int KENT_DISPLAYS_INC = 0x00F3; |
| | | public static final int NAUTILUS_INC = 0x00F4; |
| | | public static final int SMARTIFIER_OY = 0x00F5; |
| | | public static final int ELCOMETER_LIMITED = 0x00F6; |
| | | public static final int VSN_TECHNOLOGIES_INC = 0x00F7; |
| | | public static final int ACEUNI_CORP_LTD = 0x00F8; |
| | | public static final int STICKNFIND = 0x00F9; |
| | | public static final int CRYSTAL_CODE_AB = 0x00FA; |
| | | public static final int KOUKAAM_AS = 0x00FB; |
| | | public static final int DELPHI_CORPORATION = 0x00FC; |
| | | public static final int VALENCETECH_LIMITED = 0x00FD; |
| | | public static final int RESERVED = 0x00FE; |
| | | public static final int TYPO_PRODUCTS_LLC = 0x00FF; |
| | | public static final int TOMTOM_INTERNATIONAL_BV = 0x0100; |
| | | public static final int FUGOO_INC = 0x0101; |
| | | public static final int KEISER_CORPORATION = 0x0102; |
| | | public static final int BANG_OLUFSEN_A_S = 0x0103; |
| | | public static final int PLUS_LOCATIONS_SYSTEMS_PTY_LTD = 0x0104; |
| | | public static final int UBIQUITOUS_COMPUTING_TECHNOLOGY_CORPORATION = 0x0105; |
| | | public static final int INNOVATIVE_YACHTTER_SOLUTIONS = 0x0106; |
| | | public static final int WILLIAM_DEMANT_HOLDING_A_S = 0x0107; |
| | | public static final int CHICONY_ELECTRONICS_CO_LTD = 0x0108; |
| | | public static final int ATUS_BV = 0x0109; |
| | | public static final int CODEGATE_LTD = 0x010A; |
| | | public static final int ERI_INC = 0x010B; |
| | | public static final int TRANSDUCERS_DIRECT_LLC = 0x010C; |
| | | public static final int FUJITSU_TEN_LIMITED = 0x010D; |
| | | public static final int AUDI_AG = 0x010E; |
| | | public static final int HISILICON_TECHNOLOGIES_CO_LTD = 0x010F; |
| | | public static final int NIPPON_SEIKI_CO_LTD = 0x0110; |
| | | public static final int STEELSERIES_APS = 0x0111; |
| | | public static final int VYZYBL_INC = 0x0112; |
| | | public static final int OPENBRAIN_TECHNOLOGIES_CO_LTD = 0x0113; |
| | | public static final int XENSR = 0x0114; |
| | | public static final int ESOLUTIONS = 0x0115; |
| | | public static final int ONE_OAK_TECHNOLOGIES = 0x0116; |
| | | public static final int WIMOTO_TECHNOLOGIES_INC = 0x0117; |
| | | public static final int RADIUS_NETWORKS_INC = 0x0118; |
| | | public static final int WIZE_TECHNOLOGY_CO_LTD = 0x0119; |
| | | public static final int QUALCOMM_LABS_INC = 0x011A; |
| | | public static final int ARUBA_NETWORKS = 0x011B; |
| | | public static final int BAIDU = 0x011C; |
| | | public static final int ARENDI_AG = 0x011D; |
| | | public static final int SKODA_AUTO_AS = 0x011E; |
| | | public static final int VOLKSWAGON_AG = 0x011F; |
| | | public static final int PORSCHE_AG = 0x0120; |
| | | public static final int SINO_WEALTH_ELECTRONIC_LTD = 0x0121; |
| | | public static final int AIRTURN_INC = 0x0122; |
| | | public static final int KINSA_INC = 0x0123; |
| | | public static final int HID_GLOBAL = 0x0124; |
| | | public static final int SEAT_ES = 0x0125; |
| | | public static final int PROMETHEAN_LTD = 0x0126; |
| | | public static final int SALUTICA_ALLIED_SOLUTIONS = 0x0127; |
| | | public static final int GPSI_GROUP_PTY_LTD = 0x0128; |
| | | public static final int NIMBLE_DEVICES_OY = 0x0129; |
| | | public static final int CHANGZHOU_YONGSE_INFOTECH_CO_LTD = 0x012A; |
| | | public static final int SPORTIQ = 0x012B; |
| | | public static final int TEMEC_INSTRUMENTS_BV = 0x012C; |
| | | public static final int SONY_CORPORATION = 0x012D; |
| | | public static final int ASSA_ABLOY = 0x012E; |
| | | public static final int CLARION_CO_LTD = 0x012F; |
| | | public static final int WAREHOUSE_INNOVATIONS = 0x0130; |
| | | public static final int CYPRESS_SEMICONDUCTOR_CORPORATION = 0x0131; |
| | | public static final int MADS_INC = 0x0132; |
| | | public static final int BLUE_MAESTRO_LIMITED = 0x0133; |
| | | public static final int RESOLUTION_PRODUCTS_INC = 0x0134; |
| | | public static final int AIREWEAR_LLC = 0x0135; |
| | | public static final int ETC_SP_ZOO = 0x0136; |
| | | public static final int PRESTIGIO_PLAZA_LTD = 0x0137; |
| | | |
| | | private static final SparseArray<String> COMPANY_NAME_MAP = populateCompanyNameMap(); |
| | | |
| | | public static String getCompanyName(final int companyId, final String fallback) { |
| | | final String name = COMPANY_NAME_MAP.get(companyId); |
| | | return name == null ? fallback : name; |
| | | } |
| | | |
| | | private static SparseArray<String> populateCompanyNameMap() { |
| | | final SparseArray<String> map = new SparseArray<>(); |
| | | |
| | | map.put(ERICSSON_TECHNOLOGY_LICENSING, "Ericsson Technology Licensing"); |
| | | map.put(NOKIA_MOBILE_PHONES, "Nokia Mobile Phones"); |
| | | map.put(INTEL_CORP, "Intel Corp."); |
| | | map.put(IBM_CORP, "IBM Corp."); |
| | | map.put(TOSHIBA_CORP, "Toshiba Corp."); |
| | | map.put(THREE_COM, "3Com"); |
| | | map.put(MICROSOFT, "Microsoft"); |
| | | map.put(LUCENT, "Lucent"); |
| | | map.put(MOTOROLA, "Motorola"); |
| | | map.put(INFINEON_TECHNOLOGIES_AG, "Infineon Technologies AG"); |
| | | map.put(CAMBRIDGE_SILICON_RADIO, "Cambridge Silicon Radio"); |
| | | map.put(SILICON_WAVE, "Silicon Wave"); |
| | | map.put(DIGIANSWER_A_S, "Digianswer A/S"); |
| | | map.put(TEXAS_INSTRUMENTS_INC, "Texas Instruments Inc."); |
| | | map.put(CEVA_INC_FORMERLY_PARTHUS_TECHNOLOGIES_INC, "Ceva, Inc. (formerly Parthus Technologies, Inc.)"); |
| | | map.put(BROADCOM_CORPORATION, "Broadcom Corporation"); |
| | | map.put(MITEL_SEMICONDUCTOR, "Mitel Semiconductor"); |
| | | map.put(WIDCOMM_INC, "Widcomm, Inc"); |
| | | map.put(ZEEVO_INC, "Zeevo, Inc."); |
| | | map.put(ATMEL_CORPORATION, "Atmel Corporation"); |
| | | map.put(MITSUBISHI_ELECTRIC_CORPORATION, "Mitsubishi Electric Corporation"); |
| | | map.put(RTX_TELECOM_A_S, "RTX Telecom A/S"); |
| | | map.put(KC_TECHNOLOGY_INC, "KC Technology Inc."); |
| | | map.put(NEWLOGIC, "NewLogic"); |
| | | map.put(TRANSILICA_INC, "Transilica, Inc."); |
| | | map.put(ROHDE_SCHWARZ_GMBH_CO_KG, "Rohde & Schwarz GmbH & Co. KG"); |
| | | map.put(TTPCOM_LIMITED, "TTPCom Limited"); |
| | | map.put(SIGNIA_TECHNOLOGIES_INC, "Signia Technologies, Inc."); |
| | | map.put(CONEXANT_SYSTEMS_INC, "Conexant Systems Inc."); |
| | | map.put(QUALCOMM, "Qualcomm"); |
| | | map.put(INVENTEL, "Inventel"); |
| | | map.put(AVM_BERLIN, "AVM Berlin"); |
| | | map.put(BANDSPEED_INC, "BandSpeed, Inc."); |
| | | map.put(MANSELLA_LTD, "Mansella Ltd"); |
| | | map.put(NEC_CORPORATION, "NEC Corporation"); |
| | | map.put(WAVEPLUS_TECHNOLOGY_CO_LTD, "WavePlus Technology Co., Ltd."); |
| | | map.put(ALCATEL, "Alcatel"); |
| | | map.put(PHILIPS_SEMICONDUCTORS, "Philips Semiconductors"); |
| | | map.put(C_TECHNOLOGIES, "C Technologies"); |
| | | map.put(OPEN_INTERFACE, "Open Interface"); |
| | | map.put(R_F_MICRO_DEVICES, "R F Micro Devices"); |
| | | map.put(HITACHI_LTD, "Hitachi Ltd"); |
| | | map.put(SYMBOL_TECHNOLOGIES_INC, "Symbol Technologies, Inc."); |
| | | map.put(TENOVIS, "Tenovis"); |
| | | map.put(MACRONIX_INTERNATIONAL_CO_LTD, "Macronix International Co. Ltd."); |
| | | map.put(GCT_SEMICONDUCTOR, "GCT Semiconductor"); |
| | | map.put(NORWOOD_SYSTEMS, "Norwood Systems"); |
| | | map.put(MEWTEL_TECHNOLOGY_INC, "MewTel Technology Inc."); |
| | | map.put(ST_MICROELECTRONICS, "ST Microelectronics"); |
| | | map.put(SYNOPSIS, "Synopsis"); |
| | | map.put(REDM_COMMUNICATIONS_LTD, "Red-M (Communications) Ltd"); |
| | | map.put(COMMIL_LTD, "Commil Ltd"); |
| | | map.put(COMPUTER_ACCESS_TECHNOLOGY_CORPORATION_CATC, "Computer Access Technology Corporation (CATC)"); |
| | | map.put(ECLIPSE_HQ_ESPANA_SL, "Eclipse (HQ Espana) S.L."); |
| | | map.put(RENESAS_TECHNOLOGY_CORP, "Renesas Technology Corp."); |
| | | map.put(MOBILIAN_CORPORATION, "Mobilian Corporation"); |
| | | map.put(TERAX, "Terax"); |
| | | map.put(INTEGRATED_SYSTEM_SOLUTION_CORP, "Integrated System Solution Corp."); |
| | | map.put(MATSUSHITA_ELECTRIC_INDUSTRIAL_CO_LTD, "Matsushita Electric Industrial Co., Ltd."); |
| | | map.put(GENNUM_CORPORATION, "Gennum Corporation"); |
| | | map.put(RESEARCH_IN_MOTION, "Research In Motion"); |
| | | map.put(IPEXTREME_INC, "IPextreme, Inc."); |
| | | map.put(SYSTEMS_AND_CHIPS_INC, "Systems and Chips, Inc."); |
| | | map.put(BLUETOOTH_SIG_INC, "Bluetooth SIG, Inc."); |
| | | map.put(SEIKO_EPSON_CORPORATION, "Seiko Epson Corporation"); |
| | | map.put(INTEGRATED_SILICON_SOLUTION_TAIWAN_INC, "Integrated Silicon Solution Taiwan, Inc."); |
| | | map.put(CONWISE_TECHNOLOGY_CORPORATION_LTD, "CONWISE Technology Corporation Ltd"); |
| | | map.put(PARROT_SA, "PARROT SA"); |
| | | map.put(SOCKET_MOBILE, "Socket Mobile"); |
| | | map.put(ATHEROS_COMMUNICATIONS_INC, "Atheros Communications, Inc."); |
| | | map.put(MEDIATEK_INC, "MediaTek, Inc."); |
| | | map.put(BLUEGIGA, "Bluegiga"); |
| | | map.put(MARVELL_TECHNOLOGY_GROUP_LTD, "Marvell Technology Group Ltd."); |
| | | map.put(THREE_DSP_CORPORATION, "3DSP Corporation"); |
| | | map.put(ACCEL_SEMICONDUCTOR_LTD, "Accel Semiconductor Ltd."); |
| | | map.put(CONTINENTAL_AUTOMOTIVE_SYSTEMS, "Continental Automotive Systems"); |
| | | map.put(APPLE_INC, "Apple, Inc."); |
| | | map.put(STACCATO_COMMUNICATIONS_INC, "Staccato Communications, Inc."); |
| | | map.put(AVAGO_TECHNOLOGIES, "Avago Technologies"); |
| | | map.put(APT_LICENSING_LTD, "APT Licensing Ltd."); |
| | | map.put(SIRF_TECHNOLOGY, "SiRF Technology"); |
| | | map.put(TZERO_TECHNOLOGIES_INC, "Tzero Technologies, Inc."); |
| | | map.put(JM_CORPORATION, "J&M Corporation"); |
| | | map.put(FREE2MOVE_AB, "Free2move AB"); |
| | | map.put(THREE_DIJOY_CORPORATION, "3DiJoy Corporation"); |
| | | map.put(PLANTRONICS_INC, "Plantronics, Inc."); |
| | | map.put(SONY_ERICSSON_MOBILE_COMMUNICATIONS, "Sony Ericsson Mobile Communications"); |
| | | map.put(HARMAN_INTERNATIONAL_INDUSTRIES_INC, "Harman International Industries, Inc."); |
| | | map.put(VIZIO_INC, "Vizio, Inc."); |
| | | map.put(NORDIC_SEMICONDUCTOR_ASA, "Nordic Semiconductor ASA"); |
| | | map.put(EM_MICROELECTRONICMARIN_SA, "EM Microelectronic-Marin SA"); |
| | | map.put(RALINK_TECHNOLOGY_CORPORATION, "Ralink Technology Corporation"); |
| | | map.put(BELKIN_INTERNATIONAL_INC, "Belkin International, Inc."); |
| | | map.put(REALTEK_SEMICONDUCTOR_CORPORATION, "Realtek Semiconductor Corporation"); |
| | | map.put(STONESTREET_ONE_LLC, "Stonestreet One, LLC"); |
| | | map.put(WICENTRIC_INC, "Wicentric, Inc."); |
| | | map.put(RIVIERAWAVES_SAS, "RivieraWaves S.A.S"); |
| | | map.put(RDA_MICROELECTRONICS, "RDA Microelectronics"); |
| | | map.put(GIBSON_GUITARS, "Gibson Guitars"); |
| | | map.put(MICOMMAND_INC, "MiCommand Inc."); |
| | | map.put(BAND_XI_INTERNATIONAL_LLC, "Band XI International, LLC"); |
| | | map.put(HEWLETTPACKARD_COMPANY, "Hewlett-Packard Company"); |
| | | map.put(NINE_SOLUTIONS_OY, "9Solutions Oy"); |
| | | map.put(GN_NETCOM_A_S, "GN Netcom A/S"); |
| | | map.put(GENERAL_MOTORS, "General Motors"); |
| | | map.put(AD_ENGINEERING_INC, "A&D Engineering, Inc."); |
| | | map.put(MINDTREE_LTD, "MindTree Ltd."); |
| | | map.put(POLAR_ELECTRO_OY, "Polar Electro OY"); |
| | | map.put(BEAUTIFUL_ENTERPRISE_CO_LTD, "Beautiful Enterprise Co., Ltd."); |
| | | map.put(BRIARTEK_INC, "BriarTek, Inc."); |
| | | map.put(SUMMIT_DATA_COMMUNICATIONS_INC, "Summit Data Communications, Inc."); |
| | | map.put(SOUND_ID, "Sound ID"); |
| | | map.put(MONSTER_LLC, "Monster, LLC"); |
| | | map.put(CONNECTBLUE_AB, "connectBlue AB"); |
| | | map.put(SHANGHAI_SUPER_SMART_ELECTRONICS_CO_LTD, "ShangHai Super Smart Electronics Co. Ltd."); |
| | | map.put(GROUP_SENSE_LTD, "Group Sense Ltd."); |
| | | map.put(ZOMM_LLC, "Zomm, LLC"); |
| | | map.put(SAMSUNG_ELECTRONICS_CO_LTD, "Samsung Electronics Co. Ltd."); |
| | | map.put(CREATIVE_TECHNOLOGY_LTD, "Creative Technology Ltd."); |
| | | map.put(LAIRD_TECHNOLOGIES, "Laird Technologies"); |
| | | map.put(NIKE_INC, "Nike, Inc."); |
| | | map.put(LESSWIRE_AG, "lesswire AG"); |
| | | map.put(MSTAR_SEMICONDUCTOR_INC, "MStar Semiconductor, Inc."); |
| | | map.put(HANLYNN_TECHNOLOGIES, "Hanlynn Technologies"); |
| | | map.put(A_R_CAMBRIDGE, "A & R Cambridge"); |
| | | map.put(SEERS_TECHNOLOGY_CO_LTD, "Seers Technology Co. Ltd"); |
| | | map.put(SPORTS_TRACKING_TECHNOLOGIES_LTD, "Sports Tracking Technologies Ltd."); |
| | | map.put(AUTONET_MOBILE, "Autonet Mobile"); |
| | | map.put(DELORME_PUBLISHING_COMPANY_INC, "DeLorme Publishing Company, Inc."); |
| | | map.put(WUXI_VIMICRO, "WuXi Vimicro"); |
| | | map.put(SENNHEISER_COMMUNICATIONS_A_S, "Sennheiser Communications A/S"); |
| | | map.put(TIMEKEEPING_SYSTEMS_INC, "TimeKeeping Systems, Inc."); |
| | | map.put(LUDUS_HELSINKI_LTD, "Ludus Helsinki Ltd."); |
| | | map.put(BLUERADIOS_INC, "BlueRadios, Inc."); |
| | | map.put(EQUINOX_AG, "equinox AG"); |
| | | map.put(GARMIN_INTERNATIONAL_INC, "Garmin International, Inc."); |
| | | map.put(ECOTEST, "Ecotest"); |
| | | map.put(GN_RESOUND_A_S, "GN ReSound A/S"); |
| | | map.put(JAWBONE, "Jawbone"); |
| | | map.put(TOPCORN_POSITIONING_SYSTEMS_LLC, "Topcorn Positioning Systems, LLC"); |
| | | map.put(QUALCOMM_RETAIL_SOLUTIONS_INC_FORMERLY_QUALCOMM_LABS_INC, "Qualcomm Retail Solutions, Inc. (formerly Qualcomm Labs, Inc.)"); |
| | | map.put(ZSCAN_SOFTWARE, "Zscan Software"); |
| | | map.put(QUINTIC_CORP, "Quintic Corp."); |
| | | map.put(STOLLMAN_EV_GMBH, "Stollman E+V GmbH"); |
| | | map.put(FUNAI_ELECTRIC_CO_LTD, "Funai Electric Co., Ltd."); |
| | | map.put(ADVANCED_PANMOBIL_SYSTEMS_GMBH_CO_KG, "Advanced PANMOBIL Systems GmbH & Co. KG"); |
| | | map.put(THINKOPTICS_INC, "ThinkOptics, Inc."); |
| | | map.put(UNIVERSAL_ELECTRONICS_INC, "Universal Electronics, Inc."); |
| | | map.put(AIROHA_TECHNOLOGY_CORP, "Airoha Technology Corp."); |
| | | map.put(NEC_LIGHTING_LTD, "NEC Lighting, Ltd."); |
| | | map.put(ODM_TECHNOLOGY_INC, "ODM Technology, Inc."); |
| | | map.put(CONNECTEDEVICE_LTD, "ConnecteDevice Ltd."); |
| | | map.put(ZER01TV_GMBH, "zer01.tv GmbH"); |
| | | map.put(ITECH_DYNAMIC_GLOBAL_DISTRIBUTION_LTD, "i.Tech Dynamic Global Distribution Ltd."); |
| | | map.put(ALPWISE, "Alpwise"); |
| | | map.put(JIANGSU_TOPPOWER_AUTOMOTIVE_ELECTRONICS_CO_LTD, "Jiangsu Toppower Automotive Electronics Co., Ltd."); |
| | | map.put(COLORFY_INC, "Colorfy, Inc."); |
| | | map.put(GEOFORCE_INC, "Geoforce Inc."); |
| | | map.put(BOSE_CORPORATION, "Bose Corporation"); |
| | | map.put(SUUNTO_OY, "Suunto Oy"); |
| | | map.put(KENSINGTON_COMPUTER_PRODUCTS_GROUP, "Kensington Computer Products Group"); |
| | | map.put(SRMEDIZINELEKTRONIK, "SR-Medizinelektronik"); |
| | | map.put(VERTU_CORPORATION_LIMITED, "Vertu Corporation Limited"); |
| | | map.put(META_WATCH_LTD, "Meta Watch Ltd."); |
| | | map.put(LINAK_A_S, "LINAK A/S"); |
| | | map.put(OTL_DYNAMICS_LLC, "OTL Dynamics LLC"); |
| | | map.put(PANDA_OCEAN_INC, "Panda Ocean Inc."); |
| | | map.put(VISTEON_CORPORATION, "Visteon Corporation"); |
| | | map.put(ARP_DEVICES_LIMITED, "ARP Devices Limited"); |
| | | map.put(MAGNETI_MARELLI_SPA, "Magneti Marelli S.p.A"); |
| | | map.put(CAEN_RFID_SRL, "CAEN RFID srl"); |
| | | map.put(INGENIEURSYSTEMGRUPPE_ZAHN_GMBH, "Ingenieur-Systemgruppe Zahn GmbH"); |
| | | map.put(GREEN_THROTTLE_GAMES, "Green Throttle Games"); |
| | | map.put(PETER_SYSTEMTECHNIK_GMBH, "Peter Systemtechnik GmbH"); |
| | | map.put(OMEGAWAVE_OY, "Omegawave Oy"); |
| | | map.put(CINETIX, "Cinetix"); |
| | | map.put(PASSIF_SEMICONDUCTOR_CORP, "Passif Semiconductor Corp"); |
| | | map.put(SARIS_CYCLING_GROUP_INC, "Saris Cycling Group, Inc"); |
| | | map.put(BEKEY_A_S, "Bekey A/S"); |
| | | map.put(CLARINOX_TECHNOLOGIES_PTY_LTD, "Clarinox Technologies Pty. Ltd."); |
| | | map.put(BDE_TECHNOLOGY_CO_LTD, "BDE Technology Co., Ltd."); |
| | | map.put(SWIRL_NETWORKS, "Swirl Networks"); |
| | | map.put(MESO_INTERNATIONAL, "Meso international"); |
| | | map.put(TRELAB_LTD, "TreLab Ltd"); |
| | | map.put(QUALCOMM_INNOVATION_CENTER_INC_QUIC, "Qualcomm Innovation Center, Inc. (QuIC)"); |
| | | map.put(JOHNSON_CONTROLS_INC, "Johnson Controls, Inc."); |
| | | map.put(STARKEY_LABORATORIES_INC, "Starkey Laboratories Inc."); |
| | | map.put(SPOWER_ELECTRONICS_LIMITED, "S-Power Electronics Limited"); |
| | | map.put(ACE_SENSOR_INC, "Ace Sensor Inc"); |
| | | map.put(APLIX_CORPORATION, "Aplix Corporation"); |
| | | map.put(AAMP_OF_AMERICA, "AAMP of America"); |
| | | map.put(STALMART_TECHNOLOGY_LIMITED, "Stalmart Technology Limited"); |
| | | map.put(AMICCOM_ELECTRONICS_CORPORATION, "AMICCOM Electronics Corporation"); |
| | | map.put(SHENZHEN_EXCELSECU_DATA_TECHNOLOGY_COLTD, "Shenzhen Excelsecu Data Technology Co.,Ltd"); |
| | | map.put(GENEQ_INC, "Geneq Inc."); |
| | | map.put(ADIDAS_AG, "adidas AG"); |
| | | map.put(LG_ELECTRONICS, "LG Electronics"); |
| | | map.put(ONSET_COMPUTER_CORPORATION, "Onset Computer Corporation"); |
| | | map.put(SELFLY_BV, "Selfly BV"); |
| | | map.put(QUUPPA_OY, "Quuppa Oy."); |
| | | map.put(GELO_INC, "GeLo Inc"); |
| | | map.put(EVLUMA, "Evluma"); |
| | | map.put(MC10, "MC10"); |
| | | map.put(BINAURIC_SE, "Binauric SE"); |
| | | map.put(BEATS_ELECTRONICS, "Beats Electronics"); |
| | | map.put(MICROCHIP_TECHNOLOGY_INC, "Microchip Technology Inc."); |
| | | map.put(ELGATO_SYSTEMS_GMBH, "Elgato Systems GmbH"); |
| | | map.put(ARCHOS_SA, "ARCHOS SA"); |
| | | map.put(DEXCOM_INC, "Dexcom, Inc."); |
| | | map.put(POLAR_ELECTRO_EUROPE_BV, "Polar Electro Europe B.V."); |
| | | map.put(DIALOG_SEMICONDUCTOR_BV, "Dialog Semiconductor B.V."); |
| | | map.put(TAIXINGBANG_TECHNOLOGY_HK_CO_LTD, "Taixingbang Technology (HK) Co,. LTD."); |
| | | map.put(KAWANTECH, "Kawantech"); |
| | | map.put(AUSTCO_COMMUNICATION_SYSTEMS, "Austco Communication Systems"); |
| | | map.put(TIMEX_GROUP_USA_INC, "Timex Group USA, Inc."); |
| | | map.put(QUALCOMM_TECHNOLOGIES_INC, "Qualcomm Technologies, Inc."); |
| | | map.put(QUALCOMM_CONNECTED_EXPERIENCES_INC, "Qualcomm Connected Experiences, Inc."); |
| | | map.put(VOYETRA_TURTLE_BEACH, "Voyetra Turtle Beach"); |
| | | map.put(TXTR_GMBH, "txtr GmbH"); |
| | | map.put(BIOSENTRONICS, "Biosentronics"); |
| | | map.put(PROCTER_GAMBLE, "Procter & Gamble"); |
| | | map.put(HOSIDEN_CORPORATION, "Hosiden Corporation"); |
| | | map.put(MUZIK_LLC, "Muzik LLC"); |
| | | map.put(MISFIT_WEARABLES_CORP, "Misfit Wearables Corp"); |
| | | map.put(GOOGLE, "Google"); |
| | | map.put(DANLERS_LTD, "Danlers Ltd"); |
| | | map.put(SEMILINK_INC, "Semilink Inc"); |
| | | map.put(INMUSIC_BRANDS_INC, "inMusic Brands, Inc"); |
| | | map.put(LS_RESEARCH_INC, "L.S. Research Inc."); |
| | | map.put(EDEN_SOFTWARE_CONSULTANTS_LTD, "Eden Software Consultants Ltd."); |
| | | map.put(FRESHTEMP, "Freshtemp"); |
| | | map.put(KS_TECHNOLOGIES, "KS Technologies"); |
| | | map.put(ACTS_TECHNOLOGIES, "ACTS Technologies"); |
| | | map.put(VTRACK_SYSTEMS, "Vtrack Systems"); |
| | | map.put(NIELSENKELLERMAN_COMPANY, "Nielsen-Kellerman Company"); |
| | | map.put(SERVER_TECHNOLOGY_INC, "Server Technology, Inc."); |
| | | map.put(BIORESEARCH_ASSOCIATES, "BioResearch Associates"); |
| | | map.put(JOLLY_LOGIC_LLC, "Jolly Logic, LLC"); |
| | | map.put(ABOVE_AVERAGE_OUTCOMES_INC, "Above Average Outcomes, Inc."); |
| | | map.put(BITSPLITTERS_GMBH, "Bitsplitters GmbH"); |
| | | map.put(PAYPAL_INC, "PayPal, Inc."); |
| | | map.put(WITRON_TECHNOLOGY_LIMITED, "Witron Technology Limited"); |
| | | map.put(MORSE_PROJECT_INC, "Morse Project Inc."); |
| | | map.put(KENT_DISPLAYS_INC, "Kent Displays Inc."); |
| | | map.put(NAUTILUS_INC, "Nautilus Inc."); |
| | | map.put(SMARTIFIER_OY, "Smartifier Oy"); |
| | | map.put(ELCOMETER_LIMITED, "Elcometer Limited"); |
| | | map.put(VSN_TECHNOLOGIES_INC, "VSN Technologies Inc."); |
| | | map.put(ACEUNI_CORP_LTD, "AceUni Corp., Ltd."); |
| | | map.put(STICKNFIND, "StickNFind"); |
| | | map.put(CRYSTAL_CODE_AB, "Crystal Code AB"); |
| | | map.put(KOUKAAM_AS, "KOUKAAM a.s."); |
| | | map.put(DELPHI_CORPORATION, "Delphi Corporation"); |
| | | map.put(VALENCETECH_LIMITED, "ValenceTech Limited"); |
| | | map.put(RESERVED, "Reserved"); |
| | | map.put(TYPO_PRODUCTS_LLC, "Typo Products, LLC"); |
| | | map.put(TOMTOM_INTERNATIONAL_BV, "TomTom International BV"); |
| | | map.put(FUGOO_INC, "Fugoo, Inc"); |
| | | map.put(KEISER_CORPORATION, "Keiser Corporation"); |
| | | map.put(BANG_OLUFSEN_A_S, "Bang & Olufsen A/S"); |
| | | map.put(PLUS_LOCATIONS_SYSTEMS_PTY_LTD, "PLUS Locations Systems Pty Ltd"); |
| | | map.put(UBIQUITOUS_COMPUTING_TECHNOLOGY_CORPORATION, "Ubiquitous Computing Technology Corporation"); |
| | | map.put(INNOVATIVE_YACHTTER_SOLUTIONS, "Innovative Yachtter Solutions"); |
| | | map.put(WILLIAM_DEMANT_HOLDING_A_S, "William Demant Holding A/S"); |
| | | map.put(CHICONY_ELECTRONICS_CO_LTD, "Chicony Electronics Co., Ltd."); |
| | | map.put(ATUS_BV, "Atus BV"); |
| | | map.put(CODEGATE_LTD, "Codegate Ltd."); |
| | | map.put(ERI_INC, "ERi, Inc."); |
| | | map.put(TRANSDUCERS_DIRECT_LLC, "Transducers Direct, LLC"); |
| | | map.put(FUJITSU_TEN_LIMITED, "Fujitsu Ten Limited"); |
| | | map.put(AUDI_AG, "Audi AG"); |
| | | map.put(HISILICON_TECHNOLOGIES_CO_LTD, "HiSilicon Technologies Co., Ltd."); |
| | | map.put(NIPPON_SEIKI_CO_LTD, "Nippon Seiki Co., Ltd."); |
| | | map.put(STEELSERIES_APS, "Steelseries ApS"); |
| | | map.put(VYZYBL_INC, "vyzybl Inc."); |
| | | map.put(OPENBRAIN_TECHNOLOGIES_CO_LTD, "Openbrain Technologies, Co., Ltd."); |
| | | map.put(XENSR, "Xensr"); |
| | | map.put(ESOLUTIONS, "e.solutions"); |
| | | map.put(ONE_OAK_TECHNOLOGIES, "1OAK Technologies"); |
| | | map.put(WIMOTO_TECHNOLOGIES_INC, "Wimoto Technologies Inc"); |
| | | map.put(RADIUS_NETWORKS_INC, "Radius Networks, Inc."); |
| | | map.put(WIZE_TECHNOLOGY_CO_LTD, "Wize Technology Co., Ltd."); |
| | | map.put(QUALCOMM_LABS_INC, "Qualcomm Labs, Inc."); |
| | | map.put(ARUBA_NETWORKS, "Aruba Networks"); |
| | | map.put(BAIDU, "Baidu"); |
| | | map.put(ARENDI_AG, "Arendi AG"); |
| | | map.put(SKODA_AUTO_AS, "Skoda Auto a.s."); |
| | | map.put(VOLKSWAGON_AG, "Volkswagon AG"); |
| | | map.put(PORSCHE_AG, "Porsche AG"); |
| | | map.put(SINO_WEALTH_ELECTRONIC_LTD, "Sino Wealth Electronic Ltd."); |
| | | map.put(AIRTURN_INC, "AirTurn, Inc."); |
| | | map.put(KINSA_INC, "Kinsa, Inc."); |
| | | map.put(HID_GLOBAL, "HID Global"); |
| | | map.put(SEAT_ES, "SEAT es"); |
| | | map.put(PROMETHEAN_LTD, "Promethean Ltd."); |
| | | map.put(SALUTICA_ALLIED_SOLUTIONS, "Salutica Allied Solutions"); |
| | | map.put(GPSI_GROUP_PTY_LTD, "GPSI Group Pty Ltd"); |
| | | map.put(NIMBLE_DEVICES_OY, "Nimble Devices Oy"); |
| | | map.put(CHANGZHOU_YONGSE_INFOTECH_CO_LTD, "Changzhou Yongse Infotech Co., Ltd"); |
| | | map.put(SPORTIQ, "SportIQ"); |
| | | map.put(TEMEC_INSTRUMENTS_BV, "TEMEC Instruments B.V."); |
| | | map.put(SONY_CORPORATION, "Sony Corporation"); |
| | | map.put(ASSA_ABLOY, "ASSA ABLOY"); |
| | | map.put(CLARION_CO_LTD, "Clarion Co., Ltd."); |
| | | map.put(WAREHOUSE_INNOVATIONS, "Warehouse Innovations"); |
| | | map.put(CYPRESS_SEMICONDUCTOR_CORPORATION, "Cypress Semiconductor Corporation"); |
| | | map.put(MADS_INC, "MADS Inc"); |
| | | map.put(BLUE_MAESTRO_LIMITED, "Blue Maestro Limited"); |
| | | map.put(RESOLUTION_PRODUCTS_INC, "Resolution Products, Inc."); |
| | | map.put(AIREWEAR_LLC, "Airewear LLC"); |
| | | map.put(ETC_SP_ZOO, "ETC sp. z.o.o."); |
| | | map.put(PRESTIGIO_PLAZA_LTD, "Prestigio Plaza Ltd."); |
| | | |
| | | return map; |
| | | } |
| | | |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.device.resolvers; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * The UUIDS have been collected from the following sources: |
| | | * <p> |
| | | * - http://developer.nokia.com/community/wiki/Bluetooth_Services_for_Windows_Phone |
| | | * - The Bluez project |
| | | * |
| | | * @author Alexandros Schillings |
| | | */ |
| | | public class GattAttributeResolver { |
| | | public static final String BASE_GUID = "00000000-0000-1000-8000-00805f9b34fb"; |
| | | public static final String SERVICE_DISCOVERY_PROTOCOL_SDP = "00000001-0000-1000-8000-00805f9b34fb"; |
| | | public static final String USER_DATAGRAM_PROTOCOL_UDP = "00000002-0000-1000-8000-00805f9b34fb"; |
| | | public static final String RADIO_FREQUENCY_COMMUNICATION_PROTOCOL_RFCOMM = "00000003-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TCP = "00000004-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TCSBIN = "00000005-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TCSAT = "00000006-0000-1000-8000-00805f9b34fb"; |
| | | public static final String OBJECT_EXCHANGE_PROTOCOL_OBEX = "00000008-0000-1000-8000-00805f9b34fb"; |
| | | public static final String IP = "00000009-0000-1000-8000-00805f9b34fb"; |
| | | public static final String FTP = "0000000a-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HTTP = "0000000c-0000-1000-8000-00805f9b34fb"; |
| | | public static final String WSP = "0000000e-0000-1000-8000-00805f9b34fb"; |
| | | public static final String BNEP_SVC = "0000000f-0000-1000-8000-00805f9b34fb"; |
| | | public static final String UPNP_PROTOCOL = "00000010-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HIDP = "00000011-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HARDCOPY_CONTROL_CHANNEL_PROTOCOL = "00000012-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HARDCOPY_DATA_CHANNEL_PROTOCOL = "00000014-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HARDCOPY_NOTIFICATION_PROTOCOL = "00000016-0000-1000-8000-00805f9b34fb"; |
| | | public static final String VCTP_PROTOCOL = "00000017-0000-1000-8000-00805f9b34fb"; |
| | | public static final String VDTP_PROTOCOL = "00000019-0000-1000-8000-00805f9b34fb"; |
| | | public static final String CMPT_PROTOCOL = "0000001b-0000-1000-8000-00805f9b34fb"; |
| | | public static final String UDI_C_PLANE_PROTOCOL = "0000001d-0000-1000-8000-00805f9b34fb"; |
| | | public static final String MCAP_CONTROL_CHANNEL = "0000001e-0000-1000-8000-00805f9b34fb"; |
| | | public static final String MCAP_DATA_CHANNEL = "0000001f-0000-1000-8000-00805f9b34fb"; |
| | | public static final String L2CAP = "00000100-0000-1000-8000-00805f9b34fb"; |
| | | public static final String SERVICE_DISCOVERY_SERVER = "00001000-0000-1000-8000-00805f9b34fb"; |
| | | public static final String BROWSE_GROUP_DESCRIPTOR = "00001001-0000-1000-8000-00805f9b34fb"; |
| | | public static final String PUBLIC_BROWSE_GROUP = "00001002-0000-1000-8000-00805f9b34fb"; |
| | | public static final String SPP = "00001101-0000-1000-8000-00805f9b34fb"; |
| | | public static final String LAN_ACCESS_USING_PPP = "00001102-0000-1000-8000-00805f9b34fb"; |
| | | public static final String DUN_GW = "00001103-0000-1000-8000-00805f9b34fb"; |
| | | public static final String OBEX_SYNC = "00001104-0000-1000-8000-00805f9b34fb"; |
| | | public static final String OBEX_OBJECT_PUSH = "00001105-0000-1000-8000-00805f9b34fb"; |
| | | public static final String OBEX_FILE_TRANSFER = "00001106-0000-1000-8000-00805f9b34fb"; |
| | | public static final String IRMC_SYNC_COMMAND = "00001107-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HSP_HS = "00001108-0000-1000-8000-00805f9b34fb"; |
| | | public static final String CORDLESS_TELEPHONY = "00001109-0000-1000-8000-00805f9b34fb"; |
| | | public static final String AUDIO_SOURCE = "0000110a-0000-1000-8000-00805f9b34fb"; |
| | | public static final String AUDIO_SINK = "0000110b-0000-1000-8000-00805f9b34fb"; |
| | | public static final String AV_REMOTE_CONTROL_TARGET = "0000110c-0000-1000-8000-00805f9b34fb"; |
| | | public static final String ADVANCED_AUDIO = "0000110d-0000-1000-8000-00805f9b34fb"; |
| | | public static final String AVRCP_REMOTE = "0000110e-0000-1000-8000-00805f9b34fb"; |
| | | public static final String VIDEO_CONFERENCING = "0000110f-0000-1000-8000-00805f9b34fb"; |
| | | public static final String INTERCOM = "00001110-0000-1000-8000-00805f9b34fb"; |
| | | public static final String FAX = "00001111-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HEADSET_PROFILE_HSP_AUDIO_GATEWAY = "00001112-0000-1000-8000-00805f9b34fb"; |
| | | public static final String WAP = "00001113-0000-1000-8000-00805f9b34fb"; |
| | | public static final String WAP_CLIENT = "00001114-0000-1000-8000-00805f9b34fb"; |
| | | public static final String PANU = "00001115-0000-1000-8000-00805f9b34fb"; |
| | | public static final String NAP = "00001116-0000-1000-8000-00805f9b34fb"; |
| | | public static final String GN = "00001117-0000-1000-8000-00805f9b34fb"; |
| | | public static final String DIRECT_PRINTING = "00001118-0000-1000-8000-00805f9b34fb"; |
| | | public static final String REFERENCE_PRINTING = "00001119-0000-1000-8000-00805f9b34fb"; |
| | | public static final String IMAGING = "0000111a-0000-1000-8000-00805f9b34fb"; |
| | | public static final String IMAGING_RESPONDER = "0000111b-0000-1000-8000-00805f9b34fb"; |
| | | public static final String IMAGING_AUTOMATIC_ARCHIVE = "0000111c-0000-1000-8000-00805f9b34fb"; |
| | | public static final String IMAGING_REFERENCE_OBJECTS = "0000111d-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HANDS_FREE_PROFILE_HFP = "0000111e-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HANDS_FREE_PROFILE_HFP_AUDIO_GATEWAY = "0000111f-0000-1000-8000-00805f9b34fb"; |
| | | public static final String DIRECT_PRINTING_REFERENCE_OBJECTS = "00001120-0000-1000-8000-00805f9b34fb"; |
| | | public static final String REFLECTED_UI = "00001121-0000-1000-8000-00805f9b34fb"; |
| | | public static final String BASIC_PRINTING = "00001122-0000-1000-8000-00805f9b34fb"; |
| | | public static final String PRINTING_STATUS = "00001123-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HID = "00001124-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HARDCOPY_CABLE_REPLACEMENT = "00001125-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HCR_PRINT = "00001126-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HCR_SCAN = "00001127-0000-1000-8000-00805f9b34fb"; |
| | | public static final String COMMON_ISDN_ACCESS = "00001128-0000-1000-8000-00805f9b34fb"; |
| | | public static final String VIDEO_CONFERENCING_GATEWAY = "00001129-0000-1000-8000-00805f9b34fb"; |
| | | public static final String UDIMT = "0000112a-0000-1000-8000-00805f9b34fb"; |
| | | public static final String UDITA = "0000112b-0000-1000-8000-00805f9b34fb"; |
| | | public static final String AUDIO_VIDEO = "0000112c-0000-1000-8000-00805f9b34fb"; |
| | | public static final String SIM_ACCESS = "0000112d-0000-1000-8000-00805f9b34fb"; |
| | | public static final String OBEX_PCE = "0000112e-0000-1000-8000-00805f9b34fb"; |
| | | public static final String OBEX_PSE = "0000112f-0000-1000-8000-00805f9b34fb"; |
| | | public static final String OBEX_PBAP = "00001130-0000-1000-8000-00805f9b34fb"; |
| | | public static final String OBEX_MAS = "00001132-0000-1000-8000-00805f9b34fb"; |
| | | public static final String OBEX_MNS = "00001133-0000-1000-8000-00805f9b34fb"; |
| | | public static final String OBEX_MAP = "00001134-0000-1000-8000-00805f9b34fb"; |
| | | public static final String PNP = "00001200-0000-1000-8000-00805f9b34fb"; |
| | | public static final String GENERIC_NETWORKING = "00001201-0000-1000-8000-00805f9b34fb"; |
| | | public static final String GENERIC_FILE_TRANSFER = "00001202-0000-1000-8000-00805f9b34fb"; |
| | | public static final String GENERIC_AUDIO = "00001203-0000-1000-8000-00805f9b34fb"; |
| | | public static final String GENERIC_TELEPHONY = "00001204-0000-1000-8000-00805f9b34fb"; |
| | | public static final String UPNP = "00001205-0000-1000-8000-00805f9b34fb"; |
| | | public static final String UPNP_IP = "00001206-0000-1000-8000-00805f9b34fb"; |
| | | public static final String ESDP_UPNP_IP_PAN = "00001300-0000-1000-8000-00805f9b34fb"; |
| | | public static final String ESDP_UPNP_IP_LAP = "00001301-0000-1000-8000-00805f9b34fb"; |
| | | public static final String ESDP_UPNP_L2CAP = "00001302-0000-1000-8000-00805f9b34fb"; |
| | | public static final String VIDEO_DISTRIBUTION_PROFILE_VDP_SOURCE = "00001303-0000-1000-8000-00805f9b34fb"; |
| | | public static final String VIDEO_DISTRIBUTION_PROFILE_VDP_SINK = "00001304-0000-1000-8000-00805f9b34fb"; |
| | | public static final String VIDEO_DISTRIBUTION_PROFILE_VDP = "00001305-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HEALTH_DEVICE_PROFILE_HDP = "00001400-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HEALTH_DEVICE_PROFILE_HDP_SOURCE = "00001401-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HEALTH_DEVICE_PROFILE_HDP_SINK = "00001402-0000-1000-8000-00805f9b34fb"; |
| | | public static final String GAP = "00001800-0000-1000-8000-00805f9b34fb"; |
| | | public static final String GATT = "00001801-0000-1000-8000-00805f9b34fb"; |
| | | public static final String IMMEDIATE_ALERT = "00001802-0000-1000-8000-00805f9b34fb"; |
| | | public static final String LINK_LOSS = "00001803-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TX_POWER = "00001804-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HEALTH_THERMOMETER = "00001809-0000-1000-8000-00805f9b34fb"; |
| | | public static final String DEVICE_INFORMATION = "0000180a-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HEART_RATE = "0000180d-0000-1000-8000-00805f9b34fb"; |
| | | public static final String CYCLING_SC = "00001816-0000-1000-8000-00805f9b34fb"; |
| | | public static final String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb"; |
| | | public static final String DEVICE_NAME = "00002a00-0000-1000-8000-00805f9b34fb"; |
| | | public static final String APPEARANCE = "00002a01-0000-1000-8000-00805f9b34fb"; |
| | | public static final String PERIPHERAL_PRIVACY_FLAG = "00002a02-0000-1000-8000-00805f9b34fb"; |
| | | public static final String RECONNECTION_ADDRESS = "00002a03-0000-1000-8000-00805f9b34fb"; |
| | | public static final String PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS = "00002a04-0000-1000-8000-00805f9b34fb"; |
| | | public static final String SERVICE_CHANGED = "00002a05-0000-1000-8000-00805f9b34fb"; |
| | | public static final String ALERT_LEVEL = "00002a06-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TX_POWER_LEVEL = "00002a07-0000-1000-8000-00805f9b34fb"; |
| | | public static final String DATE_TIME = "00002a08-0000-1000-8000-00805f9b34fb"; |
| | | public static final String DAY_OF_WEEK = "00002a09-0000-1000-8000-00805f9b34fb"; |
| | | public static final String DAY_DATE_TIME = "00002a0a-0000-1000-8000-00805f9b34fb"; |
| | | public static final String EXACT_TIME_256 = "00002a0c-0000-1000-8000-00805f9b34fb"; |
| | | public static final String DST_OFFSET = "00002a0d-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TIME_ZONE = "00002a0e-0000-1000-8000-00805f9b34fb"; |
| | | public static final String LOCAL_TIME_INFORMATION = "00002a0f-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TIME_WITH_DST = "00002a11-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TIME_ACCURACY = "00002a12-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TIME_SOURCE = "00002a13-0000-1000-8000-00805f9b34fb"; |
| | | public static final String REFERENCE_TIME_INFORMATION = "00002a14-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TIME_UPDATE_CONTROL_POINT = "00002a16-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TIME_UPDATE_STATE = "00002a17-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TEMPERATURE_MEASUREMENT = "00002a1c-0000-1000-8000-00805f9b34fb"; |
| | | public static final String TEMPERATURE_TYPE = "00002a1d-0000-1000-8000-00805f9b34fb"; |
| | | public static final String INTERMEDIATE_TEMPERATURE = "00002a1e-0000-1000-8000-00805f9b34fb"; |
| | | public static final String MEASUREMENT_INTERVAL = "00002a21-0000-1000-8000-00805f9b34fb"; |
| | | public static final String SYSTEM_ID = "00002a23-0000-1000-8000-00805f9b34fb"; |
| | | public static final String MODEL_NUMBER_STRING = "00002a24-0000-1000-8000-00805f9b34fb"; |
| | | public static final String SERIAL_NUMBER_STRING = "00002a25-0000-1000-8000-00805f9b34fb"; |
| | | public static final String FIRMWARE_REVISION_STRING = "00002a26-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HARDWARE_REVISION_STRING = "00002a27-0000-1000-8000-00805f9b34fb"; |
| | | public static final String SOFTWARE_REVISION_STRING = "00002a28-0000-1000-8000-00805f9b34fb"; |
| | | public static final String MANUFACTURER_NAME_STRING = "00002a29-0000-1000-8000-00805f9b34fb"; |
| | | public static final String IEEE_1107320601_REGULATORY = "00002a2a-0000-1000-8000-00805f9b34fb"; |
| | | public static final String CURRENT_TIME = "00002a2b-0000-1000-8000-00805f9b34fb"; |
| | | public static final String BLOOD_PRESSURE_MEASUREMENT = "00002a35-0000-1000-8000-00805f9b34fb"; |
| | | public static final String INTERMEDIATE_CUFF_PRESSURE = "00002a36-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HEART_RATE_MEASUREMENT = "00002a37-0000-1000-8000-00805f9b34fb"; |
| | | public static final String BODY_SENSOR_LOCATION = "00002a38-0000-1000-8000-00805f9b34fb"; |
| | | public static final String HEART_RATE_CONTROL_POINT = "00002a39-0000-1000-8000-00805f9b34fb"; |
| | | public static final String ALERT_STATUS = "00002a3f-0000-1000-8000-00805f9b34fb"; |
| | | public static final String RINGER_CONTROL_POINT = "00002a40-0000-1000-8000-00805f9b34fb"; |
| | | public static final String RINGER_SETTING = "00002a41-0000-1000-8000-00805f9b34fb"; |
| | | public static final String ALERT_CATEGORY_ID_BIT_MASK = "00002a42-0000-1000-8000-00805f9b34fb"; |
| | | public static final String ALERT_CATEGORY_ID = "00002a43-0000-1000-8000-00805f9b34fb"; |
| | | public static final String ALERT_NOTIFICATION_CONTROL_POINT = "00002a44-0000-1000-8000-00805f9b34fb"; |
| | | public static final String UNREAD_ALERT_STATUS = "00002a45-0000-1000-8000-00805f9b34fb"; |
| | | public static final String NEW_ALERT = "00002a46-0000-1000-8000-00805f9b34fb"; |
| | | public static final String SUPPORTED_NEW_ALERT_CATEGORY = "00002a47-0000-1000-8000-00805f9b34fb"; |
| | | public static final String SUPPORTED_UNREAD_ALERT_CATEGORY = "00002a48-0000-1000-8000-00805f9b34fb"; |
| | | public static final String BLOOD_PRESSURE_FEATURE = "00002a49-0000-1000-8000-00805f9b34fb"; |
| | | public static final String PNPID = "00002a50-0000-1000-8000-00805f9b34fb"; |
| | | public static final String SC_CONTROL_POINT = "00002a55-0000-1000-8000-00805f9b34fb"; |
| | | public static final String CSC_MEASUREMENT = "00002a5b-0000-1000-8000-00805f9b34fb"; |
| | | public static final String CSC_FEATURE = "00002a5c-0000-1000-8000-00805f9b34fb"; |
| | | public static final String SENSOR_LOCATION = "00002a5d-0000-1000-8000-00805f9b34fb"; |
| | | public static final String ACTIVESYNC = "831c4071-7bc8-4a9c-a01c-15df25a4adbc"; |
| | | public static final String ESTIMOTE_SERVICE = "b9403000-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_UUID = "b9403003-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_MAJOR = "b9403001-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_MINOR = "b9403002-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_BATTERY = "b9403041-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_TEMPERATURE = "b9403021-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_POWER = "b9403011-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_ADVERTISING_INTERVAL = "b9403012-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_VERSION_SERVICE = "b9404000-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_SOFTWARE_VERSION = "b9404001-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_HARDWARE_VERSION = "b9404002-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_AUTHENTICATION_SERVICE = "b9402000-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_ADVERTISING_SEED = "b9402001-f5f8-466e-aff9-25556b57fe6d"; |
| | | public static final String ESTIMOTE_ADVERTISING_VECTOR = "b9402002-f5f8-466e-aff9-25556b57fe6d"; |
| | | |
| | | private final static Map<String, String> sGattAttributesMap = populateGattAttributesMap(); |
| | | |
| | | public static String getAttributeName(final String uuid, final String fallback) { |
| | | final String name = sGattAttributesMap.get(uuid.toLowerCase(Locale.US)); |
| | | return name == null ? fallback : name; |
| | | } |
| | | |
| | | private static Map<String, String> populateGattAttributesMap() { |
| | | final Map<String, String> map = new HashMap<>(); |
| | | |
| | | map.put(BASE_GUID , "Base GUID"); |
| | | map.put(SERVICE_DISCOVERY_PROTOCOL_SDP , "Service Discovery Protocol (SDP)"); |
| | | map.put(USER_DATAGRAM_PROTOCOL_UDP , "User Datagram Protocol (UDP)"); |
| | | map.put(RADIO_FREQUENCY_COMMUNICATION_PROTOCOL_RFCOMM , "Radio Frequency Communication Protocol (RFCOMM)"); |
| | | map.put(TCP , "TCP"); |
| | | map.put(TCSBIN , "TCSBIN"); |
| | | map.put(TCSAT , "TCSAT"); |
| | | map.put(OBJECT_EXCHANGE_PROTOCOL_OBEX , "Object Exchange Protocol (OBEX)"); |
| | | map.put(IP , "IP"); |
| | | map.put(FTP , "FTP"); |
| | | map.put(HTTP , "HTTP"); |
| | | map.put(WSP , "WSP"); |
| | | map.put(BNEP_SVC , "BNEP_SVC"); |
| | | map.put(UPNP_PROTOCOL , "UPNP Protocol"); |
| | | map.put(HIDP , "HIDP"); |
| | | map.put(HARDCOPY_CONTROL_CHANNEL_PROTOCOL , "Hardcopy Control Channel Protocol"); |
| | | map.put(HARDCOPY_DATA_CHANNEL_PROTOCOL , "Hardcopy Data Channel Protocol"); |
| | | map.put(HARDCOPY_NOTIFICATION_PROTOCOL , "Hardcopy Notification Protocol"); |
| | | map.put(VCTP_PROTOCOL , "VCTP Protocol"); |
| | | map.put(VDTP_PROTOCOL , "VDTP Protocol"); |
| | | map.put(CMPT_PROTOCOL , "CMPT Protocol"); |
| | | map.put(UDI_C_PLANE_PROTOCOL , "UDI C Plane Protocol"); |
| | | map.put(MCAP_CONTROL_CHANNEL , "MCAP Control Channel"); |
| | | map.put(MCAP_DATA_CHANNEL , "MCAP Data Channel"); |
| | | map.put(L2CAP , "L2CAP"); |
| | | map.put(SERVICE_DISCOVERY_SERVER , "Service Discovery Server"); |
| | | map.put(BROWSE_GROUP_DESCRIPTOR , "Browse Group Descriptor"); |
| | | map.put(PUBLIC_BROWSE_GROUP , "Public Browse Group"); |
| | | map.put(SPP , "SPP"); |
| | | map.put(LAN_ACCESS_USING_PPP , "LAN Access Using PPP"); |
| | | map.put(DUN_GW , "DUN_GW"); |
| | | map.put(OBEX_SYNC , "OBEX_SYNC"); |
| | | map.put(OBEX_OBJECT_PUSH , "OBEX Object Push"); |
| | | map.put(OBEX_FILE_TRANSFER , "OBEX File Transfer"); |
| | | map.put(IRMC_SYNC_COMMAND , "IrMC Sync Command"); |
| | | map.put(HSP_HS , "HSP_HS"); |
| | | map.put(CORDLESS_TELEPHONY , "Cordless Telephony"); |
| | | map.put(AUDIO_SOURCE , "Audio Source"); |
| | | map.put(AUDIO_SINK , "Audio Sink"); |
| | | map.put(AV_REMOTE_CONTROL_TARGET , "AV Remote Control Target"); |
| | | map.put(ADVANCED_AUDIO , "ADVANCED_AUDIO"); |
| | | map.put(AVRCP_REMOTE , "AVRCP_REMOTE"); |
| | | map.put(VIDEO_CONFERENCING , "Video Conferencing"); |
| | | map.put(INTERCOM , "Intercom"); |
| | | map.put(FAX , "FAX"); |
| | | map.put(HEADSET_PROFILE_HSP_AUDIO_GATEWAY , "Headset Profile (HSP) - Audio Gateway"); |
| | | map.put(WAP , "WAP"); |
| | | map.put(WAP_CLIENT , "WAP Client"); |
| | | map.put(PANU , "PANU"); |
| | | map.put(NAP , "NAP"); |
| | | map.put(GN , "GN"); |
| | | map.put(DIRECT_PRINTING , "Direct Printing"); |
| | | map.put(REFERENCE_PRINTING , "Reference Printing"); |
| | | map.put(IMAGING , "Imaging"); |
| | | map.put(IMAGING_RESPONDER , "Imaging Responder"); |
| | | map.put(IMAGING_AUTOMATIC_ARCHIVE , "Imaging Automatic Archive"); |
| | | map.put(IMAGING_REFERENCE_OBJECTS , "Imaging Reference Objects"); |
| | | map.put(HANDS_FREE_PROFILE_HFP , "Hands Free Profile (HFP)"); |
| | | map.put(HANDS_FREE_PROFILE_HFP_AUDIO_GATEWAY , "Hands Free Profile (HFP) â Audio Gateway"); |
| | | map.put(DIRECT_PRINTING_REFERENCE_OBJECTS , "Direct Printing Reference Objects"); |
| | | map.put(REFLECTED_UI , "Reflected UI"); |
| | | map.put(BASIC_PRINTING , "Basic Printing"); |
| | | map.put(PRINTING_STATUS , "Printing Status"); |
| | | map.put(HID , "HID"); |
| | | map.put(HARDCOPY_CABLE_REPLACEMENT , "Hardcopy Cable Replacement"); |
| | | map.put(HCR_PRINT , "HCR Print"); |
| | | map.put(HCR_SCAN , "HCR Scan"); |
| | | map.put(COMMON_ISDN_ACCESS , "Common ISDN Access"); |
| | | map.put(VIDEO_CONFERENCING_GATEWAY , "Video Conferencing Gateway"); |
| | | map.put(UDIMT , "UDIMT"); |
| | | map.put(UDITA , "UDITA"); |
| | | map.put(AUDIO_VIDEO , "Audio Video"); |
| | | map.put(SIM_ACCESS , "SIM Access"); |
| | | map.put(OBEX_PCE , "OBEX PCE"); |
| | | map.put(OBEX_PSE , "OBEX PSE"); |
| | | map.put(OBEX_PBAP , "OBEX PBAP"); |
| | | map.put(OBEX_MAS , "OBEX MAS"); |
| | | map.put(OBEX_MNS , "OBEX MNS"); |
| | | map.put(OBEX_MAP , "OBEX MAP"); |
| | | map.put(PNP , "PNP"); |
| | | map.put(GENERIC_NETWORKING , "Generic Networking"); |
| | | map.put(GENERIC_FILE_TRANSFER , "Generic File Transfer"); |
| | | map.put(GENERIC_AUDIO , "Generic Audio"); |
| | | map.put(GENERIC_TELEPHONY , "Generic Telephony"); |
| | | map.put(UPNP , "UPNP"); |
| | | map.put(UPNP_IP , "UPNP IP"); |
| | | map.put(ESDP_UPNP_IP_PAN , "ESDP UPnP IP PAN"); |
| | | map.put(ESDP_UPNP_IP_LAP , "ESDP UPnP IP LAP"); |
| | | map.put(ESDP_UPNP_L2CAP , "ESDP Upnp L2CAP"); |
| | | map.put(VIDEO_DISTRIBUTION_PROFILE_VDP_SOURCE , "Video Distribution Profile (VDP) - Source"); |
| | | map.put(VIDEO_DISTRIBUTION_PROFILE_VDP_SINK , "Video Distribution Profile (VDP) - Sink"); |
| | | map.put(VIDEO_DISTRIBUTION_PROFILE_VDP , "Video Distribution Profile (VDP)"); |
| | | map.put(HEALTH_DEVICE_PROFILE_HDP , "Health Device Profile (HDP)"); |
| | | map.put(HEALTH_DEVICE_PROFILE_HDP_SOURCE , "Health Device Profile (HDP) - Source"); |
| | | map.put(HEALTH_DEVICE_PROFILE_HDP_SINK , "Health Device Profile (HDP) - Sink"); |
| | | map.put(GAP , "GAP"); |
| | | map.put(GATT , "GATT"); |
| | | map.put(IMMEDIATE_ALERT , "IMMEDIATE_ALERT"); |
| | | map.put(LINK_LOSS , "LINK_LOSS"); |
| | | map.put(TX_POWER , "TX_POWER"); |
| | | map.put(HEALTH_THERMOMETER , "Health Thermometer"); |
| | | map.put(DEVICE_INFORMATION , "Device Information"); |
| | | map.put(HEART_RATE , "HEART_RATE"); |
| | | map.put(CYCLING_SC , "CYCLING_SC"); |
| | | map.put(CLIENT_CHARACTERISTIC_CONFIG , "CLIENT_CHARACTERISTIC_CONFIG"); |
| | | map.put(DEVICE_NAME , "Device Name"); |
| | | map.put(APPEARANCE , "Appearance"); |
| | | map.put(PERIPHERAL_PRIVACY_FLAG , "Peripheral Privacy Flag"); |
| | | map.put(RECONNECTION_ADDRESS , "Reconnection Address"); |
| | | map.put(PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS , "Peripheral Preferred Connection Parameters"); |
| | | map.put(SERVICE_CHANGED , "Service Changed"); |
| | | map.put(ALERT_LEVEL , "Alert Level"); |
| | | map.put(TX_POWER_LEVEL , "Tx Power Level"); |
| | | map.put(DATE_TIME , "Date Time"); |
| | | map.put(DAY_OF_WEEK , "Day of Week"); |
| | | map.put(DAY_DATE_TIME , "Day Date Time"); |
| | | map.put(EXACT_TIME_256 , "Exact Time 256"); |
| | | map.put(DST_OFFSET , "DST Offset"); |
| | | map.put(TIME_ZONE , "Time Zone"); |
| | | map.put(LOCAL_TIME_INFORMATION , "Local Time Information"); |
| | | map.put(TIME_WITH_DST , "Time with DST"); |
| | | map.put(TIME_ACCURACY , "Time Accuracy"); |
| | | map.put(TIME_SOURCE , "Time Source"); |
| | | map.put(REFERENCE_TIME_INFORMATION , "Reference Time Information"); |
| | | map.put(TIME_UPDATE_CONTROL_POINT , "Time Update Control Point"); |
| | | map.put(TIME_UPDATE_STATE , "Time Update State"); |
| | | map.put(TEMPERATURE_MEASUREMENT , "Temperature Measurement"); |
| | | map.put(TEMPERATURE_TYPE , "Temperature Type"); |
| | | map.put(INTERMEDIATE_TEMPERATURE , "Intermediate Temperature"); |
| | | map.put(MEASUREMENT_INTERVAL , "Measurement Interval"); |
| | | map.put(SYSTEM_ID , "System ID"); |
| | | map.put(MODEL_NUMBER_STRING , "Model Number String"); |
| | | map.put(SERIAL_NUMBER_STRING , "Serial Number String"); |
| | | map.put(FIRMWARE_REVISION_STRING , "Firmware Revision String"); |
| | | map.put(HARDWARE_REVISION_STRING , "Hardware Revision String"); |
| | | map.put(SOFTWARE_REVISION_STRING , "Software Revision String"); |
| | | map.put(MANUFACTURER_NAME_STRING , "Manufacturer Name String"); |
| | | map.put(IEEE_1107320601_REGULATORY , "IEEE 11073-20601 Regulatory"); |
| | | map.put(CURRENT_TIME , "Current Time"); |
| | | map.put(BLOOD_PRESSURE_MEASUREMENT , "Blood Pressure Measurement"); |
| | | map.put(INTERMEDIATE_CUFF_PRESSURE , "Intermediate Cuff Pressure"); |
| | | map.put(HEART_RATE_MEASUREMENT , "Heart Rate Measurement"); |
| | | map.put(BODY_SENSOR_LOCATION , "Body Sensor Location"); |
| | | map.put(HEART_RATE_CONTROL_POINT , "Heart Rate Control Point"); |
| | | map.put(ALERT_STATUS , "Alert Status"); |
| | | map.put(RINGER_CONTROL_POINT , "Ringer Control Point"); |
| | | map.put(RINGER_SETTING , "Ringer Setting"); |
| | | map.put(ALERT_CATEGORY_ID_BIT_MASK , "Alert Category ID Bit Mask"); |
| | | map.put(ALERT_CATEGORY_ID , "Alert Category ID"); |
| | | map.put(ALERT_NOTIFICATION_CONTROL_POINT , "Alert Notification Control Point"); |
| | | map.put(UNREAD_ALERT_STATUS , "Unread Alert Status"); |
| | | map.put(NEW_ALERT , "New Alert"); |
| | | map.put(SUPPORTED_NEW_ALERT_CATEGORY , "Supported New Alert Category"); |
| | | map.put(SUPPORTED_UNREAD_ALERT_CATEGORY , "Supported Unread Alert Category"); |
| | | map.put(BLOOD_PRESSURE_FEATURE , "Blood Pressure Feature"); |
| | | map.put(PNPID , "PNPID"); |
| | | map.put(SC_CONTROL_POINT , "SC_CONTROL_POINT"); |
| | | map.put(CSC_MEASUREMENT , "CSC_MEASUREMENT"); |
| | | map.put(CSC_FEATURE , "CSC_FEATURE"); |
| | | map.put(SENSOR_LOCATION , "SENSOR_LOCATION"); |
| | | map.put(ACTIVESYNC , "ActiveSync"); |
| | | map.put(ESTIMOTE_SERVICE , "Estimote Service"); |
| | | map.put(ESTIMOTE_UUID , "Estimote UUID"); |
| | | map.put(ESTIMOTE_MAJOR , "Estimote Major"); |
| | | map.put(ESTIMOTE_MINOR , "Estimote Minor"); |
| | | map.put(ESTIMOTE_BATTERY , "Estimote Battery"); |
| | | map.put(ESTIMOTE_TEMPERATURE , "Estimote Temperature"); |
| | | map.put(ESTIMOTE_POWER , "Estimote Power"); |
| | | map.put(ESTIMOTE_ADVERTISING_INTERVAL , "Estimote Advertising Interval"); |
| | | map.put(ESTIMOTE_VERSION_SERVICE , "Estimote Version Service"); |
| | | map.put(ESTIMOTE_SOFTWARE_VERSION , "Estimote Software Version"); |
| | | map.put(ESTIMOTE_HARDWARE_VERSION , "Estimote Hardware Version"); |
| | | map.put(ESTIMOTE_AUTHENTICATION_SERVICE , "Estimote Authentication Service"); |
| | | map.put(ESTIMOTE_ADVERTISING_SEED , "Estimote Advertising Seed"); |
| | | map.put(ESTIMOTE_ADVERTISING_VECTOR , "Estimote Advertising Vector"); |
| | | |
| | | return map; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.scan; |
| | | |
| | | import android.annotation.TargetApi; |
| | | import android.app.Activity; |
| | | import android.app.Application; |
| | | import android.content.Context; |
| | | import android.os.Bundle; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.BleManager; |
| | | import com.blakequ.bluetooth_manager_lib.BleParamsOptions; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | |
| | | |
| | | /** |
| | | * |
| | | * Simply creating an instance of this class and holding a reference to it in your Application can |
| | | * improve battery life by 60% by slowing down scans when your app is in the background. |
| | | * |
| | | */ |
| | | @TargetApi(18) |
| | | public class BackgroundPowerSaver implements Application.ActivityLifecycleCallbacks { |
| | | private int activeActivityCount = 0; |
| | | private Context mContext; |
| | | /** |
| | | * The default duration in milliseconds of the Bluetooth scan cycle |
| | | */ |
| | | public static final long DEFAULT_FOREGROUND_SCAN_PERIOD = 10000; |
| | | /** |
| | | * The default duration in milliseconds spent not scanning between each Bluetooth scan cycle |
| | | */ |
| | | public static final long DEFAULT_FOREGROUND_BETWEEN_SCAN_PERIOD = 5*1000; |
| | | /** |
| | | * The default duration in milliseconds of the Bluetooth scan cycle when no ranging/monitoring clients are in the foreground |
| | | */ |
| | | public static final long DEFAULT_BACKGROUND_SCAN_PERIOD = 10000; |
| | | /** |
| | | * The default duration in milliseconds spent not scanning between each Bluetooth scan cycle when no ranging/monitoring clients are in the foreground |
| | | */ |
| | | public static final long DEFAULT_BACKGROUND_BETWEEN_SCAN_PERIOD = 5 * 60 * 1000; |
| | | |
| | | private long foregroundScanPeriod = DEFAULT_FOREGROUND_SCAN_PERIOD; |
| | | private long foregroundBetweenScanPeriod = DEFAULT_FOREGROUND_BETWEEN_SCAN_PERIOD; |
| | | private long backgroundScanPeriod = DEFAULT_BACKGROUND_SCAN_PERIOD; |
| | | private long backgroundBetweenScanPeriod = DEFAULT_BACKGROUND_BETWEEN_SCAN_PERIOD; |
| | | |
| | | /** |
| | | * |
| | | * Constructs a new BackgroundPowerSaver |
| | | * |
| | | * @param context |
| | | * @deprecated the countActiveActivityStrategy flag is no longer used. |
| | | * |
| | | */ |
| | | public BackgroundPowerSaver(Context context, boolean countActiveActivityStrategy) { |
| | | this(context); |
| | | } |
| | | |
| | | /** |
| | | * |
| | | * Constructs a new BackgroundPowerSaver using the default background determination strategy |
| | | * |
| | | * @param context |
| | | */ |
| | | public BackgroundPowerSaver(Context context) { |
| | | if (android.os.Build.VERSION.SDK_INT < 18) { |
| | | LogUtils.w("BackgroundPowerSaver requires API 18 or higher."); |
| | | return; |
| | | } |
| | | ((Application)context.getApplicationContext()).registerActivityLifecycleCallbacks(this); |
| | | this.mContext = context; |
| | | } |
| | | |
| | | /** |
| | | * Sets the duration in milliseconds of each Bluetooth LE scan cycle to look for beacons. |
| | | * This function is used to setup the period when switching |
| | | * between background/foreground. To have it effect on an already running scan (when the next |
| | | * cycle starts), call {@link BluetoothScanManager#setBackgroundMode} |
| | | * |
| | | * @param p |
| | | */ |
| | | @Deprecated |
| | | public void setForegroundScanPeriod(long p) { |
| | | foregroundScanPeriod = p; |
| | | } |
| | | |
| | | /** |
| | | * Sets the duration in milliseconds between each Bluetooth LE scan cycle to look for beacons. |
| | | * This function is used to setup the period when switching |
| | | * between background/foreground. To have it effect on an already running scan (when the next |
| | | * cycle starts), call {@link BluetoothScanManager#setBackgroundMode} |
| | | * |
| | | * @param p |
| | | */ |
| | | @Deprecated |
| | | public void setForegroundBetweenScanPeriod(long p) { |
| | | foregroundBetweenScanPeriod = p; |
| | | } |
| | | |
| | | /** |
| | | * Sets the duration in milliseconds of each Bluetooth LE scan cycle to look for beacons. |
| | | * This function is used to setup the period when switching |
| | | * between background/foreground. To have it effect on an already running scan (when the next |
| | | * cycle starts), call {@link BluetoothScanManager#setBackgroundMode} |
| | | * |
| | | * @param p |
| | | */ |
| | | @Deprecated |
| | | public void setBackgroundScanPeriod(long p) { |
| | | backgroundScanPeriod = p; |
| | | } |
| | | |
| | | /** |
| | | * Sets the duration in milliseconds spent not scanning between each Bluetooth LE scan cycle when no ranging/monitoring clients are in the foreground |
| | | * |
| | | * @param p |
| | | */ |
| | | @Deprecated |
| | | public void setBackgroundBetweenScanPeriod(long p) { |
| | | backgroundBetweenScanPeriod = p; |
| | | } |
| | | |
| | | public long getScanPeriod() { |
| | | BleParamsOptions options = BleManager.getBleParamsOptions(); |
| | | if (BluetoothScanManager.getInstance(mContext).isBackgroundMode()) { |
| | | if (options != null){ |
| | | return options.getBackgroundScanPeriod(); |
| | | }else { |
| | | return backgroundScanPeriod; |
| | | } |
| | | } else { |
| | | if (options != null){ |
| | | return options.getForegroundScanPeriod(); |
| | | }else { |
| | | return foregroundScanPeriod; |
| | | } |
| | | } |
| | | } |
| | | |
| | | public long getBetweenScanPeriod() { |
| | | BleParamsOptions options = BleManager.getBleParamsOptions(); |
| | | if (BluetoothScanManager.getInstance(mContext).isBackgroundMode()) { |
| | | if (options != null){ |
| | | return options.getBackgroundBetweenScanPeriod(); |
| | | }else { |
| | | return backgroundBetweenScanPeriod; |
| | | } |
| | | } else { |
| | | if (options != null){ |
| | | return options.getForegroundBetweenScanPeriod(); |
| | | }else { |
| | | return foregroundBetweenScanPeriod; |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityCreated(Activity activity, Bundle bundle) { |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityStarted(Activity activity) { |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityResumed(Activity activity) { |
| | | activeActivityCount++; |
| | | if (activeActivityCount < 1) { |
| | | LogUtils.d("reset active activity count on resume. It was " + activeActivityCount); |
| | | activeActivityCount = 1; |
| | | } |
| | | BluetoothScanManager.getInstance(mContext).setBackgroundMode(false); |
| | | LogUtils.d("activity resumed: "+activity+" active activities: "+activeActivityCount); |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityPaused(Activity activity) { |
| | | activeActivityCount--; |
| | | LogUtils.d("activity paused: "+activity+" active activities: "+activeActivityCount); |
| | | if (activeActivityCount < 1) { |
| | | LogUtils.d("setting background mode"); |
| | | BluetoothScanManager.getInstance(mContext).setBackgroundMode(true); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityStopped(Activity activity) { |
| | | } |
| | | |
| | | @Override |
| | | public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public void onActivityDestroyed(Activity activity) { |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.scan; |
| | | |
| | | import android.annotation.TargetApi; |
| | | import android.content.Context; |
| | | import android.os.Handler; |
| | | import android.os.Looper; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.BleManager; |
| | | import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanCallbackCompat; |
| | | import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanFilterCompat; |
| | | import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanResultCompat; |
| | | import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanSettingsCompat; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/17 11:30 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description: æ«æç®¡çå¨ï¼è¦å®ç°çåè½ |
| | | * <ol> |
| | | * <li>æ«æå°è£
</li> |
| | | * <li>æ«æç®¡ç</li> |
| | | * <li>æ¯æibeaconæ«æè§£æ</li> |
| | | * <li>æç»æ«æççµç®¡çï¼BackgroundPowerSaver)</li> |
| | | * <li>å¾ªç¯æ«ææåä¸å¼å§ï¼å½è¿æ¥æ¶å¯ä»¥è®©æ«ææåï¼ä¸æ¦æå¼å°±é坿«æï¼</li> |
| | | * <li>å½åæ«æç¶æ</li> |
| | | * </ol> |
| | | * 注æï¼åè°ä¸å¨ä¸»çº¿ç¨ä¸æ§è¡ï¼éè¦èªå·±å¨ä¸»çº¿ç¨ä¸å¤çåè°ï¼{@link com.blakequ.bluetooth_manager_lib.scan.ScanOverListener} and {@link com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanCallbackCompat}ï¼, |
| | | * å°¤å
¶æ¯æ³å¨æ«æç»æä¹åç´æ¥æ§è¡è¿æ¥èçææå¼èç设å¤ï¼é½éè¦å¨ä¸»çº¿ç¨æ§è¡ï¼å¦åå¨æäºæºåå¦ä¸æä¼åºç°å¼å¸¸ã |
| | | */ |
| | | @TargetApi(18) |
| | | public final class BluetoothScanManager { |
| | | private static BluetoothScanManager INSTANCE = null; |
| | | |
| | | //is background mode or not |
| | | private boolean backgroundMode = false; |
| | | private BackgroundPowerSaver mPowerSaver; |
| | | private CycledLeScanner cycledLeScanner; |
| | | private ScanCallbackCompat scanCallbackCompat; |
| | | private final Handler mHandler; |
| | | private static Object obj = new Object(); |
| | | |
| | | private BluetoothScanManager(Context context){ |
| | | mPowerSaver = new BackgroundPowerSaver(context); |
| | | mHandler = new Handler(Looper.getMainLooper()); |
| | | cycledLeScanner = new CycledLeScanner(context, |
| | | BackgroundPowerSaver.DEFAULT_FOREGROUND_SCAN_PERIOD, |
| | | BackgroundPowerSaver.DEFAULT_FOREGROUND_BETWEEN_SCAN_PERIOD, |
| | | backgroundMode, |
| | | getScanCallback()); |
| | | BleManager.getBleParamsOptions(); |
| | | } |
| | | |
| | | public static BluetoothScanManager getInstance(Context context){ |
| | | if (INSTANCE == null){ |
| | | synchronized (obj){ |
| | | if (INSTANCE == null){ |
| | | LogUtils.d("BluetoothScanManager instance creation"); |
| | | INSTANCE = new BluetoothScanManager(context); |
| | | } |
| | | } |
| | | } |
| | | return INSTANCE; |
| | | } |
| | | |
| | | /** |
| | | * Runs the specified action on the UI thread. If the current thread is the UI |
| | | * thread, then the action is executed immediately. If the current thread is |
| | | * not the UI thread, the action is posted to the event queue of the UI thread. |
| | | * |
| | | * @param action the action to run on the UI thread |
| | | */ |
| | | public final void runOnUiThread(Runnable action) { |
| | | if (Thread.currentThread() != Looper.getMainLooper().getThread()) { |
| | | mHandler.post(action); |
| | | } else { |
| | | action.run(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * @return |
| | | */ |
| | | public BackgroundPowerSaver getPowerSaver(){ |
| | | return mPowerSaver; |
| | | } |
| | | |
| | | /** |
| | | * set scan device invoke |
| | | * @param scanCallbackCompat |
| | | */ |
| | | public void setScanCallbackCompat(ScanCallbackCompat scanCallbackCompat) { |
| | | this.scanCallbackCompat = scanCallbackCompat; |
| | | } |
| | | |
| | | public void setScanOverListener(ScanOverListener scanOverListener) { |
| | | cycledLeScanner.setScanOverListener(scanOverListener); |
| | | } |
| | | |
| | | /** |
| | | * is scanning |
| | | * @return |
| | | */ |
| | | public boolean isScanning(){ |
| | | return cycledLeScanner.isScanning(); |
| | | } |
| | | |
| | | public boolean isPauseScanning(){ |
| | | return cycledLeScanner.isPauseScan(); |
| | | } |
| | | |
| | | /** |
| | | * stop cycle scan and will restart when invoke {@link #startCycleScan()} |
| | | */ |
| | | public void stopCycleScan(){ |
| | | cycledLeScanner.setPauseScan(true); |
| | | } |
| | | |
| | | /** |
| | | * start scan device and will stop until invoke {@link #stopCycleScan()} |
| | | * notice: maybe is not scan right now |
| | | * @see #startScanNow() |
| | | * @see #stopCycleScan() |
| | | */ |
| | | public void startCycleScan(){ |
| | | cycledLeScanner.startScan(); |
| | | } |
| | | |
| | | /** |
| | | * Immediately start a scan(only one times) |
| | | */ |
| | | public void startScanOnce(){ |
| | | cycledLeScanner.startOnceScan(); |
| | | } |
| | | |
| | | /** |
| | | * start scan right now, is different {@link #startCycleScan()} |
| | | * @see #startCycleScan() |
| | | */ |
| | | public void startScanNow(){ |
| | | // FIXME: 2017/6/22 modify config not enable first time |
| | | cycledLeScanner.setBackgroundMode(mPowerSaver.getScanPeriod(), mPowerSaver.getBetweenScanPeriod(), backgroundMode); |
| | | cycledLeScanner.startScanNow(); |
| | | } |
| | | |
| | | /** |
| | | * add scan filter |
| | | * @param scanFilter |
| | | */ |
| | | public void addScanFilterCompats(ScanFilterCompat scanFilter){ |
| | | cycledLeScanner.addScanFilterCompats(scanFilter); |
| | | } |
| | | |
| | | public void setScanSettings(ScanSettingsCompat scanSettings) { |
| | | cycledLeScanner.setScanSettings(scanSettings); |
| | | } |
| | | |
| | | /** |
| | | * default using new scan method if API >= 21 |
| | | * @return |
| | | */ |
| | | @Deprecated |
| | | public static boolean isAPI21ScanningDisabled(){ |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * This method notifies the beacon service that the application is either moving to background |
| | | * mode or foreground mode. When in background mode, BluetoothLE scans to look for beacons are |
| | | * executed less frequently in order to save battery life. The specific scan rates for |
| | | * background and foreground operation are set by the defaults below, but may be customized. |
| | | * When ranging in the background, the time between updates will be much less frequent than in |
| | | * the foreground. Updates will come every time interval equal to the sum total of the |
| | | * BackgroundScanPeriod and the BackgroundBetweenScanPeriod. |
| | | * |
| | | * @param backgroundMode true indicates the app is in the background |
| | | */ |
| | | public void setBackgroundMode(boolean backgroundMode) { |
| | | if (android.os.Build.VERSION.SDK_INT < 18) { |
| | | LogUtils.w("Not supported prior to API 18. Method invocation will be ignored"); |
| | | } |
| | | if (backgroundMode != this.backgroundMode) { |
| | | this.backgroundMode = backgroundMode; |
| | | cycledLeScanner.setBackgroundMode(mPowerSaver.getScanPeriod(), mPowerSaver.getBetweenScanPeriod(), backgroundMode); |
| | | } |
| | | } |
| | | |
| | | public boolean isBackgroundMode() { |
| | | return backgroundMode; |
| | | } |
| | | |
| | | private ScanCallbackCompat getScanCallback(){ |
| | | return new ScanCallbackCompat() { |
| | | @Override |
| | | public void onBatchScanResults(final List<ScanResultCompat> results) { |
| | | if (scanCallbackCompat != null){ |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | scanCallbackCompat.onBatchScanResults(results); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onScanFailed(final int errorCode) { |
| | | if (scanCallbackCompat != null){ |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | scanCallbackCompat.onScanFailed(errorCode); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onScanResult(final int callbackType, final ScanResultCompat result) { |
| | | if (scanCallbackCompat != null){ |
| | | runOnUiThread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | scanCallbackCompat.onScanResult(callbackType, result); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.scan; |
| | | |
| | | import android.Manifest; |
| | | import android.annotation.TargetApi; |
| | | import android.bluetooth.BluetoothAdapter; |
| | | import android.content.Context; |
| | | import android.content.pm.PackageManager; |
| | | import android.location.LocationManager; |
| | | import android.os.Handler; |
| | | import android.os.SystemClock; |
| | | import androidx.core.content.ContextCompat; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.BluetoothLeScannerCompat; |
| | | import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanCallbackCompat; |
| | | import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanFilterCompat; |
| | | import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanSettingsCompat; |
| | | import com.blakequ.bluetooth_manager_lib.util.BluetoothUtils; |
| | | import com.blankj.utilcode.util.LogUtils; |
| | | |
| | | import java.util.List; |
| | | import java.util.concurrent.CopyOnWriteArrayList; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/18 11:54 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description: |
| | | */ |
| | | @TargetApi(18) |
| | | public class CycledLeScanner { |
| | | private boolean isPrintCycleTime = true; |
| | | private final Context mContext; |
| | | private long scanPeriod; |
| | | private long betweenScanPeriod; |
| | | private boolean mBackgroundFlag = false; |
| | | private ScanCallbackCompat scanCallbackCompat; |
| | | private final BluetoothUtils mBluetoothUtils; |
| | | private ScanOverListener scanOverListener; |
| | | private long nextScanStartTime = 0; |
| | | private long scanStopTime = 0; |
| | | private long lastScanEndTime = 0; |
| | | private boolean mScanning = false; |
| | | private boolean isPauseScan = false; //pause scan or restart |
| | | private boolean isOnceScan = false; //scan only once |
| | | private boolean isStartNow = false; |
| | | private final Handler mHandler = new Handler(); |
| | | private boolean isSetScanSetting = false; |
| | | private ScanSettingsCompat scanSettings; |
| | | private final List<ScanFilterCompat> scanFilterCompats = new CopyOnWriteArrayList<>(); |
| | | |
| | | public CycledLeScanner(Context context, long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, ScanCallbackCompat callbackCompat){ |
| | | this.mContext = context; |
| | | this.scanPeriod = scanPeriod; |
| | | this.betweenScanPeriod = betweenScanPeriod; |
| | | this.scanCallbackCompat = callbackCompat; |
| | | this.mBackgroundFlag = backgroundFlag; |
| | | this.mBluetoothUtils = BluetoothUtils.getInstance(context); |
| | | } |
| | | |
| | | /** |
| | | * invoke at the end of scan(every scan cycle over) |
| | | * @param scanOverListener |
| | | */ |
| | | public void setScanOverListener(ScanOverListener scanOverListener) { |
| | | this.scanOverListener = scanOverListener; |
| | | } |
| | | |
| | | /** |
| | | * add scan filter |
| | | * @param scanFilter |
| | | */ |
| | | public void addScanFilterCompats(ScanFilterCompat scanFilter){ |
| | | scanFilterCompats.add(scanFilter); |
| | | } |
| | | |
| | | public void setScanSettings(ScanSettingsCompat scanSettings) { |
| | | isSetScanSetting = true; |
| | | this.scanSettings = scanSettings; |
| | | } |
| | | |
| | | public void startOnceScan() { |
| | | isOnceScan = true; |
| | | scanLeDevice(true); |
| | | } |
| | | |
| | | /** |
| | | * pause or restart scan device cycle |
| | | * @param isPauseScan |
| | | */ |
| | | public void setPauseScan(boolean isPauseScan) { |
| | | this.isPauseScan = isPauseScan; |
| | | if (!isPauseScan){ |
| | | scanLeDevice(true); |
| | | }else { |
| | | scanLeDevice(false); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * start scan device |
| | | */ |
| | | public void startScan(){ |
| | | isPauseScan = false; |
| | | scanLeDevice(true); |
| | | } |
| | | |
| | | /** |
| | | * start scan device right now |
| | | */ |
| | | public void startScanNow(){ |
| | | isStartNow = true; |
| | | isPauseScan = false; |
| | | scanLeDevice(true); |
| | | } |
| | | |
| | | /** |
| | | * Tells the cycler the scan rate and whether it is in operating in background mode. |
| | | * Background mode flag is used only with the Android 5.0 scanning implementations to switch |
| | | * between LOW_POWER_MODE vs. LOW_LATENCY_MODE |
| | | * @param backgroundFlag is running background |
| | | */ |
| | | public void setBackgroundMode(long scanPeriod, long betweenScanPeriod, boolean backgroundFlag) { |
| | | if (android.os.Build.VERSION.SDK_INT < 18) { |
| | | LogUtils.w("Not supported prior to API 18. Method invocation will be ignored"); |
| | | return; |
| | | } |
| | | |
| | | this.scanPeriod = scanPeriod; |
| | | this.betweenScanPeriod = betweenScanPeriod; |
| | | if (backgroundFlag != mBackgroundFlag) { |
| | | LogUtils.d("restart polling task scanPeriod:" + scanPeriod + " betweenScanPeriod:" + betweenScanPeriod + " backgroundFlag:" + backgroundFlag + " mode:" + mBackgroundFlag); |
| | | mBackgroundFlag = backgroundFlag; |
| | | long now = SystemClock.elapsedRealtime(); |
| | | |
| | | //update next scan start timeï¼å¨çå¾
å¼å§æ«ææ¶ä¿®æ£ä¸ä¸æ¬¡å¼å§æ¶é´,æåå¼å§ï¼ |
| | | if (nextScanStartTime > now){ |
| | | long proposedNextScanStartTime = lastScanEndTime + betweenScanPeriod; |
| | | if (proposedNextScanStartTime < nextScanStartTime){ |
| | | LogUtils.d("Waiting...Adjusted nextScanStartTime to be" + (proposedNextScanStartTime - now) + " old:" + (nextScanStartTime - now)); |
| | | nextScanStartTime = proposedNextScanStartTime; |
| | | } |
| | | } |
| | | |
| | | //update current scan stop time(妿卿«æä¸åä¿®æ£æ¬æ¬¡çç»ææ¶é´,æåç»æ) |
| | | if (scanStopTime > now){ |
| | | long proposedStopTime = nextScanStartTime + scanPeriod; |
| | | if (proposedStopTime < scanStopTime){ |
| | | LogUtils.d("Scanning...Adjusted scanStopTime to be " + (proposedStopTime - now) + " old:" + (scanStopTime - now)); |
| | | scanStopTime = proposedStopTime; |
| | | } |
| | | } |
| | | |
| | | //set scan setting params |
| | | if (!isSetScanSetting || scanSettings == null){ |
| | | if (mBackgroundFlag) { |
| | | LogUtils.d("starting filtered scan in SCAN_MODE_LOW_POWER"); |
| | | scanSettings = (new ScanSettingsCompat.Builder().setScanMode(ScanSettingsCompat.SCAN_MODE_LOW_POWER)).build(); |
| | | } else { |
| | | LogUtils.d("starting non-filtered scan in SCAN_MODE_LOW_LATENCY"); |
| | | scanSettings = (new ScanSettingsCompat.Builder().setScanMode(ScanSettingsCompat.SCAN_MODE_LOW_LATENCY)).build(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | public boolean isScanning() { |
| | | return mScanning; |
| | | } |
| | | |
| | | public boolean isPauseScan() { |
| | | return isPauseScan; |
| | | } |
| | | |
| | | /** |
| | | * start or stop scan |
| | | * @param enable true-start scan right nowï¼false-stop scan |
| | | */ |
| | | private void scanLeDevice(boolean enable) { |
| | | BluetoothAdapter mAdapter = mBluetoothUtils.getBluetoothAdapter(); |
| | | if (mBluetoothUtils == null || !mBluetoothUtils.isBluetoothIsEnable()){ |
| | | LogUtils.e("ScanDevice: Scanning fail! BluetoothAdapter is null"); |
| | | return; |
| | | } |
| | | |
| | | |
| | | if (enable) { |
| | | //is delay scan |
| | | if (deferScanIfNeeded()){ |
| | | if (!isStartNow){ |
| | | return; |
| | | }else{ |
| | | LogUtils.i("ScanDevice: Scan right now!"); |
| | | isStartNow = false; |
| | | } |
| | | } |
| | | |
| | | if (mScanning) { |
| | | LogUtils.d("ScanDevice: Scanning is running now !"); |
| | | return; |
| | | } |
| | | LogUtils.d("ScanDevice: Starting Scanning scanPeriod:"+scanPeriod+", between:"+betweenScanPeriod); |
| | | mScanning = true; |
| | | if (!isPauseScan || isOnceScan){ |
| | | try { |
| | | if (android.os.Build.VERSION.SDK_INT < 23 || checkLocationPermission()) { |
| | | if (android.os.Build.VERSION.SDK_INT >= 23 && !isGpsProviderEnabled(mContext)){ |
| | | LogUtils.e("If SDK>=23, current SDK=" + android.os.Build.VERSION.SDK_INT+", Location info not open and can not scan any device!"); |
| | | scanCallbackCompat.onScanFailed(ScanCallbackCompat.SCAN_FAILED_LOCATION_CLOSE); |
| | | }else { |
| | | LogUtils.i("ScanDevice: Start scan..."); |
| | | BluetoothLeScannerCompat.startScan(mAdapter, scanFilterCompats, getScanSettings(), scanCallbackCompat); |
| | | } |
| | | }else{ |
| | | scanCallbackCompat.onScanFailed(ScanCallbackCompat.SCAN_FAILED_LOCATION_PERMISSION_FORBID); |
| | | LogUtils.e("If SDK>=23, current SDK="+android.os.Build.VERSION.SDK_INT+", Please check the location permission is enabled(ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION)"); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | LogUtils.e("Internal Android exception scanning for beacons "+e.toString()); |
| | | } |
| | | |
| | | if (isOnceScan){ |
| | | LogUtils.d("ScanDevice: Scanning once"); |
| | | isOnceScan = false; |
| | | } |
| | | }else{ |
| | | LogUtils.d("ScanDevice: Pause Scanning"); |
| | | } |
| | | scanStopTime = SystemClock.elapsedRealtime() + scanPeriod; |
| | | nextScanStartTime = scanStopTime + betweenScanPeriod; |
| | | scheduleScanStop(); |
| | | } else { |
| | | LogUtils.d("ScanDevice: Stopping Scan"); |
| | | stopScan(); |
| | | } |
| | | } |
| | | |
| | | private void scheduleScanStop(){ |
| | | // Stops scanning after a pre-defined scan period. |
| | | long millisecondsUntilStop = scanStopTime - SystemClock.elapsedRealtime(); |
| | | if (millisecondsUntilStop > 0) { |
| | | if (isPrintCycleTime){ |
| | | LogUtils.d("Waiting to stop scan cycle for another " + millisecondsUntilStop + " milliseconds"); |
| | | } |
| | | |
| | | if (!isPauseScan) { |
| | | mHandler.postDelayed(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | scheduleScanStop(); |
| | | } |
| | | }, millisecondsUntilStop > 1000 ? 1000 : millisecondsUntilStop); |
| | | } |
| | | } else { |
| | | LogUtils.d("Stop cycle scan"); |
| | | stopScan(); |
| | | } |
| | | } |
| | | |
| | | private void stopScan(){ |
| | | if (mScanning) { |
| | | BluetoothAdapter mAdapter = mBluetoothUtils.getBluetoothAdapter(); |
| | | if (mAdapter != null && mBluetoothUtils.isBluetoothIsEnable()) { |
| | | try { |
| | | BluetoothLeScannerCompat.stopScan(mAdapter, scanCallbackCompat); |
| | | lastScanEndTime = SystemClock.elapsedRealtime(); |
| | | LogUtils.d("stopping bluetooth le scan "+lastScanEndTime); |
| | | } catch (Exception e) { |
| | | LogUtils.w("Internal Android exception scanning for beacons "+e.toString()); |
| | | } |
| | | } else { |
| | | LogUtils.d("Bluetooth is disabled. Cannot scan for beacons."); |
| | | } |
| | | nextScanStartTime = SystemClock.elapsedRealtime() + betweenScanPeriod; |
| | | //start next scan cycle |
| | | if (!isPauseScan){ |
| | | scanLeDevice(true); |
| | | } |
| | | } |
| | | mScanning = false; |
| | | //// FIXME: 2017/6/22 å°å
¶è°æ´å°mScanningåé¢ |
| | | if (scanOverListener != null){ |
| | | scanOverListener.onScanOver(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * check is defter scan |
| | | * @return |
| | | */ |
| | | private boolean deferScanIfNeeded(){ |
| | | long millisecondsUntilStart = nextScanStartTime - SystemClock.elapsedRealtime(); |
| | | if (millisecondsUntilStart > 0) { |
| | | if (isPrintCycleTime){ |
| | | LogUtils.d("Waiting to start next Bluetooth scan for another "+millisecondsUntilStart+" milliseconds"); |
| | | } |
| | | // Don't actually wait until the next scan time -- only wait up to 1 second. This |
| | | // allows us to start scanning sooner if a consumer enters the foreground and expects |
| | | // results more quickly. |
| | | mHandler.postDelayed(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | if (!isPauseScan) { |
| | | scanLeDevice(true); |
| | | } |
| | | } |
| | | }, millisecondsUntilStart > 1000 ? 1000 : millisecondsUntilStart); |
| | | return true; |
| | | } |
| | | LogUtils.d("Start cycle scan"); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * get scan settings |
| | | * @return |
| | | */ |
| | | private ScanSettingsCompat getScanSettings() { |
| | | if (scanSettings == null){ |
| | | if (mBackgroundFlag) { |
| | | LogUtils.d("starting filtered scan in SCAN_MODE_LOW_POWER"); |
| | | scanSettings = (new ScanSettingsCompat.Builder().setScanMode(ScanSettingsCompat.SCAN_MODE_LOW_POWER)).build(); |
| | | } else { |
| | | LogUtils.d("starting non-filtered scan in SCAN_MODE_LOW_LATENCY"); |
| | | scanSettings = (new ScanSettingsCompat.Builder().setScanMode(ScanSettingsCompat.SCAN_MODE_LOW_LATENCY)).build(); |
| | | } |
| | | } |
| | | return scanSettings; |
| | | } |
| | | |
| | | /** |
| | | * is open GPS |
| | | * @param context |
| | | * @return |
| | | */ |
| | | public static boolean isGpsProviderEnabled(Context context){ |
| | | LocationManager service = (LocationManager) context.getSystemService(context.LOCATION_SERVICE); |
| | | return service.isProviderEnabled(LocationManager.GPS_PROVIDER); |
| | | } |
| | | |
| | | /** |
| | | * when API>=23, if the location disabled, can not scan any devices |
| | | * @return |
| | | */ |
| | | private boolean checkLocationPermission() { |
| | | return checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION) || checkPermission(Manifest.permission.ACCESS_FINE_LOCATION); |
| | | } |
| | | |
| | | private boolean checkPermission(final String permission) { |
| | | return ContextCompat.checkSelfPermission(mContext, permission) == PackageManager.PERMISSION_GRANTED; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.blakequ.bluetooth_manager_lib.scan; |
| | | |
| | | /** |
| | | * Copyright (C) BlakeQu All Rights Reserved <blakequ@gmail.com> |
| | | * <p/> |
| | | * Licensed under the blakequ.com License, Version 1.0 (the "License"); |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | * <p/> |
| | | * author : quhao <blakequ@gmail.com> <br> |
| | | * date : 2016/8/18 15:35 <br> |
| | | * last modify author : <br> |
| | | * version : 1.0 <br> |
| | | * description:when once scan at the cycle end |
| | | */ |
| | | public interface ScanOverListener { |
| | | void onScanOver(); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /* |
| | | * Copyright (C) 2015 Joe Rogers |
| | | * |
| | | * Licensed under the Apache License, Version 2.0 (the "License"); |
| | | * you may not use this file except in compliance with the License. |
| | | * You may obtain a copy of the License at |
| | | * |
| | | * http://www.apache.org/licenses/LICENSE-2.0 |
| | | * |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | */ |
| | | package com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat; |
| | | |
| | | import android.annotation.TargetApi; |
| | | import android.bluetooth.BluetoothAdapter; |
| | | import android.bluetooth.BluetoothDevice; |
| | | import android.bluetooth.le.ScanCallback; |
| | | import android.bluetooth.le.ScanFilter; |
| | | import android.bluetooth.le.ScanResult; |
| | | import android.bluetooth.le.ScanSettings; |
| | | import android.os.Build; |
| | | import androidx.annotation.NonNull; |
| | | import androidx.annotation.Nullable; |
| | | import androidx.annotation.RequiresPermission; |
| | | import androidx.collection.SimpleArrayMap; |
| | | |
| | | import com.blakequ.bluetooth_manager_lib.scan.BluetoothScanManager; |
| | | |
| | | import java.lang.ref.WeakReference; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | |
| | | public class BluetoothLeScannerCompat { |
| | | |
| | | private static final String TAG = "BLeScannerCompat"; |
| | | |
| | | @RequiresPermission("android.permission.BLUETOOTH_ADMIN") |
| | | public static void flushPendingScanResults(@NonNull BluetoothAdapter adapter, @NonNull ScanCallbackCompat callbackCompat) { |
| | | IMPL.flushPendingScanResults(adapter, callbackCompat); |
| | | } |
| | | |
| | | @RequiresPermission("android.permission.BLUETOOTH_ADMIN") |
| | | public static void startScan(@NonNull BluetoothAdapter adapter, @Nullable List<ScanFilterCompat> filters, @NonNull ScanSettingsCompat settings, @NonNull ScanCallbackCompat callbackCompat) { |
| | | IMPL.startScan(adapter, filters, settings, callbackCompat); |
| | | } |
| | | |
| | | @RequiresPermission("android.permission.BLUETOOTH_ADMIN") |
| | | public static void startScan(@NonNull BluetoothAdapter adapter, @NonNull ScanCallbackCompat callbackCompat) { |
| | | IMPL.startScan(adapter, callbackCompat); |
| | | } |
| | | |
| | | @RequiresPermission("android.permission.BLUETOOTH_ADMIN") |
| | | public static void stopScan(@NonNull BluetoothAdapter adapter, @NonNull ScanCallbackCompat callbackCompat) { |
| | | IMPL.stopScan(adapter, callbackCompat); |
| | | } |
| | | |
| | | static final BluetoothLeScannerCompatImpl IMPL; |
| | | |
| | | static { |
| | | final int version = Build.VERSION.SDK_INT; |
| | | if (BluetoothScanManager.isAPI21ScanningDisabled()){ |
| | | IMPL = new API18BluetoothLeScannerCompatImpl(); |
| | | }else{ |
| | | if (version >= Build.VERSION_CODES.LOLLIPOP) { |
| | | IMPL = new API21BluetoothLeScannerCompatImpl(); |
| | | }else { |
| | | IMPL = new API18BluetoothLeScannerCompatImpl(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | interface BluetoothLeScannerCompatImpl { |
| | | void flushPendingScanResults(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat); |
| | | |
| | | void startScan(BluetoothAdapter adapter, List<ScanFilterCompat> filters, ScanSettingsCompat settings, ScanCallbackCompat callbackCompat); |
| | | |
| | | void startScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat); |
| | | |
| | | void stopScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat); |
| | | } |
| | | |
| | | @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
| | | static class API21BluetoothLeScannerCompatImpl implements BluetoothLeScannerCompatImpl { |
| | | static final SimpleArrayMap<ScanCallbackCompat, API21ScanCallback> callbackMap = new SimpleArrayMap<>(); |
| | | |
| | | @Override |
| | | public void flushPendingScanResults(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) { |
| | | API21ScanCallback result = callbackMap.get(callbackCompat); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | adapter.getBluetoothLeScanner().flushPendingScanResults(result); |
| | | } |
| | | |
| | | @Override |
| | | public void startScan(BluetoothAdapter adapter, List<ScanFilterCompat> filters, ScanSettingsCompat settings, ScanCallbackCompat callbackCompat) { |
| | | |
| | | List<ScanFilter> scanFilters = null; |
| | | if (filters != null) { |
| | | scanFilters = new ArrayList<>(filters.size()); |
| | | |
| | | for (ScanFilterCompat filter : filters) { |
| | | scanFilters.add(filter.toApi21()); |
| | | } |
| | | } |
| | | if (settings == null) { |
| | | throw new IllegalStateException("Scan settings are null"); |
| | | } |
| | | ScanSettings scanSettings = settings.toApi21(); |
| | | adapter.getBluetoothLeScanner().startScan(scanFilters, scanSettings, registerCallback(callbackCompat)); |
| | | } |
| | | |
| | | @Override |
| | | public void startScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) { |
| | | adapter.getBluetoothLeScanner().startScan(registerCallback(callbackCompat)); |
| | | } |
| | | |
| | | @Override |
| | | public void stopScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) { |
| | | API21ScanCallback result = callbackMap.remove(callbackCompat); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | adapter.getBluetoothLeScanner().stopScan(result); |
| | | } |
| | | |
| | | private API21ScanCallback registerCallback(ScanCallbackCompat callbackCompat) { |
| | | API21ScanCallback result = callbackMap.get(callbackCompat); |
| | | // Attempting to rescan, just let it fail deeper down. |
| | | if (result != null) { |
| | | return result; |
| | | } |
| | | result = new API21ScanCallback(callbackCompat); |
| | | callbackMap.put(callbackCompat, result); |
| | | return result; |
| | | } |
| | | } |
| | | |
| | | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) |
| | | static class API18BluetoothLeScannerCompatImpl implements BluetoothLeScannerCompatImpl { |
| | | static final SimpleArrayMap<ScanCallbackCompat, API18ScanCallback> callbackMap = new SimpleArrayMap<>(); |
| | | |
| | | @Override |
| | | public void flushPendingScanResults(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) { |
| | | // no matching api |
| | | new IllegalArgumentException("this method can not found"); |
| | | } |
| | | |
| | | |
| | | @SuppressWarnings("deprecation") |
| | | @Override |
| | | public void startScan(BluetoothAdapter adapter, List<ScanFilterCompat> filters, ScanSettingsCompat settings, ScanCallbackCompat callbackCompat) { |
| | | adapter.startLeScan(registerCallback(filters, callbackCompat)); |
| | | } |
| | | |
| | | @SuppressWarnings("deprecation") |
| | | @Override |
| | | public void startScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) { |
| | | adapter.startLeScan(registerCallback(null, callbackCompat)); |
| | | } |
| | | |
| | | @SuppressWarnings("deprecation") |
| | | @Override |
| | | public void stopScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) { |
| | | API18ScanCallback callback = callbackMap.remove(callbackCompat); |
| | | if (callback == null) { |
| | | return; |
| | | } |
| | | adapter.stopLeScan(callback); |
| | | } |
| | | |
| | | private API18ScanCallback registerCallback(List<ScanFilterCompat> filters, ScanCallbackCompat callbackCompat) { |
| | | API18ScanCallback result = callbackMap.get(callbackCompat); |
| | | // Attempting to rescan, just let it fail deeper down. |
| | | if (result != null) { |
| | | return result; |
| | | } |
| | | result = new API18ScanCallback(filters, callbackCompat); |
| | | callbackMap.put(callbackCompat, result); |
| | | return result; |
| | | } |
| | | } |
| | | |
| | | static class API18ScanCallback implements BluetoothAdapter.LeScanCallback { |
| | | |
| | | private final List<ScanFilterCompat> filters; |
| | | private final WeakReference<ScanCallbackCompat> callbackCompatRef; |
| | | |
| | | API18ScanCallback(List<ScanFilterCompat> filters, ScanCallbackCompat callbackCompat) { |
| | | this.filters = filters; |
| | | this.callbackCompatRef = new WeakReference<>(callbackCompat); |
| | | } |
| | | |
| | | @Override |
| | | public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { |
| | | ScanCallbackCompat callbackCompat = callbackCompatRef.get(); |
| | | if (callbackCompat == null) return; |
| | | |
| | | ScanResultCompat result = new ScanResultCompat( |
| | | device, |
| | | ScanRecordCompat.parseFromBytes(scanRecord), |
| | | rssi, System.currentTimeMillis()); |
| | | |
| | | // No filters so return any result |
| | | if (filters == null) { |
| | | callbackCompat.onScanResult(ScanSettingsCompat.CALLBACK_TYPE_ALL_MATCHES, result); |
| | | return; |
| | | } |
| | | |
| | | // Filters specified, so see if there is a match. |
| | | for (ScanFilterCompat filter : filters) { |
| | | if (filter.matches(result)) { |
| | | callbackCompat.onScanResult(ScanSettingsCompat.CALLBACK_TYPE_ALL_MATCHES, result); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
| | | static class API21ScanCallback extends ScanCallback { |
| | | |
| | | private final WeakReference<ScanCallbackCompat> callbackCompatRef; |
| | | |
| | | API21ScanCallback(ScanCallbackCompat callbackCompat) { |
| | | this.callbackCompatRef = new WeakReference<>(callbackCompat); |
| | | } |
| | | |
| | | @Override |
| | | public void onScanResult(int callbackType, ScanResult result) { |
| | | ScanCallbackCompat callbackCompat = callbackCompatRef.get(); |
| | | if (callbackCompat != null) { |
| | | callbackCompat.onScanResult(callbackType, new ScanResultCompat(result)); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onBatchScanResults(List<ScanResult> results) { |
| | | ScanCallbackCompat callbackCompat = callbackCompatRef.get(); |
| | | if (callbackCompat == null) return; |
| | | List<ScanResultCompat> compatResults = new ArrayList<>(results.size()); |
| | | for (ScanResult result : results) { |
| | | compatResults.add(new ScanResultCompat(result)); |
| | | } |
| | | callbackCompat.onBatchScanResults(compatResults); |
| | | } |
| | | |
| | | @Override |
| | | public void onScanFailed(int errorCode) { |
| | | ScanCallbackCompat callbackCompat = callbackCompatRef.get(); |
| | | if (callbackCompat != null) { |
| | | callbackCompat.onScanFailed(errorCode); |
| | | } |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /* |
| | | * Copyright (C) 2009 The Android Open Source Project |
| | | * Copyright (C) 2015 Joe Rogers |
| | | * |
| | | * Licensed under the Apache License, Version 2.0 (the "License"); |
| | | * you may not use this file except in compliance with the License. |
| | | * You may obtain a copy of the License at |
| | | * |
| | | * http://www.apache.org/licenses/LICENSE-2.0 |
| | | * |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | */ |
| | | |
| | | package com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat; |
| | | |
| | | import android.os.ParcelUuid; |
| | | |
| | | import java.nio.ByteBuffer; |
| | | import java.nio.ByteOrder; |
| | | import java.util.Arrays; |
| | | import java.util.HashSet; |
| | | import java.util.UUID; |
| | | |
| | | /** |
| | | * Compat version of BluetoothUuid |
| | | */ |
| | | /* package */ class BluetoothUuidCompat { |
| | | |
| | | /* See Bluetooth Assigned Numbers document - SDP section, to get the values of UUIDs |
| | | * for the various services. |
| | | * |
| | | * The following 128 bit values are calculated as: |
| | | * uuid * 2^96 + BASE_UUID |
| | | */ |
| | | public static final ParcelUuid AudioSink = |
| | | ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid AudioSource = |
| | | ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid AdvAudioDist = |
| | | ParcelUuid.fromString("0000110D-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid HSP = |
| | | ParcelUuid.fromString("00001108-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid HSP_AG = |
| | | ParcelUuid.fromString("00001112-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid Handsfree = |
| | | ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid Handsfree_AG = |
| | | ParcelUuid.fromString("0000111F-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid AvrcpController = |
| | | ParcelUuid.fromString("0000110E-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid AvrcpTarget = |
| | | ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid ObexObjectPush = |
| | | ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb"); |
| | | public static final ParcelUuid Hid = |
| | | ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb"); |
| | | public static final ParcelUuid Hogp = |
| | | ParcelUuid.fromString("00001812-0000-1000-8000-00805f9b34fb"); |
| | | public static final ParcelUuid PANU = |
| | | ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid NAP = |
| | | ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid BNEP = |
| | | ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid PBAP_PSE = |
| | | ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid MAP = |
| | | ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid MNS = |
| | | ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB"); |
| | | public static final ParcelUuid MAS = |
| | | ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB"); |
| | | |
| | | public static final ParcelUuid BASE_UUID = |
| | | ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); |
| | | |
| | | /** |
| | | * Length of bytes for 16 bit UUID |
| | | */ |
| | | public static final int UUID_BYTES_16_BIT = 2; |
| | | /** |
| | | * Length of bytes for 32 bit UUID |
| | | */ |
| | | public static final int UUID_BYTES_32_BIT = 4; |
| | | /** |
| | | * Length of bytes for 128 bit UUID |
| | | */ |
| | | public static final int UUID_BYTES_128_BIT = 16; |
| | | |
| | | public static final ParcelUuid[] RESERVED_UUIDS = { |
| | | AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget, |
| | | ObexObjectPush, PANU, NAP, MAP, MNS, MAS}; |
| | | |
| | | public static boolean isAudioSource(ParcelUuid uuid) { |
| | | return uuid.equals(AudioSource); |
| | | } |
| | | |
| | | public static boolean isAudioSink(ParcelUuid uuid) { |
| | | return uuid.equals(AudioSink); |
| | | } |
| | | |
| | | public static boolean isAdvAudioDist(ParcelUuid uuid) { |
| | | return uuid.equals(AdvAudioDist); |
| | | } |
| | | |
| | | public static boolean isHandsfree(ParcelUuid uuid) { |
| | | return uuid.equals(Handsfree); |
| | | } |
| | | |
| | | public static boolean isHeadset(ParcelUuid uuid) { |
| | | return uuid.equals(HSP); |
| | | } |
| | | |
| | | public static boolean isAvrcpController(ParcelUuid uuid) { |
| | | return uuid.equals(AvrcpController); |
| | | } |
| | | |
| | | public static boolean isAvrcpTarget(ParcelUuid uuid) { |
| | | return uuid.equals(AvrcpTarget); |
| | | } |
| | | |
| | | public static boolean isInputDevice(ParcelUuid uuid) { |
| | | return uuid.equals(Hid); |
| | | } |
| | | |
| | | public static boolean isPanu(ParcelUuid uuid) { |
| | | return uuid.equals(PANU); |
| | | } |
| | | |
| | | public static boolean isNap(ParcelUuid uuid) { |
| | | return uuid.equals(NAP); |
| | | } |
| | | |
| | | public static boolean isBnep(ParcelUuid uuid) { |
| | | return uuid.equals(BNEP); |
| | | } |
| | | |
| | | public static boolean isMap(ParcelUuid uuid) { |
| | | return uuid.equals(MAP); |
| | | } |
| | | |
| | | public static boolean isMns(ParcelUuid uuid) { |
| | | return uuid.equals(MNS); |
| | | } |
| | | |
| | | public static boolean isMas(ParcelUuid uuid) { |
| | | return uuid.equals(MAS); |
| | | } |
| | | |
| | | /** |
| | | * Returns true if ParcelUuid is present in uuidArray |
| | | * |
| | | * @param uuidArray - Array of ParcelUuids |
| | | * @param uuid |
| | | */ |
| | | public static boolean isUuidPresent(ParcelUuid[] uuidArray, ParcelUuid uuid) { |
| | | if ((uuidArray == null || uuidArray.length == 0) && uuid == null) |
| | | return true; |
| | | |
| | | if (uuidArray == null) |
| | | return false; |
| | | |
| | | for (ParcelUuid element : uuidArray) { |
| | | if (element.equals(uuid)) return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * Returns true if there any common ParcelUuids in uuidA and uuidB. |
| | | * |
| | | * @param uuidA - List of ParcelUuids |
| | | * @param uuidB - List of ParcelUuids |
| | | */ |
| | | public static boolean containsAnyUuid(ParcelUuid[] uuidA, ParcelUuid[] uuidB) { |
| | | if (uuidA == null && uuidB == null) return true; |
| | | |
| | | if (uuidA == null) { |
| | | return uuidB.length == 0; |
| | | } |
| | | |
| | | if (uuidB == null) { |
| | | return uuidA.length == 0; |
| | | } |
| | | |
| | | HashSet<ParcelUuid> uuidSet = new HashSet<>(Arrays.asList(uuidA)); |
| | | for (ParcelUuid uuid : uuidB) { |
| | | if (uuidSet.contains(uuid)) return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * Returns true if all the ParcelUuids in ParcelUuidB are present in |
| | | * ParcelUuidA |
| | | * |
| | | * @param uuidA - Array of ParcelUuidsA |
| | | * @param uuidB - Array of ParcelUuidsB |
| | | */ |
| | | public static boolean containsAllUuids(ParcelUuid[] uuidA, ParcelUuid[] uuidB) { |
| | | if (uuidA == null && uuidB == null) return true; |
| | | |
| | | if (uuidA == null) { |
| | | return uuidB.length == 0; |
| | | } |
| | | |
| | | if (uuidB == null) return true; |
| | | |
| | | HashSet<ParcelUuid> uuidSet = new HashSet<>(Arrays.asList(uuidA)); |
| | | for (ParcelUuid uuid : uuidB) { |
| | | if (!uuidSet.contains(uuid)) return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Extract the Service Identifier or the actual uuid from the Parcel Uuid. |
| | | * For example, if 0000110B-0000-1000-8000-00805F9B34FB is the parcel Uuid, |
| | | * this function will return 110B |
| | | * |
| | | * @param parcelUuid |
| | | * @return the service identifier. |
| | | */ |
| | | public static int getServiceIdentifierFromParcelUuid(ParcelUuid parcelUuid) { |
| | | UUID uuid = parcelUuid.getUuid(); |
| | | long value = (uuid.getMostSignificantBits() & 0x0000FFFF00000000L) >>> 32; |
| | | return (int) value; |
| | | } |
| | | |
| | | /** |
| | | * Parse UUID from bytes. The {@code uuidBytes} can represent a 16-bit, 32-bit or 128-bit UUID, |
| | | * but the returned UUID is always in 128-bit format. |
| | | * Note UUID is little endian in Bluetooth. |
| | | * |
| | | * @param uuidBytes Byte representation of uuid. |
| | | * @return {@link ParcelUuid} parsed from bytes. |
| | | * @throws IllegalArgumentException If the {@code uuidBytes} cannot be parsed. |
| | | */ |
| | | public static ParcelUuid parseUuidFrom(byte[] uuidBytes) { |
| | | if (uuidBytes == null) { |
| | | throw new IllegalArgumentException("uuidBytes cannot be null"); |
| | | } |
| | | int length = uuidBytes.length; |
| | | if (length != UUID_BYTES_16_BIT && length != UUID_BYTES_32_BIT && |
| | | length != UUID_BYTES_128_BIT) { |
| | | throw new IllegalArgumentException("uuidBytes length invalid - " + length); |
| | | } |
| | | |
| | | // Construct a 128 bit UUID. |
| | | if (length == UUID_BYTES_128_BIT) { |
| | | ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN); |
| | | long msb = buf.getLong(8); |
| | | long lsb = buf.getLong(0); |
| | | return new ParcelUuid(new UUID(msb, lsb)); |
| | | } |
| | | |
| | | // For 16 bit and 32 bit UUID we need to convert them to 128 bit value. |
| | | // 128_bit_value = uuid * 2^96 + BASE_UUID |
| | | long shortUuid; |
| | | if (length == UUID_BYTES_16_BIT) { |
| | | shortUuid = uuidBytes[0] & 0xFF; |
| | | shortUuid += (uuidBytes[1] & 0xFF) << 8; |
| | | } |
| | | else { |
| | | shortUuid = uuidBytes[0] & 0xFF; |
| | | shortUuid += (uuidBytes[1] & 0xFF) << 8; |
| | | shortUuid += (uuidBytes[2] & 0xFF) << 16; |
| | | shortUuid += (uuidBytes[3] & 0xFF) << 24; |
| | | } |
| | | long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32); |
| | | long lsb = BASE_UUID.getUuid().getLeastSignificantBits(); |
| | | return new ParcelUuid(new UUID(msb, lsb)); |
| | | } |
| | | |
| | | /** |
| | | * Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid. |
| | | * |
| | | * @param parcelUuid |
| | | * @return true if the parcelUuid can be converted to 16 bit uuid, false otherwise. |
| | | */ |
| | | public static boolean is16BitUuid(ParcelUuid parcelUuid) { |
| | | UUID uuid = parcelUuid.getUuid(); |
| | | if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { |
| | | return false; |
| | | } |
| | | return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Check whether the given parcelUuid can be converted to 32 bit bluetooth uuid. |
| | | * |
| | | * @param parcelUuid |
| | | * @return true if the parcelUuid can be converted to 32 bit uuid, false otherwise. |
| | | */ |
| | | public static boolean is32BitUuid(ParcelUuid parcelUuid) { |
| | | UUID uuid = parcelUuid.getUuid(); |
| | | if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { |
| | | return false; |
| | | } |
| | | if (is16BitUuid(parcelUuid)) { |
| | | return false; |
| | | } |
| | | return ((uuid.getMostSignificantBits() & 0xFFFFFFFFL) == 0x1000L); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /* |
| | | * Copyright (C) 2013 The Android Open Source Project |
| | | * Copyright (C) 2015 Joe Rogers |
| | | * |
| | | * Licensed under the Apache License, Version 2.0 (the "License"); |
| | | * you may not use this file except in compliance with the License. |
| | | * You may obtain a copy of the License at |
| | | * |
| | | * http://www.apache.org/licenses/LICENSE-2.0 |
| | | * |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | */ |
| | | |
| | | package com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * Subset of utility methods for objects, used by the existing bluetooth framework |
| | | */ |
| | | /* package */ class ObjectsCompat { |
| | | |
| | | /* package */ static String toString(Object o) { |
| | | return (o == null) ? "null" : o.toString(); |
| | | } |
| | | |
| | | /* package */ static int hash(Object... values) { |
| | | return Arrays.hashCode(values); |
| | | } |
| | | |
| | | /* package */ static boolean equals(Object a, Object b) { |
| | | return (a == null) ? (b == null) : a.equals(b); |
| | | } |
| | | |
| | | /* package */ static boolean deepEquals(Object a, Object b) { |
| | | if (a == null || b == null) { |
| | | return a == b; |
| | | } |
| | | else if (a instanceof Object[] && b instanceof Object[]) { |
| | | return Arrays.deepEquals((Object[]) a, (Object[]) b); |
| | | } |
| | | else if (a instanceof boolean[] && b instanceof boolean[]) { |
| | | return Arrays.equals((boolean[]) a, (boolean[]) b); |
| | | } |
| | | else if (a instanceof byte[] && b instanceof byte[]) { |
| | | return Arrays.equals((byte[]) a, (byte[]) b); |
| | | } |
| | | else if (a instanceof char[] && b instanceof char[]) { |
| | | return Arrays.equals((char[]) a, (char[]) b); |
| | | } |
| | | else if (a instanceof double[] && b instanceof double[]) { |
| | | return Arrays.equals((double[]) a, (double[]) b); |
| | | } |
| | | else if (a instanceof float[] && b instanceof float[]) { |
| | | return Arrays.equals((float[]) a, (float[]) b); |
| | | } |
| | | else if (a instanceof int[] && b instanceof int[]) { |
| | | return Arrays.equals((int[]) a, (int[]) b); |
| | | } |
| | | else if (a instanceof long[] && b instanceof long[]) { |
| | | return Arrays.equals((long[]) a, (long[]) b); |
| | | } |
| | | else if (a instanceof short[] && b instanceof short[]) { |
| | | return Arrays.equals((short[]) a, (short[]) b); |
| | | } |
| | | return a.equals(b); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /* |
| | | * Copyright (C) 2014 The Android Open Source Project |
| | | * Copyright (C) 2015 Joe Rogers |
| | | * |
| | | * Licensed under the Apache License, Version 2.0 (the "License"); |
| | | * you may not use this file except in compliance with the License. |
| | | * You may obtain a copy of the License at |
| | | * |
| | | * http://www.apache.org/licenses/LICENSE-2.0 |
| | | * |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | */ |
| | | package com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * Bluetooth LE scan callbacks. Scan results are reported using these callbacks. |
| | | * |
| | | * @see BluetoothLeScannerCompat#startScan |
| | | */ |
| | | |
| | | public abstract class ScanCallbackCompat { |
| | | /** |
| | | * Fails to start scan as BLE scan with the same settings is already started by the app. |
| | | */ |
| | | public static final int SCAN_FAILED_ALREADY_STARTED = 1; |
| | | |
| | | /** |
| | | * Fails to start scan as app cannot be registered. |
| | | */ |
| | | public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2; |
| | | |
| | | /** |
| | | * Fails to start scan due an internal error |
| | | */ |
| | | public static final int SCAN_FAILED_INTERNAL_ERROR = 3; |
| | | |
| | | /** |
| | | * Fails to start power optimized scan as this feature is not supported. |
| | | */ |
| | | public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 4; |
| | | |
| | | /** |
| | | * new error code, Fails to scan as location permission is forbid() |
| | | */ |
| | | public static final int SCAN_FAILED_LOCATION_PERMISSION_FORBID = 5; |
| | | |
| | | /** |
| | | * use SDK>=23, The location is not open(if sdk>=23, will not scan any device) |
| | | * http://stackoverflow.com/questions/33043582/bluetooth-low-energy-startscan-on-android-6-0-does-not-find-devices/33045489#33045489 |
| | | */ |
| | | public static final int SCAN_FAILED_LOCATION_CLOSE = 6; |
| | | |
| | | /** |
| | | * Callback when a BLE advertisement has been found. |
| | | * |
| | | * @param callbackType Determines how this callback was triggered. Currently could only be |
| | | * {@link android.bluetooth.le.ScanSettings#CALLBACK_TYPE_ALL_MATCHES}. |
| | | * @param result A Bluetooth LE scan result. |
| | | */ |
| | | @SuppressWarnings("EmptyMethod") |
| | | public void onScanResult(int callbackType, ScanResultCompat result) { |
| | | // no implementation |
| | | } |
| | | |
| | | /** |
| | | * Callback when batch results are delivered. |
| | | * |
| | | * @param results List of scan results that are previously scanned. |
| | | */ |
| | | @SuppressWarnings("EmptyMethod") |
| | | public void onBatchScanResults(List<ScanResultCompat> results) { |
| | | } |
| | | |
| | | /** |
| | | * Callback when scan could not be started. |
| | | * |
| | | * @param errorCode Error code (one of SCAN_FAILED_*) for scan failure. |
| | | */ |
| | | @SuppressWarnings("EmptyMethod") |
| | | public void onScanFailed(int errorCode) { |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /* |
| | | * Copyright (C) 2014 The Android Open Source Project |
| | | * Copyright (C) 2015 Joe Rogers |
| | | * |
| | | * Licensed under the Apache License, Version 2.0 (the "License"); |
| | | * you may not use this file except in compliance with the License. |
| | | * You may obtain a copy of the License at |
| | | * |
| | | * http://www.apache.org/licenses/LICENSE-2.0 |
| | | * |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | */ |
| | | |
| | | package com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat; |
| | | |
| | | import android.annotation.TargetApi; |
| | | import android.bluetooth.BluetoothAdapter; |
| | | import android.bluetooth.BluetoothDevice; |
| | | import android.bluetooth.le.ScanFilter; |
| | | import android.bluetooth.le.ScanResult; |
| | | import android.os.Build; |
| | | import android.os.Parcel; |
| | | import android.os.ParcelUuid; |
| | | import android.os.Parcelable; |
| | | import androidx.annotation.Nullable; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.UUID; |
| | | |
| | | /** |
| | | * Criteria for filtering result from Bluetooth LE scans. A {@link ScanFilterCompat} allows clients to |
| | | * restrict scan results to only those that are of interest to them. |
| | | * <p/> |
| | | * Current filtering on the following fields are supported: |
| | | * <li>Service UUIDs which identify the bluetooth gatt services running on the device. |
| | | * <li>Name of remote Bluetooth LE device. |
| | | * <li>Mac address of the remote device. |
| | | * <li>Service data which is the data associated with a service. |
| | | * <li>Manufacturer specific data which is the data associated with a particular manufacturer. |
| | | * |
| | | * @see ScanResult |
| | | * @see android.bluetooth.le.BluetoothLeScanner |
| | | */ |
| | | public class ScanFilterCompat implements Parcelable { |
| | | @Nullable |
| | | private final String mDeviceName; |
| | | |
| | | @Nullable |
| | | private final String mDeviceAddress; |
| | | |
| | | @Nullable |
| | | private final ParcelUuid mServiceUuid; |
| | | @Nullable |
| | | private final ParcelUuid mServiceUuidMask; |
| | | |
| | | @Nullable |
| | | private final ParcelUuid mServiceDataUuid; |
| | | @Nullable |
| | | private final byte[] mServiceData; |
| | | @Nullable |
| | | private final byte[] mServiceDataMask; |
| | | |
| | | private final int mManufacturerId; |
| | | @Nullable |
| | | private final byte[] mManufacturerData; |
| | | @Nullable |
| | | private final byte[] mManufacturerDataMask; |
| | | private static final ScanFilterCompat EMPTY = new ScanFilterCompat.Builder().build() ; |
| | | |
| | | private ScanFilterCompat(@Nullable String name, @Nullable String deviceAddress, @Nullable ParcelUuid uuid, |
| | | @Nullable ParcelUuid uuidMask, @Nullable ParcelUuid serviceDataUuid, |
| | | @Nullable byte[] serviceData, @Nullable byte[] serviceDataMask, |
| | | int manufacturerId, @Nullable byte[] manufacturerData, @Nullable byte[] manufacturerDataMask) { |
| | | mDeviceName = name; |
| | | mServiceUuid = uuid; |
| | | mServiceUuidMask = uuidMask; |
| | | mDeviceAddress = deviceAddress; |
| | | mServiceDataUuid = serviceDataUuid; |
| | | mServiceData = serviceData; |
| | | mServiceDataMask = serviceDataMask; |
| | | mManufacturerId = manufacturerId; |
| | | mManufacturerData = manufacturerData; |
| | | mManufacturerDataMask = manufacturerDataMask; |
| | | } |
| | | |
| | | @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
| | | public ScanFilter toApi21() { |
| | | ScanFilter.Builder builder = new ScanFilter.Builder(); |
| | | if (mDeviceName != null) { |
| | | builder.setDeviceName(mDeviceName); |
| | | } |
| | | if (mServiceUuid != null) { |
| | | builder.setServiceUuid(mServiceUuid, mServiceUuidMask); |
| | | } |
| | | if (mDeviceAddress != null) { |
| | | builder.setDeviceAddress(mDeviceAddress); |
| | | } |
| | | if (mServiceDataUuid != null) { |
| | | builder.setServiceData(mServiceDataUuid, mServiceData, mServiceDataMask); |
| | | } |
| | | if (mManufacturerId < 0) { |
| | | builder.setManufacturerData(mManufacturerId, mManufacturerData, mManufacturerDataMask); |
| | | } |
| | | return builder.build(); |
| | | } |
| | | |
| | | @Override |
| | | public int describeContents() { |
| | | return 0; |
| | | } |
| | | |
| | | @Override |
| | | public void writeToParcel(Parcel dest, int flags) { |
| | | dest.writeInt(mDeviceName == null ? 0 : 1); |
| | | if (mDeviceName != null) { |
| | | dest.writeString(mDeviceName); |
| | | } |
| | | dest.writeInt(mDeviceAddress == null ? 0 : 1); |
| | | if (mDeviceAddress != null) { |
| | | dest.writeString(mDeviceAddress); |
| | | } |
| | | dest.writeInt(mServiceUuid == null ? 0 : 1); |
| | | if (mServiceUuid != null) { |
| | | dest.writeParcelable(mServiceUuid, flags); |
| | | dest.writeInt(mServiceUuidMask == null ? 0 : 1); |
| | | if (mServiceUuidMask != null) { |
| | | dest.writeParcelable(mServiceUuidMask, flags); |
| | | } |
| | | } |
| | | dest.writeInt(mServiceDataUuid == null ? 0 : 1); |
| | | if (mServiceDataUuid != null) { |
| | | dest.writeParcelable(mServiceDataUuid, flags); |
| | | dest.writeInt(mServiceData == null ? 0 : 1); |
| | | if (mServiceData != null) { |
| | | dest.writeInt(mServiceData.length); |
| | | dest.writeByteArray(mServiceData); |
| | | |
| | | dest.writeInt(mServiceDataMask == null ? 0 : 1); |
| | | if (mServiceDataMask != null) { |
| | | dest.writeInt(mServiceDataMask.length); |
| | | dest.writeByteArray(mServiceDataMask); |
| | | } |
| | | } |
| | | } |
| | | dest.writeInt(mManufacturerId); |
| | | dest.writeInt(mManufacturerData == null ? 0 : 1); |
| | | if (mManufacturerData != null) { |
| | | dest.writeInt(mManufacturerData.length); |
| | | dest.writeByteArray(mManufacturerData); |
| | | |
| | | dest.writeInt(mManufacturerDataMask == null ? 0 : 1); |
| | | if (mManufacturerDataMask != null) { |
| | | dest.writeInt(mManufacturerDataMask.length); |
| | | dest.writeByteArray(mManufacturerDataMask); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * A {@link Creator} to create {@link ScanFilterCompat} from parcel. |
| | | */ |
| | | public static final Creator<ScanFilterCompat> |
| | | CREATOR = new Creator<ScanFilterCompat>() { |
| | | |
| | | @Override |
| | | public ScanFilterCompat[] newArray(int size) { |
| | | return new ScanFilterCompat[size]; |
| | | } |
| | | |
| | | @Override |
| | | public ScanFilterCompat createFromParcel(Parcel in) { |
| | | Builder builder = new Builder(); |
| | | if (in.readInt() == 1) { |
| | | builder.setDeviceName(in.readString()); |
| | | } |
| | | if (in.readInt() == 1) { |
| | | builder.setDeviceAddress(in.readString()); |
| | | } |
| | | if (in.readInt() == 1) { |
| | | ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader()); |
| | | builder.setServiceUuid(uuid); |
| | | if (in.readInt() == 1) { |
| | | ParcelUuid uuidMask = in.readParcelable( |
| | | ParcelUuid.class.getClassLoader()); |
| | | builder.setServiceUuid(uuid, uuidMask); |
| | | } |
| | | } |
| | | if (in.readInt() == 1) { |
| | | ParcelUuid servcieDataUuid = |
| | | in.readParcelable(ParcelUuid.class.getClassLoader()); |
| | | if (in.readInt() == 1) { |
| | | int serviceDataLength = in.readInt(); |
| | | byte[] serviceData = new byte[serviceDataLength]; |
| | | in.readByteArray(serviceData); |
| | | if (in.readInt() == 0) { |
| | | builder.setServiceData(servcieDataUuid, serviceData); |
| | | } |
| | | else { |
| | | int serviceDataMaskLength = in.readInt(); |
| | | byte[] serviceDataMask = new byte[serviceDataMaskLength]; |
| | | in.readByteArray(serviceDataMask); |
| | | builder.setServiceData( |
| | | servcieDataUuid, serviceData, serviceDataMask); |
| | | } |
| | | } |
| | | } |
| | | |
| | | int manufacturerId = in.readInt(); |
| | | if (in.readInt() == 1) { |
| | | int manufacturerDataLength = in.readInt(); |
| | | byte[] manufacturerData = new byte[manufacturerDataLength]; |
| | | in.readByteArray(manufacturerData); |
| | | if (in.readInt() == 0) { |
| | | builder.setManufacturerData(manufacturerId, manufacturerData); |
| | | } |
| | | else { |
| | | int manufacturerDataMaskLength = in.readInt(); |
| | | byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength]; |
| | | in.readByteArray(manufacturerDataMask); |
| | | builder.setManufacturerData(manufacturerId, manufacturerData, |
| | | manufacturerDataMask); |
| | | } |
| | | } |
| | | |
| | | return builder.build(); |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * Returns the filter set the device name field of Bluetooth advertisement data. |
| | | */ |
| | | @Nullable |
| | | public String getDeviceName() { |
| | | return mDeviceName; |
| | | } |
| | | |
| | | /** |
| | | * Returns the filter set on the service uuid. |
| | | */ |
| | | @Nullable |
| | | public ParcelUuid getServiceUuid() { |
| | | return mServiceUuid; |
| | | } |
| | | |
| | | @Nullable |
| | | public ParcelUuid getServiceUuidMask() { |
| | | return mServiceUuidMask; |
| | | } |
| | | |
| | | @Nullable |
| | | public String getDeviceAddress() { |
| | | return mDeviceAddress; |
| | | } |
| | | |
| | | @Nullable |
| | | public byte[] getServiceData() { |
| | | return mServiceData; |
| | | } |
| | | |
| | | @Nullable |
| | | public byte[] getServiceDataMask() { |
| | | return mServiceDataMask; |
| | | } |
| | | |
| | | @Nullable |
| | | public ParcelUuid getServiceDataUuid() { |
| | | return mServiceDataUuid; |
| | | } |
| | | |
| | | /** |
| | | * Returns the manufacturer id. -1 if the manufacturer filter is not set. |
| | | */ |
| | | public int getManufacturerId() { |
| | | return mManufacturerId; |
| | | } |
| | | |
| | | @Nullable |
| | | public byte[] getManufacturerData() { |
| | | return mManufacturerData; |
| | | } |
| | | |
| | | @Nullable |
| | | public byte[] getManufacturerDataMask() { |
| | | return mManufacturerDataMask; |
| | | } |
| | | |
| | | /** |
| | | * Check if the scan filter matches a {@code scanResultCompat}. A scan result is considered as a match |
| | | * if it matches all the field filters. |
| | | */ |
| | | public boolean matches(ScanResultCompat scanResult) { |
| | | if (scanResult == null) { |
| | | return false; |
| | | } |
| | | BluetoothDevice device = scanResult.getDevice(); |
| | | // Device match. |
| | | if (mDeviceAddress != null |
| | | && (device == null || !mDeviceAddress.equals(device.getAddress()))) { |
| | | return false; |
| | | } |
| | | |
| | | ScanRecordCompat scanRecord = scanResult.getScanRecord(); |
| | | |
| | | // Scan record is null but there exist filters on it. |
| | | if (scanRecord == null |
| | | && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null |
| | | || mServiceData != null || mServiceDataUuid != null || mManufacturerId >= 0)) { |
| | | return false; |
| | | } |
| | | |
| | | // Local name match. |
| | | if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) { |
| | | return false; |
| | | } |
| | | |
| | | // UUID match. |
| | | if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask, |
| | | scanRecord.getServiceUuids())) { |
| | | return false; |
| | | } |
| | | |
| | | // Service data match |
| | | if (mServiceDataUuid != null) { |
| | | if (!matchesPartialData(mServiceData, mServiceDataMask, |
| | | scanRecord.getServiceData(mServiceDataUuid))) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | // Manufacturer data match. |
| | | if (mManufacturerId >= 0 && scanRecord != null) { |
| | | if (!matchesPartialData(mManufacturerData, mManufacturerDataMask, |
| | | scanRecord.getManufacturerSpecificData(mManufacturerId))) { |
| | | return false; |
| | | } |
| | | } |
| | | // All filters match. |
| | | return true; |
| | | } |
| | | |
| | | // Check if the uuid pattern is contained in a list of parcel uuids. |
| | | private boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, |
| | | List<ParcelUuid> uuids) { |
| | | if (uuid == null) { |
| | | return true; |
| | | } |
| | | if (uuids == null) { |
| | | return false; |
| | | } |
| | | |
| | | for (ParcelUuid parcelUuid : uuids) { |
| | | UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid(); |
| | | if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | // Check if the uuid pattern matches the particular service uuid. |
| | | private boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) { |
| | | if (mask == null) { |
| | | return uuid.equals(data); |
| | | } |
| | | if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits()) != |
| | | (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) { |
| | | return false; |
| | | } |
| | | return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits()) == |
| | | (data.getMostSignificantBits() & mask.getMostSignificantBits())); |
| | | } |
| | | |
| | | // Check whether the data pattern matches the parsed data. |
| | | private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) { |
| | | if (parsedData == null || parsedData.length < data.length) { |
| | | return false; |
| | | } |
| | | if (dataMask == null) { |
| | | for (int i = 0; i < data.length; ++i) { |
| | | if (parsedData[i] != data[i]) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | for (int i = 0; i < data.length; ++i) { |
| | | if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress=" |
| | | + mDeviceAddress |
| | | + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask |
| | | + ", mServiceDataUuid=" + ObjectsCompat.toString(mServiceDataUuid) + ", mServiceData=" |
| | | + Arrays.toString(mServiceData) + ", mServiceDataMask=" |
| | | + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId |
| | | + ", mManufacturerData=" + Arrays.toString(mManufacturerData) |
| | | + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + "]"; |
| | | } |
| | | |
| | | @Override |
| | | public int hashCode() { |
| | | return ObjectsCompat.hash(mDeviceName, mDeviceAddress, mManufacturerId, mManufacturerData, |
| | | mManufacturerDataMask, mServiceDataUuid, mServiceData, mServiceDataMask, |
| | | mServiceUuid, mServiceUuidMask); |
| | | } |
| | | |
| | | @Override |
| | | public boolean equals(Object obj) { |
| | | if (this == obj) { |
| | | return true; |
| | | } |
| | | if (obj == null || getClass() != obj.getClass()) { |
| | | return false; |
| | | } |
| | | ScanFilterCompat other = (ScanFilterCompat) obj; |
| | | return (ObjectsCompat.equals(mDeviceName, other.mDeviceName) && |
| | | ObjectsCompat.equals(mDeviceAddress, other.mDeviceAddress) && |
| | | mManufacturerId == other.mManufacturerId && |
| | | ObjectsCompat.deepEquals(mManufacturerData, other.mManufacturerData) && |
| | | ObjectsCompat.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) && |
| | | ObjectsCompat.deepEquals(mServiceDataUuid, other.mServiceDataUuid) && |
| | | ObjectsCompat.deepEquals(mServiceData, other.mServiceData) && |
| | | ObjectsCompat.deepEquals(mServiceDataMask, other.mServiceDataMask) && |
| | | ObjectsCompat.equals(mServiceUuid, other.mServiceUuid) && |
| | | ObjectsCompat.equals(mServiceUuidMask, other.mServiceUuidMask)); |
| | | } |
| | | |
| | | /** |
| | | * Checks if the scanfilter is empty |
| | | * @hide |
| | | */ |
| | | public boolean isAllFieldsEmpty() { |
| | | return EMPTY.equals(this); |
| | | } |
| | | |
| | | /** |
| | | * Builder class for {@link ScanFilterCompat}. |
| | | */ |
| | | public static final class Builder { |
| | | |
| | | private String mDeviceName; |
| | | private String mDeviceAddress; |
| | | |
| | | private ParcelUuid mServiceUuid; |
| | | private ParcelUuid mUuidMask; |
| | | |
| | | private ParcelUuid mServiceDataUuid; |
| | | private byte[] mServiceData; |
| | | private byte[] mServiceDataMask; |
| | | |
| | | private int mManufacturerId = -1; |
| | | private byte[] mManufacturerData; |
| | | private byte[] mManufacturerDataMask; |
| | | |
| | | /** |
| | | * Set filter on device name. |
| | | */ |
| | | public Builder setDeviceName(String deviceName) { |
| | | mDeviceName = deviceName; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Set filter on device address. |
| | | * |
| | | * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the |
| | | * format of "01:02:03:AB:CD:EF". The device address can be validated using |
| | | * {@link BluetoothAdapter#checkBluetoothAddress}. |
| | | * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. |
| | | */ |
| | | public Builder setDeviceAddress(String deviceAddress) { |
| | | if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) { |
| | | throw new IllegalArgumentException("invalid device address " + deviceAddress); |
| | | } |
| | | mDeviceAddress = deviceAddress; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Set filter on service uuid. |
| | | */ |
| | | public Builder setServiceUuid(ParcelUuid serviceUuid) { |
| | | mServiceUuid = serviceUuid; |
| | | mUuidMask = null; // clear uuid mask |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the |
| | | * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the |
| | | * bit in {@code serviceUuid}, and 0 to ignore that bit. |
| | | * |
| | | * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but |
| | | * {@code uuidMask} is not {@code null}. |
| | | */ |
| | | public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) { |
| | | if (mUuidMask != null && mServiceUuid == null) { |
| | | throw new IllegalArgumentException("uuid is null while uuidMask is not null!"); |
| | | } |
| | | mServiceUuid = serviceUuid; |
| | | mUuidMask = uuidMask; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Set filtering on service data. |
| | | * |
| | | * @throws IllegalArgumentException If {@code serviceDataUuid} is null. |
| | | */ |
| | | public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) { |
| | | if (serviceDataUuid == null) { |
| | | throw new IllegalArgumentException("serviceDataUuid is null"); |
| | | } |
| | | mServiceDataUuid = serviceDataUuid; |
| | | mServiceData = serviceData; |
| | | mServiceDataMask = null; // clear service data mask |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to |
| | | * match the one in service data, otherwise set it to 0 to ignore that bit. |
| | | * <p/> |
| | | * The {@code serviceDataMask} must have the same length of the {@code serviceData}. |
| | | * |
| | | * @throws IllegalArgumentException If {@code serviceDataUuid} is null or |
| | | * {@code serviceDataMask} is {@code null} while {@code serviceData} is not or |
| | | * {@code serviceDataMask} and {@code serviceData} has different length. |
| | | */ |
| | | public Builder setServiceData(ParcelUuid serviceDataUuid, |
| | | byte[] serviceData, byte[] serviceDataMask) { |
| | | if (serviceDataUuid == null) { |
| | | throw new IllegalArgumentException("serviceDataUuid is null"); |
| | | } |
| | | if (mServiceDataMask != null) { |
| | | if (mServiceData == null) { |
| | | throw new IllegalArgumentException( |
| | | "serviceData is null while serviceDataMask is not null"); |
| | | } |
| | | // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two |
| | | // byte array need to be the same. |
| | | if (mServiceData.length != mServiceDataMask.length) { |
| | | throw new IllegalArgumentException( |
| | | "size mismatch for service data and service data mask"); |
| | | } |
| | | } |
| | | mServiceDataUuid = serviceDataUuid; |
| | | mServiceData = serviceData; |
| | | mServiceDataMask = serviceDataMask; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id. |
| | | * <p/> |
| | | * Note the first two bytes of the {@code manufacturerData} is the manufacturerId. |
| | | * |
| | | * @throws IllegalArgumentException If the {@code manufacturerId} is invalid. |
| | | */ |
| | | public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) { |
| | | if (manufacturerData != null && manufacturerId < 0) { |
| | | throw new IllegalArgumentException("invalid manufacture id"); |
| | | } |
| | | mManufacturerId = manufacturerId; |
| | | mManufacturerData = manufacturerData; |
| | | mManufacturerDataMask = null; // clear manufacturer data mask |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs |
| | | * to match the one in manufacturer data, otherwise set it to 0. |
| | | * <p/> |
| | | * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}. |
| | | * |
| | | * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or |
| | | * {@code manufacturerData} is null while {@code manufacturerDataMask} is not, |
| | | * or {@code manufacturerData} and {@code manufacturerDataMask} have different |
| | | * length. |
| | | */ |
| | | public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData, |
| | | byte[] manufacturerDataMask) { |
| | | if (manufacturerData != null && manufacturerId < 0) { |
| | | throw new IllegalArgumentException("invalid manufacture id"); |
| | | } |
| | | if (mManufacturerDataMask != null) { |
| | | if (mManufacturerData == null) { |
| | | throw new IllegalArgumentException( |
| | | "manufacturerData is null while manufacturerDataMask is not null"); |
| | | } |
| | | // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths |
| | | // of the two byte array need to be the same. |
| | | if (mManufacturerData.length != mManufacturerDataMask.length) { |
| | | throw new IllegalArgumentException( |
| | | "size mismatch for manufacturerData and manufacturerDataMask"); |
| | | } |
| | | } |
| | | mManufacturerId = manufacturerId; |
| | | mManufacturerData = manufacturerData; |
| | | mManufacturerDataMask = manufacturerDataMask; |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Build {@link ScanFilterCompat}. |
| | | * |
| | | * @throws IllegalArgumentException If the filter cannot be built. |
| | | */ |
| | | public ScanFilterCompat build() { |
| | | return new ScanFilterCompat(mDeviceName, mDeviceAddress, |
| | | mServiceUuid, mUuidMask, |
| | | mServiceDataUuid, mServiceData, mServiceDataMask, |
| | | mManufacturerId, mManufacturerData, mManufacturerDataMask); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /* |
| | | * Copyright (C) 2014 The Android Open Source Project |
| | | * Copyright (C) 2015 Joe Rogers |
| | | * |
| | | * Licensed under the Apache License, Version 2.0 (the "License"); |
| | | * you may not use this file except in compliance with the License. |
| | | * You may obtain a copy of the License at |
| | | * |
| | | * http://www.apache.org/licenses/LICENSE-2.0 |
| | | * |
| | | * Unless required by applicable law or agreed to in writing, software |
| | | * distributed under the License is distributed on an "AS IS" BASIS, |
| | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | * See the License for the specific language governing permissions and |
| | | * limitations under the License. |
| | | */ |
| | | |
| | | package com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat; |
| | | |
| | | import android.annotation.TargetApi; |
| | | import android.bluetooth.le.ScanRecord; |
| | | import android.os.Build; |
| | | import android.os.ParcelUuid; |
| | | import androidx.annotation.Nullable; |
| | | import androidx.collection.ArrayMap; |
| | | import android.util.Log; |
| | | import android.util.SparseArray; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Iterator; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * Compatible version so the ScanRecord |
| | | */ |
| | | public class ScanRecordCompat { |
| | | private static final String TAG = "ScanRecordCompat"; |
| | | |
| | | // The following data type values are assigned by Bluetooth SIG. |
| | | // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18. |
| | | //https://devzone.nordicsemi.com/documentation/nrf51/4.2.0/html/group___b_l_e___g_a_p___a_d___t_y_p_e___d_e_f_i_n_i_t_i_o_n_s.html |
| | | private static final int DATA_TYPE_FLAGS = 0x01; |
| | | private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02; |
| | | private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03; |
| | | private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04; |
| | | private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05; |
| | | private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06; |
| | | private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07; |
| | | private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08; |
| | | private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09; |
| | | private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A; |
| | | private static final int DATA_TYPE_SERVICE_DATA = 0x16; |
| | | private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; |
| | | |
| | | // Flags of the advertising data. |
| | | private final int mAdvertiseFlags; |
| | | |
| | | @Nullable |
| | | private final List<ParcelUuid> mServiceUuids; |
| | | |
| | | private final SparseArray<byte[]> mManufacturerSpecificData; |
| | | |
| | | private final Map<ParcelUuid, byte[]> mServiceData; |
| | | |
| | | // Transmission power level(in dB). |
| | | private final int mTxPowerLevel; |
| | | |
| | | // Local name of the Bluetooth LE device. |
| | | private final String mDeviceName; |
| | | |
| | | // Raw bytes of scan record. |
| | | private final byte[] mBytes; |
| | | |
| | | /** |
| | | * Returns the advertising flags indicating the discoverable mode and capability of the device. |
| | | * Returns -1 if the flag field is not set. |
| | | */ |
| | | public int getAdvertiseFlags() { |
| | | return mAdvertiseFlags; |
| | | } |
| | | |
| | | /** |
| | | * Returns a list of service UUIDs within the advertisement that are used to identify the |
| | | * bluetooth GATT services. |
| | | */ |
| | | public List<ParcelUuid> getServiceUuids() { |
| | | return mServiceUuids; |
| | | } |
| | | |
| | | /** |
| | | * Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific |
| | | * data. |
| | | */ |
| | | public SparseArray<byte[]> getManufacturerSpecificData() { |
| | | return mManufacturerSpecificData; |
| | | } |
| | | |
| | | /** |
| | | * Returns the manufacturer specific data associated with the manufacturer id. Returns |
| | | * {@code null} if the {@code manufacturerId} is not found. |
| | | */ |
| | | @Nullable |
| | | public byte[] getManufacturerSpecificData(int manufacturerId) { |
| | | return mManufacturerSpecificData.get(manufacturerId); |
| | | } |
| | | |
| | | /** |
| | | * Returns a map of service UUID and its corresponding service data. |
| | | */ |
| | | public Map<ParcelUuid, byte[]> getServiceData() { |
| | | return mServiceData; |
| | | } |
| | | |
| | | /** |
| | | * Returns the service data byte array associated with the {@code serviceUuid}. Returns |
| | | * {@code null} if the {@code serviceDataUuid} is not found. |
| | | */ |
| | | @Nullable |
| | | public byte[] getServiceData(ParcelUuid serviceDataUuid) { |
| | | if (serviceDataUuid == null) { |
| | | return null; |
| | | } |
| | | return mServiceData.get(serviceDataUuid); |
| | | } |
| | | |
| | | /** |
| | | * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE} |
| | | * if the field is not set. This value can be used to calculate the path loss of a received |
| | | * packet using the following equation: |
| | | * <p> |
| | | * <code>pathloss = txPowerLevel - rssi</code> |
| | | */ |
| | | public int getTxPowerLevel() { |
| | | return mTxPowerLevel; |
| | | } |
| | | |
| | | /** |
| | | * Returns the local name of the BLE device. The is a UTF-8 encoded string. |
| | | */ |
| | | @Nullable |
| | | public String getDeviceName() { |
| | | return mDeviceName; |
| | | } |
| | | |
| | | /** |
| | | * Returns raw bytes of scan record. |
| | | */ |
| | | public byte[] getBytes() { |
| | | return mBytes; |
| | | } |
| | | |
| | | private ScanRecordCompat(@Nullable List<ParcelUuid> serviceUuids, |
| | | SparseArray<byte[]> manufacturerData, |
| | | Map<ParcelUuid, byte[]> serviceData, |
| | | int advertiseFlags, int txPowerLevel, |
| | | String localName, byte[] bytes) { |
| | | mServiceUuids = serviceUuids; |
| | | mManufacturerSpecificData = manufacturerData; |
| | | mServiceData = serviceData; |
| | | mDeviceName = localName; |
| | | mAdvertiseFlags = advertiseFlags; |
| | | mTxPowerLevel = txPowerLevel; |
| | | mBytes = bytes; |
| | | } |
| | | |
| | | @TargetApi(Build.VERSION_CODES.LOLLIPOP) |
| | | ScanRecordCompat(ScanRecord record) { |
| | | mServiceUuids = record.getServiceUuids(); |
| | | mManufacturerSpecificData = record.getManufacturerSpecificData(); |
| | | mServiceData = record.getServiceData(); |
| | | mDeviceName = record.getDeviceName(); |
| | | mAdvertiseFlags = record.getAdvertiseFlags(); |
| | | mTxPowerLevel = record.getTxPowerLevel(); |
| | | mBytes = record.getBytes(); |
| | | } |
| | | |
| | | /** |
| | | * Parse scan record bytes to {@link ScanRecordCompat}. |
| | | * <p> |
| | | * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18. |
| | | * <p> |
| | | * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong> |
| | | * order. |
| | | * |
| | | * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response. |
| | | */ |
| | | public static ScanRecordCompat parseFromBytes(byte[] scanRecord) { |
| | | if (scanRecord == null) { |
| | | return null; |
| | | } |
| | | |
| | | int currentPos = 0; |
| | | int advertiseFlag = -1; |
| | | List<ParcelUuid> serviceUuids = new ArrayList<>(); |
| | | String localName = null; |
| | | int txPowerLevel = Integer.MIN_VALUE; |
| | | |
| | | SparseArray<byte[]> manufacturerData = new SparseArray<>(); |
| | | Map<ParcelUuid, byte[]> serviceData = new ArrayMap<>(); |
| | | |
| | | try { |
| | | while (currentPos < scanRecord.length) { |
| | | // length is unsigned int. |
| | | int length = scanRecord[currentPos++] & 0xFF; |
| | | if (length == 0) { |
| | | break; |
| | | } |
| | | // Note the length includes the length of the field type itself. |
| | | int dataLength = length - 1; |
| | | // fieldType is unsigned int. |
| | | int fieldType = scanRecord[currentPos++] & 0xFF; |
| | | switch (fieldType) { |
| | | case DATA_TYPE_FLAGS: |
| | | advertiseFlag = scanRecord[currentPos] & 0xFF; |
| | | break; |
| | | case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL: |
| | | case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: |
| | | parseServiceUuid(scanRecord, currentPos, |
| | | dataLength, BluetoothUuidCompat.UUID_BYTES_16_BIT, serviceUuids); |
| | | break; |
| | | case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL: |
| | | case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE: |
| | | parseServiceUuid(scanRecord, currentPos, dataLength, |
| | | BluetoothUuidCompat.UUID_BYTES_32_BIT, serviceUuids); |
| | | break; |
| | | case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL: |
| | | case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE: |
| | | parseServiceUuid(scanRecord, currentPos, dataLength, |
| | | BluetoothUuidCompat.UUID_BYTES_128_BIT, serviceUuids); |
| | | break; |
| | | case DATA_TYPE_LOCAL_NAME_SHORT: |
| | | case DATA_TYPE_LOCAL_NAME_COMPLETE: |
| | | localName = new String( |
| | | extractBytes(scanRecord, currentPos, dataLength)); |
| | | break; |
| | | case DATA_TYPE_TX_POWER_LEVEL: |
| | | txPowerLevel = scanRecord[currentPos]; |
| | | break; |
| | | case DATA_TYPE_SERVICE_DATA: |
| | | // The first two bytes of the service data are service data UUID in little |
| | | // endian. The rest bytes are service data. |
| | | int serviceUuidLength = BluetoothUuidCompat.UUID_BYTES_16_BIT; |
| | | byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos, |
| | | serviceUuidLength); |
| | | ParcelUuid serviceDataUuid = BluetoothUuidCompat.parseUuidFrom( |
| | | serviceDataUuidBytes); |
| | | byte[] serviceDataArray = extractBytes(scanRecord, |
| | | currentPos + serviceUuidLength, dataLength - serviceUuidLength); |
| | | serviceData.put(serviceDataUuid, serviceDataArray); |
| | | break; |
| | | case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA: |
| | | // The first two bytes of the manufacturer specific data are |
| | | // manufacturer ids in little endian. |
| | | int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8) + |
| | | (scanRecord[currentPos] & 0xFF); |
| | | byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2, |
| | | dataLength - 2); |
| | | manufacturerData.put(manufacturerId, manufacturerDataBytes); |
| | | break; |
| | | default: |
| | | // Just ignore, we don't handle such data type. |
| | | break; |
| | | } |
| | | currentPos += dataLength; |
| | | } |
| | | |
| | | if (serviceUuids.isEmpty()) { |
| | | serviceUuids = null; |
| | | } |
| | | return new ScanRecordCompat(serviceUuids, manufacturerData, serviceData, |
| | | advertiseFlag, txPowerLevel, localName, scanRecord); |
| | | } catch (Exception e) { |
| | | Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord)); |
| | | // As the record is invalid, ignore all the parsed results for this packet |
| | | // and return an empty record with raw scanRecord bytes in results |
| | | return new ScanRecordCompat(null, null, null, -1, Integer.MIN_VALUE, null, scanRecord); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids |
| | | + ", mManufacturerSpecificData=" + toString(mManufacturerSpecificData) |
| | | + ", mServiceData=" + toString(mServiceData) |
| | | + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]"; |
| | | } |
| | | |
| | | // Parse service UUIDs. |
| | | private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, |
| | | int uuidLength, List<ParcelUuid> serviceUuids) { |
| | | while (dataLength > 0) { |
| | | byte[] uuidBytes = extractBytes(scanRecord, currentPos, |
| | | uuidLength); |
| | | serviceUuids.add(BluetoothUuidCompat.parseUuidFrom(uuidBytes)); |
| | | dataLength -= uuidLength; |
| | | currentPos += uuidLength; |
| | | } |
| | | return currentPos; |
| | | } |
| | | |
| | | // Helper method to extract bytes from byte array. |
| | | private static byte[] extractBytes(byte[] scanRecord, int start, int length) { |
| | | byte[] bytes = new byte[length]; |
| | | System.arraycopy(scanRecord, start, bytes, 0, length); |
| | | return bytes; |
| | | } |
| | | |
| | | /** |
| | | * Returns a string composed from a {@link SparseArray}. |
| | | */ |
| | | static String toString(SparseArray<byte[]> array) { |
| | | if (array == null) { |
| | | return "null"; |
| | | } |
| | | if (array.size() == 0) { |
| | | return "{}"; |
| | | } |
| | | StringBuilder buffer = new StringBuilder(); |
| | | buffer.append('{'); |
| | | for (int i = 0; i < array.size(); ++i) { |
| | | buffer.append(array.keyAt(i)).append("=").append(Arrays.toString(array.valueAt(i))); |
| | | } |
| | | buffer.append('}'); |
| | | return buffer.toString(); |
| | | } |
| | | |
| | | /** |
| | | * Returns a string composed from a {@link Map}. |
| | | */ |
| | | static <T> String toString(Map<T, byte[]> map) { |
| | | if (map == null) { |
| | | return "null"; |
| | | } |
| | | if (map.isEmpty()) { |
| | | return "{}"; |
| | | } |
| | | StringBuilder buffer = new StringBuilder(); |
| | | buffer.append('{'); |
| | | Iterator<Map.Entry<T, byte[]>> it = map.entrySet().iterator(); |
| | | while (it.hasNext()) { |
| | | Map.Entry<T, byte[]> entry = it.next(); |
| | | T key = entry.getKey(); |
| | | buffer.append(key).append("=").append(Arrays.toString(map.get(key))); |
| | | if (it.hasNext()) { |
| | | buffer.append(", "); |
| | | } |
| | | } |
| | | buffer.append('}'); |
| | | return buffer.toString(); |
| | | } |
| | | } |
| bluetooth-manager-lib/src/main/java/com/blakequ/bluetooth_manager_lib/scan/bluetoothcompat/ScanResultCompat.java
bluetooth-manager-lib/src/main/java/com/blakequ/bluetooth_manager_lib/scan/bluetoothcompat/ScanSettingsCompat.java
bluetooth-manager-lib/src/main/java/com/blakequ/bluetooth_manager_lib/util/BluetoothUtils.java
bluetooth-manager-lib/src/main/java/com/blakequ/bluetooth_manager_lib/util/ByteUtils.java
bluetooth-manager-lib/src/main/java/com/blakequ/bluetooth_manager_lib/util/LimitedLinkHashMap.java
bluetooth-manager-lib/src/main/res/values/strings.xml
build.gradle
comb.jks
gradle.properties
settings.gradle |