pull/1/head
kai-city 4 years ago
parent efc390724b
commit f8e7c18521
  1. 2
      README.md
  2. 41
      app/build.gradle
  3. 26
      app/src/androidTest/java/top/niunaijun/blackdex/ExampleInstrumentedTest.java
  4. 24
      app/src/androidTest/java/top/niunaijun/blackdex/ExampleInstrumentedTest.kt
  5. 9
      app/src/main/AndroidManifest.xml
  6. 43
      app/src/main/java/top/niunaijun/blackdex/App.java
  7. 26
      app/src/main/java/top/niunaijun/blackdex/App.kt
  8. 45
      app/src/main/java/top/niunaijun/blackdex/MainActivity.java
  9. 18
      app/src/main/java/top/niunaijun/blackdex/data/BlackDexConfiguration.kt
  10. 88
      app/src/main/java/top/niunaijun/blackdex/data/DexDumpRepository.kt
  11. 15
      app/src/main/java/top/niunaijun/blackdex/data/entity/AppInfo.kt
  12. 22
      app/src/main/java/top/niunaijun/blackdex/data/entity/DumpInfo.kt
  13. 27
      app/src/main/java/top/niunaijun/blackdex/util/FileUtil.kt
  14. 22
      app/src/main/java/top/niunaijun/blackdex/util/InjectionUtil.kt
  15. 30
      app/src/main/java/top/niunaijun/blackdex/util/LoadingUtil.kt
  16. 17
      app/src/main/java/top/niunaijun/blackdex/util/ToastEx.kt
  17. 41
      app/src/main/java/top/niunaijun/blackdex/util/ViewBindingEx.kt
  18. 29
      app/src/main/java/top/niunaijun/blackdex/view/base/BaseActivity.kt
  19. 115
      app/src/main/java/top/niunaijun/blackdex/view/base/BaseAdapter.kt
  20. 34
      app/src/main/java/top/niunaijun/blackdex/view/base/BaseViewModel.kt
  21. 211
      app/src/main/java/top/niunaijun/blackdex/view/main/MainActivity.kt
  22. 27
      app/src/main/java/top/niunaijun/blackdex/view/main/MainAdapter.kt
  23. 19
      app/src/main/java/top/niunaijun/blackdex/view/main/MainFactory.kt
  24. 40
      app/src/main/java/top/niunaijun/blackdex/view/main/MainViewModel.kt
  25. 11
      app/src/main/res/drawable-anydpi/ic_folder.xml
  26. 11
      app/src/main/res/drawable-anydpi/ic_search.xml
  27. BIN
      app/src/main/res/drawable-hdpi/ic_folder.png
  28. BIN
      app/src/main/res/drawable-hdpi/ic_search.png
  29. BIN
      app/src/main/res/drawable-mdpi/ic_folder.png
  30. BIN
      app/src/main/res/drawable-mdpi/ic_search.png
  31. BIN
      app/src/main/res/drawable-xhdpi/ic_folder.png
  32. BIN
      app/src/main/res/drawable-xhdpi/ic_search.png
  33. BIN
      app/src/main/res/drawable-xxhdpi/ic_folder.png
  34. BIN
      app/src/main/res/drawable-xxhdpi/ic_search.png
  35. 14
      app/src/main/res/drawable/splash.xml
  36. 52
      app/src/main/res/layout/activity_main.xml
  37. 37
      app/src/main/res/layout/item_package.xml
  38. 12
      app/src/main/res/layout/view_toolbar.xml
  39. 17
      app/src/main/res/menu/menu_main.xml
  40. 16
      app/src/main/res/values-night/themes.xml
  41. 12
      app/src/main/res/values/colors.xml
  42. 5
      app/src/main/res/values/strings.xml
  43. 24
      app/src/main/res/values/themes.xml
  44. 17
      app/src/test/java/top/niunaijun/blackdex/ExampleUnitTest.java
  45. 17
      app/src/test/java/top/niunaijun/blackdex/ExampleUnitTest.kt
  46. 25
      build.gradle
  47. 2
      settings.gradle

@ -2,7 +2,7 @@
![](https://img.shields.io/badge/language-java-brightgreen.svg)
BlackDex是一个运行在Android手机上的脱壳工具,支持5.0~12,无需依赖任何环境任何手机都可以使用,包括模拟器。只需5秒,即可对已安装包括未安装的APK进行脱壳。
BlackDex是一个运行在Android手机上的脱壳工具,支持5.0~12,无需依赖任何环境任何手机都可以使用,包括模拟器。只需秒,即可对已安装包括未安装的APK进行脱壳。
## 项目声明
- 由于 [黑盒BlackBox](https://github.com/nnjun/BlackBox) 已被抄家,多的不想说了,请关注本项目或者其他未来项目吧。

@ -1,5 +1,6 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
@ -14,10 +15,12 @@ android {
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
// SO库架构
abiFilters 'armeabi-v7a', 'x86'
}
}
buildTypes {
@ -26,20 +29,50 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
buildFeatures{
viewBinding true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation project(':Bcore')
//coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2"
//viewModel liveData lifecycle
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
//third
implementation 'com.afollestad.material-dialogs:core:3.3.0'
//dialog
implementation 'com.github.nukc.stateview:kotlin:2.2.0'
//
implementation 'com.roger.catloadinglibrary:catloadinglibrary:1.0.9'
//dialog
implementation 'com.github.Ferfalk:SimpleSearchView:0.2.0'
//searchView
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

@ -1,26 +0,0 @@
package top.niunaijun.blackdex;
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("top.niunaijun.blackdex", appContext.getPackageName());
}
}

@ -0,0 +1,24 @@
package top.niunaijun.blackdex
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("top.niunaijun.blackdex", appContext.packageName)
}
}

@ -1,16 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="top.niunaijun.blackdex">
<!-- Android 11 需要 -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".App"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BlackDex">
<activity android:name=".MainActivity">
<activity android:name=".view.main.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

@ -1,43 +0,0 @@
package top.niunaijun.blackdex;
import android.app.Application;
import android.content.Context;
import java.io.File;
import top.niunaijun.blackbox.BlackDexCore;
import top.niunaijun.blackbox.app.configuration.ClientConfiguration;
/**
* Created by Milk on 2021/5/20.
* * _
* (`ω
*  つ0
* しーJ
* 此处无Bug
*/
public class App extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
String dir = new File(base.getExternalCacheDir().getParent(), "dump").getAbsolutePath();
BlackDexCore.get().doAttachBaseContext(base, new ClientConfiguration() {
@Override
public String getHostPackageName() {
return base.getPackageName();
}
@Override
public String getDexDumpDir() {
// 此处一定要给固定值,可以在doAttachBaseContext之前就把路径确定好。否则doAttachBaseContext后可能会遭到hook。
return dir;
}
});
}
@Override
public void onCreate() {
super.onCreate();
BlackDexCore.get().doCreate();
}
}

@ -0,0 +1,26 @@
package top.niunaijun.blackdex
import android.app.Application
import android.content.Context
import top.niunaijun.blackbox.BlackDexCore
import top.niunaijun.blackbox.app.configuration.ClientConfiguration
import top.niunaijun.blackdex.data.BlackDexConfiguration
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/5/23 14:00
*/
class App : Application() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
BlackDexCore.get().doAttachBaseContext(base,BlackDexConfiguration(base!!))
}
override fun onCreate() {
super.onCreate()
BlackDexCore.get().doCreate()
}
}

@ -1,45 +0,0 @@
package top.niunaijun.blackdex;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import java.io.File;
import top.niunaijun.blackbox.BlackDexCore;
import top.niunaijun.blackbox.core.system.dump.IBDumpMonitor;
import top.niunaijun.blackbox.entity.dump.DumpResult;
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注册dump监听
BlackDexCore.get().registerDumpMonitor(mMonitor);
findViewById(R.id.btn_click).setOnClickListener(v -> {
// 此方法会阻塞
boolean b = BlackDexCore.get().dumpDex("com.hicorenational.antifraud");
if (!b) {
Log.d(TAG, "dumpDex: error.");
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
BlackDexCore.get().unregisterDumpMonitor(mMonitor);
}
private final IBDumpMonitor mMonitor = new IBDumpMonitor.Stub() {
@Override
public void onDump(DumpResult result) {
Log.d(TAG, "onDump: " + result.toString());
}
};
}

@ -0,0 +1,18 @@
package top.niunaijun.blackdex.data
import android.content.Context
import top.niunaijun.blackbox.app.configuration.ClientConfiguration
/**
*
* @Description: 启动配置文件
* @Author: wukaicheng
* @CreateDate: 2021/5/23 14:04
*/
class BlackDexConfiguration(private val context: Context) : ClientConfiguration() {
override fun getHostPackageName(): String {
return context.packageName
}
}

@ -0,0 +1,88 @@
package top.niunaijun.blackdex.data
import android.content.pm.ApplicationInfo
import android.net.Uri
import android.webkit.URLUtil
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackbox.BlackBoxCore.getPackageManager
import top.niunaijun.blackbox.BlackDexCore
import top.niunaijun.blackbox.utils.AbiUtils
import top.niunaijun.blackbox.utils.FileUtils
import top.niunaijun.blackdex.data.entity.AppInfo
import top.niunaijun.blackdex.data.entity.DumpInfo
import java.io.File
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/5/23 14:29
*/
class DexDumpRepository {
private var dumpTaskId = 0
fun getAppList(mAppListLiveData: MutableLiveData<List<AppInfo>>) {
val installedApplications: List<ApplicationInfo> = getPackageManager().getInstalledApplications(0)
val installedList = mutableListOf<AppInfo>()
for (installedApplication in installedApplications) {
val file = File(installedApplication.sourceDir)
if ((installedApplication.flags and ApplicationInfo.FLAG_SYSTEM) != 0) continue
if (!AbiUtils.isSupport(file)) continue
val info = AppInfo(
installedApplication.loadLabel(getPackageManager()).toString(),
installedApplication.packageName,
installedApplication.loadIcon(getPackageManager())
)
installedList.add(info)
}
mAppListLiveData.postValue(installedList)
}
fun dumpDex(source: String, dexDumpLiveData: MutableLiveData<DumpInfo>) {
dexDumpLiveData.postValue(DumpInfo(DumpInfo.LOADING))
val result = if (URLUtil.isValidUrl(source)) {
BlackDexCore.get().dumpDex(Uri.parse(source))
} else {
BlackDexCore.get().dumpDex(source)
}
if(result){
dumpTaskId++
startCountdown(dexDumpLiveData)
}else{
dexDumpLiveData.postValue(DumpInfo(DumpInfo.TIMEOUT))
}
}
fun dumpSuccess(){
dumpTaskId++
}
private fun startCountdown(dexDumpLiveData: MutableLiveData<DumpInfo>){
GlobalScope.launch {
val tempId = dumpTaskId
delay(10000)
if(tempId == dumpTaskId){
dexDumpLiveData.postValue(DumpInfo(DumpInfo.TIMEOUT))
}
}
}
}

@ -0,0 +1,15 @@
package top.niunaijun.blackdex.data.entity
import android.graphics.drawable.Drawable
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/5/23 14:36
*/
data class AppInfo(
val name:String,
val packageName:String,
val icon:Drawable
)

@ -0,0 +1,22 @@
package top.niunaijun.blackdex.data.entity
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/5/23 14:36
*/
data class DumpInfo(
val state: Int,
val msg: String = ""
) {
companion object {
const val SUCCESS = 200
const val FAIL = 404
const val LOADING = 300
const val TIMEOUT = 500
}
}

@ -0,0 +1,27 @@
package top.niunaijun.blackdex.util
import top.niunaijun.blackbox.BlackBoxCore
import top.niunaijun.blackbox.utils.FileUtils
import top.niunaijun.blackbox.utils.compat.BuildCompat
import java.io.File
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/5/23 14:54
*/
object FileUtil {
fun getDexDumpDir(): String {
if (BuildCompat.isR()) {
val dump = File(BlackBoxCore.getContext().externalCacheDir?.parentFile?.parentFile?.parentFile, "Download/dexDump")
FileUtils.mkdirs(dump)
return dump.absolutePath
}else{
val dump = File(BlackBoxCore.getContext().externalCacheDir?.parentFile, "dump")
FileUtils.mkdirs(dump)
return dump.absolutePath
}
}
}

@ -0,0 +1,22 @@
package top.niunaijun.blackdex.util
import top.niunaijun.blackdex.data.DexDumpRepository
import top.niunaijun.blackdex.view.main.MainFactory
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/4/29 22:38
*/
object InjectionUtil {
private val dexDumpRepository = DexDumpRepository()
fun getMainFactory() : MainFactory {
return MainFactory(dexDumpRepository)
}
}

@ -0,0 +1,30 @@
package top.niunaijun.blackdex.util
import android.view.KeyEvent
import androidx.fragment.app.FragmentManager
import com.roger.catloadinglibrary.CatLoadingView
import top.niunaijun.blackdex.R
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/4/30 23:04
*/
object LoadingUtil {
fun showLoading(loadingView: CatLoadingView, fragmentManager: FragmentManager) {
if (!loadingView.isAdded) {
loadingView.setBackgroundColor(R.color.primary)
loadingView.show(fragmentManager, "")
fragmentManager.executePendingTransactions()
loadingView.setClickCancelAble(false)
loadingView.dialog?.setOnKeyListener { _, keyCode, _ ->
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
return@setOnKeyListener true
}
false
}
}
}
}

@ -0,0 +1,17 @@
package top.niunaijun.blackdex.util
import android.content.Context
import android.widget.Toast
import androidx.viewbinding.ViewBinding
import com.google.android.material.snackbar.Snackbar
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/5/2 0:13
*/
fun Context.toast(msg:String){
Toast.makeText(this,msg,Toast.LENGTH_LONG).show()
}

@ -0,0 +1,41 @@
package top.niunaijun.blackdex.util
import android.app.Activity
import android.app.Dialog
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
/**
*
* @Description: viewBinding 扩展类
* @Author: wukaicheng
* @CreateDate: 2021/4/29 21:23
*/
inline fun <reified T : ViewBinding> Activity.inflate(): Lazy<T> = lazy {
inflateBinding(layoutInflater)
}
inline fun <reified T : ViewBinding> Fragment.inflate(): Lazy<T> = lazy {
inflateBinding(layoutInflater)
}
inline fun <reified T : ViewBinding> Dialog.inflate(): Lazy<T> = lazy {
inflateBinding(layoutInflater)
}
inline fun <reified T : ViewBinding> inflateBinding(layoutInflater: LayoutInflater): T {
val method = T::class.java.getMethod("inflate", LayoutInflater::class.java)
return method.invoke(null, layoutInflater) as T
}
inline fun <reified T : ViewBinding> newBindingViewHolder(viewGroup: ViewGroup, attachToParent:Boolean = false): T {
val method = T::class.java.getMethod("inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java)
return method.invoke(null,LayoutInflater.from(viewGroup.context),viewGroup,attachToParent) as T
}

@ -0,0 +1,29 @@
package top.niunaijun.blackdex.view.base
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
/**
*
* @Description:BaseActivity
* @Author: wukaicheng
* @CreateDate: 2021/5/4 15:58
*/
open class BaseActivity : AppCompatActivity() {
protected fun initToolbar(toolbar: Toolbar,title:Int, showBack: Boolean = false, onBack: (() -> Unit)? = null) {
setSupportActionBar(toolbar)
toolbar.setTitle(title)
if (showBack) {
supportActionBar?.let {
it.setDisplayHomeAsUpEnabled(true)
toolbar.setNavigationOnClickListener {
if (onBack != null) {
onBack()
}
finish()
}
}
}
}
}

@ -0,0 +1,115 @@
package top.niunaijun.blackdex.view.base
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
/**
*
* @Description: 抽象adapter
* @Author: wukaicheng
* @CreateDate: 2021/4/29 21:42
*/
abstract class BaseAdapter<T : ViewBinding, D> : RecyclerView.Adapter<BaseAdapter.ViewHolder<T>>() {
var dataList: MutableList<D> = ArrayList()
private var onItemClick: ((position: Int, binding: T, data: D) -> Unit)? = null
private var onLongClick: ((position: Int, binding: T, data: D) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<T> {
return ViewHolder(getViewBinding(parent))
}
override fun getItemCount(): Int {
return dataList.size
}
override fun onBindViewHolder(holder: ViewHolder<T>, position: Int) {
val bean = dataList[position]
val binding = holder.bindIng
initView(binding, position, bean)
binding.root.setOnClickListener {
if (onItemClick != null) {
onItemClick!!(position, holder.bindIng, bean)
}
}
binding.root.setOnLongClickListener {
if (onLongClick != null) {
onLongClick!!(position, holder.bindIng, bean)
}
true
}
}
open fun replaceData(newDataList: List<D>) {
this.dataList = arrayListOf<D>().apply {
newDataList.forEach {
this.add(it)
}
}
notifyDataSetChanged()
}
open fun addData(list: List<D>) {
val index = this.dataList.size
this.dataList.addAll(list)
notifyItemRangeInserted(index, list.size)
}
open fun addData(bean: D) {
val index = this.dataList.size
this.dataList.add(bean)
notifyItemRangeInserted(index, 1)
}
open fun updateData(bean: D, position: Int) {
if (dataList.size > position) {
dataList[position] = bean
notifyItemChanged(position)
}
}
open fun removeData(bean: D): Int {
val position: Int = this.dataList.indexOf(bean)
if (position >= 0) {
removeDataAt(position)
}
return position
}
open fun removeDataAt(position: Int) {
if (position >= 0) {
this.dataList.removeAt(position)
notifyItemRemoved(position)
notifyItemRangeChanged(position, dataList.size - position)
}
}
fun setOnItemClick(function: (position: Int, binding: T, data: D) -> Unit) {
this.onItemClick = function
}
fun setOnItemLongClick(function: (position: Int, binding: T, data: D) -> Unit) {
this.onLongClick = function
}
fun getLayoutInflater(parent: ViewGroup): LayoutInflater {
return LayoutInflater.from(parent.context)
}
abstract fun getViewBinding(parent: ViewGroup): T
abstract fun initView(binding: T, position: Int, data: D)
class ViewHolder<T : ViewBinding>(val bindIng: T) : RecyclerView.ViewHolder(bindIng.root)
}

@ -0,0 +1,34 @@
package top.niunaijun.blackdex.view.base
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.*
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/4/29 23:33
*/
open class BaseViewModel : ViewModel() {
fun launchOnUI(block: suspend CoroutineScope.() -> Unit) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
try {
block()
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
}
override fun onCleared() {
super.onCleared()
viewModelScope.cancel()
}
}

@ -0,0 +1,211 @@
package top.niunaijun.blackdex.view.main
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
import com.ferfalk.simplesearchview.SimpleSearchView
import com.roger.catloadinglibrary.CatLoadingView
import top.niunaijun.blackbox.BlackDexCore
import top.niunaijun.blackbox.core.system.dump.IBDumpMonitor
import top.niunaijun.blackbox.entity.dump.DumpResult
import top.niunaijun.blackdex.R
import top.niunaijun.blackdex.data.entity.AppInfo
import top.niunaijun.blackdex.data.entity.DumpInfo
import top.niunaijun.blackdex.databinding.ActivityMainBinding
import top.niunaijun.blackdex.util.InjectionUtil
import top.niunaijun.blackdex.util.LoadingUtil
import top.niunaijun.blackdex.util.inflate
import top.niunaijun.blackdex.view.base.BaseActivity
class MainActivity : BaseActivity() {
private val viewBinding: ActivityMainBinding by inflate()
private lateinit var viewModel: MainViewModel
private lateinit var mAdapter: MainAdapter
private lateinit var loadingView: CatLoadingView
private var appList: List<AppInfo> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(viewBinding.root)
initToolbar(viewBinding.toolbarLayout.toolbar, R.string.app_name)
initView()
initViewModel()
initSearchView()
BlackDexCore.get().registerDumpMonitor(mMonitor)
}
private fun initView() {
mAdapter = MainAdapter()
viewBinding.recyclerView.adapter = mAdapter
viewBinding.recyclerView.layoutManager = LinearLayoutManager(this)
mAdapter.setOnItemClick { _, _, data ->
viewModel.startDexDump(data.packageName)
}
viewBinding.fab.setOnClickListener {
openDocumentedResult.launch("application/vnd.android.package-archive")
}
}
private fun initViewModel() {
viewModel = ViewModelProvider(this, InjectionUtil.getMainFactory()).get(MainViewModel::class.java)
viewModel.getAppList()
viewBinding.stateView.showLoading()
viewModel.mAppListLiveData.observe(this) {
it?.let {
this.appList = it
viewBinding.searchView.setQuery("", false)
filterApp("")
if (it.isNotEmpty()) {
viewBinding.stateView.showContent()
} else {
viewBinding.stateView.showEmpty()
}
}
}
viewModel.mDexDumpLiveData.observe(this) {
it?.let {
when (it.state) {
DumpInfo.LOADING -> {
showLoading()
}
DumpInfo.TIMEOUT -> {
loadingView.dismiss()
MaterialDialog(this).show {
title(text = "脱壳失败")
message(text = "未知错误,可前往GitHub(https://github.com/CodingGay/BlackDex)提Issue")
negativeButton(text = "Github"){
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/CodingGay/BlackDex/issues"))
startActivity(intent)
}
positiveButton(text = "确定")
}
}
else -> {
viewModel.dexDumpSuccess()
val title = if (it.state == DumpInfo.SUCCESS) {
"脱壳成功"
} else {
"脱壳失败"
}
loadingView.dismiss()
MaterialDialog(this).show {
title(text = title)
message(text = it.msg)
positiveButton(text = "确定")
}
}
}
}
}
}
private val mMonitor = object: IBDumpMonitor.Stub(){
override fun onDump(result: DumpResult?) {
result?.let {
if(result.success){
viewModel.mDexDumpLiveData.postValue(DumpInfo(DumpInfo.SUCCESS,"DEX文件储存在:${result.dir}"))
}else{
viewModel.mDexDumpLiveData.postValue(DumpInfo(DumpInfo.FAIL,"错误原因:${result.msg}"))
}
}
}
}
private fun initSearchView() {
viewBinding.searchView.setOnQueryTextListener(object : SimpleSearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String): Boolean {
filterApp(newText)
return true
}
override fun onQueryTextCleared(): Boolean {
return true
}
override fun onQueryTextSubmit(query: String): Boolean {
return true
}
})
}
private fun filterApp(newText: String) {
val newList = this.appList.filter {
it.name.contains(newText, true) or it.packageName.contains(newText, true)
}
mAdapter.replaceData(newList)
}
private fun showLoading() {
if (!this::loadingView.isInitialized) {
loadingView = CatLoadingView()
}
LoadingUtil.showLoading(loadingView, supportFragmentManager)
}
private val openDocumentedResult = registerForActivityResult(ActivityResultContracts.GetContent()) {
it?.run {
viewModel.startDexDump(it.toString())
}
}
override fun onBackPressed() {
if (viewBinding.searchView.isSearchOpen) {
viewBinding.searchView.closeSearch()
} else {
super.onBackPressed()
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
val item = menu!!.findItem(R.id.main_search)
viewBinding.searchView.setMenuItem(item)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
R.id.main_git->{
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/CodingGay/BlackDex"))
startActivity(intent)
}
}
return super.onOptionsItemSelected(item)
}
}

