| @ -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) | ||||
|     } | ||||
| } | ||||
| @ -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> | ||||
| After Width: | Height: | Size: 248 B | 
| After Width: | Height: | Size: 425 B | 
| After Width: | Height: | Size: 163 B | 
| After Width: | Height: | Size: 247 B | 
| @ -1,30 +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"> | ||||
|         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"> | ||||
|                     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" /> | ||||
|                         android:color="#44000000" | ||||
|                         android:offset="0.0" /> | ||||
|                 <item | ||||
|                     android:color="#00000000" | ||||
|                     android:offset="1.0" /> | ||||
|                         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" /> | ||||
|             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> | ||||
| After Width: | Height: | Size: 318 B | 
| After Width: | Height: | Size: 462 B | 
| After Width: | Height: | Size: 421 B | 
| After Width: | Height: | Size: 810 B | 
| @ -1,170 +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" /> | ||||
|         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,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"> | ||||
| 
 | ||||
|     <Button | ||||
|         android:id="@+id/btn_click" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:text="@string/app_name" /> | ||||
|         xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:orientation="vertical"> | ||||
| 
 | ||||
|     <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: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,5 +1,5 @@ | ||||
| rootProject.name = "BlackDex" | ||||
| include ':app' | ||||
| include ':Bcore:black-hook' | ||||
| include ':Bcore:black-fake' | ||||
| include ':Bcore' | ||||
| include ':app' | ||||
|  | ||||