capacitor-dex-editor 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
package com.aetherlink.dexeditor;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
|
+
import android.content.Intent;
|
|
4
5
|
import android.content.pm.PackageInfo;
|
|
5
6
|
import android.content.pm.PackageManager;
|
|
6
7
|
import android.content.pm.Signature;
|
|
8
|
+
import android.net.Uri;
|
|
9
|
+
import android.os.Build;
|
|
7
10
|
import android.util.Log;
|
|
8
11
|
|
|
12
|
+
import androidx.core.content.FileProvider;
|
|
13
|
+
|
|
9
14
|
import com.getcapacitor.JSArray;
|
|
10
15
|
import com.getcapacitor.JSObject;
|
|
11
16
|
|
|
@@ -365,6 +370,145 @@ public class ApkManager {
|
|
|
365
370
|
return result;
|
|
366
371
|
}
|
|
367
372
|
|
|
373
|
+
/**
|
|
374
|
+
* 安装 APK
|
|
375
|
+
*/
|
|
376
|
+
public void installApk(String apkPath) throws Exception {
|
|
377
|
+
if (context == null) {
|
|
378
|
+
throw new Exception("Context not available");
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
File apkFile = new File(apkPath);
|
|
382
|
+
if (!apkFile.exists()) {
|
|
383
|
+
throw new IOException("APK file not found: " + apkPath);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
387
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
388
|
+
|
|
389
|
+
Uri apkUri;
|
|
390
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
391
|
+
// Android 7.0+ 需要使用 FileProvider
|
|
392
|
+
String authority = context.getPackageName() + ".fileprovider";
|
|
393
|
+
apkUri = FileProvider.getUriForFile(context, authority, apkFile);
|
|
394
|
+
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
395
|
+
} else {
|
|
396
|
+
apkUri = Uri.fromFile(apkFile);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
|
|
400
|
+
context.startActivity(intent);
|
|
401
|
+
|
|
402
|
+
Log.d(TAG, "Started APK installation: " + apkPath);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* 列出 APK 指定目录的内容(支持目录导航)
|
|
407
|
+
*/
|
|
408
|
+
public JSObject listApkDirectory(String apkPath, String directory) throws Exception {
|
|
409
|
+
File apkFile = new File(apkPath);
|
|
410
|
+
if (!apkFile.exists()) {
|
|
411
|
+
throw new IOException("APK file not found: " + apkPath);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 规范化目录路径
|
|
415
|
+
if (directory == null) directory = "";
|
|
416
|
+
if (directory.startsWith("/")) directory = directory.substring(1);
|
|
417
|
+
if (!directory.isEmpty() && !directory.endsWith("/")) directory += "/";
|
|
418
|
+
|
|
419
|
+
JSObject result = new JSObject();
|
|
420
|
+
result.put("currentPath", directory.isEmpty() ? "/" : "/" + directory);
|
|
421
|
+
|
|
422
|
+
JSArray items = new JSArray();
|
|
423
|
+
int folderCount = 0;
|
|
424
|
+
int fileCount = 0;
|
|
425
|
+
|
|
426
|
+
// 用于跟踪已添加的目录
|
|
427
|
+
java.util.Set<String> addedDirs = new java.util.HashSet<>();
|
|
428
|
+
|
|
429
|
+
try (ZipFile zipFile = new ZipFile(apkFile)) {
|
|
430
|
+
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
|
431
|
+
while (entries.hasMoreElements()) {
|
|
432
|
+
ZipEntry entry = entries.nextElement();
|
|
433
|
+
String name = entry.getName();
|
|
434
|
+
|
|
435
|
+
// 如果指定了目录,只显示该目录下的内容
|
|
436
|
+
if (!directory.isEmpty()) {
|
|
437
|
+
if (!name.startsWith(directory)) continue;
|
|
438
|
+
name = name.substring(directory.length());
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (name.isEmpty()) continue;
|
|
442
|
+
|
|
443
|
+
// 检查是否是直接子项(不包含更深层的路径)
|
|
444
|
+
int slashIndex = name.indexOf('/');
|
|
445
|
+
boolean isDirectChild = slashIndex == -1 || slashIndex == name.length() - 1;
|
|
446
|
+
|
|
447
|
+
if (isDirectChild) {
|
|
448
|
+
// 直接子项
|
|
449
|
+
JSObject item = new JSObject();
|
|
450
|
+
String displayName = name.endsWith("/") ? name.substring(0, name.length() - 1) : name;
|
|
451
|
+
item.put("name", displayName);
|
|
452
|
+
item.put("path", directory + name);
|
|
453
|
+
item.put("isDirectory", entry.isDirectory());
|
|
454
|
+
item.put("size", entry.getSize());
|
|
455
|
+
item.put("compressedSize", entry.getCompressedSize());
|
|
456
|
+
item.put("lastModified", entry.getTime());
|
|
457
|
+
|
|
458
|
+
// 根据文件类型设置图标类型
|
|
459
|
+
item.put("type", getFileType(displayName, entry.isDirectory()));
|
|
460
|
+
|
|
461
|
+
items.put(item);
|
|
462
|
+
if (entry.isDirectory()) folderCount++;
|
|
463
|
+
else fileCount++;
|
|
464
|
+
} else {
|
|
465
|
+
// 子目录中的文件,添加其父目录
|
|
466
|
+
String dirName = name.substring(0, slashIndex);
|
|
467
|
+
if (!addedDirs.contains(dirName)) {
|
|
468
|
+
addedDirs.add(dirName);
|
|
469
|
+
|
|
470
|
+
JSObject item = new JSObject();
|
|
471
|
+
item.put("name", dirName);
|
|
472
|
+
item.put("path", directory + dirName + "/");
|
|
473
|
+
item.put("isDirectory", true);
|
|
474
|
+
item.put("size", 0);
|
|
475
|
+
item.put("type", "folder");
|
|
476
|
+
|
|
477
|
+
items.put(item);
|
|
478
|
+
folderCount++;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
result.put("items", items);
|
|
485
|
+
result.put("folderCount", folderCount);
|
|
486
|
+
result.put("fileCount", fileCount);
|
|
487
|
+
|
|
488
|
+
return result;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* 获取文件类型
|
|
493
|
+
*/
|
|
494
|
+
private String getFileType(String name, boolean isDirectory) {
|
|
495
|
+
if (isDirectory) return "folder";
|
|
496
|
+
|
|
497
|
+
String lowerName = name.toLowerCase();
|
|
498
|
+
if (lowerName.endsWith(".dex")) return "dex";
|
|
499
|
+
if (lowerName.endsWith(".xml")) return "xml";
|
|
500
|
+
if (lowerName.endsWith(".arsc")) return "resource";
|
|
501
|
+
if (lowerName.endsWith(".so")) return "native";
|
|
502
|
+
if (lowerName.endsWith(".png") || lowerName.endsWith(".jpg") ||
|
|
503
|
+
lowerName.endsWith(".jpeg") || lowerName.endsWith(".webp") ||
|
|
504
|
+
lowerName.endsWith(".gif")) return "image";
|
|
505
|
+
if (lowerName.endsWith(".smali")) return "smali";
|
|
506
|
+
if (lowerName.endsWith(".bin") || lowerName.endsWith(".dat")) return "binary";
|
|
507
|
+
if (lowerName.equals("androidmanifest.xml")) return "manifest";
|
|
508
|
+
|
|
509
|
+
return "file";
|
|
510
|
+
}
|
|
511
|
+
|
|
368
512
|
/**
|
|
369
513
|
* 关闭 APK 会话
|
|
370
514
|
*/
|
|
@@ -390,6 +390,17 @@ public class DexEditorPluginPlugin extends Plugin {
|
|
|
390
390
|
result.put("data", apkManager.getSessionDexFiles(params.getString("sessionId")));
|
|
391
391
|
break;
|
|
392
392
|
|
|
393
|
+
case "installApk":
|
|
394
|
+
apkManager.installApk(params.getString("apkPath"));
|
|
395
|
+
break;
|
|
396
|
+
|
|
397
|
+
case "listApkDirectory":
|
|
398
|
+
result.put("data", apkManager.listApkDirectory(
|
|
399
|
+
params.getString("apkPath"),
|
|
400
|
+
params.optString("directory", "")
|
|
401
|
+
));
|
|
402
|
+
break;
|
|
403
|
+
|
|
393
404
|
default:
|
|
394
405
|
result.put("success", false);
|
|
395
406
|
result.put("error", "Unknown action: " + action);
|