├── .idea
├── .name
├── copyright
│ └── profiles_settings.xml
├── vcs.xml
├── inspectionProfiles
│ ├── profiles_settings.xml
│ └── Project_Default.xml
├── modules.xml
├── gradle.xml
├── compiler.xml
└── misc.xml
├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── web_hi_res_512.png
│ │ ├── res
│ │ │ ├── drawable-hdpi
│ │ │ │ ├── ic_mili.png
│ │ │ │ ├── ic_unit_cm.png
│ │ │ │ ├── ic_unit_kg.png
│ │ │ │ ├── ic_hint_mass.png
│ │ │ │ ├── ic_hint_name.png
│ │ │ │ ├── ic_hint_steps.png
│ │ │ │ ├── ic_gender_female.png
│ │ │ │ ├── ic_gender_male.png
│ │ │ │ ├── ic_hint_height.png
│ │ │ │ ├── ic_gender_male_disabled.png
│ │ │ │ └── ic_gender_female_disabled.png
│ │ │ ├── drawable-mdpi
│ │ │ │ ├── ic_mili.png
│ │ │ │ ├── ic_unit_cm.png
│ │ │ │ ├── ic_unit_kg.png
│ │ │ │ ├── ic_hint_mass.png
│ │ │ │ ├── ic_hint_name.png
│ │ │ │ ├── ic_hint_steps.png
│ │ │ │ ├── ic_gender_female.png
│ │ │ │ ├── ic_gender_male.png
│ │ │ │ ├── ic_hint_height.png
│ │ │ │ ├── ic_gender_male_disabled.png
│ │ │ │ └── ic_gender_female_disabled.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ ├── ic_mili.png
│ │ │ │ ├── ic_unit_cm.png
│ │ │ │ ├── ic_unit_kg.png
│ │ │ │ ├── ic_hint_mass.png
│ │ │ │ ├── ic_hint_name.png
│ │ │ │ ├── ic_gender_male.png
│ │ │ │ ├── ic_hint_height.png
│ │ │ │ ├── ic_hint_steps.png
│ │ │ │ ├── ic_gender_female.png
│ │ │ │ ├── ic_gender_female_disabled.png
│ │ │ │ └── ic_gender_male_disabled.png
│ │ │ ├── drawable-xxhdpi
│ │ │ │ ├── ic_mili.png
│ │ │ │ ├── ic_unit_cm.png
│ │ │ │ ├── ic_unit_kg.png
│ │ │ │ ├── ic_gender_male.png
│ │ │ │ ├── ic_hint_height.png
│ │ │ │ ├── ic_hint_mass.png
│ │ │ │ ├── ic_hint_name.png
│ │ │ │ ├── ic_hint_steps.png
│ │ │ │ ├── ic_gender_female.png
│ │ │ │ ├── ic_gender_male_disabled.png
│ │ │ │ └── ic_gender_female_disabled.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ │ ├── ic_mili.png
│ │ │ │ ├── ic_unit_cm.png
│ │ │ │ ├── ic_unit_kg.png
│ │ │ │ ├── ic_hint_mass.png
│ │ │ │ ├── ic_hint_name.png
│ │ │ │ ├── ic_hint_steps.png
│ │ │ │ ├── ic_gender_male.png
│ │ │ │ ├── ic_hint_height.png
│ │ │ │ ├── ic_gender_female.png
│ │ │ │ ├── ic_gender_male_disabled.png
│ │ │ │ └── ic_gender_female_disabled.png
│ │ │ ├── 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
│ │ │ │ ├── styles.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── attrs_steps_view.xml
│ │ │ │ └── strings.xml
│ │ │ ├── layout
│ │ │ │ ├── ab_custom.xml
│ │ │ │ ├── activity_search_band.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── search_scan.xml
│ │ │ │ ├── search_profile.xml
│ │ │ │ ├── card_steps.xml
│ │ │ │ └── band_profile.xml
│ │ │ ├── drawable
│ │ │ │ ├── radio_gender_female.xml
│ │ │ │ └── radio_gender_male.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ └── menu
│ │ │ │ └── menu_main.xml
│ │ ├── java
│ │ │ └── io
│ │ │ │ └── github
│ │ │ │ └── indrora
│ │ │ │ └── jouretnuit
│ │ │ │ ├── miband
│ │ │ │ ├── data
│ │ │ │ │ ├── IBinaryData.java
│ │ │ │ │ ├── ICharacteristicBound.java
│ │ │ │ │ ├── NotifyHandler.java
│ │ │ │ │ ├── BattStats.java
│ │ │ │ │ ├── UserInfo.java
│ │ │ │ │ ├── StepCount.java
│ │ │ │ │ └── BatteryInfo.java
│ │ │ │ ├── Helper.java
│ │ │ │ └── BandConstants.java
│ │ │ │ ├── model
│ │ │ │ ├── ActivityDay.java
│ │ │ │ ├── ActivityPeriod.java
│ │ │ │ └── StringConstants.java
│ │ │ │ ├── JENApp.java
│ │ │ │ ├── MiBandService.java
│ │ │ │ ├── SearchBandActivity.java
│ │ │ │ ├── widget
│ │ │ │ ├── StepsView.java
│ │ │ │ └── CircleGaugeView.java
│ │ │ │ ├── ActivityHistoryAdapter.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── MiBandDevice.java
│ │ └── AndroidManifest.xml
│ └── androidTest
│ │ └── java
│ │ └── io
│ │ └── github
│ │ └── indrora
│ │ └── jouretnuit
│ │ └── ApplicationTest.java
├── proguard-rules.pro
├── build.gradle
└── app.iml
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── account.svg
├── gender-female.svg
├── gender-male.svg
├── account-circle.svg
├── weight.svg
├── README
├── gradle.properties
├── Jour-et-Nuit.iml
├── .navigation
└── app
│ └── raw
│ └── main.nvg.xml
├── weight-kilogram.svg
├── LICENSE
├── user_height.svg
├── gradlew.bat
├── mili.svg
├── unit_kg.svg
├── unit_cm.svg
├── gradlew
└── protocol.txt
/.idea/.name:
--------------------------------------------------------------------------------
1 | Jour et Nuit
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/app/src/main/web_hi_res_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/web_hi_res_512.png
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_mili.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-hdpi/ic_mili.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_mili.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-mdpi/ic_mili.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_mili.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xhdpi/ic_mili.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_unit_cm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-hdpi/ic_unit_cm.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_unit_kg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-hdpi/ic_unit_kg.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_unit_cm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-mdpi/ic_unit_cm.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_unit_kg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-mdpi/ic_unit_kg.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_unit_cm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xhdpi/ic_unit_cm.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_unit_kg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xhdpi/ic_unit_kg.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_mili.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxhdpi/ic_mili.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_mili.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxxhdpi/ic_mili.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_hint_mass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-hdpi/ic_hint_mass.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_hint_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-hdpi/ic_hint_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_hint_steps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-hdpi/ic_hint_steps.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_hint_mass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-mdpi/ic_hint_mass.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_hint_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-mdpi/ic_hint_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_hint_steps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-mdpi/ic_hint_steps.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_hint_mass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xhdpi/ic_hint_mass.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_hint_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xhdpi/ic_hint_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_unit_cm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxhdpi/ic_unit_cm.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_unit_kg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxhdpi/ic_unit_kg.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_unit_cm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxxhdpi/ic_unit_cm.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_unit_kg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxxhdpi/ic_unit_kg.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_gender_female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-hdpi/ic_gender_female.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_gender_male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-hdpi/ic_gender_male.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_hint_height.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-hdpi/ic_hint_height.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_gender_female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-mdpi/ic_gender_female.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_gender_male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-mdpi/ic_gender_male.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_hint_height.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-mdpi/ic_hint_height.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_gender_male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xhdpi/ic_gender_male.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_hint_height.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xhdpi/ic_hint_height.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_hint_steps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xhdpi/ic_hint_steps.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_gender_male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxhdpi/ic_gender_male.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_hint_height.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxhdpi/ic_hint_height.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_hint_mass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxhdpi/ic_hint_mass.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_hint_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxhdpi/ic_hint_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_hint_steps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxhdpi/ic_hint_steps.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_hint_mass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxxhdpi/ic_hint_mass.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_hint_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxxhdpi/ic_hint_name.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_hint_steps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxxhdpi/ic_hint_steps.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_gender_female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xhdpi/ic_gender_female.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_gender_female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxhdpi/ic_gender_female.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_gender_male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxxhdpi/ic_gender_male.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_hint_height.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxxhdpi/ic_hint_height.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_gender_female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxxhdpi/ic_gender_female.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_gender_male_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-hdpi/ic_gender_male_disabled.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_gender_male_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-mdpi/ic_gender_male_disabled.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_gender_female_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-hdpi/ic_gender_female_disabled.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_gender_female_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-mdpi/ic_gender_female_disabled.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_gender_female_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xhdpi/ic_gender_female_disabled.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_gender_male_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xhdpi/ic_gender_male_disabled.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_gender_male_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxhdpi/ic_gender_male_disabled.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_gender_male_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxxhdpi/ic_gender_male_disabled.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_gender_female_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxhdpi/ic_gender_female_disabled.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_gender_female_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrora/jour-et-nuit/HEAD/app/src/main/res/drawable-xxxhdpi/ic_gender_female_disabled.png
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gitignore for basic android project.
2 |
3 | .gradle
4 | /local.properties
5 | /.idea/workspace.xml
6 | /.idea/tasks.xml
7 | /.idea/libraries
8 | .DS_Store
9 | /build
10 | /captures
11 |
12 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/miband/data/IBinaryData.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.miband.data;
2 |
3 | /**
4 | * Created by indrora on 7/9/15.
5 | */
6 | public interface IBinaryData {
7 | byte[] toBytes();
8 | boolean fromBytes(byte[] in);
9 | }
10 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/miband/data/ICharacteristicBound.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.miband.data;
2 |
3 | /**
4 | * Created by indrora on 7/16/15.
5 | */
6 | public interface ICharacteristicBound {
7 |
8 | void characteristicChanged(byte[] newValue);
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 32dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/ab_custom.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/model/ActivityDay.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.model;
2 |
3 | import java.util.Date;
4 |
5 | /**
6 | * Created by indrora on 7/5/15.
7 | */
8 | public class ActivityDay {
9 | public Date date;
10 |
11 | public ActivityPeriod[] activityPeriods;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/radio_gender_female.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/radio_gender_male.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs_steps_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/account.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/io/github/indrora/jouretnuit/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit;
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 | }
--------------------------------------------------------------------------------
/gender-female.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gender-male.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/account-circle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/weight.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_search_band.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/miband/data/NotifyHandler.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.miband.data;
2 |
3 | import android.bluetooth.BluetoothGatt;
4 | import android.bluetooth.BluetoothGattCharacteristic;
5 | import android.util.Log;
6 |
7 | /**
8 | * Created by indrora on 7/12/15.
9 | */
10 | public abstract class NotifyHandler {
11 | private BluetoothGatt gatt;
12 | public NotifyHandler(BluetoothGatt g) {
13 | gatt = g;
14 | }
15 | public void handleChange(BluetoothGattCharacteristic characteristic) {
16 | Log.d("NotifyHandler", "Called handleChange() for "+characteristic.getUuid().toString());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Jour et Nuit
3 |
4 | Steps
5 | Sleep
6 | History
7 |
8 | Hello world!
9 |
10 | Settings
11 |
12 | %1$d kcal
13 | %1$d km
14 |
15 |
16 | Hello blank fragment
17 | Select Band
18 |
19 |
--------------------------------------------------------------------------------
/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 /home/indrora/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 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | ***
2 | This project is unmaintained. There's a lot of reasons, but fundamentally, this project has finished for me.
3 | ***
4 |
5 | Jour Et Nuit: Day 'n' Nite.
6 |
7 | This software aims to be a functional replacement for the Xiaomi software for talking to the Xiaomi Mi Band called "Mi Fit"
8 |
9 | The goals of this software are to:
10 |
11 | * Provide a functional stand-in for the software without the need to create an account with Xiaomi
12 | * Offer more granular information density than is offered by the Xiaomi software
13 | * Export data (instead of holding it hostage)
14 |
15 | How I've done this
16 | ------------------
17 |
18 | This software is the result of reading over a lot of HCI snoop logs and reverse engineering of the obfuscated application.
19 |
20 | The document "protocol.txt" is my knowledge of the protocol as it stands from multiple sources across the internet.
21 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/Jour-et-Nuit.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | defaultConfig {
8 | applicationId "io.github.indrora.jouretnuit"
9 | minSdkVersion 18
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 |
23 | dependencies {
24 | compile fileTree(dir: 'libs', include: ['*.jar'])
25 | compile 'com.android.support:support-v13:22.2.0'
26 | compile 'com.android.support:appcompat-v7:22.2.0'
27 | compile 'com.android.support:cardview-v7:21.0.+'
28 | compile 'me.grantland:autofittextview:0.2.+'
29 | compile 'com.pnikosis:materialish-progress:1.5'
30 | compile 'com.android.support:recyclerview-v7:21.0.+'
31 |
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/miband/data/BattStats.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.miband.data;
2 |
3 | import io.github.indrora.jouretnuit.miband.Helper;
4 |
5 | /**
6 | * Created by indrora on 7/10/15.
7 | */
8 | public class BattStats implements IBinaryData {
9 |
10 | int timeWake = 0;
11 | int timeLight = 0;
12 | int timeConnect = 0;
13 | int timeVibrate = 0;
14 | int timeOther = 0;
15 |
16 | @Override
17 | public byte[] toBytes() {
18 | return null;
19 | }
20 |
21 | @Override
22 | public boolean fromBytes(byte[] in) {
23 | if(in == null) return false;
24 | if(in.length != 20) { return false; }
25 |
26 | this.timeWake = Helper.intFromBytes(in, 0);
27 | this.timeVibrate = Helper.intFromBytes(in, 4);
28 | this.timeLight = Helper.intFromBytes(in, 8);
29 | this.timeConnect = Helper.intFromBytes(in, 12);
30 | this.timeOther = Helper.intFromBytes(in, 16);
31 |
32 | return true;
33 |
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/model/ActivityPeriod.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.model;
2 |
3 | import java.util.Calendar;
4 |
5 | import io.github.indrora.jouretnuit.miband.data.UserInfo;
6 |
7 | /**
8 | * Created by indrora on 7/5/15.
9 | */
10 | public class ActivityPeriod {
11 | public Calendar start;
12 | // we'll divide this by 60 later.
13 | public int minutes;
14 | public int steps;
15 | // in meters
16 | public int distance;
17 |
18 | public ActivityPeriod() {
19 | start = Calendar.getInstance();
20 | minutes = 0;
21 | steps = 0;
22 | distance = 0;
23 | }
24 |
25 | public float caloriesBurned(UserInfo user) {
26 | // CB = [0.0215 x KPH^3 - 0.1765 x KPH^2 + 0.8710 x KPH + 1.4577] x WKG x T
27 | float km = (float)distance/1000f;
28 | float hr = (float)minutes/60f;
29 | float kmh = km/hr;
30 | return (
31 | 0.0215f * (float)Math.pow(kmh, 3)
32 | - 0.7165f * (float)Math.pow(kmh, 2)
33 | + 1.4577f
34 | ) * (float)(user.mass) * hr;
35 | }
36 |
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/.navigation/app/raw/main.nvg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
10 |
13 |
14 |
15 |
18 |
21 |
22 |
23 |
26 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/search_scan.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
19 |
20 |
27 |
28 |
--------------------------------------------------------------------------------
/weight-kilogram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/miband/data/UserInfo.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.miband.data;
2 |
3 | import java.util.Arrays;
4 |
5 | import io.github.indrora.jouretnuit.miband.Helper;
6 |
7 | /**
8 | * Created by indrora on 7/9/15.
9 | */
10 | public class UserInfo implements IBinaryData {
11 |
12 | public static final int GENDER_MALE = 1;
13 | public static final int GENDER_FEMALE = 0;
14 |
15 | public int age;
16 | public int height;
17 | public int mass;
18 | public int __type__;
19 | public int uid;
20 | public int gender;
21 | public byte[] alias;
22 |
23 | public String getStringAlias() {
24 | return String.valueOf(alias);
25 | }
26 |
27 | @Override
28 | public byte[] toBytes() {
29 | return new byte[0];
30 | }
31 |
32 | @Override
33 | public boolean fromBytes(byte[] in) {
34 | if(in == null) return false;
35 | if(in.length != 20) return false;
36 | uid = Helper.intFromBytes(in, 0);
37 | gender = in[4];
38 | age = in[5];
39 | height = in[6];
40 | mass = in[7];
41 | __type__ = in[8];
42 | alias = Arrays.copyOfRange(in, 9, 19);
43 | return true;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Morgan ``Indrora'' Gangwere
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/miband/data/StepCount.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.miband.data;
2 |
3 | import android.content.Intent;
4 | import android.util.Log;
5 |
6 | import io.github.indrora.jouretnuit.JENApp;
7 | import io.github.indrora.jouretnuit.miband.Helper;
8 | import io.github.indrora.jouretnuit.model.StringConstants;
9 |
10 | /**
11 | * Created by indrora on 7/16/15.
12 | */
13 | public class StepCount implements IBinaryData, ICharacteristicBound {
14 | private int steps = 0;
15 |
16 | public static final String KEY_STEP_COUNT = "steps";
17 |
18 | public int getSteps() { return steps; }
19 |
20 | @Override
21 | public byte[] toBytes() {
22 | return null;
23 | }
24 |
25 | @Override
26 | public boolean fromBytes(byte[] in) {
27 | if(in.length == 4) {
28 | steps = (in[0] & 0xFF) +
29 | ((in[1] & 0xFF) << 8 )+
30 | ((in[2] & 0xFF) << 16 )+
31 | ((in[3] & 0xFF) << 24 );
32 | Log.d("StepCount", "converted step count: "+steps);
33 | return true;
34 | }
35 |
36 | return false;
37 | }
38 |
39 | @Override
40 | public void characteristicChanged(byte[] newValue) {
41 | if(!this.fromBytes(newValue)) {
42 | Log.e("StepCount", "Failed: step count is wrong!");
43 | }
44 | Intent intent = new Intent(StringConstants.RESPONSE_STEP_COUNT);
45 | intent.putExtra(KEY_STEP_COUNT, this.steps);
46 | JENApp.makeBroadcast(intent);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/search_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/JENApp.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit;
2 |
3 | import android.app.Application;
4 | import android.content.Intent;
5 | import android.support.v4.content.LocalBroadcastManager;
6 | import android.util.Log;
7 | import io.github.indrora.jouretnuit.model.StringConstants;
8 |
9 | /**
10 | * Created by indrora on 7/7/15.
11 | */
12 | public class JENApp extends Application {
13 |
14 | private static JENApp _instance;
15 |
16 | private static MiBandDevice _miBand = MiBandService.getContainer();
17 |
18 | public static void makeBroadcast(Intent i) {
19 | Log.d("JENApp", "Sending broadcast for "+i.getAction());
20 | if(_instance != null) {
21 | LocalBroadcastManager.getInstance(_instance).sendBroadcast(i);
22 | }
23 | else {
24 | Log.d("JENApp", "Can't send intent: JENApp hasn't been done yet.");
25 | }
26 | }
27 |
28 | @Override
29 | public void onCreate() {
30 | super.onCreate();
31 | Log.d("JENApp", "Startup -> onCreate");
32 | _instance = this;
33 |
34 | /* Intent i = new Intent();
35 | i.setAction(StringConstants.REQUEST_DEVICE_CONNECT);
36 | i.setClass(this, MiBandService.class);
37 | i.putExtra("address", "88:0F:10:86:EF:F7");
38 | startService(i);
39 | */
40 | Intent i = new Intent();
41 | i.setClass(this, SearchBandActivity.class);
42 | i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
43 | startActivity(i);
44 |
45 | }
46 |
47 | @Override
48 | public void onTerminate() {
49 |
50 | Intent i = new Intent(StringConstants.REQUEST_DEVICE_DISCONNECT);
51 | startService(i);
52 |
53 | super.onTerminate();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/model/StringConstants.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.model;
2 |
3 | /**
4 | * Created by indrora on 7/7/15.
5 | */
6 | public class StringConstants {
7 |
8 | // INTENT_xxx is sent from the service
9 | // BLE_xxx is sent TO the service.
10 |
11 | public static final String RESPONSE_DEVICE_CONNECTED = "respond:ble:deviceConnected";
12 | public static final String RESPONSE_STEP_COUNT = "respond:ble:updateSteps";
13 | public static final String RESPONSE_BATTERY_INFO = "respond:ble:device_batteryinfo";
14 | public static final String RESPONSE_SYNC_DATA = "respond:ble:getData";
15 |
16 | public static final String REQUEST_DEVICE_CONNECT = "ble:connect";
17 | public static final String REQUEST_DEVICE_DISCONNECT = "ble:disconnect";
18 |
19 | public static final String REQUEST_DEVICE_PAIR = "ble:pairDevice";
20 | public static final String REQUEST_STEP_UPDATES = "ble:registerSteps";
21 |
22 | public static final String REQUEST_STEP_COUNT = "ble:getSteps";
23 | public static final String REQUEST_DEVICE_NAME = "ble:getDeviceName";
24 | public static final String REQUEST_BATTERY_INFO = "ble:getBattery";
25 | public static final String REQUEST_BATTERY_USAGE = "ble:getUsage";
26 |
27 | public static final String BLE_SET_USER_INFO = "ble:setUserInfo";
28 | public static final String BLE_SET_LED_COLOR = "ble:setLedColor";
29 | public static final String BLE_SET_GOAL = "ble:setGoal";
30 |
31 | public static final String BLE_QUERY_BATTERY = "ble:queryBattery";
32 | public static final String BLE_QUERY_DEVICE_INFO = "ble:queryDevicInfo";
33 |
34 | public static final String BLE_GET_ACTIVITY_DATA = "ble:getActivityData";
35 |
36 | public static final String REQUEST_FRESH_VALUES = "ble:getFresh";
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/MiBandService.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit;
2 |
3 | import android.app.IntentService;
4 | import android.content.Intent;
5 | import android.util.Log;
6 |
7 | import io.github.indrora.jouretnuit.model.StringConstants;
8 |
9 | public class MiBandService extends IntentService {
10 |
11 | public MiBandService() {
12 | super("MiBandService");
13 | }
14 |
15 | @Override
16 | protected void onHandleIntent(Intent intent) {
17 |
18 | Log.d("MiBandService", "handling intent" + intent.getAction());
19 |
20 | if (intent == null) {
21 | return;
22 | }
23 |
24 | if (mDevice == null) {
25 | mDevice = new MiBandDevice(getApplicationContext());
26 | }
27 |
28 | if (intent.getAction().equals(StringConstants.REQUEST_DEVICE_CONNECT)) {
29 | // handle the request to connect.
30 | String address = intent.getStringExtra("address");
31 | if (address == null || address.isEmpty()) {
32 | return; // Don't even service it.
33 | }
34 | mDevice.connect(address);
35 | }else if (intent.getAction().equals(StringConstants.REQUEST_DEVICE_DISCONNECT)) {
36 | mDevice.disconnect();
37 |
38 | } else if (intent.getAction().equals(StringConstants.REQUEST_STEP_COUNT)) {
39 |
40 | mDevice.mBluetoothGatt.readCharacteristic(mDevice.mStepsCharacteristic);
41 |
42 | } else if (intent.getAction().equals(StringConstants.REQUEST_BATTERY_INFO)) {
43 |
44 | mDevice.mBluetoothGatt.readCharacteristic(mDevice.mBatteryCharacteristic);
45 |
46 | } else if (intent.getAction().equals(StringConstants.REQUEST_STEP_UPDATES)) {
47 | Log.v("MiBandService", "Writing to the device...");
48 | boolean enable = intent.getBooleanExtra("enable", false);
49 | mDevice.setStepUpdates(enable);
50 | } else if (intent.getAction().equals(StringConstants.REQUEST_FRESH_VALUES)) {
51 | mDevice.updateValues();
52 |
53 | }
54 | }
55 |
56 | private static MiBandDevice mDevice;
57 |
58 | public static MiBandDevice getContainer() {
59 | return mDevice;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/user_height.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
22 | image/svg+xml
23 |
25 |
26 |
27 |
28 |
29 |
31 |
51 |
54 |
58 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/miband/data/BatteryInfo.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.miband.data;
2 |
3 | import android.content.Intent;
4 | import android.os.Parcel;
5 | import android.os.Parcelable;
6 | import android.util.Log;
7 |
8 | import java.util.GregorianCalendar;
9 |
10 | import io.github.indrora.jouretnuit.JENApp;
11 | import io.github.indrora.jouretnuit.miband.Helper;
12 | import io.github.indrora.jouretnuit.model.StringConstants;
13 |
14 | /**
15 | * Created by indrora on 7/9/15.
16 | */
17 | public class BatteryInfo implements IBinaryData, ICharacteristicBound {
18 |
19 | public static final String KEY_CHARGE_LEVEL = "chargeLevel";
20 | public static final String KEY_CHARGE_COUNT = "chargeCount";
21 | public static final String KEY_LAST_CHARGED = "lastCharged";
22 | public static final String KEY_CHARGE_STATUS = "chargeStatus";
23 |
24 | public static final int STATUS_CHARGED = 0;
25 | public static final int STATUS_CHARGING = 1;
26 | public static final int SOMETHING_ELSE = 2;
27 |
28 | public GregorianCalendar lastCharged;
29 | public int chargeLevel;
30 | public int chargeCount;
31 | public int status;
32 |
33 |
34 | @Override
35 | public byte[] toBytes() {
36 | // since we have no data to produce (it's a one-way operation)
37 | // we'll return null.
38 | return null;
39 | }
40 |
41 | @Override
42 | public boolean fromBytes(byte[] in) {
43 | // If we have no data, we have no data
44 | if (in == null) {
45 | Log.e("BatteryInfo", "Input was null!");
46 | return false;
47 | }
48 | // if we have not-10 bytes, we don't have the right packet.
49 | if (in.length != 10) {
50 | Log.e("BatteryInfo", "Was expecting 10 bytes, got "+in.length);
51 | return false;
52 | }
53 | // Charge level (0-100)
54 | this.chargeLevel = in[0];
55 |
56 | this.lastCharged = Helper.calendarFromBytes(in, 1);
57 | this.chargeCount = Helper.int16FromBytes(in, 7);
58 | this.status = in[9];
59 | return true;
60 |
61 | }
62 |
63 | @Override
64 | public void characteristicChanged(byte[] newValue) {
65 | if (this.fromBytes(newValue)) {
66 | Intent i = new Intent(StringConstants.RESPONSE_BATTERY_INFO);
67 | i.putExtra(KEY_CHARGE_LEVEL, this.chargeLevel);
68 | i.putExtra(KEY_CHARGE_STATUS, this.status);
69 | i.putExtra(KEY_CHARGE_COUNT, this.chargeCount);
70 | i.putExtra(KEY_LAST_CHARGED, this.lastCharged);
71 | JENApp.makeBroadcast(i);
72 | }
73 | else {
74 | Log.e("BatteryInfo", "WTF? Couldn't get the right new value");
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/miband/Helper.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.miband;
2 |
3 | import java.util.Arrays;
4 | import java.util.GregorianCalendar;
5 | import java.util.UUID;
6 |
7 | /**
8 | * Created by indrora on 7/7/15.
9 | */
10 | public class Helper {
11 | public static UUID UUID16(final String s) {
12 | return UUID.fromString(String.format("0000%4s-0000-1000-8000-00805f9b34fb", s));
13 | }
14 |
15 | public static int int8FromBytes(byte[] data, int offset) {
16 | return data[offset] & 0xFF;
17 | }
18 | public static int int16FromBytes(byte[] data, int offset) {
19 | return data[offset] & 0xFF | ( data[offset+1] & 0xFF) << 8;
20 | }
21 | public static byte[] int8ToBytes(byte in) {
22 | return new byte[] { in };
23 | }
24 | public static byte[] int16ToBytes(short in) {
25 | return new byte[] { (byte)(in & 0xFF), (byte)( (in & 0xFF00) >> 8) };
26 | }
27 |
28 | public static int intFromBytes(byte[] buffer, int offset) {
29 | int tmp = 0;
30 | for(int bOffset = 0; bOffset < 4; bOffset++) {
31 | tmp |= (buffer[offset+bOffset] << bOffset*8);
32 | }
33 | return tmp;
34 | }
35 |
36 | public static byte[] appendArrays(byte[] ... arrays ) {
37 | byte[] retArr = new byte[0];
38 | int pos = 0;
39 | for(byte[] arr : arrays) {
40 | pos = retArr.length;
41 | retArr = Arrays.copyOf(retArr, retArr.length + arr.length);
42 | for(int idx = 0; idx < retArr.length; idx++) {
43 | retArr[pos+idx] = arr[idx];
44 | }
45 | }
46 | return retArr;
47 | }
48 |
49 | public static byte[] calendarToBytes(GregorianCalendar cal) {
50 | return new byte[] {
51 | (byte)( cal.get(GregorianCalendar.YEAR) - 2000),
52 | (byte)cal.get(GregorianCalendar.MONTH),
53 | (byte)cal.get(GregorianCalendar.DAY_OF_MONTH),
54 | (byte)cal.get(GregorianCalendar.HOUR_OF_DAY),
55 | (byte)cal.get(GregorianCalendar.MINUTE),
56 | (byte)cal.get(GregorianCalendar.SECOND)
57 | };
58 | }
59 | public static GregorianCalendar calendarFromBytes(byte[] buffer, int offset) {
60 | if(offset+5 > buffer.length) return null;
61 | GregorianCalendar cal = new GregorianCalendar();
62 | cal.set(buffer[offset]+2000, buffer[offset+1], buffer[offset+2], buffer[offset+3], buffer[offset+4], buffer[offset+5]);
63 | return cal;
64 | }
65 |
66 | public static String byteArrayToHex(byte[] a) {
67 | StringBuilder sb = new StringBuilder(a.length * 2);
68 | for(byte b: a)
69 | sb.append(String.format("%02x ", b & 0xff));
70 | return sb.toString();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/card_steps.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
22 |
23 |
28 |
29 |
35 |
36 |
42 |
43 |
44 |
45 |
46 |
52 |
53 |
61 |
62 |
67 |
68 |
69 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/SearchBandActivity.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit;
2 |
3 |
4 | import android.app.Activity;
5 | import android.app.ListActivity;
6 | import android.bluetooth.BluetoothAdapter;
7 | import android.bluetooth.BluetoothDevice;
8 | import android.bluetooth.BluetoothManager;
9 | import android.content.Context;
10 | import android.database.DataSetObserver;
11 | import android.os.Bundle;
12 | import android.util.Log;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.view.animation.AlphaAnimation;
16 | import android.view.animation.Animation;
17 | import android.view.animation.AnimationSet;
18 | import android.view.animation.AnimationUtils;
19 | import android.widget.BaseAdapter;
20 | import android.widget.ImageView;
21 | import android.widget.ListAdapter;
22 | import android.widget.SimpleAdapter;
23 | import android.widget.Toast;
24 | import android.widget.ViewFlipper;
25 |
26 |
27 | public class SearchBandActivity extends Activity {
28 |
29 | BluetoothManager btManager;
30 | BluetoothDevice bDevice;
31 |
32 | private static final int STATE_SEARCH_DEVICE = 0;
33 | private static final int STATE_CONFIRM_DEVICE = 1;
34 | private static final int STATE_CONFIGURE_DEVICE = 2;
35 |
36 | private void setState(int state) {
37 |
38 | }
39 |
40 | View mSearchView;
41 | View mConfirmView;
42 | View mConfigView;
43 |
44 | @Override
45 | protected void onCreate(Bundle savedInstanceState) {
46 | super.onCreate(savedInstanceState);
47 | setContentView(R.layout.activity_search_band);
48 |
49 | ViewFlipper vf = (ViewFlipper)findViewById(R.id.flipper);
50 | /* mSearchView = getLayoutInflater().inflate(R.layout.search_scan, null);
51 |
52 | ImageView miliImage = (ImageView)mSearchView.findViewById(R.id.mili_image);
53 | Animation fadeAnim = new AlphaAnimation(0.5f, 1.0f);
54 | fadeAnim.setDuration(750);
55 | fadeAnim.setRepeatMode(Animation.REVERSE);
56 | fadeAnim.setRepeatCount(Animation.INFINITE);
57 | miliImage.startAnimation(fadeAnim);
58 |
59 | vf.addView(mSearchView, STATE_SEARCH_DEVICE);
60 | */
61 |
62 | mConfigView = getLayoutInflater().inflate(R.layout.search_profile, null);
63 |
64 | vf.addView(mConfigView);
65 |
66 | // get the bluetooth manager
67 | btManager = (BluetoothManager)getSystemService(BLUETOOTH_SERVICE);
68 | btManager.getAdapter().startLeScan(scanCallback);
69 | }
70 |
71 | BluetoothAdapter.LeScanCallback scanCallback = new BluetoothAdapter.LeScanCallback() {
72 | @Override
73 | public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
74 | if(device.getAddress().startsWith("88:0F:10")) {
75 | // It's the device we want.
76 | Log.d("SearchBandActivity", "Found Mi band @ " + device.getAddress() + " with rssi " + rssi);
77 | bDevice = device;
78 | // stop the scan.
79 | Toast.makeText(SearchBandActivity.this, "Found Mi Band: "+device.getAddress(), Toast.LENGTH_SHORT);
80 |
81 |
82 | }
83 | }
84 | };
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/band_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
25 |
26 |
27 |
30 |
36 |
37 |
43 |
44 |
45 |
46 |
47 |
51 |
52 |
67 |
68 |
80 |
81 |
82 |
90 |
91 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/widget/StepsView.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.widget;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.IntentFilter;
7 | import android.support.v4.content.LocalBroadcastManager;
8 | import android.util.AttributeSet;
9 | import android.util.Log;
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.widget.FrameLayout;
14 | import android.widget.LinearLayout;
15 | import android.widget.TextView;
16 |
17 | import com.pnikosis.materialishprogress.ProgressWheel;
18 |
19 | import java.text.DateFormat;
20 | import java.util.Date;
21 | import java.util.GregorianCalendar;
22 |
23 | import io.github.indrora.jouretnuit.MiBandDevice;
24 | import io.github.indrora.jouretnuit.MiBandService;
25 | import io.github.indrora.jouretnuit.R;
26 | import io.github.indrora.jouretnuit.model.StringConstants;
27 |
28 |
29 | /**
30 | * Created by indrora on 7/14/15.
31 | */
32 | public class StepsView extends LinearLayout {
33 |
34 | private void inflate(Context context) {
35 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
36 | inflater.inflate(R.layout.card_steps, this, true);
37 | }
38 |
39 | View rootView;
40 | public StepsView(Context context) {
41 | super(context);
42 | inflate(context);
43 | }
44 |
45 | public StepsView(Context context, AttributeSet attrs) {
46 | super(context, attrs);
47 | inflate(context);
48 | }
49 |
50 | TextView stepsTV;
51 | TextView batteryTV;
52 | TextView detailsTV;
53 | ProgressWheel spinny;
54 |
55 | @Override
56 | protected void onFinishInflate() {
57 | super.onFinishInflate();
58 |
59 | stepsTV = (TextView)this.findViewById(R.id.stepsTV);
60 | detailsTV = (TextView)this.findViewById(R.id.detailsTV);
61 | batteryTV = (TextView)this.findViewById(R.id.batteryTV);
62 | spinny = (ProgressWheel)this.findViewById(R.id.stepsProgress);
63 |
64 |
65 | IntentFilter intentFilter = new IntentFilter();
66 | intentFilter.addAction(StringConstants.RESPONSE_STEP_COUNT);
67 | intentFilter.addAction(StringConstants.RESPONSE_BATTERY_INFO);
68 |
69 | LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this.getContext());
70 | localBroadcastManager.registerReceiver(valuesBR, intentFilter);
71 |
72 | updateValues();
73 |
74 | }
75 |
76 | BroadcastReceiver valuesBR = new BroadcastReceiver() {
77 | @Override
78 | public void onReceive(Context context, Intent intent) {
79 | Log.d("StepsView", "Got intent...");
80 | updateValues();
81 | }
82 | };
83 |
84 | private void updateValues() {
85 | MiBandDevice device = MiBandService.getContainer();
86 | if(device != null) {
87 | int steps = device.device_steps.getSteps();
88 | stepsTV.setText(String.format("%d steps",steps));
89 | int meters = (int)(0.7f*steps);
90 | detailsTV.setText("dist: " + meters);
91 | spinny.setProgress(steps / 5000f);
92 |
93 | Date d = device.device_batteryinfo.lastCharged.getTime();
94 |
95 | DateFormat df = DateFormat.getDateInstance();
96 | batteryTV.setText(String.format("%d%% - %s", device.device_batteryinfo.chargeLevel,df.format(d)) );
97 | }
98 | else {
99 | Log.d("StepsView", "WTF?");
100 | stepsTV.setText("???");
101 | }
102 | this.invalidate();
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/mili.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
22 | image/svg+xml
23 |
25 |
26 |
27 |
28 |
29 |
31 |
53 |
57 |
61 |
65 |
69 |
70 |
73 |
79 |
81 |
87 |
93 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/unit_kg.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
22 | image/svg+xml
23 |
25 |
26 |
27 |
28 |
29 |
31 |
53 |
57 |
58 |
61 |
62 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/ActivityHistoryAdapter.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit;
2 |
3 | import android.database.DataSetObserver;
4 | import android.support.v7.widget.CardView;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.util.Log;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ListAdapter;
10 | import android.widget.TextView;
11 |
12 | /**
13 | * Created by indrora on 7/14/15.
14 | */
15 | public class ActivityHistoryAdapter extends RecyclerView.Adapter {
16 |
17 |
18 | View[] fixedItems;
19 | ListAdapter dataAdapter;
20 |
21 | public ActivityHistoryAdapter(View[] forced_items, ListAdapter source) {
22 | fixedItems = forced_items;
23 | dataAdapter = source;
24 | // make it such that updates to the data adapter are propogated downwards to the RecyclerView
25 | if(dataAdapter != null ) {
26 | dataAdapter.registerDataSetObserver(new DataSetObserver() {
27 | /**
28 | * This method is called when the entire data set has changed,
29 | * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}.
30 | */
31 | @Override
32 | public void onChanged() {
33 | ActivityHistoryAdapter.this.notifyDataSetChanged();
34 | }
35 |
36 | /**
37 | * This method is called when the entire data becomes invalid,
38 | * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a
39 | * {@link Cursor}.
40 | */
41 | @Override
42 | public void onInvalidated() {
43 | ActivityHistoryAdapter.this.notifyDataSetChanged();
44 | }
45 | });
46 | }
47 | }
48 |
49 | public class CardViewHolder extends RecyclerView.ViewHolder {
50 | private View internalView;
51 | private CardView wrapperView;
52 | private ViewGroup vg;
53 | public CardViewHolder(CardView v) {
54 | super(v);
55 | wrapperView = v;
56 | internalView = new View(v.getContext());
57 | }
58 | public void setViewGroup(ViewGroup vv) {
59 | vg = vv;
60 | }
61 | public void setCardContents(View vx) {
62 | internalView = vx;
63 | try {
64 | ((CardView)(vx.getParent())).removeView(vx);
65 | } catch (Exception e) { }
66 | wrapperView.addView(internalView);
67 |
68 | wrapperView.invalidate();
69 | }
70 | }
71 |
72 | @Override
73 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
74 | CardViewHolder cvh;
75 | CardView cv = new CardView(parent.getContext());
76 | cv.setLayoutParams(new CardView.LayoutParams(CardView.LayoutParams.MATCH_PARENT, CardView.LayoutParams.WRAP_CONTENT));
77 | //cv.setCardElevation(10f);
78 | cv.setPreventCornerOverlap(true);
79 | cv.setUseCompatPadding(true);
80 | cvh = new CardViewHolder(cv);
81 | return cvh;
82 | }
83 | @Override
84 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
85 | // get the appropriate view.
86 | CardViewHolder cvh = (CardViewHolder)holder;
87 | if(fixedItems != null && position < fixedItems.length) {
88 | View v = fixedItems[position];
89 | cvh.setCardContents(fixedItems[position]);
90 | cvh.wrapperView.invalidate();
91 | }
92 | else {
93 | if(dataAdapter == null ) { return; }
94 | int fixedPostion = position - fixedItems.length;
95 | View tmpView = cvh.internalView;
96 | cvh.setCardContents(dataAdapter.getView(fixedPostion, tmpView, cvh.vg));
97 | }
98 |
99 | }
100 | @Override
101 | public int getItemCount() {
102 | if(dataAdapter == null) { return fixedItems.length; }
103 | else { return dataAdapter.getCount() + fixedItems.length; }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/unit_cm.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
22 | image/svg+xml
23 |
25 |
26 |
27 |
28 |
29 |
31 |
53 |
57 |
58 |
61 |
62 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/miband/BandConstants.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.miband;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 | import java.util.UUID;
6 |
7 | /**
8 | * Created by indrora on 7/6/15.
9 | */
10 | public class BandConstants {
11 |
12 | public static final int ALIAS_LEN = 10;
13 |
14 | public static final int USER_GENDER_FEMALE = 0;
15 | public static final int USER_GENDER_MALE = 1;
16 |
17 | /**
18 | * Things we can be notified about.
19 | */
20 | public final class NOTIFY {
21 | public static final int NORMAL = 0;
22 | public static final int DEVICE_MALFUNCTION = 255;
23 | public static final int FITNESS_GOAL_ACHIEVED = 7;
24 | public static final int PAIR_CANCEL = 239;
25 | public static final int SET_LATENCY_SUCCESS = 8;
26 | public static final int UNKNOWN = -1;
27 |
28 | /**
29 | * Authentication (??)
30 | */
31 | public final class AUTHENTICATION {
32 | public static final int FAILED = 6;
33 | public static final int SUCCESS = 5;
34 | }
35 |
36 | /**
37 | * Connection parameter updates
38 | */
39 | public final class CONN_PARAM_UPDATE {
40 | public static final int FAILED = 3;
41 | public static final int SUCCESS = 4;
42 | }
43 |
44 | /**
45 | * Firmware (update?)
46 | */
47 | public final class FIRMWARE {
48 | public static final int FAILED = 1;
49 | public static final int SUCCESS = 2;
50 | }
51 |
52 | /**
53 | * FW check (??)
54 | */
55 | public final class FW_CHECK {
56 | public static final int FAILED = 11;
57 | public static final int SUCCESS = 12;
58 | }
59 |
60 | /**
61 | * Reset authentication
62 | */
63 | public final class RESET_AUTHENTICATION {
64 | public static final int FAILED = 9;
65 | public static final int SUCCESS = 10;
66 | }
67 |
68 | /**
69 | * Motor status?
70 | */
71 | public final class STATUS {
72 | public static final int MOTOR_ALARM = 17;
73 | public static final int MOTOR_AUTH = 19;
74 | public static final int MOTOR_AUTH_SUCCESS = 21;
75 | public static final int MOTOR_CALL = 14;
76 | public static final int MOTOR_DISCONNECT = 15;
77 | public static final int MOTOR_GOAL = 18;
78 | public static final int MOTOR_NOTIFY = 13;
79 | public static final int MOTOR_SHUTDOWN = 20;
80 | public static final int MOTOR_SMART_ALARM = 16;
81 | public static final int MOTOR_TEST = 22;
82 | }
83 | }
84 |
85 | public static final UUID UUID_DESCRIPTOR_CHARACTERISTIC_USER_CONFIGURATION = Helper.UUID16("2901");
86 | public static final UUID UUID_DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION = Helper.UUID16("2902");
87 |
88 |
89 | public static final UUID UUID_CHARACTERISTIC_ACTIVITY_DATA = Helper.UUID16("FF07");
90 | public static final UUID UUID_CHARACTERISTIC_BATTERY = Helper.UUID16("FF0C");
91 | public static final UUID UUID_CHARACTERISTIC_CONTROL_POINT = Helper.UUID16("FF05");
92 | public static final UUID UUID_CHARACTERISTIC_DATE_TIME = Helper.UUID16("FF0A");
93 | public static final UUID UUID_CHARACTERISTIC_DEVICE_INFO = Helper.UUID16("FF01");
94 | public static final UUID UUID_CHARACTERISTIC_DEVICE_NAME = Helper.UUID16("FF02");
95 | public static final UUID UUID_CHARACTERISTIC_FIRMWARE_DATA = Helper.UUID16("FF08");
96 | public static final UUID UUID_CHARACTERISTIC_LE_PARAMS = Helper.UUID16("FF09");
97 | public static final UUID UUID_CHARACTERISTIC_NOTIFICATION = Helper.UUID16("FF03");
98 | public static final UUID UUID_CHARACTERISTIC_REALTIME_STEPS = Helper.UUID16("FF06");
99 | public static final UUID UUID_CHARACTERISTIC_SENSOR_DATA = Helper.UUID16("FF0E");
100 | public static final UUID UUID_CHARACTERISTIC_STATISTICS = Helper.UUID16("FF0B");
101 | public static final UUID UUID_CHARACTERISTIC_TEST = Helper.UUID16("FF0D");
102 | public static final UUID UUID_CHARACTERISTIC_USER_INFO = Helper.UUID16("FF04");
103 | public static final UUID UUID_SERVICE_MILI_SERVICE = Helper.UUID16("FEE0");
104 |
105 |
106 |
107 | public static String getHelp(UUID which) {
108 | if(which.equals(UUID_CHARACTERISTIC_ACTIVITY_DATA )) return "known: activity data";
109 | if(which.equals(UUID_CHARACTERISTIC_BATTERY)) return "known: battery";
110 | if(which.equals(UUID_CHARACTERISTIC_CONTROL_POINT )) return "known: control point";
111 | if(which.equals(UUID_CHARACTERISTIC_DATE_TIME)) return "known: date_time";
112 | if(which.equals(UUID_CHARACTERISTIC_DEVICE_INFO)) return "known: device_info";
113 | if(which.equals(UUID_CHARACTERISTIC_DEVICE_NAME)) return "known: device_name";
114 | if(which.equals(UUID_CHARACTERISTIC_FIRMWARE_DATA)) return "known: firmware_data";
115 | if(which.equals(UUID_CHARACTERISTIC_LE_PARAMS )) return "known: le_params";
116 | if(which.equals(UUID_CHARACTERISTIC_NOTIFICATION)) return "known: notification";
117 | if(which.equals(UUID_CHARACTERISTIC_REALTIME_STEPS)) return "known: realtime_steps";
118 | if(which.equals(UUID_CHARACTERISTIC_SENSOR_DATA)) return "known: sensor_data";
119 | if(which.equals(UUID_CHARACTERISTIC_STATISTICS )) return "known: statistics";
120 | if(which.equals(UUID_CHARACTERISTIC_TEST)) return "known: test";
121 | if(which.equals(UUID_CHARACTERISTIC_USER_INFO)) return "known: user_info";
122 | if(which.equals(UUID_SERVICE_MILI_SERVICE)) return "known: Service MILI_SERVICE";
123 | return "unkown: "+which.toString();
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/protocol.txt:
--------------------------------------------------------------------------------
1 | ********************************************************************************
2 | Xiaomi Mi-band protocol documentation -- Data structures and stuff!
3 | ********************************************************************************
4 |
5 | Basic data structures:
6 |
7 | * All integers are in Network Order (MSB first -- 0xDEADBEEF is stored as EF BE AD DE)
8 | * Integral values are unsigned unless otherwise noted.
9 |
10 | Basic structures:
11 |
12 | struct {
13 | uint8_t year;
14 | uint8_t month;
15 | uint8_t day;
16 | uint8_t hour;
17 | uint8_t minute;
18 | uint8_t second;
19 | } DATETIME
20 |
21 | struct {
22 | uint8_t level;
23 | uint16_t charges;
24 | uint8_t status;
25 | DATETIME lastCharged;
26 | } BATTERY_INFO
27 |
28 | struct {
29 | uint16_t connIntMax;
30 | uint16_t connIntMin;
31 | uint16_t latency;
32 | uint16_t timeout;
33 | uint16_t connInt;
34 | uint16_t advInt;
35 | } LE_PARAMS
36 |
37 | COLOR {
38 | uint8_t RED;
39 | uint8_t BLUE;
40 | uint8_t GREEN;
41 | }
42 |
43 | struct {
44 | uint32_t uid;
45 | uint8_t gender;
46 | uint8_t age;
47 | uint8_t height; // in cm
48 | uint8_t mass; // in kg
49 | uint8_t type; // unknown usage?
50 | uchar name[10]; // 10 bytes for name, UTF8. No assumed final 0x00;
51 | uint8_t crc8; // CRC8 of data.
52 | } USERDATA
53 |
54 | CRC8 is weird -- Xor'd against some byte in the MAC address?
55 |
56 |
57 | Characteristics on the device
58 | ---
59 |
60 | Service 1 (0xFEE0) "Mili-Service"
61 | 0xFF01 read DEVICE_INFO
62 | 0xFF02 read write DEVICE_NAME
63 | 0xFF03 read notify NOTIFICATION
64 | 0xFF04 read write USER_INFO
65 | 0xFF05 write CONTROL_POINT
66 | 0xFF06 read notify REALTIME_STEPS
67 | 0xFF07 read indicate ACTIVITY_DATA
68 | 0xFF08 write(NR) FIRMWARE_DATA
69 | 0xFF09 read write LE_PARAMS
70 | 0xFF0A read write DATE_TIME
71 | 0xFF0B read write STATISTICS
72 | 0xFF0C read notify BATTERY
73 | 0xFF0D read write TEST
74 | 0xFF0E read notify SENSOR_DATA
75 | 0xFF0F read write PAIR
76 |
77 |
78 | CONTROL_POINT commands:
79 |
80 | cmd | operands | description
81 | 03 | 1 | Set realtime step notification (uint8_t enable)
82 | 04 | CC | Set *alarm clock* data
83 | 05 | 2 | Set goal (byte, uint16)
84 | 06 | 0 | Get activity information -> starts sync??
85 | 07 | ? | Send firmware information
86 | 08 | 1 | Send notification (byte argument)
87 | 09 | 0 | Factory reset (!! REMOVES PAIRING INFORMATION !!)
88 | 10 | ? | Confirm activity data transfer complete
89 | 11 | 0 | Sync: used in Firmware push
90 | 12 | 0 | Reboot (removes pairing data? )
91 | 13 ********** unused?
92 | 14 | 4 | Set color scheme [ COLOR and a byte: flash color? ]
93 | 15 | 1 | Set Wear location
94 | 16 | 1 | Set step count -> uint16
95 | 17 | ? | Stop sync data (reset sync location?)
96 | 18 | 1? | get sensor data
97 | 19 | 0 | Stop motor vibrate
98 |
99 | * used in multiple places
100 |
101 |
102 | Pairing: To pair with the device, first make sure it is discoverable; this is done while it is in sleep mode, has been unpaired, has been factory reset, or is charging.
103 |
104 | Write to the PAIRING characteristic the value 0x02. Poll regularly for a value on the band at that endpoint. When it becomes 0x02 as well, you have paired with the device. If it becomes 0xFF, you have failed to pair properly. Try again.
105 |
106 | Immediately after pairing, you should write to the device two TIME structures, the second of which is !0x7F (128?) as all values.
107 |
108 | Write user information to the device; this is done by writing a USERATA structure to the User Info characteristic. This value (UID, etc) is used as an authentication to the device for later data transfers.
109 |
110 | Step tracking information is provided through the REALTIME_STEPS characteristic. To enable notifications, first do the standard BTLEGATT thing of writing notify/indicate to the characteristic control descriptor, then write the REALTIME_STEPS command to the contorl point with the arguemnt 1 or 0.
111 |
112 | Step notification
113 | -----------------
114 |
115 | To get step counts, read the REALTIME_STEPS characteristic.
116 | To get step updates, set up notifications on the REALTIME_STEPS characteristic (you should do this on conenction!) and write opcode 3 (enable step notifications) with operand 1 (to enable) or 0 (to disable)
117 |
118 | Timer (alarm) functionality
119 | ---------------------------
120 |
121 | Timer command structure looks like this:
122 |
123 | struct TIMER_COMMAND {
124 | byte command = 4; // fixed
125 | byte index; // 0, 1, 2
126 | byte enabled; // boolean (0 = disabled, 1 = enabled)
127 | byte year; // Year \
128 | byte month; // Month | Used in a special case where days = 0;
129 | byte day; // Day /
130 | byte hour;
131 | byte minute;
132 | byte second;
133 | byte wakeDuration; // If we're in light sleep when the alarm would go off, buzz!
134 | byte days; // monday = 1, sunday = 64 -- you do the math.
135 | }
136 |
137 | Setting user information
138 | ------------------------
139 |
140 | The User Info characeristic is used to set user info and authenticate to the device.
141 |
142 | Sending firmware
143 | ----------------
144 |
145 | Firmware is tricky.
146 |
147 | Since we can only write 20 bytes at a time to the firmware block, we send 20 bytes at a time.
148 |
149 | First, firmware information is sent.
150 | Then, the firmware data is sent.
151 | Then, the device is rebooted (!!)
152 | Then, we have updated.
153 |
154 | There's CRC8's in there too, but I can't entirely tell what's going on.
155 |
156 | Vibrating the motor
157 | -------------------
158 |
159 | !! This takes battery to do! !!
160 |
161 | Send the notification 0 for "short" vibrates
162 | Send the notification 1 for "long" vibrates (theoretically)
163 |
164 | You should keep at hand the "kill" vibrate command.
165 |
166 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/widget/CircleGaugeView.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Canvas;
7 | import android.graphics.DashPathEffect;
8 | import android.graphics.Paint;
9 | import android.graphics.RectF;
10 | import android.util.AttributeSet;
11 | import android.util.TypedValue;
12 | import android.view.View;
13 |
14 | /**
15 | * Created by indrora on 7/4/15.
16 | */
17 | public class CircleGaugeView extends View {
18 |
19 | /**
20 | * Class to draw a circle for timers and stopwatches.
21 | * These two usages require two different animation modes:
22 | * Timer counts down. In this mode the animation is counter-clockwise and stops at 0.
23 | * Stopwatch counts up. In this mode the animation is clockwise and will run until stopped.
24 | */
25 |
26 |
27 | private int mAccentColor;
28 | private int mOverflowColor;
29 | private int mWhiteColor;
30 | private long mMaxValue = 0;
31 | private long mCurrentValue = 0;
32 | private long mOverflowValue = 0;
33 | private static float mStrokeSize = 12;
34 | private static float mDotRadius = 6;
35 | private static float mMarkerStrokeSize = 14;
36 | private final Paint mPaint = new Paint();
37 | private final Paint mFill = new Paint();
38 | private final RectF mArcRect = new RectF();
39 | private float mRadiusOffset; // amount to remove from radius to account for markers on circle
40 | private float mScreenDensity;
41 |
42 | // Stopwatch mode is the default.
43 | private boolean mTimerMode = false;
44 |
45 | @SuppressWarnings("unused")
46 | public CircleGaugeView(Context context) {
47 | this(context, null);
48 | }
49 |
50 | public CircleGaugeView(Context context, AttributeSet attrs) {
51 |
52 | super(context, attrs);
53 | init(context);
54 | }
55 |
56 | public void setValue(long value, long max) {
57 | if (value >= max) {
58 | mMaxValue = mCurrentValue = value;
59 | mOverflowValue = max;
60 | } else {
61 | mOverflowValue = 0;
62 | mMaxValue = max;
63 | mCurrentValue = value;
64 | }
65 | invalidate();
66 | }
67 |
68 |
69 | /**
70 | * Calculate the amount by which the radius of a CircleTimerView should be offset by the any
71 | * of the extra painted objects.
72 | */
73 | public static float calculateRadiusOffset(
74 | float strokeSize, float dotStrokeSize, float markerStrokeSize) {
75 | return Math.max(strokeSize, Math.max(dotStrokeSize, markerStrokeSize));
76 | }
77 |
78 |
79 | private void init(Context c) {
80 |
81 | Resources resources = c.getResources();
82 | mScreenDensity = resources.getDisplayMetrics().density;
83 | mStrokeSize = 16* mScreenDensity;
84 | mMarkerStrokeSize = 16* mScreenDensity;
85 | mDotRadius = 8* mScreenDensity;
86 | mRadiusOffset = calculateRadiusOffset(
87 | mStrokeSize, mDotRadius * 2.0f, mMarkerStrokeSize);
88 | mPaint.setAntiAlias(true);
89 | mPaint.setStyle(Paint.Style.STROKE);
90 |
91 |
92 |
93 | mWhiteColor = resources.getColor(android.support.v7.appcompat.R.color.background_material_dark);
94 | mAccentColor = resources.getColor(android.support.v7.appcompat.R.color.accent_material_light);
95 | mOverflowColor = resources.getColor(android.support.v7.appcompat.R.color.accent_material_dark);
96 |
97 | mFill.setAntiAlias(true);
98 | mFill.setStyle(Paint.Style.FILL);
99 | mFill.setColor(mAccentColor);
100 | }
101 |
102 | public void setTimerMode(boolean mode) {
103 | mTimerMode = mode;
104 | }
105 |
106 | @Override
107 | public void onDraw(Canvas canvas) {
108 | int xCenter = getWidth() / 2 + 1;
109 | int yCenter = getHeight() / 2;
110 |
111 | mPaint.setStrokeWidth(mStrokeSize);
112 | float radius = Math.min(xCenter, yCenter) - mRadiusOffset;
113 |
114 | if (mCurrentValue == 0) {
115 | // just draw a complete white circle, no red arc needed
116 | mPaint.setColor(mWhiteColor);
117 | canvas.drawCircle(xCenter, yCenter, radius, mPaint);
118 | } else {
119 | //draw a combination of red and white arcs to create a circle
120 | mArcRect.top = yCenter - radius;
121 | mArcRect.bottom = yCenter + radius;
122 | mArcRect.left = xCenter - radius;
123 | mArcRect.right = xCenter + radius;
124 | float redPercent = (float) mCurrentValue / (float) mMaxValue;
125 | // prevent timer from doing more than one full circle
126 | redPercent = (redPercent > 1 && mTimerMode) ? 1 : redPercent;
127 |
128 | float whitePercent = 1 - (redPercent > 1 ? 1 : redPercent);
129 | // draw red arc here
130 | mPaint.setColor(mAccentColor);
131 | if (mTimerMode) {
132 | canvas.drawArc(mArcRect, 270, -redPercent * 360, false, mPaint);
133 | } else {
134 | canvas.drawArc(mArcRect, 270, +redPercent * 360, false, mPaint);
135 | }
136 |
137 | // draw white arc here
138 | mPaint.setStrokeWidth(mStrokeSize);
139 | mPaint.setColor(mWhiteColor);
140 | if (mTimerMode) {
141 | canvas.drawArc(mArcRect, 270, +whitePercent * 360, false, mPaint);
142 | } else {
143 | canvas.drawArc(mArcRect, 270 + (1 - whitePercent) * 360,
144 | whitePercent * 360, false, mPaint);
145 | }
146 |
147 | if (mOverflowValue > 0) {
148 | float angle = (float) (mOverflowValue) / (float) mMaxValue * 360;
149 |
150 | mPaint.setStrokeWidth(mStrokeSize);
151 | mPaint.setColor(mOverflowColor);
152 | canvas.drawArc(mArcRect, 270, -360 + angle, false, mPaint);
153 |
154 | mPaint.setStrokeWidth(1.25f * mStrokeSize);
155 | mPaint.setColor(mWhiteColor);
156 | // draw 2dips thick marker
157 | // the formula to draw the marker 1 unit thick is:
158 | // 180 / (radius * Math.PI)
159 | // after that we have to scale it by the screen density
160 | canvas.drawArc(mArcRect, 270 + angle, 2*mScreenDensity *
161 | (float) (360 / (radius * Math.PI)), false, mPaint);
162 |
163 | }
164 |
165 | if(mCurrentValue< mMaxValue) {
166 | drawRedDot(canvas, redPercent, xCenter, yCenter, radius);
167 | }
168 |
169 | }
170 | }
171 |
172 | protected void drawRedDot(
173 | Canvas canvas, float degrees, int xCenter, int yCenter, float radius) {
174 | mPaint.setColor(mAccentColor);
175 | float dotPercent;
176 | if (mTimerMode) {
177 | dotPercent = 270 - degrees * 360;
178 | } else {
179 | dotPercent = 270 + degrees * 360;
180 | }
181 |
182 | final double dotRadians = Math.toRadians(dotPercent);
183 | canvas.drawCircle(xCenter + (float) (radius * Math.cos(dotRadians)),
184 | yCenter + (float) (radius * Math.sin(dotRadians)), mDotRadius, mFill);
185 | }
186 |
187 | }
188 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/MainActivity.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit;
2 |
3 | import java.util.HashMap;
4 | import java.util.Locale;
5 | import java.util.Map;
6 |
7 | import android.app.Activity;
8 | import android.content.BroadcastReceiver;
9 | import android.content.Context;
10 | import android.content.Intent;
11 | import android.content.IntentFilter;
12 | import android.os.Bundle;
13 | import android.os.Handler;
14 | import android.support.v13.app.FragmentPagerAdapter;
15 | import android.support.v4.content.LocalBroadcastManager;
16 | import android.support.v4.view.ViewPager;
17 | import android.support.v4.widget.SwipeRefreshLayout;
18 | import android.support.v7.app.ActionBar;
19 | import android.support.v7.widget.LinearLayoutManager;
20 | import android.support.v7.widget.RecyclerView;
21 | import android.util.Log;
22 | import android.view.Menu;
23 | import android.view.MenuItem;
24 | import android.view.View;
25 | import android.view.Window;
26 | import android.widget.TextView;
27 |
28 |
29 | import io.github.indrora.jouretnuit.miband.data.BatteryInfo;
30 | import io.github.indrora.jouretnuit.miband.data.StepCount;
31 | import io.github.indrora.jouretnuit.model.StringConstants;
32 | import io.github.indrora.jouretnuit.widget.StepsView;
33 |
34 |
35 | public class MainActivity extends android.app.Activity {
36 |
37 | private Map broadcastReceiverMap = new HashMap<>();
38 |
39 | public MainActivity() {
40 | super();
41 | broadcastReceiverMap.put(StringConstants.RESPONSE_DEVICE_CONNECTED, onConnectBR);
42 | broadcastReceiverMap.put(StringConstants.RESPONSE_STEP_COUNT, onStepsBR);
43 | broadcastReceiverMap.put(StringConstants.RESPONSE_BATTERY_INFO, onBatteryBR);
44 | broadcastReceiverMap.put(StringConstants.RESPONSE_SYNC_DATA, syncCompleteBR);
45 | }
46 |
47 | RecyclerView.LayoutManager mLayoutManager;
48 |
49 | TextView stepsView;
50 | TextView batteryView;
51 | StepsView mStepsCard;
52 | ActivityHistoryAdapter activityHistoryAdapter;
53 | SwipeRefreshLayout refreshLayout;
54 |
55 | @Override
56 | protected void onCreate(Bundle savedInstanceState) {
57 | super.onCreate(savedInstanceState);
58 | setContentView(R.layout.activity_main);
59 |
60 | RecyclerView rv = (RecyclerView) findViewById(R.id.cardTray);
61 |
62 | mLayoutManager = new LinearLayoutManager(this);
63 | rv.setLayoutManager(mLayoutManager);
64 |
65 | stepsView = new TextView(this);
66 | stepsView.setText("...");
67 | batteryView = new TextView(this);
68 | batteryView.setText("Wooo!");
69 |
70 | mStepsCard = new StepsView(this);
71 |
72 |
73 | activityHistoryAdapter = new ActivityHistoryAdapter(
74 | new View[]{
75 | mStepsCard,
76 | stepsView,
77 | batteryView},
78 | null
79 | );
80 | rv.setAdapter(activityHistoryAdapter);
81 |
82 | refreshLayout = (SwipeRefreshLayout) findViewById(R.id.pullRefresh);
83 |
84 | refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
85 | @Override
86 | public void onRefresh() {
87 | Intent i = new Intent(StringConstants.REQUEST_FRESH_VALUES);
88 | i.setClass(MainActivity.this, MiBandService.class);
89 | startService(i);
90 | }
91 | });
92 | }
93 |
94 |
95 | @Override
96 | protected void onResume() {
97 |
98 | LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(getApplicationContext());
99 |
100 | for (String s : broadcastReceiverMap.keySet()) {
101 | localBroadcastManager.registerReceiver(broadcastReceiverMap.get(s), new IntentFilter(s));
102 | }
103 |
104 | if (MiBandService.getContainer() != null) {
105 | // update the things
106 | stepsView.setText("Walked " + MiBandService.getContainer().device_steps.getSteps() + " steps");
107 | }
108 | super.onResume();
109 | }
110 |
111 | BroadcastReceiver onStepsBR = new BroadcastReceiver() {
112 | @Override
113 | public void onReceive(Context context, Intent intent) {
114 | Log.d("MainActivty", "onStepsBR: we get device_Steps!");
115 | int steps = intent.getIntExtra(StepCount.KEY_STEP_COUNT, -1);
116 | Log.d("MainActivity", "onStepsBR: Got step count "+steps);
117 | stepsView.setText("Walked " + steps + " device_Steps!");
118 | stepsView.invalidate();
119 | activityHistoryAdapter.notifyDataSetChanged();
120 | }
121 | };
122 |
123 | BroadcastReceiver onBatteryBR = new BroadcastReceiver() {
124 | @Override
125 | public void onReceive(Context context, Intent intent) {
126 | for (String s : intent.getExtras().keySet()) {
127 | Log.d("Battery info", "< " + s + " > " + intent.getExtras().get(s));
128 | }
129 | batteryView.setText("Battery at " + intent.getIntExtra(BatteryInfo.KEY_CHARGE_LEVEL, -1) + "% ");
130 | activityHistoryAdapter.notifyDataSetChanged();
131 | }
132 | };
133 |
134 |
135 | BroadcastReceiver onConnectBR = new BroadcastReceiver() {
136 | @Override
137 | public void onReceive(Context context, Intent intent) {
138 | Log.d("MainActivity:onConnect", "WE GET CONNECTION!");
139 | // We've connected. Get the step count.
140 | Intent i = new Intent();
141 | i.setAction(StringConstants.REQUEST_STEP_UPDATES);
142 | i.setClass(MainActivity.this, MiBandService.class);
143 | i.putExtra("enable", true);
144 | MainActivity.this.startService(i);
145 | i.setAction(StringConstants.REQUEST_FRESH_VALUES);
146 | i.removeExtra("enable");
147 | MainActivity.this.startService(i);
148 | }
149 | };
150 |
151 | BroadcastReceiver syncCompleteBR = new BroadcastReceiver() {
152 | @Override
153 | public void onReceive(Context context, Intent intent) {
154 | refreshLayout.setRefreshing(false);
155 | }
156 | };
157 |
158 | @Override
159 | protected void onStop() {
160 | LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this);
161 | // Clean up the broadcast recievers we've used.
162 | for (BroadcastReceiver br : broadcastReceiverMap.values()) {
163 | localBroadcastManager.unregisterReceiver(br);
164 | }
165 |
166 | try {
167 | MiBandService.getContainer().disconnect();
168 |
169 | }
170 | catch(Exception e) {}
171 | super.onStop();
172 | }
173 |
174 | @Override
175 | public boolean onCreateOptionsMenu(Menu menu) {
176 | // Inflate the menu; this adds items to the action bar if it is present.
177 | getMenuInflater().inflate(R.menu.menu_main, menu);
178 | return true;
179 | }
180 |
181 | @Override
182 | public boolean onOptionsItemSelected(MenuItem item) {
183 | // Handle action bar item clicks here. The action bar will
184 | // automatically handle clicks on the Home/Up button, so long
185 | // as you specify a parent activity in AndroidManifest.xml.
186 | int id = item.getItemId();
187 |
188 | //noinspection SimplifiableIfStatement
189 | if (id == R.id.action_settings) {
190 | return true;
191 | }
192 |
193 | return super.onOptionsItemSelected(item);
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/indrora/jouretnuit/MiBandDevice.java:
--------------------------------------------------------------------------------
1 | package io.github.indrora.jouretnuit;
2 |
3 | import android.bluetooth.BluetoothDevice;
4 | import android.bluetooth.BluetoothGatt;
5 | import android.bluetooth.BluetoothGattCallback;
6 | import android.bluetooth.BluetoothGattCharacteristic;
7 | import android.bluetooth.BluetoothGattDescriptor;
8 | import android.bluetooth.BluetoothGattService;
9 | import android.bluetooth.BluetoothManager;
10 | import android.content.Context;
11 | import android.content.Intent;
12 | import android.util.Log;
13 |
14 | import com.pnikosis.materialishprogress.ProgressWheel;
15 |
16 | import java.util.HashMap;
17 | import java.util.Map;
18 | import java.util.UUID;
19 |
20 | import io.github.indrora.jouretnuit.miband.BandConstants;
21 | import io.github.indrora.jouretnuit.miband.Helper;
22 | import io.github.indrora.jouretnuit.miband.data.BatteryInfo;
23 | import io.github.indrora.jouretnuit.miband.data.ICharacteristicBound;
24 | import io.github.indrora.jouretnuit.miband.data.StepCount;
25 | import io.github.indrora.jouretnuit.model.StringConstants;
26 |
27 | /**
28 | * Created by indrora on 7/15/15.
29 | */
30 | public class MiBandDevice {
31 |
32 |
33 | public BatteryInfo device_batteryinfo = new BatteryInfo();
34 | public StepCount device_steps = new StepCount();
35 |
36 | private Map boundCharacteristics = new HashMap();
37 |
38 | BluetoothManager mBtManager;
39 | BluetoothGatt mBluetoothGatt;
40 | BluetoothDevice mBluetoothDevice;
41 | BluetoothGattService mBandGattService;
42 |
43 | BluetoothGattCharacteristic mControlPointCharacteristic;
44 | BluetoothGattCharacteristic mStepsCharacteristic;
45 | BluetoothGattCharacteristic mBatteryCharacteristic;
46 | BluetoothGattCharacteristic mSensorsCharacteristic;
47 |
48 | private String btAddress;
49 | private boolean isConnected;
50 |
51 |
52 | Context appContext;
53 |
54 | public MiBandDevice(Context appContext) {
55 | this.appContext = appContext;
56 |
57 | boundCharacteristics.put(BandConstants.UUID_CHARACTERISTIC_REALTIME_STEPS, device_steps);
58 | boundCharacteristics.put(BandConstants.UUID_CHARACTERISTIC_BATTERY, device_batteryinfo);
59 | }
60 |
61 |
62 | BluetoothGattCallback btGattCallback = new BluetoothGattCallback() {
63 |
64 | @Override
65 | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
66 | Log.d("$GattCallback", "onConnectionStateChange -> " + newState);
67 | super.onConnectionStateChange(gatt, status, newState);
68 | if (status == BluetoothGatt.GATT_SUCCESS) {
69 | isConnected = true;
70 | gatt.discoverServices();
71 | } else {
72 | isConnected = false;
73 | }
74 | }
75 |
76 | @Override
77 | public void onServicesDiscovered(BluetoothGatt gatt, int status) {
78 | Log.d("$GattCallback", "onServicesDiscovered -> " + status);
79 | super.onServicesDiscovered(gatt, status);
80 | mBandGattService = gatt.getService(BandConstants.UUID_SERVICE_MILI_SERVICE);
81 |
82 | mControlPointCharacteristic = mBandGattService.getCharacteristic(BandConstants.UUID_CHARACTERISTIC_CONTROL_POINT);
83 |
84 | mStepsCharacteristic = mBandGattService.getCharacteristic(BandConstants.UUID_CHARACTERISTIC_REALTIME_STEPS);
85 | mBatteryCharacteristic = mBandGattService.getCharacteristic(BandConstants.UUID_CHARACTERISTIC_BATTERY);
86 | mSensorsCharacteristic = mBandGattService.getCharacteristic(BandConstants.UUID_CHARACTERISTIC_SENSOR_DATA);
87 |
88 | updateValues();
89 |
90 | // set up the neccesary parts for real time updates
91 | setCharacteristicNotification(mStepsCharacteristic, true);
92 | setCharacteristicNotification(mSensorsCharacteristic, true);
93 | Log.d("$GattCallback", "onServicesDiscovered -> we've got walk sign.");
94 |
95 | Intent i = new Intent();
96 | i.setAction(StringConstants.RESPONSE_DEVICE_CONNECTED);
97 | JENApp.makeBroadcast(i);
98 |
99 | }
100 |
101 | @Override
102 | public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
103 | synchronized (characteristic) {
104 |
105 | Log.d("$GattCallback", "onCharacteristicRead -> " + BandConstants.getHelp(characteristic.getUuid()) + " -> " + status);
106 |
107 | byte[] b = characteristic.getValue();
108 | Log.d("$GattCallback", "New value: [ " + Helper.byteArrayToHex(b) + "]");
109 |
110 | UUID tUuid = characteristic.getUuid();
111 | if (boundCharacteristics.containsKey(tUuid)) {
112 | boundCharacteristics.get(tUuid).characteristicChanged(b);
113 | }
114 |
115 | characteristic.notify();
116 | }
117 |
118 | super.onCharacteristicRead(gatt, characteristic, status);
119 |
120 | }
121 |
122 | @Override
123 | public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
124 | Log.d("$GattCallback", "Wrote to {" + characteristic.getUuid() + "} with status " + status);
125 | synchronized (characteristic) {
126 | characteristic.notify();
127 | }
128 | //super.onCharacteristicWrite(gatt, characteristic, status);
129 | }
130 |
131 | @Override
132 | public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
133 | Log.d("$GattCallback", "Characteristic {" + characteristic.getUuid() + "} changed!");
134 | byte[] b = characteristic.getValue();
135 | Log.d("$GattCallback", "new value: " + Helper.byteArrayToHex(b));
136 |
137 |
138 | if (boundCharacteristics.containsKey(characteristic.getUuid())) {
139 | boundCharacteristics.get(characteristic.getUuid()).characteristicChanged(b);
140 | }
141 | }
142 |
143 | @Override
144 | public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
145 | Log.d("$GattCallback", "Wrote descriptor: " + descriptor.getUuid() + " new value = " + Helper.byteArrayToHex(descriptor.getValue()));
146 | //super.onDescriptorWrite(gatt, descriptor, status);
147 | synchronized (descriptor) {
148 | descriptor.notify();
149 | }
150 | }
151 | };
152 |
153 |
154 | public boolean getConnected() {
155 | return isConnected && mBluetoothGatt != null;
156 | }
157 |
158 | private boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enable) {
159 | mBluetoothGatt.setCharacteristicNotification(characteristic, enable);
160 | BluetoothGattDescriptor btGattDescriptor = characteristic.getDescriptor(BandConstants.UUID_DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION);
161 |
162 | if (!enable) {
163 | btGattDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
164 | } else {
165 | if (0 != (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE))
166 | btGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
167 | else if (0 != (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY))
168 | btGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
169 | }
170 | boolean result = mBluetoothGatt.writeDescriptor(btGattDescriptor);
171 | try {
172 | btGattDescriptor.wait();
173 | } finally {
174 | return result;
175 | }
176 | }
177 |
178 | public void connect(String address) {
179 | btAddress = address;
180 | Log.d("MiBandService", "Connecting to device " + btAddress);
181 | mBtManager = (BluetoothManager) appContext.getSystemService(Context.BLUETOOTH_SERVICE);
182 | mBluetoothDevice = mBtManager.getAdapter().getRemoteDevice(btAddress);
183 | mBluetoothGatt = mBluetoothDevice.connectGatt(appContext, true, btGattCallback);
184 | if( mBluetoothGatt.connect() ) {
185 | Log.d("MiBandDevice", "Connected mBluetoothGatt!");
186 | }
187 | else {
188 | Log.e("MiBandDevice", "Unable to connect to mBluetoothGatt!!");
189 | }
190 | }
191 |
192 |
193 | public void updateValues() {
194 |
195 | Thread t = new Thread() {
196 | @Override
197 | public void run() {
198 | if(mBandGattService == null) { return; }
199 | synchronized (mStepsCharacteristic) {
200 | mBluetoothGatt.readCharacteristic(mStepsCharacteristic);
201 | try {
202 | Log.d("MiBandDevice", "wait on: steps characteristic");
203 | mStepsCharacteristic.wait();
204 | } catch (Exception ee) {
205 | Log.e("MiBandDevice", "Failed in waiting!", ee);
206 | }
207 | }
208 | synchronized (mBatteryCharacteristic) {
209 |
210 | mBluetoothGatt.readCharacteristic(mBatteryCharacteristic);
211 | try {
212 | Log.d("MiBandDevice", "Wait on: battery characteristic");
213 | mBatteryCharacteristic.wait();
214 | } catch (Exception ee) {
215 | Log.e("MiBandDevice", "Failed in waiting!", ee);
216 | }
217 | }
218 |
219 |
220 | Log.d("MiBandDevice", "Finished updating values");
221 | Intent i = new Intent(StringConstants.RESPONSE_SYNC_DATA);
222 | JENApp.makeBroadcast(i);
223 |
224 | }
225 | };
226 |
227 | t.start();
228 |
229 |
230 |
231 | }
232 |
233 | public void disconnect() {
234 | // disconnect BTLE.
235 | if(mStepsCharacteristic != null)
236 | setCharacteristicNotification(mStepsCharacteristic, false);
237 | if(mSensorsCharacteristic != null)
238 | setCharacteristicNotification(mSensorsCharacteristic, false);
239 |
240 | setStepUpdates(false);
241 |
242 | isConnected = false;
243 | mBluetoothGatt.close();
244 |
245 | }
246 |
247 |
248 | public boolean setStepUpdates(boolean enable) {
249 | Log.d("MiBandDevice", "Setting step updates to " + (enable ? "enabled" : "disabled"));
250 | mControlPointCharacteristic = mBandGattService.getCharacteristic(BandConstants.UUID_CHARACTERISTIC_CONTROL_POINT);
251 | mControlPointCharacteristic.setValue(new byte[]{18, (byte) (enable ? 1 : 0)});
252 | if (mBluetoothGatt.writeCharacteristic(mControlPointCharacteristic)) {
253 | Log.d("MiBandDevice", "We wrote the value!");
254 | try {
255 | mControlPointCharacteristic.wait(2000);
256 | } catch (Exception e) {
257 | return false;
258 | }
259 | return true;
260 | }
261 | Log.e("MiBandDevice", "Couldn't write value!");
262 | return false;
263 | }
264 | }
265 |
--------------------------------------------------------------------------------