Initial commit

This commit is contained in:
unknown
2022-06-09 19:24:41 +08:00
commit 6b7abef35b
57 changed files with 3595 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

0
.idea/.gitignore generated vendored Normal file
View File

116
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,116 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

6
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

21
.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="delegatedBuild" value="false" />
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

35
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="..\:/Project/TesMla/app/src/main/res/layout/fragment_files.xml" value="0.13072916666666667" />
<entry key="..\:/Project/TesMla/app/src/main/res/layout/fragment_phone.xml" value="0.13072916666666667" />
<entry key="..\:/Projects/Bluetooth/Google/TesMla/app/src/main/res/layout/activity_main.xml" value="0.18478260869565216" />
<entry key="..\:/Projects/Bluetooth/Google/TesMla/app/src/main/res/layout/fragment_files.xml" value="0.1" />
<entry key="..\:/Projects/Bluetooth/Google/TesMla/app/src/main/res/layout/fragment_phone.xml" value="0.1911231884057971" />
<entry key="..\:/Projects/Bluetooth/Google/TesMla/app/src/main/res/layout/fragment_vehicle.xml" value="0.1911231884057971" />
<entry key="..\:/Projects/Bluetooth/Google/TesMla/app/src/main/res/layout/listview_item.xml" value="0.1911231884057971" />
<entry key="..\:/Projects/Bluetooth/Google/TesMla/app/src/main/res/menu/bottom_nav_menu.xml" value="0.22916666666666666" />
<entry key="..\:/TesMla/app/src/main/res/layout/activity_main.xml" value="0.2441123188405797" />
<entry key="..\:/TesMla/app/src/main/res/layout/fragment_files.xml" value="0.209375" />
<entry key="..\:/TesMla/app/src/main/res/layout/fragment_phone.xml" value="0.20677083333333332" />
<entry key="..\:/TesMla/app/src/main/res/layout/fragment_vehicle.xml" value="0.209375" />
<entry key="..\:/TesMla/app/src/main/res/layout/listview_item.xml" value="0.20677083333333332" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="VisualizationToolProject">
<option name="state">
<ProjectState>
<option name="scale" value="0.1" />
</ProjectState>
</option>
</component>
</project>

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

45
app/build.gradle Normal file
View File

@@ -0,0 +1,45 @@
plugins {
id 'com.android.application'
}
android {
compileSdk 30
defaultConfig {
applicationId "com.example.tesmla"
minSdk 26
targetSdk 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.navigation:navigation-fragment:2.3.5'
implementation 'androidx.navigation:navigation-ui:2.3.5'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

21
app/proguard-rules.pro vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1,26 @@
package com.example.tesmla;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.example.tesmla", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.tesmla">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BlUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-feature android:name="android.bluetooth.le" android:required="true"/>
<application
android:requestLegacyExternalStorage="true"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@drawable/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.TesMla">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,90 @@
package com.example.tesmla;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.example.tesmla.databinding.ActivityMainBinding;
import java.io.DataOutputStream;
import java.io.File;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_vehicle, R.id.navigation_phone, R.id.navigation_files)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);
BluetoothManager mBluetoothManager=(BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = mBluetoothManager.getAdapter();
//.判断蓝牙是否打开
if (!mBluetoothAdapter.enable()) {
Intent btEnable = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);//没打开请求打开蓝牙
startActivityForResult(btEnable, 100);
}
if(upgradeRootPermission(getPackageCodePath())){
Log.w("[ROOT]","already change to root permission");
}
File file = new File(String.format(getResources().getString(R.string.dir),"log"));
if (file.exists()) {
if (!file.delete()) {
Log.e("[err]", "log delete failed");
}
}
}
/**
* 应用程序运行命令获取 Root权限设备必须已破解(获得ROOT权限)
* @return 应用程序是/否获取Root权限
*/
public static boolean upgradeRootPermission(String pkgCodePath) {
Process process = null;
DataOutputStream os = null;
try {
String cmd="chmod 777 " + pkgCodePath;
process = Runtime.getRuntime().exec("su"); //切换到root帐号
os = new DataOutputStream(process.getOutputStream());
os.writeBytes(cmd + "\n");
os.writeBytes("exit\n");
os.flush();
process.waitFor();
} catch (Exception e) {
return false;
} finally {
try {
if (os != null) {
os.close();
}
if (process != null) {
process.destroy();
}
} catch (Exception e) {
Log.e("[ERR]","runtime wrong");
}
}
return true;
}
}

View File