@ -0,0 +1,27 @@
package top.niunaijun.blackdex.view.main
import android.view.ViewGroup
import top.niunaijun.blackdex.data.entity.AppInfo
import top.niunaijun.blackdex.databinding.ItemPackageBinding
import top.niunaijun.blackdex.util.newBindingViewHolder
import top.niunaijun.blackdex.view.base.BaseAdapter
/**
*
* @Description: 软件显示界面适配器
* @Author: wukaicheng
* @CreateDate: 2021/4/29 21:52
*/
class MainAdapter : BaseAdapter<ItemPackageBinding, AppInfo>() {
override fun getViewBinding(parent: ViewGroup): ItemPackageBinding {
return newBindingViewHolder(parent, false)
}
override fun initView(binding: ItemPackageBinding, position: Int, data: AppInfo) {
binding.icon.setImageDrawable(data.icon)
binding.name.text = data.name
binding.packageName.text = data.packageName
}
}

@ -0,0 +1,19 @@
package top.niunaijun.blackdex.view.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import top.niunaijun.blackdex.data.DexDumpRepository
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/5/23 14:29
*/
@Suppress("UNCHECKED_CAST")
class MainFactory(private val repo:DexDumpRepository): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(repo) as T
}
}

@ -0,0 +1,40 @@
package top.niunaijun.blackdex.view.main
import androidx.lifecycle.MutableLiveData
import top.niunaijun.blackdex.data.DexDumpRepository
import top.niunaijun.blackdex.data.entity.AppInfo
import top.niunaijun.blackdex.data.entity.DumpInfo
import top.niunaijun.blackdex.view.base.BaseViewModel
/**
*
* @Description:
* @Author: wukaicheng
* @CreateDate: 2021/5/23 14:29
*/
class MainViewModel(private val repo: DexDumpRepository) : BaseViewModel() {
val mAppListLiveData = MutableLiveData<List<AppInfo>>()
val mDexDumpLiveData = MutableLiveData<DumpInfo>()
fun getAppList() {
launchOnUI {
repo.getAppList(mAppListLiveData)
}
}
fun startDexDump(source: String) {
launchOnUI {
repo.dumpDex(source, mDexDumpLiveData)
}
}
fun dexDumpSuccess() {
launchOnUI {
repo.dumpSuccess()
}
}
}

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="@android:color/white"
android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
</vector>

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="@android:color/white"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
</shape>
</item>
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/ic_launcher" />
</item>
</layer-list>

