├── Changelog.md ├── README.md ├── README_CN.md ├── demo └── mynt-sdk-demo │ ├── .gitignore │ ├── app │ ├── .gitignore │ ├── build.gradle │ ├── libs │ │ └── mynt-sdk-deploy-v1.1.4_065b2c2.jar │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── slightech │ │ │ └── mynt │ │ │ └── sdk │ │ │ └── demo │ │ │ └── ApplicationTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ │ ├── 20160418_25.bin │ │ │ └── 20160816_26.bin │ │ ├── java │ │ │ └── com │ │ │ │ └── slightech │ │ │ │ └── mynt │ │ │ │ └── sdk │ │ │ │ └── demo │ │ │ │ ├── Firmware.java │ │ │ │ ├── MyApplication.java │ │ │ │ ├── ui │ │ │ │ ├── ControlActivity.java │ │ │ │ ├── SearchActivity.java │ │ │ │ ├── base │ │ │ │ │ ├── BaseActivity.java │ │ │ │ │ └── BaseDialogFragment.java │ │ │ │ └── dialog │ │ │ │ │ └── UpdateDialogFragment.java │ │ │ │ └── util │ │ │ │ ├── KitUtils.java │ │ │ │ ├── LogUtils.java │ │ │ │ ├── PermissionUtils.java │ │ │ │ └── ToastUtils.java │ │ └── res │ │ │ ├── anim │ │ │ ├── slide_in_left.xml │ │ │ ├── slide_in_right.xml │ │ │ ├── slide_out_left.xml │ │ │ └── slide_out_right.xml │ │ │ ├── layout │ │ │ ├── activity_control.xml │ │ │ ├── activity_search.xml │ │ │ ├── frag_dlg_update.xml │ │ │ ├── item_device.xml │ │ │ ├── item_info.xml │ │ │ └── toolbar.xml │ │ │ ├── menu │ │ │ ├── control.xml │ │ │ └── search.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-v21 │ │ │ └── styles.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── config.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── slightech │ │ └── mynt │ │ └── sdk │ │ └── demo │ │ └── ExampleUnitTest.java │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── doc ├── how │ ├── how_to_update_firmware_en.md │ └── how_to_update_firmware_zh.md ├── mynt-sdk-doc-deploy-v1.1.4_065b2c2.zip ├── usage_en.md └── usage_zh.md ├── libs └── mynt-sdk-deploy-v1.1.4_065b2c2.jar └── static ├── colorful.png ├── slide-ctr-photo.png ├── slide_mynt.png ├── update_firmware_check_software.png ├── update_firmware_select_action.png └── update_firmware_select_bin.png /Changelog.md: -------------------------------------------------------------------------------- 1 | 2 | # Changelog 3 | 4 | ## v1.1.4_065b2c2 5 | 6 | * Added check services 7 | 8 | ## v1.1.3_7bc4a24 9 | 10 | * Added setup connect mode 11 | * Added multi callbacks 12 | * Changed cloud params 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![](https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/master/static/slide_mynt.png) 3 | 4 | [MYNT]: https://mynt.slightech.com/ 5 | 6 | # MYNT SDK 7 | 8 | The SDK to help developers to search and control MYNTs conveniently. 9 | 10 | * Listen the MYNTs connect state, do scan, connect, disconnect etc. 11 | * Set the MYNTs click events with HID or custom actions to control music, camera, PPT etc. 12 | 13 | ## What is [MYNT][] 14 | 15 | MYNT is a thinnest smart tracker, only 1/8 inch. Easy to fit in a wallet, attach to a keychain, or affix to a laptop or a TV remote. 16 | 17 | MYNT is a most exquisite smart tracker, processed with PVD technology on stainless steel with frosting or polishing. Four colors are optional: Silver, Black, Gold, and Blue. 18 | 19 | ![](https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/master/static/colorful.png) 20 | 21 | MYNT is not just a smart tracker, can also become your everyday smart companion. In addition to anti-loss and finder capability, MYNT can also act the following really cool utilities: 22 | 23 | * Remote shutter: Free your hand, release your passion. 24 | * Music remoter: Control freely, enjoy your music. 25 | * Slide remoter: Control freely, facilitate efficiency. 26 | 27 | ![](https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/master/static/slide-ctr-photo.png) 28 | 29 | MYNT - No lost, all found, control everything. 30 | 31 | - Website: https://mynt.slightech.com 32 | - Purchase: https://www.amazon.com/dp/B017QML97W 33 | 34 | ## SDK Directory Structure 35 | 36 | * libs: the MYNT SDK libraries. 37 | * doc: the MYNT SDK API and usage documnets. 38 | - [download API doc](https://github.com/slightech/MYNT-SDK-Android/raw/master/doc/mynt-sdk-doc-1.1.2.zip) 39 | - [usage_en](https://github.com/slightech/MYNT-SDK-Android/blob/master/doc/usage_en.md), [usage_zh](https://github.com/slightech/MYNT-SDK-Android/blob/master/doc/usage_zh.md) 40 | * demo: the demo shows how to control the MYNTs with the libraries. 41 | 42 | ## Other Introductions 43 | 44 | * [How to update firmware](https://github.com/slightech/MYNT-SDK-Android/blob/master/doc/how/how_to_update_firmware_en.md) 45 | 46 | ## More Languages 47 | 48 | * [中文说明](https://github.com/slightech/MYNT-SDK-Android/blob/master/README_CN.md) 49 | 50 | ## Copyright 51 | 52 | Copyright 2016 Slightech Co., Ltd. All rights reserved. 53 | 54 | Licensed under the Apache License, Version 2.0 (the "License"); 55 | you may not use this file except in compliance with the License. 56 | You may obtain a copy of the License at 57 | 58 | http://www.apache.org/licenses/LICENSE-2.0 59 | 60 | Unless required by applicable law or agreed to in writing, software 61 | distributed under the License is distributed on an "AS IS" BASIS, 62 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 63 | See the License for the specific language governing permissions and 64 | limitations under the License. 65 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | 2 | ![](https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/master/static/slide_mynt.png) 3 | 4 | [MYNT]: https://mynt.slightech.com/ 5 | 6 | # MYNT SDK 7 | 8 | MYNT SDK 用于帮助开发者更快得实现小觅的搜索和控制。 9 | 10 | * 监听小觅连接状态,操作扫描、连接、断开等。 11 | * 设定小觅单双击等行为,HID或自定义,控制音乐、相机、PPT等。 12 | 13 | ## [小觅][MYNT] 是什么? 14 | 15 | 小觅是一款超薄的智能防丢器,厚度仅0.35cm,轻松挂在钥匙,夹在钱包,放进行李,贴在物品表面。 16 | 17 | 小觅是一款超酷的智能防丢器,采用不锈钢PVD工艺,镜面或磨砂,银黑金蓝四色可选,独有外观专利。 18 | 19 | ![](https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/master/static/colorful.png) 20 | 21 | 小觅防丢,人狗不丢:双向寻物、社区找回、携手公益。 22 | 23 | 小觅不只是防丢器,更是生活好伴侣:放手自拍、随心切歌、遥控PPT。 24 | 25 | ![](https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/master/static/slide-ctr-photo.png) 26 | 27 | - 官网:http://mynt.slightech.com/cn 28 | - 购买:https://xiaomishuma.tmall.com/ 29 | 30 | ## SDK 目录结构 31 | 32 | * libs: MYNT SDK 库。 33 | * doc: MYNT API 和使用文档。 34 | - [download API doc](https://github.com/slightech/MYNT-SDK-Android/raw/master/doc/mynt-sdk-doc-1.1.2.zip) 35 | - [usage_en](https://github.com/slightech/MYNT-SDK-Android/blob/master/doc/usage_en.md), [usage_zh](https://github.com/slightech/MYNT-SDK-Android/blob/master/doc/usage_zh.md) 36 | * demo: 使用 SDK 库控制小觅的样例。 37 | 38 | ## 其他介绍 39 | 40 | * [如何更新固件](https://github.com/slightech/MYNT-SDK-Android/blob/master/doc/how/how_to_update_firmware_zh.md) 41 | 42 | ## Copyright 43 | 44 | Copyright 2016 Slightech Co., Ltd. All rights reserved. 45 | 46 | Licensed under the Apache License, Version 2.0 (the "License"); 47 | you may not use this file except in compliance with the License. 48 | You may obtain a copy of the License at 49 | 50 | http://www.apache.org/licenses/LICENSE-2.0 51 | 52 | Unless required by applicable law or agreed to in writing, software 53 | distributed under the License is distributed on an "AS IS" BASIS, 54 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 55 | See the License for the specific language governing permissions and 56 | limitations under the License. 57 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .gradle 4 | *.iml 5 | /build 6 | /local.properties 7 | /captures -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.2" 6 | 7 | useLibrary 'org.apache.http.legacy' 8 | 9 | defaultConfig { 10 | applicationId "com.slightech.mynt.sdk.demo" 11 | minSdkVersion 18 12 | targetSdkVersion 22 13 | //targetSdkVersion 23 14 | // Only when the bluetooth and location are both on can the devices be scanned. 15 | // * http://stackoverflow.com/questions/32708374/bluetooth-le-scanfilters-dont-work-on-android-m 16 | // * http://developer.android.com/about/versions/marshmallow/android-6.0-changes.html#behavior-hardware-id 17 | versionCode 1 18 | versionName "1.0" 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | compile fileTree(dir: 'libs', include: ['*.jar']) 30 | testCompile 'junit:junit:4.12' 31 | compile 'com.android.support:appcompat-v7:24.2.1' 32 | compile 'com.android.support:design:24.2.1' 33 | } 34 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/libs/mynt-sdk-deploy-v1.1.4_065b2c2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/7c294d7d881bda54e19f21e899dc2bee02924fa3/demo/mynt-sdk-demo/app/libs/mynt-sdk-deploy-v1.1.4_065b2c2.jar -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/John/Develop/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/androidTest/java/com/slightech/mynt/sdk/demo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.slightech.mynt.sdk.demo; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/assets/20160418_25.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/7c294d7d881bda54e19f21e899dc2bee02924fa3/demo/mynt-sdk-demo/app/src/main/assets/20160418_25.bin -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/assets/20160816_26.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/7c294d7d881bda54e19f21e899dc2bee02924fa3/demo/mynt-sdk-demo/app/src/main/assets/20160816_26.bin -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/java/com/slightech/mynt/sdk/demo/Firmware.java: -------------------------------------------------------------------------------- 1 | package com.slightech.mynt.sdk.demo; 2 | 3 | public final class Firmware { 4 | 5 | public static String[] FILES = new String[] { 6 | "20160418_25.bin", 7 | "20160816_26.bin", 8 | }; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/java/com/slightech/mynt/sdk/demo/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.slightech.mynt.sdk.demo; 2 | 3 | import android.app.Application; 4 | import android.os.StrictMode; 5 | 6 | import com.slightech.mynt.api.MyntManager; 7 | 8 | public class MyApplication extends Application { 9 | 10 | private static MyApplication mInstance; 11 | 12 | private MyntManager mMyntManager; 13 | 14 | @Override 15 | public void onCreate() { 16 | super.onCreate(); 17 | mInstance = this; 18 | if (BuildConfig.DEBUG) { 19 | setupStrictMode(); 20 | } 21 | MyntManager.setDebug(BuildConfig.DEBUG); 22 | } 23 | 24 | public static MyApplication getInstance() { 25 | return mInstance; 26 | } 27 | 28 | public static MyntManager getMyntManager() { 29 | final MyApplication app = getInstance(); 30 | if (app.mMyntManager == null) { 31 | app.mMyntManager = new MyntManager(app); 32 | } 33 | return app.mMyntManager; 34 | } 35 | 36 | private void setupStrictMode() { 37 | StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 38 | .detectDiskReads() 39 | .detectDiskWrites() 40 | .detectNetwork() 41 | .penaltyLog() 42 | .build()); 43 | StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() 44 | .detectLeakedSqlLiteObjects() 45 | .detectLeakedClosableObjects() 46 | .penaltyLog() 47 | .penaltyDeath() 48 | .build()); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/java/com/slightech/mynt/sdk/demo/ui/ControlActivity.java: -------------------------------------------------------------------------------- 1 | package com.slightech.mynt.sdk.demo.ui; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.content.Intent; 8 | import android.graphics.Color; 9 | import android.os.Bundle; 10 | import android.os.PowerManager; 11 | import android.support.annotation.ColorInt; 12 | import android.support.v4.content.ContextCompat; 13 | import android.support.v7.app.AlertDialog; 14 | import android.support.v7.widget.LinearLayoutManager; 15 | import android.support.v7.widget.RecyclerView; 16 | import android.support.v7.widget.Toolbar; 17 | import android.util.Log; 18 | import android.view.LayoutInflater; 19 | import android.view.Menu; 20 | import android.view.MenuItem; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | import android.widget.TextView; 24 | 25 | import com.slightech.mynt.api.MyntManager; 26 | import com.slightech.mynt.api.callback.EventCallback; 27 | import com.slightech.mynt.api.callback.KeyCallback; 28 | import com.slightech.mynt.api.callback.PairCallback; 29 | import com.slightech.mynt.api.event.ActionEvent; 30 | import com.slightech.mynt.api.event.ClickEvent; 31 | import com.slightech.mynt.api.mode.ConnectMode; 32 | import com.slightech.mynt.api.mode.ControlMode; 33 | import com.slightech.mynt.api.model.Device; 34 | import com.slightech.mynt.api.oad.OADProgInfo; 35 | import com.slightech.mynt.api.oad.OADUpdater; 36 | import com.slightech.mynt.sdk.demo.MyApplication; 37 | import com.slightech.mynt.sdk.demo.R; 38 | import com.slightech.mynt.sdk.demo.ui.base.BaseActivity; 39 | import com.slightech.mynt.sdk.demo.ui.dialog.UpdateDialogFragment; 40 | import com.slightech.mynt.sdk.demo.util.KitUtils; 41 | import com.slightech.mynt.sdk.demo.util.LogUtils; 42 | import com.slightech.mynt.sdk.demo.util.ToastUtils; 43 | 44 | import java.text.SimpleDateFormat; 45 | import java.util.Date; 46 | import java.util.LinkedList; 47 | 48 | public class ControlActivity extends BaseActivity implements PairCallback, EventCallback, KeyCallback, 49 | UpdateDialogFragment.OnUpdateSelectListener { 50 | 51 | static final String TAG = "SearchActivity"; 52 | 53 | public static final String EXTRA_DEVICE = "device"; 54 | 55 | public static void start(Activity activity, Device device) { 56 | Intent intent = new Intent(activity, ControlActivity.class); 57 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); 58 | intent.putExtra(ControlActivity.EXTRA_DEVICE, device); 59 | activity.startActivity(intent); 60 | activity.overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left); 61 | } 62 | 63 | private RecyclerView mRecyclerView; 64 | private ViewAdapter mViewAdapter; 65 | private TextView mTextView; 66 | 67 | private MyntManager mMyntManager; 68 | private String mDeviceSn; 69 | 70 | public enum State { 71 | Disconnected, Connecting, Connected, Binding, Bound, 72 | } 73 | 74 | private State mState = State.Disconnected; 75 | private boolean mAlarmOn = false; 76 | private boolean mUpdating = false; 77 | 78 | @SuppressLint("SimpleDateFormat") 79 | private final SimpleDateFormat mDateFormat = new SimpleDateFormat("HH:mm:ss.SSS"); 80 | 81 | @Override 82 | protected void onCreate(Bundle savedInstanceState) { 83 | super.onCreate(savedInstanceState); 84 | setContentView(R.layout.activity_control); 85 | setTitle(R.string.title_control); 86 | initViews(); 87 | intObjects(); 88 | } 89 | 90 | private void initViews() { 91 | Toolbar bar = KitUtils.findById(this, R.id.bar); 92 | if (bar != null) { 93 | setSupportActionBar(bar); 94 | } 95 | 96 | mRecyclerView = KitUtils.findById(this, R.id.recycler); 97 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); 98 | mRecyclerView.setHasFixedSize(true); 99 | 100 | mViewAdapter = new ViewAdapter(); 101 | mRecyclerView.setAdapter(mViewAdapter); 102 | 103 | mTextView = KitUtils.findById(this, R.id.text); 104 | } 105 | 106 | private void updateInfo(Device device) { 107 | mTextView.setText(String.format("%s\n%s", 108 | device.connectMode.name(), device.controlMode.name())); 109 | } 110 | 111 | private void updateInfo(String sn, ControlMode mode) { 112 | // update control mode directly 113 | final Device device = mMyntManager.findDevice(sn); 114 | if (device == null) return; 115 | device.controlMode = mode; 116 | updateInfo(device); 117 | } 118 | 119 | private void intObjects() { 120 | Device device = getIntent().getParcelableExtra(EXTRA_DEVICE); 121 | assert device != null : "Must give a device to control"; 122 | LogUtils.i(TAG, "device: %s", device.sn); 123 | 124 | mMyntManager = MyApplication.getMyntManager(); 125 | // don't forget to remove callbacks in `onDestroy` 126 | mMyntManager.addPairCallback(this); 127 | mMyntManager.addEventCallback(this); 128 | 129 | // If support the lower version firmware with key pairing, unnecessary now 130 | mMyntManager.setKeyCallback(this); 131 | 132 | mDeviceSn = device.sn; 133 | 134 | setTitle(mDeviceSn); 135 | } 136 | 137 | @Override 138 | public boolean onCreateOptionsMenu(Menu menu) { 139 | getMenuInflater().inflate(R.menu.control, menu); 140 | 141 | menu.findItem(R.id.connect).setVisible(mState == State.Disconnected); 142 | 143 | boolean connected = (mState == State.Connected); 144 | boolean bound = (mState == State.Bound); 145 | 146 | final Device device = mMyntManager.findDevice(mDeviceSn); 147 | boolean bleMode = device != null && device.connectMode == ConnectMode.CONNECT_BLE; 148 | boolean hidMode = device != null && device.connectMode == ConnectMode.CONNECT_HID; 149 | 150 | menu.findItem(R.id.disconnect).setVisible(connected || bound); 151 | 152 | menu.findItem(R.id.toggle_alarm).setVisible(bound); 153 | menu.findItem(R.id.request_rssi).setVisible(bound); 154 | menu.findItem(R.id.request_battery).setVisible(bound); 155 | menu.findItem(R.id.request_info).setVisible(bound); 156 | menu.findItem(R.id.request_control_custom_action).setVisible(bound); 157 | menu.findItem(R.id.send_control_mode).setVisible(bound); 158 | menu.findItem(R.id.send_control_custom_clicks).setVisible(bound); 159 | menu.findItem(R.id.update_firmware).setVisible(bound); 160 | 161 | // It's not recommended to using BLE connect mode. 162 | menu.findItem(R.id.setup_ble_mode).setVisible(false); 163 | //menu.findItem(R.id.setup_ble_mode).setVisible(bound && hidMode); 164 | menu.findItem(R.id.setup_hid_mode).setVisible(bound && bleMode); 165 | 166 | // `MyntManager#sendControl*` methods are only valid in `ConnectMode#CONNECT_HID` mode. 167 | menu.findItem(R.id.send_control_mode).setEnabled(hidMode); 168 | menu.findItem(R.id.send_control_custom_clicks).setEnabled(hidMode); 169 | return super.onCreateOptionsMenu(menu); 170 | } 171 | 172 | @Override 173 | public boolean onOptionsItemSelected(MenuItem item) { 174 | switch (item.getItemId()) { 175 | case R.id.connect: 176 | connect(); 177 | break; 178 | case R.id.disconnect: 179 | disconnect(); 180 | break; 181 | case R.id.toggle_alarm: 182 | mAlarmOn = !mAlarmOn; 183 | pStrong(getString(R.string.toggle_alarm) + " " + mAlarmOn); 184 | mMyntManager.alarmLong(mDeviceSn, mAlarmOn); 185 | break; 186 | case R.id.request_rssi: 187 | pStrong(getString(R.string.request_rssi)); 188 | mMyntManager.requestRssi(mDeviceSn); 189 | break; 190 | case R.id.request_battery: 191 | pStrong(getString(R.string.request_battery)); 192 | mMyntManager.requestBattery(mDeviceSn); 193 | break; 194 | case R.id.request_info: 195 | pStrong(getString(R.string.request_info)); 196 | mMyntManager.requestInfo(mDeviceSn); 197 | break; 198 | case R.id.request_control_custom_action: 199 | pStrong(getString(R.string.request_control_custom_action)); 200 | mMyntManager.requestControlCustomAction(mDeviceSn); 201 | break; 202 | case R.id.send_control_mode: 203 | new AlertDialog.Builder(this) 204 | .setTitle(R.string.send_control_mode) 205 | .setItems(R.array.control_modes, new DialogInterface.OnClickListener() { 206 | @Override 207 | public void onClick(DialogInterface dialog, int which) { 208 | ControlMode mode = ControlMode.get(which + 1); 209 | pStrong(getString(R.string.send_control_mode) + " " + mode.name()); 210 | mMyntManager.sendControlMode(mDeviceSn, mode); 211 | updateInfo(mDeviceSn, mode); 212 | } 213 | }) 214 | .show(); 215 | break; 216 | case R.id.send_control_custom_clicks: 217 | pStrong(getString(R.string.send_control_custom_clicks)); 218 | mMyntManager.sendControlCustomClicks(mDeviceSn); 219 | updateInfo(mDeviceSn, ControlMode.CONTROL_MODE_CUSTOM); 220 | break; 221 | case R.id.update_firmware: 222 | updateFirmware(); 223 | break; 224 | case R.id.setup_ble_mode: 225 | pStrong(getString(R.string.setup_ble_mode)); 226 | mMyntManager.setupConnectBLE(mDeviceSn); 227 | break; 228 | case R.id.setup_hid_mode: 229 | pStrong(getString(R.string.setup_hid_mode)); 230 | mMyntManager.setupConnectHID(mDeviceSn); 231 | break; 232 | case R.id.clear_history: 233 | mViewAdapter.clear(); 234 | break; 235 | } 236 | return super.onOptionsItemSelected(item); 237 | } 238 | 239 | private void connect() { 240 | pStrong("connect"); 241 | // should not use the input device directly whose state will be incorrect 242 | boolean ok = mMyntManager.connect(mDeviceSn); 243 | if (!ok) pWarn("connect failed"); 244 | } 245 | 246 | private void disconnect() { 247 | // keepSystemBond false 248 | pStrong("disconnect"); 249 | mMyntManager.disconnect(mDeviceSn, false); 250 | } 251 | 252 | private void updateFirmware() { 253 | String msg = null; 254 | if (mUpdating) { 255 | msg = "firmware is updating now"; 256 | return; 257 | } 258 | if (!mMyntManager.checkOADService(mDeviceSn)) { 259 | msg = "firmware update service is failed"; 260 | } 261 | if (msg != null) { 262 | pWarn(msg); 263 | ToastUtils.show(this, msg); 264 | return; 265 | } 266 | UpdateDialogFragment.show(this, mDeviceSn).setOnUpdateSelectListener(this); 267 | } 268 | 269 | @Override 270 | public void onUpdateSelect(String filepath, boolean formAssets) { 271 | pStrong("select: " + filepath); 272 | mMyntManager.sendOADFile(mDeviceSn, filepath, formAssets, createUpdateCallback()); 273 | } 274 | 275 | private OADUpdater.Callback createUpdateCallback() { 276 | return new OADUpdater.Callback() { 277 | 278 | long mTimeStart; 279 | PowerManager.WakeLock mWakeLock; 280 | 281 | @Override 282 | public void onError(OADUpdater updater, int errorCode) { 283 | String errorMsg = errorCode + ": " + OADUpdater.errorToString(errorCode); 284 | pWarn("update error: " + errorMsg); 285 | ToastUtils.show(ControlActivity.this, errorMsg); 286 | mUpdating = false; 287 | } 288 | 289 | @Override 290 | public void onPrepare(OADUpdater updater) { 291 | pStrong("update: prepare"); 292 | } 293 | 294 | @Override 295 | public void onStart(OADUpdater updater) { 296 | pStrong("update: start to send"); 297 | mTimeStart = System.currentTimeMillis(); 298 | PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 299 | mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "StayWake"); 300 | mWakeLock.acquire(); 301 | mUpdating = true; 302 | } 303 | 304 | @Override 305 | public void onProgress(OADUpdater updater, float progress) { 306 | final OADProgInfo info = updater.getOADProgInfo(); 307 | Log.i(TAG, String.format( 308 | "progress: %.2f; trans blocks: %d; total blocks: %d", 309 | progress, info.iBlocks(), info.nBlocks())); 310 | setTitle(String.format("%.1f%%, %d/%d blocks", progress, info.iBlocks(), info.nBlocks())); 311 | } 312 | 313 | @Override 314 | public void onStop(OADUpdater updater, boolean success) { 315 | final long elapsed = System.currentTimeMillis() - mTimeStart; 316 | String result = (success ? "success" : "failed"); 317 | pStrong("update: " + result + "; elapsed: " + (elapsed / 1000) + "s"); 318 | if (mWakeLock != null) { 319 | mWakeLock.release(); 320 | } 321 | mWakeLock = null; 322 | mUpdating = false; 323 | setTitle(mDeviceSn); 324 | ToastUtils.show(ControlActivity.this, "Update " + result); 325 | 326 | // disconnect if success for reconnecting it much quickly 327 | if (success) { 328 | mMyntManager.disconnect(mDeviceSn); 329 | } 330 | } 331 | }; 332 | } 333 | 334 | @Override 335 | public void onAttachedToWindow() { 336 | super.onAttachedToWindow(); 337 | connect(); 338 | } 339 | 340 | @Override 341 | public void onDetachedFromWindow() { 342 | super.onDetachedFromWindow(); 343 | disconnect(); 344 | } 345 | 346 | @Override 347 | protected void onDestroy() { 348 | super.onDestroy(); 349 | mMyntManager.removePairCallback(this); 350 | mMyntManager.removeEventCallback(this); 351 | } 352 | 353 | @Override 354 | public void finish() { 355 | super.finish(); 356 | overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right); 357 | } 358 | 359 | private void updateState(State state) { 360 | mState = state; 361 | invalidateOptionsMenu(); 362 | } 363 | 364 | @Override 365 | public void pairConnectStart(Device device) { 366 | pInfo("pairConnectStart"); 367 | updateState(State.Connecting); 368 | } 369 | 370 | @Override 371 | public void pairConnectOver(Device device, int errorCode, int status) { 372 | if (errorCode == 0) { 373 | // success 374 | pInfo("pairConnectOver: %d, %d", errorCode, status); 375 | updateState(State.Connected); 376 | } else { 377 | // failed, then will disconnect 378 | pError("pairConnectOver: %d, %d", errorCode, status); 379 | } 380 | } 381 | 382 | @Override 383 | public void pairServicesDiscovered(Device device, boolean success) { 384 | if (success) { 385 | pInfo("pairServicesDiscovered: true"); 386 | updateState(State.Binding); 387 | } else { 388 | pError("pairServicesDiscovered: false"); 389 | } 390 | } 391 | 392 | @Override 393 | public void pairServicesDiscoverTimeout(Device device) { 394 | pWarn("pairServicesDiscoverTimeout"); 395 | } 396 | 397 | @Override 398 | public void pairBindOver(Device device) { 399 | pInfo("pairBindOver:\n" 400 | + " connectMode: %s\n" 401 | + " controlMode: %s", 402 | device.connectMode.name(), 403 | device.controlMode.name()); 404 | updateState(State.Bound); 405 | updateInfo(device); 406 | // get the firmware info once bound 407 | pStrong(getString(R.string.request_info)); 408 | mMyntManager.requestInfo(mDeviceSn); 409 | } 410 | 411 | @Override 412 | public void pairDisconnect(Device device, boolean fromUser) { 413 | pInfo("pairDisconnect: %s", fromUser); 414 | updateState(State.Disconnected); 415 | } 416 | 417 | @Override 418 | public void pairDisconnectError(Device device, int errorCode, int status) { 419 | pWarn("pairDisconnectError: %d, %d", errorCode, status); 420 | } 421 | 422 | @Override 423 | public void eventFired(Device device, ClickEvent event) { 424 | pInfo("eventFired: %s", event.name()); 425 | } 426 | 427 | @Override 428 | public void eventFired(Device device, ActionEvent event) { 429 | pInfo("eventFired: %s", event.name()); 430 | } 431 | 432 | @Override 433 | public void eventInfoChanged(Device device, Device.Info info) { 434 | pInfo("eventInfoChanged:\n" 435 | + " firmware: %s\n" 436 | + " hardware: %s\n" 437 | + " software: %s", 438 | info.firmwareVersion(), 439 | info.hardwareVersion(), 440 | info.softwareVersion()); 441 | } 442 | 443 | @Override 444 | public void eventActionChanged(Device device, Device.Action action) { 445 | pInfo("eventActionChanged:\n" 446 | + " click: %s\n" 447 | + " doubleClick: %s\n" 448 | + " tripleClick: %s\n" 449 | + " longClick: %s\n" 450 | + " clickHold: %s", 451 | action.click, 452 | action.doubleClick, 453 | action.tripleClick, 454 | action.longClick, 455 | action.clickHold); 456 | } 457 | 458 | @Override 459 | public void eventRssiChanged(Device device, int rssi) { 460 | pInfo("eventRssiChanged: %s", rssi); 461 | } 462 | 463 | @Override 464 | public void eventBatteryChanged(Device device, int battery) { 465 | pInfo("eventBatteryChanged: %s", battery); 466 | } 467 | 468 | @Override 469 | public void eventAlarmChanged(Device device, boolean on) { 470 | pInfo("eventAlarmChanged: %s", on); 471 | } 472 | 473 | @Override 474 | public String keyStored(Device device) { 475 | pStrong("please click the MYNT button to pair"); 476 | return null; 477 | } 478 | 479 | @Override 480 | public void keyPaired(Device device, String password) { 481 | pInfo("keyPaired: %s", password); 482 | } 483 | 484 | private void pInfo(String msg, Object... args) { 485 | LogUtils.i(TAG, msg, args); 486 | pushInfo(Color.GRAY, msg, args); 487 | } 488 | 489 | private void pStrong(String msg, Object... args) { 490 | LogUtils.i(TAG, msg, args); 491 | pushInfo(ContextCompat.getColor(this, R.color.blue), msg, args); 492 | } 493 | 494 | private void pWarn(String msg, Object... args) { 495 | LogUtils.w(TAG, msg, args); 496 | pushInfo(ContextCompat.getColor(this, R.color.orange), msg, args); 497 | } 498 | 499 | private void pError(String msg, Object... args) { 500 | LogUtils.e(TAG, msg, args); 501 | pushInfo(ContextCompat.getColor(this, R.color.red), msg, args); 502 | } 503 | 504 | private void pushInfo(@ColorInt int color, String msg, Object... args) { 505 | mViewAdapter.pushInfo(color, mDateFormat.format(new Date()) + " " + String.format(msg, args)); 506 | } 507 | 508 | public static class Info { 509 | 510 | public int color; 511 | public String text; 512 | 513 | public Info(int color, String text) { 514 | this.color = color; 515 | this.text = text; 516 | } 517 | } 518 | 519 | public static class ViewHolder extends RecyclerView.ViewHolder { 520 | 521 | public TextView textInfo; 522 | 523 | public ViewHolder(View itemView) { 524 | super(itemView); 525 | textInfo = KitUtils.findById(itemView, R.id.tv_info); 526 | } 527 | } 528 | 529 | public static class ViewAdapter extends RecyclerView.Adapter { 530 | 531 | private LinkedList mInfos; 532 | 533 | public ViewAdapter() { 534 | mInfos = new LinkedList<>(); 535 | } 536 | 537 | public void pushInfo(@ColorInt int color, String text) { 538 | mInfos.push(new Info(color, text)); 539 | notifyDataSetChanged(); 540 | } 541 | 542 | public void clear() { 543 | mInfos.clear(); 544 | notifyDataSetChanged(); 545 | } 546 | 547 | @Override 548 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 549 | Context context = parent.getContext(); 550 | View v = LayoutInflater.from(context).inflate(R.layout.item_info, parent, false); 551 | return new ViewHolder(v); 552 | } 553 | 554 | @Override 555 | public void onBindViewHolder(ViewHolder holder, int position) { 556 | final Info info = mInfos.get(position); 557 | holder.textInfo.setText(info.text); 558 | holder.textInfo.setTextColor(info.color); 559 | } 560 | 561 | @Override 562 | public int getItemCount() { 563 | return mInfos.size(); 564 | } 565 | } 566 | } 567 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/java/com/slightech/mynt/sdk/demo/ui/SearchActivity.java: -------------------------------------------------------------------------------- 1 | package com.slightech.mynt.sdk.demo.ui; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.support.v7.widget.Toolbar; 9 | import android.view.LayoutInflater; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.TextView; 15 | 16 | import com.slightech.bluetooth.BluetoothUtils; 17 | import com.slightech.bluetooth.le.LeState; 18 | import com.slightech.mynt.api.MyntManager; 19 | import com.slightech.mynt.api.callback.FoundCallback; 20 | import com.slightech.mynt.api.model.Device; 21 | import com.slightech.mynt.sdk.demo.MyApplication; 22 | import com.slightech.mynt.sdk.demo.R; 23 | import com.slightech.mynt.sdk.demo.ui.base.BaseActivity; 24 | import com.slightech.mynt.sdk.demo.util.KitUtils; 25 | import com.slightech.mynt.sdk.demo.util.LogUtils; 26 | import com.slightech.mynt.sdk.demo.util.ToastUtils; 27 | 28 | import java.util.Collections; 29 | import java.util.Comparator; 30 | import java.util.Iterator; 31 | import java.util.LinkedList; 32 | 33 | public class SearchActivity extends BaseActivity implements FoundCallback { 34 | 35 | static final String TAG = "SearchActivity"; 36 | 37 | private RecyclerView mRecyclerView; 38 | private ViewAdapter mViewAdapter; 39 | 40 | private MyntManager mMyntManager; 41 | 42 | private boolean mSearching = false; 43 | 44 | @Override 45 | protected void onCreate(Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | setContentView(R.layout.activity_search); 48 | setTitle(R.string.title_search); 49 | initViews(); 50 | initObjects(); 51 | } 52 | 53 | private void initViews() { 54 | Toolbar bar = KitUtils.findById(this, R.id.bar); 55 | if (bar != null) { 56 | setSupportActionBar(bar); 57 | } 58 | 59 | mRecyclerView = KitUtils.findById(this, R.id.recycler); 60 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); 61 | mRecyclerView.setHasFixedSize(true); 62 | 63 | mViewAdapter = new ViewAdapter(); 64 | mViewAdapter.setOnDeviceClickListener(new ViewAdapter.OnDeviceClickListener() { 65 | @Override 66 | public void onItemViewClick(View view, int position, Device device) { 67 | ControlActivity.start(SearchActivity.this, device); 68 | } 69 | }); 70 | mRecyclerView.setAdapter(mViewAdapter); 71 | } 72 | 73 | private void initObjects() { 74 | mMyntManager = MyApplication.getMyntManager(); 75 | // don't forget to remove callbacks in `onDestroy` 76 | mMyntManager.addFoundCallback(this); 77 | } 78 | 79 | @Override 80 | public boolean onCreateOptionsMenu(Menu menu) { 81 | getMenuInflater().inflate(R.menu.search, menu); 82 | menu.findItem(R.id.start).setVisible(!mSearching); 83 | menu.findItem(R.id.stop).setVisible(mSearching); 84 | return super.onCreateOptionsMenu(menu); 85 | } 86 | 87 | @Override 88 | public boolean onOptionsItemSelected(MenuItem item) { 89 | switch (item.getItemId()) { 90 | case R.id.start: startSearch(); break; 91 | case R.id.stop: stopSearch(true); break; 92 | } 93 | return super.onOptionsItemSelected(item); 94 | } 95 | 96 | private void startSearch() { 97 | LogUtils.i(TAG, "startSearch"); 98 | mMyntManager.startSearch(); 99 | mSearching = true; 100 | invalidateOptionsMenu(); 101 | } 102 | 103 | private void stopSearch(boolean fromUser) { 104 | LogUtils.i(TAG, "stopSearch"); 105 | mMyntManager.stopSearch(); 106 | mSearching = false; 107 | invalidateOptionsMenu(); 108 | if (fromUser) { 109 | mViewAdapter.clear(); 110 | } 111 | } 112 | 113 | @Override 114 | public void onAttachedToWindow() { 115 | super.onAttachedToWindow(); 116 | startSearch(); 117 | } 118 | 119 | @Override 120 | public void onDetachedFromWindow() { 121 | super.onDetachedFromWindow(); 122 | stopSearch(false); 123 | } 124 | 125 | @Override 126 | protected void onDestroy() { 127 | super.onDestroy(); 128 | mMyntManager.removeFoundCallback(this); 129 | } 130 | 131 | @Override 132 | public void foundFailed(int errorCode) { 133 | LogUtils.i(TAG, "foundFailed: %d", errorCode); 134 | stopSearch(false); 135 | switch (errorCode) { 136 | case LeState.ERROR_BLE_NOT_SUPPORTED: 137 | ToastUtils.show(this, R.string.error_ble_not_supported); 138 | break; 139 | case LeState.ERROR_BLUETOOTH_NOT_SUPPORTED: 140 | ToastUtils.show(this, R.string.error_bluetooth_not_supported); 141 | break; 142 | case LeState.ERROR_BLUETOOTH_OFF: 143 | BluetoothUtils.requestEnable(this); 144 | break; 145 | case LeState.ERROR_SCAN_FAILED: 146 | ToastUtils.show(this, R.string.error_scan_failed); 147 | break; 148 | } 149 | } 150 | 151 | @Override 152 | public void foundDevice(Device device, boolean newOne) { 153 | LogUtils.i(TAG, "foundDevice: %s, %s", device.sn, newOne); 154 | mViewAdapter.addDevice(device); 155 | } 156 | 157 | @Override 158 | public void foundDeviceRemoved(Device device) { 159 | LogUtils.i(TAG, "foundDeviceRemoved"); 160 | mViewAdapter.removeDevice(device); 161 | } 162 | 163 | public static class ViewHolder extends RecyclerView.ViewHolder { 164 | 165 | public TextView textName; 166 | public TextView textAddr; 167 | public TextView textRssi; 168 | 169 | public ViewHolder(View itemView) { 170 | super(itemView); 171 | textName = KitUtils.findById(itemView, R.id.tv_name); 172 | textAddr = KitUtils.findById(itemView, R.id.tv_addr); 173 | textRssi = KitUtils.findById(itemView, R.id.tv_rssi); 174 | } 175 | } 176 | 177 | public static class ViewAdapter extends RecyclerView.Adapter 178 | implements View.OnClickListener { 179 | 180 | static final String FILTER_NAME_PREFIX = "MYNT"; 181 | 182 | private LinkedList mDevices; 183 | private LinkedList mDevicesCache; 184 | 185 | private long mLastUpdateMillis; 186 | 187 | private OnDeviceClickListener mOnDeviceClickListener; 188 | 189 | public ViewAdapter() { 190 | mDevices = new LinkedList<>(); 191 | mDevicesCache = new LinkedList<>(); 192 | } 193 | 194 | public void setOnDeviceClickListener(@Nullable OnDeviceClickListener listener) { 195 | mOnDeviceClickListener = listener; 196 | } 197 | 198 | public void addDevice(Device device) { 199 | Device deviceCache = containsDevice(mDevicesCache, device.sn); 200 | if (deviceCache != null) { 201 | return; 202 | } 203 | Device deviceNow = containsDevice(mDevices, device.sn); 204 | if (deviceNow == null) { 205 | mDevicesCache.add(device); 206 | } 207 | notifyDevicesChanged(true); 208 | } 209 | 210 | public void removeDevice(Device device) { 211 | Device deviceNow = removeDevice(mDevices, device.sn); 212 | if (deviceNow == null) { 213 | removeDevice(mDevicesCache, device.sn); 214 | return; 215 | } 216 | notifyDevicesChanged(false); 217 | } 218 | 219 | public void clear() { 220 | mDevices.clear(); 221 | mDevicesCache.clear(); 222 | notifyDataSetChanged(); 223 | } 224 | 225 | private Device containsDevice(LinkedList devices, String sn) { 226 | for (Device device : devices) { 227 | if (sn.equals(device.sn)) { 228 | return device; 229 | } 230 | } 231 | return null; 232 | } 233 | 234 | private Device removeDevice(LinkedList devices, String sn) { 235 | Iterator it = devices.iterator(); 236 | Device device; 237 | while (it.hasNext()) { 238 | device = it.next(); 239 | if (sn.equals(device.sn)) { 240 | it.remove(); 241 | return device; 242 | } 243 | } 244 | return null; 245 | } 246 | 247 | private void notifyDevicesChanged(boolean delayWithCache) { 248 | if (delayWithCache && (System.currentTimeMillis() - mLastUpdateMillis < 1000)) { 249 | return; 250 | } 251 | mLastUpdateMillis = System.currentTimeMillis(); 252 | 253 | if (!mDevicesCache.isEmpty()) { 254 | mDevices.addAll(mDevicesCache); 255 | mDevicesCache.clear(); 256 | } 257 | sortDevices(); 258 | notifyDataSetChanged(); 259 | } 260 | 261 | private void sortDevices() { 262 | Collections.sort(mDevices, new Comparator() { 263 | @Override 264 | public int compare(Device lhs, Device rhs) { 265 | String lhsName = lhs.name; 266 | String rhsName = rhs.name; 267 | int rssiDiff = rhs.rssi - lhs.rssi; 268 | if (isEmpty(lhsName)) { 269 | if (isEmpty(rhsName)) return rssiDiff; 270 | else return 1; 271 | } else if (isMynt(lhsName)) { 272 | if (isEmpty(rhsName)) return -1; 273 | else if (isMynt(rhsName)) return rssiDiff; 274 | else return -1; 275 | } else { 276 | if (isEmpty(rhsName)) return -1; 277 | else if (isMynt(rhsName)) return 1; 278 | else return rssiDiff; 279 | } 280 | } 281 | 282 | private boolean isEmpty(String s) { 283 | return s == null || s.length() <= 0; 284 | } 285 | 286 | private boolean isMynt(String s) { 287 | return s.startsWith(FILTER_NAME_PREFIX); 288 | } 289 | }); 290 | } 291 | 292 | @Override 293 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 294 | Context context = parent.getContext(); 295 | View v = LayoutInflater.from(context).inflate(R.layout.item_device, parent, false); 296 | v.setOnClickListener(this); 297 | return new ViewHolder(v); 298 | } 299 | 300 | @Override 301 | public void onBindViewHolder(ViewHolder holder, int position) { 302 | holder.itemView.setTag(position); 303 | 304 | final Device device = mDevices.get(position); 305 | holder.textName.setText(device.name + " " + device.sn); 306 | holder.textAddr.setText(device.address); 307 | holder.textRssi.setText(device.rssi + "dB"); 308 | } 309 | 310 | @Override 311 | public int getItemCount() { 312 | return mDevices.size(); 313 | } 314 | 315 | @Override 316 | public void onClick(View v) { 317 | if (mOnDeviceClickListener != null) { 318 | final int position = (int) v.getTag(); 319 | mOnDeviceClickListener.onItemViewClick(v, position, mDevices.get(position)); 320 | } 321 | } 322 | 323 | public interface OnDeviceClickListener { 324 | void onItemViewClick(View view, int position, Device device); 325 | } 326 | } 327 | 328 | //private final int REQ_ACCESS_LOCATION = 1; 329 | 330 | /** 331 | *

