diff --git a/.gitignore b/.gitignore index 347e252..1ce5acb 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ google-services.json # Android Profiling *.hprof + +.DS_Store diff --git a/README.md b/README.md index ac25a28..8bd7aec 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # mEDIFIER -Edifier Connect(140MB+)->mEDIFIER(3MB+) +I just don't know why the Edifier Connect takes more than 200MB on my phone. + +# Limitations ++ Tested on W820NB and W820NB Plus only ++ Works for RFCOMM only \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..c407dda --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'priv.wh201906.medifier' + compileSdk 31 + + defaultConfig { + applicationId "priv.wh201906.medifier" + minSdk 19 + targetSdk 31 + versionCode 1 + versionName "0.0.1" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..79280b3 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/priv/wh201906/medifier/MainActivity.java b/app/src/main/java/priv/wh201906/medifier/MainActivity.java new file mode 100644 index 0000000..fb3b3db --- /dev/null +++ b/app/src/main/java/priv/wh201906/medifier/MainActivity.java @@ -0,0 +1,281 @@ +package priv.wh201906.medifier; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; + +import android.Manifest; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.ParcelUuid; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.SimpleAdapter; +import android.widget.Toast; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public class MainActivity extends AppCompatActivity +{ + private static final String TAG = "mEDIFIER"; + private final UUID SPP_UUID = UUID.fromString("EDF00000-EDFE-DFED-FEDF-EDFEDFEDFEDF"); + private BluetoothAdapter mBluetoothAdapter; + private BluetoothSocket mBluetoothSocket; + private OutputStream mOutputStream; + private boolean mConnected = false; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Button connectButton = findViewById(R.id.connectButton); + ListView commandListView = findViewById(R.id.commandListView); + + connectButton.setOnClickListener(view -> + { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + if (mBluetoothAdapter == null) + { + Toast.makeText(getApplicationContext(), R.string.toast_no_bluetooth, Toast.LENGTH_SHORT).show(); + } + if (!mBluetoothAdapter.isEnabled()) + { + Toast.makeText(getApplicationContext(), R.string.toast_bluetooth_not_open, Toast.LENGTH_SHORT).show(); + } + else + { + connectToDevice(); + } + + }); + + InputStream inputStream = getResources().openRawResource(R.raw.cmd); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder stringBuilder = new StringBuilder(); + String line; + try + { + while ((line = reader.readLine()) != null) + stringBuilder.append(line); + reader.close(); + inputStream.close(); + } catch (IOException e) + { + e.printStackTrace(); + } + + JSONArray jsonArray = null; + List> adapterData = new ArrayList<>(); + try + { + jsonArray = new JSONArray(stringBuilder.toString()); + String packageName = getPackageName(); + for (int i = 0; i < jsonArray.length(); i++) + { + JSONObject jsonObject = jsonArray.getJSONObject(i); + String name = jsonObject.getString("name"); + int translatedTextId = getResources().getIdentifier(name, "string", packageName); + String cmd = jsonObject.getString("cmd"); + + Map item = new HashMap<>(); + item.put("name", (translatedTextId == 0) ? name : getString(translatedTextId)); + item.put("cmd", cmd); + + adapterData.add(item); + } + } catch (JSONException e) + { + throw new RuntimeException(e); + } + + String[] from = {"name", "cmd"}; + int[] to = {android.R.id.text1, android.R.id.text2}; + + SimpleAdapter adapter = new SimpleAdapter(this, adapterData, android.R.layout.simple_list_item_2, from, to); + commandListView.setAdapter(adapter); + commandListView.setOnItemClickListener((parent, view, position, id) -> + { + Map button = (Map) parent.getItemAtPosition(position); + String cmdHex = button.get("cmd"); + byte[] cmd = hexToBytes(cmdHex); + byte[] cmdWithHead = new byte[cmd.length + 2]; + cmdWithHead[0] = -86; + // should not be more than 127 + cmdWithHead[1] = (byte) cmd.length; + System.arraycopy(cmd, 0, cmdWithHead, 2, cmd.length); + byte[] fullCmd = addCRC(cmdWithHead); + Log.i("DATA", bytesToHex(fullCmd)); + sendData(fullCmd); + }); + + + } + + public static String bytesToHex(byte[] bytes) + { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) + { + sb.append(String.format("%02X", b)); + } + return sb.toString(); + } + + public static byte[] hexToBytes(String hexString) + { + int length = hexString.length(); + byte[] data = new byte[length / 2]; + for (int i = 0; i < length; i += 2) + { + data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + + Character.digit(hexString.charAt(i + 1), 16)); + } + return data; + } + + public byte[] addCRC(byte[] bArr) + { + int i; + byte b; + i = 8217; + for (byte b2 : bArr) + { + i += b2 & 255; + } + int length = bArr.length + 2; + byte[] bArr2 = new byte[length]; + System.arraycopy(bArr, 0, bArr2, 0, bArr.length); + bArr2[length - 2] = (byte) (i >> 8); + bArr2[length - 1] = (byte) (i & 255); + CRCCheck(bArr2); + Log.i("CRC", "CRC_value:" + bytesToHex(bArr2) + ",length:" + i + ",data:" + bytesToHex(bArr)); + return bArr2; + } + + public static boolean CRCCheck(byte[] bArr) + { + int i = (bArr[bArr.length - 1] & 255) + ((bArr[bArr.length - 2] << 8) & 65280); + int i2 = 0; + for (int i3 = 0; i3 < bArr.length - 1; i3++) + { + if (i3 < bArr.length - 2) + { + i2 += bArr[i3] & 255; + } + } + return i2 + 8217 == i; + } + + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) + { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == 0) + { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) + { + } + else + { + } + } + } + + private void connectToDevice() + { + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) + { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH_CONNECT}, 0); + return; + } + Set bondedDevices = mBluetoothAdapter.getBondedDevices(); + BluetoothSocket socket = null; + boolean isEDIFIERDevice = false; + for (BluetoothDevice device : bondedDevices) + { + try + { + isEDIFIERDevice = false; + Log.i(TAG, device.getAddress()); + for (ParcelUuid uuid : device.getUuids()) + { + if (uuid.getUuid().equals(SPP_UUID)) + { + isEDIFIERDevice = true; + break; + } + } + if (!isEDIFIERDevice) + continue; + + socket = device.createRfcommSocketToServiceRecord(SPP_UUID); + socket.connect(); + break; + } catch (IOException e) + { + e.printStackTrace(); + } + } + if (!isEDIFIERDevice) + { + Toast.makeText(getApplicationContext(), R.string.toast_device_not_found, Toast.LENGTH_SHORT).show(); + } + if (socket.isConnected()) + { + mBluetoothSocket = socket; + try + { + mOutputStream = mBluetoothSocket.getOutputStream(); + } catch (IOException e) + { + e.printStackTrace(); + } + mConnected = true; + Log.i(TAG, "Connected"); + Toast.makeText(getApplicationContext(), R.string.toast_device_connected, Toast.LENGTH_SHORT).show(); + } + } + + private void sendData(byte[] data) + { + if (!mConnected) + { + Toast.makeText(getApplicationContext(), R.string.toast_device_not_connected, Toast.LENGTH_SHORT).show(); + return; + } + try + { + mOutputStream.write(data); + } catch (IOException e) + { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..5148412 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,34 @@ + + + +