初次提交
commit
005d65330d
@ -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,0 +1 @@
|
|||||||
|
/build
|
@ -0,0 +1,107 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace 'com.xypower.wpywapp'
|
||||||
|
compileSdk 33
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.xypower.wpywapp"
|
||||||
|
minSdk 28
|
||||||
|
targetSdk 33
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
signingConfigs {
|
||||||
|
debug {
|
||||||
|
storeFile file("../sign/platform.jks")
|
||||||
|
storePassword 'dowse_mtk8788'
|
||||||
|
keyAlias 'ds_mt8788_key'
|
||||||
|
keyPassword 'dowse_mtk8788'
|
||||||
|
}
|
||||||
|
|
||||||
|
release {
|
||||||
|
storeFile file("../sign/platform.jks")
|
||||||
|
storePassword 'dowse_mtk8788'
|
||||||
|
keyAlias 'ds_mt8788_key'
|
||||||
|
keyPassword 'dowse_mtk8788'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*支持databinding
|
||||||
|
* */
|
||||||
|
dataBinding {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
|
implementation 'com.google.android.material:material:1.8.0'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
|
||||||
|
implementation 'androidx.navigation:navigation-fragment:2.5.3'
|
||||||
|
implementation 'androidx.navigation:navigation-ui:2.5.3'
|
||||||
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
|
implementation 'me.jessyan:autosize:1.2.1'
|
||||||
|
|
||||||
|
//viewmodel依赖
|
||||||
|
implementation "androidx.lifecycle:lifecycle-viewmodel:2.4.0"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
|
||||||
|
//Livedata去除粘性事件
|
||||||
|
implementation 'com.kunminx.arch:unpeek-livedata:7.2.0-beta1'
|
||||||
|
|
||||||
|
implementation "com.tananaev:adblib:1.3"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Add this library
|
||||||
|
// implementation 'com.github.MuntashirAkon:libadb-android:1.0.1'
|
||||||
|
|
||||||
|
// Library to generate X509Certificate. You can also use BouncyCastle for
|
||||||
|
// this. See example for use-case.
|
||||||
|
implementation 'com.github.MuntashirAkon:sun-security-android:1.1'
|
||||||
|
|
||||||
|
// Bypass hidden API if you want to use the Android default Conscrypt in
|
||||||
|
// Android 9 (Pie) or later. It also requires additional steps. See
|
||||||
|
// https://github.com/LSPosed/AndroidHiddenApiBypass to find out more about
|
||||||
|
// this.
|
||||||
|
implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:2.0'
|
||||||
|
|
||||||
|
// Use custom Conscrypt library. If you want to connect to a remote ADB
|
||||||
|
// daemon instead of the device the app is currently running or do not want
|
||||||
|
// to bypass hidden API, this is the recommended choice.
|
||||||
|
implementation 'org.conscrypt:conscrypt-android:2.5.2'
|
||||||
|
implementation 'com.github.MuntashirAkon.spake2-java:android:2.0.0'
|
||||||
|
implementation 'org.bouncycastle:bcprov-jdk15to18:1.78'
|
||||||
|
|
||||||
|
implementation 'com.tananaev:adblib:1.3'
|
||||||
|
|
||||||
|
|
||||||
|
api 'com.github.KunMinX:MVI-Dispatcher:7.6.0'
|
||||||
|
// api 'com.github.KunMinX.Strict-DataBinding:strict_databinding:6.2.0'
|
||||||
|
|
||||||
|
//Livedata去除粘性事件
|
||||||
|
implementation 'com.kunminx.arch:unpeek-livedata:7.8.0'
|
||||||
|
}
|
@ -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.xypower.wpywapp;
|
||||||
|
|
||||||
|
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.xypower.wpywapp", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- 允许应用程序改变网络状态 -->
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <!-- 允许应用程序改变WIFI连接状态 -->
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <!-- 允许应用程序访问有关的网络信息 -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 允许应用程序访问WIFI网卡的网络信息 -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- 允许应用程序完全使用网络 -->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.SU" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<application
|
||||||
|
android:name=".WpywApplication"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.Wpywapp"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
|
tools:targetApi="31">
|
||||||
|
<activity
|
||||||
|
android:name=".ui.BottomActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:label="@string/title_activity_bottom" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="design_width_in_dp"
|
||||||
|
android:value="360" />
|
||||||
|
<meta-data
|
||||||
|
android:name="design_height_in_dp"
|
||||||
|
android:value="480" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.xypower.wpywapp;
|
||||||
|
|
||||||
|
public class ADBManager {
|
||||||
|
private static final String ADB_PATH = "/path/to/adb"; // 指定adb的路径
|
||||||
|
|
||||||
|
public static void connect() {
|
||||||
|
// 执行adb连接命令
|
||||||
|
executeCommand(ADB_PATH + " connect <device-ip>:<device-port>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void disconnect() {
|
||||||
|
// 执行adb断开连接命令
|
||||||
|
executeCommand(ADB_PATH + " disconnect <device-ip>:<device-port>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void executeCommand(String command) {
|
||||||
|
// 执行adb命令的逻辑
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,398 @@
|
|||||||
|
package com.xypower.wpywapp;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.databinding.DataBindingUtil;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.tananaev.adblib.AdbConnection;
|
||||||
|
import com.tananaev.adblib.AdbCrypto;
|
||||||
|
import com.tananaev.adblib.AdbStream;
|
||||||
|
import com.xypower.wpywapp.bean.TerminalBean;
|
||||||
|
import com.xypower.wpywapp.databinding.ActivityMainBinding;
|
||||||
|
import com.xypower.wpywapp.interfaces.MainActCallback;
|
||||||
|
import com.xypower.wpywapp.jadb.JadbDevice;
|
||||||
|
import com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbConnectionManager;
|
||||||
|
import com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbInputStream;
|
||||||
|
import com.xypower.wpywapp.tool.LocationUtil;
|
||||||
|
import com.xypower.wpywapp.ui.BottomActivity;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
这三个文件夹下日志的清除
|
||||||
|
* /var/log/xymp/xymanagerLogs
|
||||||
|
* /var/log/xymp/logs
|
||||||
|
* /usr/local/share/tomcat/logs/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
这个文件日志的切割
|
||||||
|
/usr/local/share/tomcat/logs/catalina.out
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private JadbDevice jadbDevice;
|
||||||
|
private TerminalBean bean = new TerminalBean();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
|
||||||
|
bean.setVerion("未知");
|
||||||
|
bean.setSim("未知");
|
||||||
|
bean.setPower("未知");
|
||||||
|
binding.setBean(bean);
|
||||||
|
Handler handler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(@NonNull Message msg) {
|
||||||
|
super.handleMessage(msg);
|
||||||
|
if (msg.what == 1) {
|
||||||
|
Toast.makeText(MainActivity.this, "1111", Toast.LENGTH_SHORT).show();
|
||||||
|
} else if (msg.what == 2) {
|
||||||
|
Toast.makeText(MainActivity.this, "2222", Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(MainActivity.this, "3333", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
binding.setCallback(new MainActCallback() {
|
||||||
|
@Override
|
||||||
|
public void link(View view) {
|
||||||
|
|
||||||
|
LocationUtil.register(MainActivity.this, 0, 0, new LocationUtil.OnLocationChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void getLastKnownLocation(Location location) {
|
||||||
|
Log.e("xyh", "onLocationChanged: " + location.getLatitude());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocationChanged(Location location) {
|
||||||
|
//位置信息变化时触发
|
||||||
|
Log.e("xyh", "定位方式:" + location.getProvider());
|
||||||
|
Log.e("xyh", "纬度:" + location.getLatitude());
|
||||||
|
Log.e("xyh", "经度:" + location.getLongitude());
|
||||||
|
Log.e("xyh", "海拔:" + location.getAltitude());
|
||||||
|
Log.e("xyh", "时间:" + location.getTime());
|
||||||
|
Log.e("xyh", "国家:" + LocationUtil.getCountryName(MainActivity.this, location.getLatitude(), location.getLongitude()));
|
||||||
|
Log.e("xyh", "获取地理位置:" + LocationUtil.getAddress(MainActivity.this, location.getLatitude(), location.getLongitude()));
|
||||||
|
Log.e("xyh", "所在地:" + LocationUtil.getLocality(MainActivity.this, location.getLatitude(), location.getLongitude()));
|
||||||
|
Log.e("xyh", "所在街道:" + LocationUtil.getStreet(MainActivity.this, location.getLatitude(), location.getLongitude()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||||
|
System.out.println("dfsad");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//连接设备
|
||||||
|
|
||||||
|
// Intent intent = new Intent(MainActivity.this, BottomActivity.class);
|
||||||
|
// startActivity(intent);
|
||||||
|
// String s = binding.ip.getText().toString();
|
||||||
|
// if (RegexUtil.checkIpAddress(s)) {
|
||||||
|
//获取可adb调试的设备列表
|
||||||
|
// new Thread(new Runnable() {
|
||||||
|
// @Override
|
||||||
|
// public void run() {
|
||||||
|
//// try {
|
||||||
|
////// Process process = Runtime.getRuntime().exec("adb shell devices");
|
||||||
|
////// int i = process.waitFor();
|
||||||
|
////// if (i == 0) {
|
||||||
|
////// InputStream inputStream = process.getInputStream();
|
||||||
|
////// System.out.println("2312");
|
||||||
|
////// } else {
|
||||||
|
////// InputStream errorStream = process.getErrorStream();
|
||||||
|
////// System.out.println("888484");
|
||||||
|
////// }
|
||||||
|
////// AdbServer adbServer = new AdbServer(new AdbResponder() {
|
||||||
|
////// @Override
|
||||||
|
////// public void onCommand(String command) {
|
||||||
|
////// Toast.makeText(MainActivity.this,"cehgnogn1",Toast.LENGTH_SHORT).show();
|
||||||
|
////// }
|
||||||
|
//////
|
||||||
|
////// @Override
|
||||||
|
////// public int getVersion() {
|
||||||
|
////// Toast.makeText(MainActivity.this,"cehgnogn3",Toast.LENGTH_SHORT).show();
|
||||||
|
////// return 0;
|
||||||
|
////// }
|
||||||
|
//////
|
||||||
|
////// @Override
|
||||||
|
////// public List<AdbDeviceResponder> getDevices() {
|
||||||
|
////// Toast.makeText(MainActivity.this,"cehgnogn2",Toast.LENGTH_SHORT).show();
|
||||||
|
////// return null;
|
||||||
|
////// }
|
||||||
|
////// });
|
||||||
|
////// Runnable responder = adbServer.createResponder(new Socket("127.0.0.1", 15037));
|
||||||
|
////// responder.run();
|
||||||
|
////// JadbConnection jadb = new JadbConnection("192.168.0.100",5555);
|
||||||
|
//// JadbConnection jadb = new JadbConnection("192.168.0.118", 5555);
|
||||||
|
////// JadbConnection jadb = new JadbConnection("127.0.0.1", 5555);
|
||||||
|
////// InetSocketAddress inetSocketAddress = jadb.connectToTcpDevice(new InetSocketAddress("192.168.0.101",5555));
|
||||||
|
////// Message msg1 = new Message();
|
||||||
|
////// msg1.what = 3;
|
||||||
|
////// handler.sendMessage(msg1);
|
||||||
|
////// System.out.println(inetSocketAddress);
|
||||||
|
//// List<JadbDevice> devices = jadb.getDevices();
|
||||||
|
//// jadbDevice = devices.get(0);
|
||||||
|
////// jadbDevice.pull();
|
||||||
|
//// } catch (IOException e) {
|
||||||
|
//// throw new RuntimeException(e);
|
||||||
|
//// } catch (JadbException e) {
|
||||||
|
//// throw new RuntimeException(e);
|
||||||
|
////// } catch (ConnectionToRemoteDeviceException e) {
|
||||||
|
////// throw new RuntimeException(e);
|
||||||
|
//// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//// JadbConnection jadbConnection = new JadbConnection();
|
||||||
|
//// List<JadbDevice> anyDevice = null;
|
||||||
|
//// try {
|
||||||
|
//// InetSocketAddress inetSocketAddress = jadbConnection.connectToTcpDevice(new InetSocketAddress("192.168.0.118",5555));
|
||||||
|
//// List<JadbDevice> devices = jadbConnection.getDevices();
|
||||||
|
//// JadbDevice jadbDevice = devices.get(1);
|
||||||
|
//// RemoteFile remoteFile = new RemoteFile("/storage/emulated/0/iport_log.txt");
|
||||||
|
//// String absolutePath = getFilesDir().getAbsolutePath();
|
||||||
|
//// jadbDevice.pull(remoteFile,new File(absolutePath+"/a.txt"));
|
||||||
|
//// System.out.println(inetSocketAddress);
|
||||||
|
//// System.out.println(devices);
|
||||||
|
////// anyDevice = jadbConnection.getDevices();
|
||||||
|
//// } catch (IOException e) {
|
||||||
|
//// e.printStackTrace();
|
||||||
|
//// } catch (JadbException e) {
|
||||||
|
//// e.printStackTrace();
|
||||||
|
////// } catch (ConnectionToRemoteDeviceException e) {
|
||||||
|
//// e.printStackTrace();
|
||||||
|
//// } catch (ConnectionToRemoteDeviceException e) {
|
||||||
|
//// e.printStackTrace();
|
||||||
|
////
|
||||||
|
//// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//// try {
|
||||||
|
//// if(Runtime.getRuntime().exec(new String[]{"adb", "version"}).waitFor()!=0){
|
||||||
|
//// System.out.println("dfsd");
|
||||||
|
//// }
|
||||||
|
//// } catch (IOException e) {
|
||||||
|
//// throw new RuntimeException(e);
|
||||||
|
//// } catch (InterruptedException e) {
|
||||||
|
//// throw new RuntimeException(e);
|
||||||
|
//// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// }).start();
|
||||||
|
// } else {
|
||||||
|
// Toast.makeText(MainActivity.this, "IP不合法", Toast.LENGTH_SHORT).show();
|
||||||
|
// }
|
||||||
|
// asyncTask.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getInfo(View view) {
|
||||||
|
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// ADBService adbService = new ADBServiceImpl();
|
||||||
|
//// adbService.pullFile()
|
||||||
|
// try {
|
||||||
|
// List<String> connectedDevicesUdid = adbService.getConnectedDevicesUdid();
|
||||||
|
// System.out.println(connectedDevicesUdid);
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// throw new RuntimeException(e);
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean connect = AdbConnectionManager.getInstance(MainActivity.this).connect("192.168.50.109", 5555);
|
||||||
|
if (connect) {
|
||||||
|
// /storage/emulated/0/i1/log/2024_04_21_yunWei.log
|
||||||
|
String absolutePath = getFilesDir().getAbsolutePath();
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream adbStream1 = AdbConnectionManager.getInstance(MainActivity.this).openStream("shell: exit");
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream adbStream = AdbConnectionManager.getInstance(MainActivity.this).openStream("pull: /storage/emulated/0/i1/log/2024_04_21_yunWei.log "+absolutePath+"/");
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream adbStream = AdbConnectionManager.getInstance(MainActivity.this).openStream("shell: date");
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbConnection adbConnection = AdbConnectionManager.getInstance(MainActivity.this).getAdbConnection();
|
||||||
|
// ("pull: /storage/emulated/0/i1/log/2024_04_21_yunWei.log "+absolutePath+"/");
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream open = adbConnection.open("sync: recv /storage/emulated/0/i1/iport_log.txt "+absolutePath+"/");
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream adbStream = AdbConnectionManager.getInstance(MainActivity.this).openStream("sync: recv: /storage/emulated/0/i1/iport_log.txt "+absolutePath+"/");
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream adbStream = AdbConnectionManager.getInstance(MainActivity.this).openStream("shell:cat /storage/emulated/0/iport_log.txt");
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream adbStream = AdbConnectionManager.getInstance(MainActivity.this).openStream("shell:cat /storage/emulated/0/iport_log.txt");
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream adbStream = AdbConnectionManager.getInstance(MainActivity.this).openStream("shell:cp /storage/emulated/0/iport_log.txt "+absolutePath+"/aa.txt");
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream adbStream2 = AdbConnectionManager.getInstance(MainActivity.this).openStream("sync:");
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream adbStream1 = AdbConnectionManager.getInstance(MainActivity.this).openStream(LocalServices.SYNC);
|
||||||
|
// adbStream1.flush();
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream adbStream = AdbConnectionManager.getInstance(MainActivity.this).openStream("shell: sync send /storage/emulated/0/ "+absolutePath+"/324.txt");
|
||||||
|
// com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream adbStream = AdbConnectionManager.getInstance(MainActivity.this).openStream("sync:"+"\n" +"recv: /storage/emulated/0/i1/iport_log.txt");
|
||||||
|
com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.AdbStream adbStream = AdbConnectionManager.getInstance(MainActivity.this).openStream("sync:"+"\n" +"recv: /storage/emulated/0/i1/iport_log.txt");
|
||||||
|
// byte[] b = new byte[4];
|
||||||
|
// AdbInputStream adbInputStream = adbStream.openInputStream();
|
||||||
|
AdbInputStream adbInputStream = adbStream.openInputStream();
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
String result = null;
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(adbInputStream));
|
||||||
|
String line = null;
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
sb.append(line);
|
||||||
|
}
|
||||||
|
result = sb.toString();
|
||||||
|
|
||||||
|
// int off = 0;
|
||||||
|
// int n = 0;
|
||||||
|
// int len = b.length;
|
||||||
|
// while (n < len) {
|
||||||
|
// int count = adbInputStream.read(b, off + n, len - n);
|
||||||
|
// if (count < 0)
|
||||||
|
// throw new EOFException();
|
||||||
|
// n += count;
|
||||||
|
// }
|
||||||
|
|
||||||
|
System.out.println(result);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
try {
|
||||||
|
AdbConnectionManager.getInstance(MainActivity.this).disconnect();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// Process process = Runtime.getRuntime().exec("version");
|
||||||
|
// int i = process.waitFor();
|
||||||
|
// if (i == 0) {
|
||||||
|
// InputStream inputStream = process.getInputStream();
|
||||||
|
// System.out.println("2312");
|
||||||
|
// } else {
|
||||||
|
// InputStream errorStream = process.getErrorStream();
|
||||||
|
// System.out.println("888484");
|
||||||
|
// }
|
||||||
|
//// JadbConnection jadb = new JadbConnection(s, 5555);
|
||||||
|
//// List<JadbDevice> devices = jadb.getDevices();
|
||||||
|
//// jadbDevice = devices.get(0);
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// throw new RuntimeException(e);
|
||||||
|
// } catch (InterruptedException e) {
|
||||||
|
// throw new RuntimeException(e);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
//获取设备信息
|
||||||
|
// try {
|
||||||
|
// if (jadbDevice != null) {
|
||||||
|
// jadbDevice.pull(new RemoteFile("/path/to/file.txt"), new File("file.txt"));
|
||||||
|
// //解析文件 将文件的值赋给bean对象
|
||||||
|
// binding.setBean(bean);
|
||||||
|
// }
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// throw new RuntimeException(e);
|
||||||
|
// } catch (JadbException e) {
|
||||||
|
// throw new RuntimeException(e);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void takePic(View view) {
|
||||||
|
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
|
||||||
|
Socket socket = null;
|
||||||
|
AdbCrypto crypto = new AdbCrypto();
|
||||||
|
AdbConnection adbConnection = null;
|
||||||
|
try {
|
||||||
|
// Message msg = new Message();
|
||||||
|
// msg.what = 1;
|
||||||
|
// handler.sendMessage(msg);
|
||||||
|
socket = new Socket("192.168.0.101", 5037);
|
||||||
|
// Message msg1 = new Message();
|
||||||
|
// msg1.what = 3;
|
||||||
|
// handler.sendMessage(msg1);
|
||||||
|
// handler.sendMessage(new Message());
|
||||||
|
adbConnection = AdbConnection.create(socket, crypto);
|
||||||
|
adbConnection.connect();
|
||||||
|
Message msg1 = new Message();
|
||||||
|
msg1.what = 3;
|
||||||
|
handler.sendMessage(msg1);
|
||||||
|
System.out.println("dsadADas");
|
||||||
|
AdbStream fsda = adbConnection.open("devices");
|
||||||
|
System.out.println("rewrewrew");
|
||||||
|
} catch (IOException e) {
|
||||||
|
Message msg = new Message();
|
||||||
|
msg.what = 2;
|
||||||
|
handler.sendMessage(msg);
|
||||||
|
// throw new RuntimeException(e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
|
||||||
|
// CommandResult adbDevices = ShellUtils.execCommand("adb version", false);
|
||||||
|
// System.out.println(adbDevices.errorMsg);
|
||||||
|
// testCmd.runAdbCommand("version");
|
||||||
|
// System.out.println(s);
|
||||||
|
// boolean b = testCmd.haveRoot();
|
||||||
|
// if (b) {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// if (jadbDevice != null) {
|
||||||
|
//手动拍照
|
||||||
|
// jadbDevice.buildCmdLine("手动拍照");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean runRootCommand(String command) {
|
||||||
|
Process process = null;
|
||||||
|
DataOutputStream os = null;
|
||||||
|
try {
|
||||||
|
process = Runtime.getRuntime().exec("su");
|
||||||
|
os = new DataOutputStream(process.getOutputStream());
|
||||||
|
os.writeBytes(command + "\n");
|
||||||
|
os.writeBytes("exit\n");
|
||||||
|
os.flush();
|
||||||
|
process.waitFor();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (os != null) {
|
||||||
|
os.close();
|
||||||
|
}
|
||||||
|
process.destroy();
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package com.xypower.wpywapp;
|
||||||
|
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.ViewModelStore;
|
||||||
|
import androidx.lifecycle.ViewModelStoreOwner;
|
||||||
|
|
||||||
|
public class WpywApplication extends Application implements ViewModelStoreOwner {
|
||||||
|
private ViewModelStore mAppViewModelStore;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
mAppViewModelStore = new ViewModelStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ViewModelStore getViewModelStore() {
|
||||||
|
return mAppViewModelStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是UI进程
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isUIProcess() {
|
||||||
|
int pid = android.os.Process.myPid();
|
||||||
|
String processName = "";
|
||||||
|
ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
|
for (ActivityManager.RunningAppProcessInfo runningAppProcessInfo : activityManager.getRunningAppProcesses()) {
|
||||||
|
if (runningAppProcessInfo.pid == pid) {
|
||||||
|
processName = runningAppProcessInfo.processName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TextUtils.equals(getPackageName(), processName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.xypower.wpywapp.base;
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
public class BaseViewModel extends ViewModel {
|
||||||
|
@Override
|
||||||
|
protected void onCleared() {
|
||||||
|
super.onCleared();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.xypower.wpywapp.bean;
|
||||||
|
|
||||||
|
public class TerminalBean {
|
||||||
|
|
||||||
|
private String verion;
|
||||||
|
private String sim;
|
||||||
|
private String power;
|
||||||
|
|
||||||
|
public String getVerion() {
|
||||||
|
return verion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVerion(String verion) {
|
||||||
|
this.verion = verion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSim() {
|
||||||
|
return sim;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSim(String sim) {
|
||||||
|
this.sim = sim;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPower() {
|
||||||
|
return power;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPower(String power) {
|
||||||
|
this.power = power;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.xypower.wpywapp.interfaces;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface MainActCallback {
|
||||||
|
|
||||||
|
void link(View view);
|
||||||
|
|
||||||
|
void getInfo(View view);
|
||||||
|
|
||||||
|
void takePic(View view);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class AdbFilterInputStream extends FilterInputStream {
|
||||||
|
public AdbFilterInputStream(InputStream inputStream) {
|
||||||
|
super(inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
int b1 = in.read();
|
||||||
|
if (b1 == 0x0d) {
|
||||||
|
in.mark(1);
|
||||||
|
int b2 = in.read();
|
||||||
|
if (b2 == 0x0a) {
|
||||||
|
return b2;
|
||||||
|
}
|
||||||
|
in.reset();
|
||||||
|
}
|
||||||
|
return b1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int b = read();
|
||||||
|
if (b == -1) return n == 0 ? -1 : n;
|
||||||
|
buffer[offset + n] = (byte) b;
|
||||||
|
n++;
|
||||||
|
|
||||||
|
// Return as soon as no more data is available (and at least one byte was read)
|
||||||
|
if (in.available() <= 0) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] buffer) throws IOException {
|
||||||
|
return read(buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class AdbFilterOutputStream extends LookBackFilteringOutputStream {
|
||||||
|
public AdbFilterOutputStream(OutputStream inner) {
|
||||||
|
super(inner, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int c) throws IOException {
|
||||||
|
if (!lookback().isEmpty()) {
|
||||||
|
Byte last = lookback().getFirst();
|
||||||
|
if (last != null && last == 0x0d && c == 0x0a) {
|
||||||
|
unwrite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.write(c);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches the ADB server
|
||||||
|
*/
|
||||||
|
public class AdbServerLauncher {
|
||||||
|
private final String executable;
|
||||||
|
private Subprocess subprocess;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new launcher loading ADB from the environment.
|
||||||
|
*
|
||||||
|
* @param subprocess the sub-process.
|
||||||
|
* @param environment the environment to use to locate the ADB executable.
|
||||||
|
*/
|
||||||
|
public AdbServerLauncher(Subprocess subprocess, Map<String, String> environment) {
|
||||||
|
this(subprocess, findAdbExecutable(environment));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new launcher with the specified ADB.
|
||||||
|
*
|
||||||
|
* @param subprocess the sub-process.
|
||||||
|
* @param executable the location of the ADB executable.
|
||||||
|
*/
|
||||||
|
public AdbServerLauncher(Subprocess subprocess, String executable) {
|
||||||
|
this.subprocess = subprocess;
|
||||||
|
this.executable = executable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String findAdbExecutable(Map<String, String> environment) {
|
||||||
|
String androidHome = environment.get("ANDROID_HOME");
|
||||||
|
if (androidHome == null || androidHome.equals("")) {
|
||||||
|
return "adb";
|
||||||
|
}
|
||||||
|
return androidHome + "/platform-tools/adb";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void launch() throws IOException, InterruptedException {
|
||||||
|
Process p = subprocess.execute(new String[]{executable, "start-server"});
|
||||||
|
p.waitFor();
|
||||||
|
int exitValue = p.exitValue();
|
||||||
|
if (exitValue != 0) throw new IOException("adb exited with exit code: " + exitValue);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
public class ConnectionToRemoteDeviceException extends Exception {
|
||||||
|
public ConnectionToRemoteDeviceException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface DeviceDetectionListener {
|
||||||
|
void onDetect(List<JadbDevice> devices);
|
||||||
|
void onException(Exception e);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class DeviceWatcher implements Runnable {
|
||||||
|
private Transport transport;
|
||||||
|
private final DeviceDetectionListener listener;
|
||||||
|
private final JadbConnection connection;
|
||||||
|
|
||||||
|
public DeviceWatcher(Transport transport, DeviceDetectionListener listener, JadbConnection connection) {
|
||||||
|
this.transport = transport;
|
||||||
|
this.listener = listener;
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("squid:S2189") // watcher is stopped by closing transport
|
||||||
|
private void watch() {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
listener.onDetect(connection.parseDevices(transport.readString()));
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
synchronized(this) {
|
||||||
|
if (transport != null) {
|
||||||
|
listener.onException(ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
listener.onException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() throws IOException {
|
||||||
|
synchronized(this) {
|
||||||
|
transport.close();
|
||||||
|
transport = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
class HostConnectToRemoteTcpDevice extends HostConnectionCommand {
|
||||||
|
HostConnectToRemoteTcpDevice(Transport transport) {
|
||||||
|
super(transport, new ResponseValidatorImp());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Visible for testing
|
||||||
|
HostConnectToRemoteTcpDevice(Transport transport, ResponseValidator responseValidator) {
|
||||||
|
super(transport, responseValidator);
|
||||||
|
}
|
||||||
|
|
||||||
|
InetSocketAddress connect(InetSocketAddress inetSocketAddress)
|
||||||
|
throws IOException, JadbException, ConnectionToRemoteDeviceException {
|
||||||
|
return executeHostCommand("connect", inetSocketAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class ResponseValidatorImp extends ResponseValidatorBase {
|
||||||
|
private static final String SUCCESSFULLY_CONNECTED = "connected to";
|
||||||
|
private static final String ALREADY_CONNECTED = "already connected to";
|
||||||
|
|
||||||
|
ResponseValidatorImp() {
|
||||||
|
super(SUCCESSFULLY_CONNECTED, ALREADY_CONNECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
public class HostConnectionCommand {
|
||||||
|
private final Transport transport;
|
||||||
|
private final ResponseValidator responseValidator;
|
||||||
|
|
||||||
|
HostConnectionCommand(Transport transport, ResponseValidator responseValidator) {
|
||||||
|
this.transport = transport;
|
||||||
|
this.responseValidator = responseValidator;
|
||||||
|
}
|
||||||
|
|
||||||
|
InetSocketAddress executeHostCommand(String command, InetSocketAddress inetSocketAddress)
|
||||||
|
throws IOException, JadbException, ConnectionToRemoteDeviceException {
|
||||||
|
transport.send(String.format("host:%s:%s:%d", command, inetSocketAddress.getHostString(), inetSocketAddress.getPort()));
|
||||||
|
verifyTransportLevel();
|
||||||
|
verifyProtocolLevel();
|
||||||
|
|
||||||
|
return inetSocketAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyTransportLevel() throws IOException, JadbException {
|
||||||
|
transport.verifyResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyProtocolLevel() throws IOException, ConnectionToRemoteDeviceException {
|
||||||
|
String status = transport.readString();
|
||||||
|
responseValidator.validate(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@VisibleForTesting
|
||||||
|
interface ResponseValidator {
|
||||||
|
void validate(String response) throws ConnectionToRemoteDeviceException;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ResponseValidatorBase implements ResponseValidator {
|
||||||
|
private final String successMessage;
|
||||||
|
private final String errorMessage;
|
||||||
|
|
||||||
|
ResponseValidatorBase(String successMessage, String errorMessage) {
|
||||||
|
this.successMessage = successMessage;
|
||||||
|
this.errorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(String response) throws ConnectionToRemoteDeviceException {
|
||||||
|
if (!checkIfConnectedSuccessfully(response) && !checkIfAlreadyConnected(response)) {
|
||||||
|
throw new ConnectionToRemoteDeviceException(extractError(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkIfConnectedSuccessfully(String response) {
|
||||||
|
return response.startsWith(successMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkIfAlreadyConnected(String response) {
|
||||||
|
return response.startsWith(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractError(String response) {
|
||||||
|
int lastColon = response.lastIndexOf(':');
|
||||||
|
if (lastColon != -1) {
|
||||||
|
return response.substring(lastColon);
|
||||||
|
} else {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
public class HostDisconnectFromRemoteTcpDevice extends HostConnectionCommand {
|
||||||
|
HostDisconnectFromRemoteTcpDevice(Transport transport) {
|
||||||
|
super(transport, new ResponseValidatorImp());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Visible for testing
|
||||||
|
HostDisconnectFromRemoteTcpDevice(Transport transport, ResponseValidator responseValidator) {
|
||||||
|
super(transport, responseValidator);
|
||||||
|
}
|
||||||
|
|
||||||
|
InetSocketAddress disconnect(InetSocketAddress inetSocketAddress)
|
||||||
|
throws IOException, JadbException, ConnectionToRemoteDeviceException {
|
||||||
|
return executeHostCommand("disconnect", inetSocketAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class ResponseValidatorImp extends ResponseValidatorBase {
|
||||||
|
private static final String SUCCESSFULLY_DISCONNECTED = "disconnected";
|
||||||
|
private static final String ALREADY_DISCONNECTED = "error: no such device";
|
||||||
|
|
||||||
|
ResponseValidatorImp() {
|
||||||
|
super(SUCCESSFULLY_DISCONNECTED, ALREADY_DISCONNECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Törcsi on 2016. 03. 01..
|
||||||
|
*/
|
||||||
|
public interface ITransportFactory {
|
||||||
|
Transport createTransport() throws IOException;
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class JadbConnection implements ITransportFactory {
|
||||||
|
|
||||||
|
private final String host;
|
||||||
|
private final int port;
|
||||||
|
|
||||||
|
private static final int DEFAULTPORT = 5037;
|
||||||
|
|
||||||
|
public JadbConnection() {
|
||||||
|
this("localhost", DEFAULTPORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadbConnection(String host, int port) {
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transport createTransport() throws IOException {
|
||||||
|
return new Transport(new Socket(host, port));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHostVersion() throws IOException, JadbException {
|
||||||
|
try (Transport transport = createTransport()) {
|
||||||
|
transport.send("host:version");
|
||||||
|
transport.verifyResponse();
|
||||||
|
return transport.readString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetSocketAddress connectToTcpDevice(InetSocketAddress inetSocketAddress)
|
||||||
|
throws IOException, JadbException, ConnectionToRemoteDeviceException {
|
||||||
|
try (Transport transport = createTransport()) {
|
||||||
|
return new HostConnectToRemoteTcpDevice(transport).connect(inetSocketAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetSocketAddress disconnectFromTcpDevice(InetSocketAddress tcpAddressEntity)
|
||||||
|
throws IOException, JadbException, ConnectionToRemoteDeviceException {
|
||||||
|
try (Transport transport = createTransport()) {
|
||||||
|
return new HostDisconnectFromRemoteTcpDevice(transport).disconnect(tcpAddressEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JadbDevice> getDevices() throws IOException, JadbException {
|
||||||
|
try (Transport transport = createTransport()) {
|
||||||
|
transport.send("host:devices");
|
||||||
|
transport.verifyResponse();
|
||||||
|
String body = transport.readString();
|
||||||
|
return parseDevices(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceWatcher createDeviceWatcher(DeviceDetectionListener listener) throws IOException, JadbException {
|
||||||
|
Transport transport = createTransport();
|
||||||
|
transport.send("host:track-devices");
|
||||||
|
transport.verifyResponse();
|
||||||
|
return new DeviceWatcher(transport, listener, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JadbDevice> parseDevices(String body) {
|
||||||
|
String[] lines = body.split("\n");
|
||||||
|
ArrayList<JadbDevice> devices = new ArrayList<>(lines.length);
|
||||||
|
for (String line : lines) {
|
||||||
|
String[] parts = line.split("\t");
|
||||||
|
if (parts.length > 1) {
|
||||||
|
devices.add(new JadbDevice(parts[0], this)); // parts[1] is type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JadbDevice getAnyDevice() {
|
||||||
|
return JadbDevice.createAny(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,256 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import com.xypower.wpywapp.jadb.managers.Bash;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class JadbDevice {
|
||||||
|
@SuppressWarnings("squid:S00115")
|
||||||
|
public enum State {
|
||||||
|
Unknown,
|
||||||
|
Offline,
|
||||||
|
Device,
|
||||||
|
Recovery,
|
||||||
|
BootLoader,
|
||||||
|
Unauthorized,
|
||||||
|
Authorizing,
|
||||||
|
Sideload,
|
||||||
|
Connecting,
|
||||||
|
Rescue
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection OctalInteger
|
||||||
|
private static final int DEFAULT_MODE = 0664;
|
||||||
|
private final String serial;
|
||||||
|
private final ITransportFactory transportFactory;
|
||||||
|
private static final int DEFAULT_TCPIP_PORT = 5555;
|
||||||
|
|
||||||
|
JadbDevice(String serial, ITransportFactory tFactory) {
|
||||||
|
this.serial = serial;
|
||||||
|
this.transportFactory = tFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JadbDevice createAny(JadbConnection connection) {
|
||||||
|
return new JadbDevice(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JadbDevice(ITransportFactory tFactory) {
|
||||||
|
serial = null;
|
||||||
|
this.transportFactory = tFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private State convertState(String type) {
|
||||||
|
switch (type) {
|
||||||
|
case "device": return State.Device;
|
||||||
|
case "offline": return State.Offline;
|
||||||
|
case "bootloader": return State.BootLoader;
|
||||||
|
case "recovery": return State.Recovery;
|
||||||
|
case "unauthorized": return State.Unauthorized;
|
||||||
|
case "authorizing" : return State.Authorizing;
|
||||||
|
case "connecting": return State.Connecting;
|
||||||
|
case "sideload": return State.Sideload;
|
||||||
|
case "rescue" : return State.Rescue;
|
||||||
|
default: return State.Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Transport getTransport() throws IOException, JadbException {
|
||||||
|
Transport transport = transportFactory.createTransport();
|
||||||
|
// Do not use try-with-resources here. We want to return unclosed Transport and it is up to caller
|
||||||
|
// to close it. Here we close it only in case of exception.
|
||||||
|
try {
|
||||||
|
send(transport, serial == null ? "host:transport-any" : "host:transport:" + serial );
|
||||||
|
} catch (IOException|JadbException e) {
|
||||||
|
transport.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return transport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSerial() {
|
||||||
|
return serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State getState() throws IOException, JadbException {
|
||||||
|
try (Transport transport = transportFactory.createTransport()) {
|
||||||
|
send(transport, serial == null ? "host:get-state" : "host-serial:" + serial + ":get-state");
|
||||||
|
return convertState(transport.readString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <p>Execute a shell command.</p>
|
||||||
|
*
|
||||||
|
* <p>For Lollipop and later see: {@link #execute(String, String...)}</p>
|
||||||
|
*
|
||||||
|
* @param command main command to run. E.g. "ls"
|
||||||
|
* @param args arguments to the command.
|
||||||
|
* @return combined stdout/stderr stream.
|
||||||
|
* @throws IOException
|
||||||
|
* @throws JadbException
|
||||||
|
*/
|
||||||
|
public InputStream executeShell(String command, String... args) throws IOException, JadbException {
|
||||||
|
Transport transport = getTransport();
|
||||||
|
StringBuilder shellLine = buildCmdLine(command, args);
|
||||||
|
send(transport, "shell:" + shellLine.toString());
|
||||||
|
return new AdbFilterInputStream(new BufferedInputStream(transport.getInputStream()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @deprecated Use InputStream executeShell(String command, String... args) method instead. Together with
|
||||||
|
* Stream.copy(in, out), it is possible to achieve the same effect.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void executeShell(OutputStream output, String command, String... args) throws IOException, JadbException {
|
||||||
|
try (Transport transport = getTransport()) {
|
||||||
|
StringBuilder shellLine = buildCmdLine(command, args);
|
||||||
|
send(transport, "shell:" + shellLine.toString());
|
||||||
|
if (output == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AdbFilterOutputStream out = new AdbFilterOutputStream(output);
|
||||||
|
transport.readResponseTo(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <p>Execute a command with raw binary output.</p>
|
||||||
|
*
|
||||||
|
* <p>Support for this command was added in Lollipop (Android 5.0), and is the recommended way to transmit binary
|
||||||
|
* data with that version or later. For earlier versions of Android, use
|
||||||
|
* {@link #executeShell(String, String...)}.</p>
|
||||||
|
*
|
||||||
|
* @param command main command to run, e.g. "screencap"
|
||||||
|
* @param args arguments to the command, e.g. "-p".
|
||||||
|
* @return combined stdout/stderr stream.
|
||||||
|
* @throws IOException
|
||||||
|
* @throws JadbException
|
||||||
|
*/
|
||||||
|
public InputStream execute(String command, String... args) throws IOException, JadbException {
|
||||||
|
Transport transport = getTransport();
|
||||||
|
StringBuilder shellLine = buildCmdLine(command, args);
|
||||||
|
send(transport, "exec:" + shellLine.toString());
|
||||||
|
return new BufferedInputStream(transport.getInputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a command line string from the command and its arguments.
|
||||||
|
*
|
||||||
|
* @param command the command.
|
||||||
|
* @param args the list of arguments.
|
||||||
|
* @return the command line.
|
||||||
|
*/
|
||||||
|
private StringBuilder buildCmdLine(String command, String... args) {
|
||||||
|
StringBuilder shellLine = new StringBuilder(command);
|
||||||
|
for (String arg : args) {
|
||||||
|
shellLine.append(" ");
|
||||||
|
shellLine.append(Bash.quote(arg));
|
||||||
|
}
|
||||||
|
return shellLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable tcpip on the default port (5555)
|
||||||
|
*
|
||||||
|
* @return success or failure
|
||||||
|
*/
|
||||||
|
public void enableAdbOverTCP() throws IOException, JadbException {
|
||||||
|
enableAdbOverTCP(DEFAULT_TCPIP_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable tcpip on a specific port
|
||||||
|
*
|
||||||
|
* @param port for the device to bind on
|
||||||
|
*
|
||||||
|
* @return success or failure
|
||||||
|
*/
|
||||||
|
public void enableAdbOverTCP(int port) throws IOException, JadbException {
|
||||||
|
try (Transport transport = getTransport()) {
|
||||||
|
send(transport, String.format("tcpip:%d", port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RemoteFile> list(String remotePath) throws IOException, JadbException {
|
||||||
|
try (Transport transport = getTransport()) {
|
||||||
|
SyncTransport sync = transport.startSync();
|
||||||
|
sync.send("LIST", remotePath);
|
||||||
|
|
||||||
|
List<RemoteFile> result = new ArrayList<>();
|
||||||
|
for (RemoteFileRecord dent = sync.readDirectoryEntry(); dent != RemoteFileRecord.DONE; dent = sync.readDirectoryEntry()) {
|
||||||
|
result.add(dent);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void push(InputStream source, long lastModified, int mode, RemoteFile remote) throws IOException, JadbException {
|
||||||
|
try (Transport transport = getTransport()) {
|
||||||
|
SyncTransport sync = transport.startSync();
|
||||||
|
sync.send("SEND", remote.getPath() + "," + mode);
|
||||||
|
|
||||||
|
sync.sendStream(source);
|
||||||
|
|
||||||
|
sync.sendStatus("DONE", (int) lastModified);
|
||||||
|
sync.verifyStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void push(File local, RemoteFile remote) throws IOException, JadbException {
|
||||||
|
try (FileInputStream fileStream = new FileInputStream(local)) {
|
||||||
|
push(fileStream, TimeUnit.MILLISECONDS.toSeconds(local.lastModified()), DEFAULT_MODE, remote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pull(RemoteFile remote, OutputStream destination) throws IOException, JadbException {
|
||||||
|
try (Transport transport = getTransport()) {
|
||||||
|
SyncTransport sync = transport.startSync();
|
||||||
|
sync.send("RECV", remote.getPath());
|
||||||
|
|
||||||
|
sync.readChunksTo(destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pull(RemoteFile remote, File local) throws IOException, JadbException {
|
||||||
|
try (FileOutputStream fileStream = new FileOutputStream(local)) {
|
||||||
|
pull(remote, fileStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(Transport transport, String command) throws IOException, JadbException {
|
||||||
|
transport.send(command);
|
||||||
|
transport.verifyResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Android Device with serial " + serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((serial == null) ? 0 : serial.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
JadbDevice other = (JadbDevice) obj;
|
||||||
|
if (serial == null) {
|
||||||
|
return other.serial == null;
|
||||||
|
}
|
||||||
|
return serial.equals(other.serial);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
public class JadbException extends Exception {
|
||||||
|
|
||||||
|
public JadbException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -3879283786835654165L;
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.FilterOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
|
||||||
|
public class LookBackFilteringOutputStream extends FilterOutputStream {
|
||||||
|
private final ArrayDeque<Byte> buffer;
|
||||||
|
private final int lookBackBufferSize;
|
||||||
|
|
||||||
|
protected LookBackFilteringOutputStream(OutputStream inner, int lookBackBufferSize)
|
||||||
|
{
|
||||||
|
super(inner);
|
||||||
|
this.lookBackBufferSize = lookBackBufferSize;
|
||||||
|
this.buffer = new ArrayDeque<>(lookBackBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void unwrite() {
|
||||||
|
buffer.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ArrayDeque<Byte> lookback() {
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int c) throws IOException {
|
||||||
|
buffer.addLast((byte) c);
|
||||||
|
flushBuffer(lookBackBufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
flushBuffer(0);
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushBuffer(int size) throws IOException {
|
||||||
|
while (buffer.size() > size) {
|
||||||
|
Byte b = buffer.removeFirst();
|
||||||
|
out.write(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by vidstige on 2014-03-20
|
||||||
|
*/
|
||||||
|
public class RemoteFile {
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
public RemoteFile(String path) { this.path = path; }
|
||||||
|
|
||||||
|
public String getName() { throw new UnsupportedOperationException(); }
|
||||||
|
public int getSize() { throw new UnsupportedOperationException(); }
|
||||||
|
public int getLastModified() { throw new UnsupportedOperationException(); }
|
||||||
|
public boolean isDirectory() { throw new UnsupportedOperationException(); }
|
||||||
|
|
||||||
|
public String getPath() { return path;}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
RemoteFile that = (RemoteFile) o;
|
||||||
|
return path.equals(that.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return path.hashCode();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by vidstige on 2014-03-19.
|
||||||
|
*/
|
||||||
|
class RemoteFileRecord extends RemoteFile {
|
||||||
|
public static final RemoteFileRecord DONE = new RemoteFileRecord(null, 0, 0, 0);
|
||||||
|
|
||||||
|
private final int mode;
|
||||||
|
private final int size;
|
||||||
|
private final int lastModified;
|
||||||
|
|
||||||
|
public RemoteFileRecord(String name, int mode, int size, int lastModified) {
|
||||||
|
super(name);
|
||||||
|
this.mode = mode;
|
||||||
|
this.size = size;
|
||||||
|
this.lastModified = lastModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLastModified() {
|
||||||
|
return lastModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDirectory() {
|
||||||
|
return (mode & (1 << 14)) == (1 << 14);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
public class Stream {
|
||||||
|
private Stream() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copy(InputStream in, OutputStream out) throws IOException {
|
||||||
|
byte[] buffer = new byte[1024 * 10];
|
||||||
|
int len;
|
||||||
|
while ((len = in.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String readAll(InputStream input, Charset charset) throws IOException {
|
||||||
|
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
|
||||||
|
Stream.copy(input, tmp);
|
||||||
|
return new String(tmp.toByteArray(), charset);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class Subprocess {
|
||||||
|
public Process execute(String[] command) throws IOException {
|
||||||
|
return Runtime.getRuntime().exec(command);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by vidstige on 2014-03-19.
|
||||||
|
*/
|
||||||
|
public class SyncTransport {
|
||||||
|
|
||||||
|
private final DataOutput output;
|
||||||
|
private final DataInput input;
|
||||||
|
|
||||||
|
public SyncTransport(DataOutput outputStream, DataInput inputStream) {
|
||||||
|
output = outputStream;
|
||||||
|
input = inputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String syncCommand, String name) throws IOException {
|
||||||
|
if (syncCommand.length() != 4) throw new IllegalArgumentException("sync commands must have length 4");
|
||||||
|
output.writeBytes(syncCommand);
|
||||||
|
byte[] data = name.getBytes(StandardCharsets.UTF_8);
|
||||||
|
output.writeInt(Integer.reverseBytes(data.length));
|
||||||
|
output.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendStatus(String statusCode, int length) throws IOException {
|
||||||
|
output.writeBytes(statusCode);
|
||||||
|
output.writeInt(Integer.reverseBytes(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void verifyStatus() throws IOException, JadbException {
|
||||||
|
String status = readString(4);
|
||||||
|
int length = readInt();
|
||||||
|
if ("FAIL".equals(status)) {
|
||||||
|
String error = readString(length);
|
||||||
|
throw new JadbException(error);
|
||||||
|
}
|
||||||
|
if (!"OKAY".equals(status)) {
|
||||||
|
throw new JadbException("Unknown error: " + status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readInt() throws IOException {
|
||||||
|
return Integer.reverseBytes(input.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readString(int length) throws IOException {
|
||||||
|
byte[] buffer = new byte[length];
|
||||||
|
input.readFully(buffer);
|
||||||
|
return new String(buffer, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendDirectoryEntry(RemoteFile file) throws IOException {
|
||||||
|
output.writeBytes("DENT");
|
||||||
|
output.writeInt(Integer.reverseBytes(0666 | (file.isDirectory() ? (1 << 14) : 0)));
|
||||||
|
output.writeInt(Integer.reverseBytes(file.getSize()));
|
||||||
|
output.writeInt(Integer.reverseBytes(file.getLastModified()));
|
||||||
|
byte[] pathChars = file.getPath().getBytes(StandardCharsets.UTF_8);
|
||||||
|
output.writeInt(Integer.reverseBytes(pathChars.length));
|
||||||
|
output.write(pathChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendDirectoryEntryDone() throws IOException {
|
||||||
|
output.writeBytes("DONE");
|
||||||
|
output.writeBytes("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); // equivalent to the length of a "normal" dent
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteFileRecord readDirectoryEntry() throws IOException {
|
||||||
|
String id = readString(4);
|
||||||
|
int mode = readInt();
|
||||||
|
int size = readInt();
|
||||||
|
int time = readInt();
|
||||||
|
int nameLength = readInt();
|
||||||
|
String name = readString(nameLength);
|
||||||
|
|
||||||
|
if (!"DENT".equals(id)) return RemoteFileRecord.DONE;
|
||||||
|
return new RemoteFileRecord(name, mode, size, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendChunk(byte[] buffer, int offset, int length) throws IOException {
|
||||||
|
output.writeBytes("DATA");
|
||||||
|
output.writeInt(Integer.reverseBytes(length));
|
||||||
|
output.write(buffer, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readChunk(byte[] buffer) throws IOException, JadbException {
|
||||||
|
String id = readString(4);
|
||||||
|
int n = readInt();
|
||||||
|
if ("FAIL".equals(id)) {
|
||||||
|
throw new JadbException(readString(n));
|
||||||
|
}
|
||||||
|
if (!"DATA".equals(id)) return -1;
|
||||||
|
input.readFully(buffer, 0, n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendStream(InputStream in) throws IOException {
|
||||||
|
byte[] buffer = new byte[1024 * 64];
|
||||||
|
int n = in.read(buffer);
|
||||||
|
while (n != -1) {
|
||||||
|
sendChunk(buffer, 0, n);
|
||||||
|
n = in.read(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readChunksTo(OutputStream stream) throws IOException, JadbException {
|
||||||
|
byte[] buffer = new byte[1024 * 64];
|
||||||
|
int n = readChunk(buffer);
|
||||||
|
while (n != -1) {
|
||||||
|
stream.write(buffer, 0, n);
|
||||||
|
n = readChunk(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package com.xypower.wpywapp.jadb;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
class Transport implements Closeable {
|
||||||
|
|
||||||
|
private final OutputStream outputStream;
|
||||||
|
private final InputStream inputStream;
|
||||||
|
private final DataInputStream dataInput;
|
||||||
|
private final DataOutputStream dataOutput;
|
||||||
|
|
||||||
|
private Transport(OutputStream outputStream, InputStream inputStream) {
|
||||||
|
this.outputStream = outputStream;
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
this.dataInput = new DataInputStream(inputStream);
|
||||||
|
this.dataOutput = new DataOutputStream(outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transport(Socket socket) throws IOException {
|
||||||
|
this(socket.getOutputStream(), socket.getInputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String readString() throws IOException {
|
||||||
|
String encodedLength = readString(4);
|
||||||
|
int length = Integer.parseInt(encodedLength, 16);
|
||||||
|
return readString(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readResponseTo(OutputStream output) throws IOException {
|
||||||
|
Stream.copy(inputStream, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getInputStream() {
|
||||||
|
return inputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void verifyResponse() throws IOException, JadbException {
|
||||||
|
String response = readString(4);
|
||||||
|
if (!"OKAY".equals(response)) {
|
||||||
|
String error = readString();
|
||||||
|
throw new JadbException("command failed: " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String readString(int length) throws IOException {
|
||||||
|
byte[] responseBuffer = new byte[length];
|
||||||
|
dataInput.readFully(responseBuffer);
|
||||||
|
return new String(responseBuffer, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCommandLength(String command) {
|
||||||
|
return String.format("%04x", command.getBytes().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String command) throws IOException {
|
||||||
|
OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
|
||||||
|
writer.write(getCommandLength(command));
|
||||||
|
writer.write(command);
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyncTransport startSync() throws IOException, JadbException {
|
||||||
|
send("sync:");
|
||||||
|
verifyResponse();
|
||||||
|
return new SyncTransport(dataOutput, dataInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
dataInput.close();
|
||||||
|
dataOutput.close();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.xypower.wpywapp.jadb.managers;
|
||||||
|
|
||||||
|
public class Bash {
|
||||||
|
private Bash() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String quote(String s) {
|
||||||
|
return "'" + s.replace("'", "'\\''") + "'";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.xypower.wpywapp.jadb.managers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android package
|
||||||
|
*/
|
||||||
|
public class Package {
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public Package(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() { return name; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof Package)) return false;
|
||||||
|
Package that = (Package) o;
|
||||||
|
return name.equals(that.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() { return name.hashCode(); }
|
||||||
|
}
|
@ -0,0 +1,144 @@
|
|||||||
|
package com.xypower.wpywapp.jadb.managers;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import com.xypower.wpywapp.jadb.JadbDevice;
|
||||||
|
import com.xypower.wpywapp.jadb.JadbException;
|
||||||
|
import com.xypower.wpywapp.jadb.RemoteFile;
|
||||||
|
import com.xypower.wpywapp.jadb.Stream;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java interface to package manager. Launches package manager through jadb
|
||||||
|
*/
|
||||||
|
public class PackageManager {
|
||||||
|
private final JadbDevice device;
|
||||||
|
|
||||||
|
public PackageManager(JadbDevice device) {
|
||||||
|
this.device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Package> getPackages() throws IOException, JadbException {
|
||||||
|
try (BufferedReader input = new BufferedReader(new InputStreamReader(device.executeShell("pm", "list", "packages"), StandardCharsets.UTF_8))) {
|
||||||
|
ArrayList<Package> result = new ArrayList<>();
|
||||||
|
String line;
|
||||||
|
while ((line = input.readLine()) != null) {
|
||||||
|
final String prefix = "package:";
|
||||||
|
if (line.startsWith(prefix)) {
|
||||||
|
result.add(new Package(line.substring(prefix.length())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getErrorMessage(String operation, String target, String errorMessage) {
|
||||||
|
return "Could not " + operation + " " + target + ": " + errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyOperation(String operation, String target, String result) throws JadbException {
|
||||||
|
if (!result.contains("Success")) throw new JadbException(getErrorMessage(operation, target, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void remove(RemoteFile file) throws IOException, JadbException {
|
||||||
|
InputStream s = device.executeShell("rm", "-f", file.getPath());
|
||||||
|
Stream.readAll(s, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void install(File apkFile, List<String> extraArguments) throws IOException, JadbException {
|
||||||
|
RemoteFile remote = new RemoteFile("/data/local/tmp/" + apkFile.getName());
|
||||||
|
device.push(apkFile, remote);
|
||||||
|
List<String> arguments = new ArrayList<>();
|
||||||
|
arguments.add("install");
|
||||||
|
arguments.addAll(extraArguments);
|
||||||
|
arguments.add(remote.getPath());
|
||||||
|
InputStream s = device.executeShell("pm", arguments.toArray(new String[0]));
|
||||||
|
String result = Stream.readAll(s, StandardCharsets.UTF_8);
|
||||||
|
remove(remote);
|
||||||
|
verifyOperation("install", apkFile.getName(), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void install(File apkFile) throws IOException, JadbException {
|
||||||
|
install(apkFile, new ArrayList<String>(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void installWithOptions(File apkFile, List<? extends InstallOption> options) throws IOException, JadbException {
|
||||||
|
List<String> optionsAsStr = new ArrayList<>(options.size());
|
||||||
|
|
||||||
|
for(InstallOption installOption : options) {
|
||||||
|
optionsAsStr.add(installOption.getStringRepresentation());
|
||||||
|
}
|
||||||
|
install(apkFile, optionsAsStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forceInstall(File apkFile) throws IOException, JadbException {
|
||||||
|
installWithOptions(apkFile, Collections.singletonList(REINSTALL_KEEPING_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uninstall(java.lang.Package name) throws IOException, JadbException {
|
||||||
|
InputStream s = device.executeShell("pm", "uninstall", name.toString());
|
||||||
|
String result = Stream.readAll(s, StandardCharsets.UTF_8);
|
||||||
|
verifyOperation("uninstall", name.toString(), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void launch(java.lang.Package name) throws IOException, JadbException {
|
||||||
|
InputStream s = device.executeShell("monkey", "-p", name.toString(), "-c", "android.intent.category.LAUNCHER", "1");
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
//<editor-fold desc="InstallOption">
|
||||||
|
public static class InstallOption {
|
||||||
|
private final StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
InstallOption(String ... varargs) {
|
||||||
|
String suffix = "";
|
||||||
|
for(String str: varargs) {
|
||||||
|
stringBuilder.append(suffix).append(str);
|
||||||
|
suffix = " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getStringRepresentation() {
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final InstallOption WITH_FORWARD_LOCK = new InstallOption("-l");
|
||||||
|
|
||||||
|
public static final InstallOption REINSTALL_KEEPING_DATA =
|
||||||
|
new InstallOption("-r");
|
||||||
|
|
||||||
|
public static final InstallOption ALLOW_TEST_APK =
|
||||||
|
new InstallOption("-t");
|
||||||
|
|
||||||
|
@SuppressWarnings("squid:S00100")
|
||||||
|
public static InstallOption WITH_INSTALLER_PACKAGE_NAME(String name)
|
||||||
|
{
|
||||||
|
return new InstallOption("-t", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("squid:S00100")
|
||||||
|
public static InstallOption ON_SHARED_MASS_STORAGE(String name) {
|
||||||
|
return new InstallOption("-s", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("squid:S00100")
|
||||||
|
public static InstallOption ON_INTERNAL_SYSTEM_MEMORY(String name) {
|
||||||
|
return new InstallOption("-f", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final InstallOption ALLOW_VERSION_DOWNGRADE =
|
||||||
|
new InstallOption("-d");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This option is supported only from Android 6.X+
|
||||||
|
*/
|
||||||
|
public static final InstallOption GRANT_ALL_PERMISSIONS = new InstallOption("-g");
|
||||||
|
|
||||||
|
//</editor-fold>
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.xypower.wpywapp.jadb.managers;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import com.xypower.wpywapp.jadb.JadbDevice;
|
||||||
|
import com.xypower.wpywapp.jadb.JadbException;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class which works with properties, uses getprop and setprop methods of android shell
|
||||||
|
*/
|
||||||
|
public class PropertyManager {
|
||||||
|
private final Pattern pattern = Pattern.compile("^\\[([a-zA-Z0-9_.-]*)]:.\\[([^\\[\\]]*)]");
|
||||||
|
private final JadbDevice device;
|
||||||
|
|
||||||
|
public PropertyManager(JadbDevice device) {
|
||||||
|
this.device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getprop() throws IOException, JadbException {
|
||||||
|
try (BufferedReader bufferedReader =
|
||||||
|
new BufferedReader(new InputStreamReader(device.executeShell("getprop"), StandardCharsets.UTF_8))) {
|
||||||
|
return parseProp(bufferedReader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> parseProp(BufferedReader bufferedReader) throws IOException {
|
||||||
|
HashMap<String, String> result = new HashMap<>();
|
||||||
|
|
||||||
|
String line;
|
||||||
|
Matcher matcher = pattern.matcher("");
|
||||||
|
|
||||||
|
while ((line = bufferedReader.readLine()) != null) {
|
||||||
|
matcher.reset(line);
|
||||||
|
|
||||||
|
if (matcher.find()) {
|
||||||
|
if (matcher.groupCount() < 2) {
|
||||||
|
System.err.println("Property line: " + line + " does not match pattern. Ignoring");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String key = matcher.group(1);
|
||||||
|
String value = matcher.group(2);
|
||||||
|
result.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.xypower.wpywapp.jadb.server;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import com.xypower.wpywapp.jadb.JadbException;
|
||||||
|
import com.xypower.wpywapp.jadb.RemoteFile;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by vidstige on 20/03/14.
|
||||||
|
*/
|
||||||
|
public interface AdbDeviceResponder {
|
||||||
|
String getSerial();
|
||||||
|
String getType();
|
||||||
|
|
||||||
|
void filePushed(RemoteFile path, int mode, ByteArrayOutputStream buffer) throws JadbException;
|
||||||
|
void filePulled(RemoteFile path, ByteArrayOutputStream buffer) throws JadbException, IOException;
|
||||||
|
|
||||||
|
void shell(String command, DataOutputStream stdout, DataInput stdin) throws IOException;
|
||||||
|
void enableIpCommand(String ip, DataOutputStream outputStream) throws IOException;
|
||||||
|
|
||||||
|
List<RemoteFile> list(String path) throws IOException;
|
||||||
|
}
|
@ -0,0 +1,248 @@
|
|||||||
|
package com.xypower.wpywapp.jadb.server;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import com.xypower.wpywapp.jadb.JadbException;
|
||||||
|
import com.xypower.wpywapp.jadb.RemoteFile;
|
||||||
|
import com.xypower.wpywapp.jadb.SyncTransport;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.ProtocolException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
class AdbProtocolHandler implements Runnable {
|
||||||
|
private final Socket socket;
|
||||||
|
private final AdbResponder responder;
|
||||||
|
private AdbDeviceResponder selected;
|
||||||
|
|
||||||
|
public AdbProtocolHandler(Socket socket, AdbResponder responder) {
|
||||||
|
this.socket = socket;
|
||||||
|
this.responder = responder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdbDeviceResponder findDevice(String serial) throws ProtocolException {
|
||||||
|
for (AdbDeviceResponder d : responder.getDevices()) {
|
||||||
|
if (d.getSerial().equals(serial)) return d;
|
||||||
|
}
|
||||||
|
throw new ProtocolException("'" + serial + "' not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
runServer();
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (e.getMessage() != null) // thrown when exiting for some reason
|
||||||
|
System.out.println("IO Error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runServer() throws IOException {
|
||||||
|
try (
|
||||||
|
DataInputStream input = new DataInputStream(socket.getInputStream());
|
||||||
|
DataOutputStream output = new DataOutputStream(socket.getOutputStream())
|
||||||
|
) {
|
||||||
|
//noinspection StatementWithEmptyBody
|
||||||
|
while (processCommand(input, output)) {
|
||||||
|
// nothing to do here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processCommand(DataInput input, DataOutputStream output) throws IOException {
|
||||||
|
String command = readCommand(input);
|
||||||
|
responder.onCommand(command);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ("host:version".equals(command)) {
|
||||||
|
hostVersion(output);
|
||||||
|
} else if ("host:transport-any".equals(command)) {
|
||||||
|
hostTransportAny(output);
|
||||||
|
} else if ("host:devices".equals(command)) {
|
||||||
|
hostDevices(output);
|
||||||
|
} else if (command.startsWith("host:transport:")) {
|
||||||
|
hostTransport(output, command);
|
||||||
|
} else if ("sync:".equals(command)) {
|
||||||
|
sync(output, input);
|
||||||
|
} else if (command.startsWith("shell:")) {
|
||||||
|
shell(input, output, command);
|
||||||
|
return false;
|
||||||
|
} else if ("host:get-state".equals(command)) {
|
||||||
|
hostGetState(output);
|
||||||
|
} else if (command.startsWith("host-serial:")) {
|
||||||
|
hostSerial(output, command);
|
||||||
|
} else if (command.startsWith("tcpip:")) {
|
||||||
|
handleTcpip(output, command);
|
||||||
|
} else {
|
||||||
|
throw new ProtocolException("Unknown command: " + command);
|
||||||
|
}
|
||||||
|
} catch (ProtocolException e) {
|
||||||
|
output.writeBytes("FAIL");
|
||||||
|
send(output, e.getMessage());
|
||||||
|
}
|
||||||
|
output.flush();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTcpip(DataOutputStream output, String command) throws IOException {
|
||||||
|
output.writeBytes("OKAY");
|
||||||
|
selected.enableIpCommand(command.substring("tcpip:".length()), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hostSerial(DataOutput output, String command) throws IOException {
|
||||||
|
String[] strs = command.split(":",0);
|
||||||
|
if (strs.length != 3) {
|
||||||
|
throw new ProtocolException("Invalid command: " + command);
|
||||||
|
}
|
||||||
|
|
||||||
|
String serial = strs[1];
|
||||||
|
boolean found = false;
|
||||||
|
output.writeBytes("OKAY");
|
||||||
|
for (AdbDeviceResponder d : responder.getDevices()) {
|
||||||
|
if (d.getSerial().equals(serial)) {
|
||||||
|
send(output, d.getType());
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
send(output, "unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hostGetState(DataOutput output) throws IOException {
|
||||||
|
// TODO: Check so that exactly one device is selected.
|
||||||
|
AdbDeviceResponder device = responder.getDevices().get(0);
|
||||||
|
output.writeBytes("OKAY");
|
||||||
|
send(output, device.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shell(DataInput input, DataOutputStream output, String command) throws IOException {
|
||||||
|
String shellCommand = command.substring("shell:".length());
|
||||||
|
output.writeBytes("OKAY");
|
||||||
|
shell(shellCommand, output, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hostTransport(DataOutput output, String command) throws IOException {
|
||||||
|
String serial = command.substring("host:transport:".length());
|
||||||
|
selected = findDevice(serial);
|
||||||
|
output.writeBytes("OKAY");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hostDevices(DataOutput output) throws IOException {
|
||||||
|
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream writer = new DataOutputStream(tmp);
|
||||||
|
for (AdbDeviceResponder d : responder.getDevices()) {
|
||||||
|
writer.writeBytes(d.getSerial() + "\t" + d.getType() + "\n");
|
||||||
|
}
|
||||||
|
output.writeBytes("OKAY");
|
||||||
|
send(output, new String(tmp.toByteArray(), StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hostTransportAny(DataOutput output) throws IOException {
|
||||||
|
// TODO: Check so that exactly one device is selected.
|
||||||
|
selected = responder.getDevices().get(0);
|
||||||
|
output.writeBytes("OKAY");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hostVersion(DataOutput output) throws IOException {
|
||||||
|
output.writeBytes("OKAY");
|
||||||
|
send(output, String.format("%04x", responder.getVersion()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shell(String command, DataOutputStream stdout, DataInput stdin) throws IOException {
|
||||||
|
selected.shell(command, stdout, stdin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readInt(DataInput input) throws IOException {
|
||||||
|
return Integer.reverseBytes(input.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readHexInt(DataInput input) throws IOException {
|
||||||
|
return Integer.parseInt(readString(input, 4), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readString(DataInput input, int length) throws IOException {
|
||||||
|
byte[] responseBuffer = new byte[length];
|
||||||
|
input.readFully(responseBuffer);
|
||||||
|
return new String(responseBuffer, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readCommand(DataInput input) throws IOException {
|
||||||
|
int length = readHexInt(input);
|
||||||
|
return readString(input, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sync(DataOutput output, DataInput input) throws IOException {
|
||||||
|
output.writeBytes("OKAY");
|
||||||
|
try {
|
||||||
|
String id = readString(input, 4);
|
||||||
|
int length = readInt(input);
|
||||||
|
switch (id) {
|
||||||
|
case "SEND":
|
||||||
|
syncSend(output, input, length);
|
||||||
|
break;
|
||||||
|
case "RECV":
|
||||||
|
syncRecv(output, input, length);
|
||||||
|
break;
|
||||||
|
case "LIST":
|
||||||
|
syncList(output, input, length);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new JadbException("Unknown sync id " + id);
|
||||||
|
}
|
||||||
|
} catch (JadbException e) { // sync response with a different type of fail message
|
||||||
|
SyncTransport sync = getSyncTransport(output, input);
|
||||||
|
sync.send("FAIL", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncRecv(DataOutput output, DataInput input, int length) throws IOException, JadbException {
|
||||||
|
String remotePath = readString(input, length);
|
||||||
|
SyncTransport transport = getSyncTransport(output, input);
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
selected.filePulled(new RemoteFile(remotePath), buffer);
|
||||||
|
transport.sendStream(new ByteArrayInputStream(buffer.toByteArray()));
|
||||||
|
transport.sendStatus("DONE", 0); // ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncSend(DataOutput output, DataInput input, int length) throws IOException, JadbException {
|
||||||
|
String remotePath = readString(input, length);
|
||||||
|
int idx = remotePath.lastIndexOf(',');
|
||||||
|
String path = remotePath;
|
||||||
|
int mode = 0666;
|
||||||
|
if (idx > 0) {
|
||||||
|
path = remotePath.substring(0, idx);
|
||||||
|
mode = Integer.parseInt(remotePath.substring(idx + 1));
|
||||||
|
}
|
||||||
|
SyncTransport transport = getSyncTransport(output, input);
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
transport.readChunksTo(buffer);
|
||||||
|
selected.filePushed(new RemoteFile(path), mode, buffer);
|
||||||
|
transport.sendStatus("OKAY", 0); // 0 = ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncList(DataOutput output, DataInput input, int length) throws IOException, JadbException {
|
||||||
|
String remotePath = readString(input, length);
|
||||||
|
SyncTransport transport = getSyncTransport(output, input);
|
||||||
|
for (RemoteFile file : selected.list(remotePath)) {
|
||||||
|
transport.sendDirectoryEntry(file);
|
||||||
|
}
|
||||||
|
transport.sendDirectoryEntryDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCommandLength(String command) {
|
||||||
|
return String.format("%04x", command.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(DataOutput writer, String response) throws IOException {
|
||||||
|
writer.writeBytes(getCommandLength(response));
|
||||||
|
writer.writeBytes(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SyncTransport getSyncTransport(DataOutput output, DataInput input) {
|
||||||
|
return new SyncTransport(output, input);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.xypower.wpywapp.jadb.server;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by vidstige on 20/03/14.
|
||||||
|
*/
|
||||||
|
public interface AdbResponder {
|
||||||
|
void onCommand(String command);
|
||||||
|
|
||||||
|
int getVersion();
|
||||||
|
|
||||||
|
List<AdbDeviceResponder> getDevices();
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.xypower.wpywapp.jadb.server;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by vidstige on 2014-03-20
|
||||||
|
*/
|
||||||
|
public class AdbServer extends SocketServer {
|
||||||
|
|
||||||
|
public static final int DEFAULT_PORT = 15037;
|
||||||
|
private final AdbResponder responder;
|
||||||
|
|
||||||
|
public AdbServer(AdbResponder responder) {
|
||||||
|
this(responder, DEFAULT_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdbServer(AdbResponder responder, int port) {
|
||||||
|
super(port);
|
||||||
|
this.responder = responder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Runnable createResponder(Socket socket) {
|
||||||
|
return new AdbProtocolHandler(socket, responder);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package com.xypower.wpywapp.jadb.server;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
// >set ANDROID_ADB_SERVER_PORT=15037
|
||||||
|
public abstract class SocketServer implements Runnable {
|
||||||
|
|
||||||
|
private final int port;
|
||||||
|
private ServerSocket socket;
|
||||||
|
private Thread thread;
|
||||||
|
|
||||||
|
private boolean isStarted = false;
|
||||||
|
private final Object lockObject = new Object();
|
||||||
|
|
||||||
|
protected SocketServer(int port) {
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws InterruptedException {
|
||||||
|
thread = new Thread(this, "Fake Adb Server");
|
||||||
|
thread.setDaemon(true);
|
||||||
|
thread.start();
|
||||||
|
waitForServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("squid:S2189") // server is stopped by closing SocketServer
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
socket = new ServerSocket(port);
|
||||||
|
socket.setReuseAddress(true);
|
||||||
|
|
||||||
|
serverReady();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
Socket c = socket.accept();
|
||||||
|
createResponder(c).run();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Empty on purpose
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void serverReady() {
|
||||||
|
synchronized (lockObject) {
|
||||||
|
isStarted = true;
|
||||||
|
lockObject.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForServer() throws InterruptedException {
|
||||||
|
synchronized (lockObject) {
|
||||||
|
while (!isStarted) {
|
||||||
|
lockObject.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Runnable createResponder(Socket socket);
|
||||||
|
|
||||||
|
public void stop() throws IOException, InterruptedException {
|
||||||
|
socket.close();
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
/build
|
@ -0,0 +1,60 @@
|
|||||||
|
package com.xypower.wpywapp.libadb
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'com.android.library'
|
||||||
|
id 'maven-publish'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'io.github.muntashirakon'
|
||||||
|
version = '3.0.0'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk 34
|
||||||
|
namespace "io.github.muntashirakon.adb"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk 1
|
||||||
|
targetSdk 34
|
||||||
|
aarMetadata {
|
||||||
|
minCompileSdk = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
singleVariant("release") {
|
||||||
|
withSourcesJar()
|
||||||
|
withJavadocJar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
release(MavenPublication) {
|
||||||
|
artifactId = 'libadb'
|
||||||
|
afterEvaluate {
|
||||||
|
from components.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "androidx.annotation:annotation:1.7.1"
|
||||||
|
implementation 'org.bouncycastle:bcprov-jdk15to18:1.78'
|
||||||
|
implementation 'com.github.MuntashirAkon.spake2-java:android:2.0.0'
|
||||||
|
|
||||||
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
<!-- SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0 -->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
</manifest>
|
@ -0,0 +1,509 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.CallSuper;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.android.AdbMdns;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import javax.security.auth.DestroyFailedException;
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public abstract class AbsAdbConnectionManager implements Closeable {
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
@Nullable
|
||||||
|
private AdbConnection mAdbConnection;
|
||||||
|
private String mHostAddress = "127.0.0.1";
|
||||||
|
private int mApi = Build.VERSION_CODES.P;
|
||||||
|
private long mTimeout = Long.MAX_VALUE;
|
||||||
|
private TimeUnit mTimeoutUnit = TimeUnit.MILLISECONDS;
|
||||||
|
private boolean mThrowOnUnauthorised = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return generated/stored private key.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
protected abstract PrivateKey getPrivateKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return public key wrapped around a certificate.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
protected abstract Certificate getCertificate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a name for the device. This can be the app label, hostname or user@hostname.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
protected abstract String getDeviceName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set host address for this connection. On the same device, this should be {@code 127.0.0.1}.
|
||||||
|
*/
|
||||||
|
@CallSuper
|
||||||
|
public void setHostAddress(@NonNull String hostAddress) {
|
||||||
|
mHostAddress = Objects.requireNonNull(hostAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get host address for this connection. Default value is {@code 127.0.0.1}.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public String getHostAddress() {
|
||||||
|
return mHostAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Android API (i.e. SDK) version for this connection. If the daemon and the client are located in the same
|
||||||
|
* directory, the value should be {@link Build.VERSION#SDK_INT} in order to improve performance as well as security.
|
||||||
|
*
|
||||||
|
* @param api The API version, default is {@link Build.VERSION_CODES#BASE}.
|
||||||
|
*/
|
||||||
|
public void setApi(int api) {
|
||||||
|
this.mApi = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Android API (i.e. SDK) version for this connection. Default value is {@link Build.VERSION_CODES#BASE}.
|
||||||
|
*/
|
||||||
|
public int getApi() {
|
||||||
|
return mApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set time to wait for the connection to be made.
|
||||||
|
*
|
||||||
|
* @param timeout Timeout value
|
||||||
|
* @param unit Timeout unit
|
||||||
|
*/
|
||||||
|
@CallSuper
|
||||||
|
public void setTimeout(long timeout, TimeUnit unit) {
|
||||||
|
mTimeout = timeout;
|
||||||
|
mTimeoutUnit = unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get time to wait for the connection to be made. If not set using {@link #setTimeout(long, TimeUnit)}, the default
|
||||||
|
* timeout is {@link Long#MAX_VALUE} milliseconds.
|
||||||
|
*
|
||||||
|
* @return Timeout in milliseconds
|
||||||
|
*/
|
||||||
|
public long getTimeout() {
|
||||||
|
return mTimeoutUnit.toMillis(mTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unit for the timeout. If not set using {@link #setTimeout(long, TimeUnit)}, the default timeout unit is
|
||||||
|
* {@link TimeUnit#MILLISECONDS}.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public TimeUnit getTimeoutUnit() {
|
||||||
|
return mTimeoutUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to throw {@link AdbAuthenticationFailedException} if the daemon rejects the first authentication
|
||||||
|
* attempt.
|
||||||
|
*
|
||||||
|
* @param throwOnUnauthorised {@code true} to throw {@link AdbAuthenticationFailedException} or {@code false}
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
@CallSuper
|
||||||
|
public void setThrowOnUnauthorised(boolean throwOnUnauthorised) {
|
||||||
|
mThrowOnUnauthorised = throwOnUnauthorised;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether to throw {@link AdbAuthenticationFailedException} if the daemon rejects the first authentication
|
||||||
|
* attempt.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the system is configured to throw {@link AdbAuthenticationFailedException} or
|
||||||
|
* {@code false} otherwise. The default value is {@code false}.
|
||||||
|
*/
|
||||||
|
public boolean isThrowOnUnauthorised() {
|
||||||
|
return mThrowOnUnauthorised;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link AdbConnection} backed by this object.
|
||||||
|
*
|
||||||
|
* @return Underlying {@link AdbConnection}, or {@code null} if the connection hasn't been made yet.
|
||||||
|
*/
|
||||||
|
@CallSuper
|
||||||
|
@Nullable
|
||||||
|
public AdbConnection getAdbConnection() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
return mAdbConnection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if it is connected to an ADB daemon.
|
||||||
|
*
|
||||||
|
* @return {@code true} if connected, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isConnected() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
return mAdbConnection != null && mAdbConnection.isConnected() && mAdbConnection.isConnectionEstablished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to connect to ADB by performing an automatic network discovery of TLS host and port. Host address set by
|
||||||
|
* {@link #setHostAddress(String)} is ignored.
|
||||||
|
*
|
||||||
|
* @param context Application context
|
||||||
|
* @param timeoutMillis Amount of time spent in searching for a host and a port.
|
||||||
|
* @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection
|
||||||
|
* attempt is unsuccessful, or it has already been made.
|
||||||
|
* @throws IOException If the socket connection could not be made.
|
||||||
|
* @throws InterruptedException If timeout has reached.
|
||||||
|
* @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the ADB
|
||||||
|
* daemon has rejected the first authentication attempt, which indicates
|
||||||
|
* that the daemon has not saved the public key from a previous connection.
|
||||||
|
* @throws AdbPairingRequiredException If ADB lacks pairing
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
public boolean connectTls(@NonNull Context context, long timeoutMillis)
|
||||||
|
throws IOException, InterruptedException, AdbPairingRequiredException {
|
||||||
|
return autoConnect(context, AdbMdns.SERVICE_TYPE_TLS_CONNECT, timeoutMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to connect to ADB by performing an automatic network discovery of TCP host and port. Host address set by
|
||||||
|
* {@link #setHostAddress(String)} is ignored.
|
||||||
|
*
|
||||||
|
* @param context Application context
|
||||||
|
* @param timeoutMillis Amount of time spent in searching for a host and a port.
|
||||||
|
* @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection
|
||||||
|
* attempt is unsuccessful, or it has already been made.
|
||||||
|
* @throws IOException If the socket connection could not be made.
|
||||||
|
* @throws InterruptedException If timeout has reached.
|
||||||
|
* @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the ADB
|
||||||
|
* daemon has rejected the first authentication attempt, which indicates
|
||||||
|
* that the daemon has not saved the public key from a previous connection.
|
||||||
|
* @throws AdbPairingRequiredException If ADB lacks pairing
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
public boolean connectTcp(@NonNull Context context, long timeoutMillis)
|
||||||
|
throws IOException, InterruptedException, AdbPairingRequiredException {
|
||||||
|
return autoConnect(context, AdbMdns.SERVICE_TYPE_ADB, timeoutMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to connect to ADB by performing an automatic network discovery of host and port. Host address set by
|
||||||
|
* {@link #setHostAddress(String)} is ignored.
|
||||||
|
*
|
||||||
|
* @param context Application context
|
||||||
|
* @param timeoutMillis Amount of time spent in searching for a host and a port.
|
||||||
|
* @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection
|
||||||
|
* attempt is unsuccessful, or it has already been made.
|
||||||
|
* @throws IOException If the socket connection could not be made.
|
||||||
|
* @throws InterruptedException If timeout has reached.
|
||||||
|
* @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the ADB
|
||||||
|
* daemon has rejected the first authentication attempt, which indicates
|
||||||
|
* that the daemon has not saved the public key from a previous connection.
|
||||||
|
* @throws AdbPairingRequiredException If ADB lacks pairing
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
public boolean autoConnect(@NonNull Context context, long timeoutMillis)
|
||||||
|
throws IOException, InterruptedException, AdbPairingRequiredException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
AtomicInteger atomicPort = new AtomicInteger(-1);
|
||||||
|
AtomicReference<String> atomicHostAddress = new AtomicReference<>(null);
|
||||||
|
CountDownLatch resolveHostAndPort = new CountDownLatch(1);
|
||||||
|
|
||||||
|
AdbMdns adbMdnsTcp = new AdbMdns(context, AdbMdns.SERVICE_TYPE_ADB, (hostAddress, port) -> {
|
||||||
|
if (hostAddress != null) {
|
||||||
|
atomicHostAddress.set(hostAddress.getHostAddress());
|
||||||
|
atomicPort.set(port);
|
||||||
|
}
|
||||||
|
resolveHostAndPort.countDown();
|
||||||
|
});
|
||||||
|
adbMdnsTcp.start();
|
||||||
|
|
||||||
|
AdbMdns adbMdnsTls = new AdbMdns(context, AdbMdns.SERVICE_TYPE_TLS_CONNECT, (hostAddress, port) -> {
|
||||||
|
if (hostAddress != null) {
|
||||||
|
atomicHostAddress.set(hostAddress.getHostAddress());
|
||||||
|
atomicPort.set(port);
|
||||||
|
}
|
||||||
|
resolveHostAndPort.countDown();
|
||||||
|
});
|
||||||
|
adbMdnsTls.start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!resolveHostAndPort.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
|
||||||
|
throw new InterruptedException("Timed out while trying to find a valid host address and port");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
adbMdnsTcp.stop();
|
||||||
|
adbMdnsTls.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
String host = atomicHostAddress.get();
|
||||||
|
int port = atomicPort.get();
|
||||||
|
|
||||||
|
if (host == null || port == -1) {
|
||||||
|
throw new IOException("Could not find any valid host address or port");
|
||||||
|
}
|
||||||
|
|
||||||
|
mHostAddress = host;
|
||||||
|
mAdbConnection = new AdbConnection.Builder(host, port)
|
||||||
|
.setApi(mApi)
|
||||||
|
.setKeyPair(getAdbKeyPair())
|
||||||
|
.setDeviceName(Objects.requireNonNull(getDeviceName()))
|
||||||
|
.build();
|
||||||
|
return mAdbConnection.connect(mTimeout, mTimeoutUnit, mThrowOnUnauthorised);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
private boolean autoConnect(@NonNull Context context, @AdbMdns.ServiceType @NonNull String serviceType, long timeoutMillis)
|
||||||
|
throws IOException, InterruptedException, AdbPairingRequiredException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
AtomicInteger atomicPort = new AtomicInteger(-1);
|
||||||
|
AtomicReference<String> atomicHostAddress = new AtomicReference<>(null);
|
||||||
|
CountDownLatch resolveHostAndPort = new CountDownLatch(1);
|
||||||
|
|
||||||
|
AdbMdns adbMdns = new AdbMdns(context, serviceType, (hostAddress, port) -> {
|
||||||
|
if (hostAddress != null) {
|
||||||
|
atomicHostAddress.set(hostAddress.getHostAddress());
|
||||||
|
atomicPort.set(port);
|
||||||
|
}
|
||||||
|
resolveHostAndPort.countDown();
|
||||||
|
});
|
||||||
|
adbMdns.start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!resolveHostAndPort.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
|
||||||
|
throw new InterruptedException("Timed out while trying to find a valid host address and port");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
adbMdns.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
String host = atomicHostAddress.get();
|
||||||
|
int port = atomicPort.get();
|
||||||
|
|
||||||
|
if (host == null || port == -1) {
|
||||||
|
throw new IOException("Could not find any valid host address or port");
|
||||||
|
}
|
||||||
|
|
||||||
|
mHostAddress = host;
|
||||||
|
mAdbConnection = new AdbConnection.Builder(host, port)
|
||||||
|
.setApi(mApi)
|
||||||
|
.setKeyPair(getAdbKeyPair())
|
||||||
|
.setDeviceName(Objects.requireNonNull(getDeviceName()))
|
||||||
|
.build();
|
||||||
|
return mAdbConnection.connect(mTimeout, mTimeoutUnit, mThrowOnUnauthorised);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to connect to ADB given a port number. Host address is set via {@link #setHostAddress(String)}.
|
||||||
|
*
|
||||||
|
* @param port Port number
|
||||||
|
* @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection
|
||||||
|
* attempt is unsuccessful, or it has already been made.
|
||||||
|
* @throws IOException If the socket connection could not be made.
|
||||||
|
* @throws InterruptedException If timeout has reached.
|
||||||
|
* @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the ADB
|
||||||
|
* daemon has rejected the first authentication attempt, which indicates
|
||||||
|
* that the daemon has not saved the public key from a previous connection.
|
||||||
|
* @throws AdbPairingRequiredException If ADB lacks pairing
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
public boolean connect(int port) throws IOException, InterruptedException, AdbPairingRequiredException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (isConnected()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mAdbConnection = new AdbConnection.Builder(mHostAddress, port)
|
||||||
|
.setApi(mApi)
|
||||||
|
.setKeyPair(getAdbKeyPair())
|
||||||
|
.setDeviceName(Objects.requireNonNull(getDeviceName()))
|
||||||
|
.build();
|
||||||
|
return mAdbConnection.connect(mTimeout, mTimeoutUnit, mThrowOnUnauthorised);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to connect to ADB via a host address and a port number.
|
||||||
|
*
|
||||||
|
* @param host Host address to use instead of taking it from the {@link #getHostAddress()}
|
||||||
|
* @param port Port number
|
||||||
|
* @return {@code true} if and only if the connection is successful. It returns {@code false} if the connection
|
||||||
|
* attempt is unsuccessful, or it has already been made.
|
||||||
|
* @throws IOException If the socket connection could not be made.
|
||||||
|
* @throws InterruptedException If timeout has reached.
|
||||||
|
* @throws AdbAuthenticationFailedException If {@link #isThrowOnUnauthorised()} is set to {@code true}, and the
|
||||||
|
* ADB daemon has rejected the first authentication attempt, which
|
||||||
|
* indicates that the daemon has not saved the public key from a previous
|
||||||
|
* connection.
|
||||||
|
* @throws AdbPairingRequiredException If ADB lacks pairing
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
public boolean connect(@NonNull String host, int port)
|
||||||
|
throws IOException, InterruptedException, AdbPairingRequiredException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (isConnected()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mHostAddress = host;
|
||||||
|
mAdbConnection = new AdbConnection.Builder(host, port)
|
||||||
|
.setApi(mApi)
|
||||||
|
.setKeyPair(getAdbKeyPair())
|
||||||
|
.setDeviceName(Objects.requireNonNull(getDeviceName()))
|
||||||
|
.build();
|
||||||
|
return mAdbConnection.connect(mTimeout, mTimeoutUnit, mThrowOnUnauthorised);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect the underlying {@link AdbConnection}.
|
||||||
|
*
|
||||||
|
* @throws IOException If the underlying socket fails to close
|
||||||
|
*/
|
||||||
|
public void disconnect() throws IOException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mAdbConnection != null) {
|
||||||
|
mAdbConnection.close();
|
||||||
|
mAdbConnection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens an {@link AdbStream} object corresponding to the specified destination.
|
||||||
|
* This routine will block until the connection completes.
|
||||||
|
*
|
||||||
|
* @param destination The destination to open on the target
|
||||||
|
* @return {@link AdbStream} object corresponding to the specified destination
|
||||||
|
* @throws IOException If the steam fails or no connection has been made
|
||||||
|
* @throws InterruptedException If the stream fails while sending the packet
|
||||||
|
* @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8.
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
@NonNull
|
||||||
|
public AdbStream openStream(String destination) throws IOException, InterruptedException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mAdbConnection != null && mAdbConnection.isConnected()) {
|
||||||
|
try {
|
||||||
|
return mAdbConnection.open(destination);
|
||||||
|
} catch (AdbPairingRequiredException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IOException("Not connected to ADB.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens an {@link AdbStream} object corresponding to the specified destination.
|
||||||
|
* This routine will block until the connection completes.
|
||||||
|
*
|
||||||
|
* @param service The service to open. One of the services under {@link LocalServices.Services}.
|
||||||
|
* @param args Additional arguments supported by the service (see the corresponding constant to learn more).
|
||||||
|
* @return AdbStream object corresponding to the specified destination
|
||||||
|
* @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8
|
||||||
|
* @throws IOException If the stream fails while sending the packet
|
||||||
|
* @throws InterruptedException If we are unable to wait for the connection to finish
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public AdbStream openStream(@LocalServices.Services int service, @NonNull String... args)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mAdbConnection != null && mAdbConnection.isConnected()) {
|
||||||
|
try {
|
||||||
|
return mAdbConnection.open(service, args);
|
||||||
|
} catch (AdbPairingRequiredException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IOException("Not connected to ADB.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pair with an ADB daemon given port number and pairing code.
|
||||||
|
*
|
||||||
|
* @param port Port number
|
||||||
|
* @param pairingCode The six-digit pairing code as string
|
||||||
|
* @return {@code true} if the pairing is successful and {@code false} otherwise.
|
||||||
|
* @throws Exception If pairing failed for some reason.
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
@RequiresApi(Build.VERSION_CODES.GINGERBREAD)
|
||||||
|
public boolean pair(int port, @NonNull String pairingCode) throws Exception {
|
||||||
|
return pair(mHostAddress, port, pairingCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pair with an ADB daemon given host address, port number and pairing code.
|
||||||
|
*
|
||||||
|
* @param host Host address to use instead of taking it from the {@link #getHostAddress()}
|
||||||
|
* @param port Port number
|
||||||
|
* @param pairingCode The six-digit pairing code as string
|
||||||
|
* @return {@code true} if the pairing is successful and {@code false} otherwise.
|
||||||
|
* @throws Exception If pairing failed for some reason.
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
@RequiresApi(Build.VERSION_CODES.GINGERBREAD)
|
||||||
|
public boolean pair(@NonNull String host, int port, @NonNull String pairingCode) throws Exception {
|
||||||
|
synchronized (mLock) {
|
||||||
|
KeyPair keyPair = getAdbKeyPair();
|
||||||
|
try (PairingConnectionCtx pairingClient = new PairingConnectionCtx(Objects.requireNonNull(host), port,
|
||||||
|
StringCompat.getBytes(Objects.requireNonNull(pairingCode), "UTF-8"), keyPair, getDeviceName())) {
|
||||||
|
// TODO: 5/12/21 Return true/false instead of only exceptions
|
||||||
|
pairingClient.start();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the underlying {@link AdbConnection} and destroy the private key.
|
||||||
|
*
|
||||||
|
* @throws IOException If socket fails to close.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
try {
|
||||||
|
getPrivateKey().destroy();
|
||||||
|
} catch (DestroyFailedException | NoSuchMethodError e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (mAdbConnection != null) {
|
||||||
|
mAdbConnection.close();
|
||||||
|
mAdbConnection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private KeyPair getAdbKeyPair() {
|
||||||
|
return new KeyPair(Objects.requireNonNull(getPrivateKey()), Objects.requireNonNull(getCertificate()));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-License-Identifier: BSD-3-Clause AND (GPL-3.0-or-later OR Apache-2.0)
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when the ADB daemon rejects our initial authentication attempt, which typically means that the peer has not
|
||||||
|
* previously saved our public key.
|
||||||
|
*/
|
||||||
|
// Copyright 2020 Sam Palmer
|
||||||
|
public class AdbAuthenticationFailedException extends RuntimeException {
|
||||||
|
public AdbAuthenticationFailedException() {
|
||||||
|
super("Initial authentication attempt rejected by peer.");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,749 @@
|
|||||||
|
// SPDX-License-Identifier: BSD-3-Clause AND (GPL-3.0-or-later OR Apache-2.0)
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.GuardedBy;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import javax.security.auth.DestroyFailedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents an ADB connection.
|
||||||
|
*/
|
||||||
|
// Copyright 2013 Cameron Gutman
|
||||||
|
public class AdbConnection implements Closeable {
|
||||||
|
public static final String TAG = AdbConnection.class.getSimpleName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The underlying socket that this class uses to communicate with the target device.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private final Socket mSocket;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final String mHost;
|
||||||
|
|
||||||
|
private final int mPort;
|
||||||
|
|
||||||
|
private final int mApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last allocated local stream ID. The ID chosen for the next stream will be this value + 1.
|
||||||
|
*/
|
||||||
|
private int mLastLocalId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The input stream that this class uses to read from the socket.
|
||||||
|
*/
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@NonNull
|
||||||
|
private final InputStream mPlainInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The output stream that this class uses to read from the socket.
|
||||||
|
*/
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@NonNull
|
||||||
|
private final OutputStream mPlainOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The input stream that this class uses to read from the TLS socket.
|
||||||
|
*/
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private volatile InputStream mTlsInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The output stream that this class uses to read from the TLS socket.
|
||||||
|
*/
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@Nullable
|
||||||
|
private volatile OutputStream mTlsOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The backend thread that handles responding to ADB packets.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private final Thread mConnectionThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether a CNXN has been attempted.
|
||||||
|
*/
|
||||||
|
private volatile boolean mConnectAttempted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the connection thread should give up if the first authentication attempt fails.
|
||||||
|
*/
|
||||||
|
private volatile boolean mAbortOnUnauthorised;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the first authentication attempt failed and {@link #mAbortOnUnauthorised} was {@code true}.
|
||||||
|
*/
|
||||||
|
private volatile boolean mAuthorisationFailed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether a CNXN packet has been received from the peer.
|
||||||
|
*/
|
||||||
|
private volatile boolean mConnectionEstablished;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exceptions that occur in {@link #createConnectionThread()}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private volatile Exception mConnectionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the maximum amount data that can be sent to the remote peer.
|
||||||
|
* This is only valid after connect() returns successfully.
|
||||||
|
*/
|
||||||
|
private volatile int mMaxData;
|
||||||
|
|
||||||
|
private volatile int mProtocolVersion;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final KeyPair mKeyPair;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private volatile String mDeviceName = "Unknown Device";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether this connection has already sent a signed token.
|
||||||
|
*/
|
||||||
|
private volatile boolean mSentSignature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hash map of our opened streams indexed by local ID.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private final ConcurrentHashMap<Integer, AdbStream> mOpenedStreams;
|
||||||
|
|
||||||
|
private volatile boolean mIsTls = false;
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@NonNull
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a AdbConnection object associated with the socket and crypto object specified.
|
||||||
|
*
|
||||||
|
* @return A new AdbConnection object.
|
||||||
|
* @throws IOException If there is a socket error
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
@NonNull
|
||||||
|
public static AdbConnection create(@NonNull String host, int port, @NonNull PrivateKey privateKey,
|
||||||
|
@NonNull Certificate certificate)
|
||||||
|
throws IOException {
|
||||||
|
return create(host, port, privateKey, certificate, Build.VERSION_CODES.BASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a AdbConnection object associated with the socket and crypto object specified.
|
||||||
|
*
|
||||||
|
* @return A new AdbConnection object.
|
||||||
|
* @throws IOException If there is a socket error
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
@NonNull
|
||||||
|
public static AdbConnection create(@NonNull String host, int port, @NonNull PrivateKey privateKey,
|
||||||
|
@NonNull Certificate certificate, int api)
|
||||||
|
throws IOException {
|
||||||
|
return create(host, port, new KeyPair(Objects.requireNonNull(privateKey), Objects.requireNonNull(certificate)),
|
||||||
|
api);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a AdbConnection object associated with the socket and crypto object specified.
|
||||||
|
*
|
||||||
|
* @return A new AdbConnection object.
|
||||||
|
* @throws IOException If there is a socket error
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
@NonNull
|
||||||
|
static AdbConnection create(@NonNull String host, int port, @NonNull KeyPair keyPair, int api) throws IOException {
|
||||||
|
return new AdbConnection(host, port, keyPair, api);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor to initialize some internal state
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
private AdbConnection(@NonNull String host, int port, @NonNull KeyPair keyPair, int api) throws IOException {
|
||||||
|
this.mHost = Objects.requireNonNull(host);
|
||||||
|
this.mPort = port;
|
||||||
|
this.mApi = api;
|
||||||
|
this.mProtocolVersion = AdbProtocol.getProtocolVersion(mApi);
|
||||||
|
this.mMaxData = AdbProtocol.getMaxData(api);
|
||||||
|
this.mKeyPair = Objects.requireNonNull(keyPair);
|
||||||
|
try {
|
||||||
|
this.mSocket = new Socket(host, port);
|
||||||
|
} catch (Throwable th) {
|
||||||
|
//noinspection UnnecessaryInitCause
|
||||||
|
throw (IOException) new IOException().initCause(th);
|
||||||
|
}
|
||||||
|
this.mPlainInputStream = mSocket.getInputStream();
|
||||||
|
this.mPlainOutputStream = mSocket.getOutputStream();
|
||||||
|
|
||||||
|
// Disable Nagle because we're sending tiny packets
|
||||||
|
mSocket.setTcpNoDelay(true);
|
||||||
|
|
||||||
|
this.mOpenedStreams = new ConcurrentHashMap<>();
|
||||||
|
this.mLastLocalId = 0;
|
||||||
|
this.mConnectionThread = createConnectionThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@NonNull
|
||||||
|
private InputStream getInputStream() {
|
||||||
|
return mIsTls ? Objects.requireNonNull(mTlsInputStream) : mPlainInputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
@NonNull
|
||||||
|
private OutputStream getOutputStream() {
|
||||||
|
return mIsTls ? Objects.requireNonNull(mTlsOutputStream) : mPlainOutputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new connection thread.
|
||||||
|
*
|
||||||
|
* @return A new connection thread.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private Thread createConnectionThread() {
|
||||||
|
return new Thread(() -> {
|
||||||
|
loop:
|
||||||
|
while (!mConnectionThread.isInterrupted()) {
|
||||||
|
try {
|
||||||
|
// Read and parse a message off the socket's input stream
|
||||||
|
AdbProtocol.Message msg = AdbProtocol.Message.parse(getInputStream(), mProtocolVersion, mMaxData);
|
||||||
|
|
||||||
|
switch (msg.command) {
|
||||||
|
// Stream-oriented commands
|
||||||
|
case AdbProtocol.A_OKAY:
|
||||||
|
case AdbProtocol.A_WRTE:
|
||||||
|
case AdbProtocol.A_CLSE: {
|
||||||
|
// Ignore all packets when not connected
|
||||||
|
if (!mConnectionEstablished) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the stream object corresponding to the packet
|
||||||
|
AdbStream waitingStream = mOpenedStreams.get(msg.arg1);
|
||||||
|
if (waitingStream == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (waitingStream) {
|
||||||
|
if (msg.command == AdbProtocol.A_OKAY) {
|
||||||
|
// We're ready for writes
|
||||||
|
waitingStream.updateRemoteId(msg.arg0);
|
||||||
|
waitingStream.readyForWrite();
|
||||||
|
|
||||||
|
// Notify an open/write
|
||||||
|
waitingStream.notify();
|
||||||
|
} else if (msg.command == AdbProtocol.A_WRTE) {
|
||||||
|
// Got some data from our partner
|
||||||
|
waitingStream.addPayload(msg.payload);
|
||||||
|
|
||||||
|
// Tell it we're ready for more
|
||||||
|
waitingStream.sendReady();
|
||||||
|
} else { // if (msg.command == AdbProtocol.A_CLSE) {
|
||||||
|
mOpenedStreams.remove(msg.arg1);
|
||||||
|
// Notify readers and writers
|
||||||
|
waitingStream.notifyClose(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AdbProtocol.A_STLS: {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
|
||||||
|
sendPacket(AdbProtocol.generateStls());
|
||||||
|
|
||||||
|
SSLContext sslContext = SslUtils.getSslContext(mKeyPair);
|
||||||
|
SSLSocket tlsSocket = (SSLSocket) sslContext.getSocketFactory()
|
||||||
|
.createSocket(mSocket, mHost, mPort, true);
|
||||||
|
tlsSocket.startHandshake();
|
||||||
|
Log.d(TAG, "Handshake succeeded.");
|
||||||
|
|
||||||
|
synchronized (AdbConnection.this) {
|
||||||
|
mTlsInputStream = tlsSocket.getInputStream();
|
||||||
|
mTlsOutputStream = tlsSocket.getOutputStream();
|
||||||
|
mIsTls = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AdbProtocol.A_AUTH: {
|
||||||
|
if (mIsTls) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (msg.arg0 != AdbProtocol.ADB_AUTH_TOKEN) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
byte[] packet;
|
||||||
|
// This is an authentication challenge
|
||||||
|
if (mSentSignature) {
|
||||||
|
if (mAbortOnUnauthorised) {
|
||||||
|
mAuthorisationFailed = true;
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've already tried our signature, so send our public key
|
||||||
|
packet = AdbProtocol.generateAuth(AdbProtocol.ADB_AUTH_RSAPUBLICKEY, AndroidPubkey
|
||||||
|
.encodeWithName((RSAPublicKey) mKeyPair.getPublicKey(), mDeviceName));
|
||||||
|
} else {
|
||||||
|
// Sign the token
|
||||||
|
packet = AdbProtocol.generateAuth(AdbProtocol.ADB_AUTH_SIGNATURE, AndroidPubkey
|
||||||
|
.adbAuthSign(mKeyPair.getPrivateKey(), msg.payload));
|
||||||
|
mSentSignature = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the AUTH reply
|
||||||
|
sendPacket(packet);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AdbProtocol.A_CNXN: {
|
||||||
|
synchronized (AdbConnection.this) {
|
||||||
|
mProtocolVersion = msg.arg0;
|
||||||
|
mMaxData = msg.arg1;
|
||||||
|
mConnectionEstablished = true;
|
||||||
|
AdbConnection.this.notifyAll();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AdbProtocol.A_OPEN:
|
||||||
|
case AdbProtocol.A_SYNC:
|
||||||
|
default:
|
||||||
|
Log.e(TAG, String.format("Unrecognized command = 0x%x", msg.command));
|
||||||
|
// Unrecognized packet, just drop it
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
mConnectionException = e;
|
||||||
|
e.printStackTrace();
|
||||||
|
// The cleanup is taken care of by a combination of this thread and close()
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This thread takes care of cleaning up pending streams
|
||||||
|
synchronized (AdbConnection.this) {
|
||||||
|
cleanupStreams();
|
||||||
|
AdbConnection.this.notifyAll();
|
||||||
|
mConnectionEstablished = false;
|
||||||
|
mConnectAttempted = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a name for the device. Default is “Unknown Device”.
|
||||||
|
*
|
||||||
|
* @param deviceName Name of the device, could be the app label, hostname or user@hostname.
|
||||||
|
*/
|
||||||
|
public void setDeviceName(@NonNull String deviceName) {
|
||||||
|
this.mDeviceName = Objects.requireNonNull(deviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the version of the ADB protocol supported by the ADB daemon. The result may depend on the API version
|
||||||
|
* specified and whether the connection has been established. In API 29 (Android 9) or later, the daemon returns
|
||||||
|
* {@link AdbProtocol#A_VERSION_SKIP_CHECKSUM} regardless of the protocol used to create the connection. So, if
|
||||||
|
* {@link #mApi} is set to API 28 or earlier but the OS version is Android 9 or later, before establishing the
|
||||||
|
* connection, it returns {@link AdbProtocol#A_VERSION_MIN}, and after establishing the connection, it returns
|
||||||
|
* {@link AdbProtocol#A_VERSION_SKIP_CHECKSUM}. In other cases, it always returns {@link AdbProtocol#A_VERSION_MIN}.
|
||||||
|
*
|
||||||
|
* @see #isConnectionEstablished()
|
||||||
|
*/
|
||||||
|
public int getProtocolVersion() {
|
||||||
|
return mProtocolVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the max data size supported by the ADB daemon. A connection have to be attempted before calling this method
|
||||||
|
* and shall be blocked if the connection is in progress.
|
||||||
|
*
|
||||||
|
* @return The maximum data size indicated in the CONNECT packet.
|
||||||
|
* @throws InterruptedException If a connection cannot be waited on.
|
||||||
|
* @throws IOException if the connection fails.
|
||||||
|
* @throws AdbPairingRequiredException If ADB lacks pairing
|
||||||
|
*/
|
||||||
|
public int getMaxData() throws InterruptedException, IOException, AdbPairingRequiredException {
|
||||||
|
if (!mConnectAttempted) {
|
||||||
|
throw new IllegalStateException("connect() must be called first");
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForConnection(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
return mMaxData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a connection has been established. A connection has been established if a CONNECT request has been
|
||||||
|
* received from the ADB daemon.
|
||||||
|
*/
|
||||||
|
public boolean isConnectionEstablished() {
|
||||||
|
return mConnectionEstablished;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the underlying socket is connected to an ADB daemon and is not in a closed state.
|
||||||
|
*/
|
||||||
|
public boolean isConnected() {
|
||||||
|
return !mSocket.isClosed() && mSocket.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #connect(long, TimeUnit, boolean)} without throwing anything if the first authentication attempt
|
||||||
|
* fails.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the connection was established, or {@code false} if the connection timed out
|
||||||
|
* @throws IOException If the socket fails while connecting
|
||||||
|
* @throws InterruptedException If timeout has reached
|
||||||
|
* @throws AdbPairingRequiredException If ADB lacks pairing
|
||||||
|
*/
|
||||||
|
public boolean connect() throws IOException, InterruptedException, AdbPairingRequiredException {
|
||||||
|
return connect(Long.MAX_VALUE, TimeUnit.MILLISECONDS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to the remote device. This routine will block until the connection completes or the timeout elapses.
|
||||||
|
*
|
||||||
|
* @param timeout the time to wait for the lock
|
||||||
|
* @param unit the time unit of the timeout argument
|
||||||
|
* @param throwOnUnauthorised Whether to throw an {@link AdbAuthenticationFailedException}
|
||||||
|
* if the peer rejects out first authentication attempt
|
||||||
|
* @return {@code true} if the connection was established, or {@code false} if the connection timed out
|
||||||
|
* @throws IOException If the socket fails while connecting
|
||||||
|
* @throws InterruptedException If timeout has reached
|
||||||
|
* @throws AdbAuthenticationFailedException If {@code throwOnUnauthorised} is {@code true} and the peer rejects the
|
||||||
|
* first authentication attempt, which indicates that the peer has not
|
||||||
|
* saved the public key from a previous connection
|
||||||
|
* @throws AdbPairingRequiredException If ADB lacks pairing
|
||||||
|
*/
|
||||||
|
public boolean connect(long timeout, @NonNull TimeUnit unit, boolean throwOnUnauthorised)
|
||||||
|
throws IOException, InterruptedException, AdbAuthenticationFailedException, AdbPairingRequiredException {
|
||||||
|
if (mConnectionEstablished) {
|
||||||
|
throw new IllegalStateException("Already connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send CONNECT
|
||||||
|
sendPacket(AdbProtocol.generateConnect(mApi));
|
||||||
|
|
||||||
|
// Start the connection thread to respond to the peer
|
||||||
|
mConnectAttempted = true;
|
||||||
|
mAbortOnUnauthorised = throwOnUnauthorised;
|
||||||
|
mAuthorisationFailed = false;
|
||||||
|
mConnectionThread.start();
|
||||||
|
|
||||||
|
return waitForConnection(timeout, Objects.requireNonNull(unit));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens an {@link AdbStream} object corresponding to the specified destination.
|
||||||
|
* This routine will block until the connection completes.
|
||||||
|
*
|
||||||
|
* @param service The service to open. One of the services under {@link LocalServices.Services}.
|
||||||
|
* @param args Additional arguments supported by the service (see the corresponding constant to learn more).
|
||||||
|
* @return AdbStream object corresponding to the specified destination
|
||||||
|
* @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8
|
||||||
|
* @throws IOException If the stream fails while sending the packet
|
||||||
|
* @throws InterruptedException If we are unable to wait for the connection to finish
|
||||||
|
* @throws AdbPairingRequiredException If ADB lacks pairing
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public AdbStream open(@LocalServices.Services int service, @NonNull String... args)
|
||||||
|
throws IOException, InterruptedException, AdbPairingRequiredException {
|
||||||
|
if (service < LocalServices.SERVICE_FIRST || service > LocalServices.SERVICE_LAST) {
|
||||||
|
throw new IllegalArgumentException("Invalid service: " + service);
|
||||||
|
}
|
||||||
|
return open(LocalServices.getDestination(service, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens an AdbStream object corresponding to the specified destination.
|
||||||
|
* This routine will block until the connection completes.
|
||||||
|
*
|
||||||
|
* @param destination The destination to open on the target
|
||||||
|
* @return AdbStream object corresponding to the specified destination
|
||||||
|
* @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8
|
||||||
|
* @throws IOException If the stream fails while sending the packet
|
||||||
|
* @throws InterruptedException If we are unable to wait for the connection to finish
|
||||||
|
* @throws AdbPairingRequiredException If ADB lacks pairing
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public AdbStream open(@NonNull String destination)
|
||||||
|
throws IOException, InterruptedException, AdbPairingRequiredException {
|
||||||
|
int localId = ++mLastLocalId;
|
||||||
|
|
||||||
|
if (!mConnectAttempted) {
|
||||||
|
throw new IllegalStateException("connect() must be called first");
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForConnection(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
// Add this stream to this list of half-open streams
|
||||||
|
AdbStream stream = new AdbStream(this, localId);
|
||||||
|
mOpenedStreams.put(localId, stream);
|
||||||
|
|
||||||
|
// Send OPEN
|
||||||
|
sendPacket(AdbProtocol.generateOpen(localId, Objects.requireNonNull(destination)));
|
||||||
|
|
||||||
|
// Wait for the connection thread to receive the OKAY
|
||||||
|
synchronized (stream) {
|
||||||
|
stream.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the OPEN request was rejected
|
||||||
|
if (stream.isClosed()) {
|
||||||
|
mOpenedStreams.remove(localId);
|
||||||
|
throw new ConnectException("Stream open actively rejected by remote peer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean waitForConnection(long timeout, @NonNull TimeUnit unit)
|
||||||
|
throws InterruptedException, IOException, AdbPairingRequiredException {
|
||||||
|
synchronized (this) {
|
||||||
|
// Block if a connection is pending, but not yet complete
|
||||||
|
long timeoutEndMillis = System.currentTimeMillis() + Objects.requireNonNull(unit).toMillis(timeout);
|
||||||
|
while (!mConnectionEstablished && mConnectAttempted && timeoutEndMillis - System.currentTimeMillis() > 0) {
|
||||||
|
wait(timeoutEndMillis - System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mConnectionEstablished) {
|
||||||
|
if (mConnectAttempted) {
|
||||||
|
return false;
|
||||||
|
} else if (mAuthorisationFailed) {
|
||||||
|
// The peer may not have saved the public key in the past connections, or they've been removed.
|
||||||
|
throw new AdbAuthenticationFailedException();
|
||||||
|
} else {
|
||||||
|
Exception connectionException = mConnectionException;
|
||||||
|
if (connectionException != null) {
|
||||||
|
if (connectionException instanceof javax.net.ssl.SSLProtocolException) {
|
||||||
|
String message = connectionException.getMessage();
|
||||||
|
if (message != null && message.contains("protocol error")) {
|
||||||
|
throw (AdbPairingRequiredException) (new AdbPairingRequiredException("ADB pairing is required.").initCause(connectionException));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IOException("Connection failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function terminates all I/O on streams associated with this ADB connection
|
||||||
|
*/
|
||||||
|
private void cleanupStreams() {
|
||||||
|
// Close all streams on this connection
|
||||||
|
for (AdbStream s : mOpenedStreams.values()) {
|
||||||
|
try {
|
||||||
|
s.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mOpenedStreams.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This routine closes the Adb connection and underlying socket
|
||||||
|
*
|
||||||
|
* @throws IOException if the socket fails to close
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
// Closing the socket will kick the connection thread
|
||||||
|
mSocket.close();
|
||||||
|
|
||||||
|
// Wait for the connection thread to die
|
||||||
|
mConnectionThread.interrupt();
|
||||||
|
try {
|
||||||
|
mConnectionThread.join();
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy keypair
|
||||||
|
try {
|
||||||
|
mKeyPair.destroy();
|
||||||
|
} catch (DestroyFailedException ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendPacket(byte[] packet) throws IOException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
OutputStream os = getOutputStream();
|
||||||
|
os.write(packet);
|
||||||
|
os.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void flushPacket() throws IOException {
|
||||||
|
synchronized (mLock) {
|
||||||
|
getOutputStream().flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private String mHost = "127.0.0.1";
|
||||||
|
private int mPort = 5555;
|
||||||
|
private int mApi = Build.VERSION_CODES.BASE;
|
||||||
|
private PrivateKey mPrivateKey;
|
||||||
|
private Certificate mCertificate;
|
||||||
|
private KeyPair mKeyPair;
|
||||||
|
private String mDeviceName;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(String host, int port) {
|
||||||
|
mHost = host;
|
||||||
|
mPort = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set host address. Default is 127.0.0.1
|
||||||
|
*/
|
||||||
|
public Builder setHost(String host) {
|
||||||
|
this.mHost = host;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set port number. Default is 5555.
|
||||||
|
*/
|
||||||
|
public Builder setPort(int port) {
|
||||||
|
this.mPort = port;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a name for the device. Default is “Unknown Device”.
|
||||||
|
*
|
||||||
|
* @param deviceName Name of the device, could be the app label, hostname or user@hostname.
|
||||||
|
*/
|
||||||
|
public Builder setDeviceName(String deviceName) {
|
||||||
|
this.mDeviceName = deviceName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Android API (i.e. SDK) version for this connection. If the ADB daemon and the client are located in the
|
||||||
|
* same device, the value should be {@link Build.VERSION#SDK_INT} in order to improve performance as well as
|
||||||
|
* security.
|
||||||
|
*
|
||||||
|
* @param api The API version, default is {@link Build.VERSION_CODES#BASE}.
|
||||||
|
*/
|
||||||
|
public Builder setApi(int api) {
|
||||||
|
this.mApi = api;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set generated/stored private key.
|
||||||
|
*/
|
||||||
|
public Builder setPrivateKey(PrivateKey privateKey) {
|
||||||
|
this.mPrivateKey = privateKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set public key wrapped around a certificate
|
||||||
|
*/
|
||||||
|
public Builder setCertificate(Certificate certificate) {
|
||||||
|
this.mCertificate = certificate;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder setKeyPair(KeyPair keyPair) {
|
||||||
|
this.mKeyPair = keyPair;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link AdbConnection} associated with the socket and crypto object specified.
|
||||||
|
*
|
||||||
|
* @throws IOException If there was an error while establishing a socket connection
|
||||||
|
*/
|
||||||
|
public AdbConnection build() throws IOException {
|
||||||
|
if (mKeyPair == null) {
|
||||||
|
if (mPrivateKey == null || mCertificate == null) {
|
||||||
|
throw new UnsupportedOperationException("Private key and certificate must be set.");
|
||||||
|
}
|
||||||
|
mKeyPair = new KeyPair(mPrivateKey, mCertificate);
|
||||||
|
}
|
||||||
|
AdbConnection adbConnection = create(mHost, mPort, mKeyPair, mApi);
|
||||||
|
if (mDeviceName != null) {
|
||||||
|
adbConnection.setDeviceName(mDeviceName);
|
||||||
|
}
|
||||||
|
return adbConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #connect(long, TimeUnit, boolean)} without throwing anything if the first authentication
|
||||||
|
* attempt fails.
|
||||||
|
*
|
||||||
|
* @return The underlying {@link AdbConnection}
|
||||||
|
* @throws IOException If the socket fails while connecting
|
||||||
|
* @throws InterruptedException If timeout has reached
|
||||||
|
* @throws AdbPairingRequiredException If ADB lacks pairing
|
||||||
|
*/
|
||||||
|
public AdbConnection connect() throws IOException, InterruptedException, AdbPairingRequiredException {
|
||||||
|
AdbConnection adbConnection = build();
|
||||||
|
if (adbConnection.connect()) {
|
||||||
|
throw new IOException("Unable to establish a new connection.");
|
||||||
|
}
|
||||||
|
return adbConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to the remote device. This routine will block until the connection completes or the timeout elapses.
|
||||||
|
*
|
||||||
|
* @param timeout the time to wait for the lock
|
||||||
|
* @param unit the time unit of the timeout argument
|
||||||
|
* @param throwOnUnauthorised Whether to throw an {@link AdbAuthenticationFailedException}
|
||||||
|
* if the peer rejects out first authentication attempt
|
||||||
|
* @return {@code true} if the connection was established, or {@code false} if the connection timed out
|
||||||
|
* @throws IOException If the socket fails while connecting
|
||||||
|
* @throws InterruptedException If timeout has reached
|
||||||
|
* @throws AdbAuthenticationFailedException If {@code throwOnUnauthorised} is {@code true} and the peer rejects
|
||||||
|
* the first authentication attempt, which indicates that the peer has
|
||||||
|
* not saved the public key from a previous connection
|
||||||
|
* @throws AdbPairingRequiredException If ADB lacks pairing
|
||||||
|
*/
|
||||||
|
public AdbConnection connect(long timeout, @NonNull TimeUnit unit, boolean throwOnUnauthorised)
|
||||||
|
throws IOException, InterruptedException, AdbPairingRequiredException {
|
||||||
|
AdbConnection adbConnection = build();
|
||||||
|
if (adbConnection.connect(timeout, unit, throwOnUnauthorised)) {
|
||||||
|
throw new IOException("Unable to establish a new connection.");
|
||||||
|
}
|
||||||
|
return adbConnection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,170 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.sun.misc.BASE64Encoder;
|
||||||
|
import android.sun.security.provider.X509Factory;
|
||||||
|
import android.sun.security.x509.AlgorithmId;
|
||||||
|
import android.sun.security.x509.CertificateAlgorithmId;
|
||||||
|
import android.sun.security.x509.CertificateExtensions;
|
||||||
|
import android.sun.security.x509.CertificateIssuerName;
|
||||||
|
import android.sun.security.x509.CertificateSerialNumber;
|
||||||
|
import android.sun.security.x509.CertificateSubjectName;
|
||||||
|
import android.sun.security.x509.CertificateValidity;
|
||||||
|
import android.sun.security.x509.CertificateVersion;
|
||||||
|
import android.sun.security.x509.CertificateX509Key;
|
||||||
|
import android.sun.security.x509.KeyIdentifier;
|
||||||
|
import android.sun.security.x509.PrivateKeyUsageExtension;
|
||||||
|
import android.sun.security.x509.SubjectKeyIdentifierExtension;
|
||||||
|
import android.sun.security.x509.X500Name;
|
||||||
|
import android.sun.security.x509.X509CertImpl;
|
||||||
|
import android.sun.security.x509.X509CertInfo;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.spec.EncodedKeySpec;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
|
||||||
|
public class AdbConnectionManager extends AbsAdbConnectionManager {
|
||||||
|
private static AbsAdbConnectionManager INSTANCE;
|
||||||
|
|
||||||
|
public static AbsAdbConnectionManager getInstance(@NonNull Context context) throws Exception {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
INSTANCE = new AdbConnectionManager(context);
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrivateKey mPrivateKey;
|
||||||
|
private Certificate mCertificate;
|
||||||
|
|
||||||
|
private AdbConnectionManager(@NonNull Context context) throws Exception {
|
||||||
|
setApi(Build.VERSION.SDK_INT);
|
||||||
|
mPrivateKey = readPrivateKeyFromFile(context);
|
||||||
|
mCertificate = readCertificateFromFile(context);
|
||||||
|
if (mPrivateKey == null) {
|
||||||
|
// Generate a new key pair
|
||||||
|
int keySize = 2048;
|
||||||
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||||
|
keyPairGenerator.initialize(keySize, SecureRandom.getInstance("SHA1PRNG"));
|
||||||
|
KeyPair generateKeyPair = keyPairGenerator.generateKeyPair();
|
||||||
|
PublicKey publicKey = generateKeyPair.getPublic();
|
||||||
|
mPrivateKey = generateKeyPair.getPrivate();
|
||||||
|
// Generate a new certificate
|
||||||
|
String subject = "CN=My Awesome App";
|
||||||
|
String algorithmName = "SHA512withRSA";
|
||||||
|
long expiryDate = System.currentTimeMillis() + 86400000;
|
||||||
|
CertificateExtensions certificateExtensions = new CertificateExtensions();
|
||||||
|
certificateExtensions.set("SubjectKeyIdentifier", new SubjectKeyIdentifierExtension(
|
||||||
|
new KeyIdentifier(publicKey).getIdentifier()));
|
||||||
|
X500Name x500Name = new X500Name(subject);
|
||||||
|
Date notBefore = new Date();
|
||||||
|
Date notAfter = new Date(expiryDate);
|
||||||
|
certificateExtensions.set("PrivateKeyUsage", new PrivateKeyUsageExtension(notBefore, notAfter));
|
||||||
|
CertificateValidity certificateValidity = new CertificateValidity(notBefore, notAfter);
|
||||||
|
X509CertInfo x509CertInfo = new X509CertInfo();
|
||||||
|
x509CertInfo.set("version", new CertificateVersion(2));
|
||||||
|
x509CertInfo.set("serialNumber", new CertificateSerialNumber(new Random().nextInt() & Integer.MAX_VALUE));
|
||||||
|
x509CertInfo.set("algorithmID", new CertificateAlgorithmId(AlgorithmId.get(algorithmName)));
|
||||||
|
x509CertInfo.set("subject", new CertificateSubjectName(x500Name));
|
||||||
|
x509CertInfo.set("key", new CertificateX509Key(publicKey));
|
||||||
|
x509CertInfo.set("validity", certificateValidity);
|
||||||
|
x509CertInfo.set("issuer", new CertificateIssuerName(x500Name));
|
||||||
|
x509CertInfo.set("extensions", certificateExtensions);
|
||||||
|
X509CertImpl x509CertImpl = new X509CertImpl(x509CertInfo);
|
||||||
|
x509CertImpl.sign(mPrivateKey, algorithmName);
|
||||||
|
mCertificate = x509CertImpl;
|
||||||
|
// Write files
|
||||||
|
writePrivateKeyToFile(context, mPrivateKey);
|
||||||
|
writeCertificateToFile(context, mCertificate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected PrivateKey getPrivateKey() {
|
||||||
|
return mPrivateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Certificate getCertificate() {
|
||||||
|
return mCertificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected String getDeviceName() {
|
||||||
|
return "MyAwesomeApp";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Certificate readCertificateFromFile(@NonNull Context context)
|
||||||
|
throws IOException, CertificateException {
|
||||||
|
File certFile = new File(context.getFilesDir(), "cert.pem");
|
||||||
|
if (!certFile.exists()) return null;
|
||||||
|
try (InputStream cert = new FileInputStream(certFile)) {
|
||||||
|
return CertificateFactory.getInstance("X.509").generateCertificate(cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeCertificateToFile(@NonNull Context context, @NonNull Certificate certificate)
|
||||||
|
throws CertificateEncodingException, IOException {
|
||||||
|
File certFile = new File(context.getFilesDir(), "cert.pem");
|
||||||
|
BASE64Encoder encoder = new BASE64Encoder();
|
||||||
|
try (OutputStream os = new FileOutputStream(certFile)) {
|
||||||
|
os.write(X509Factory.BEGIN_CERT.getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.write('\n');
|
||||||
|
encoder.encode(certificate.getEncoded(), os);
|
||||||
|
os.write('\n');
|
||||||
|
os.write(X509Factory.END_CERT.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static PrivateKey readPrivateKeyFromFile(@NonNull Context context)
|
||||||
|
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||||
|
File privateKeyFile = new File(context.getFilesDir(), "private.key");
|
||||||
|
if (!privateKeyFile.exists()) return null;
|
||||||
|
byte[] privKeyBytes = new byte[(int) privateKeyFile.length()];
|
||||||
|
try (InputStream is = new FileInputStream(privateKeyFile)) {
|
||||||
|
is.read(privKeyBytes);
|
||||||
|
}
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privKeyBytes);
|
||||||
|
return keyFactory.generatePrivate(privateKeySpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writePrivateKeyToFile(@NonNull Context context, @NonNull PrivateKey privateKey)
|
||||||
|
throws IOException {
|
||||||
|
File privateKeyFile = new File(context.getFilesDir(), "private.key");
|
||||||
|
try (OutputStream os = new FileOutputStream(privateKeyFile)) {
|
||||||
|
os.write(privateKey.getEncoded());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class AdbInputStream extends InputStream {
|
||||||
|
public AdbStream mAdbStream;
|
||||||
|
|
||||||
|
public AdbInputStream(AdbStream adbStream) {
|
||||||
|
this.mAdbStream = adbStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
byte[] bytes = new byte[1];
|
||||||
|
if (read(bytes) == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return bytes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b) throws IOException {
|
||||||
|
return read(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (mAdbStream.isClosed()) return -1;
|
||||||
|
return mAdbStream.read(b, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
return mAdbStream.available();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class AdbOutputStream extends OutputStream {
|
||||||
|
private final AdbStream mAdbStream;
|
||||||
|
|
||||||
|
public AdbOutputStream(AdbStream adbStream) {
|
||||||
|
this.mAdbStream = adbStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
write(new byte[]{(byte) (b & 0xFF)});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b) throws IOException {
|
||||||
|
write(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
|
mAdbStream.write(b, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
mAdbStream.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
public class AdbPairingRequiredException extends Exception {
|
||||||
|
public AdbPairingRequiredException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,497 @@
|
|||||||
|
// SPDX-License-Identifier: BSD-3-Clause AND (GPL-3.0-or-later OR Apache-2.0)
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.StreamCorruptedException;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides useful functions and fields for ADB protocol details.
|
||||||
|
*/
|
||||||
|
// Copyright 2013 Cameron Gutman
|
||||||
|
final class AdbProtocol {
|
||||||
|
/**
|
||||||
|
* The length of the ADB message header
|
||||||
|
*/
|
||||||
|
public static final int ADB_HEADER_LENGTH = 24;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SYNC(online, sequence, "")
|
||||||
|
*
|
||||||
|
* @deprecated Obsolete, no longer used. Never used on the client side.
|
||||||
|
*/
|
||||||
|
public static final int A_SYNC = 0x434e5953;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CNXN is the connect message. No messages (except AUTH) are valid before this message is received.
|
||||||
|
*/
|
||||||
|
public static final int A_CNXN = 0x4e584e43;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The payload sent with the CONNECT message.
|
||||||
|
*/
|
||||||
|
public static final byte[] SYSTEM_IDENTITY_STRING_HOST = StringCompat.getBytes("host::\0", "UTF-8");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AUTH is the authentication message. It is part of the RSA public key authentication added in Android 4.2.2
|
||||||
|
* ({@link Build.VERSION_CODES#JELLY_BEAN_MR1}).
|
||||||
|
*/
|
||||||
|
public static final int A_AUTH = 0x48545541;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPEN is the open stream message. It is sent to open a new stream on the target device.
|
||||||
|
*/
|
||||||
|
public static final int A_OPEN = 0x4e45504f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OKAY is a success message. It is sent when a write is processed successfully.
|
||||||
|
*/
|
||||||
|
public static final int A_OKAY = 0x59414b4f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLSE is the close stream message. It is sent to close an existing stream on the target device.
|
||||||
|
*/
|
||||||
|
public static final int A_CLSE = 0x45534c43;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WRTE is the write stream message. It is sent with a payload that is the data to write to the stream.
|
||||||
|
*/
|
||||||
|
public static final int A_WRTE = 0x45545257;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* STLS is the Stream-based TLS1.3 authentication method, added in Android 9 ({@link Build.VERSION_CODES#P}).
|
||||||
|
*/
|
||||||
|
public static final int A_STLS = 0x534c5453;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({A_SYNC, A_CNXN, A_OPEN, A_OKAY, A_CLSE, A_WRTE, A_AUTH, A_STLS})
|
||||||
|
private @interface Command {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Original payload size
|
||||||
|
*/
|
||||||
|
public static final int MAX_PAYLOAD_V1 = 4 * 1024;
|
||||||
|
/**
|
||||||
|
* Supported payload size since Android 7 (N)
|
||||||
|
*/
|
||||||
|
public static final int MAX_PAYLOAD_V2 = 256 * 1024;
|
||||||
|
/**
|
||||||
|
* Supported payload size since Android 9 (P)
|
||||||
|
*/
|
||||||
|
public static final int MAX_PAYLOAD_V3 = 1024 * 1024;
|
||||||
|
/**
|
||||||
|
* Maximum supported payload size is set to the original to support all APIs
|
||||||
|
*/
|
||||||
|
public static final int MAX_PAYLOAD = MAX_PAYLOAD_V1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The original version of the ADB protocol
|
||||||
|
*/
|
||||||
|
public static final int A_VERSION_MIN = 0x01000000;
|
||||||
|
/**
|
||||||
|
* The new version of the ADB protocol introduced in Android 9 (P) with the introduction of TLS
|
||||||
|
*/
|
||||||
|
public static final int A_VERSION_SKIP_CHECKSUM = 0x01000001;
|
||||||
|
public static final int A_VERSION = A_VERSION_MIN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current version of the Stream-based TLS
|
||||||
|
*/
|
||||||
|
public static final int A_STLS_VERSION_MIN = 0x01000000;
|
||||||
|
public static final int A_STLS_VERSION = A_STLS_VERSION_MIN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This authentication type represents a SHA1 hash to sign.
|
||||||
|
*/
|
||||||
|
public static final int ADB_AUTH_TOKEN = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This authentication type represents the signed SHA1 hash.
|
||||||
|
*/
|
||||||
|
public static final int ADB_AUTH_SIGNATURE = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This authentication type represents an RSA public key.
|
||||||
|
*/
|
||||||
|
public static final int ADB_AUTH_RSAPUBLICKEY = 3;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({ADB_AUTH_TOKEN, ADB_AUTH_SIGNATURE, ADB_AUTH_RSAPUBLICKEY})
|
||||||
|
private @interface AuthType {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getMaxData(int api) {
|
||||||
|
if (api >= Build.VERSION_CODES.P) {
|
||||||
|
return MAX_PAYLOAD_V3;
|
||||||
|
}
|
||||||
|
if (api >= Build.VERSION_CODES.N) {
|
||||||
|
return MAX_PAYLOAD_V2;
|
||||||
|
}
|
||||||
|
return MAX_PAYLOAD_V1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getProtocolVersion(int api) {
|
||||||
|
if (api >= Build.VERSION_CODES.P) {
|
||||||
|
return A_VERSION_SKIP_CHECKSUM;
|
||||||
|
}
|
||||||
|
return A_VERSION_MIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function performs a checksum on the ADB payload data.
|
||||||
|
*
|
||||||
|
* @param data The data
|
||||||
|
* @return The checksum of the data
|
||||||
|
*/
|
||||||
|
private static int getPayloadChecksum(@NonNull byte[] data) {
|
||||||
|
return getPayloadChecksum(data, 0, data.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function performs a checksum on the ADB payload data.
|
||||||
|
*
|
||||||
|
* @param data The data
|
||||||
|
* @param offset The start offset in the data
|
||||||
|
* @param length The number of bytes to take from the data
|
||||||
|
* @return The checksum of the data
|
||||||
|
*/
|
||||||
|
private static int getPayloadChecksum(@NonNull byte[] data, int offset, int length) {
|
||||||
|
int checksum = 0;
|
||||||
|
for (int i = offset; i < offset + length; ++i) {
|
||||||
|
checksum += data[i] & 0xFF;
|
||||||
|
}
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function generates an ADB message given the fields.
|
||||||
|
*
|
||||||
|
* @param command Command identifier constant
|
||||||
|
* @param arg0 First argument
|
||||||
|
* @param arg1 Second argument
|
||||||
|
* @param data The data
|
||||||
|
* @return Byte array containing the message
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static byte[] generateMessage(@Command int command, int arg0, int arg1, @Nullable byte[] data) {
|
||||||
|
return generateMessage(command, arg0, arg1, data, 0, data == null ? 0 : data.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function generates an ADB message given the fields.
|
||||||
|
*
|
||||||
|
* @param command Command identifier constant
|
||||||
|
* @param arg0 First argument
|
||||||
|
* @param arg1 Second argument
|
||||||
|
* @param data The data
|
||||||
|
* @param offset The start offset in the data
|
||||||
|
* @param length The number of bytes to take from the data
|
||||||
|
* @return Byte array containing the message
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static byte[] generateMessage(@Command int command, int arg0, int arg1, @Nullable byte[] data, int offset, int length) {
|
||||||
|
// Protocol as defined at https://github.com/aosp-mirror/platform_system_core/blob/6072de17cd812daf238092695f26a552d3122f8c/adb/protocol.txt
|
||||||
|
// struct message {
|
||||||
|
// unsigned command; // command identifier constant
|
||||||
|
// unsigned arg0; // first argument
|
||||||
|
// unsigned arg1; // second argument
|
||||||
|
// unsigned data_length; // length of payload (0 is allowed)
|
||||||
|
// unsigned data_check; // checksum of data payload
|
||||||
|
// unsigned magic; // command ^ 0xffffffff
|
||||||
|
// };
|
||||||
|
|
||||||
|
ByteBuffer message;
|
||||||
|
|
||||||
|
if (data != null) {
|
||||||
|
message = ByteBuffer.allocate(ADB_HEADER_LENGTH + length).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
} else {
|
||||||
|
message = ByteBuffer.allocate(ADB_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
message.putInt(command);
|
||||||
|
message.putInt(arg0);
|
||||||
|
message.putInt(arg1);
|
||||||
|
|
||||||
|
if (data != null) {
|
||||||
|
message.putInt(length);
|
||||||
|
message.putInt(getPayloadChecksum(data, offset, length));
|
||||||
|
} else {
|
||||||
|
message.putInt(0);
|
||||||
|
message.putInt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
message.putInt(~command);
|
||||||
|
|
||||||
|
if (data != null) {
|
||||||
|
message.put(data, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a CONNECT message for a given API.
|
||||||
|
* <p>
|
||||||
|
* CONNECT(version, maxdata, "system-identity-string")
|
||||||
|
*
|
||||||
|
* @param api API version
|
||||||
|
* @return Byte array containing the message
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static byte[] generateConnect(int api) {
|
||||||
|
return generateMessage(A_CNXN, getProtocolVersion(api), getMaxData(api), SYSTEM_IDENTITY_STRING_HOST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an AUTH message with the specified type and payload.
|
||||||
|
* <p>
|
||||||
|
* AUTH(type, 0, "data")
|
||||||
|
*
|
||||||
|
* @param type Authentication type (see ADB_AUTH_* constants)
|
||||||
|
* @param data The data
|
||||||
|
* @return Byte array containing the message
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static byte[] generateAuth(@AuthType int type, byte[] data) {
|
||||||
|
return generateMessage(A_AUTH, type, 0, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an STLS message with default parameters.
|
||||||
|
* <p>
|
||||||
|
* STLS(version, 0, "")
|
||||||
|
*
|
||||||
|
* @return Byte array containing the message
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static byte[] generateStls() {
|
||||||
|
return generateMessage(A_STLS, A_STLS_VERSION, 0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an OPEN stream message with the specified local ID and destination.
|
||||||
|
* <p>
|
||||||
|
* OPEN(local-id, 0, "destination")
|
||||||
|
*
|
||||||
|
* @param localId A unique local ID identifying the stream
|
||||||
|
* @param destination The destination of the stream on the target
|
||||||
|
* @return Byte array containing the message
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static byte[] generateOpen(int localId, @NonNull String destination) {
|
||||||
|
ByteBuffer bbuf = ByteBuffer.allocate(destination.length() + 100);
|
||||||
|
bbuf.put(StringCompat.getBytes(destination, "UTF-8"));
|
||||||
|
bbuf.put((byte) 0);
|
||||||
|
return generateMessage(A_OPEN, localId, 0, bbuf.array());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a WRITE stream message with the specified IDs and payload.
|
||||||
|
* <p>
|
||||||
|
* WRITE(local-id, remote-id, "data")
|
||||||
|
*
|
||||||
|
* @param localId The unique local ID of the stream
|
||||||
|
* @param remoteId The unique remote ID of the stream
|
||||||
|
* @param data The data
|
||||||
|
* @param offset The start offset in the data
|
||||||
|
* @param length The number of bytes to take from the data
|
||||||
|
* @return Byte array containing the message
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static byte[] generateWrite(int localId, int remoteId, byte[] data, int offset, int length) {
|
||||||
|
return generateMessage(A_WRTE, localId, remoteId, data, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a CLOSE stream message with the specified IDs.
|
||||||
|
* <p>
|
||||||
|
* CLOSE(local-id, remote-id, "")
|
||||||
|
*
|
||||||
|
* @param localId The unique local ID of the stream
|
||||||
|
* @param remoteId The unique remote ID of the stream
|
||||||
|
* @return Byte array containing the message
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static byte[] generateClose(int localId, int remoteId) {
|
||||||
|
return generateMessage(A_CLSE, localId, remoteId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an OKAY/READY message with the specified IDs.
|
||||||
|
* <p>
|
||||||
|
* READY(local-id, remote-id, "")
|
||||||
|
*
|
||||||
|
* @param localId The unique local ID of the stream
|
||||||
|
* @param remoteId The unique remote ID of the stream
|
||||||
|
* @return Byte array containing the message
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static byte[] generateReady(int localId, int remoteId) {
|
||||||
|
return generateMessage(A_OKAY, localId, remoteId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides an abstraction for the ADB message format.
|
||||||
|
*/
|
||||||
|
static final class Message {
|
||||||
|
/**
|
||||||
|
* The command field of the message
|
||||||
|
*/
|
||||||
|
@Command
|
||||||
|
public final int command;
|
||||||
|
/**
|
||||||
|
* The arg0 field of the message
|
||||||
|
*/
|
||||||
|
public final int arg0;
|
||||||
|
/**
|
||||||
|
* The arg1 field of the message
|
||||||
|
*/
|
||||||
|
public final int arg1;
|
||||||
|
/**
|
||||||
|
* The payload length field of the message
|
||||||
|
*/
|
||||||
|
public final int dataLength;
|
||||||
|
/**
|
||||||
|
* The checksum field of the message
|
||||||
|
*/
|
||||||
|
public final int dataCheck;
|
||||||
|
/**
|
||||||
|
* The magic field of the message
|
||||||
|
*/
|
||||||
|
public final int magic;
|
||||||
|
/**
|
||||||
|
* The payload of the message
|
||||||
|
*/
|
||||||
|
public byte[] payload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read and parse an ADB message from the supplied input stream.
|
||||||
|
* <p>
|
||||||
|
* <b>Note:</b> If data is corrupted, the connection has to be closed immediately to avoid inconsistencies.
|
||||||
|
*
|
||||||
|
* @param in InputStream object to read data from
|
||||||
|
* @return An AdbMessage object represented the message read
|
||||||
|
* @throws IOException If the stream fails while reading.
|
||||||
|
* @throws StreamCorruptedException If data is corrupted.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static Message parse(@NonNull InputStream in, int protocolVersion, int maxData) throws IOException {
|
||||||
|
ByteBuffer header = ByteBuffer.allocate(ADB_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
// Read header
|
||||||
|
int dataRead = 0;
|
||||||
|
do {
|
||||||
|
int bytesRead = in.read(header.array(), dataRead, ADB_HEADER_LENGTH - dataRead);
|
||||||
|
if (bytesRead < 0) {
|
||||||
|
throw new IOException("Stream closed");
|
||||||
|
} else dataRead += bytesRead;
|
||||||
|
} while (dataRead < ADB_HEADER_LENGTH);
|
||||||
|
|
||||||
|
Message msg = new Message(header);
|
||||||
|
|
||||||
|
// Validate header
|
||||||
|
if (msg.command != (~msg.magic)) { // magic = cmd ^ 0xFFFFFFFF
|
||||||
|
throw new StreamCorruptedException(String.format("Invalid header: Invalid magic 0x%x.", msg.magic));
|
||||||
|
}
|
||||||
|
if (msg.command != A_SYNC && msg.command != A_CNXN && msg.command != A_OPEN && msg.command != A_OKAY
|
||||||
|
&& msg.command != A_CLSE && msg.command != A_WRTE && msg.command != A_AUTH
|
||||||
|
&& msg.command != A_STLS) {
|
||||||
|
throw new StreamCorruptedException(String.format("Invalid header: Invalid command 0x%x.", msg.command));
|
||||||
|
}
|
||||||
|
if (msg.dataLength < 0 || msg.dataLength > maxData) {
|
||||||
|
throw new StreamCorruptedException(String.format("Invalid header: Invalid data length %d", msg.dataLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.dataLength == 0) {
|
||||||
|
// No payload supplied, return immediately
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read payload
|
||||||
|
msg.payload = new byte[msg.dataLength];
|
||||||
|
dataRead = 0;
|
||||||
|
do {
|
||||||
|
int bytesRead = in.read(msg.payload, dataRead, msg.dataLength - dataRead);
|
||||||
|
if (bytesRead < 0) {
|
||||||
|
throw new IOException("Stream closed");
|
||||||
|
} else dataRead += bytesRead;
|
||||||
|
} while (dataRead < msg.dataLength);
|
||||||
|
|
||||||
|
// Verify payload
|
||||||
|
if ((protocolVersion <= A_VERSION_MIN || (msg.command == A_CNXN && msg.arg0 <= A_VERSION_MIN))
|
||||||
|
&& getPayloadChecksum(msg.payload) != msg.dataCheck) {
|
||||||
|
// Checksum verification failed
|
||||||
|
throw new StreamCorruptedException("Invalid header: Checksum mismatched.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message(@NonNull ByteBuffer header) {
|
||||||
|
command = header.getInt();
|
||||||
|
arg0 = header.getInt();
|
||||||
|
arg1 = header.getInt();
|
||||||
|
dataLength = header.getInt();
|
||||||
|
dataCheck = header.getInt();
|
||||||
|
magic = header.getInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String tag;
|
||||||
|
switch (command) {
|
||||||
|
case A_SYNC:
|
||||||
|
tag = "SYNC";
|
||||||
|
break;
|
||||||
|
case A_CNXN:
|
||||||
|
tag = "CNXN";
|
||||||
|
break;
|
||||||
|
case A_OPEN:
|
||||||
|
tag = "OPEN";
|
||||||
|
break;
|
||||||
|
case A_OKAY:
|
||||||
|
tag = "OKAY";
|
||||||
|
break;
|
||||||
|
case A_CLSE:
|
||||||
|
tag = "CLSE";
|
||||||
|
break;
|
||||||
|
case A_WRTE:
|
||||||
|
tag = "WRTE";
|
||||||
|
break;
|
||||||
|
case A_AUTH:
|
||||||
|
tag = "AUTH";
|
||||||
|
break;
|
||||||
|
case A_STLS:
|
||||||
|
tag = "STLS";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tag = "????";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "Message{" +
|
||||||
|
"command=" + tag +
|
||||||
|
", arg0=0x" + Integer.toHexString(arg0) +
|
||||||
|
", arg1=0x" + Integer.toHexString(arg1) +
|
||||||
|
", payloadLength=" + dataLength +
|
||||||
|
", checksum=" + dataCheck +
|
||||||
|
", magic=0x" + Integer.toHexString(magic) +
|
||||||
|
", payload=" + Arrays.toString(payload) +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,300 @@
|
|||||||
|
// SPDX-License-Identifier: BSD-3-Clause AND (GPL-3.0-or-later OR Apache-2.0)
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class abstracts the underlying ADB streams
|
||||||
|
*/
|
||||||
|
// Copyright 2013 Cameron Gutman
|
||||||
|
public class AdbStream implements Closeable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The AdbConnection object that the stream communicates over
|
||||||
|
*/
|
||||||
|
private final AdbConnection mAdbConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The local ID of the stream
|
||||||
|
*/
|
||||||
|
private final int mLocalId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The remote ID of the stream
|
||||||
|
*/
|
||||||
|
private volatile int mRemoteId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether WRTE is currently allowed
|
||||||
|
*/
|
||||||
|
private final AtomicBoolean mWriteReady;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A queue of data from the target's WRTE packets
|
||||||
|
*/
|
||||||
|
private final Queue<byte[]> mReadQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store data received from the first WRTE packet in order to support buffering.
|
||||||
|
*/
|
||||||
|
private final ByteBuffer mReadBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the connection is closed already
|
||||||
|
*/
|
||||||
|
private volatile boolean mIsClosed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the remote peer has closed but we still have unread data in the queue
|
||||||
|
*/
|
||||||
|
private volatile boolean mPendingClose;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new AdbStream object on the specified AdbConnection
|
||||||
|
* with the given local ID.
|
||||||
|
*
|
||||||
|
* @param adbConnection AdbConnection that this stream is running on
|
||||||
|
* @param localId Local ID of the stream
|
||||||
|
*/
|
||||||
|
AdbStream(AdbConnection adbConnection, int localId)
|
||||||
|
throws IOException, InterruptedException, AdbPairingRequiredException {
|
||||||
|
this.mAdbConnection = adbConnection;
|
||||||
|
this.mLocalId = localId;
|
||||||
|
this.mReadQueue = new ConcurrentLinkedQueue<>();
|
||||||
|
this.mReadBuffer = (ByteBuffer) ByteBuffer.allocate(adbConnection.getMaxData()).flip();
|
||||||
|
this.mWriteReady = new AtomicBoolean(false);
|
||||||
|
this.mIsClosed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdbInputStream openInputStream() {
|
||||||
|
return new AdbInputStream(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdbOutputStream openOutputStream() {
|
||||||
|
return new AdbOutputStream(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the connection thread to indicate newly received data.
|
||||||
|
*
|
||||||
|
* @param payload Data inside the WRTE message
|
||||||
|
*/
|
||||||
|
void addPayload(byte[] payload) {
|
||||||
|
synchronized (mReadQueue) {
|
||||||
|
mReadQueue.add(payload);
|
||||||
|
mReadQueue.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the connection thread to send an OKAY packet, allowing the
|
||||||
|
* other side to continue transmission.
|
||||||
|
*
|
||||||
|
* @throws IOException If the connection fails while sending the packet
|
||||||
|
*/
|
||||||
|
void sendReady() throws IOException {
|
||||||
|
// Generate and send a OKAY packet
|
||||||
|
mAdbConnection.sendPacket(AdbProtocol.generateReady(mLocalId, mRemoteId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the connection thread to update the remote ID for this stream
|
||||||
|
*
|
||||||
|
* @param remoteId New remote ID
|
||||||
|
*/
|
||||||
|
void updateRemoteId(int remoteId) {
|
||||||
|
this.mRemoteId = remoteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the connection thread to indicate the stream is okay to send data.
|
||||||
|
*/
|
||||||
|
void readyForWrite() {
|
||||||
|
mWriteReady.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the connection thread to notify that the stream was closed by the peer.
|
||||||
|
*/
|
||||||
|
void notifyClose(boolean closedByPeer) {
|
||||||
|
// We don't call close() because it sends another CLSE
|
||||||
|
if (closedByPeer && !mReadQueue.isEmpty()) {
|
||||||
|
// The remote peer closed the stream, but we haven't finished reading the remaining data
|
||||||
|
mPendingClose = true;
|
||||||
|
} else {
|
||||||
|
mIsClosed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify readers and writers
|
||||||
|
synchronized (this) {
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
synchronized (mReadQueue) {
|
||||||
|
mReadQueue.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read bytes from the ADB daemon.
|
||||||
|
*
|
||||||
|
* @return the next byte of data, or {@code -1} if the end of the stream is reached.
|
||||||
|
* @throws IOException If the stream fails while waiting
|
||||||
|
*/
|
||||||
|
public int read(byte[] bytes, int offset, int length) throws IOException {
|
||||||
|
if (mReadBuffer.hasRemaining()) {
|
||||||
|
return readBuffer(bytes, offset, length);
|
||||||
|
}
|
||||||
|
// Buffer has no data, grab from the queue
|
||||||
|
synchronized (mReadQueue) {
|
||||||
|
byte[] data;
|
||||||
|
// Wait for the connection to close or data to be received
|
||||||
|
while ((data = mReadQueue.poll()) == null && !mIsClosed) {
|
||||||
|
try {
|
||||||
|
mReadQueue.wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
//noinspection UnnecessaryInitCause
|
||||||
|
throw (IOException) new IOException().initCause(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add data to the buffer
|
||||||
|
if (data != null) {
|
||||||
|
mReadBuffer.clear();
|
||||||
|
mReadBuffer.put(data);
|
||||||
|
mReadBuffer.flip();
|
||||||
|
if (mReadBuffer.hasRemaining()) {
|
||||||
|
return readBuffer(bytes, offset, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsClosed) {
|
||||||
|
throw new IOException("Stream closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPendingClose && mReadQueue.isEmpty()) {
|
||||||
|
// The peer closed the stream, and we've finished reading the stream data, so this stream is finished
|
||||||
|
mIsClosed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readBuffer(byte[] bytes, int offset, int length) {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = offset; i < offset + length; ++i) {
|
||||||
|
if (mReadBuffer.hasRemaining()) {
|
||||||
|
bytes[i] = mReadBuffer.get();
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a WRTE packet with a given byte array payload. It does not flush the stream.
|
||||||
|
*
|
||||||
|
* @param bytes Payload in the form of a byte array
|
||||||
|
* @throws IOException If the stream fails while sending data
|
||||||
|
*/
|
||||||
|
public void write(byte[] bytes, int offset, int length) throws IOException {
|
||||||
|
synchronized (this) {
|
||||||
|
// Make sure we're ready for a WRTE
|
||||||
|
while (!mIsClosed && !mWriteReady.compareAndSet(true, false)) {
|
||||||
|
try {
|
||||||
|
wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
//noinspection UnnecessaryInitCause
|
||||||
|
throw (IOException) new IOException().initCause(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsClosed) {
|
||||||
|
throw new IOException("Stream closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Split and send data as WRTE packet
|
||||||
|
// TODO: A WRITE message may not be sent until a READY message is received.
|
||||||
|
// Once a WRITE message is sent, an additional WRITE message may not be
|
||||||
|
// sent until another READY message has been received. Recipients of
|
||||||
|
// a WRITE message that is in violation of this requirement will CLOSE
|
||||||
|
// the connection.
|
||||||
|
int maxData;
|
||||||
|
try {
|
||||||
|
maxData = mAdbConnection.getMaxData();
|
||||||
|
} catch (InterruptedException | AdbPairingRequiredException e) {
|
||||||
|
//noinspection UnnecessaryInitCause
|
||||||
|
throw (IOException) new IOException().initCause(e);
|
||||||
|
}
|
||||||
|
while (length != 0) {
|
||||||
|
if (length <= maxData) {
|
||||||
|
mAdbConnection.sendPacket(AdbProtocol.generateWrite(mLocalId, mRemoteId, bytes, offset, length));
|
||||||
|
offset = offset + length;
|
||||||
|
length = 0;
|
||||||
|
} else { // if (length > maxData) {
|
||||||
|
mAdbConnection.sendPacket(AdbProtocol.generateWrite(mLocalId, mRemoteId, bytes, offset, maxData));
|
||||||
|
offset = offset + maxData;
|
||||||
|
length = length - maxData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flush() throws IOException {
|
||||||
|
if (mIsClosed) {
|
||||||
|
throw new IOException("Stream closed");
|
||||||
|
}
|
||||||
|
mAdbConnection.flushPacket();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the stream. This sends a close message to the peer.
|
||||||
|
*
|
||||||
|
* @throws IOException If the stream fails while sending the close message.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
synchronized (this) {
|
||||||
|
// This may already be closed by the remote host
|
||||||
|
if (mIsClosed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Notify readers/writers that we've closed
|
||||||
|
notifyClose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
mAdbConnection.sendPacket(AdbProtocol.generateClose(mLocalId, mRemoteId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the stream is closed or not
|
||||||
|
*
|
||||||
|
* @return True if the stream is close, false if not
|
||||||
|
*/
|
||||||
|
public boolean isClosed() {
|
||||||
|
return mIsClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an estimate of available data.
|
||||||
|
*
|
||||||
|
* @return an estimate of the number of bytes that can be read from this stream without blocking.
|
||||||
|
* @throws IOException if the stream is close.
|
||||||
|
*/
|
||||||
|
public int available() throws IOException {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mIsClosed) {
|
||||||
|
throw new IOException("Stream closed.");
|
||||||
|
}
|
||||||
|
if (mReadBuffer.hasRemaining()) {
|
||||||
|
return mReadBuffer.remaining();
|
||||||
|
}
|
||||||
|
byte[] data = mReadQueue.peek();
|
||||||
|
return data == null ? 0 : data.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,245 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.RSAPublicKeySpec;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
|
||||||
|
final class AndroidPubkey {
|
||||||
|
/**
|
||||||
|
* Size of an RSA modulus such as an encrypted block or a signature.
|
||||||
|
*/
|
||||||
|
public static final int ANDROID_PUBKEY_MODULUS_SIZE = 2048 / 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of an encoded RSA key.
|
||||||
|
*/
|
||||||
|
public static final int ANDROID_PUBKEY_ENCODED_SIZE = 3 * 4 + 2 * ANDROID_PUBKEY_MODULUS_SIZE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of the RSA modulus in words.
|
||||||
|
*/
|
||||||
|
public static final int ANDROID_PUBKEY_MODULUS_SIZE_WORDS = ANDROID_PUBKEY_MODULUS_SIZE / 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The RSA signature padding as an int array.
|
||||||
|
*/
|
||||||
|
private static final int[] SIGNATURE_PADDING_AS_INT = new int[]{
|
||||||
|
0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
|
||||||
|
0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00,
|
||||||
|
0x04, 0x14
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The RSA signature padding as a byte array
|
||||||
|
*/
|
||||||
|
private static final byte[] RSA_SHA_PKCS1_SIGNATURE_PADDING;
|
||||||
|
|
||||||
|
static {
|
||||||
|
RSA_SHA_PKCS1_SIGNATURE_PADDING = new byte[SIGNATURE_PADDING_AS_INT.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < RSA_SHA_PKCS1_SIGNATURE_PADDING.length; i++)
|
||||||
|
RSA_SHA_PKCS1_SIGNATURE_PADDING[i] = (byte) SIGNATURE_PADDING_AS_INT[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs the ADB SHA1 payload with the private key of this object.
|
||||||
|
*
|
||||||
|
* @param privateKey Private key to sign with
|
||||||
|
* @param payload SHA1 payload to sign
|
||||||
|
* @return Signed SHA1 payload
|
||||||
|
* @throws GeneralSecurityException If signing fails
|
||||||
|
*/
|
||||||
|
// Taken from adb_auth_sign
|
||||||
|
@NonNull
|
||||||
|
public static byte[] adbAuthSign(@NonNull PrivateKey privateKey, byte[] payload)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
|
||||||
|
c.init(Cipher.ENCRYPT_MODE, privateKey);
|
||||||
|
c.update(RSA_SHA_PKCS1_SIGNATURE_PADDING);
|
||||||
|
return c.doFinal(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a standard RSAPublicKey object to the special ADB format. Available since 4.2.2.
|
||||||
|
*
|
||||||
|
* @param publicKey RSAPublicKey object to convert
|
||||||
|
* @param name Name without null terminator
|
||||||
|
* @return Byte array containing the converted RSAPublicKey object
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static byte[] encodeWithName(@NonNull RSAPublicKey publicKey, @NonNull String name)
|
||||||
|
throws InvalidKeyException {
|
||||||
|
int pkeySize = 4 * (int) Math.ceil(ANDROID_PUBKEY_ENCODED_SIZE / 3.0);
|
||||||
|
try (ByteArrayNoThrowOutputStream bos = new ByteArrayNoThrowOutputStream(pkeySize + name.length() + 2)) {
|
||||||
|
bos.write(Base64.encode(encode(publicKey),0));
|
||||||
|
bos.write(getUserInfo(name));
|
||||||
|
return bos.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken from get_user_info except that a custom name is used instead of host@user
|
||||||
|
@VisibleForTesting
|
||||||
|
@NonNull
|
||||||
|
static byte[] getUserInfo(@NonNull String name) {
|
||||||
|
return StringCompat.getBytes(String.format(" %s\u0000", name), "UTF-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://android.googlesource.com/platform/system/core/+/e797a5c75afc17024d0f0f488c130128fcd704e2/libcrypto_utils/android_pubkey.cpp
|
||||||
|
// typedef struct RSAPublicKey {
|
||||||
|
// uint32_t modulus_size_words; // Modulus length. This must be ANDROID_PUBKEY_MODULUS_SIZE.
|
||||||
|
// uint32_t n0inv; // Precomputed montgomery parameter: -1 / n[0] mod 2^32
|
||||||
|
// uint8_t modulus[ANDROID_PUBKEY_MODULUS_SIZE]; // RSA modulus as a little-endian array.
|
||||||
|
// uint8_t rr[ANDROID_PUBKEY_MODULUS_SIZE]; // Montgomery parameter R^2 as a little-endian array.
|
||||||
|
// uint32_t exponent; // RSA modulus: 3 or 65537
|
||||||
|
// } RSAPublicKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates a new {@link RSAPublicKey} object, decodes a public RSA key stored in Android's custom binary format,
|
||||||
|
* and sets the key parameters. The resulting key can be used with the standard Java cryptography API to perform
|
||||||
|
* public operations.
|
||||||
|
*
|
||||||
|
* @param androidPubkey Public RSA key in Android's custom binary format. The size of the key must be at least
|
||||||
|
* {@link #ANDROID_PUBKEY_ENCODED_SIZE}
|
||||||
|
* @return {@link RSAPublicKey} object
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static RSAPublicKey decode(@NonNull byte[] androidPubkey)
|
||||||
|
throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||||
|
BigInteger n;
|
||||||
|
BigInteger e;
|
||||||
|
|
||||||
|
// Check size is large enough and the modulus size is correct.
|
||||||
|
if (androidPubkey.length < ANDROID_PUBKEY_ENCODED_SIZE) {
|
||||||
|
throw new InvalidKeyException("Invalid key length");
|
||||||
|
}
|
||||||
|
ByteBuffer keyStruct = ByteBuffer.wrap(androidPubkey).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
int modulusSize = keyStruct.getInt();
|
||||||
|
if (modulusSize != ANDROID_PUBKEY_MODULUS_SIZE_WORDS) {
|
||||||
|
throw new InvalidKeyException("Invalid modulus length.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the modulus to big-endian byte order as expected by BN_bin2bn.
|
||||||
|
byte[] modulus = new byte[ANDROID_PUBKEY_MODULUS_SIZE];
|
||||||
|
keyStruct.position(8);
|
||||||
|
keyStruct.get(modulus);
|
||||||
|
n = new BigInteger(1, swapEndianness(modulus));
|
||||||
|
|
||||||
|
// Read the exponent.
|
||||||
|
keyStruct.position(520);
|
||||||
|
e = BigInteger.valueOf(keyStruct.getInt());
|
||||||
|
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e);
|
||||||
|
return (RSAPublicKey) keyFactory.generatePublic(publicKeySpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the given key in the Android RSA public key binary format.
|
||||||
|
*
|
||||||
|
* @return Public RSA key in Android's custom binary format. The size of the key should be at least
|
||||||
|
* {@link #ANDROID_PUBKEY_ENCODED_SIZE}
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static byte[] encode(@NonNull RSAPublicKey publicKey) throws InvalidKeyException {
|
||||||
|
BigInteger r32;
|
||||||
|
BigInteger n0inv;
|
||||||
|
BigInteger rr;
|
||||||
|
|
||||||
|
if (publicKey.getModulus().toByteArray().length < ANDROID_PUBKEY_MODULUS_SIZE) {
|
||||||
|
throw new InvalidKeyException("Invalid key length " + publicKey.getModulus().toByteArray().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer keyStruct = ByteBuffer.allocate(ANDROID_PUBKEY_ENCODED_SIZE).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
// Store the modulus size.
|
||||||
|
keyStruct.putInt(ANDROID_PUBKEY_MODULUS_SIZE_WORDS); // modulus_size_words
|
||||||
|
|
||||||
|
// Compute and store n0inv = -1 / N[0] mod 2^32.
|
||||||
|
r32 = BigInteger.ZERO.setBit(32); // r32 = 2^32
|
||||||
|
n0inv = publicKey.getModulus().mod(r32); // n0inv = N[0] mod 2^32
|
||||||
|
n0inv = n0inv.modInverse(r32); // n0inv = 1/n0inv mod 2^32
|
||||||
|
n0inv = r32.subtract(n0inv); // n0inv = 2^32 - n0inv
|
||||||
|
keyStruct.putInt(n0inv.intValue()); // n0inv
|
||||||
|
|
||||||
|
// Store the modulus.
|
||||||
|
keyStruct.put(Objects.requireNonNull(BigEndianToLittleEndianPadded(ANDROID_PUBKEY_MODULUS_SIZE, publicKey.getModulus())));
|
||||||
|
|
||||||
|
// Compute and store rr = (2^(rsa_size)) ^ 2 mod N.
|
||||||
|
rr = BigInteger.ZERO.setBit(ANDROID_PUBKEY_MODULUS_SIZE * 8); // rr = 2^(rsa_size)
|
||||||
|
rr = rr.modPow(BigInteger.valueOf(2), publicKey.getModulus()); // rr = rr^2 mod N
|
||||||
|
keyStruct.put(Objects.requireNonNull(BigEndianToLittleEndianPadded(ANDROID_PUBKEY_MODULUS_SIZE, rr)));
|
||||||
|
|
||||||
|
// Store the exponent.
|
||||||
|
keyStruct.putInt(publicKey.getPublicExponent().intValue()); // exponent
|
||||||
|
|
||||||
|
return keyStruct.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static byte[] BigEndianToLittleEndianPadded(int len, @NonNull BigInteger in) {
|
||||||
|
byte[] out = new byte[len];
|
||||||
|
byte[] bytes = swapEndianness(in.toByteArray()); // Convert big endian -> little endian
|
||||||
|
int num_bytes = bytes.length;
|
||||||
|
if (len < num_bytes) {
|
||||||
|
if (!fitsInBytes(bytes, num_bytes, len)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
num_bytes = len;
|
||||||
|
}
|
||||||
|
System.arraycopy(bytes, 0, out, 0, num_bytes);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean fitsInBytes(@NonNull byte[] bytes, int num_bytes, int len) {
|
||||||
|
byte mask = 0;
|
||||||
|
for (int i = len; i < num_bytes; i++) {
|
||||||
|
mask |= bytes[i];
|
||||||
|
}
|
||||||
|
return mask == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static byte[] swapEndianness(@NonNull byte[] bytes) {
|
||||||
|
int len = bytes.length;
|
||||||
|
byte[] out = new byte[len];
|
||||||
|
for (int i = 0; i < len; ++i) {
|
||||||
|
out[i] = bytes[len - i - 1];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
class ByteArrayNoThrowOutputStream extends ByteArrayOutputStream {
|
||||||
|
public ByteArrayNoThrowOutputStream() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteArrayNoThrowOutputStream(int size) {
|
||||||
|
super(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b) {
|
||||||
|
write(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
|
||||||
|
import javax.security.auth.DestroyFailedException;
|
||||||
|
|
||||||
|
final class KeyPair {
|
||||||
|
private final PrivateKey mPrivateKey;
|
||||||
|
private final Certificate mCertificate;
|
||||||
|
|
||||||
|
public KeyPair(PrivateKey privateKey, Certificate certificate) {
|
||||||
|
mPrivateKey = privateKey;
|
||||||
|
mCertificate = certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivateKey getPrivateKey() {
|
||||||
|
return mPrivateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicKey getPublicKey() {
|
||||||
|
return mCertificate.getPublicKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Certificate getCertificate() {
|
||||||
|
return mCertificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroy() throws DestroyFailedException {
|
||||||
|
try {
|
||||||
|
mPrivateKey.destroy();
|
||||||
|
} catch (NoSuchMethodError ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,271 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local services extracted from the <a href="https://cs.android.com/android/platform/superproject/+/master:packages/modules/adb/client/commandline.cpp">ADB client</a>
|
||||||
|
* for easy access.
|
||||||
|
*/
|
||||||
|
public class LocalServices {
|
||||||
|
static final int SERVICE_FIRST = 1;
|
||||||
|
|
||||||
|
public static final int SHELL = 1;
|
||||||
|
/**
|
||||||
|
* Remount the device's filesystem in read-write mode, instead of read-only. This is usually necessary before
|
||||||
|
* performing an {@link #SYNC} request. This request may not succeed on certain builds which do not allow that.
|
||||||
|
* <p>
|
||||||
|
* This essentially executes {@code /system/bin/remount} command. Additional arguments such as {@code -R} can be
|
||||||
|
* passed too.
|
||||||
|
*/
|
||||||
|
public static final int REMOUNT = 2;
|
||||||
|
public static final int FILE = 3;
|
||||||
|
public static final int TCP_CONNECT = 4;
|
||||||
|
public static final int LOCAL_UNIX_SOCKET = 5;
|
||||||
|
public static final int LOCAL_UNIX_SOCKET_RESERVED = 6;
|
||||||
|
public static final int LOCAL_UNIX_SOCKET_ABSTRACT = 7;
|
||||||
|
public static final int LOCAL_UNIX_SOCKET_FILE_SYSTEM = 8;
|
||||||
|
/**
|
||||||
|
* Receive snapshots of the framebuffer. It requires sufficient privileges (or the connection is closed immediately)
|
||||||
|
* but works as follows:
|
||||||
|
* <p>
|
||||||
|
* After an {@link AdbStream} is opened, ADB daemon sends a 16-byte binary structure containing the following fields
|
||||||
|
* (little-endian format):
|
||||||
|
* <pre>
|
||||||
|
* uint32_t depth; // framebuffer depth = 16
|
||||||
|
* uint32_t size; // framebuffer size in bytes = 2 * width * height
|
||||||
|
* uint32_t width; // framebuffer width in pixels
|
||||||
|
* uint32_t height; // framebuffer height in pixels
|
||||||
|
* </pre>
|
||||||
|
* After that, each time a snapshot is wanted, one byte should be sent through the channel, which will trigger the
|
||||||
|
* daemon to send {@code size} bytes of framebuffer data.
|
||||||
|
*/
|
||||||
|
public static final int FRAMEBUFFER = 9;
|
||||||
|
/**
|
||||||
|
* Connects to the JDWP thread running in the VM of process PID (specified as an argument).
|
||||||
|
*/
|
||||||
|
public static final int CONNECT_JDWP = 10;
|
||||||
|
/**
|
||||||
|
* Receive the list of JDWP PIDs periodically. The format of the returned data is the following (in order):
|
||||||
|
* <ol>
|
||||||
|
* <li> {@code hex4}: The length of all content as a 4-char hexadecimal string i.e. {@code %04zx}.
|
||||||
|
* <li> {@code content}: A series of ASCII lines of the following format:
|
||||||
|
* <pre>
|
||||||
|
* <pid> "\n"
|
||||||
|
* </pre>
|
||||||
|
* </ol>
|
||||||
|
* This service is used by DDMS to know which debuggable processes are running on the device/emulator.
|
||||||
|
* <p>
|
||||||
|
* Note that there is no single-shot service to retrieve the list only once.
|
||||||
|
*/
|
||||||
|
public static final int TRACK_JDWP = 11;
|
||||||
|
public static final int SYNC = 12;
|
||||||
|
/**
|
||||||
|
* Reverse socket connections from the device running ADB daemon to this client. This should not be used if both
|
||||||
|
* the ADB daemon and the client are in the same device.
|
||||||
|
* <p>
|
||||||
|
* It takes an additional argument called {@code forward-command}. It can be one of the following:
|
||||||
|
* <ul>
|
||||||
|
* <li> {@code list-forward}: List all forwarded connections from the device
|
||||||
|
* This returns something that looks like the following:
|
||||||
|
* <ol>
|
||||||
|
* <li> {@code hex4}: The length of the payload, as 4 hexadecimal chars i.e. {@code %04zx}.
|
||||||
|
* <li> {@code payload}: A series of lines of the following format:
|
||||||
|
* <pre>
|
||||||
|
* host " " <local> " " <remote> "\n"
|
||||||
|
* </pre>
|
||||||
|
* Where <local> is the device-specific endpoint (e.g. {@code tcp:9000}), and <remote> is the
|
||||||
|
* client-specific endpoint.
|
||||||
|
* </ol>
|
||||||
|
* <li> forward:<local>;<remote>
|
||||||
|
* <li> forward:norebind:<local>;<remote>
|
||||||
|
* <li> killforward-all
|
||||||
|
* <li> killforward:<local>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public static final int REVERSE = 13;
|
||||||
|
/**
|
||||||
|
* Backup some or all packages installed in the device. For this to work, {@code allowBackup=true} must be present
|
||||||
|
* in the application section of the AndroidManifest.xml of the app.
|
||||||
|
* <p>
|
||||||
|
* It takes additional arguments which can be one of the following:
|
||||||
|
* <ul>
|
||||||
|
* <li>List of packages (as array)
|
||||||
|
* <li>{@code -all}
|
||||||
|
* <li>{@code -shared}
|
||||||
|
* </ul>
|
||||||
|
* Output is a stream which is in zlib format with 24 bytes at the front (if unencrypted).
|
||||||
|
*/
|
||||||
|
public static final int BACKUP = 14;
|
||||||
|
/**
|
||||||
|
* Restore a backup. Input is a stream which is in zlib format with 24 bytes at the front (if unencrypted).
|
||||||
|
*/
|
||||||
|
public static final int RESTORE = 15;
|
||||||
|
|
||||||
|
static final int SERVICE_LAST = 15;
|
||||||
|
|
||||||
|
@IntDef({
|
||||||
|
SHELL,
|
||||||
|
REMOUNT,
|
||||||
|
FILE,
|
||||||
|
TCP_CONNECT,
|
||||||
|
LOCAL_UNIX_SOCKET,
|
||||||
|
LOCAL_UNIX_SOCKET_RESERVED,
|
||||||
|
LOCAL_UNIX_SOCKET_ABSTRACT,
|
||||||
|
LOCAL_UNIX_SOCKET_FILE_SYSTEM,
|
||||||
|
FRAMEBUFFER,
|
||||||
|
CONNECT_JDWP,
|
||||||
|
TRACK_JDWP,
|
||||||
|
SYNC,
|
||||||
|
REVERSE,
|
||||||
|
BACKUP,
|
||||||
|
RESTORE,
|
||||||
|
})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface Services {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
static String getServiceName(@Services int service) {
|
||||||
|
switch (service) {
|
||||||
|
case SHELL:
|
||||||
|
return "shell:";
|
||||||
|
case CONNECT_JDWP:
|
||||||
|
return "jdwp:";
|
||||||
|
case FILE:
|
||||||
|
return "dev:";
|
||||||
|
case FRAMEBUFFER:
|
||||||
|
return "framebuffer:";
|
||||||
|
case LOCAL_UNIX_SOCKET:
|
||||||
|
return "local:";
|
||||||
|
case LOCAL_UNIX_SOCKET_ABSTRACT:
|
||||||
|
return "localabstract:";
|
||||||
|
case LOCAL_UNIX_SOCKET_FILE_SYSTEM:
|
||||||
|
return "localfilesystem:";
|
||||||
|
case LOCAL_UNIX_SOCKET_RESERVED:
|
||||||
|
return "localreserved:";
|
||||||
|
case REMOUNT:
|
||||||
|
return "remount:";
|
||||||
|
case REVERSE:
|
||||||
|
return "reverse:";
|
||||||
|
case SYNC:
|
||||||
|
return "sync:";
|
||||||
|
case TCP_CONNECT:
|
||||||
|
return "tcp:";
|
||||||
|
case TRACK_JDWP:
|
||||||
|
return "track-jdwp";
|
||||||
|
case BACKUP:
|
||||||
|
return "backup:";
|
||||||
|
case RESTORE:
|
||||||
|
return "restore:";
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid service: " + service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
static String getDestination(@Services int service, @NonNull String... args) {
|
||||||
|
String serviceName = getServiceName(service);
|
||||||
|
StringBuilder destination = new StringBuilder(serviceName);
|
||||||
|
switch (service) {
|
||||||
|
case SHELL:
|
||||||
|
for (String arg : args) {
|
||||||
|
if (arg.contains("\"")) {
|
||||||
|
throw new IllegalArgumentException("Arguments for inline shell cannot contain double" +
|
||||||
|
" quotations.");
|
||||||
|
}
|
||||||
|
if (arg.contains(" ")) {
|
||||||
|
destination.append("\"").append(Objects.requireNonNull(arg)).append("\"");
|
||||||
|
} else destination.append(Objects.requireNonNull(arg));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FILE:
|
||||||
|
if (args.length == 0) {
|
||||||
|
throw new IllegalArgumentException("File name must be specified.");
|
||||||
|
} else if (args.length != 1) {
|
||||||
|
throw new IllegalArgumentException("Service expects exactly one argument, " + args.length
|
||||||
|
+ " supplied.");
|
||||||
|
}
|
||||||
|
destination.append(Objects.requireNonNull(args[0]));
|
||||||
|
break;
|
||||||
|
case TCP_CONNECT:
|
||||||
|
if (args.length == 0) {
|
||||||
|
throw new IllegalArgumentException("Port number must be specified.");
|
||||||
|
} else if (args.length == 1) {
|
||||||
|
destination.append(args[0]);
|
||||||
|
} else if (args.length == 2) {
|
||||||
|
destination.append(Objects.requireNonNull(args[0]))
|
||||||
|
.append(':')
|
||||||
|
.append(Objects.requireNonNull(args[1]));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Invalid number of arguments supplied.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LOCAL_UNIX_SOCKET:
|
||||||
|
case LOCAL_UNIX_SOCKET_ABSTRACT:
|
||||||
|
case LOCAL_UNIX_SOCKET_FILE_SYSTEM:
|
||||||
|
case LOCAL_UNIX_SOCKET_RESERVED:
|
||||||
|
if (args.length == 0) {
|
||||||
|
throw new IllegalArgumentException("Path must be specified.");
|
||||||
|
} else if (args.length != 1) {
|
||||||
|
throw new IllegalArgumentException("Service expects exactly one argument, " + args.length
|
||||||
|
+ " supplied.");
|
||||||
|
}
|
||||||
|
destination.append(Objects.requireNonNull(args[0]));
|
||||||
|
break;
|
||||||
|
case CONNECT_JDWP:
|
||||||
|
if (args.length == 0) {
|
||||||
|
throw new IllegalArgumentException("PID must be specified.");
|
||||||
|
} else if (args.length != 1) {
|
||||||
|
throw new IllegalArgumentException("Service expects exactly one argument, " + args.length
|
||||||
|
+ " supplied.");
|
||||||
|
}
|
||||||
|
destination.append(Objects.requireNonNull(args[0]));
|
||||||
|
break;
|
||||||
|
case REVERSE:
|
||||||
|
if (args.length == 0) {
|
||||||
|
throw new IllegalArgumentException("Forward command must be specified.");
|
||||||
|
} else if (args.length != 1) {
|
||||||
|
throw new IllegalArgumentException("Service expects exactly one argument, " + args.length
|
||||||
|
+ " supplied.");
|
||||||
|
}
|
||||||
|
if (args[0] == null) {
|
||||||
|
throw new IllegalArgumentException("Forward command is empty");
|
||||||
|
}
|
||||||
|
if ("list-forward".equals(args[0]) || "killforward-all".equals(args[0])) {
|
||||||
|
destination.append(args[0]);
|
||||||
|
} else if (args[0].startsWith("forward:") || args[0].startsWith("killforward:")) {
|
||||||
|
destination.append(args[0]);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Invalid forward command.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BACKUP:
|
||||||
|
if (args.length == 0) {
|
||||||
|
throw new IllegalArgumentException("At least one package must be specified or use -shared/-all.");
|
||||||
|
}
|
||||||
|
case REMOUNT:
|
||||||
|
// Additional arguments for the commands
|
||||||
|
destination.append(TextUtils.join(" ", args));
|
||||||
|
break;
|
||||||
|
case RESTORE:
|
||||||
|
case FRAMEBUFFER:
|
||||||
|
case SYNC:
|
||||||
|
case TRACK_JDWP:
|
||||||
|
if (args.length != 0) {
|
||||||
|
throw new IllegalArgumentException("Service expects no arguments.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return destination.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,319 @@
|
|||||||
|
// SPDX-License-Identifier: MIT AND (GPL-3.0-or-later OR Apache-2.0)
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.GuardedBy;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.SecureRandomSpi;
|
||||||
|
import java.security.Security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixes for the output of the default PRNG having low entropy.
|
||||||
|
* <p>
|
||||||
|
* The fixes need to be applied via {@link #apply()} before any use of Java
|
||||||
|
* Cryptography Architecture primitives. A good place to invoke them is in the
|
||||||
|
* application's {@code onCreate}.
|
||||||
|
*/
|
||||||
|
// Copyright 2013 Google Inc.
|
||||||
|
public final class PRNGFixes {
|
||||||
|
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hidden constructor to prevent instantiation.
|
||||||
|
*/
|
||||||
|
private PRNGFixes() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies all fixes.
|
||||||
|
*
|
||||||
|
* @throws SecurityException if a fix is needed but could not be applied.
|
||||||
|
*/
|
||||||
|
public static void apply() {
|
||||||
|
applyOpenSSLFix();
|
||||||
|
installLinuxPRNGSecureRandom();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
|
||||||
|
* fix is not needed.
|
||||||
|
*
|
||||||
|
* @throws SecurityException if the fix is needed but could not be applied.
|
||||||
|
*/
|
||||||
|
private static void applyOpenSSLFix() throws SecurityException {
|
||||||
|
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
|| (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2)) {
|
||||||
|
// No need to apply the fix
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Mix in the device- and invocation-specific seed.
|
||||||
|
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||||
|
.getMethod("RAND_seed", byte[].class)
|
||||||
|
.invoke(null, generateSeed());
|
||||||
|
|
||||||
|
// Mix output of Linux PRNG into OpenSSL's PRNG
|
||||||
|
int bytesRead = (Integer) Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||||
|
.getMethod("RAND_load_file", String.class, long.class)
|
||||||
|
.invoke(null, "/dev/urandom", 1024);
|
||||||
|
if (bytesRead != 1024) {
|
||||||
|
throw new IOException("Unexpected number of bytes read from Linux PRNG: " + bytesRead);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
|
||||||
|
* default. Does nothing if the implementation is already the default or if
|
||||||
|
* there is not need to install the implementation.
|
||||||
|
*
|
||||||
|
* @throws SecurityException if the fix is needed but could not be applied.
|
||||||
|
*/
|
||||||
|
private static void installLinuxPRNGSecureRandom()
|
||||||
|
throws SecurityException {
|
||||||
|
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||||
|
// No need to apply the fix
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install a Linux PRNG-based SecureRandom implementation as the
|
||||||
|
// default, if not yet installed.
|
||||||
|
Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
|
||||||
|
if ((secureRandomProviders == null)
|
||||||
|
|| (secureRandomProviders.length < 1)
|
||||||
|
|| (!LinuxPRNGSecureRandomProvider.class.equals(secureRandomProviders[0].getClass()))) {
|
||||||
|
Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that new SecureRandom() and
|
||||||
|
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
|
||||||
|
// by the Linux PRNG-based SecureRandom implementation.
|
||||||
|
SecureRandom rng1 = new SecureRandom();
|
||||||
|
if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider().getClass())) {
|
||||||
|
throw new SecurityException("new SecureRandom() backed by wrong Provider: "
|
||||||
|
+ rng1.getProvider().getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
SecureRandom rng2;
|
||||||
|
try {
|
||||||
|
rng2 = SecureRandom.getInstance("SHA1PRNG");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new SecurityException("SHA1PRNG not available", e);
|
||||||
|
}
|
||||||
|
if (!LinuxPRNGSecureRandomProvider.class.equals(
|
||||||
|
rng2.getProvider().getClass())) {
|
||||||
|
throw new SecurityException("SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong provider: "
|
||||||
|
+ rng2.getProvider().getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code Provider} of {@code SecureRandom} engines which pass through
|
||||||
|
* all requests to the Linux PRNG.
|
||||||
|
*/
|
||||||
|
private static class LinuxPRNGSecureRandomProvider extends Provider {
|
||||||
|
|
||||||
|
public LinuxPRNGSecureRandomProvider() {
|
||||||
|
super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses /dev/urandom");
|
||||||
|
// Although /dev/urandom is not a SHA-1 PRNG, some apps
|
||||||
|
// explicitly request a SHA1PRNG SecureRandom and we thus need to
|
||||||
|
// prevent them from getting the default implementation whose output
|
||||||
|
// may have low entropy.
|
||||||
|
put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
|
||||||
|
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link SecureRandomSpi} which passes all requests to the Linux PRNG
|
||||||
|
* ({@code /dev/urandom}).
|
||||||
|
*/
|
||||||
|
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
|
||||||
|
* are passed through to the Linux PRNG (/dev/urandom). Instances of
|
||||||
|
* this class seed themselves by mixing in the current time, PID, UID,
|
||||||
|
* build fingerprint, and hardware serial number (where available) into
|
||||||
|
* Linux PRNG.
|
||||||
|
*
|
||||||
|
* Concurrency: Read requests to the underlying Linux PRNG are
|
||||||
|
* serialized (on sLock) to ensure that multiple threads do not get
|
||||||
|
* duplicated PRNG output.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static final File URANDOM_FILE = new File("/dev/urandom");
|
||||||
|
|
||||||
|
private static final Object sLock = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input stream for reading from Linux PRNG or {@code null} if not yet
|
||||||
|
* opened.
|
||||||
|
*/
|
||||||
|
@GuardedBy("sLock")
|
||||||
|
private static DataInputStream sUrandomIn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output stream for writing to Linux PRNG or {@code null} if not yet
|
||||||
|
* opened.
|
||||||
|
*/
|
||||||
|
@GuardedBy("sLock")
|
||||||
|
private static OutputStream sUrandomOut;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this engine instance has been seeded. This is needed because
|
||||||
|
* each instance needs to seed itself if the client does not explicitly
|
||||||
|
* seed it.
|
||||||
|
*/
|
||||||
|
private boolean mSeeded;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineSetSeed(byte[] bytes) {
|
||||||
|
try {
|
||||||
|
OutputStream out;
|
||||||
|
synchronized (sLock) {
|
||||||
|
out = getUrandomOutputStream();
|
||||||
|
}
|
||||||
|
out.write(bytes);
|
||||||
|
out.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// On a small fraction of devices /dev/urandom is not writable.
|
||||||
|
// Log and ignore.
|
||||||
|
Log.w(PRNGFixes.class.getSimpleName(),
|
||||||
|
"Failed to mix seed into " + URANDOM_FILE);
|
||||||
|
} finally {
|
||||||
|
mSeeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineNextBytes(byte[] bytes) {
|
||||||
|
if (!mSeeded) {
|
||||||
|
// Mix in the device- and invocation-specific seed.
|
||||||
|
engineSetSeed(generateSeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DataInputStream in;
|
||||||
|
synchronized (sLock) {
|
||||||
|
in = getUrandomInputStream();
|
||||||
|
}
|
||||||
|
synchronized (in) {
|
||||||
|
in.readFully(bytes);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SecurityException(
|
||||||
|
"Failed to read from " + URANDOM_FILE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] engineGenerateSeed(int size) {
|
||||||
|
byte[] seed = new byte[size];
|
||||||
|
engineNextBytes(seed);
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataInputStream getUrandomInputStream() {
|
||||||
|
synchronized (sLock) {
|
||||||
|
if (sUrandomIn == null) {
|
||||||
|
// NOTE: Consider inserting a BufferedInputStream between
|
||||||
|
// DataInputStream and FileInputStream if you need higher
|
||||||
|
// PRNG output performance and can live with future PRNG
|
||||||
|
// output being pulled into this process prematurely.
|
||||||
|
try {
|
||||||
|
sUrandomIn = new DataInputStream(
|
||||||
|
new FileInputStream(URANDOM_FILE));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SecurityException("Failed to open "
|
||||||
|
+ URANDOM_FILE + " for reading", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sUrandomIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputStream getUrandomOutputStream() throws IOException {
|
||||||
|
synchronized (sLock) {
|
||||||
|
if (sUrandomOut == null) {
|
||||||
|
sUrandomOut = new FileOutputStream(URANDOM_FILE);
|
||||||
|
}
|
||||||
|
return sUrandomOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a device- and invocation-specific seed to be mixed into the
|
||||||
|
* Linux PRNG.
|
||||||
|
*/
|
||||||
|
private static byte[] generateSeed() {
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream seedBufferOut =
|
||||||
|
new DataOutputStream(seedBuffer);
|
||||||
|
seedBufferOut.writeLong(System.currentTimeMillis());
|
||||||
|
seedBufferOut.writeLong(System.nanoTime());
|
||||||
|
seedBufferOut.writeInt(Process.myPid());
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BASE_1_1) {
|
||||||
|
seedBufferOut.writeInt(Process.myUid());
|
||||||
|
}
|
||||||
|
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
|
||||||
|
seedBufferOut.close();
|
||||||
|
return seedBuffer.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SecurityException("Failed to generate seed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the hardware serial number of this device.
|
||||||
|
*
|
||||||
|
* @return serial number or {@code null} if not available.
|
||||||
|
*/
|
||||||
|
private static String getDeviceSerialNumber() {
|
||||||
|
// We're using the Reflection API because Build.SERIAL is only available
|
||||||
|
// since API Level 9 (Gingerbread, Android 2.3).
|
||||||
|
try {
|
||||||
|
return (String) Build.class.getField("SERIAL").get(null);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getBuildFingerprintAndDeviceSerial() {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
String fingerprint = Build.FINGERPRINT;
|
||||||
|
if (fingerprint != null) {
|
||||||
|
result.append(fingerprint);
|
||||||
|
}
|
||||||
|
String serial = getDeviceSerialNumber();
|
||||||
|
if (serial != null) {
|
||||||
|
result.append(serial);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return result.toString().getBytes("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException("UTF-8 encoding not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||||
|
import org.bouncycastle.crypto.engines.AESEngine;
|
||||||
|
import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
|
||||||
|
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.modes.GCMModeCipher;
|
||||||
|
import org.bouncycastle.crypto.params.AEADParameters;
|
||||||
|
import org.bouncycastle.crypto.params.HKDFParameters;
|
||||||
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import javax.security.auth.Destroyable;
|
||||||
|
|
||||||
|
import io.github.muntashirakon.crypto.spake2.Spake2Context;
|
||||||
|
import io.github.muntashirakon.crypto.spake2.Spake2Role;
|
||||||
|
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.GINGERBREAD)
|
||||||
|
class PairingAuthCtx implements Destroyable {
|
||||||
|
// The following values are taken from the following source and are subjected to change
|
||||||
|
// https://github.com/aosp-mirror/platform_system_core/blob/android-11.0.0_r1/adb/pairing_auth/pairing_auth.cpp
|
||||||
|
private static final byte[] CLIENT_NAME = StringCompat.getBytes("adb pair client\u0000", "UTF-8");
|
||||||
|
private static final byte[] SERVER_NAME = StringCompat.getBytes("adb pair server\u0000", "UTF-8");
|
||||||
|
|
||||||
|
// The following values are taken from the following source and are subjected to change
|
||||||
|
// https://github.com/aosp-mirror/platform_system_core/blob/android-11.0.0_r1/adb/pairing_auth/aes_128_gcm.cpp
|
||||||
|
private static final byte[] INFO = StringCompat.getBytes("adb pairing_auth aes-128-gcm key", "UTF-8");
|
||||||
|
private static final int HKDF_KEY_LENGTH = 128 / 8;
|
||||||
|
public static final int GCM_IV_LENGTH = 12; // in bytes
|
||||||
|
|
||||||
|
private final byte[] mMsg;
|
||||||
|
private final Spake2Context mSpake2Ctx;
|
||||||
|
private final byte[] mSecretKey = new byte[HKDF_KEY_LENGTH];
|
||||||
|
private long mDecIv = 0;
|
||||||
|
private long mEncIv = 0;
|
||||||
|
private boolean mIsDestroyed = false;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static PairingAuthCtx createAlice(byte[] password) {
|
||||||
|
Spake2Context spake25519 = new Spake2Context(Spake2Role.Alice, CLIENT_NAME, SERVER_NAME);
|
||||||
|
try {
|
||||||
|
return new PairingAuthCtx(spake25519, password);
|
||||||
|
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
public static PairingAuthCtx createBob(byte[] password) {
|
||||||
|
Spake2Context spake25519 = new Spake2Context(Spake2Role.Bob, SERVER_NAME, CLIENT_NAME);
|
||||||
|
try {
|
||||||
|
return new PairingAuthCtx(spake25519, password);
|
||||||
|
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PairingAuthCtx(Spake2Context spake25519, byte[] password)
|
||||||
|
throws IllegalArgumentException, IllegalStateException {
|
||||||
|
mSpake2Ctx = spake25519;
|
||||||
|
mMsg = mSpake2Ctx.generateMessage(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getMsg() {
|
||||||
|
return mMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean initCipher(byte[] theirMsg) throws IllegalArgumentException, IllegalStateException {
|
||||||
|
if (mIsDestroyed) return false;
|
||||||
|
byte[] keyMaterial = mSpake2Ctx.processMessage(theirMsg);
|
||||||
|
if (keyMaterial == null) return false;
|
||||||
|
HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA256Digest());
|
||||||
|
hkdf.init(new HKDFParameters(keyMaterial, null, INFO));
|
||||||
|
hkdf.generateBytes(mSecretKey, 0, mSecretKey.length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public byte[] encrypt(@NonNull byte[] in) {
|
||||||
|
return encryptDecrypt(true, in, ByteBuffer.allocate(GCM_IV_LENGTH)
|
||||||
|
.order(ByteOrder.LITTLE_ENDIAN).putLong(mEncIv++).array());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public byte[] decrypt(@NonNull byte[] in) {
|
||||||
|
return encryptDecrypt(false, in, ByteBuffer.allocate(GCM_IV_LENGTH)
|
||||||
|
.order(ByteOrder.LITTLE_ENDIAN).putLong(mDecIv++).array());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDestroyed() {
|
||||||
|
return mIsDestroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
mIsDestroyed = true;
|
||||||
|
Arrays.fill(mSecretKey, (byte) 0);
|
||||||
|
mSpake2Ctx.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private byte[] encryptDecrypt(boolean forEncryption, @NonNull byte[] in, @NonNull byte[] iv) {
|
||||||
|
if (mIsDestroyed) return null;
|
||||||
|
AEADParameters spec = new AEADParameters(new KeyParameter(mSecretKey), mSecretKey.length * 8, iv);
|
||||||
|
GCMModeCipher cipher = GCMBlockCipher.newInstance(AESEngine.newInstance());
|
||||||
|
cipher.init(forEncryption, spec);
|
||||||
|
byte[] out = new byte[cipher.getOutputSize(in.length)];
|
||||||
|
int newOffset = cipher.processBytes(in, 0, in.length, out, 0);
|
||||||
|
try {
|
||||||
|
cipher.doFinal(out, newOffset);
|
||||||
|
} catch (InvalidCipherTextException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,384 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLException;
|
||||||
|
import javax.net.ssl.SSLServerSocket;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
|
||||||
|
// https://github.com/aosp-mirror/platform_system_core/blob/android-11.0.0_r1/adb/pairing_connection/pairing_connection.cpp
|
||||||
|
// Also based on Shizuku's implementation
|
||||||
|
@RequiresApi(Build.VERSION_CODES.GINGERBREAD)
|
||||||
|
public final class PairingConnectionCtx implements Closeable {
|
||||||
|
public static final String TAG = PairingConnectionCtx.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final String EXPORTED_KEY_LABEL = "adb-label\u0000";
|
||||||
|
public static final int EXPORT_KEY_SIZE = 64;
|
||||||
|
|
||||||
|
private enum State {
|
||||||
|
Ready,
|
||||||
|
ExchangingMsgs,
|
||||||
|
ExchangingPeerInfo,
|
||||||
|
Stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Role {
|
||||||
|
Client,
|
||||||
|
Server,
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String mHost;
|
||||||
|
private final int mPort;
|
||||||
|
private final byte[] mPswd;
|
||||||
|
private final PeerInfo mPeerInfo;
|
||||||
|
private final SSLContext mSslContext;
|
||||||
|
private final Role mRole = Role.Client;
|
||||||
|
|
||||||
|
private DataInputStream mInputStream;
|
||||||
|
private DataOutputStream mOutputStream;
|
||||||
|
private PairingAuthCtx mPairingAuthCtx;
|
||||||
|
private State mState = State.Ready;
|
||||||
|
|
||||||
|
public PairingConnectionCtx(@NonNull String host, int port, @NonNull byte[] pswd, @NonNull KeyPair keyPair,
|
||||||
|
@NonNull String deviceName)
|
||||||
|
throws NoSuchAlgorithmException, KeyManagementException, InvalidKeyException {
|
||||||
|
this.mHost = Objects.requireNonNull(host);
|
||||||
|
this.mPort = port;
|
||||||
|
this.mPswd = Objects.requireNonNull(pswd);
|
||||||
|
this.mPeerInfo = new PeerInfo(PeerInfo.ADB_RSA_PUB_KEY, AndroidPubkey.encodeWithName((RSAPublicKey)
|
||||||
|
keyPair.getPublicKey(), Objects.requireNonNull(deviceName)));
|
||||||
|
this.mSslContext = SslUtils.getSslContext(keyPair);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PairingConnectionCtx(@NonNull String host, int port, @NonNull byte[] pswd, @NonNull PrivateKey privateKey,
|
||||||
|
@NonNull Certificate certificate, @NonNull String deviceName)
|
||||||
|
throws NoSuchAlgorithmException, KeyManagementException, InvalidKeyException {
|
||||||
|
this(host, port, pswd, new KeyPair(Objects.requireNonNull(privateKey), Objects.requireNonNull(certificate)),
|
||||||
|
deviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws IOException {
|
||||||
|
if (mState != State.Ready) {
|
||||||
|
throw new IOException("Connection is not ready yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
mState = State.ExchangingMsgs;
|
||||||
|
|
||||||
|
// Start worker
|
||||||
|
setupTlsConnection();
|
||||||
|
|
||||||
|
for (; ; ) {
|
||||||
|
switch (mState) {
|
||||||
|
case ExchangingMsgs:
|
||||||
|
if (!doExchangeMsgs()) {
|
||||||
|
notifyResult();
|
||||||
|
throw new IOException("Exchanging message wasn't successful.");
|
||||||
|
}
|
||||||
|
mState = State.ExchangingPeerInfo;
|
||||||
|
break;
|
||||||
|
case ExchangingPeerInfo:
|
||||||
|
if (!doExchangePeerInfo()) {
|
||||||
|
notifyResult();
|
||||||
|
throw new IOException("Could not exchange peer info.");
|
||||||
|
}
|
||||||
|
notifyResult();
|
||||||
|
return;
|
||||||
|
case Ready:
|
||||||
|
case Stopped:
|
||||||
|
throw new IOException("Connection closed with errors.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyResult() {
|
||||||
|
mState = State.Stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTlsConnection() throws IOException {
|
||||||
|
Socket socket;
|
||||||
|
if (mRole == Role.Server) {
|
||||||
|
SSLServerSocket sslServerSocket = (SSLServerSocket) mSslContext.getServerSocketFactory().createServerSocket(mPort);
|
||||||
|
socket = sslServerSocket.accept();
|
||||||
|
// TODO: Write automated test scripts after removing Conscrypt dependency.
|
||||||
|
} else { // role == Role.Client
|
||||||
|
socket = new Socket(mHost, mPort);
|
||||||
|
}
|
||||||
|
socket.setTcpNoDelay(true);
|
||||||
|
|
||||||
|
// We use custom SSLContext to allow any SSL certificates
|
||||||
|
SSLSocket sslSocket = (SSLSocket) mSslContext.getSocketFactory().createSocket(socket, mHost, mPort, true);
|
||||||
|
sslSocket.startHandshake();
|
||||||
|
Log.d(TAG, "Handshake succeeded.");
|
||||||
|
|
||||||
|
mInputStream = new DataInputStream(sslSocket.getInputStream());
|
||||||
|
mOutputStream = new DataOutputStream(sslSocket.getOutputStream());
|
||||||
|
|
||||||
|
// To ensure the connection is not stolen while we do the PAKE, append the exported key material from the
|
||||||
|
// tls connection to the password.
|
||||||
|
byte[] keyMaterial = exportKeyingMaterial(sslSocket, EXPORT_KEY_SIZE);
|
||||||
|
byte[] passwordBytes = new byte[mPswd.length + keyMaterial.length];
|
||||||
|
System.arraycopy(mPswd, 0, passwordBytes, 0, mPswd.length);
|
||||||
|
System.arraycopy(keyMaterial, 0, passwordBytes, mPswd.length, keyMaterial.length);
|
||||||
|
|
||||||
|
PairingAuthCtx pairingAuthCtx = PairingAuthCtx.createAlice(passwordBytes);
|
||||||
|
if (pairingAuthCtx == null) {
|
||||||
|
throw new IOException("Unable to create PairingAuthCtx.");
|
||||||
|
}
|
||||||
|
this.mPairingAuthCtx = pairingAuthCtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("PrivateApi") // Conscrypt is a stable private API
|
||||||
|
private byte[] exportKeyingMaterial(SSLSocket sslSocket, int length) throws SSLException {
|
||||||
|
// Conscrypt#exportKeyingMaterial(SSLSocket socket, String label, byte[] context, int length): byte[]
|
||||||
|
// throws SSLException
|
||||||
|
try {
|
||||||
|
Class<?> conscryptClass;
|
||||||
|
if (SslUtils.isCustomConscrypt()) {
|
||||||
|
conscryptClass = Class.forName("org.conscrypt.Conscrypt");
|
||||||
|
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
// Although support for conscrypt has been added in Android 5.0 (Lollipop),
|
||||||
|
// TLS1.3 isn't supported until Android 9 (Pie).
|
||||||
|
throw new SSLException("TLSv1.3 isn't supported on your platform. Use custom Conscrypt library instead.");
|
||||||
|
} else {
|
||||||
|
conscryptClass = Class.forName("com.android.org.conscrypt.Conscrypt");
|
||||||
|
}
|
||||||
|
Method exportKeyingMaterial = conscryptClass.getMethod("exportKeyingMaterial", SSLSocket.class,
|
||||||
|
String.class, byte[].class, int.class);
|
||||||
|
return (byte[]) exportKeyingMaterial.invoke(null, sslSocket, EXPORTED_KEY_LABEL, null, length);
|
||||||
|
} catch (SSLException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Throwable th) {
|
||||||
|
throw new SSLException(th);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeHeader(@NonNull PairingPacketHeader header, @NonNull byte[] payload) throws IOException {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(PairingPacketHeader.PAIRING_PACKET_HEADER_SIZE)
|
||||||
|
.order(ByteOrder.BIG_ENDIAN);
|
||||||
|
header.writeTo(buffer);
|
||||||
|
|
||||||
|
mOutputStream.write(buffer.array());
|
||||||
|
mOutputStream.write(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private PairingPacketHeader readHeader() throws IOException {
|
||||||
|
byte[] bytes = new byte[PairingPacketHeader.PAIRING_PACKET_HEADER_SIZE];
|
||||||
|
mInputStream.readFully(bytes);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
return PairingPacketHeader.readFrom(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private PairingPacketHeader createHeader(byte type, int payloadSize) {
|
||||||
|
return new PairingPacketHeader(PairingPacketHeader.CURRENT_KEY_HEADER_VERSION, type, payloadSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkHeaderType(byte expected, byte actual) {
|
||||||
|
if (expected != actual) {
|
||||||
|
Log.e(TAG, "Unexpected header type (expected=" + expected + " actual=" + actual + ")");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean doExchangeMsgs() throws IOException {
|
||||||
|
byte[] msg = mPairingAuthCtx.getMsg();
|
||||||
|
|
||||||
|
PairingPacketHeader ourHeader = createHeader(PairingPacketHeader.SPAKE2_MSG, msg.length);
|
||||||
|
// Write our SPAKE2 msg
|
||||||
|
writeHeader(ourHeader, msg);
|
||||||
|
|
||||||
|
// Read the peer's SPAKE2 msg header
|
||||||
|
PairingPacketHeader theirHeader = readHeader();
|
||||||
|
if (theirHeader == null || !checkHeaderType(PairingPacketHeader.SPAKE2_MSG, theirHeader.type)) return false;
|
||||||
|
|
||||||
|
// Read the SPAKE2 msg payload and initialize the cipher for encrypting the PeerInfo and certificate.
|
||||||
|
byte[] theirMsg = new byte[theirHeader.payloadSize];
|
||||||
|
mInputStream.readFully(theirMsg);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return mPairingAuthCtx.initCipher(theirMsg);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Unable to initialize pairing cipher");
|
||||||
|
//noinspection UnnecessaryInitCause
|
||||||
|
throw (IOException) new IOException().initCause(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean doExchangePeerInfo() throws IOException {
|
||||||
|
// Encrypt PeerInfo
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(PeerInfo.MAX_PEER_INFO_SIZE).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
mPeerInfo.writeTo(buffer);
|
||||||
|
byte[] outBuffer = mPairingAuthCtx.encrypt(buffer.array());
|
||||||
|
if (outBuffer == null) {
|
||||||
|
Log.e(TAG, "Failed to encrypt peer info");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out the packet header
|
||||||
|
PairingPacketHeader ourHeader = createHeader(PairingPacketHeader.PEER_INFO, outBuffer.length);
|
||||||
|
// Write out the encrypted payload
|
||||||
|
writeHeader(ourHeader, outBuffer);
|
||||||
|
|
||||||
|
// Read in the peer's packet header
|
||||||
|
PairingPacketHeader theirHeader = readHeader();
|
||||||
|
if (theirHeader == null || !checkHeaderType(PairingPacketHeader.PEER_INFO, theirHeader.type)) return false;
|
||||||
|
|
||||||
|
// Read in the encrypted peer certificate
|
||||||
|
byte[] theirMsg = new byte[theirHeader.payloadSize];
|
||||||
|
mInputStream.readFully(theirMsg);
|
||||||
|
|
||||||
|
// Try to decrypt the certificate
|
||||||
|
byte[] decryptedMsg = mPairingAuthCtx.decrypt(theirMsg);
|
||||||
|
if (decryptedMsg == null) {
|
||||||
|
Log.e(TAG, "Unsupported payload while decrypting peer info.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The decrypted message should contain the PeerInfo.
|
||||||
|
if (decryptedMsg.length != PeerInfo.MAX_PEER_INFO_SIZE) {
|
||||||
|
Log.e(TAG, "Got size=" + decryptedMsg.length + " PeerInfo.size=" + PeerInfo.MAX_PEER_INFO_SIZE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PeerInfo theirPeerInfo = PeerInfo.readFrom(ByteBuffer.wrap(decryptedMsg));
|
||||||
|
Log.d(TAG, theirPeerInfo.toString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
Arrays.fill(mPswd, (byte) 0);
|
||||||
|
try {
|
||||||
|
mInputStream.close();
|
||||||
|
} catch (IOException ignore) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mOutputStream.close();
|
||||||
|
} catch (IOException ignore) {
|
||||||
|
}
|
||||||
|
if (mState != State.Ready) {
|
||||||
|
mPairingAuthCtx.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PeerInfo {
|
||||||
|
public static final int MAX_PEER_INFO_SIZE = 1 << 13;
|
||||||
|
|
||||||
|
public static final byte ADB_RSA_PUB_KEY = 0;
|
||||||
|
public static final byte ADB_DEVICE_GUID = 0;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static PeerInfo readFrom(@NonNull ByteBuffer buffer) {
|
||||||
|
byte type = buffer.get();
|
||||||
|
byte[] data = new byte[MAX_PEER_INFO_SIZE - 1];
|
||||||
|
buffer.get(data);
|
||||||
|
return new PeerInfo(type, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final byte type;
|
||||||
|
private final byte[] data = new byte[MAX_PEER_INFO_SIZE - 1];
|
||||||
|
|
||||||
|
public PeerInfo(byte type, byte[] data) {
|
||||||
|
this.type = type;
|
||||||
|
System.arraycopy(data, 0, this.data, 0, Math.min(data.length, MAX_PEER_INFO_SIZE - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeTo(@NonNull ByteBuffer buffer) {
|
||||||
|
buffer.put(type).put(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PeerInfo{" +
|
||||||
|
"type=" + type +
|
||||||
|
", data=" + Arrays.toString(data) +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PairingPacketHeader {
|
||||||
|
public static final byte CURRENT_KEY_HEADER_VERSION = 1;
|
||||||
|
public static final byte MIN_SUPPORTED_KEY_HEADER_VERSION = 1;
|
||||||
|
public static final byte MAX_SUPPORTED_KEY_HEADER_VERSION = 1;
|
||||||
|
|
||||||
|
public static final int MAX_PAYLOAD_SIZE = 2 * PeerInfo.MAX_PEER_INFO_SIZE;
|
||||||
|
public static final byte PAIRING_PACKET_HEADER_SIZE = 6;
|
||||||
|
|
||||||
|
public static final byte SPAKE2_MSG = 0;
|
||||||
|
public static final byte PEER_INFO = 1;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static PairingPacketHeader readFrom(@NonNull ByteBuffer buffer) {
|
||||||
|
byte version = buffer.get();
|
||||||
|
byte type = buffer.get();
|
||||||
|
int payload = buffer.getInt();
|
||||||
|
if (version < MIN_SUPPORTED_KEY_HEADER_VERSION || version > MAX_SUPPORTED_KEY_HEADER_VERSION) {
|
||||||
|
Log.e(TAG, "PairingPacketHeader version mismatch (us=" + CURRENT_KEY_HEADER_VERSION
|
||||||
|
+ " them=" + version + ")");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (type != SPAKE2_MSG && type != PEER_INFO) {
|
||||||
|
Log.e(TAG, "Unknown PairingPacket type " + type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (payload <= 0 || payload > MAX_PAYLOAD_SIZE) {
|
||||||
|
Log.e(TAG, "Header payload not within a safe payload size (size=" + payload + ")");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new PairingPacketHeader(version, type, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final byte version;
|
||||||
|
private final byte type;
|
||||||
|
private final int payloadSize;
|
||||||
|
|
||||||
|
public PairingPacketHeader(byte version, byte type, int payloadSize) {
|
||||||
|
this.version = version;
|
||||||
|
this.type = type;
|
||||||
|
this.payloadSize = payloadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeTo(@NonNull ByteBuffer buffer) {
|
||||||
|
buffer.put(version).put(type).putInt(payloadSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PairingPacketHeader{" +
|
||||||
|
"version=" + version +
|
||||||
|
", type=" + type +
|
||||||
|
", payloadSize=" + payloadSize +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
final class SslUtils {
|
||||||
|
private static boolean customConscrypt = false;
|
||||||
|
private static SSLContext sslContext;
|
||||||
|
|
||||||
|
public static boolean isCustomConscrypt() {
|
||||||
|
return customConscrypt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("TrulyRandom") // The users are already instructed to fix this issue
|
||||||
|
@NonNull
|
||||||
|
public static SSLContext getSslContext(KeyPair keyPair) throws NoSuchAlgorithmException, KeyManagementException {
|
||||||
|
if (sslContext != null) {
|
||||||
|
return sslContext;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Class<?> providerClass = Class.forName("org.conscrypt.OpenSSLProvider");
|
||||||
|
Provider openSslProvder = (Provider) providerClass.getDeclaredConstructor().newInstance();
|
||||||
|
sslContext = SSLContext.getInstance("TLSv1.3", openSslProvder);
|
||||||
|
customConscrypt = true;
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
// Custom error message to inform user that they should use custom Conscrypt library.
|
||||||
|
throw new NoSuchAlgorithmException("TLSv1.3 isn't supported on your platform. Use custom Conscrypt library instead.");
|
||||||
|
}
|
||||||
|
sslContext = SSLContext.getInstance("TLSv1.3");
|
||||||
|
customConscrypt = false;
|
||||||
|
}
|
||||||
|
System.out.println("Using " + (customConscrypt ? "custom" : "default") + " TLSv1.3 provider...");
|
||||||
|
sslContext.init(new KeyManager[]{getKeyManager(keyPair)},
|
||||||
|
new X509TrustManager[]{getAllAcceptingTrustManager()},
|
||||||
|
new SecureRandom());
|
||||||
|
return sslContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static KeyManager getKeyManager(KeyPair keyPair) {
|
||||||
|
return new X509ExtendedKeyManager() {
|
||||||
|
private final String mAlias = "key";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getClientAliases(String keyType, Principal[] issuers) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
|
||||||
|
for (String keyType : keyTypes) {
|
||||||
|
if (keyType.equals("RSA")) return mAlias;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getServerAliases(String keyType, Principal[] issuers) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getCertificateChain(String alias) {
|
||||||
|
if (this.mAlias.equals(alias)) {
|
||||||
|
return new X509Certificate[]{(X509Certificate) keyPair.getCertificate()};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrivateKey getPrivateKey(String alias) {
|
||||||
|
if (this.mAlias.equals(alias)) {
|
||||||
|
return keyPair.getPrivateKey();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("TrustAllX509TrustManager") // Accept all certificates
|
||||||
|
@NonNull
|
||||||
|
private static X509TrustManager getAllAcceptingTrustManager() {
|
||||||
|
return new X509TrustManager() {
|
||||||
|
@Override
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return new X509Certificate[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.IllegalCharsetNameException;
|
||||||
|
|
||||||
|
final class StringCompat {
|
||||||
|
@NonNull
|
||||||
|
public static byte[] getBytes(@NonNull String text, @NonNull String charsetName) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
|
||||||
|
return text.getBytes(Charset.forName(charsetName));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return text.getBytes(charsetName);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw (IllegalCharsetNameException) new IllegalCharsetNameException("Illegal charset " + charsetName)
|
||||||
|
.initCause(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,189 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.android;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.nsd.NsdManager;
|
||||||
|
import android.net.nsd.NsdServiceInfo;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.annotation.StringDef;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatic discovery of ADB daemons.
|
||||||
|
*/
|
||||||
|
// Copyright 2020 南宫雪珊
|
||||||
|
// Copyright 2022 Muntashir Al-Islam
|
||||||
|
// Based on https://android.googlesource.com/platform/packages/modules/adb/+/eddd2d3a386a83f5d1e14f87a318adef4c2f1a9d/adb_mdns.cpp
|
||||||
|
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
public class AdbMdns {
|
||||||
|
public static final String SERVICE_TYPE_ADB = "adb";
|
||||||
|
public static final String SERVICE_TYPE_TLS_PAIRING = "adb-tls-pairing";
|
||||||
|
public static final String SERVICE_TYPE_TLS_CONNECT = "adb-tls-connect";
|
||||||
|
|
||||||
|
@StringDef({
|
||||||
|
SERVICE_TYPE_ADB,
|
||||||
|
SERVICE_TYPE_TLS_PAIRING,
|
||||||
|
SERVICE_TYPE_TLS_CONNECT,
|
||||||
|
})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
public @interface ServiceType {
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnAdbDaemonDiscoveredListener {
|
||||||
|
void onPortChanged(@Nullable InetAddress hostAddress, int port);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Context mContext;
|
||||||
|
@NonNull
|
||||||
|
private final String mServiceType;
|
||||||
|
@NonNull
|
||||||
|
private final OnAdbDaemonDiscoveredListener mAdbDaemonDiscoveredListener;
|
||||||
|
private final NsdManager.DiscoveryListener mDiscoveryListener;
|
||||||
|
private final NsdManager mNsdManager;
|
||||||
|
|
||||||
|
private boolean mRegistered;
|
||||||
|
private boolean mRunning;
|
||||||
|
@Nullable
|
||||||
|
private String mServiceName;
|
||||||
|
|
||||||
|
public AdbMdns(@NonNull Context context, @ServiceType @NonNull String serviceType,
|
||||||
|
@NonNull OnAdbDaemonDiscoveredListener portChangeListener) {
|
||||||
|
mContext = Objects.requireNonNull(context);
|
||||||
|
mServiceType = String.format("_%s._tcp", Objects.requireNonNull(serviceType));
|
||||||
|
mAdbDaemonDiscoveredListener = Objects.requireNonNull(portChangeListener);
|
||||||
|
mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
|
||||||
|
mDiscoveryListener = new DiscoveryListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
if (mRunning) return;
|
||||||
|
mRunning = true;
|
||||||
|
if (!mRegistered) {
|
||||||
|
mNsdManager.discoverServices(mServiceType, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (!mRunning) return;
|
||||||
|
mRunning = false;
|
||||||
|
if (mRegistered) {
|
||||||
|
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDiscoveryStart() {
|
||||||
|
mRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDiscoverStop() {
|
||||||
|
mRegistered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onServiceFound(NsdServiceInfo serviceInfo) {
|
||||||
|
mNsdManager.resolveService(serviceInfo, new ResolveListener(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onServiceLost(NsdServiceInfo serviceInfo) {
|
||||||
|
if (mServiceName != null && mServiceName.equals(serviceInfo.getServiceName())) {
|
||||||
|
mAdbDaemonDiscoveredListener.onPortChanged(serviceInfo.getHost(), -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onServiceResolved(NsdServiceInfo serviceInfo) {
|
||||||
|
if (!mRunning) return;
|
||||||
|
try {
|
||||||
|
for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
|
||||||
|
for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())) {
|
||||||
|
String inetHost = inetAddress.getHostAddress();
|
||||||
|
if (inetHost != null && inetHost.equals(serviceInfo.getHost().getHostAddress())
|
||||||
|
&& isPortAvailable(serviceInfo.getPort())) {
|
||||||
|
mServiceName = serviceInfo.getServiceName();
|
||||||
|
mAdbDaemonDiscoveredListener.onPortChanged(serviceInfo.getHost(), serviceInfo.getPort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SocketException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPortAvailable(int port) {
|
||||||
|
try (ServerSocket socket = new ServerSocket()) {
|
||||||
|
socket.bind(new InetSocketAddress(AndroidUtils.getHostIpAddress(mContext), port), 1);
|
||||||
|
return false;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DiscoveryListener implements NsdManager.DiscoveryListener {
|
||||||
|
@NonNull
|
||||||
|
private final AdbMdns mAdbMdns;
|
||||||
|
|
||||||
|
private DiscoveryListener(@NonNull AdbMdns adbMdns) {
|
||||||
|
mAdbMdns = adbMdns;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDiscoveryStarted(String serviceType) {
|
||||||
|
mAdbMdns.onDiscoveryStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDiscoveryStopped(String serviceType) {
|
||||||
|
mAdbMdns.onDiscoverStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceFound(NsdServiceInfo serviceInfo) {
|
||||||
|
mAdbMdns.onServiceFound(serviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceLost(NsdServiceInfo serviceInfo) {
|
||||||
|
mAdbMdns.onServiceLost(serviceInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ResolveListener implements NsdManager.ResolveListener {
|
||||||
|
@NonNull
|
||||||
|
private final AdbMdns mAdbMdns;
|
||||||
|
|
||||||
|
private ResolveListener(@NonNull AdbMdns adbMdns) {
|
||||||
|
mAdbMdns = adbMdns;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceResolved(NsdServiceInfo serviceInfo) {
|
||||||
|
mAdbMdns.onServiceResolved(serviceInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
|
||||||
|
package com.xypower.wpywapp.libadb.src.main.java.io.github.muntashirakon.adb.android;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.provider.Settings;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
public class AndroidUtils {
|
||||||
|
// https://github.com/firebase/firebase-android-sdk/blob/7d86138304a6573cbe2c61b66b247e930fa05767/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CommonUtils.java#L402
|
||||||
|
private static final String GOLDFISH = "goldfish";
|
||||||
|
private static final String RANCHU = "ranchu";
|
||||||
|
private static final String SDK = "sdk";
|
||||||
|
|
||||||
|
public static boolean isEmulator(@NonNull Context context) {
|
||||||
|
if (Build.PRODUCT.contains(SDK)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO
|
||||||
|
&& (Build.HARDWARE.contains(GOLDFISH) || Build.HARDWARE.contains(RANCHU))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
|
||||||
|
@SuppressLint("HardwareIds")
|
||||||
|
String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||||
|
return androidId == null;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static String getHostIpAddress(@NonNull Context context) {
|
||||||
|
if (AndroidUtils.isEmulator(context)) {
|
||||||
|
return "10.0.2.2";
|
||||||
|
}
|
||||||
|
String ipAddress;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
ipAddress = InetAddress.getLoopbackAddress().getHostAddress();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
ipAddress = InetAddress.getLocalHost().getHostAddress();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
ipAddress = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ipAddress == null || ipAddress.equals("::1")) {
|
||||||
|
return "127.0.0.1";
|
||||||
|
}
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
<p>All Android dependencies are kept under this package for easy reference.</p>
|
@ -0,0 +1,118 @@
|
|||||||
|
//// SPDX-License-Identifier: GPL-3.0-or-later OR Apache-2.0
|
||||||
|
//
|
||||||
|
//package com.xypower.wpywapp.libadb.src.test.java.io.github.muntashirakon.adb;
|
||||||
|
//
|
||||||
|
//import org.bouncycastle.util.encoders.Base64;
|
||||||
|
//import org.junit.Before;
|
||||||
|
//import org.junit.Test;
|
||||||
|
//
|
||||||
|
//import java.math.BigInteger;
|
||||||
|
//import java.security.InvalidKeyException;
|
||||||
|
//import java.security.NoSuchAlgorithmException;
|
||||||
|
//import java.security.Signature;
|
||||||
|
//import java.security.SignatureException;
|
||||||
|
//import java.security.interfaces.RSAPublicKey;
|
||||||
|
//import java.security.spec.InvalidKeySpecException;
|
||||||
|
//
|
||||||
|
//import static io.github.muntashirakon.adb.AndroidPubkey.ANDROID_PUBKEY_ENCODED_SIZE;
|
||||||
|
//import static org.junit.Assert.assertArrayEquals;
|
||||||
|
//import static org.junit.Assert.assertEquals;
|
||||||
|
//import static org.junit.Assert.assertTrue;
|
||||||
|
//
|
||||||
|
//public class AndroidPubkeyTest {
|
||||||
|
// private static final byte[] DIGEST = {
|
||||||
|
// 0x31, 0x5f, 0x5b, (byte) 0xdb, 0x76, (byte) 0xd0, 0x78, (byte) 0xc4, 0x3b, (byte) 0x8a, (byte) 0xc0,
|
||||||
|
// 0x06, 0x4e, 0x4a, 0x01, 0x64, 0x61, 0x2b, 0x1f, (byte) 0xce, 0x77, (byte) 0xc8,
|
||||||
|
// 0x69, 0x34, 0x5b, (byte) 0xfc, (byte) 0x94, (byte) 0xc7, 0x58, (byte) 0x94, (byte) 0xed, (byte) 0xd3,
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// private static final String[] KEYS = new String[] {
|
||||||
|
// "20686253007643618731996444194404343867753606615063450617882673005393433941259733754887313620913867473991126076227684529071148116710971688307399269161393680066708223500416626973582453156074796463551655506880263757530692111705709492670974173865966567229986684076018218836054376260921024258822748029571012424062161309155451659011612585496480130049086533401220131725127467783734498206350584978609270260042864036755332689164701948323567009422782353582299103998793983781904622199786127904484293079447215050401933233201140660611995982950449318766441491470794461010754367235997277784582260919909111021889463573881293340673638258140979906632549357292907467007755298711750754693102503358563113321739768558800007075371722687735888311697725543005911629713945900921982291726988822299885412290333597947505237030964811359845572308590371290828162825800693816284267659154250460946638141531109051703958714241913036543711553816745407369784077289292046547482798618404863636910359464490602947766408857942988353632212055739706153132169632236839182513092231542800002960492871433639429992495815686493836203332828415821398124085709593551180637824827628263278195555177924931666478926276783820224389307415162160245683448074970045831874158357509710640316611667245663639463489328681291088128",
|
||||||
|
// "20686253017157961681070131581867072824582833493615686581285202812978323130952408474782337908483014193592959801158288937338861941384391935424181886115887706116180211910326203195827988548568848400075560545658694754072288281934799100824960003665469676627306218542649151650279594335310005774745074102233566034401681058875954456976451246992673780060830565203755535989743862421541016487655599803066890290569271073410408457924901854658523004566584730402488976324736395344746149490673499453625232592526755899773745566599094231998380788633185151420348626744906833795805930985207848484533950617753433631247755579325235753032891647748010767177734536686383017861046451119520379951845678816861944386294035286574657104145896837994175074910881605806540878194352563352369751154950231748036893872855863146059482454816118277619882879924798566063675418426269661488345544005535393697052314311247263890598514707311723166476167962362723441242864747039873917888833291982018201334656703016344691467362831812856230943332947764107530472082685276225170871545829843748520918630697076657160036883930216256677113853170428981812392602195530517909206428574912517211805856768824444514391062511206137932417723915871095033856634183547083377240616484404928845477796622124210865489291636384376815872",
|
||||||
|
// "20686253012016661596297187165295953810124840916410787780259125355974730724901079696472630368937851048257090320232652157100991308376039941481829068380105246658346925724672501337521327139011807932361821638555679532028068537874220234967686261347630150684757119393835764732734869517200772690364990583127671131537583714931243019173197423018607993303448083354399470192423616983206536107095715775647406269008431508946794838483788727203899942491271993309634327951609153908662621681729016801610515674359693917283475097384354342498810325041161909134028476434295385254073752420814778998269287749533881374904934126049438895801381045118590479396113271334390254276279559056023087328573647647215037607856427456918131285395255693120064580259556735556512377323876432429678739578757363573131334912599024688462135510569451655007983978712803921393726583392694890019375462303417604382964474778937531805444153682154861176782819373335110870826625829171651646773939431655533573703527937698849234129612444879578476798107659804571742743573317338460943463599693739087545180845307099319818906850017269705411337110447078163740825844263091960377332261204721538817750302929778362322626050195563553439929517852576974112801805532465084720065825179978157459297137449086759181900619029928059339008",
|
||||||
|
// "20686253007164298102187579022292645782622950267376457918101697669358007301488761837295142744126519186355305488389221986154518328646591249427359811715893827299671450881507028164629286041250862175310771677831707558127385367785197803483575096787182004608184320944234595728063837868041235349956855438493602107933402900375680275580658634153323631657275297128293064166637994755918951415154912946597772260559048829851194391800720841779417294685741008056484437199267285946970880090812268047882017049497063496655486104722944355404468824860822958344153152666873208144779124031791189998000506056940151806004196000409215856732235270100083588540707679527939391873638758465419555753702157561970233144942307459852882991188472466621152506161477788274973160814417916904823923993324434775898693916605927490225550209649971721585529738473244554435841443463085919856039187050589485138795602302083255665055214846777613999233572727226441658259782563053572457091354281339853515214717324207788284184709742610276702868576013972404040386528578798398612655692848123956484300353770097737367273673338660759524265042605762204830694090505973135478745543215676846804149964034093456987246386704478034104616628251724608616498482846759154973645939865112578396682953063481438609050519636632406065408",
|
||||||
|
// "20686253013631672077140860925430431681294767795873158523291037328668344494856968639956583809600800609243074268151952075506986744112173705954028708402476984870394191960873402354883924141967550191866699568842907604201375691789447875717904606519911990426542474202257873447575301056799275687300162520647747107269726474224871972583841770132600021392824170364479997596039740110453703092023353509635466916728572484548240377314945387893669640571134322168573530937553272534344026352868086607470719702834083444892288110107409203387500645304783564563250097472203694663921776055854765509655453678476671839476460047809572066383800073578102412448677588127686456580476332728179819886722353651757881477563060082645588924556499391555273484226830089405923826482032061727033691299171028963180078332147031372667760359558227049050997025606644301016548127476540817805335115255877601761512137427173611440130583277311855464175578764391247410560993954608789331880719073041849580910172771756479685430766451208229539612899065008167414362054168182076054905614418672686595489698195205424496943265168367449853961685536803353605862503830213266961051166928463688349071936538327772591282503905358730343548547940428875061176999789174879128250317383032697059739647594262479435287070553934024147200",
|
||||||
|
// "20686253009137100677580536481772220019834185053674835675104805567705108100012120793382980473459675176967509777404926949610065001902175161695962003614967789069678586166752499518706455413722715678873250863436364658098942388406798677103189295795606470644008251449037179794502035845361840650187691172794383123040477163218916023150334807750836446866590977919532470017535348573419966872388934361829146368421225272657444717715086585757479506344335650280743058062849751597138313964046571213056448713724664410661632559255622391230727347897637119451531510420187884140761879388980580251574156491409434892603700316490405451728370661830847139541500680542314851537865497120978838526245028564455048339534705369938676962653936571813488824888264746297425445127522661324094850525422265769467214262342247384649233154604849204645241734493281272153516726661106475381716751539505876924878328383153759965309983960685978480476384651288642156631803739479287061925347068044278079778523599362177880625669615321469494541417207707795112792614703988712865352607326208856280054996813476487679520479619834119561518751799862129372840860158171276154958342280155058027979175914132253409673754984859885834350221155842157851019202903208837729538330893332631827655821675906374426442436139049029730560",
|
||||||
|
// "20686253016517652578576226218037280304512998878967782081643030383762252648179296997011341926638803523834137938978407145354361840964277764429298580433833879753046327333287664442011756674514375099551151016760768951640623954608522240638785781825523830834849040469461889966736539399995075932019080426966535734677817177333374424187501199788948653236985818361084089703379091583995023155469499841274582203201561305334645372332402890018547968670807123139871175453983341212876440749594027233646858105401856148507349000386220939016569850622488928617382330345394648656509732599042249901506581310905883901959534984505897371121911753593182717796270161120916944128299041339386928147022131549318082428289605796149115464012497661997457808599992494790605129095762340900378198216634471902525669353909170959057894014738736417606036662468401786737193290238560507341770145447091177773612273526207707414967862580865925752359713241893890750323872352660127060159105979208005350694984418621693785977972962175850201555015110758212910334258690225997862373112062500145196188829278217998167244761059788293227986694796302184050693617864339940026112434075085598165880397454033611127221858601971082649842623914964304450321042940327685124173187185970331014916661615200988009485722532416914981120",
|
||||||
|
// "20686253011995955629076325009649943801969349915799396834999938713706577453135666219827378331078791305568238767012603523685969479718725348248703275237034007216573140943056580311779171211523596323125369774697691841627573832408640321451759775024971845341190607371902257208390902094844221754727824487661732047560640579666147972731827393052406954025855636608759486135987631984582660021446965995410633211966029298815507401281475655895406689670177716825267171265721675030636564325187593177345954853929509007143136312590872799900706331444637303395615294877562776780224309357978460277807368192971699424316405430599226137562559166681593644350393134128404993203172540565405337112770214130741644001329050834501676799331704650166273747915134031416224513404401863940821611218604578118152404764705070475345051218888969396184751106112136786410948127273335981631264270134095884196287017029694382607779915472082121808328197672918175667411956374486872625493883365083931491511971634291156868483178497893682614629335075497305499292286022408321989969349011048118695523910315223143950295729792988112393289748804875597785632720969020601590483488192942403773245914283442681067607851920760864623779136461182039085569209069946423436337464957827499602113031939666405499335195577103935340800",
|
||||||
|
// "20686253012905249420753123641394125581338490431246330974155061264735030723090537231131634982701543430151111947340098713400919665484501369074291449678715950324404084971416405468246859755401939519930479946881325066315343108805200689731279192503717023845106229875664749782703938485867918135872531314893451199777947488701264763722901060781624134818284452130770584902522697944415430677834037635796007590708428660357263597678671576847673995935030932886476827700205482688638424266106169588048237006500162050678096674641748475495896688241776715995439349517453160862577278522303148756730047750797909055062592477399585648804967165274624689461786686029846025735530763855449050156214748263389735761523970386426501083016336782069534800132132814441821194458883018197602204145921510758387553720734875367542620886529149295030141428678136073816645318546217907182935719212846985527885268490931042846941843895577575986868990275246964000930910996610476509616118972208655613370459666905798364207165929375677455372211850892032163428761255574831943581170173798962498310302619079810560252634395602186653677496588976269539691991643655645015285991996519815871201426765673211289585535433601874045698363351158020555486412212492888981581014428858434326634365544072244470887968780251830419712",
|
||||||
|
// "20686253012482713944170510486006503495965426418624979215207888186336584311563125668176618892341461495958989530109497058836208707044168245279695940788984032479278397649608713044920405685548592937000089068732569152566103178304391968262275366634982743672579525342222333315935193825742608586653438687027252465742150192128141744663537027469859051411958033527779975309458836521627355592244019941914250716050720601976679339560457780808022948385738031590113665471354096086486875697653599495164469297979201195396357751415927806268197841979271997133417306549038027016271542831330284708665839765584145061136794149016409901583479427347126229617073748763355410944442711689578134558428523469322867841307165512505150561440558923516413487943512575228004609949233825559134736566257971598139983362643764003946061810478982678081113673133692828469537444314163454166205388851192666274080898281014667651235171324569407150427688362370268502748192144621240298284168580601438138025554803091157078261255439130748192811252524386962992249383432688422311292315445533782932932345446806388697895661377268890467142415746788623768632474596533761037877631922147784592888033960567149861018268485072235834034618474495971824025999049529331906554158125086921199432771280915863695896037886622831739136",
|
||||||
|
// "20686253017596800673636777315187534270021266420333274774530680374903816600749071306262945801692120725976064051984138994058746162072937121793853747964627815552811045393684927849560459595356986505364881801969476935753890194193700810599429133930398705250755254836009280177917842707354484199908531777573893797187673907712955690758349411755815598103902935655017090298516565740392457105436565547185001255070561133895788176000782782281412093088710504721088727045238362229707513145743387163755472103144359822049806563915934557475036319622688517527471037575232699010534869068383647539493972360025529780544812641848222048512592585298352948703198597172454291713942776468719578698387846171669051363353131159533380505301214164797646016372174811126560901763198906646299690775898549445445030438862050154367175654798908548309949046431701706518418835907611144671153892500148829267345643289528684512615681494813120785604119244868024564867462454807660897301092541915645564379781995052629376347977326432306238090292830317108743463398855319724461757906202501054760315093634564844367373695116166888912389471388175972952318344253589126281155102370809690828946212895190035082791906579557462335096539646297577735157137081490920968937162188359746613039900117481527765337456961996600639744",
|
||||||
|
// "20686253021670819484641397455235988893189004357659986764689257803151409177614461364649970433006714126825319428176308950177181495096102292376641455488005607469636772210940591752734984815566464618433768320892209283008301650256134897180639126904431683668620836360770522622987413924299753298563911962706925772038987129402614215060442406183258855362002322270760435388491304782642695492897104954057313819686015892539037035024067606433686326406317580126032026561653107203711784714174553258867988922391584823277517348441610017887616725654033103467290522569906901516453939475371008093949281078143911461551090436073001946185740446013553585657161583955882953841177944465026376300855580949947151004864741065528954219420584830087663819106583270144309975996802314686009200802567965571029611087066652726123514794266804514760076981917095632831537957124002094177446121777714425501240501887464206036422959211832442482836841510124546833303416051006026030814775613976153585582885196096288806254708297796542938960077290086802302255719602193211623734787514863004972159363553648873516823809184020531994302661726715338441496144201492168521781315979852201195520642908900413827363866480500192561642524090584879553530477247358859671792789145550860764422409857398848511879706827894470738176",
|
||||||
|
// "20686253007779418538146435611960103480111665071547886109640735401007273574064776615540701179896428377142052442567006099697162043246765708980976145988576633992791951978052644497415923662944617922884643981724695008383893280530637173581248946990739081903596261263913018197718895124329861431222284653233340024925156526473018621659579602661924631346612848404894851502528305401866261493226279256928046285934986381048052034850172444367408714525087992488005859355731486073285673373650699722735519641713322832229543168311432490222848569998028342925773025050691460124263527225809542829858215777298289860007795297848906118975152353977610454216097635614989263760656961019437297514661609993804271689676470502860376666591936653476564447284287948444996394814992534945211106658510902269472032893555816145339327183535542766040947125922850444024392962311389749647694036921114853739217072675694979896955780825486065012334086560739968331771161859717998929049806691359069902247801751447279973168812586166245092210343145733018596567430818598988422806423359671179738425327829924055793651209866185908863272645671672053161275134558190614726280574493595200071091284480123481958235975875823917688354274132904898857900963754501168982796149653431776796588769427548375743948019813104125739264",
|
||||||
|
// "20686253007604876142864868042280524165402934179882610914697720762337073476216917389996124773136098970057364791099676488858171958566606397819353213332750920419342798801931043028447913975575839783079948872757605203684602892225855510703027538051098416020818203339920511893350539925823111193247570595010592164929661686244708062422421354240082899804498188803419141823510080225562148441961167143787065210061597742895645200944179873692681009532549500390826790962525777523907581122863044028963918723919775810059776782603566022001398041618219382359180430108215466147420894596600846280899658544993640061836655948799058416531930107797645597300743718230009291894571536654250713613225292199432580280761478601571747214473410393967212703089811962847476443829426705969655282817199293842112005075777464459419904700085996691611909093195963267420749267548377551222829489604133422219862110546274841495019087571956520965526972054781444561101065107965888687457231748284726446965643182772550915093919251096627310403665749159263278352617835127791887411059400056120654704697281677759462412423949563123799300922337337031434011248992699167653294572541741943792138821657961461481276538506021735660790599721725446180641231442745067805783335704101764512016456627263309212202141050291403882752",
|
||||||
|
// "20686253007021608241689663659963106759118723298198840826493322751986448442693666027764988836450224647331743582291670090917210382327270531482163927227988223092974790331550687752773643667187876554181907548400902488924327949585152208488928230350565913895512207957249179245514039152326127203620938403901730645311641228704710362766137452100607502345181504058992226565546374405238584328644321921093037858527565125084661975400406032983072371091348426803460064043823708388520703550652014817583643133020229845020242944346824617584906011027684828450454972028242671768211013258545366598182329727396593002246615440504964904310838157610000834126195478113929502258505249404659889702740225008795785252745301900749237920017295319107372211580703483426556992675462580169514689033492702354908848878614127325367288349925054680142050382109615731173807465659057525671650993994596156034190434249834629081700288210478883667431390577000482807339023985920513954187158118910393481098591489866417654826052352494975210865965568210016302565250763960252121003586393653990260068738875254130452064019745948999144292814505789232823453267156783611177723108336077508126670001743428554516943943698961609321446420452826416665860184743437533100494614025619487465731926709595422582461864066896099279104",
|
||||||
|
// "20686253017576328604397757628696916976575522581262455658158084545267679494101813744271361066833895727275999990362314163487589781714736086134535643149499659121815284171966664818069832388563106430729424843320170501399681992962816815405466327181883993596336326588913980995040153015039842498603793624311027080239766082382138185011127721145696561736552179519972972189860686192212873871541534333569298609110022068676249161087325016973188219381193106238748350352370017704856280849939084182123474167745313719296726857149643911426423892259144920430332447233445727394410596908871121943691683251347594543016832133353836309946329620336278195398229250583694331105065483574747460477844028959296763798522682650225035915279057821815082477316716472233093622430236186011224736996806782238285457943841178908716320629716681607442109914452439698030106201073955996654282803838012221802786122722463668921800632723654622027772616422218197375832565654227089919843040086396119306096757556190008760084017430688032081654953292207865443621116020116320243741976393955557450724420382106551748948637361361237424102598662130622446951212397620681130020351934387640468968673784832844072192051121879959906157803839174272822858710610565971349380572855080734703266039110827437002755727257645879394560",
|
||||||
|
// "20686253015632194446964549752865306095675796992984280836459827244594988985728341929467828908174136317388413601759640625700697567744571888535192920357933899911995149737015137803918541787810386447480938150007979984323709223630514858926018913313903533635089468154621298339064447275675686499058206570127524798025633713627136893628055375633970109073821185460898039856680258335615963791682491988980277498832573429849348764105832237085843498643498195695163785310161310344120423648634988353729865581801558283805742288660458122510719188397548085580202887930607910533534516693933657267260780859925051022338611693920561790642016487656354781128170103744324893680564297782645461578140938361264816397758641997289612268599215669012143301913510494792757326575404821915282598318350394530566425173158364070432671579795603645149354325381596532039075427958043639242360621114659751231872073036120732181043105972973109788769983560711082730917065413254697951492111991932795350395291042777547404358191506284743875086602012076691203993957228415814341341317076567346863011063363884150995846166969589047317632929392743149362575140604429955129918452363340274074095953595228692113309367060073180152866610193934981631436082786314283160713802768267996895973676591655886961137949790395651064064",
|
||||||
|
// "20686253008280076434063627792976585679258240901117771774848183680897991358808598372294247945349776781930725891159660642772324594460473591428092423907922733507827637130679002417108815594381100952950780741331843640801790721759935458380251498677194095462459535511544103447989319232932465020496572704531324890159727609395394440487675667733293698176844674240132373049476798001543842889891227651621801389443373039936389118507727414268889974019400734622787942381724850321036295849698333470872363731655908573604683885345765197680079122655638597424255830729241057473120238971524652052886581049260033714044170704671920548231649930751117900234527322744641536960791319040850129139290431448454658633022000433479483039128614980515014378005152986215764207972187867609137628051392176711451953239313092106278507175222152806081191880959633861865125667318317791167731714976299042173744637249086661016974783212613211649879521639713884566600223256092655930849081112415596892313717463722708665626233145587935660772625224236074541613317671527221358656944545879413948646453563296121659821042385880073296612404056486812912561838422746761742255638925665021058593318024264673057295116442867443437729038595456425369615349800800527803917805871133175616209136030135179809038417225106990301440",
|
||||||
|
// "20686253024951658247963784256461033211161677366121981865435929241662537577939141826066423582382620272467025896961525533649529834951928815732696192031254242001310976196395313722735262686979806326419012885189967369774411212706004362512392149609842235706677304433133443952583032520610040362070455319152450494660815670160633796512952042902222316108338953442388922875611033294149441219453308994445197831263244239682760264737410367007220572112879442727200430192143800723337291968248880476611034380579121992553429999072019510264034072224367169076681647830266796198698184376443826044285160611787998026826563875013778342721603031807058331671932037301518976354062079082150728323092697138014541325857426045339923252246197903880065058351701199295574478678901159403090260453086693563410504667697340773698567005624143430980337698805702319491020156284314353992648718466728823214596123324888480179957675142649621354384527020551369805874882218433548180164260225089194029633825464003674135793863996433213944915953458051073341886199898474985986664620533716615542750212008869522238677744797202727258018045172140382145668069884754503189079041670422324774317816739968675502203964561699314302235308312748370659797007514871638586017968916729509284382607208921962626655481606857704866048",
|
||||||
|
// "20686253015329051263633614301062841926780457793244333362445652976039258394085904409997569212036513569627020122416105184089156035648212037623378445311136404144169674742143343217136229368645245696403751711017904206141255246181738620208604563131352355142398183885029711993169895631312556797474335190203415666424731815815318567886625912492670817003495147968334343459502296653971351530810954197377406607923523701655523747303747572405902504136614192793569878303382172717914572317866962252666818690300625318696007782616025654101394182820792992039050797265658839910245551706198215350312385971886655288470117569599164586651540666084220530837687983855363703457438523945771377188384096611403701921732343378131772105605616111230060395767250296130321274230574922212950953525510434760474902956273375775869424140206127230345610638214846167903417131896656272090379493970136500706006844054279981738876197283239177788834113814444218385342970502089028750415245685729607184037765166339498069290010361131234888359345071611741250271620624165209355771772144843622884733754033577735187746521804131583913214093166309888724137377435858133185966519694264750031605141214041348529307409532348278522558737337691108243193690034235284716283236278857093221201219489501714217369972614111881593088",
|
||||||
|
// };
|
||||||
|
// private static final String[] SIGNATURES = new String[] {
|
||||||
|
// "8528503594670204436052357015928862792506358113369106777406225289021120755325204284915095461436534593726527514965930392069276752206673637292335104066658988404928981760593418142871524780306395774215697719603009673950459832507052700686367128506073190142373812763000304886902929199810111204681208295515597746727406521222365667866848729045254159801613868059972975579907523041955922782192361370263034597048555422592874100353115430271270939317456318510451811646267157137577724074073241955677595103004166831493027134464898153615411682508571271524917051485420113620183698439910489332188676948813054298376589190870205376721722",
|
||||||
|
// "12714752653171379071902223518900086455469254778099176783756546057501252740388760646633111438554988457804110827172122185741643374376377476845818226306550070549540100924375173033053872725592206270543865057909506204686416217217175205975936864769675359443835464932866652953564668335772839380415278907842078521565813907913565738769303530371937587632865012931316702492771417680964122472313546679654313803229834342261250191743408149738051095079442654981806457495648490488615580878652623030639431127849097089635678230782461813874847730298433045763073635235796063186457060470168072867337516295219377274643254214166187741928195",
|
||||||
|
// "13908999250565370485651189816621604309406838390888107327583648160922258563174890896666525571590573441122409145906761904814530942061844184451657972503856070661891327123507140696829535452282425270195643203742925435202847440053652045520143275171734902378485911971935113470515174453988792439225620093679659059409128896537294846575246782278559049832955440237015381818190544157681329515828756452798679892374749946754033759443639475313334060671371142984239868008118691626803951984815668325843801784709432199331447858105577423245817671538352849819889855177911611607915665983585833151320422174088007859931381348016319265461694",
|
||||||
|
// "10663928803538142495874798753524371044109713444112476166920999738661408452717517594386747916849812868865845184227592286809206405151706316864404978033443369238163211298053129657897912511475878426734168668138448142684774180483848489762892194047712675443661602073836529221861198253894032646440612405989149292046072809162239075060183622012102135247005904791258725387249405763973030121585327387996523577075440922630211242753391888083653575027601995970547566022378966563679332453413606097684906558136475009659092263370997631008298982911150670427099551097448345541506637646597809408731612314781030427962298783217248914223806",
|
||||||
|
// "2306715670154317872337078201496706067018016875073166641656805679181208518844313408613041429049277142712575268846333720788389889629069536193483907615434194600534988985015385608627085443890672124721486043133411014103460576453162713180941943637321699804181949641374502608547423908202551134802437475178385172142729079080049166263881079442027408091314096230581266354997604485294706409288693050419576182967429750806152579311940537301588263217818952406077159490924792302551188620989397020490518323111334752644091498632583425115828895844183618266417719447627326662269293970039782216019092059851746977085656284304341508057993",
|
||||||
|
// "4659358238164596158948789743224240069711265838365302036573085292613329291682190238495190399519475812546652724862729777463345410534406299261146698284440785385833664987236353362536409224868061055771434659441519724477807214986173373107393432602854768393738777892796122503107069552589289874263521713955968508746406485296523435090470754765084421945615052354670619502660960366860227870952975168583254800217898305238191480303688181715819411899720342242163096384819366364486118817498794842558871973328704472113837172120537734616429530518033340109825721160272151409573547421252939751876457633571421692240302001467345028442601",
|
||||||
|
// "4049477946498817875644423736582614129429104652925293903578491350187944109357140281079215461385310310227699148230226103813511093858314731577806298771933039828435988536582683795573155489962134767330360695808904080698959547160137535666545619466761042007784679323603927971926202759931895569401425552243257170909460081819203771507281795697451004296311404365704082156689073796657538886169509765942854790434677625126588993604623270447590077611239790400219558927531394967281599304514894414627660359455930701315462448554588404533268609540765516347343066315049932609011242206592126920115645749055638930724732121135122116141166",
|
||||||
|
// "-15821074705595424775489985353461364659530670829708431022534131332700730014024858879661015439321392526031983716218847190089100850526695320564737549648506802838787532008475561270345163817098663400387502475397954072183343244145179711236049401253605959260401466676172293703977395461139502392520912345905534309764600115973777897708515531741002585035454835142666002006897210376288419070871163424306698312361191916347081600355010527694492360779722717717750770230108246572032092508371533877677011198486557314243866055301211143049322569030292978261520962920278653892612567676627225563117772597001334618978059073836056716669225",
|
||||||
|
// "-3253944899977321876942783783337240903554123129096642235321635334925673112914508303442241100990114967311556077951307727781710343006400897352395067739623245112414217140757020483043902863652615234151122932808390646136142553799710067271445726854624819815642073592109754781288339958163810012173119634614599349819358363736874502076277367766337257137053470157924537552941473944009380652976471624978609651165801832875705879218057300556705292310854083054205343202526672473020368237867322973861758935442876895613369159646304355164238984667221571500917386264593984463607981687284420899772355044207890979293151892151717827509819",
|
||||||
|
// "9499141240577043451589632252552399646757731315654129758760015349045571672430739028949896539759619921365912271793699639488523910752170974284878504952562109143512853537668435378131893436415070630973505762192641967189753052893201272349296728232365714025670987726850356205941217266924338273790152610590219162996666427392130736201968771225650743588893284231594006554226911151399044815787836234434162548055950690340366254107040568369155820242765188526434944719636880243193487042089094702078107054425645211148742327810444676596010608038011230808463633129576609253919361989917628880939682855954355300546341301909315104641292",
|
||||||
|
// "2785829464159962785792464154214301170737801888846376572507071703868621757492888529934734721293299181626548638776917276845515937348930186241646686214395195802058604838439131193816292088810714374344194956806787815938869239553710725569223765884297868657637234129941827347968009169909865231403471928705479925843478070964175560548021424092972184851431376822977172730546215170545593072337665216944481311725273881895390870402865177526671062318915225337990250246626982564561668482752340309344623795724163702528068799345034816267947632414651344549931495044609876171529597903856776762161922406306755255424075670945233876468235",
|
||||||
|
// "9693399573277126168117955145498856804911026370939734084616110928720966781810687706982467213763886301391323622148228097849029578280382356919106089289850794524605638208088414420886934380985144626389853251740087295754111363849778298354982244500232225340060817412736334732343104887783650252691586118114592726131559322014895783483438914376642606356658629244848851203222766693264106049037206515886724418160132270092812177670695280590828717742154134306911500638045109639882998686085462128237114003344000744142984004964727666767343836058943694299268149858387245532882552313792450703351859102940672866025492125862734810702868",
|
||||||
|
// "2588026480762077299880435211630462130829042494172803139844752929165151314366576349692479708707902818325002447901174618910914735813021836209789642281992964413385413651230009333804422618970344994918374367306597128918353293939655079898804834272495466247472976936750289217893184038643984663442084787174472727556425006629985118671735361758704886890235923409705820370265894413685399080693702003337025366946813158285546498272869112876242928634530360165484359119404508056677189703714418493887546407516490045931543871651300601164453872612008839158278878642833640818155397129455955015836831802614094386547616288007668519396656",
|
||||||
|
// "-11728748857916220938525563370642851174888323460551443012033711709118634455694860641893644284241538905301760022334170567702665954472646365780973999849934512640749241332019089471035867766088117990425231025030842560049003944730933840471815845647939175446669519176892871100667327144122288934729242175451319185176733723593770503584081246166626636645030357166311144861050296775574567995193508629237812544699293985433002148216129743645932049717574867996068064127541167278296952065930035012382421475825376778590937794554225647092055932998039404832926821724386093261449972691735490787917635072888523885575813644811005358833688",
|
||||||
|
// "-15229937207665399077858745779056928142076482322535266981438711930746572914030457988461614013826308364893535928521195966837848031188929394769614489759198148009387852082467397161392193074580370561838070595801238055150547001898037272379177792186097373425590642040064504319592980875488946638406365056236476661594999049247103250079448049760186508271091630332833596279458697068078832218087075544964591498372812863039399793891647438843170406180128852474289724184601066049404074247449524559845321683147746932532594721096776453826585995068663187778354136614612407086729935627102933428001169544176494702260120088616177094122596",
|
||||||
|
// "4423817485097350932424418489755694412699593088843423207118652642017753124828670168023683986663483348010821563740973349339039999652593572489226634061594354003970858509410018682617414793335864951696251946617385611984536476771260728219342233573060916777310319872368264904534560004743365113463062419519791519083814132572592001715626074081892878836026456243524878087931315541387440248693791684485700635565529131429893772243402800191002799200914843481323690852313588145236316661063827783308810378121509659713394573216583671878018935266378492137948180346291679018739284608191825337179228499660670255782455554732811032893595",
|
||||||
|
// "14060727610797171065873730743344628826300688857981195853338897134682600312555998942680998373000530961057721631672534399458386502204866782136713687245588626842312961478612189645195715241803501047584717823837224418642078891142575255896720379626691373477223808040839453370799715426143237974402342331020941533125564932963228074818891707580296516262584451816695873169230235668081091425131622898726217356123668519420820803676318446901933688924722455581079574809688082011559628335681866023133655136379898615214209349346203435853293689318189116037465645474669519318589567809649024218325830547412938703120625760137723658167545",
|
||||||
|
// "12143975642996547734993210479666904095397741261764866078090510262645460132290235986630376713004350672851651807054875332939956731775733959997351324143497076406076776154977723396094902338839439098161231984686885314849947129332117197645344151755344324038625202859604817402251985217489847067013287339738384296915967594310421751908244841803407747710371415190598155158739898568686373111717605879278384135851318969079281050507414909011506576169586220120054116056221990949404820925350166961128163192137909212020962545745326872002538950594091355414304183527270036601878504771221000529804769425263948649859429914831451793830447",
|
||||||
|
// "7681905128991420738358979923777240433337546546660192093231163456029821861424188702648102462301544733499592591906609649350535414419477664467070025593525931603028515171360109575816154776046183332815807349628739221532165348992025144025203444717113795284728750784856862721905401143841702874481392901879089726940312479501647028937736816673686877284445430463537947530572199915087117895344271025474693120828230653492777168767540782277375513209223619007329449471451811372004116502977451480549159579358749540564515729677785198516083957467543681269622455949740687260144765977931043642705413831493101417185777391942989245676973",
|
||||||
|
// "6324957736802947762698105017681228051594271495029008384509236777170014306498658302206164665097448703570931287007659305566350360036085316522653055137878512074480755437541173568670880476898182300821763522340043814781148109583375526242284492181946863678861291620814183269643728258345959383134128289995080908583694306703558113066837628145217885578776624299254179033312744171665733436458179725336656848290970572987456090589484353899143202322971998318634185334126489034866595781074592194167476225131764951162637607536439161513547138214421713349130949142742184752353531752780426054707814473818221353155710305120310324535073",
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// private final RSAPublicKey[] mPublicKeys = new RSAPublicKey[20];
|
||||||
|
//
|
||||||
|
// @Before
|
||||||
|
// public void setUp() throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
|
||||||
|
// for (int i = 0; i < 20; ++i) {
|
||||||
|
// mPublicKeys[i] = AndroidPubkey.decode(new BigInteger(KEYS[i]).toByteArray());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Test
|
||||||
|
// public void decodeTest() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
||||||
|
// for (int i = 0; i < 20; ++i) {
|
||||||
|
// Signature signature = Signature.getInstance("SHA256withRSA");
|
||||||
|
// signature.initVerify(mPublicKeys[i]);
|
||||||
|
// signature.update(DIGEST);
|
||||||
|
// assertTrue(signature.verify(new BigInteger(SIGNATURES[i]).toByteArray()));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Test
|
||||||
|
// public void encodeTest() throws InvalidKeyException {
|
||||||
|
// for (int i = 0; i < 20; ++i) {
|
||||||
|
// byte[] pubKey = AndroidPubkey.encode(mPublicKeys[i]);
|
||||||
|
// assertEquals(ANDROID_PUBKEY_ENCODED_SIZE, pubKey.length);
|
||||||
|
// assertArrayEquals(new BigInteger(KEYS[i]).toByteArray(), pubKey);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Test
|
||||||
|
// public void encodeWithNameTest() throws InvalidKeyException {
|
||||||
|
// String name = "MyAwesomeApp";
|
||||||
|
// byte[] nameEncodedBytes = AndroidPubkey.getUserInfo(name);
|
||||||
|
// int pkeyB64Size = 4 * (int) Math.ceil(ANDROID_PUBKEY_ENCODED_SIZE / 3.0);
|
||||||
|
// int expectedLength = pkeyB64Size + nameEncodedBytes.length;
|
||||||
|
// for (int i = 0; i < 20; ++i) {
|
||||||
|
// byte[] expectedEncodedBytes = new byte[expectedLength];
|
||||||
|
// System.arraycopy(Base64.encode(new BigInteger(KEYS[i]).toByteArray()), 0, expectedEncodedBytes, 0, pkeyB64Size);
|
||||||
|
// System.arraycopy(nameEncodedBytes, 0, expectedEncodedBytes, pkeyB64Size, nameEncodedBytes.length);
|
||||||
|
// byte[] actualEncodedBytes = AndroidPubkey.encodeWithName(mPublicKeys[i], name);
|
||||||
|
// assertEquals(expectedLength, actualEncodedBytes.length);
|
||||||
|
// assertArrayEquals(expectedEncodedBytes, actualEncodedBytes);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.xypower.wpywapp.page;
|
||||||
|
|
||||||
|
public final class BuildConfig {
|
||||||
|
public static final boolean DEBUG = false;
|
||||||
|
public static final String LIBRARY_PACKAGE_NAME = "com.kunminx.strictdatabinding";
|
||||||
|
public static final String BUILD_TYPE = "release";
|
||||||
|
|
||||||
|
public BuildConfig() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.xypower.wpywapp.page;
|
||||||
|
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
public class DataBindingConfig {
|
||||||
|
|
||||||
|
private final int layout;
|
||||||
|
|
||||||
|
private final int vmVariableId;
|
||||||
|
|
||||||
|
private final ViewModel stateViewModel;
|
||||||
|
|
||||||
|
private final SparseArray<Object> bindingParams = new SparseArray<>();
|
||||||
|
|
||||||
|
public DataBindingConfig(@NonNull Integer layout,
|
||||||
|
@NonNull Integer vmVariableId,
|
||||||
|
@NonNull ViewModel stateViewModel) {
|
||||||
|
this.layout = layout;
|
||||||
|
this.vmVariableId = vmVariableId;
|
||||||
|
this.stateViewModel = stateViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLayout() {
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVmVariableId() {
|
||||||
|
return vmVariableId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewModel getStateViewModel() {
|
||||||
|
return stateViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SparseArray<Object> getBindingParams() {
|
||||||
|
return bindingParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataBindingConfig addBindingParam(@NonNull Integer variableId,
|
||||||
|
@NonNull Object object) {
|
||||||
|
if (bindingParams.get(variableId) == null) {
|
||||||
|
bindingParams.put(variableId, object);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package com.xypower.wpywapp.tool;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* result of command
|
||||||
|
*
|
||||||
|
|
||||||
|
|
||||||
|
*
|
||||||
|
{@link CommandResult#result} means result of command, 0 means normal, else means error, same to excute in
|
||||||
|
* linux shell
|
||||||
|
|
||||||
|
*
|
||||||
|
{@link CommandResult#successMsg} means success message of command result
|
||||||
|
|
||||||
|
*
|
||||||
|
{@link CommandResult#errorMsg} means error message of command result
|
||||||
|
|
||||||
|
*
|
||||||
|
|
||||||
|
|
||||||
|
*
|
||||||
|
* @author Trinea 2013-5-16
|
||||||
|
*/
|
||||||
|
public class CommandResult {
|
||||||
|
|
||||||
|
|
||||||
|
/** result of command **/
|
||||||
|
public int result;
|
||||||
|
/** success message of command result **/
|
||||||
|
public String successMsg;
|
||||||
|
/** error message of command result **/
|
||||||
|
public String errorMsg;
|
||||||
|
|
||||||
|
|
||||||
|
public CommandResult(int result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public CommandResult(int result, String successMsg, String errorMsg) {
|
||||||
|
this.result = result;
|
||||||
|
this.successMsg = successMsg;
|
||||||
|
this.errorMsg = errorMsg;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,204 @@
|
|||||||
|
package com.xypower.wpywapp.tool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Administrator on 15-4-14.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ShellUtils
|
||||||
|
*/
|
||||||
|
public class ShellUtils {
|
||||||
|
|
||||||
|
|
||||||
|
public static final String COMMAND_SU = "su";
|
||||||
|
public static final String COMMAND_SH = "sh";
|
||||||
|
public static final String COMMAND_EXIT = "exit\n";
|
||||||
|
public static final String COMMAND_LINE_END = "\n";
|
||||||
|
|
||||||
|
|
||||||
|
private ShellUtils() {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check whether has root permission
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean checkRootPermission() {
|
||||||
|
return execCommand("echo root", true, false).result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* execute shell command, default return result msg
|
||||||
|
*
|
||||||
|
* @param command command
|
||||||
|
* @param isRoot whether need to run with root
|
||||||
|
* @return
|
||||||
|
* @see ShellUtils#execCommand(Object[], boolean, boolean)
|
||||||
|
*/
|
||||||
|
public static CommandResult execCommand(String command, boolean isRoot) {
|
||||||
|
return execCommand(new String[] {command}, isRoot, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* execute shell commands, default return result msg
|
||||||
|
*
|
||||||
|
* @param commands command list
|
||||||
|
* @param isRoot whether need to run with root
|
||||||
|
* @return
|
||||||
|
* @see ShellUtils#execCommand(Object[], boolean, boolean)
|
||||||
|
*/
|
||||||
|
public static CommandResult execCommand(List commands, boolean isRoot) {
|
||||||
|
return execCommand(commands == null ? null : commands.toArray(new String[] {}),isRoot, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* execute shell commands, default return result msg
|
||||||
|
*
|
||||||
|
* @param commands command array
|
||||||
|
* @param isRoot whether need to run with root
|
||||||
|
* @return
|
||||||
|
* @see ShellUtils#execCommand(Object[], boolean, boolean)
|
||||||
|
*/
|
||||||
|
public static CommandResult execCommand(String[] commands, boolean isRoot) {
|
||||||
|
return execCommand(commands, isRoot, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* execute shell command
|
||||||
|
*
|
||||||
|
* @param command command
|
||||||
|
* @param isRoot whether need to run with root
|
||||||
|
* @param isNeedResultMsg whether need result msg
|
||||||
|
* @return
|
||||||
|
* @see ShellUtils#execCommand(Object[], boolean, boolean)
|
||||||
|
*/
|
||||||
|
public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) {
|
||||||
|
return execCommand(new String[] {command}, isRoot, isNeedResultMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* execute shell commands
|
||||||
|
*
|
||||||
|
* @param commands command list
|
||||||
|
* @param isRoot whether need to run with root
|
||||||
|
* @param isNeedResultMsg whether need result msg
|
||||||
|
* @return
|
||||||
|
* @see ShellUtils#execCommand(Object[], boolean, boolean)
|
||||||
|
*/
|
||||||
|
public static CommandResult execCommand(List commands, boolean isRoot, boolean isNeedResultMsg) {
|
||||||
|
return execCommand(commands==null?null:commands.toArray(new String[] {}), isRoot, isNeedResultMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* execute shell commands
|
||||||
|
*
|
||||||
|
* @param commands command array
|
||||||
|
* @param isRoot whether need to run with root
|
||||||
|
* @param isNeedResultMsg whether need result msg
|
||||||
|
* @return
|
||||||
|
|
||||||
|
|
||||||
|
*
|
||||||
|
if isNeedResultMsg is false, {@link CommandResult#successMsg} is null and
|
||||||
|
* {@link CommandResult#errorMsg} is null.
|
||||||
|
|
||||||
|
*
|
||||||
|
if {@link CommandResult#result} is -1, there maybe some excepiton.
|
||||||
|
|
||||||
|
*
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
public static CommandResult execCommand(Object[] commands, boolean isRoot, boolean isNeedResultMsg) {
|
||||||
|
int result = -1;
|
||||||
|
if (commands == null || commands.length == 0) {
|
||||||
|
return new CommandResult(result, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Process process = null;
|
||||||
|
BufferedReader successResult = null;
|
||||||
|
BufferedReader errorResult = null;
|
||||||
|
StringBuilder successMsg = null;
|
||||||
|
StringBuilder errorMsg = null;
|
||||||
|
|
||||||
|
|
||||||
|
DataOutputStream os = null;
|
||||||
|
try {
|
||||||
|
process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);
|
||||||
|
os = new DataOutputStream(process.getOutputStream());
|
||||||
|
for (Object command : commands) {
|
||||||
|
String strCommand= String.valueOf(command);
|
||||||
|
|
||||||
|
if (strCommand == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// donnot use os.writeBytes(strCommand), avoid chinese charset error
|
||||||
|
os.write(strCommand.getBytes());
|
||||||
|
os.writeBytes(COMMAND_LINE_END);
|
||||||
|
os.flush();
|
||||||
|
}
|
||||||
|
os.writeBytes(COMMAND_EXIT);
|
||||||
|
os.flush();
|
||||||
|
|
||||||
|
|
||||||
|
result = process.waitFor();
|
||||||
|
// get command result
|
||||||
|
if (isNeedResultMsg) {
|
||||||
|
successMsg = new StringBuilder();
|
||||||
|
errorMsg = new StringBuilder();
|
||||||
|
successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||||
|
errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
||||||
|
String s;
|
||||||
|
while ((s = successResult.readLine()) != null) {
|
||||||
|
successMsg.append(s);
|
||||||
|
}
|
||||||
|
while ((s = errorResult.readLine()) != null) {
|
||||||
|
errorMsg.append(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (os != null) {
|
||||||
|
os.close();
|
||||||
|
}
|
||||||
|
if (successResult != null) {
|
||||||
|
successResult.close();
|
||||||
|
}
|
||||||
|
if (errorResult != null) {
|
||||||
|
errorResult.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (process != null) {
|
||||||
|
process.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new CommandResult(result, successMsg == null ? null : successMsg.toString(), errorMsg == null ? null
|
||||||
|
: errorMsg.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,337 @@
|
|||||||
|
///**
|
||||||
|
// * siir.es.adbWireless.adbWireless.java
|
||||||
|
// *
|
||||||
|
// * This program is free software: you can redistribute it and/or modify
|
||||||
|
// * it under the terms of the GNU General Public License as published by
|
||||||
|
// * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// * (at your option) any later version.
|
||||||
|
// *
|
||||||
|
// * This program is distributed in the hope that it will be useful,
|
||||||
|
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// * GNU General Public License for more details.
|
||||||
|
// *
|
||||||
|
// * You should have received a copy of the GNU General Public License
|
||||||
|
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
// *
|
||||||
|
// **/
|
||||||
|
//
|
||||||
|
//package com.xypower.wpywapp.tool;
|
||||||
|
//
|
||||||
|
//import android.app.Activity;
|
||||||
|
//import android.app.AlertDialog;
|
||||||
|
//import android.app.Notification;
|
||||||
|
//import android.app.NotificationManager;
|
||||||
|
//import android.app.PendingIntent;
|
||||||
|
//import android.appwidget.AppWidgetManager;
|
||||||
|
//import android.content.ComponentName;
|
||||||
|
//import android.content.Context;
|
||||||
|
//import android.content.DialogInterface;
|
||||||
|
//import android.content.Intent;
|
||||||
|
//import android.content.SharedPreferences;
|
||||||
|
//import android.net.wifi.WifiInfo;
|
||||||
|
//import android.net.wifi.WifiManager;
|
||||||
|
//import android.os.PowerManager;
|
||||||
|
//import android.os.PowerManager.WakeLock;
|
||||||
|
//import android.preference.PreferenceManager;
|
||||||
|
//import android.widget.Toast;
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//import java.io.BufferedReader;
|
||||||
|
//import java.io.DataOutputStream;
|
||||||
|
//import java.io.InputStreamReader;
|
||||||
|
//
|
||||||
|
//public class Utils {
|
||||||
|
//
|
||||||
|
// public static NotificationManager mNotificationManager;
|
||||||
|
// public static WakeLock mWakeLock;
|
||||||
|
//
|
||||||
|
// public static final int START_NOTIFICATION_ID = 1;
|
||||||
|
// public static final int ACTIVITY_SETTINGS = 2;
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// @SuppressWarnings("deprecation")
|
||||||
|
// public static boolean adbStart(Context context) {
|
||||||
|
// try {
|
||||||
|
//// if (!adbWireless.USB_DEBUG) {
|
||||||
|
// Utils.setProp("service.adb.tcp.port", "5555");
|
||||||
|
// try {
|
||||||
|
// if (Utils.isProcessRunning("adbd")) {
|
||||||
|
// Utils.runRootCommand("stop adbd");
|
||||||
|
// }
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// }
|
||||||
|
// Utils.runRootCommand("start adbd");
|
||||||
|
//// }
|
||||||
|
// try {
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// }
|
||||||
|
// SharedPreferences settings = context.getSharedPreferences("wireless", 0);
|
||||||
|
// SharedPreferences.Editor editor = settings.edit();
|
||||||
|
// editor.putBoolean("mState", true);
|
||||||
|
// editor.commit();
|
||||||
|
//
|
||||||
|
//// ComponentName cn = new ComponentName(context, adbWidgetProvider.class);
|
||||||
|
//// AppWidgetManager.getInstance(context).updateAppWidget(cn, adbWireless.remoteViews);
|
||||||
|
//
|
||||||
|
// // Try to auto connect
|
||||||
|
//// if (Utils.prefsAutoCon(context)) {
|
||||||
|
// Utils.autoConnect(context, "c");
|
||||||
|
//// }
|
||||||
|
//
|
||||||
|
// // Wake Lock
|
||||||
|
//// if (Utils.prefsScreenOn(context)) {
|
||||||
|
//// final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
|
//// mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, context.getClass().getName());
|
||||||
|
//// mWakeLock.acquire();
|
||||||
|
//// }
|
||||||
|
//
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static boolean adbStop(Context context) throws Exception {
|
||||||
|
// try {
|
||||||
|
//// if (!adbWireless.USB_DEBUG) {
|
||||||
|
//// if (adbWireless.mState) {
|
||||||
|
// setProp("service.adb.tcp.port", "-1");
|
||||||
|
// runRootCommand("stop adbd");
|
||||||
|
// runRootCommand("start adbd");
|
||||||
|
//// }
|
||||||
|
//// }
|
||||||
|
// adbWireless.mState = false;
|
||||||
|
//
|
||||||
|
// SharedPreferences settings = context.getSharedPreferences("wireless", 0);
|
||||||
|
// SharedPreferences.Editor editor = settings.edit();
|
||||||
|
// editor.putBoolean("mState", false);
|
||||||
|
// editor.commit();
|
||||||
|
//
|
||||||
|
// ComponentName cn = new ComponentName(context, adbWidgetProvider.class);
|
||||||
|
// AppWidgetManager.getInstance(context).updateAppWidget(cn, adbWireless.remoteViews);
|
||||||
|
//
|
||||||
|
// // Try to auto disconnect
|
||||||
|
// if (Utils.prefsAutoCon(context)) {
|
||||||
|
// Utils.autoConnect(context, "d");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Wake Lock
|
||||||
|
// if(mWakeLock != null) {
|
||||||
|
// mWakeLock.release();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (Utils.mNotificationManager != null) {
|
||||||
|
// Utils.mNotificationManager.cancelAll();
|
||||||
|
// }
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// return true;
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static void autoConnect(Context context, String mode) {
|
||||||
|
// String autoConIP = Utils.prefsAutoConIP(context);
|
||||||
|
// String autoConPort = Utils.prefsAutoConPort(context);
|
||||||
|
//
|
||||||
|
// if (autoConIP.trim().equals("") || autoConPort.trim().equals("")) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// String urlRequest = "http://" + autoConIP + ":" + autoConPort + "/" + mode + "/" + Utils.getWifiIp(context);
|
||||||
|
//
|
||||||
|
// try {
|
||||||
|
//// new AutoConnectTask(urlRequest).execute();
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static boolean isProcessRunning(String processName) throws Exception {
|
||||||
|
// boolean running = false;
|
||||||
|
// Process process = null;
|
||||||
|
// process = Runtime.getRuntime().exec("ps");
|
||||||
|
// BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||||
|
// String line = null;
|
||||||
|
// while ((line = in.readLine()) != null) {
|
||||||
|
// if (line.contains(processName)) {
|
||||||
|
// running = true;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// in.close();
|
||||||
|
// process.waitFor();
|
||||||
|
// return running;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static boolean hasRootPermission() {
|
||||||
|
// Process process = null;
|
||||||
|
// DataOutputStream os = null;
|
||||||
|
// boolean rooted = true;
|
||||||
|
// try {
|
||||||
|
// process = Runtime.getRuntime().exec("su");
|
||||||
|
// os = new DataOutputStream(process.getOutputStream());
|
||||||
|
// os.writeBytes("exit\n");
|
||||||
|
// os.flush();
|
||||||
|
// process.waitFor();
|
||||||
|
// if (process.exitValue() != 0) {
|
||||||
|
// rooted = false;
|
||||||
|
// }
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// rooted = false;
|
||||||
|
// } finally {
|
||||||
|
// if (os != null) {
|
||||||
|
// try {
|
||||||
|
// os.close();
|
||||||
|
// process.destroy();
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return rooted;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static boolean runRootCommand(String command) {
|
||||||
|
// Process process = null;
|
||||||
|
// DataOutputStream os = null;
|
||||||
|
// try {
|
||||||
|
// process = Runtime.getRuntime().exec("su");
|
||||||
|
// os = new DataOutputStream(process.getOutputStream());
|
||||||
|
// os.writeBytes(command + "\n");
|
||||||
|
// os.writeBytes("exit\n");
|
||||||
|
// os.flush();
|
||||||
|
// process.waitFor();
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// return false;
|
||||||
|
// } finally {
|
||||||
|
// try {
|
||||||
|
// if (os != null) {
|
||||||
|
// os.close();
|
||||||
|
// }
|
||||||
|
// process.destroy();
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static boolean setProp(String property, String value) {
|
||||||
|
// return runRootCommand("setprop " + property + " " + value);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static String getWifiIp(Context context) {
|
||||||
|
// WifiManager mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||||
|
// int ip = mWifiManager.getConnectionInfo().getIpAddress();
|
||||||
|
// return (ip & 0xFF) + "." + ((ip >> 8) & 0xFF) + "." + ((ip >> 16) & 0xFF) + "." + ((ip >> 24) & 0xFF);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static void enableWiFi(Context context, boolean enable) {
|
||||||
|
// if (enable) {
|
||||||
|
//// Toast.makeText(context, R.string.turning_on_wifi, Toast.LENGTH_LONG).show();
|
||||||
|
// } else {
|
||||||
|
//// Toast.makeText(context, R.string.turning_off_wifi, Toast.LENGTH_LONG).show();
|
||||||
|
// }
|
||||||
|
// WifiManager mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||||
|
// mWifiManager.setWifiEnabled(enable);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static boolean checkWifiState(Context context) {
|
||||||
|
// try {
|
||||||
|
// WifiManager mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||||
|
// WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
|
||||||
|
// if (!mWifiManager.isWifiEnabled() || wifiInfo.getSSID() == null) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return true;
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//// @SuppressWarnings("deprecation")
|
||||||
|
//// public static void showNotification(Context context, int icon, String text) {
|
||||||
|
//// final Notification notifyDetails = new Notification(icon, text, System.currentTimeMillis());
|
||||||
|
//// notifyDetails.flags = Notification.FLAG_ONGOING_EVENT;
|
||||||
|
////
|
||||||
|
//// if (prefsSound(context)) {
|
||||||
|
//// notifyDetails.defaults |= Notification.DEFAULT_SOUND;
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// if (prefsVibrate(context)) {
|
||||||
|
//// notifyDetails.defaults |= Notification.DEFAULT_VIBRATE;
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// Intent notifyIntent = new Intent(context, adbWireless.class);
|
||||||
|
//// notifyIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
//// PendingIntent intent = PendingIntent.getActivity(context, 0, notifyIntent, 0);
|
||||||
|
//// notifyDetails.setLatestEventInfo(context, context.getResources().getString(R.string.noti_title), text, intent);
|
||||||
|
////
|
||||||
|
//// if (Utils.mNotificationManager != null) {
|
||||||
|
//// Utils.mNotificationManager.notify(Utils.START_NOTIFICATION_ID, notifyDetails);
|
||||||
|
//// } else {
|
||||||
|
//// Utils.mNotificationManager = (NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// Utils.mNotificationManager.notify(Utils.START_NOTIFICATION_ID, notifyDetails);
|
||||||
|
//// }
|
||||||
|
//
|
||||||
|
//// public static boolean prefsOnBoot(Context context) {
|
||||||
|
//// SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
//// return pref.getBoolean(context.getResources().getString(R.string.pref_onboot_key), false);
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// public static boolean prefsVibrate(Context context) {
|
||||||
|
//// SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
//// return pref.getBoolean(context.getResources().getString(R.string.pref_vibrate_key), true);
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// public static boolean prefsSound(Context context) {
|
||||||
|
//// SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
//// return pref.getBoolean(context.getResources().getString(R.string.pref_sound_key), true);
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// public static boolean prefsNoti(Context context) {
|
||||||
|
//// SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
//// return pref.getBoolean(context.getResources().getString(R.string.pref_noti_key), true);
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// public static boolean prefsHaptic(Context context) {
|
||||||
|
//// SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
//// return pref.getBoolean(context.getResources().getString(R.string.pref_haptic_key), true);
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// public static boolean prefsWiFiOn(Context context) {
|
||||||
|
//// SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
//// return pref.getBoolean(context.getResources().getString(R.string.pref_wifi_on_key), false);
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// public static boolean prefsWiFiOff(Context context) {
|
||||||
|
//// SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
//// return pref.getBoolean(context.getResources().getString(R.string.pref_wifi_off_key), false);
|
||||||
|
////
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// public static boolean prefsAutoCon(Context context) {
|
||||||
|
//// SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
//// return pref.getBoolean(context.getResources().getString(R.string.pref_autocon_key), false);
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// public static String prefsAutoConIP(Context context) {
|
||||||
|
//// SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
//// return pref.getString(context.getResources().getString(R.string.pref_autoconip_key), "");
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// public static String prefsAutoConPort(Context context) {
|
||||||
|
//// SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
//// return pref.getString(context.getResources().getString(R.string.pref_autoconport_key), "8555");
|
||||||
|
//// }
|
||||||
|
////
|
||||||
|
//// public static boolean prefsScreenOn(Context context) {
|
||||||
|
//// SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
//// return pref.getBoolean(context.getResources().getString(R.string.pref_screenon_key), false);
|
||||||
|
//// }
|
||||||
|
//
|
||||||
|
//}
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.xypower.wpywapp.ui;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.navigation.NavController;
|
||||||
|
import androidx.navigation.Navigation;
|
||||||
|
import androidx.navigation.ui.NavigationUI;
|
||||||
|
|
||||||
|
import com.xypower.wpywapp.BR;
|
||||||
|
import com.xypower.wpywapp.R;
|
||||||
|
import com.xypower.wpywapp.base.BaseActivity;
|
||||||
|
import com.xypower.wpywapp.databinding.ActivityBottomBinding;
|
||||||
|
import com.xypower.wpywapp.interfaces.MainActCallback;
|
||||||
|
import com.xypower.wpywapp.page.DataBindingConfig;
|
||||||
|
import com.xypower.wpywapp.viewmoel.BottomViewModel;
|
||||||
|
|
||||||
|
public class BottomActivity extends BaseActivity {
|
||||||
|
|
||||||
|
private ActivityBottomBinding binding;
|
||||||
|
private BottomViewModel mState;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initViewModel() {
|
||||||
|
mState = getActivityScopeViewModel(BottomViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DataBindingConfig getDataBindingConfig() {
|
||||||
|
return new DataBindingConfig(R.layout.activity_bottom, BR.vm, mState).addBindingParam(BR.callback, new MainActCallback() {
|
||||||
|
@Override
|
||||||
|
public void link(View view) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getInfo(View view) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void takePic(View view) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
binding = (ActivityBottomBinding) getBinding();
|
||||||
|
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_bottom);
|
||||||
|
NavigationUI.setupWithNavController(binding.navView, navController);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.xypower.wpywapp.ui.dashboard;
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
public class DashboardViewModel extends ViewModel {
|
||||||
|
|
||||||
|
|
||||||
|
public DashboardViewModel() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.xypower.wpywapp.ui.home;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
public class HomeViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private final MutableLiveData<String> mText;
|
||||||
|
|
||||||
|
public HomeViewModel() {
|
||||||
|
mText = new MutableLiveData<>();
|
||||||
|
mText.setValue("This is home fragment");
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<String> getText() {
|
||||||
|
return mText;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.xypower.wpywapp.ui.notifications;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
public class NotificationsViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private final MutableLiveData<String> mText;
|
||||||
|
|
||||||
|
public NotificationsViewModel() {
|
||||||
|
mText = new MutableLiveData<>();
|
||||||
|
mText.setValue("This is notifications fragment");
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<String> getText() {
|
||||||
|
return mText;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.xypower.wpywapp.viewmoel;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
public class BottomViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private final MutableLiveData<String> mText;
|
||||||
|
|
||||||
|
public BottomViewModel() {
|
||||||
|
mText = new MutableLiveData<>();
|
||||||
|
mText.setValue("This is notifications fragment");
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<String> getText() {
|
||||||
|
return mText;
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
@ -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>
|
@ -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,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>
|
@ -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>
|
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="vm"
|
||||||
|
type="com.xypower.wpywapp.viewmoel.BottomViewModel" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="callback"
|
||||||
|
type="com.xypower.wpywapp.interfaces.MainActCallback" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/nav_host_fragment_activity_bottom"
|
||||||
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:defaultNavHost="false"
|
||||||
|
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" />
|
||||||
|
|
||||||
|
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
|
android:id="@+id/nav_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:menu="@menu/bottom_nav_menu" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout>
|
||||||
|
<data>
|
||||||
|
<variable
|
||||||
|
name="bean"
|
||||||
|
type="com.xypower.wpywapp.bean.TerminalBean" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="callback"
|
||||||
|
type="com.xypower.wpywapp.interfaces.MainActCallback" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="30dp"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ip_lv"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/ip"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="输入IP地址"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/link"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/link"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="@{callback::link}"
|
||||||
|
android:text="连接设备"
|
||||||
|
app:layout_constraintRight_toRightOf="parent" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</layout>
|
@ -0,0 +1,83 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout 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">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="vm"
|
||||||
|
type="com.xypower.wpywapp.ui.dashboard.DashboardViewModel" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="10dp">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/video_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
|
||||||
|
android:background="@color/cardview_dark_background"
|
||||||
|
app:cardCornerRadius="10dp"
|
||||||
|
app:layout_constraintHeight_percent="0.4"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<VideoView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"></VideoView>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/channel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:text="视频通道选择"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/video_info" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinner"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="5dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/channel"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/video_info"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/channel" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_maliu"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:text="码流通道选择"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/channel" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinner2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="5dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/video_maliu"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/video_maliu" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="播放"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/spinner2"></Button>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
@ -0,0 +1,906 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout 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">
|
||||||
|
<data>
|
||||||
|
<variable
|
||||||
|
name="vm"
|
||||||
|
type="com.xypower.wpywapp.ui.home.HomeViewModel" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ui.home.HomeFragment">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:scrollbars="none"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/basc_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:background="@color/cardview_dark_background"
|
||||||
|
app:cardCornerRadius="10dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/model_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="设备型号:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/model_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/model_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/series_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="系列号:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/model_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/series_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/series_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/hw_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="硬件版本:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/series_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/hw_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/hw_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sys_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="系统版本:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/hw_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/sys_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/sys_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mcu_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="MCU版本:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/sys_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/mcu_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/mcu_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/runtime_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="运行时间:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/mcu_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/runtime_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/runtime_hint" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="更新"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/runtime_hint"></Button>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
|
||||||
|
android:id="@+id/sim_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:background="@color/cardview_dark_background"
|
||||||
|
app:cardCornerRadius="10dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/basc_info">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/operator_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="运营商(内):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/operator_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/operator_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nettype_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="网络类型(内):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/operator_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/nettype_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/nettype_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/iccid_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="ICCID(内):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/nettype_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/iccid_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/iccid_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/apn_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="APN(内):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/iccid_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/apn_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/apn_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/singal_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="信号(内):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/apn_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/singal_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/singal_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/net_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="网络(内):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/singal_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/net_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/net_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/operator_w_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="运营商(外):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/net_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/operator_w_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/operator_w_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nettype_w_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="网络类型(外):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/operator_w_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/nettype_w_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/nettype_w_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/iccid_w_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="ICCID(外):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/nettype_w_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/iccid_w_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/iccid_w_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/apn_w_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="APN(外):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/iccid_w_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/apn_w_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/apn_w_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/singal_w_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="信号(外):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/apn_w_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/singal_w_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/singal_w_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/net_w_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="网络(外):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/singal_w_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/net_w_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/net_w_hint" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="更新"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/net_w_hint"></Button>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/mcu_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:background="@color/cardview_dark_background"
|
||||||
|
app:cardCornerRadius="10dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/sim_info">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mcu_version_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="版本号:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/mcu_version_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/mcu_version_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sun_power_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="太阳能电压:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/mcu_version_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/sun_power_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/sun_power_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/battery_dl_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="电池电量:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/sun_power_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/battery_dl_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/battery_dl_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/battery_dy_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="电池电压:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/battery_dl_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/battery_dy_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/battery_dy_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/work_dl_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="信号(内):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/battery_dy_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/work_dl_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/work_dl_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/charge_status_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="充电状态:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/work_dl_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/charge_status_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/charge_status_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/charge_dl_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="充电电流:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/charge_status_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/charge_dl_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/charge_dl_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bord_temp_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="板子温度:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/charge_dl_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/bord_temp_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/bord_temp_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/battery_temp_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="电池温度:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/bord_temp_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/battery_temp_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/battery_temp_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sddy_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="上电电压:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/battery_temp_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/sddy_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/sddy_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dddy_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="断电电压:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/sddy_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/dddy_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/dddy_hint" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="更新"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/dddy_hint"></Button>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/wifi_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:background="@color/cardview_dark_background"
|
||||||
|
app:cardCornerRadius="10dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/mcu_info">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/redian_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="热点开关:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/redian_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/redian_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/redian_name_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="热点名称:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/redian_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/redian_name_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/redian_name_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/wifi_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="WIFI开关:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/redian_name_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/wifi_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/wifi_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/wifi_state_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="WIFI状态:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/wifi_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/wifi_state_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/wifi_state_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/wifi_link_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="信号(内):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/wifi_state_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/wifi_link_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/wifi_link_hint" />
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="更新"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/wifi_link_hint"></Button>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/tf_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:background="@color/cardview_dark_background"
|
||||||
|
app:cardCornerRadius="10dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/wifi_info">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tf1_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="TF1(SD):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/tf1_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/tf1_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tf2_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="TF2(USB):"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tf1_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/tf2_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/tf2_hint" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="更新"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tf2_hint"></Button>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/jiami_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:background="@color/cardview_dark_background"
|
||||||
|
app:cardCornerRadius="10dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tf_info">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/jiami_version_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="版本号:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/jiami_version_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/jiami_version_hint" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="更新"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/jiami_version_hint"></Button>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/yunwei_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:background="@color/cardview_dark_background"
|
||||||
|
app:cardCornerRadius="10dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/jiami_info">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/yunwei_model_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="运维模式:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/yunwei_model_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/yunwei_model_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/yunwei_params_hint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="运维参数:"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/yunwei_model_hint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="未知"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/yunwei_params_hint"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/yunwei_params_hint" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:text="更新"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/yunwei_params_hint"></Button>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
@ -0,0 +1,83 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout 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">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="vm"
|
||||||
|
type="com.xypower.wpywapp.ui.notifications.NotificationsViewModel" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="10dp">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/video_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
|
||||||
|
android:background="@color/cardview_dark_background"
|
||||||
|
app:cardCornerRadius="10dp"
|
||||||
|
app:layout_constraintHeight_percent="0.4"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"></ImageView>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/channel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:text="通道选择"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/video_info" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinner"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="5dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/channel"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/video_info"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/channel" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_maliu"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:text="分辨率选择"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/channel" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinner2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="5dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/video_maliu"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/video_maliu" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="拍照"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/spinner2"></Button>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
@ -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_home"
|
||||||
|
android:icon="@drawable/ic_home_black_24dp"
|
||||||
|
android:title="@string/title_home" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/navigation_dashboard"
|
||||||
|
android:icon="@drawable/ic_dashboard_black_24dp"
|
||||||
|
android:title="@string/title_dashboard" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/navigation_notifications"
|
||||||
|
android:icon="@drawable/ic_notifications_black_24dp"
|
||||||
|
android:title="@string/title_notifications" />
|
||||||
|
|
||||||
|
</menu>
|
@ -0,0 +1,6 @@
|
|||||||
|
<?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" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
@ -0,0 +1,6 @@
|
|||||||
|
<?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" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue