Initial commit
15
.gitignore
vendored
Normal 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
116
.idea/codeStyles/Project.xml
generated
Normal 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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
|
||||
/build
|
||||
45
app/build.gradle
Normal 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
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
50
app/src/main/AndroidManifest.xml
Normal 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>
|
||||
90
app/src/main/java/com/example/tesmla/MainActivity.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
571
app/src/main/java/com/example/tesmla/ui/files/FilesFragment.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
332
app/src/main/java/com/example/tesmla/ui/phone/PhoneFragment.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
164
app/src/main/java/com/example/tesmla/utils/Conversion.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
32
app/src/main/java/com/example/tesmla/utils/ToastUtils.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
30
app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_dashboard_black_24dp.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_home_black_24dp.xml
Normal 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>
|
||||
BIN
app/src/main/res/drawable/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal 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>
|
||||
@@ -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>
|
||||
BIN
app/src/main/res/drawable/ic_tesla.bmp
Normal file
|
After Width: | Height: | Size: 44 KiB |
31
app/src/main/res/layout/activity_main.xml
Normal 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>
|
||||
142
app/src/main/res/layout/fragment_files.xml
Normal 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>
|
||||
68
app/src/main/res/layout/fragment_phone.xml
Normal 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>
|
||||
142
app/src/main/res/layout/fragment_vehicle.xml
Normal 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> -->
|
||||
25
app/src/main/res/layout/listview_item.xml
Normal 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>
|
||||
19
app/src/main/res/menu/bottom_nav_menu.xml
Normal 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>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal 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>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal 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>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
25
app/src/main/res/navigation/mobile_navigation.xml
Normal 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>
|
||||
16
app/src/main/res/values-night/themes.xml
Normal 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>
|
||||
52
app/src/main/res/values/colors.xml
Normal 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>
|
||||
5
app/src/main/res/values/dimens.xml
Normal 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>
|
||||
41
app/src/main/res/values/strings.xml
Normal 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~9,A~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>
|
||||
16
app/src/main/res/values/themes.xml
Normal 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>
|
||||
17
app/src/test/java/com/example/tesmla/ExampleUnitTest.java
Normal 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
@@ -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
@@ -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
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
@@ -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
@@ -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
@@ -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'
|
||||