@@ -0,0 +1,571 @@
package com.example.tesmla.ui.files;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.example.tesmla.R;
import com.example.tesmla.databinding.FragmentFilesBinding;
import com.example.tesmla.utils.Conversion;
import com.example.tesmla.utils.ToastUtils;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.List;
import java.util.UUID;
/**
* This Class show as right fragment
* Function: 1. Connect the specified device
* 2. Work as a BLE Client sending the similar command as Tesla App
* 3. Communicate with connected device and show the data
*/
public class FilesFragment extends Fragment {
private BluetoothGatt mBluetoothGatt;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothDevice mDevice;
private String mac;
private boolean isServiceConnected;
private TextView log_data;
private TextView addr;
private EditText tx_to_car;
private Button connect;
private Button disconn;
private String state_cmd;
private String sign1;
private String sign2;
private String counter = "NULL";
private boolean epoch;
UUID SER_UUID = UUID.fromString("00000211-B2D1-43F0-9B88-960CEBF8B91E");
UUID TX_UUID = UUID.fromString("00000212-B2D1-43F0-9B88-960CEBF8B91E");
UUID RX_UUID = UUID.fromString("00000213-B2D1-43F0-9B88-960CEBF8B91E");
UUID DESPCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
BluetoothGattService car_service;
BluetoothGattCharacteristic characteristic_TX;
BluetoothGattCharacteristic characteristic_RX;
private final MyHandler mhandler = new MyHandler(Looper.myLooper());
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
FragmentFilesBinding binding = FragmentFilesBinding.inflate(inflater, container, false);
View root = binding.getRoot();
addr = binding.addr;
Message msg = new Message();
msg.what = 4;
mhandler.sendMessage(msg);
isServiceConnected = false;
log_data = binding.log;
connect = binding.connect;
disconn = binding.disconnect;
tx_to_car = binding.txToCar;
Button send = binding.send;
Button getPK = binding.getPk;
Button change = binding.changeMtu;
log_data.setMovementMethod(ScrollingMovementMethod.getInstance());
log_data.setGravity(Gravity.BOTTOM);
if(!isServiceConnected){
disconn.setEnabled(false);
}
FragmentActivity act = getActivity();
if(act != null) {
BluetoothManager mBluetoothManager = (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
if(!mBluetoothAdapter.isEnabled()){
mBluetoothAdapter.enable();
}
}
try {
mac = getAddress();
} catch (IOException e) {
e.printStackTrace();
}
addr.setText(mac);
connect.setOnClickListener((View v) -> startConnect());
disconn.setOnClickListener((View v) -> stopConnect());
change.setOnClickListener((View v) -> changeMTU());
getPK.setOnClickListener((View v) -> {
try {
getPublicKey();
} catch (IOException e) {
e.printStackTrace();
}
});
send.setOnClickListener((View v) -> {
try {
epoch = true;
startSend(state_cmd);
} catch (IOException e) {
e.printStackTrace();
}
});
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (mBluetoothGatt != null) {
stopConnect();
}
}
/**
* log data in data/data/com.example.tesmla/files/log
* @param data String: data to be recorded
* @throws IOException : file not found or permission denied
*/
private void writeLog(String data) throws IOException {
BufferedWriter writer;
FragmentActivity act = getActivity();
if(act != null) {
FileOutputStream out = act.openFileOutput("log", Context.MODE_APPEND);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(data);
writer.flush();
}
}
/**
* record pk and token info in data/data/com.example.tesmla/files/fake_vehicle
* @param data String: pk and token to be recorded
* @throws IOException : file not found or permission denied
*/
private void record(String data) throws IOException {
BufferedWriter writer;
FragmentActivity act = getActivity();
if(act != null) {
FileOutputStream out = act.openFileOutput("fake_tesla", Context.MODE_APPEND);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(data);
writer.flush();
}
}
private void getPublicKey() throws IOException {
if(isServiceConnected){
startSend("000E120C0A0A080312060A04AB5794C2");
}
else{
ToastUtils.showToast(getActivity(), "未连接设备", 2000);
}
}
/**
* Determine whether the input string satisfies MAC address format requirement
* @param val String :The string to be determined
* @return boolean: true if satisfied
*/
private boolean stringIsMac(String val) {
String trueMacAddress = "([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}";
return val.matches(trueMacAddress);
}
/**
* Start connect to the device by specific mac address and add callback
*/
public void startConnect() {
if(stringIsMac(mac)) {
mDevice = mBluetoothAdapter.getRemoteDevice(mac);
if (mDevice != null) {
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
mBluetoothGatt = null;
}
mBluetoothGatt = mDevice.connectGatt(getActivity(), false, mGattCallback);
}
}
}
/**
* Return the bluetooth mac address recorded in data/data/com.example.tesmla/files/fake_dev
* @return String Bluetooth MAC address
* @throws IOException file not found or permission denied
*/
private String getAddress() throws IOException {
try {
File file = new File(String.format(getResources().getString(R.string.dir),"fake_dev"));
if (file.exists()) {
FileInputStream fin = new FileInputStream(file);
byte[] buffer = new byte[80];
if (fin.read(buffer) > 0) {
String s = new String(buffer);
return s.substring(s.indexOf("\n") + 1, s.indexOf("\n") + 18);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* Send the data to GATT server
* @param tx the command data need to be send
* @throws IOException file not found or permission denied
*/
public void startSend(String tx) throws IOException {
if (mBluetoothGatt != null && isServiceConnected) {
String tx_from_screen = tx_to_car.getText().toString();
Message msg = new Message();
msg.what = 0;
msg.obj = "";
if(tx_from_screen.length()>0){//If user input command on screen, send the input data.
byte[] send_data = Conversion.hexStringToBytes(tx_from_screen);
characteristic_TX.setValue(send_data);
characteristic_TX.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
mBluetoothGatt.writeCharacteristic(characteristic_TX);
msg.obj = "<<" + tx_from_screen + "\n";
try {
writeLog("<<" + tx_from_screen + "\n");
} catch (IOException e) {
e.printStackTrace();
}
}
else{//send the specific data
byte[] send_data = Conversion.hexStringToBytes(tx);
msg.obj = "<<" + tx + "\n";
writeLog("<<" + tx + "\n");
new Thread(){
@Override
public void run() {
super.run();
characteristic_TX.setValue(send_data);
characteristic_TX.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
boolean writeTrue = mBluetoothGatt.writeCharacteristic(characteristic_TX);
try {
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
mhandler.sendMessage(msg);
}
}
/**
* Request an MTU size used for a given connection.
*/
public void changeMTU(){
if(mBluetoothGatt!= null && isServiceConnected) {
mBluetoothGatt.requestMtu(259);
Message msg = new Message();
msg.what = 2;
msg.obj = "mtu size changed\n";
mhandler.sendMessage(msg);
}
}
public void stopConnect() {
if (mBluetoothGatt != null) {
isServiceConnected = false;
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
try {
mac = getAddress();
addr.setText(mac);
} catch (IOException e) {
e.printStackTrace();
}
connect.setEnabled(true);
}
}
/**
* Callback to handle incoming response from the GATT server.
*/
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
/**
* Callback indicating when GATT client has connected/disconnected to/from a remote GATT server.
* @param gatt BluetoothGatt: GATT client
* @param status int: Status of the connect or disconnect operation. BluetoothGatt.GATT_SUCCESS if the operation succeeds.
* @param newState int: Returns the new connection state. Can be one of BluetoothProfile.STATE_DISCONNECTED or BluetoothProfile#STATE_CONNECTED
*/
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
FragmentActivity act = getActivity();
if (newState == BluetoothProfile.STATE_CONNECTED) {
//discover the service of GATT server
if (act != null) {
if (status == BluetoothGatt.GATT_SUCCESS) {
isServiceConnected = true;
gatt.discoverServices();
Message msg = new Message();
msg.what = 3;
mhandler.sendMessage(msg);
epoch = true;
try {
writeLog("############# fake as app ##############\n");
//read the public key and token from the file which be recorded during the process of fake as app
File file = new File(String.format(getResources().getString(R.string.dir),"fake_app"));
if (file.exists()) {
FileInputStream fin = new FileInputStream(file);
byte[] buffer = new byte[4096];
if (fin.read(buffer) > 50) {
String s = new String(buffer);
int index = 0;
int tmp = s.indexOf("sign2:");
if(tmp > 0) {
while (s.indexOf("sign2:", tmp) > 0 && tmp > 0) {
index = s.indexOf("sign1:", index + 1);
tmp = s.indexOf("sign2:", tmp + 1);
Log.e("!!!!!!!!", index + " " + tmp);
}
}else{
index = s.lastIndexOf("sign1:");
}
if(index >= 0) {
sign1 = s.substring(index + 6, s.indexOf("\n", index));
}
index = s.lastIndexOf("sign2:");
if(index >= 0) {
sign2 = s.substring(index + 6, s.indexOf("\n", index));
}
index = s.lastIndexOf("cmd:");
if(index >= 0) {
state_cmd = s.substring(index + 4, s.indexOf("\n", index));
}
}
}
}catch (IOException e) {
e.printStackTrace();
}
}
else{//Connection failed
String err = "Cannot connect device with error status: " + status;
gatt.close();
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
mBluetoothGatt = null;
}
if (mDevice != null) {
mBluetoothGatt = mDevice.connectGatt(act, false, mGattCallback);
}
Log.e("[ERR]", err);
}
}
}
//Device connection state change to disconnected.
else if (newState == BluetoothProfile.STATE_DISCONNECTED ||newState == BluetoothProfile.STATE_DISCONNECTING) {
Log.i("", "BluetoothDevice DISCONNECTED: " );
isServiceConnected = false;
Message msg = new Message();
msg.what = 4;
mhandler.sendMessage(msg);
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
mBluetoothGatt = null;
}
gatt.close();
if (mDevice != null) {
mBluetoothGatt = mDevice.connectGatt(act, false, mGattCallback);
}
}
}
/**
* Callback triggered as a result of a remote characteristic notification/indication.
* Note that the value within the characteristic object may have changed since receiving the remote characteristic notification,
* so check the parameter value for the value at the time of notification.
* @param gatt BluetoothGatt: GATT client the characteristic is associated with
* @param characteristic BluetoothGattCharacteristic: Characteristic that has been updated as a result of a remote notification event.
*/
@Override
public final void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
byte[] value = characteristic.getValue();
String data = Conversion.Bytes2HexString(value).toUpperCase();
//Show the received response on the screen
Message msg = new Message();
msg.what = 2;
msg.obj = ">>" + Conversion.Bytes2HexString(value) + "\n";
mhandler.sendMessage(msg);
String cnt_resp = "null";
String auto_pattern = "00041A021802"; //Response: Authentication-level requirement
if (!data.startsWith(auto_pattern) && epoch) { //Not Authentication-level requirement
//record the data in log_app file
Log.d("[recv]", "receive:" + data);
int index = data.lastIndexOf("08") + 2;
if(index > 3){
cnt_resp = data.substring(index);
}
try {
writeLog(">>" + data + "\n");
if (data.startsWith("004512431A4104")) {//If received the public key, record in pk file
record("pk:" + data + "\n");
}
else if(data.startsWith("001C1A1A12160A14")){//received token
record("token:" + data + "\n");
startSend(sign2);
epoch = false;
}
else if(!counter.equals(cnt_resp)){
counter = sign1.substring(sign1.indexOf("30") +2, sign1.indexOf("2A04",sign1.indexOf("30")));
startSend(sign1);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Callback invoked when the list of remote services, characteristics and descriptors for the remote device have been updated,
* ie new services have been discovered.
* @param gatt BluetoothGatt: GATT client invoked BluetoothGatt#discoverServices
* @param status int: BluetoothGatt#GATT_SUCCESS if the remote device has been explored successfully.
*/
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
List<BluetoothGattService> mServiceList;
mServiceList = gatt.getServices();
if (mServiceList != null) {
System.out.println(mServiceList);
System.out.println("Services num:" + mServiceList.size());
for (BluetoothGattService service : mServiceList){
List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
System.out.println("扫描到Service" + service.getUuid());
for (BluetoothGattCharacteristic characteristic : characteristics) {
System.out.println("characteristic: " + characteristic.getUuid() );
}
if(service.getUuid().equals(SER_UUID)){
car_service = service;
characteristic_TX = car_service.getCharacteristic(TX_UUID);
characteristic_RX = car_service.getCharacteristic(RX_UUID);
BluetoothGattDescriptor descriptor = characteristic_RX.getDescriptor(DESPCRIPTOR);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
mBluetoothGatt.setCharacteristicNotification(characteristic_RX, true);
}
}
}
}
/**
* Callback indicating the result of a descriptor write operation.
* @param gatt BluetoothGatt: GATT client invoked BluetoothGatt#writeDescriptor
* @param descriptor BluetoothGattDescriptor: Descriptor that was write to the associated remote device.
* @param status int: The result of the write operation BluetoothGatt#GATT_SUCCESS if the operation succeeds.
*/
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
}
};
/**
* Bluetooth operation often work as asynchronous thread. This handler operate the UI in non-main thread
*/
class MyHandler extends Handler {
public MyHandler(@NonNull Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 4:
try {
mac = getAddress();
addr.setText(mac);
connect.setEnabled(true);
} catch (IOException e) {
e.printStackTrace();
}
break;
case 3:
addr.setText(getResources().getString(R.string.connected));
connect.setEnabled(false);
disconn.setEnabled(true);
break;
case 2:
log_data.append(""+msg.obj);
break;
case 1:
tx_to_car.setText((String)msg.obj);
break;
case 0:
log_data.append(""+msg.obj);
tx_to_car.setText("");
break;
default:
Log.e("[err]", "unknown handler");
}
}
}
}

View File

@@ -0,0 +1,91 @@
package com.example.tesmla.ui.phone;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.example.tesmla.R;
import java.util.List;
/**
* This class help show the result of BLE scan. It record the name, mac address and broadcast data for each device which been scanned.
*/
public class ListViewAdapter extends BaseAdapter {
//Info like Name and mac address which listview need
private final List<BlueTooth_item__Bean> listDatas;
private final LayoutInflater layoutInflater;
ListViewAdapter(Context mcontext, List<BlueTooth_item__Bean> listDatas){
this.listDatas = listDatas;
layoutInflater = LayoutInflater.from(mcontext);
}
@Override
public int getCount() {
return listDatas.size();
}
@Override
public Object getItem(int position) {
return listDatas.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@SuppressLint("InflateParams")
@Override
public View getView(int position, View view, ViewGroup parent) {
ListviewHolder listviewHolder;
if(view ==null){
listviewHolder = new ListviewHolder();
view = layoutInflater.inflate(R.layout.listview_item, null);
listviewHolder.device_Name = view.findViewById(R.id.device_name);
listviewHolder.device_Address= view.findViewById(R.id.device_address);
view.setTag(listviewHolder);
}else{
listviewHolder = (ListviewHolder) view.getTag();
}
listviewHolder.device_Name.setText(listDatas.get(position).blueToothDevie_Name);
listviewHolder.device_Address.setText(listDatas.get(position).blueToothDevie_Address);
return view;
}
static class ListviewHolder{
private TextView device_Name;
private TextView device_Address;
}
//This class is used for item of listview. Record name, mac address and broadcast data for each item
public static class BlueTooth_item__Bean {
String blueToothDevie_Name;
String blueToothDevie_Address;
String record_data;
@Override
public boolean equals(Object obj) {
if(obj instanceof BlueTooth_item__Bean){
if(blueToothDevie_Address.equals(((BlueTooth_item__Bean) obj).blueToothDevie_Address)){
return true;
}
}else{
return false;
}
return super.equals(obj);
}
@Override
public int hashCode() {
return blueToothDevie_Address.hashCode();
}
}
}

View File

@@ -0,0 +1,332 @@
package com.example.tesmla.ui.phone;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.example.tesmla.databinding.FragmentPhoneBinding;
import com.example.tesmla.utils.Conversion;
import com.example.tesmla.utils.ToastUtils;
import java.io.BufferedWriter;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
/**
* This Class show as middle fragment
* Function: 1. Scan surrounding BLE devices and show the result with the MAC address & name in the form of list
* 2. Change Bluetooth Setting of Mac address and Bluetooth adapter Name according to the selected device
* 3. Record MAC address, name, broadcast data of the selected device
*/
public class PhoneFragment extends Fragment {
private FragmentPhoneBinding binding;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothLeScanner mBLEScanner;
private boolean mScanning = false;
private String fake_name;
private ListViewAdapter adapter;
private List<ListViewAdapter.BlueTooth_item__Bean> BlueToothDevice_Info; //蓝牙设备的信息
private EditText addr;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentPhoneBinding.inflate(inflater, container, false);
View root = binding.getRoot();
Button start_scan = binding.startScan;
Button stop_scan = binding.stopScan;
Button mac_change = binding.changeMac;
ListView listview = binding.lvScan;
addr = binding.macToBe;
start_scan.setOnClickListener((View v) -> startBLEScan());
stop_scan.setOnClickListener((View v) -> stopBLEScan());
mac_change.setOnClickListener((View v) -> changeMAC());
//If bluetooth has not been enable, start the bluetooth service
FragmentActivity act = getActivity();
if(act != null) {
BluetoothManager mBluetoothManager = (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
}
if(!mBluetoothAdapter.isEnabled()){
mBluetoothAdapter.enable();
}
initData();
adapter = new ListViewAdapter(getActivity(), BlueToothDevice_Info);
listview.setAdapter(adapter);
//Click the list view item to choose the specific device.
//when select one device, record the mac addr, name and broadcast data in /data/data/com.example.tesmla/files/fake_dev.
listview.setOnItemClickListener((AdapterView<?> parent, View view, int position, long id)->{
ListViewAdapter.BlueTooth_item__Bean tesla = BlueToothDevice_Info.get(position);
BufferedWriter writer = null;
try{
if(act != null) {
FileOutputStream out = act.openFileOutput("fake_dev", Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(tesla.blueToothDevie_Name);
writer.append("\n");
writer.append(tesla.blueToothDevie_Address);
writer.append("\nspecific_data:");
writer.append(tesla.record_data);
writer.append("\n");
ToastUtils.showToast(getActivity(), "已保存", 2000);
addr.setText(tesla.blueToothDevie_Address);//show the mac address of selected device on the top of the fragment
fake_name = tesla.blueToothDevie_Name;
}
}
catch(IOException e){
e.printStackTrace();
}finally {
try{
if(writer != null){writer.close();}
}catch (IOException e){
e.printStackTrace();
}
}
});
return root;
}
/**
* Show the basic listview window
*/
private void initData() {
//Simulate the BLE device info
BlueToothDevice_Info = new ArrayList<>();
for(int i = 0 ;i<10;i++){
ListViewAdapter.BlueTooth_item__Bean bluetooth_device_item_info = new ListViewAdapter.BlueTooth_item__Bean();
bluetooth_device_item_info.blueToothDevie_Name = "蓝牙设备名字"+i;
bluetooth_device_item_info.blueToothDevie_Address = "蓝牙设备mac地址"+i;
BlueToothDevice_Info.add(bluetooth_device_item_info);
}
}
private void openAndInitBt() {
if (mBluetoothAdapter == null) {
ToastUtils.showToast(getActivity(), "不支持蓝牙", 2000);
return;
}//do not support the bluetooth
FragmentActivity act = getActivity();
if (act != null) {
if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
ToastUtils.showToast(getActivity(), "不支持ble蓝牙", 2000);//do not support the BLE
}
}
}
/**
* Determine whether the input string satisfies MAC address format requirement
* @param val String :The string to be determined
* @return boolean: true if satisfied
*/
private boolean stringIsMac(String val) {
String trueMacAddress = "([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}";
return val.matches(trueMacAddress);
}
/**
* Change the Bluetooth MAC address and name of device
* Pre-requirement: rebuild the Android system and open the interface to change property of ro.vendor.bt.boot.macaddr
*/
public void changeMAC(){
if(mScanning) {
mScanning = false;
stopBLEScan();
}
mBluetoothAdapter.setName(fake_name);
if (mBluetoothAdapter.isEnabled()) {
mBluetoothAdapter.disable();
}
//get the new mac address and set to the property
String mac_new = addr.getText().toString();
if(stringIsMac(mac_new)){
Process process = null;
DataOutputStream os = null;
try {
process = Runtime.getRuntime().exec("su"); //switch to permission of root
os = new DataOutputStream(process.getOutputStream());
String cmd = "setprop ro.vendor.bt.boot.macaddr " + addr.getText().toString();
os.writeBytes(cmd + "\n");
os.writeBytes("exit\n");
os.flush();
Log.e("[MAC]", addr.getText().toString());
process.waitFor();
} catch (Exception e) {
Log.e("[err]", "mac addr set failed.");
} finally {
try {
if(os!= null) os.close();
if(process != null) process.destroy();
} catch (Exception e) {
Log.w("[ERR]", "error occur " + e);
}
}
ToastUtils.showToast(getActivity(), "修改成功", 1000);//不支持ble蓝牙
} else{
ToastUtils.showToast(getActivity(), "MAC地址不合法", 1000);//不支持ble蓝牙
}
}
private void startBLEScan(){
openAndInitBt();
BlueToothDevice_Info.clear();
if(!mBluetoothAdapter.isEnabled()){
mBluetoothAdapter.enable();
}
FragmentActivity act = getActivity();
if(act != null) {
int checkCallPhonePermission = ContextCompat.checkSelfPermission(act, Manifest.permission.ACCESS_COARSE_LOCATION);
//Determine whether need to indicate the user allowing the permission
if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(act, Manifest.permission.ACCESS_FINE_LOCATION))
ToastUtils.showToast(getActivity(), "开位置权限", 2000);
ActivityCompat.requestPermissions(act, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
return;
}
}
if (!mBluetoothAdapter.isEnabled()){
ToastUtils.showToast(getActivity(), "蓝牙未开启", 2000);
}else {
if (mScanning) {
ToastUtils.showToast(getActivity(), "正在扫描", 2000);
} else {
// Set the current state of scanning
mScanning = true;
if (mBLEScanner == null) {
mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
}
mBLEScanner.startScan(null, mScanSetting(), mScanCallback);
//Set stopping the process of scan automatically
new Handler().postDelayed(()-> {
if(mScanning){
mScanning = false;
mBLEScanner.stopScan(mScanCallback);
Log.w("[end]", "stop searching");
}},10000);
}
}
}
public ScanSettings mScanSetting(){
//Create object of ScanSettings' build to set params
ScanSettings.Builder builder = new ScanSettings.Builder()
//Use low power mode as default.
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
//android 6.0 add the type of callback and match mode settings
//Search for the BLE broadcast package which satisfies the filter
builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
//Set the matching mode of bluetooth LE scan filter hardware matching
//MATCH_MODE_STICKY Mode: For sticky mode, higher threshold of signal strength and sightings is required before reporting by hw
builder.setMatchMode(ScanSettings.MATCH_MODE_STICKY);
//Determine whether the bluetooth chip support the scanning in batches
if (mBluetoothAdapter.isOffloadedScanBatchingSupported()) {
//Set the delay of reporting the result.(unit: ms)
//Set to 0 means report the result immediately
builder.setReportDelay(0L);
}
return builder.build();
}
ScanCallback mScanCallback = new ScanCallback() {
//When scanning get the broadcast package
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
//Operate the info of scanned devices (mac,name,broadcast data)
BluetoothDevice device = result.getDevice();
final ListViewAdapter.BlueTooth_item__Bean bean = new ListViewAdapter.BlueTooth_item__Bean();
if(device.getName() != null) {
bean.blueToothDevie_Name = device.getName();
}
else{
bean.blueToothDevie_Name = "NULL";
}
bean.blueToothDevie_Address = device.getAddress();
byte[] manufacture_data = result.getScanRecord().getManufacturerSpecificData(0x4c);
if(manufacture_data != null) {
bean.record_data = Conversion.Bytes2HexString(manufacture_data);
}else{
bean.record_data = "null";
}
if(!BlueToothDevice_Info.contains(bean)){
BlueToothDevice_Info.add(bean);
adapter.notifyDataSetChanged();
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
//Return scan results in batches
//@param results the former list of scan results.
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
//Called when start of BLE scan failed.
//Scanning too often will get ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED. The APP cant be registered.
}
};
public void stopBLEScan(){
Log.w("[stop]","stop BLE scan");
if(mScanning && mBLEScanner != null) {
mBLEScanner.stopScan(mScanCallback);
mScanning = false;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
stopBLEScan();
binding = null;
}
}

View File

@@ -0,0 +1,817 @@
package com.example.tesmla.ui.vehicle;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelUuid;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.example.tesmla.R;
import com.example.tesmla.databinding.FragmentVehicleBinding;
import com.example.tesmla.utils.ToastUtils;
import com.example.tesmla.utils.Conversion;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.UUID;
/**
* This Class show as left fragment
* Function: 1. Send broadcast package
* 2. Work as a BLE Server offering the same services as Tesla vehicle
* 3. Communicate with device which connect to this server
*/
public class VehicleFragment extends Fragment {
private EditText edit_data;
private EditText tx_to_app;
private TextView data_type;
private TextView log_data;
private EditText dev_name;
private boolean connect_state = false;
private boolean indicate_state = false;
private boolean specific_data = false;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothManager mBluetoothManager;
private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
private BluetoothGattServer mBluetoothGattServer;
BluetoothDevice mBluetoothDevice;
//Operate UI in bluetooth thread.
private final MyHandler mhandler = new MyHandler(Looper.myLooper());
//Declaration of the content of GATT server.
/* Service UUID */
public static UUID Service_car = UUID.fromString("00000211-B2D1-43F0-9B88-960CEBF8B91E");
/* Information Characteristic */
/* Information Characteristic */
public static UUID Charac1 = UUID.fromString("00000212-B2D1-43F0-9B88-960CEBF8B91E");
public static UUID Charac2 = UUID.fromString("00000213-B2D1-43F0-9B88-960CEBF8B91E");
public static UUID Charac3 = UUID.fromString("00000214-B2D1-43F0-9B88-960CEBF8B91E");
/* Characteristic Descriptor*/
public static UUID USERR_DESCRP = UUID.fromString("00002901-0000-1000-8000-00805f9b34fb");
public static UUID CLIENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
BluetoothGattCharacteristic To_Vehicle;
BluetoothGattCharacteristic From_Vehicle;
BluetoothGattCharacteristic Vehicle_Info;
BluetoothGattDescriptor configDescriptor2;
BluetoothGattDescriptor UserDescriptor1;
BluetoothGattDescriptor UserDescriptor2;
BluetoothGattDescriptor UserDescriptor3;
private FragmentVehicleBinding binding;
private Thread thread;
private String pk;
private String token;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
if(Looper.myLooper() == null){
Looper.prepare();
}
binding = FragmentVehicleBinding.inflate(inflater, container, false);
View root = binding.getRoot();
edit_data = binding.editData;
tx_to_app = binding.txToPhone;
data_type = binding.rxDataType;
log_data = binding.dataLog;
dev_name = binding.editName;
log_data.setMovementMethod(ScrollingMovementMethod.getInstance());
log_data.setGravity(Gravity.BOTTOM);
Button start_Adv = binding.startAdv;
Button stop_adv = binding.stopAdv;
Button send = binding.sendData;
start_Adv.setOnClickListener((View v) -> {
try {
startAction();
} catch (IOException e) {
e.printStackTrace();
}
});
stop_adv.setOnClickListener((View v) -> stopAction());
send.setOnClickListener((View v) -> {
try {
send_resp(1,"");
} catch (IOException e) {
e.printStackTrace();
}
});
FragmentActivity act = getActivity();
if(act != null) {
mBluetoothManager = (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
if(!mBluetoothAdapter.isEnabled()){
mBluetoothAdapter.enable();
ToastUtils.showToast(getActivity(), "打开蓝牙", 2000);
}
}
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
if(mBluetoothGattServer!=null) {
stopAction();
}
}
public void startAction() throws IOException {
openAndInitBt();
createGattServer();
startAdvertising();
}
public void stopAction() {
if(mBluetoothGattServer!=null) {
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
mBluetoothGattServer.close();
ToastUtils.showToast(getActivity(), "已停止广播", 2000);
}
}
/**
* Send the response data to connected device by GATT Indicate and record data in /data/data/com.example.tesmla/files/log
* @param type int: 0: Authentication-level requirement 1: other response data
* @param data String: The data need to be send
* @throws IOException record file does not exist or permission denied.
*/
public void send_resp(int type, String data) throws IOException {
if(connect_state) {
Message msg = new Message();
msg.what = 5;
if(type != 0) {//Determine whether User has input the specific data
String temp = tx_to_app.getText().toString();
msg.obj = "";
if (temp.length() > 0) {//If User has input
data = temp;
}
}
if(!data.equals("")) {
//Send indication to connected device
From_Vehicle.setValue(Conversion.hexStringToBytes(data));
mBluetoothGattServer.notifyCharacteristicChanged(mBluetoothDevice, From_Vehicle, true);
//Show the data on the screen
msg.obj = "<<" + data + "\n";
if (type == 1) {
//record data in files
writeLog("<<" + data + "\n");
Log.d("[send]", data + "\n");
indicate_state = false;
}
}
mhandler.sendMessage(msg);
}
}
/**
* Log the communicated data in /data/data/com.example.tesmla/files/log
* @param data String: The data to be logged
* @throws IOException : file does not exist or permission denied.
*/
private void writeLog(String data) throws IOException {
BufferedWriter writer;
FragmentActivity act = getActivity();
if(act != null) {
FileOutputStream out = act.openFileOutput("log", Context.MODE_APPEND);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(data);
writer.flush();
}
}
/**
* Record signed msgs in fake_app /data/data/com.example.tesmla/files/file
* @param data String: The data to be record
* @throws IOException : file does not exist or permission denied.
*/
private void record(String data) throws IOException {
BufferedWriter writer;
FragmentActivity act = getActivity();
if(act != null) {
FileOutputStream out = act.openFileOutput("fake_app", Context.MODE_APPEND);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(data);
writer.flush();
}
}
/**
* 1.Start and initial bluetooth service
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void openAndInitBt(){
if (mBluetoothAdapter==null){
ToastUtils.showToast(getActivity(), "不支持蓝牙", 2000);
return;}
FragmentActivity act = getActivity();
if(act != null) {
if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
ToastUtils.showToast(getActivity(), "不支持ble蓝牙", 2000);
}
}
}
/**
* 2.Create GATT server working as Tesla vehicle
*/
private void createGattServer() {
//2.1.Create new service
BluetoothGattService service_vehicle = new BluetoothGattService(Service_car,BluetoothGattService.SERVICE_TYPE_PRIMARY);
//2.2 Create new Characteristic
To_Vehicle = new BluetoothGattCharacteristic(Charac1,
//Write-only characteristic, not supports notifications
BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_WRITE);
From_Vehicle = new BluetoothGattCharacteristic(Charac2,
//only supports indications
BluetoothGattCharacteristic.PROPERTY_INDICATE,
BluetoothGattCharacteristic.PERMISSION_READ);
Vehicle_Info = new BluetoothGattCharacteristic(Charac3,
//Read-only characteristic, supports notifications
BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_READ);
Vehicle_Info.setValue("version for test 1.0");
//2.3 Create Descriptor for characteristic
configDescriptor2 = new BluetoothGattDescriptor(CLIENT_CONFIG,
//Read/write descriptor
BluetoothGattDescriptor.PERMISSION_WRITE | BluetoothGattDescriptor.PERMISSION_READ);
UserDescriptor1 = new BluetoothGattDescriptor(USERR_DESCRP,
//Read/write descriptor
BluetoothGattDescriptor.PERMISSION_WRITE );
UserDescriptor2 = new BluetoothGattDescriptor(USERR_DESCRP,
//Read/write descriptor
BluetoothGattDescriptor.PERMISSION_WRITE);
//configDescriptor2.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
UserDescriptor3 = new BluetoothGattDescriptor(USERR_DESCRP,
//Read/write descriptor
BluetoothGattDescriptor.PERMISSION_WRITE);
To_Vehicle.setValue("To Vehicle");
To_Vehicle.addDescriptor(UserDescriptor1);
From_Vehicle.addDescriptor(UserDescriptor2);
From_Vehicle.addDescriptor(configDescriptor2);
Vehicle_Info.addDescriptor(UserDescriptor3);
//2.4 Configure the characteristics to service
service_vehicle.addCharacteristic(To_Vehicle);
service_vehicle.addCharacteristic(From_Vehicle);
service_vehicle.addCharacteristic(Vehicle_Info);
//2.5 Enable Gatt server and add callback function
mBluetoothGattServer = mBluetoothManager.openGattServer(getActivity(), mGattServerCallback);
if (mBluetoothGattServer == null) {
return;
}
mBluetoothGattServer.addService(service_vehicle);
}
/**
* 3. Start Advertisement
* @throws IOException file not found or permission denied
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void startAdvertising() throws IOException {
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
Message msg1 = new Message();
msg1.what = 3;
if (mBluetoothLeAdvertiser == null) {
ToastUtils.showToast(getActivity(), "不支持LE广播", 2000);
return;
}
//Set advertisement params: low latency, high power, timeout, etc.
AdvertiseSettings settings = new AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
.setConnectable(true)//must set true
.setTimeout(0)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
.build();
String name = dev_name.getText().toString();
if (!name.equals("NULL") && name.length() > 1) {//If user has set own name. use the input name
mBluetoothAdapter.setName(name);
msg1.obj = String.format(getResources().getString(R.string.edit_Data), name);
Log.w("[dev name]", "manual set: " + name);
}else {
try {//If not set, read the record file in scanning process
File file = new File(String.format(getResources().getString(R.string.dir), "fake_dev"));
if (file.exists()) {
FileInputStream fin = new FileInputStream(file);
byte[] buffer = new byte[40];
if (fin.read(buffer) > 0) {
String s = new String(buffer);
name = s.substring(0, s.indexOf("\n"));
Log.w("[read]", name);
if (!name.equals("NULL")) {
mBluetoothAdapter.setName(name);//Set bluetooth adapter name
msg1.obj = name;
Log.w("[dev name]", "file read: " + name);
} else {//If record file does not record the name, use default name.
mBluetoothAdapter.setName("S000001234512345bC");
msg1.obj = String.format(getResources().getString(R.string.default_data), mBluetoothAdapter.getName());
Log.w("[dev name]", "default" + mBluetoothAdapter.getName());
}
}
}else {//If record file does not exist, use default data and name.
mBluetoothAdapter.setName("S000001234512345bC");
msg1.obj = String.format(getResources().getString(R.string.default_data), mBluetoothAdapter.getName());
Log.w("[dev name]", "default " + mBluetoothAdapter.getName());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
mhandler.sendMessage(msg1);
Message msg2 = new Message();
msg2.what = 4;
String str = edit_data.getText().toString();
if (str.length() < 1) {//If user has not input manufacture specific data, use the record data in scanning process.
File file = new File(String.format(getResources().getString(R.string.dir), "fake_dev"));
if (file.exists()) {
FileInputStream fin = new FileInputStream(file);
byte[] buffer = new byte[4096];
if (fin.read(buffer) > 0) {
String s = new String(buffer);
if(s.indexOf("specific_data:")>10){
str = s.substring(s.indexOf("specific_data:")+14, s.lastIndexOf("\n"));
if (!str.equals("NULL")) {
msg2.obj = str;
specific_data = true;
}
}
}
}
if(!specific_data) {
msg2.obj = String.format(getResources().getString(R.string.default_data), "5d8f");
str = "021574278BDAB64445208F0C720EAF05993500005D1234";
}
byte []broadcast = Conversion.hexStringToBytes(str);
//Add broadcast data
AdvertiseData adv_data = new AdvertiseData.Builder()
.addManufacturerData(0x004C,broadcast)
.build();
AdvertiseData adv_resp_data = new AdvertiseData.Builder()
.addServiceUuid( ParcelUuid.fromString("00001122-0000-1000-8000-00805f9b34fb"))
.setIncludeDeviceName(true)
.build();
mBluetoothLeAdvertiser.startAdvertising(settings, adv_data, adv_resp_data, mAdvertiseCallback);
}
else{//Use user input manufacture data
final byte[] broadcastData = Conversion.hexStringToBytes(str);
msg2.obj = str;
AdvertiseData define_data = new AdvertiseData.Builder()
.addManufacturerData(0x004C,broadcastData)
.build();
AdvertiseData adv_resp_data = new AdvertiseData.Builder()
.addServiceUuid( ParcelUuid.fromString("00001122-0000-1000-8000-00805f9b34fb"))
.setIncludeDeviceName(true)
.build();
mBluetoothLeAdvertiser.startAdvertising(settings, define_data, adv_resp_data, mAdvertiseCallback);
}
mhandler.sendMessage(msg2);
}
/**
* advertisement callback function. Show toast of advertisement state
*/
private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
Log.i("", "LE Advertise Started.");
ToastUtils.showToast(getActivity(), "开启广播成功", 2000);
}
@Override
public void onStartFailure(int errorCode) {
Log.w("", "LE Advertise Failed: "+errorCode);
ToastUtils.showToast(getActivity(), "开启广播失败 errorCode" + errorCode, 2000);
}
};
/**
* Callback to handle incoming requests to the GATT server.
* All write and read of characteristics or descriptors will be operated here.
*/
private final BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
/**
* Callback indicating when a remote device has been connected or disconnected..
* @param device BluetoothDevice: Remote device that has been connected or disconnected.
* @param status int: Status of the connect or disconnect operation.
* @param newState int: Returns the new connection state. Can be one of BluetoothProfile.STATE_DISCONNECTED or BluetoothProfile#STATE_CONNECTED
*/
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i("[connection state changed]", "BluetoothDevice CONNECTED: " + device);
mBluetoothDevice = device;
connect_state = true;
//If new connection created, delete the former record file.
File file = new File(String.format(getResources().getString(R.string.dir),"fake_app"));
if (file.exists()) {
if(!file.delete()){
Log.e("[err]","fake_app file not exist.");
}
}
try {
writeLog("############# fake as tesla ##############\n");
//read the public key and token from the file which be recorded during the process of fake as app
file = new File(String.format(getResources().getString(R.string.dir),"fake_tesla"));
if (file.exists()) {
FileInputStream fin = new FileInputStream(file);
byte[] buffer = new byte[4096];
if (fin.read(buffer) > 50) {
String s = new String(buffer);
if(s.lastIndexOf("pk:") >= 0) {
//Log.e("[!!!!!!!!!]",""+s.lastIndexOf("pk:"));
pk = s.substring(s.lastIndexOf("pk:") + 3, s.lastIndexOf("pk:") + 145);
}
/*else{//if public key has not found in file, use default public key
pk = "004512431a4104a64f09b8a4041e98ad7dca0c198f5def6e4753b74d74195d4aaaf743c58560b0aeeabcb643249617567a6c76ceba1067ebe57df9e6123b4af769382651ee3a5a";
pk = pk.toUpperCase();
}*/
if(s.indexOf("token:")>=0){
token = s.substring(s.indexOf("token:") + 6, s.indexOf("token:") + 66);
}
/*else{
token = "001C1A1A12160A148A39B3ADCCED4DFA817B91EDEA8DD681043312341802";
token = token.toUpperCase();
}*/
}
}
/* else{
pk = "004512431a4104a64f09b8a4041e98ad7dca0c198f5def6e4753b74d74195d4aaaf743c58560b0aeeabcb643249617567a6c76ceba1067ebe57df9e6123b4af769382651ee3a5a";
pk = pk.toUpperCase();
token = "001C1A1A12160A148A39B3ADCCED4DFA817B91EDEA8DD681043312341802";
token = token.toUpperCase();
}*/
}catch (IOException e) {
e.printStackTrace();
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i("", "BluetoothDevice DISCONNECTED: " + device);
connect_state = false;
indicate_state = false;
thread = null;
}
//Define the new thread to send authentication-level data every second
thread = new Thread(()->{
while (connect_state) {
try {
Thread.sleep(1000);
if(!indicate_state) send_resp(0, "00041a021802");
} catch (InterruptedException |IOException e) {
e.printStackTrace();
}
}
});
}
/**
* Callback indicating the MTU for a given device connection has changed.
* @param device BluetoothDevice: The remote device that requested the MTU change
* @param mtu int: The new MTU size
*/
@Override
public void onMtuChanged(BluetoothDevice device, int mtu) {
super.onMtuChanged(device, mtu);
Log.w("[MTU]", "change MTU to "+mtu);
Message msg = new Message();
msg.what = 5;
msg.obj = "mtu size changed\n";
mhandler.sendMessage(msg);
}
/**
* Connected device read server's characteristic
* @param device BluetoothDevice: The remote device that has requested the read operation
* @param requestId int: The Id of the request
* @param offset int: Offset into the value of the characteristic
* @param characteristic BluetoothGattCharacteristic: Characteristic to be read
*/
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset,
BluetoothGattCharacteristic characteristic) {
//Only has one characteristic can be read
if(Charac3.equals(characteristic.getUuid())){
Log.w("[READ]","UUID:"+characteristic.getUuid());
mBluetoothGattServer.sendResponse(device,requestId, BluetoothGatt.GATT_SUCCESS,0, Vehicle_Info.getValue());
}
}
/**
* A remote client has requested to read a local descriptor.
* @param device BluetoothDevice: The remote device that has requested the read operation
* @param requestId int: The Id of the request
* @param offset int: Offset into the value of the characteristic
* @param descriptor BluetoothGattDescriptor: Descriptor to be read
*/
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset,
BluetoothGattDescriptor descriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
Log.w("[Read]","descriptor"+descriptor.getUuid()+descriptor.getPermissions());
byte[] a = {0x12,0x34};
mBluetoothGattServer.sendResponse(device,
requestId,
BluetoothGatt.GATT_FAILURE,
0,a//Conversion.intToByteArray(descriptor.getPermissions())
);
}
/**
* A remote client has requested to write to a local descriptor.
* @param device BluetoothDevice: The remote device that has requested the write operation
* @param requestId int: The Id of the request
* @param descriptor BluetoothGattDescriptor: Descriptor to be written to.
* @param preparedWrite boolean: true, if this write operation should be queued for later execution.
* @param responseNeeded boolean: true, if the remote device requires a response
* @param offset int: The offset given for the value
* @param value byte: The value the client wants to assign to the descriptor
*/
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,BluetoothGattDescriptor descriptor,boolean preparedWrite, boolean responseNeeded,
int offset, byte[] value) {
super.onDescriptorWriteRequest(device, requestId, descriptor,preparedWrite,responseNeeded,offset,value);
Message msg = new Message();
msg.what = 0;
msg.obj = descriptor.getUuid() + ":\n";
if(!thread.isAlive()){
thread.start();
}
if (CLIENT_CONFIG.equals(descriptor.getUuid())) {
if (Arrays.equals(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE, value)) {
msg.obj += "ENABLE_NOTIFICATION_VALUE";
} else if (Arrays.equals(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE, value)) {
msg.obj += "DISABLE_NOTIFICATION_VALUE";
}else if(Arrays.equals(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE, value)) {
msg.obj += "ENABLE_INDICATION_VALUE";
}else if(Arrays.equals(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE, value)) {
msg.obj += "DISABLE_NOTIFICATION_VALUE";
}
if (responseNeeded) {
mBluetoothGattServer.sendResponse(device,
requestId,
BluetoothGatt.GATT_SUCCESS,
0,
null);
}
} else {
if (responseNeeded) {
mBluetoothGattServer.sendResponse(device,
requestId,
BluetoothGatt.GATT_FAILURE,
0,
null);
}
}
if(Looper.myLooper() == null){
Looper.prepare();
}
mhandler.sendMessage(msg);
if (responseNeeded) {
mBluetoothGattServer.sendResponse(device,
requestId,
BluetoothGatt.GATT_FAILURE,
0,
null);
}
}
/**
* A remote client has requested to write to a local characteristic.
* @param device BluetoothDevice: The remote device that has requested the write operation
* @param requestId int: The Id of the request
* @param characteristic BluetoothGattCharacteristic: Characteristic to be written to.
* @param preparedWrite boolean: true, if this write operation should be queued for later execution.
* @param responseNeeded boolean: true, if the remote device requires a response
* @param offset int: The offset given for the value
* @param value byte: The value the client wants to assign to the characteristic
*/
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
indicate_state = true;
//Send Data to UI Interface
String recv = Conversion.Bytes2HexString(value);
Message msg = new Message();
msg.what = 1;
msg.obj = recv;
Log.d("[recv]", recv);
//if authentication process end, stop the authentication-level requirement thread
//if(recv.equals("00051203C00101")){thread = null;}
//record data in log files
try {
writeLog(">>" + recv + "\n");
} catch (IOException e) {
e.printStackTrace();
}
if(Looper.myLooper() == null){ Looper.prepare(); }
mhandler.sendMessage(msg);
mBluetoothDevice = device;
if (Charac1.equals(characteristic.getUuid())) {
byte[] info_req_get_pk = {0x0a, 0x0a, 0x08, 0x03};//Command: send public key of vehicle
byte[] info_req_get_capabilities = {0x0a, 0x0a, 0x08, 0x10};//Command: require get vehicle capabilities
String tx_data;
if (value.length> 10 && value[2] == 0x12) {//Normal Command
byte[] pattern = new byte[4];
System.arraycopy(value, 4, pattern, 0, 4);
if (Arrays.equals(pattern, info_req_get_pk)) { //Command: send public key of vehicle
tx_data = pk;
Log.w("[send pk]", tx_data);
} else if (Arrays.equals(pattern, info_req_get_capabilities)) {//Command: get vehicle capabilities
byte[] IND_pattern2 = {0x00, 0x07, -102, 0x01, 0x04, 0x08, 0x01, 0x10, 0x01};
tx_data = Conversion.Bytes2HexString(IND_pattern2);
Log.w("[send]", tx_data);
} else {//Other command
byte[] IND_pattern3 = {0x00, 0x02, 0x0a, 0x00}; //vehicle state = 0
tx_data = Conversion.Bytes2HexString(IND_pattern3);
Log.w("[send]", tx_data);
}
try {
send_resp(1, tx_data);
if(value[1] == 0x0C){//Command: get vehicle lock state
record("cmd:" + recv +"\n");
}
} catch (IOException e) {
e.printStackTrace();
}
} else if (value[2] == 0x0a) {//Signed Message
//Get Counter Byte Length
int len = recv.indexOf("2A04", recv.indexOf("30"))/2 - 5;
//Set Indication data, response the counter
byte[] cnt = new byte[len];
System.arraycopy(value, 5, cnt, 0, len);
byte[] IND_pattern = {0x00, 0x06, 0x22, 0x04, 0x12, 0x02, 0x08};
if (len == 2) {
IND_pattern[1] += 1;
IND_pattern[3] += 1;
IND_pattern[5] += 1;
}
byte[] cnt_resp = new byte[len + IND_pattern.length];
System.arraycopy(IND_pattern, 0, cnt_resp, 0, IND_pattern.length);
System.arraycopy(cnt, 0, cnt_resp, IND_pattern.length, len);
tx_data = Conversion.Bytes2HexString(cnt_resp);
try {
send_resp(1, tx_data);
if(value[1] < 0x24) { //if receive msg len less than 0x24, it will be the first signed msg
record("sign1:" + recv +"\n");
if(token != null) send_resp(1, token);
}else{
record("sign2:" + recv + "\n");
}
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
Log.w("", "无效写特性操作");
}
if (responseNeeded) {
mBluetoothGattServer.sendResponse(device,
requestId,
BluetoothGatt.GATT_SUCCESS,
0,
null);
}
}
/**
* Callback invoked when a notification or indication has been sent to a remote device.
* When multiple notifications are to be sent, an application must wait for this callback to be received before sending additional notifications.
* @param device BluetoothDevice: The remote device the notification has been sent to
* @param status int: BluetoothGatt#GATT_SUCCESS if the operation was successful
*/
@Override
public void onNotificationSent(BluetoothDevice device, int status){
super.onNotificationSent(device, status);
}
};//Bluetooth GATT callback function end
/**
* Bluetooth operation often work as asynchronous thread. This handler operate the UI in non-main thread
*/
class MyHandler extends Handler {
public MyHandler(@NonNull Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
data_type.setText(getResources().getString(R.string.write_dsp));
break;
case 1:
data_type.setText(getResources().getString(R.string.write_chara));
log_data.append(">>" + msg.obj +"\n");
break;
case 3:
dev_name.setText((String)msg.obj);
break;
case 4:
edit_data.setText((String)msg.obj);
break;
case 5:
log_data.append(msg.obj + "");
break;
default:
data_type.setText(getResources().getString(R.string.unknown_op));
}
}
}
}

View File

@@ -0,0 +1,164 @@
package com.example.tesmla.utils;
/**
* This class encapsulates utility functions which for data format conversion
* */
public class Conversion {
public static byte loUint16(short v) {
return (byte) (v & 0xFF);
}
public static byte hiUint16(short v) {
return (byte) (v >> 8);
}
public static short buildUint16(byte hi, byte lo) {
return (short) ((hi << 8) + (lo & 0xff));
}
public static boolean isAsciiPrintable(String str) {
if (str == null) {
return false;
}
int sz = str.length();
for (int i = 0; i < sz; i++) {
if (!isAsciiPrintable(str.charAt(i))) {
return false;
}
}
return true;
}
private static boolean isAsciiPrintable(char ch) {
return ch >= 32 && ch < 127;
}
public static byte[] hexStringToBytes(String hexString) {
if (hexString == null || hexString.equals("")) {
return null;
}
hexString = hexString.toUpperCase();
if(hexString.length()%2!=0){
hexString="0"+hexString;
}
int length = hexString.length() / 2;
char[] hexChars = hexString.toCharArray();
byte[] d = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
}
return d;
}
public static byte charToByte(char c) {
return (byte) "0123456789ABCDEF".indexOf(c);
}
private final static byte[] hex = "0123456789ABCDEF".getBytes();
// switch byte[] to hex string
public static String Bytes2HexString(byte[] b) {
byte[] buff = new byte[2 * b.length];
for (int i = 0; i < b.length; i++) {
buff[2 * i] = hex[(b[i] >> 4) & 0x0f];
buff[2 * i + 1] = hex[b[i] & 0x0f];
}
return new String(buff);
}
public static int byteStrToInt(String valueStr) {
valueStr = valueStr.toUpperCase();
if (valueStr.length() % 2 != 0) {
valueStr = "0" + valueStr;
}
int returnValue = 0;
int length = valueStr.length();
for (int i = 0; i < length; i++) {
int value = charToByte(valueStr.charAt(i));
returnValue += Math.pow(16, length - i - 1) * value;
}
return returnValue;
}
public static byte[] intToByteArray(int i) {
byte[] result = new byte[4];
result[0] = (byte)((i >> 24) & 0xFF);
result[1] = (byte)((i >> 16) & 0xFF);
result[2] = (byte)((i >> 8) & 0xFF);
result[3] = (byte)(i & 0xFF);
return result;
}
public static int bytesToInt(byte[] values) {
String valueStr = Bytes2HexString(values);
if (valueStr.length() % 2 != 0) {
valueStr = "0" + valueStr;
}
int returnValue = 0;
int length = valueStr.length();
for (int i = 0; i < length; i++) {
int value = charToByte(valueStr.charAt(i));
returnValue += Math.pow(16, length - i - 1) * value;
}
return returnValue;
}
/**
* switch Byte to Bit
*/
public static String byteToBit(byte b) {
return "" +(byte)((b >> 7) & 0x1) +
(byte)((b >> 6) & 0x1) +
(byte)((b >> 5) & 0x1) +
(byte)((b >> 4) & 0x1) +
(byte)((b >> 3) & 0x1) +
(byte)((b >> 2) & 0x1) +
(byte)((b >> 1) & 0x1) +
(byte)(b & 0x1);
}
/**
* switch Bit to Byte
*/
public static byte BitToByte(String byteStr) {
int re, len;
if (null == byteStr) {
return 0;
}
len = byteStr.length();
if (len != 4 && len != 8) {
return 0;
}
if (len == 8) {// operate with 8 bit
if (byteStr.charAt(0) == '0') {// positive
re = Integer.parseInt(byteStr, 2);
} else {// negative
re = Integer.parseInt(byteStr, 2) - 256;
}
} else {//operate with 4 bit
re = Integer.parseInt(byteStr, 2);
}
return (byte) re;
}
public static byte[] subByteArray(byte[] src,int start,int length){
byte[] result=new byte[length];
System.arraycopy(src, start, result, 0, length);
return result;
}
}