Android 6.0 及以上蓝牙能够扫描到设备,需要蓝牙和定位都开启时才行,如果 targetSdkVersion >= 23。 332 | * 333 | *

http://stackoverflow.com/questions/32708374/bluetooth-le-scanfilters-dont-work-on-android-m 334 | * 335 | *

java.lang.SecurityException: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results 336 | */ 337 | /*private void requestPermissions() { 338 | if (!PermissionUtils.checkPermissionsGranted(this, ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION)) { 339 | requestPermissions(new String[]{ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}, 340 | "Request ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION to get scan results for Bluetooth LE", 341 | REQ_ACCESS_LOCATION); 342 | } 343 | } 344 | 345 | private void requestPermissions(@NonNull final String[] permissions, 346 | @Nullable final String explanation, 347 | final int requestCode) { 348 | if (PermissionUtils.shouldPermissionsShowRationale(this, permissions)) { 349 | if (explanation == null) { 350 | return; 351 | } 352 | new AlertDialog.Builder(this) 353 | .setTitle("Request Permissions") 354 | .setMessage(explanation) 355 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 356 | @Override 357 | public void onClick(DialogInterface dialog, int which) { 358 | ActivityCompat.requestPermissions(SearchActivity.this, 359 | permissions, requestCode); 360 | } 361 | }) 362 | .show(); 363 | } else { 364 | ActivityCompat.requestPermissions(this, permissions, requestCode); 365 | } 366 | } 367 | 368 | @Override 369 | public void onRequestPermissionsResult(int requestCode, 370 | @NonNull String[] permissions, 371 | @NonNull int[] grantResults) { 372 | if (requestCode == REQ_ACCESS_LOCATION) { 373 | if (PermissionUtils.verifyPermission(grantResults)) { 374 | ToastUtils.show(this, "Permission was granted, yay!"); 375 | } else { 376 | ToastUtils.show(this, "Permission denied, boo!"); 377 | } 378 | } else { 379 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 380 | } 381 | }*/ 382 | } 383 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/java/com/slightech/mynt/sdk/demo/ui/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.slightech.mynt.sdk.demo.ui.base; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | 5 | public class BaseActivity extends AppCompatActivity { 6 | } 7 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/java/com/slightech/mynt/sdk/demo/ui/base/BaseDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.slightech.mynt.sdk.demo.ui.base; 2 | 3 | import android.support.v7.app.AppCompatDialogFragment; 4 | 5 | public abstract class BaseDialogFragment extends AppCompatDialogFragment { 6 | } 7 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/java/com/slightech/mynt/sdk/demo/ui/dialog/UpdateDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.slightech.mynt.sdk.demo.ui.dialog; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.app.AlertDialog; 6 | import android.app.Dialog; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.database.Cursor; 10 | import android.net.Uri; 11 | import android.os.Bundle; 12 | import android.support.annotation.NonNull; 13 | import android.view.View; 14 | import android.widget.AdapterView; 15 | import android.widget.ArrayAdapter; 16 | import android.widget.ListView; 17 | 18 | import com.slightech.mynt.sdk.demo.Firmware; 19 | import com.slightech.mynt.sdk.demo.R; 20 | import com.slightech.mynt.sdk.demo.ui.base.BaseActivity; 21 | import com.slightech.mynt.sdk.demo.ui.base.BaseDialogFragment; 22 | import com.slightech.mynt.sdk.demo.util.ToastUtils; 23 | 24 | import java.util.ArrayList; 25 | import java.util.Arrays; 26 | 27 | public class UpdateDialogFragment extends BaseDialogFragment implements 28 | ListView.OnItemClickListener { 29 | 30 | public static UpdateDialogFragment show(BaseActivity activity, String title) { 31 | UpdateDialogFragment dlg = new UpdateDialogFragment(); 32 | Bundle args = new Bundle(); 33 | args.putString("title", title); 34 | dlg.setArguments(args); 35 | dlg.show(activity.getSupportFragmentManager(), "dlg_update"); 36 | return dlg; 37 | } 38 | 39 | private final String SELECT_BIN_FILE = "Select external bin file"; 40 | private final int REQ_SELECT = 1; 41 | 42 | private ListView mListView; 43 | 44 | private String mTitle; 45 | 46 | private ArrayList mData; 47 | 48 | private OnUpdateSelectListener mListener; 49 | 50 | public UpdateDialogFragment setOnUpdateSelectListener(OnUpdateSelectListener l) { 51 | mListener = l; 52 | return this; 53 | } 54 | 55 | @Override 56 | public void onDetach() { 57 | super.onDetach(); 58 | mListener = null; 59 | } 60 | 61 | @Override 62 | public void onCreate(Bundle savedInstanceState) { 63 | super.onCreate(savedInstanceState); 64 | Bundle args = getArguments(); 65 | mTitle = args.getString("title"); 66 | } 67 | 68 | @NonNull 69 | @Override 70 | public Dialog onCreateDialog(Bundle savedInstanceState) { 71 | Activity activity = getActivity(); 72 | @SuppressLint("InflateParams") 73 | View v = activity.getLayoutInflater().inflate(R.layout.frag_dlg_update, null); 74 | 75 | ListView list = (ListView) v.findViewById(R.id.list); 76 | list.setOnItemClickListener(this); 77 | mListView = list; 78 | 79 | updateListView(); 80 | 81 | AlertDialog.Builder b = new AlertDialog.Builder(activity) 82 | .setTitle(mTitle) 83 | .setView(v) 84 | .setNegativeButton(android.R.string.no, null); 85 | return b.create(); 86 | } 87 | 88 | private void updateListView() { 89 | ArrayList data = new ArrayList<>(); 90 | data.addAll(Arrays.asList(Firmware.FILES)); 91 | data.add(SELECT_BIN_FILE); 92 | mData = data; 93 | mListView.setAdapter(new ArrayAdapter<>(getActivity(), 94 | android.R.layout.simple_list_item_1, 95 | android.R.id.text1, data)); 96 | } 97 | 98 | @Override 99 | public void onItemClick(AdapterView parent, View view, int position, long id) { 100 | int end = mData.size() - 1; 101 | if (position == end) { 102 | showFileChooser(); 103 | } else { 104 | onUpdateSelect(mData.get(position), true); 105 | } 106 | } 107 | 108 | private void showFileChooser() { 109 | Intent intent = new Intent(Intent.ACTION_GET_CONTENT); 110 | intent.setType("*/*"); 111 | intent.addCategory(Intent.CATEGORY_OPENABLE); 112 | try { 113 | startActivityForResult(Intent.createChooser(intent, SELECT_BIN_FILE), REQ_SELECT); 114 | } catch (android.content.ActivityNotFoundException ex) { 115 | ToastUtils.show(getActivity(), "Please install a File Manager."); 116 | } 117 | } 118 | 119 | @Override 120 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 121 | switch (requestCode) { 122 | case REQ_SELECT: 123 | if (resultCode == Activity.RESULT_OK) { 124 | Uri uri = data.getData(); 125 | String path = getPath(getActivity(), uri); 126 | onUpdateSelect(path, false); 127 | } 128 | break; 129 | } 130 | } 131 | 132 | private String getPath(Context context, Uri uri) { 133 | if ("content".equalsIgnoreCase(uri.getScheme())) { 134 | String[] projection = {"_data"}; 135 | try { 136 | Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); 137 | if (cursor != null) { 138 | int index = cursor.getColumnIndexOrThrow("_data"); 139 | if (cursor.moveToFirst()) { 140 | return cursor.getString(index); 141 | } 142 | cursor.close(); 143 | } 144 | } catch (Exception e) { 145 | e.printStackTrace(); 146 | } 147 | } else if ("file".equalsIgnoreCase(uri.getScheme())) { 148 | return uri.getPath(); 149 | } 150 | return null; 151 | } 152 | 153 | private void onUpdateSelect(String path, boolean formAssets) { 154 | if (mListener != null && path != null) { 155 | mListener.onUpdateSelect(path, formAssets); 156 | } 157 | if (path != null) { 158 | dismiss(); 159 | } 160 | } 161 | 162 | public interface OnUpdateSelectListener { 163 | void onUpdateSelect(String filepath, boolean formAssets); 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/java/com/slightech/mynt/sdk/demo/util/KitUtils.java: -------------------------------------------------------------------------------- 1 | package com.slightech.mynt.sdk.demo.util; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.view.View; 6 | 7 | public class KitUtils { 8 | 9 | @SuppressWarnings({ "unchecked", "UnusedDeclaration" }) 10 | public static T findById(View view, int id) { 11 | return (T) view.findViewById(id); 12 | } 13 | 14 | @SuppressWarnings({ "unchecked", "UnusedDeclaration" }) 15 | public static T findById(Activity activity, int id) { 16 | return (T) activity.findViewById(id); 17 | } 18 | 19 | /** 20 | * Return the handle to a system-level service by name. 21 | * 22 | * @see Context#getSystemService(String) 23 | */ 24 | @SuppressWarnings({ "unchecked", "UnusedDeclaration" }) 25 | public static T getService(Context context, String name) { 26 | return (T) context.getSystemService(name); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/java/com/slightech/mynt/sdk/demo/util/LogUtils.java: -------------------------------------------------------------------------------- 1 | package com.slightech.mynt.sdk.demo.util; 2 | 3 | import android.util.Log; 4 | 5 | import com.slightech.mynt.sdk.demo.BuildConfig; 6 | 7 | public class LogUtils { 8 | 9 | public static final boolean VISIBLE = BuildConfig.DEBUG; 10 | 11 | public static void v(String tag, String msg, Object... args) { 12 | println(Log.VERBOSE, tag, msg, args); 13 | } 14 | 15 | public static void d(String tag, String msg, Object... args) { 16 | println(Log.DEBUG, tag, msg, args); 17 | } 18 | 19 | public static void i(String tag, String msg, Object... args) { 20 | println(Log.INFO, tag, msg, args); 21 | } 22 | 23 | public static void w(String tag, String msg, Object... args) { 24 | println(Log.WARN, tag, msg, args); 25 | } 26 | 27 | public static void e(String tag, String msg, Object... args) { 28 | println(Log.ERROR, tag, msg, args); 29 | } 30 | 31 | public static void println(int priority, String tag, String msg, Object... args) { 32 | if (VISIBLE) Log.println(priority, tag, String.format(msg, args)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/java/com/slightech/mynt/sdk/demo/util/PermissionUtils.java: -------------------------------------------------------------------------------- 1 | package com.slightech.mynt.sdk.demo.util; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager; 6 | import android.support.annotation.NonNull; 7 | import android.support.v4.app.ActivityCompat; 8 | import android.support.v4.content.ContextCompat; 9 | 10 | /** 11 | * Utility class that wraps access to the runtime permissions API in M and provides basic helper 12 | * methods. 13 | * 14 | * @see PermissionUtil.java 16 | * @see RxPermissions 17 | */ 18 | public class PermissionUtils { 19 | 20 | /** 21 | * Check that the specific permission has been granted. 22 | */ 23 | public static boolean checkPermissionGranted(@NonNull Context context, 24 | @NonNull String permission) { 25 | return ContextCompat.checkSelfPermission(context, permission) 26 | == PackageManager.PERMISSION_GRANTED; 27 | } 28 | 29 | /** 30 | * Check that all specific permissions have been granted. 31 | * 32 | * @see ContextCompat#checkSelfPermission(Context, String) 33 | */ 34 | public static boolean checkPermissionsGranted(@NonNull Context context, 35 | @NonNull String... permissions) { 36 | for (String permission : permissions) { 37 | if (ContextCompat.checkSelfPermission(context, permission) 38 | != PackageManager.PERMISSION_GRANTED) { 39 | return false; 40 | } 41 | } 42 | return true; 43 | } 44 | 45 | /** 46 | * Check that should show request permission rationale. 47 | */ 48 | public static boolean shouldPermissionShowRationale(@NonNull Activity activity, 49 | @NonNull String permission) { 50 | return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission); 51 | } 52 | 53 | /** 54 | * Check that should show request permission rationale. 55 | * 56 | * @see ActivityCompat#shouldShowRequestPermissionRationale(Activity, String) 57 | */ 58 | public static boolean shouldPermissionsShowRationale(@NonNull Activity activity, 59 | @NonNull String... permissions) { 60 | for (String permission : permissions) { 61 | if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) { 62 | return true; 63 | } 64 | } 65 | return false; 66 | } 67 | 68 | /** 69 | * Check that the given permission has been granted. 70 | */ 71 | public static boolean verifyPermission(int[] grantResults) { 72 | return grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED; 73 | } 74 | 75 | /** 76 | * Check that all given permissions have been granted by verifying that each entry in the 77 | * given array is of the value {@link PackageManager#PERMISSION_GRANTED}. 78 | * 79 | * @see Activity#onRequestPermissionsResult(int, String[], int[]) 80 | */ 81 | public static boolean verifyPermissions(int[] grantResults) { 82 | // At least one result must be checked. 83 | if(grantResults.length < 1){ 84 | return false; 85 | } 86 | 87 | // Verify that each required permission has been granted, otherwise return false. 88 | for (int result : grantResults) { 89 | if (result != PackageManager.PERMISSION_GRANTED) { 90 | return false; 91 | } 92 | } 93 | return true; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/java/com/slightech/mynt/sdk/demo/util/ToastUtils.java: -------------------------------------------------------------------------------- 1 | package com.slightech.mynt.sdk.demo.util; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.StringRes; 5 | 6 | public class ToastUtils { 7 | 8 | public static void show(Context context, @StringRes int resId) { 9 | if (context == null) return; 10 | android.widget.Toast.makeText(context, resId, android.widget.Toast.LENGTH_SHORT).show(); 11 | } 12 | 13 | public static void show(Context context, CharSequence text) { 14 | if (context == null) return; 15 | android.widget.Toast.makeText(context, text, android.widget.Toast.LENGTH_SHORT).show(); 16 | } 17 | 18 | public static void showLong(Context context, @StringRes int resId) { 19 | if (context == null) return; 20 | android.widget.Toast.makeText(context, resId, android.widget.Toast.LENGTH_LONG).show(); 21 | } 22 | 23 | public static void showLong(Context context, CharSequence text) { 24 | if (context == null) return; 25 | android.widget.Toast.makeText(context, text, android.widget.Toast.LENGTH_LONG).show(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/anim/slide_in_left.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/anim/slide_in_right.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/anim/slide_out_left.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/anim/slide_out_right.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/layout/activity_control.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 20 | 21 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/layout/activity_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 18 | 19 | 23 | 24 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/layout/frag_dlg_update.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/layout/item_device.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 17 | 22 | 25 | 30 | 33 | 38 | 41 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/layout/item_info.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/menu/control.xml: -------------------------------------------------------------------------------- 1 | 2 |

4 | 8 | 12 | 13 | 18 | 23 | 28 | 33 | 38 | 39 | 44 | 49 | 50 | 55 | 60 | 65 | 66 | 71 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/menu/search.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/7c294d7d881bda54e19f21e899dc2bee02924fa3/demo/mynt-sdk-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/7c294d7d881bda54e19f21e899dc2bee02924fa3/demo/mynt-sdk-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/7c294d7d881bda54e19f21e899dc2bee02924fa3/demo/mynt-sdk-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/7c294d7d881bda54e19f21e899dc2bee02924fa3/demo/mynt-sdk-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slightech/MYNT-SDK-Android/7c294d7d881bda54e19f21e899dc2bee02924fa3/demo/mynt-sdk-demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #ff33b5e5 8 | #ff99cc00 9 | #ffffbb33 10 | #ffff4444 11 | 12 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/values/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 300 4 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MYNT Demo 3 | 4 | Search MYNTs 5 | Control MYNTs 6 | 7 | Start 8 | Stop 9 | 10 | Connect 11 | Disconnect 12 | 13 | Toggle alarm 14 | Request rssi 15 | Request battery 16 | Request info 17 | Request control custom action 18 | Send control mode 19 | Send control custom clicks 20 | Update Firmware 21 | Setup BLE mode 22 | Setup HID mode 23 | 24 | Clear history 25 | 26 | 27 | Music 28 | Camera 29 | PPT 30 | Custom 31 | Default 32 | 33 | 34 | Addr 35 | Rssi 36 | 37 | BLE not supported 38 | Bluetooth not supported 39 | Scan failed 40 | Address invalid 41 | Connect failed 42 | Discover failed 43 | Disconnect failed 44 | 45 | Click the button on MYNT device could wake it from sleep! 46 | 47 | 48 | -------------------------------------------------------------------------------- /demo/mynt-sdk-demo/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 |