Skip to content

Commit 8b58f1a

Browse files
Update MainActivity to support TiShiNeng structure, fix layout casting issues, and improve location UX
1 parent 80b529a commit 8b58f1a

2 files changed

Lines changed: 150 additions & 9 deletions

File tree

app/src/main/java/com/tensorhub/manifold/MainActivity.java

Lines changed: 146 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.json.JSONArray;
5858
import org.json.JSONException;
5959
import org.json.JSONObject;
60+
import com.google.android.datatransport.Transform;
6061

6162
import java.io.ByteArrayOutputStream;
6263
import java.io.File;
@@ -392,7 +393,7 @@ protected void onCreate(Bundle savedInstanceState) {
392393
// Start button click listener
393394
findViewById(R.id.startBtn).setOnClickListener(v -> {
394395
if (!isLocationEnabled()) {
395-
showMessage("请开启定位服务后重试");
396+
showMessage("��开启定位服务后重试");
396397
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
397398
return;
398399
}
@@ -640,7 +641,11 @@ private void showLeJianDialog() {
640641
}
641642
// 保存账号信息(支持多个账号,phone:password 按行存储)
642643
saveLeJianAccount(phone, password);
643-
loginWithOkHttp(phone, password, distance);
644+
645+
// 切换为针对“体适能APP”的登录与上传逻辑
646+
// loginWithOkHttp(phone, password, distance); // 原乐健逻辑
647+
loginTiShiNeng(phone, password, distance); // 新增的体适能逻辑占位符
648+
644649
dialog.dismiss();
645650
} catch (Exception e) {
646651
showToast("无效的里程输入");
@@ -830,6 +835,143 @@ private void showToast(String message) {
830835
);
831836
}
832837

838+
/**
839+
* 适配指南:针对“体适能APP”的登录逻辑占位符
840+
*
841+
* 适配新APP通常涉及以下步骤(需要自行抓包分析):
842+
* 1. 抓取“登录”接口:
843+
* - 确定URL(如 https://api.tishineng.com/login)
844+
* - 确定请求方式(POST/GET)
845+
* - 分析请求Header(关键字段:User-Agent, Authorization, Custom-Sign等)
846+
* - 分析请求Body(是否加密?参数名是 username 还是 phone?)
847+
* - 分析响应:获取 Token 或 Cookie,以及 UserID
848+
*
849+
* 2. 抓取“上传跑步/成绩”接口:
850+
* - 确定URL
851+
* - 分析数据结构:是上传轨迹点数组(JSON/GPX),还是仅上传最终里程数值?
852+
* - 分析签名算法:通常会有 sign = MD5(param1 + param2 + salt) 这样的校验
853+
*/
854+
public void loginTiShiNeng(String phone, String password, float distance) {
855+
new Thread(() -> {
856+
try {
857+
// 检查 .so 库是否就绪(通过尝试加载 Transform 类触发)
858+
try {
859+
Transform.INSTANCE.toString();
860+
} catch (Throwable t) {
861+
runOnUiThread(() -> new com.google.android.material.dialog.MaterialAlertDialogBuilder(this)
862+
.setTitle("缺少核心组件")
863+
.setMessage("未检测到 libtsnsecret.so 库,无法进行签名加密。\n\n" +
864+
"请从体适能APK中提取 libtsnsecret.so (arm64-v8a) \n" +
865+
"并放入 app/src/main/jniLibs/arm64-v8a/ 目录下。")
866+
.setPositiveButton("我知道了", null)
867+
.show());
868+
return;
869+
}
870+
871+
// 构造登录 JSON
872+
JSONObject loginPayload = new JSONObject();
873+
loginPayload.put("username", phone);
874+
loginPayload.put("password", password); // 注意:这里可能需要 MD5 或原样发送,需抓包确认
875+
// loginPayload.put("grant_type", "password"); // OAuth 常见参数,需抓包确认
876+
// loginPayload.put("scope", "all");
877+
878+
// 发送登录请求 (URL 需抓包确认,这里是示例占位)
879+
// Request request = ...
880+
// Response response = ...
881+
882+
// 模拟登录成功获取 Token (仅作演示,实际需解析 response)
883+
Log.d("TiShiNeng", "模拟登录流程...");
884+
String mockToken = "mock_token_" + System.currentTimeMillis();
885+
886+
// 实际使用时,请替换为真实 Token
887+
// String token = responseJson.getString("access_token");
888+
889+
runOnUiThread(() -> showToast("体适能登录成功(模拟),准备加密上传..."));
890+
891+
uploadTiShiNengData(mockToken, distance);
892+
893+
} catch (Exception e) {
894+
e.printStackTrace();
895+
runOnUiThread(() -> showToast("体适能登录异常:" + e.getMessage()));
896+
}
897+
}).start();
898+
}
899+
900+
/**
901+
* 适配指南:针对“体适能APP”的数据上传逻辑占位符
902+
*/
903+
public void uploadTiShiNengData(String token, float distance) {
904+
Log.d("TiShiNeng", "准备上传数据,距离:" + distance + "km");
905+
new Thread(() -> {
906+
try {
907+
// 1. 构造原始数据 JSON
908+
// 参考 FuckTSN Hook 到的结构,通常需要包含轨迹点
909+
JSONObject data = new JSONObject();
910+
data.put("sportType", 1); // 跑步
911+
912+
// 生成轨迹点与计算数据 (复用LeJian的逻辑,因为物理运动是一样的)
913+
// 注意:体适能可能有特定的字段名,需抓包确认。这里演示通用结构。
914+
double speed = 500.0; // 配速
915+
int keepTime = (int)(distance * speed);
916+
917+
data.put("sportTime", keepTime);
918+
data.put("sportRange", distance * 1000); // meters
919+
data.put("begintime", System.currentTimeMillis() - keepTime * 1000);
920+
data.put("endtime", System.currentTimeMillis());
921+
data.put("speed", distance * 1000.0 / keepTime);
922+
923+
// 生成轨迹 (Important: TiShiNeng likely validates this)
924+
// data.put("trajectory", generateEvaluatedTrajectory(distance)); // 需实现
925+
926+
String jsonRaw = data.toString();
927+
String timestamp = String.valueOf(System.currentTimeMillis());
928+
929+
// 2. 调用核心 native 方法进行加密 (Transform.s)
930+
// s 方法返回数组:[0]=解密key, [1]=加密后的data
931+
String[] encryptedResult = Transform.INSTANCE.s(
932+
getApplicationContext(),
933+
token,
934+
timestamp,
935+
jsonRaw
936+
);
937+
938+
if (encryptedResult == null || encryptedResult.length < 2) {
939+
throw new RuntimeException("Native加密失败,返回为空");
940+
}
941+
942+
String key = encryptedResult[0]; // 用于解密的 Key(由服务器使用或客户端解密响应)
943+
String encryptedData = encryptedResult[1]; // Body 中实际传输的密文
944+
945+
// 3. 生成签名 (Transform.v)
946+
String sign = Transform.INSTANCE.v(getApplicationContext(), token, timestamp);
947+
948+
Log.d("TiShiNeng", "加密成功!\nKey: " + key + "\nSign: " + sign + "\nData: " + encryptedData);
949+
950+
// 4. 发送上传请求 (URL 需抓包确认)
951+
/*
952+
Request request = new Request.Builder()
953+
.url("https://api.tishineng.com/v1/upload") // 示例 URL
954+
.header("Authorization", "Bearer " + token)
955+
.header("Sign", sign)
956+
.header("Timestamp", timestamp)
957+
.post(RequestBody.create(MediaType.parse("application/json"), encryptedData))
958+
.build();
959+
960+
// 执行请求...
961+
*/
962+
963+
Thread.sleep(1000);
964+
runOnUiThread(() -> showLongerToast("🎉 本地加密签名成功!(模拟上传) \n请抓包获取真实 URL 完成最后一步。"));
965+
966+
} catch (UnsatisfiedLinkError e) {
967+
runOnUiThread(() -> showToast("错误:找不到 libtsnsecret.so,请确保库文件已放入 jniLibs"));
968+
} catch (Exception e) {
969+
e.printStackTrace();
970+
runOnUiThread(() -> showToast("上传适配异常:" + e.getMessage()));
971+
}
972+
}).start();
973+
}
974+
833975
public void loginWithOkHttp(String phone, String password, float distance) {
834976
new Thread(() -> {
835977
try {
@@ -901,7 +1043,7 @@ public void loginWithOkHttp(String phone, String password, float distance) {
9011043
semesterId = getSemesterId(token);
9021044

9031045
if (token.isEmpty() || id == null || schoolId == null || semesterId == null) {
904-
runOnUiThread(() -> showToast("登录成功,但缺少必要参数,上传取消"));
1046+
runOnUiThread(() -> showToast("登录成功,但缺少��要参数,上传取消"));
9051047
return;
9061048
}
9071049

@@ -1182,7 +1324,6 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in
11821324

11831325
if (requestCode == 999) { // 通知权限回调
11841326
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
1185-
// ✅ 统一使用 Snackbar 样式的 showMessage
11861327
showMessage("✅ 通知权限已授权");
11871328
} else {
11881329
showPillMessage("未授权通知,可能看不到后台记录", true);
@@ -1206,7 +1347,7 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in
12061347
showMessage("✅ 服务权限已获取,后台记录已开启");
12071348
}
12081349
} else {
1209-
showPillMessage("权限不足,无法准确记录轨迹", true);
1350+
showPillMessage("权限不足,无法准确记录���迹", true);
12101351
new com.google.android.material.dialog.MaterialAlertDialogBuilder(this)
12111352
.setTitle("权限说明")
12121353
.setMessage("轨迹记录需要后台定位权限,否则App切入后台后记录会中断。")

app/src/main/res/layout/activity_main.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@
212212
android:textAppearance="?attr/textAppearanceLabelMedium"
213213
android:textColor="?attr/colorOnSurfaceVariant" />
214214

215-
<TextView
215+
<com.google.android.material.textview.MaterialTextView
216216
android:id="@+id/areaResult"
217217
android:layout_width="wrap_content"
218218
android:layout_height="wrap_content"
@@ -242,7 +242,7 @@
242242
android:text="开始 / 结束 / 用时"
243243
android:textAppearance="?attr/textAppearanceLabelSmall" />
244244

245-
<TextView
245+
<com.google.android.material.textview.MaterialTextView
246246
android:id="@+id/timeResult"
247247
android:layout_width="wrap_content"
248248
android:layout_height="wrap_content"
@@ -271,7 +271,7 @@
271271
android:text="计步数 (系统)"
272272
android:textAppearance="?attr/textAppearanceLabelSmall" />
273273

274-
<TextView
274+
<com.google.android.material.textview.MaterialTextView
275275
android:id="@+id/stepCountText"
276276
android:layout_width="wrap_content"
277277
android:layout_height="wrap_content"
@@ -293,7 +293,7 @@
293293
android:text="真实步数"
294294
android:textAppearance="?attr/textAppearanceLabelSmall" />
295295

296-
<TextView
296+
<com.google.android.material.textview.MaterialTextView
297297
android:id="@+id/realStepCountText"
298298
android:layout_width="wrap_content"
299299
android:layout_height="wrap_content"

0 commit comments

Comments
 (0)