View File

@@ -0,0 +1,32 @@
package com.example.tesmla.utils;
import android.content.Context;
import android.widget.Toast;
import java.util.Timer;
import java.util.TimerTask;
/**
* This class indicate the information by toast
*/
public class ToastUtils {
public static void showToast(Context context, String content, int duration) {
if(duration<=0) return;
final Toast toast = Toast.makeText(context, content, Toast.LENGTH_LONG);
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
toast.show();
}
}, 0, 3000);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
toast.cancel();
timer.cancel();
}
}, duration);
}
}

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />
<fragment
android:id="@+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.vehicle.VehicleFragment"
android:background="@color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:id = "@+id/dev_addr"
android:layout_marginStart="8dp"
android:layout_weight="0.2"
android:text = "@string/mac"
android:layout_height="48dp"
android:textColor="@color/purple_200"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:id = "@+id/addr"
android:layout_weight="0.5"
android:layout_height="48dp"
android:textColor="@color/dark_blue"
android:textSize="16sp"/>
<Button
android:id="@+id/connect"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginEnd="10dp"
android:inputType="none"
android:text="@string/connect"
android:textColor="@color/white_half"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp">
<Button
android:id="@+id/change_mtu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/white"
android:layout_weight="1"
android:text="@string/change_mtu_size"
android:textColor="@color/dark_blue"
style="?android:attr/buttonBarButtonStyle" />
<Button
android:id="@+id/disconnect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/bg_week"
android:text="@string/disconnect"
android:textColor="@color/dark_blue"
style="?android:attr/buttonBarButtonStyle" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="15dp"
android:gravity="center"
android:text="@string/tx_to_car"
android:textColor="@color/purple_200"
android:textSize="16sp" />
<EditText
android:id="@+id/tx_to_car"
android:layout_width="match_parent"
android:layout_height="70dp"
android:autofillHints="data"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="@color/light_gray"
android:textColor="@color/black"
android:textSize="12sp"
tools:ignore="LabelFor,SpeakableTextPresentCheck"
android:inputType="text" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp">
<Button
android:id="@+id/get_pk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/bg_week"
android:text="@string/get_pk"
android:textColor="@color/dark_blue"
style="?android:attr/buttonBarButtonStyle" />
<Button
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/white"
android:layout_weight="1"
android:text="@string/send_to_car"
android:textColor="@color/dark_blue"
style="?android:attr/buttonBarButtonStyle" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="15dp"
android:layout_marginStart="8dp"
android:gravity="center"
android:text="@string/log"
android:textColor="@color/purple_200"
android:textSize="16sp" />
<TextView
android:id="@+id/log"
android:layout_width="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_height="280dp"
android:autofillHints="data"
android:background="@color/light_gray"
android:textColor="@color/black"
android:textSize="12sp" />
</LinearLayout>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.phone.PhoneFragment"
android:background="@color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_weight="0.1"
android:layout_marginStart="8dp"
android:text = "@string/mac_addr"
android:layout_height="48dp"
android:textColor="@color/purple_200"
android:textSize="16sp"/>
<EditText
android:layout_width="wrap_content"
android:id = "@+id/mac_to_be"
android:layout_weight="0.6"
android:inputType="none"
android:textColorHint="@color/text_light_gray"
android:layout_height="48dp"
android:textColor="@color/dark_blue"
android:textSize="16sp"
android:autofillHints="@string/hint_mac"
tools:ignore="LabelFor" />
<Button
android:id="@+id/change_mac"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginEnd="10dp"
android:inputType="none"
android:text="@string/change_mac"
android:textColor="@color/white_half"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/start_scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight= "1"
android:text="@string/start_scan"
style="?android:attr/buttonBarButtonStyle" />
<Button
android:id="@+id/stop_scan"
android:layout_weight= "1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stop_scan"
style="?android:attr/buttonBarButtonStyle" />
</LinearLayout>
<ListView
android:id="@+id/lv_scan"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8"?>
<!--<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.vehicle.VehicleFragment"
android:orientation="vertical"
android:background="@color/white">
<TextView
android:layout_width="wrap_content"
android:layout_height="48dp"
android:gravity="center"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="@string/to_broadcast"
android:textColor="@color/purple_200"
android:textSize="16sp" />
<EditText
android:id="@+id/edit_name"
android:layout_width="match_parent"
android:layout_height="48dp"
android:autofillHints="data"
android:layout_margin="8dp"
android:hint="@string/hint_name"
android:inputType="none"
android:textColor="@color/black"
android:textColorHint="@color/text_light_gray"
tools:ignore="TextContrastCheck" />
<EditText
android:id="@+id/edit_data"
android:layout_width="match_parent"
android:layout_height="48dp"
android:autofillHints="data"
android:layout_margin="8dp"
android:hint="@string/hint"
android:inputType="none"
android:textColor="@color/black"
android:textColorHint="@color/text_light_gray"
tools:ignore="TextContrastCheck" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/start_adv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/white"
android:layout_weight="1"
android:text="@string/start_adv"
android:textColor="@color/dark_blue"
style="?android:attr/buttonBarButtonStyle" />
<Button
android:id="@+id/stop_adv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/bg_week"
android:text="@string/stop_adv"
android:textColor="@color/dark_blue"
style="?android:attr/buttonBarButtonStyle" />
<Button
android:id="@+id/send_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/bg_week"
android:text="@string/send_to_car"
android:textColor="@color/dark_blue"
style="?android:attr/buttonBarButtonStyle" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginTop="10dp"
android:text="@string/tx_to_app"
android:textColor="@color/purple_200"
android:textSize="16sp" />
<EditText
android:id="@+id/tx_to_phone"
android:inputType="none"
android:textColor="@color/black"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="@color/light_gray"
android:autofillHints = "data"
android:textSize="12sp"
tools:ignore="TextFields" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:gravity="center"
android:text="@string/log"
android:textColor="@color/purple_200"
android:textSize="16sp" />
<TextView
android:id="@+id/rx_data_type"
android:layout_width="286dp"
android:layout_height="48dp"
android:inputType="none"
android:textColor="@color/black"
android:textSize="16sp" />
</LinearLayout>
<TextView
android:id="@+id/data_log"
android:inputType="none"
android:textColor="@color/black"
android:layout_width="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_height="230dp"
android:scrollbars="vertical"
android:background="@color/light_gray"
android:textSize="12sp"/>
</LinearLayout>
<!--</androidx.constraintlayout.widget.ConstraintLayout> -->

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="48dp"
android:gravity="start"
android:layout_weight="1"
android:textColor="@color/num_light_gray"
android:textSize="16sp"
android:layout_margin="5dp"
android:id = "@+id/device_name"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="48dp"
android:gravity="end"
android:layout_weight="1"
android:textColor="@color/num_light_gray"
android:textSize="16sp"
android:layout_margin="5dp"
android:id = "@+id/device_address"/>
</LinearLayout>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_vehicle"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_vehicle" />
<item
android:id="@+id/navigation_phone"
android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_phone" />
<item
android:id="@+id/navigation_files"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_files" />
</menu>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mobile_navigation"
app:startDestination="@+id/navigation_vehicle">
<fragment
android:id="@+id/navigation_vehicle"
android:name="com.example.tesmla.ui.vehicle.VehicleFragment"
android:label="@string/title_vehicle"
tools:layout="@layout/fragment_vehicle" />
<fragment
android:id="@+id/navigation_phone"
android:name="com.example.tesmla.ui.phone.PhoneFragment"
android:label="@string/title_phone"
tools:layout="@layout/fragment_phone" />
<fragment
android:id="@+id/navigation_files"
android:name="com.example.tesmla.ui.files.FilesFragment"
android:label="@string/title_files"
tools:layout="@layout/fragment_files" />
</navigation>

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.TesMla" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="half_theme_green">#5053d555</color>
<color name="theme_green">#53d555</color>
<color name="hard_green">#00b640</color>
<color name="white_half">#88FFFFFF</color>
<color name="yellow">#ffff00</color>
<color name="gray_email">#efefef</color>
<color name="light_gray">#cccccc</color>
<color name="edit_bg_gray">#b1b1b1</color>
<color name="theme_white">#f8f8f8</color>
<color name="dark_gray">#555555</color>
<color name="bg_date">#eff0fa</color>
<color name="red_menstrual">#ff98b0</color>
<!-- 蓝色 -->
<color name="blue">#12b6f5</color>
<color name="measure_blue">#00838F</color>
<color name="light_blue">#e4f5e9</color>
<color name="dark_blue">#1565C0</color>
<color name="measure_white">#f8f8f8</color>
<!-- 粉红 -->
<color name="pink">#ffeaeb</color>
<color name="green_light">#b5d95e</color>
<color name="pink_hard">#bd4e4e</color>
<!-- 紫色 -->
<color name="purple">#ea79fb</color>
<color name="text_light_gray">#999999</color>
<color name="text_lighter_gray">#bbbbbb</color>
<color name="num_light_gray">#666666</color>
<color name="text_dark_red">#ff4c4c</color>
<color name="half_transparent">#80000000</color>
<color name="half_white">#b8FFFFFF</color>
<color name="bg_check_result">#def4e9</color>
<color name="bg_week">#e4f7e3</color>
<color name="navi_color">#ffffff</color>
<color name="dialog_btn_bg">#eaeaea</color>
<color name="card_bg_unactived">#eaeaea</color>
<color name="card_bg_actived">#dfeffb</color>
<color name="switch_on">#2ea2f5</color>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,41 @@
<resources>
<string name="app_name">TesMla</string>
<string name="title_vehicle">Fake as Tesla</string>
<string name="title_phone">Scan</string>
<string name="title_files">Fake as App</string>
<string name = "unknown_op">unknown operation</string>
<string name = "write_chara">"ATT_WRITE_REQ for characteristic"</string>
<string name = "write_dsp">"ATT_WRITE_REQ for descriptor"</string>
<string name = "default_data">"Using default data: %s"</string>
<string name = "edit_Data">"Using Input Name: %s"</string>
<string name = "to_broadcast">要广播的数据:</string>
<string name = "hint">输入16进制字符串0~9A~F</string>
<string name = "hint_name">输入广播设备名</string>
<string name = "hint_mac">选择或输入(分号隔开)</string>
<string name = "start_adv">伪造广播</string>
<string name = "stop_adv">停止广播</string>
<string name = "send">回复</string>
<string name = "start_scan">开始扫描</string>
<string name = "stop_scan">停止扫描</string>
<string name = "mac">连接的设备:</string>
<string name = "tx_to_car">发送给车的数据:</string>
<string name = "tx_to_app">发送给app的数据:</string>
<string name = "connect">连接</string>
<string name = "get_pk">获取公钥</string>
<string name = "send_to_car">发送数据</string>
<string name = "disconnect">断开连接</string>
<string name = "load">加载文件</string>
<string name = "load_file">文件名:</string>
<string name = "change_mtu_size">更改MTU</string>
<string name = "connected">已连接</string>
<string name = "dir">/data/data/com.example.tesmla/files/%s</string>
<string name = "log">数据记录</string>
<string name = "change_mac">修改mac</string>
<string name = "mac_addr">MAC:</string>
</resources>

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.TesMla" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,17 @@
package com.example.tesmla;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

17
build.gradle Normal file
View File

@@ -0,0 +1,17 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.4"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

19
gradle.properties Normal file
View File

@@ -0,0 +1,19 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Mon Jan 17 14:46:09 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

185
gradlew vendored Normal file
View File

@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

10
settings.gradle Normal file
View File

@@ -0,0 +1,10 @@
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
}
}
rootProject.name = "TesMla"
include ':app'