@ -1,15 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:gravity="center"
tools:context=".MainActivity">
android:orientation="vertical">
<Button
android:id="@+id/btn_click"
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="@+id/toolbar_layout"
layout="@layout/view_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
<com.ferfalk.simplesearchview.SimpleSearchView
android:id="@+id/searchView"
app:type="card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/primary" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.github.nukc.stateview.StateView
android:id="@+id/stateView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:id="@+id/fab"
android:layout_gravity="bottom|end"
android:layout_margin="24dp"
android:src="@drawable/ic_folder"
android:layout_height="wrap_content"
android:text="@string/app_name" />
android:contentDescription="TODO" />
</FrameLayout>
</LinearLayout>

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:orientation="horizontal"
android:padding="8dp"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/icon"
android:layout_width="48dp"
android:layout_height="48dp" />
<LinearLayout
android:orientation="vertical"
android:layout_gravity="center"
android:layout_marginStart="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:textSize="16sp"
android:textColor="@color/primary_text"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/packageName"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.MaterialToolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ToolBarColorStyle"
android:background="@color/primary"
app:title="@string/app_name"
app:titleTextColor="@color/white">
</com.google.android.material.appbar.MaterialToolbar>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/main_search"
android:icon="@drawable/ic_search"
android:title="@string/filter"
app:showAsAction="ifRoom" />
<item
android:id="@+id/main_git"
android:title="@string/open_source"
app:showAsAction="never"/>
</menu>

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

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="primary">#3F3F3F</color>
<color name="primary_dark">#3F3F3F</color>
<color name="primary_light">#3F3F3F</color>
<color name="accent">#ffffff</color>
<color name="primary_text">#212121</color>
<color name="secondary_text">#757575</color>
<color name="white">#FFFFFFFF</color>
</resources>

