@ -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" |
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
xmlns:aapt="http://schemas.android.com/aapt" |
xmlns:aapt="http://schemas.android.com/aapt" |
||||||
android:width="108dp" |
android:width="108dp" |
||||||
android:height="108dp" |
android:height="108dp" |
||||||
android:viewportWidth="108" |
android:viewportWidth="108" |
||||||
android:viewportHeight="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"> |
<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"> |
<aapt:attr name="android:fillColor"> |
||||||
<gradient |
<gradient |
||||||
android:endX="85.84757" |
android:endX="85.84757" |
||||||
android:endY="92.4963" |
android:endY="92.4963" |
||||||
android:startX="42.9492" |
android:startX="42.9492" |
||||||
android:startY="49.59793" |
android:startY="49.59793" |
||||||
android:type="linear"> |
android:type="linear"> |
||||||
<item |
<item |
||||||
android:color="#44000000" |
android:color="#44000000" |
||||||
android:offset="0.0" /> |
android:offset="0.0" /> |
||||||
<item |
<item |
||||||
android:color="#00000000" |
android:color="#00000000" |
||||||
android:offset="1.0" /> |
android:offset="1.0" /> |
||||||
</gradient> |
</gradient> |
||||||
</aapt:attr> |
</aapt:attr> |
||||||
</path> |
</path> |
||||||
<path |
<path |
||||||
android:fillColor="#FFFFFF" |
android:fillColor="#FFFFFF" |
||||||
android:fillType="nonZero" |
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: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:strokeWidth="1" |
||||||
android:strokeColor="#00000000" /> |
android:strokeColor="#00000000" /> |
||||||
</vector> |
</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"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
android:width="108dp" |
android:width="108dp" |
||||||
android:height="108dp" |
android:height="108dp" |
||||||
android:viewportWidth="108" |
android:viewportWidth="108" |
||||||
android:viewportHeight="108"> |
android:viewportHeight="108"> |
||||||
<path |
<path |
||||||
android:fillColor="#3DDC84" |
android:fillColor="#3DDC84" |
||||||
android:pathData="M0,0h108v108h-108z" /> |
android:pathData="M0,0h108v108h-108z" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M9,0L9,108" |
android:pathData="M9,0L9,108" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M19,0L19,108" |
android:pathData="M19,0L19,108" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M29,0L29,108" |
android:pathData="M29,0L29,108" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M39,0L39,108" |
android:pathData="M39,0L39,108" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M49,0L49,108" |
android:pathData="M49,0L49,108" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M59,0L59,108" |
android:pathData="M59,0L59,108" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M69,0L69,108" |
android:pathData="M69,0L69,108" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M79,0L79,108" |
android:pathData="M79,0L79,108" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M89,0L89,108" |
android:pathData="M89,0L89,108" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M99,0L99,108" |
android:pathData="M99,0L99,108" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M0,9L108,9" |
android:pathData="M0,9L108,9" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M0,19L108,19" |
android:pathData="M0,19L108,19" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M0,29L108,29" |
android:pathData="M0,29L108,29" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M0,39L108,39" |
android:pathData="M0,39L108,39" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M0,49L108,49" |
android:pathData="M0,49L108,49" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M0,59L108,59" |
android:pathData="M0,59L108,59" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M0,69L108,69" |
android:pathData="M0,69L108,69" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M0,79L108,79" |
android:pathData="M0,79L108,79" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M0,89L108,89" |
android:pathData="M0,89L108,89" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M0,99L108,99" |
android:pathData="M0,99L108,99" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M19,29L89,29" |
android:pathData="M19,29L89,29" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M19,39L89,39" |
android:pathData="M19,39L89,39" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M19,49L89,49" |
android:pathData="M19,49L89,49" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M19,59L89,59" |
android:pathData="M19,59L89,59" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M19,69L89,69" |
android:pathData="M19,69L89,69" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M19,79L89,79" |
android:pathData="M19,79L89,79" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M29,19L29,89" |
android:pathData="M29,19L29,89" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M39,19L39,89" |
android:pathData="M39,19L39,89" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M49,19L49,89" |
android:pathData="M49,19L49,89" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M59,19L59,89" |
android:pathData="M59,19L59,89" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M69,19L69,89" |
android:pathData="M69,19L69,89" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
<path |
<path |
||||||
android:fillColor="#00000000" |
android:fillColor="#00000000" |
||||||
android:pathData="M79,19L79,89" |
android:pathData="M79,19L79,89" |
||||||
android:strokeWidth="0.8" |
android:strokeWidth="0.8" |
||||||
android:strokeColor="#33FFFFFF" /> |
android:strokeColor="#33FFFFFF" /> |
||||||
</vector> |
</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"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
xmlns:tools="http://schemas.android.com/tools" |
android:layout_width="match_parent" |
||||||
android:layout_width="match_parent" |
android:layout_height="match_parent" |
||||||
android:layout_height="match_parent" |
android:orientation="vertical"> |
||||||
android:gravity="center" |
|
||||||
tools:context=".MainActivity"> |
<FrameLayout |
||||||
|
android:layout_width="match_parent" |
||||||
<Button |
android:layout_height="wrap_content"> |
||||||
android:id="@+id/btn_click" |
|
||||||
android:layout_width="wrap_content" |
<include |
||||||
android:layout_height="wrap_content" |
android:id="@+id/toolbar_layout" |
||||||
android:text="@string/app_name" /> |
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> |
</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"> |
<resources xmlns:tools="http://schemas.android.com/tools"> |
||||||
<!-- Base application theme. --> |
<!-- Base application theme. --> |
||||||
<style name="Theme.BlackDex" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> |
<style name="Theme.BlackBox" parent="Theme.MaterialComponents.DayNight.NoActionBar"> |
||||||
<!-- Primary brand color. --> |
<!-- Primary brand color. --> |
||||||
<item name="colorPrimary">@color/purple_200</item> |
<item name="colorPrimary">@color/primary</item> |
||||||
<item name="colorPrimaryVariant">@color/purple_700</item> |
<item name="colorPrimaryVariant">@color/primary_dark</item> |
||||||
<item name="colorOnPrimary">@color/black</item> |
<item name="colorOnPrimary">@color/white</item> |
||||||
<!-- Secondary brand color. --> |
<!-- Secondary brand color. --> |
||||||
<item name="colorSecondary">@color/teal_200</item> |
<item name="colorSecondary">@color/primary_light</item> |
||||||
<item name="colorSecondaryVariant">@color/teal_200</item> |
<item name="colorSecondaryVariant">@color/primary</item> |
||||||
<item name="colorOnSecondary">@color/black</item> |
<item name="colorOnSecondary">@color/white</item> |
||||||
<!-- Status bar color. --> |
<!-- Status bar color. --> |
||||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> |
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> |
||||||
<!-- Customize your theme here. --> |
<!-- Customize your theme here. --> |
||||||
|
|
||||||
|
<item name="actionMenuTextColor">@color/white</item> |
||||||
</style> |
</style> |
||||||
</resources> |
</resources> |
@ -1,10 +1,10 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||||
<resources> |
<resources> |
||||||
<color name="purple_200">#FFBB86FC</color> |
<color name="primary">#3F3F3F</color> |
||||||
<color name="purple_500">#FF6200EE</color> |
<color name="primary_dark">#3F3F3F</color> |
||||||
<color name="purple_700">#FF3700B3</color> |
<color name="primary_light">#3F3F3F</color> |
||||||
<color name="teal_200">#FF03DAC5</color> |
<color name="accent">#ffffff</color> |
||||||
<color name="teal_700">#FF018786</color> |
<color name="primary_text">#212121</color> |
||||||
<color name="black">#FF000000</color> |
<color name="secondary_text">#757575</color> |
||||||
<color name="white">#FFFFFFFF</color> |
<color name="white">#FFFFFFFF</color> |
||||||
</resources> |
</resources> |
@ -1,3 +1,8 @@ |
|||||||
<resources> |
<resources> |
||||||
|
|
||||||
<string name="app_name">BlackDex</string> |
<string name="app_name">BlackDex</string> |
||||||
|
<string name="filter">过滤</string> |
||||||
|
<string name="choose">选择</string> |
||||||
|
<string name="log">软件日志</string> |
||||||
|
<string name="open_source">开源地址</string> |
||||||
</resources> |
</resources> |
@ -1,16 +1,28 @@ |
|||||||
<resources xmlns:tools="http://schemas.android.com/tools"> |
<resources xmlns:tools="http://schemas.android.com/tools"> |
||||||
<!-- Base application theme. --> |
<!-- Base application theme. --> |
||||||
<style name="Theme.BlackDex" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> |
<style name="Theme.BlackDex" parent="Theme.MaterialComponents.DayNight.NoActionBar"> |
||||||
<!-- Primary brand color. --> |
<!-- Primary brand color. --> |
||||||
<item name="colorPrimary">@color/purple_500</item> |
<item name="colorPrimary">@color/primary</item> |
||||||
<item name="colorPrimaryVariant">@color/purple_700</item> |
<item name="colorPrimaryVariant">@color/primary_dark</item> |
||||||
<item name="colorOnPrimary">@color/white</item> |
<item name="colorOnPrimary">@color/white</item> |
||||||
<!-- Secondary brand color. --> |
<!-- Secondary brand color. --> |
||||||
<item name="colorSecondary">@color/teal_200</item> |
<item name="colorSecondary">@color/primary_light</item> |
||||||
<item name="colorSecondaryVariant">@color/teal_700</item> |
<item name="colorSecondaryVariant">@color/primary</item> |
||||||
<item name="colorOnSecondary">@color/black</item> |
<item name="colorOnSecondary">@color/white</item> |
||||||
<!-- Status bar color. --> |
<!-- Status bar color. --> |
||||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> |
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> |
||||||
<!-- Customize your theme here. --> |
<!-- Customize your theme here. --> |
||||||
|
|
||||||
|
<item name="actionMenuTextColor">@color/white</item> |
||||||
</style> |
</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> |
</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" |
rootProject.name = "BlackDex" |
||||||
include ':app' |
|
||||||
include ':Bcore:black-hook' |
include ':Bcore:black-hook' |
||||||
include ':Bcore:black-fake' |
include ':Bcore:black-fake' |
||||||
include ':Bcore' |
include ':Bcore' |
||||||
|
include ':app' |
||||||
|