diff --git a/README.md b/README.md index 674bc65..61cb757 100644 --- a/README.md +++ b/README.md @@ -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) 已被抄家,多的不想说了,请关注本项目或者其他未来项目吧。 diff --git a/app/build.gradle b/app/build.gradle index d4eceb0..8710ded 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' } \ No newline at end of file diff --git a/app/src/androidTest/java/top/niunaijun/blackdex/ExampleInstrumentedTest.java b/app/src/androidTest/java/top/niunaijun/blackdex/ExampleInstrumentedTest.java deleted file mode 100644 index a3fec20..0000000 --- a/app/src/androidTest/java/top/niunaijun/blackdex/ExampleInstrumentedTest.java +++ /dev/null @@ -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 Testing documentation - */ -@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()); - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/top/niunaijun/blackdex/ExampleInstrumentedTest.kt b/app/src/androidTest/java/top/niunaijun/blackdex/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..69060a5 --- /dev/null +++ b/app/src/androidTest/java/top/niunaijun/blackdex/ExampleInstrumentedTest.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 75000f6..ad12602 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,16 +1,21 @@ + xmlns:tools="http://schemas.android.com/tools" + package="top.niunaijun.blackdex"> + + + - + 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"> + diff --git a/app/src/main/java/top/niunaijun/blackdex/App.java b/app/src/main/java/top/niunaijun/blackdex/App.java deleted file mode 100644 index 7aa367f..0000000 --- a/app/src/main/java/top/niunaijun/blackdex/App.java +++ /dev/null @@ -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(); - } -} diff --git a/app/src/main/java/top/niunaijun/blackdex/App.kt b/app/src/main/java/top/niunaijun/blackdex/App.kt new file mode 100644 index 0000000..1ba0721 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/App.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/MainActivity.java b/app/src/main/java/top/niunaijun/blackdex/MainActivity.java deleted file mode 100644 index 2bcf1c3..0000000 --- a/app/src/main/java/top/niunaijun/blackdex/MainActivity.java +++ /dev/null @@ -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()); - } - }; -} diff --git a/app/src/main/java/top/niunaijun/blackdex/data/BlackDexConfiguration.kt b/app/src/main/java/top/niunaijun/blackdex/data/BlackDexConfiguration.kt new file mode 100644 index 0000000..62fba09 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/data/BlackDexConfiguration.kt @@ -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 + } + + +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/data/DexDumpRepository.kt b/app/src/main/java/top/niunaijun/blackdex/data/DexDumpRepository.kt new file mode 100644 index 0000000..c02956e --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/data/DexDumpRepository.kt @@ -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>) { + + val installedApplications: List = getPackageManager().getInstalledApplications(0) + val installedList = mutableListOf() + + 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) { + + 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){ + GlobalScope.launch { + val tempId = dumpTaskId + delay(10000) + + if(tempId == dumpTaskId){ + dexDumpLiveData.postValue(DumpInfo(DumpInfo.TIMEOUT)) + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/data/entity/AppInfo.kt b/app/src/main/java/top/niunaijun/blackdex/data/entity/AppInfo.kt new file mode 100644 index 0000000..5b5dea4 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/data/entity/AppInfo.kt @@ -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 +) diff --git a/app/src/main/java/top/niunaijun/blackdex/data/entity/DumpInfo.kt b/app/src/main/java/top/niunaijun/blackdex/data/entity/DumpInfo.kt new file mode 100644 index 0000000..47d1ced --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/data/entity/DumpInfo.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/util/FileUtil.kt b/app/src/main/java/top/niunaijun/blackdex/util/FileUtil.kt new file mode 100644 index 0000000..c78a117 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/util/FileUtil.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/util/InjectionUtil.kt b/app/src/main/java/top/niunaijun/blackdex/util/InjectionUtil.kt new file mode 100644 index 0000000..08556b5 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/util/InjectionUtil.kt @@ -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) + } + +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/util/LoadingUtil.kt b/app/src/main/java/top/niunaijun/blackdex/util/LoadingUtil.kt new file mode 100644 index 0000000..d6d91d3 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/util/LoadingUtil.kt @@ -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 + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/util/ToastEx.kt b/app/src/main/java/top/niunaijun/blackdex/util/ToastEx.kt new file mode 100644 index 0000000..3931cd0 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/util/ToastEx.kt @@ -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() +} diff --git a/app/src/main/java/top/niunaijun/blackdex/util/ViewBindingEx.kt b/app/src/main/java/top/niunaijun/blackdex/util/ViewBindingEx.kt new file mode 100644 index 0000000..ee4e835 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/util/ViewBindingEx.kt @@ -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 Activity.inflate(): Lazy = lazy { + inflateBinding(layoutInflater) +} + +inline fun Fragment.inflate(): Lazy = lazy { + inflateBinding(layoutInflater) +} + +inline fun Dialog.inflate(): Lazy = lazy { + inflateBinding(layoutInflater) +} + + +inline fun inflateBinding(layoutInflater: LayoutInflater): T { + val method = T::class.java.getMethod("inflate", LayoutInflater::class.java) + return method.invoke(null, layoutInflater) as T +} + +inline fun 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 +} diff --git a/app/src/main/java/top/niunaijun/blackdex/view/base/BaseActivity.kt b/app/src/main/java/top/niunaijun/blackdex/view/base/BaseActivity.kt new file mode 100644 index 0000000..be3359d --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/view/base/BaseActivity.kt @@ -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() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/view/base/BaseAdapter.kt b/app/src/main/java/top/niunaijun/blackdex/view/base/BaseAdapter.kt new file mode 100644 index 0000000..1056432 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/view/base/BaseAdapter.kt @@ -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 : RecyclerView.Adapter>() { + + var dataList: MutableList = 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 { + return ViewHolder(getViewBinding(parent)) + } + + + override fun getItemCount(): Int { + return dataList.size + } + + override fun onBindViewHolder(holder: ViewHolder, 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) { + this.dataList = arrayListOf().apply { + newDataList.forEach { + this.add(it) + } + } + notifyDataSetChanged() + } + + open fun addData(list: List) { + 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(val bindIng: T) : RecyclerView.ViewHolder(bindIng.root) + +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/view/base/BaseViewModel.kt b/app/src/main/java/top/niunaijun/blackdex/view/base/BaseViewModel.kt new file mode 100644 index 0000000..a5b7b8b --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/view/base/BaseViewModel.kt @@ -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() + } + +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/view/main/MainActivity.kt b/app/src/main/java/top/niunaijun/blackdex/view/main/MainActivity.kt new file mode 100644 index 0000000..748bad7 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/view/main/MainActivity.kt @@ -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 = 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) + } + +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/view/main/MainAdapter.kt b/app/src/main/java/top/niunaijun/blackdex/view/main/MainAdapter.kt new file mode 100644 index 0000000..0e89517 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/view/main/MainAdapter.kt @@ -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() { + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/view/main/MainFactory.kt b/app/src/main/java/top/niunaijun/blackdex/view/main/MainFactory.kt new file mode 100644 index 0000000..3c43d4b --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/view/main/MainFactory.kt @@ -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 create(modelClass: Class): T { + return MainViewModel(repo) as T + } +} \ No newline at end of file diff --git a/app/src/main/java/top/niunaijun/blackdex/view/main/MainViewModel.kt b/app/src/main/java/top/niunaijun/blackdex/view/main/MainViewModel.kt new file mode 100644 index 0000000..aca3139 --- /dev/null +++ b/app/src/main/java/top/niunaijun/blackdex/view/main/MainViewModel.kt @@ -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>() + + val mDexDumpLiveData = MutableLiveData() + + + fun getAppList() { + launchOnUI { + repo.getAppList(mAppListLiveData) + } + } + + fun startDexDump(source: String) { + launchOnUI { + repo.dumpDex(source, mDexDumpLiveData) + } + } + + fun dexDumpSuccess() { + launchOnUI { + repo.dumpSuccess() + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable-anydpi/ic_folder.xml b/app/src/main/res/drawable-anydpi/ic_folder.xml new file mode 100644 index 0000000..a39ab19 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_folder.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_search.xml b/app/src/main/res/drawable-anydpi/ic_search.xml new file mode 100644 index 0000000..44dcb4a --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_search.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-hdpi/ic_folder.png b/app/src/main/res/drawable-hdpi/ic_folder.png new file mode 100644 index 0000000..82a87a7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_folder.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_search.png b/app/src/main/res/drawable-hdpi/ic_search.png new file mode 100644 index 0000000..98c4737 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_search.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_folder.png b/app/src/main/res/drawable-mdpi/ic_folder.png new file mode 100644 index 0000000..e7e2306 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_folder.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_search.png b/app/src/main/res/drawable-mdpi/ic_search.png new file mode 100644 index 0000000..25e1bf8 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_search.png differ diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml index 2b068d1..912980c 100644 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -1,30 +1,30 @@ + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + android:endX="85.84757" + android:endY="92.4963" + android:startX="42.9492" + android:startY="49.59793" + android:type="linear"> + android:color="#44000000" + android:offset="0.0" /> + android:color="#00000000" + android:offset="1.0" /> + 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" /> \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/ic_folder.png b/app/src/main/res/drawable-xhdpi/ic_folder.png new file mode 100644 index 0000000..099e909 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_folder.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_search.png b/app/src/main/res/drawable-xhdpi/ic_search.png new file mode 100644 index 0000000..8b62340 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_search.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_folder.png b/app/src/main/res/drawable-xxhdpi/ic_folder.png new file mode 100644 index 0000000..943eb08 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_folder.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_search.png b/app/src/main/res/drawable-xxhdpi/ic_search.png new file mode 100644 index 0000000..f8a2af5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_search.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 07d5da9..6ae213e 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,170 +1,170 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/splash.xml b/app/src/main/res/drawable/splash.xml new file mode 100644 index 0000000..3dd706a --- /dev/null +++ b/app/src/main/res/drawable/splash.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 636b9c7..903e339 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,15 +1,55 @@ - -