/*
* Copyleft of Simone Margaritelli aka evilsocket <evilsocket@gmail.com>
* http://www.evilsocket.net/
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software. If not, see <http://www.gnu.org/licenses/>.
*/
package com.evilsocket.blehacks;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import android.util.Log;
import android.widget.TextView;
public class Utils
{
static class Logger
{
private static final String TAG = "BLEHACKS";
private static TextView _logView = null;
private static MainActivity _activity = null;
public static void setLogView( MainActivity act, TextView v ) {
_activity = act;
_logView = v;
}
private static void log2view( final String m ) {
if( _activity != null && _logView != null ){
_activity.runOnUiThread(new Runnable() {
@Override
public void run() {
_logView.append( m + "\n" );
}
});
}
}
public static void e( String m ) {
Log.e( TAG, m );
log2view( "[ERROR] " + m );
}
public static void i( String m ) {
Log.i( TAG, m );
log2view( "[INFO] " + m );
}
public static void w( String m ) {
Log.w( TAG, m );
log2view( "[WARN] " + m );
}
public static void d( String m ) {
//Log.d( TAG, m );
//log2view( "[DEBUG] " + m );
}
}
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
if( bytes == null ){
return "<null>";
}
char[] hexChars = new char[bytes.length * 2];
int v;
for ( int j = 0; j < bytes.length; j++ ) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static byte[] hexToBytes( String s ) {
byte[] buffer = new byte[ s.length() / 2 ];
for( int i = 0, j = 0; i < s.length(); i += 2, ++j ){
buffer[j] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return buffer;
}
private static Map<Integer, String> sSettingsCodeToStringMap;
private static Map<String, Integer> sSettingsStringToCodeMap;
static
{
final HashMap<String, Integer> hashMap = new HashMap<String, Integer>(50);
hashMap.put("SERIAL_NUMBER", 0);
hashMap.put("BAND_COLOR", 1);
hashMap.put("BLE_ADDRESS", 2);
hashMap.put("XTAL_TRIM", 3);
hashMap.put("BLE_POWER_LEVEL", 4);
hashMap.put("ADC_CAL_VCC_MV", 5);
hashMap.put("ADC_CAL_VREFINT_CONV", 6);
hashMap.put("ADC_CAL_BAT_RATIO", 7);
hashMap.put("LED_ROW_BALANCE", 8);
hashMap.put("LED_DOT_CORRECTION", 9);
hashMap.put("ALS_BOOST", 10);
hashMap.put("GOAL_0", 40);
hashMap.put("GOAL_1", 41);
hashMap.put("GOAL_2", 42);
hashMap.put("GOAL_3", 43);
hashMap.put("GOAL_4", 44);
hashMap.put("GOAL_5", 45);
hashMap.put("GOAL_6", 46);
hashMap.put("TEMP_GOAL", 47);
hashMap.put("FUEL", 48);
hashMap.put("CALORIES", 49);
hashMap.put("STEPS", 50);
hashMap.put("24HRCLOCK", 56);
hashMap.put("WEIGHT", 61);
hashMap.put("HEIGHT", 62);
hashMap.put("DOB", 63);
hashMap.put("GENDER", 64);
hashMap.put("ZONEOFFSET", 69);
hashMap.put("DSTOFFSET", 70);
hashMap.put("MOVE_REMINDER_HRS", 92);
hashMap.put("HOURSWON", 90);
hashMap.put("FIRST_NAME", 97);
hashMap.put("HANDEDNESS", 65);
hashMap.put("IN_SESSION_LED", 99);
hashMap.put("MENU_CALORIES", 57);
hashMap.put("MENU_FUELRATE", 60);
hashMap.put("MENU_GOAL", 59);
hashMap.put("MENU_STEPS", 58);
hashMap.put("MENU_STARS", 89);
hashMap.put("LIFETIME_FUEL", 94);
hashMap.put("DISCOVERY_TOKEN", 75);
sSettingsStringToCodeMap = (Map<String, Integer>)Collections.unmodifiableMap(hashMap);
sSettingsCodeToStringMap = new HashMap<Integer, String>(sSettingsStringToCodeMap.size());
for( String s : sSettingsStringToCodeMap.keySet() )
{
sSettingsCodeToStringMap.put( sSettingsStringToCodeMap.get(s), s);
}
}
protected static String getSettingKey(final int n) {
return sSettingsCodeToStringMap.get(n);
}
public static int getSettingCode(String name ){
return sSettingsStringToCodeMap.get(name);
}
public static Collection<Integer> getSettingCodes(){
return sSettingsStringToCodeMap.values();
}
protected static SettingType getSettingType(final int n) {
switch (n) {
default: {
return SettingType.UINT32;
}
case 0:
case 97: {
return SettingType.STRING;
}
case 3:
case 4:
case 65: {
return SettingType.BYTE;
}
case 2:
case 8:
case 9:
case 10:
case 75: {
return SettingType.BYTE_ARRAY;
}
case 63: {
return SettingType.DOB_CALENDAR;
}
case 56:
case 57:
case 58:
case 59:
case 60:
case 89: {
return SettingType.BOOLEAN;
}
case 64: {
return SettingType.GENDER;
}
case 69: {
return SettingType.UINT32;
}
case 70: {
return SettingType.BYTE;
}
case 99: {
return SettingType.BOOLEAN;
}
case 62: {
return SettingType.BYTE;
}
case 61: {
return SettingType.UINT16;
}
}
}
protected enum SettingType
{
BOOLEAN,
BYTE,
BYTE_ARRAY,
DOB_CALENDAR,
GENDER,
HOURS,
STRING,
UINT16,
UINT32;
}
public static void processGetSettingsResponse(byte[] raw) throws Exception {
ByteBuffer byteBuffer = ByteBuffer.wrap(raw);
// skip protocol first four bytes
byte dummy1 = byteBuffer.get();
byte dummy2 = byteBuffer.get();
byte dummy3 = byteBuffer.get();
byte dummy4 = byteBuffer.get();
byte elements = byteBuffer.get();
if (elements <= 0) {
return;
}
for(byte b = 0; b < elements; ++b)
{
if( byteBuffer.remaining() < 2 ) {
throw new Exception("Decoding error, not enough remaining bytes");
}
byte configKey = byteBuffer.get();
byte valueSize = byteBuffer.get();
valueSize = (byte)( Math.min( (int)valueSize, byteBuffer.remaining() ) & 0xFF );
final String settingKey = getSettingKey(configKey);
if (settingKey == null)
{
Logger.e( "Unknown config key " + configKey );
byteBuffer.position(valueSize + byteBuffer.position());
}
else
{
switch (getSettingType(configKey))
{
case BYTE: {
Logger.i( "Got setting: " + settingKey + " = " + ( byteBuffer.get() & 0xFF ) );
break;
}
case BYTE_ARRAY: {
final byte[] array = new byte[valueSize];
byteBuffer.get(array);
Logger.i( "Got setting: " + settingKey + " = " + Utils.bytesToHex(array) );
break;
}
case STRING: {
final byte[] array2 = new byte[valueSize];
byteBuffer.get(array2);
byte b2;
for (b2 = 0; b2 < valueSize && array2[b2] != 0; ++b2) {}
Logger.i( "Got setting: " + settingKey + " = " + new String(array2, 0, b2, Charset.forName("US-ASCII")) );
break;
}
case UINT32: {
Logger.i( "Got setting: " + settingKey + " = " + byteBuffer.getInt() );
break;
}
case UINT16: {
Logger.i( "Got setting: " + settingKey + " = " + byteBuffer.getShort() );
break;
}
case BOOLEAN: {
Logger.i( "Got setting: " + settingKey + " = " + ( byteBuffer.get() == 0 ? "false" : "true" ) );
break;
}
case DOB_CALENDAR: {
final int int1 = byteBuffer.getInt();
Logger.i( "Got setting: " + settingKey + " = " + ( new GregorianCalendar(int1 & 0xFFFF, -1 + (0xFF & int1 >>> 16), int1 >>> 24) ) );
break;
}
case GENDER: {
String gender = "UNKNOWN";
switch (byteBuffer.get()) {
case 77: {
gender = "MALE";
break;
}
case 70: {
gender = "FEMALE";
break;
}
}
Logger.i( "Got setting: " + settingKey + " = " + gender );
break;
}
default:
throw new Exception("Unknown config type");
}
}
}
}
}