@ -1,3 +1,8 @@
<resources>
<string name="app_name">BlackDex</string>
<string name="filter">过滤</string>
<string name="choose">选择</string>
<string name="log">软件日志</string>
<string name="open_source">开源地址</string>
</resources>

@ -1,16 +1,28 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.BlackDex" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<style name="Theme.BlackDex" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryVariant">@color/primary_dark</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<item name="colorSecondary">@color/primary_light</item>
<item name="colorSecondaryVariant">@color/primary</item>
<item name="colorOnSecondary">@color/white</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="actionMenuTextColor">@color/white</item>
</style>
<style name="ToolBarColorStyle" parent="ThemeOverlay.AppCompat.DayNight.ActionBar">
<item name="actionMenuTextColor">@color/white</item>
<item name="colorControlNormal">@color/white</item>
</style>
<style name="WelcomeTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@drawable/splash</item>
</style>
</resources>

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

@ -0,0 +1,17 @@
package top.niunaijun.blackdex
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

@ -1,11 +1,21 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
kotlin_version = '1.5.0'
}
repositories {
google()
mavenCentral()
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/central' }
jcenter { url 'https://maven.aliyun.com/repository/jcenter' }
jcenter { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
maven { url 'https://dl.bintray.com/umsdk/release' }
maven { url 'https://jitpack.io' }
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@ -14,9 +24,14 @@ buildscript {
allprojects {
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/central' }
jcenter { url 'https://maven.aliyun.com/repository/jcenter' }
jcenter { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
maven { url 'https://dl.bintray.com/umsdk/release' }
maven { url 'https://jitpack.io' }
}
}

@ -1,5 +1,5 @@
rootProject.name = "BlackDex"
include ':app'
include ':Bcore:black-hook'
include ':Bcore:black-fake'
include ':Bcore'
include ':app'

Loading…
Cancel
Save