diff --git a/app/build.gradle b/app/build.gradle index 9c64d29..5495239 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,14 +12,21 @@ android { signingConfigs { releaseConfig } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } compileSdkVersion 28 defaultConfig { applicationId "com.wrbug.developerhelper" minSdkVersion 21 targetSdkVersion 28 - versionCode 100000 - versionName "1.0.0.0" + versionCode 100040 + versionName "1.0.4" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + ndk { + abiFilters "armeabi", "armeabi-v7a", "arm64-v8a" + } } buildTypes { debug { @@ -36,6 +43,7 @@ android { dataBinding { enabled = true } + } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') @@ -48,20 +56,24 @@ dependencies { androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.0.0' - implementation 'com.tencent:mmkv:1.0.14' implementation 'com.google.android.material:material:1.1.0-alpha02' implementation 'com.github.yhaolpz:FloatWindow:1.0.9' - implementation 'com.jaredrummler:android-shell:1.0.0' implementation project(':basecommon') kapt "com.android.databinding:compiler:3.1.4" implementation 'com.elvishew:xlog:1.6.1' - implementation "org.jetbrains.anko:anko-commons:0.10.8" + implementation 'org.jetbrains.anko:anko-commons:0.10.8' //dagger2 implementation 'com.google.dagger:dagger:2.16' // https://mvnrepository.com/artifact/dom4j/dom4j - implementation group: 'dom4j', name: 'dom4j', version: '1.6.1' - + implementation 'dom4j:dom4j:1.6.1' + implementation 'com.evrencoskun.library:tableview:0.8.8' + implementation 'gdut.bsx:share2:0.9.3' kapt 'com.google.dagger:dagger-compiler:2.16' + implementation project(':commonutil') + implementation project(':mmkv') + implementation project(':xposedmodule') + implementation project(':commonwidget') + implementation 'de.blox:graphview:0.5.0' } kapt { generateStubs = true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 44283e2..25f34e2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,8 +3,12 @@ xmlns:tools="http://schemas.android.com/tools" package="com.wrbug.developerhelper"> + + + + @@ -51,7 +56,21 @@ android:name=".ui.activity.guide.GuideActivity" android:label="@string/title_activity_guide" android:theme="@style/AppTheme.NoActionBar" /> - + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/DeveloperApplication.kt b/app/src/main/java/com/wrbug/developerhelper/DeveloperApplication.kt index 12e6207..b76ab66 100644 --- a/app/src/main/java/com/wrbug/developerhelper/DeveloperApplication.kt +++ b/app/src/main/java/com/wrbug/developerhelper/DeveloperApplication.kt @@ -2,14 +2,17 @@ package com.wrbug.developerhelper import android.os.Handler import android.os.Looper +import com.elvishew.xlog.LogConfiguration import com.elvishew.xlog.LogLevel import com.elvishew.xlog.XLog -import com.tencent.mmkv.MMKV +import com.elvishew.xlog.internal.DefaultsFactory import com.wrbug.developerhelper.basecommon.BaseApp +import com.wrbug.developerhelper.commonutil.CommonUtils +import com.wrbug.developerhelper.mmkv.manager.MMKVManager import java.io.File import java.io.FileOutputStream import kotlin.concurrent.thread -import com.wrbug.developerhelper.ui.widget.flexibletoast.FlexibleToast +import com.wrbug.developerhelper.commonwidget.flexibletoast.FlexibleToast class DeveloperApplication : BaseApp() { @@ -32,12 +35,20 @@ class DeveloperApplication : BaseApp() { override fun onCreate() { super.onCreate() + registerModule() instance = this - XLog.init(LogLevel.ALL) - MMKV.initialize(this) + XLog.init( + LogConfiguration.Builder().logLevel(LogLevel.ALL).tag("developerHelper.print-->").build(), + DefaultsFactory.createPrinter() + ) releaseAssetsFile() } + private fun registerModule() { + MMKVManager.register(this) + CommonUtils.register(this) + } + private fun releaseAssetsFile() { thread { val inputStream = BaseApp.instance.assets.open("zip.dex") diff --git a/app/src/main/java/com/wrbug/developerhelper/constant/ReceiverConstant.kt b/app/src/main/java/com/wrbug/developerhelper/constant/ReceiverConstant.kt index 2522dfc..77f8af7 100644 --- a/app/src/main/java/com/wrbug/developerhelper/constant/ReceiverConstant.kt +++ b/app/src/main/java/com/wrbug/developerhelper/constant/ReceiverConstant.kt @@ -3,6 +3,7 @@ package com.wrbug.developerhelper.constant object ReceiverConstant { const val ACTION_HIERARCHY_VIEW = "ACTION_HIERARCHY_VIEW" const val ACTION_SET_FLOAT_BUTTON_VISIBLE = "ACTION_SET_FLOAT_BUTTON_VISIBLE" - - const val ACTION_ACCESSIBILITY_SERVICE_STATUS_CHANGED="ACTION_ACCESSIBILITY_SERVICE_STATUS_CHANGED" + const val ACTION_ADB_WIFI_CLICKED = "ACTION_ADB_WIFI_CLICKED" + const val ACTION_FINISH_HIERACHY_Activity = "ACTION_FINISH_HIERACHY_Activity" + const val ACTION_ACCESSIBILITY_SERVICE_STATUS_CHANGED = "ACTION_ACCESSIBILITY_SERVICE_STATUS_CHANGED" } \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/model/entity/DatabaseTableInfo.kt b/app/src/main/java/com/wrbug/developerhelper/model/entity/DatabaseTableInfo.kt new file mode 100644 index 0000000..f502775 --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/model/entity/DatabaseTableInfo.kt @@ -0,0 +1,12 @@ +package com.wrbug.developerhelper.model.entity + +class DatabaseTableInfo { + var name = "" + var keys = arrayOf() + var count = 0 + var rows: List
    > = arrayListOf() + set(value) { + field = value + count = value.size + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/model/entity/HierarchyNode.kt b/app/src/main/java/com/wrbug/developerhelper/model/entity/HierarchyNode.kt index bca7198..8f11257 100644 --- a/app/src/main/java/com/wrbug/developerhelper/model/entity/HierarchyNode.kt +++ b/app/src/main/java/com/wrbug/developerhelper/model/entity/HierarchyNode.kt @@ -4,7 +4,7 @@ import android.graphics.Rect import android.os.Parcel import android.os.Parcelable import com.google.gson.reflect.TypeToken -import com.wrbug.developerhelper.util.JsonHelper +import com.wrbug.developerhelper.commonutil.JsonHelper import java.io.Serializable @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") diff --git a/app/src/main/java/com/wrbug/developerhelper/model/entity/SharedPreferenceItemInfo.kt b/app/src/main/java/com/wrbug/developerhelper/model/entity/SharedPreferenceItemInfo.kt index 6ee6694..6bf2927 100644 --- a/app/src/main/java/com/wrbug/developerhelper/model/entity/SharedPreferenceItemInfo.kt +++ b/app/src/main/java/com/wrbug/developerhelper/model/entity/SharedPreferenceItemInfo.kt @@ -1,6 +1,6 @@ package com.wrbug.developerhelper.model.entity -import com.wrbug.developerhelper.util.* +import com.wrbug.developerhelper.commonutil.* class SharedPreferenceItemInfo { var key: String = "" diff --git a/app/src/main/java/com/wrbug/developerhelper/model/entity/VersionInfo.kt b/app/src/main/java/com/wrbug/developerhelper/model/entity/VersionInfo.kt new file mode 100644 index 0000000..4990a36 --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/model/entity/VersionInfo.kt @@ -0,0 +1,9 @@ +package com.wrbug.developerhelper.model.entity + +class VersionInfo { + var size = "" + var versionName = "" + var feature = "" + var downloadUrl = "" + var updateDate = "" +} diff --git a/app/src/main/java/com/wrbug/developerhelper/model/mmkv/ConfigKv.kt b/app/src/main/java/com/wrbug/developerhelper/model/mmkv/ConfigKv.kt deleted file mode 100644 index 0df5954..0000000 --- a/app/src/main/java/com/wrbug/developerhelper/model/mmkv/ConfigKv.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.wrbug.developerhelper.model.mmkv - -interface ConfigKv { - fun setOpenRoot(openRoot: Boolean) - fun getOpenRoot(): Boolean - fun setOpenXposed(openXposed: Boolean) - fun getOpenXposed(): Boolean -} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/service/AccessibilityManager.kt b/app/src/main/java/com/wrbug/developerhelper/service/AccessibilityManager.kt index f9cfc71..418707e 100644 --- a/app/src/main/java/com/wrbug/developerhelper/service/AccessibilityManager.kt +++ b/app/src/main/java/com/wrbug/developerhelper/service/AccessibilityManager.kt @@ -5,8 +5,8 @@ import android.content.Intent import android.provider.Settings import com.wrbug.developerhelper.R import com.wrbug.developerhelper.basecommon.showToast -import com.wrbug.developerhelper.shell.Callback -import com.wrbug.developerhelper.shell.ShellManager +import com.wrbug.developerhelper.commonutil.shell.Callback +import com.wrbug.developerhelper.commonutil.shell.ShellManager object AccessibilityManager { fun startService(context: Context?, callback: Callback? = null) { @@ -15,18 +15,23 @@ object AccessibilityManager { override fun onSuccess(data: Boolean) { if (!data) { showToast(getString(R.string.please_open_accessbility_service)) - val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) + startAccessibilitySetting(context) } callback?.onSuccess(data) } override fun onFailed(msg: String) { callback?.onFailed(msg) + startAccessibilitySetting(context) } }) } } + + fun startAccessibilitySetting(context: Context) { + val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + } } \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/service/DeveloperHelperAccessibilityService.kt b/app/src/main/java/com/wrbug/developerhelper/service/DeveloperHelperAccessibilityService.kt index 08c450a..e9cdee1 100644 --- a/app/src/main/java/com/wrbug/developerhelper/service/DeveloperHelperAccessibilityService.kt +++ b/app/src/main/java/com/wrbug/developerhelper/service/DeveloperHelperAccessibilityService.kt @@ -13,15 +13,15 @@ import android.view.accessibility.AccessibilityNodeInfo import com.wrbug.developerhelper.R import com.wrbug.developerhelper.basecommon.BaseApp import com.wrbug.developerhelper.basecommon.showToast +import com.wrbug.developerhelper.commonutil.AppInfoManager +import com.wrbug.developerhelper.commonutil.entity.ApkInfo +import com.wrbug.developerhelper.commonutil.entity.TopActivityInfo import com.wrbug.developerhelper.constant.ReceiverConstant -import com.wrbug.developerhelper.model.entity.ApkInfo import com.wrbug.developerhelper.model.entity.HierarchyNode -import com.wrbug.developerhelper.model.entity.TopActivityInfo -import com.wrbug.developerhelper.shell.Callback -import com.wrbug.developerhelper.shell.ShellManager +import com.wrbug.developerhelper.commonutil.shell.Callback +import com.wrbug.developerhelper.commonutil.shell.ShellManager import com.wrbug.developerhelper.ui.activity.hierachy.HierarchyActivity -import com.wrbug.developerhelper.util.AppInfoManager -import com.wrbug.developerhelper.util.UiUtils +import com.wrbug.developerhelper.commonutil.UiUtils class DeveloperHelperAccessibilityService : AccessibilityService() { diff --git a/app/src/main/java/com/wrbug/developerhelper/service/FloatWindowService.kt b/app/src/main/java/com/wrbug/developerhelper/service/FloatWindowService.kt index a6d70d5..ba11265 100644 --- a/app/src/main/java/com/wrbug/developerhelper/service/FloatWindowService.kt +++ b/app/src/main/java/com/wrbug/developerhelper/service/FloatWindowService.kt @@ -8,11 +8,13 @@ import android.content.IntentFilter import android.os.Build import android.os.IBinder import android.view.LayoutInflater -import android.widget.Toast +import android.widget.RemoteViews import androidx.core.app.NotificationCompat import com.wrbug.developerhelper.R +import com.wrbug.developerhelper.basecommon.showToast import com.wrbug.developerhelper.constant.ReceiverConstant -import com.wrbug.developerhelper.shell.Callback +import com.wrbug.developerhelper.commonutil.shell.Callback +import com.wrbug.developerhelper.commonutil.shell.ShellManager import com.wrbug.developerhelper.ui.activity.main.MainActivity import com.yhao.floatwindow.FloatWindow import com.yhao.floatwindow.Screen @@ -22,6 +24,7 @@ class FloatWindowService : Service() { companion object { const val FLOAT_BUTTON = "floatButton" + private const val CHANNEL_ID = "DEMON" fun start(context: Context) { context.startService(Intent(context, FloatWindowService::class.java)) } @@ -29,9 +32,25 @@ class FloatWindowService : Service() { fun stop(context: Context) { context.stopService(Intent(context, FloatWindowService::class.java)) } - } + fun setFloatButtonVisible(context: Context, visible: Boolean) { + val intent = Intent(ReceiverConstant.ACTION_SET_FLOAT_BUTTON_VISIBLE) + intent.putExtra("visible", visible) + context.sendBroadcast(intent) + } + } + private val floatCustomView: RemoteViews by lazy { + RemoteViews(packageName, R.layout.view_float_custom).apply { + setOnClickPendingIntent( + R.id.adbWifiContainer, PendingIntent.getBroadcast( + applicationContext, 0, + Intent(ReceiverConstant.ACTION_ADB_WIFI_CLICKED), PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + } + } + private lateinit var notification: Notification private val receiver = Receiver() override fun onCreate() { super.onCreate() @@ -71,7 +90,7 @@ class FloatWindowService : Service() { val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) { val channel = - NotificationChannel("channel1", getString(R.string.demon_process), NotificationManager.IMPORTANCE_LOW) + NotificationChannel(CHANNEL_ID, getString(R.string.demon_process), NotificationManager.IMPORTANCE_LOW) channel.enableLights(true) channel.setShowBadge(true) notificationManager.createNotificationChannel(channel) @@ -79,19 +98,33 @@ class FloatWindowService : Service() { val intent = Intent(this, MainActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT) - val builder = NotificationCompat.Builder(this, "channel1") + val builder = NotificationCompat.Builder(this, CHANNEL_ID) .setAutoCancel(false) .setContentIntent(pendingIntent) .setContentTitle(getString(R.string.app_name)) .setContentText(getString(R.string.demon_process_content)) .setSmallIcon(R.drawable.ic_launcher_notify) .setVibrate(null) - val notification = builder.build() + notification = builder.build() notification.flags = Notification.FLAG_ONGOING_EVENT or Notification.FLAG_NO_CLEAR or Notification.FLAG_FOREGROUND_SERVICE + updateNotification() + } + + private fun updateNotification() { startForeground(0x10000, notification) } + private fun updateNotificationContent(text: String) { + floatCustomView.setTextViewText(R.id.contentTv, text) + updateNotification() + } + + private fun updateNotificationWifi(id: Int) { + floatCustomView.setImageViewResource(R.id.adbWifiIv, id) + updateNotification() + } + private fun showFloatButton() { FloatWindow.get(FLOAT_BUTTON).show() } @@ -102,6 +135,7 @@ class FloatWindowService : Service() { private fun initReceiver() { val filter = IntentFilter(ReceiverConstant.ACTION_SET_FLOAT_BUTTON_VISIBLE) + filter.addAction(ReceiverConstant.ACTION_ADB_WIFI_CLICKED) registerReceiver(receiver, filter) } @@ -137,6 +171,17 @@ class FloatWindowService : Service() { hideFloatButton() } } + ReceiverConstant.ACTION_ADB_WIFI_CLICKED -> { + updateNotificationContent("正在开启adb wifi") + val success = ShellManager.openAdbWifi() + if (success) { + updateNotificationContent("adb wifi 已开启") + updateNotificationWifi(R.drawable.ic_wifi_primary) + } else { + updateNotificationContent("adb wifi 开启失败") + updateNotificationWifi(R.drawable.ic_wifi_gray) + } + } } } diff --git a/app/src/main/java/com/wrbug/developerhelper/shell/Callback.kt b/app/src/main/java/com/wrbug/developerhelper/shell/Callback.kt deleted file mode 100644 index 7a77d44..0000000 --- a/app/src/main/java/com/wrbug/developerhelper/shell/Callback.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.wrbug.developerhelper.shell - -interface Callback { - fun onSuccess(data: T) - fun onFailed(msg: String) {} -} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/activity/databaseedit/DatabaseEditActivity.kt b/app/src/main/java/com/wrbug/developerhelper/ui/activity/databaseedit/DatabaseEditActivity.kt new file mode 100644 index 0000000..cdfe075 --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/activity/databaseedit/DatabaseEditActivity.kt @@ -0,0 +1,178 @@ +package com.wrbug.developerhelper.ui.activity.databaseedit + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.evrencoskun.tableview.listener.ITableViewListener +import com.wrbug.developerhelper.R +import com.wrbug.developerhelper.basecommon.BaseActivity +import com.wrbug.developerhelper.basecommon.setupActionBar +import com.wrbug.developerhelper.basecommon.showToast +import com.wrbug.developerhelper.model.entity.DatabaseTableInfo +import com.wrbug.developerhelper.commonutil.shell.ShellManager +import com.wrbug.developerhelper.util.DatabaseUtils +import com.wrbug.developerhelper.commonutil.dp2px +import kotlinx.android.synthetic.main.activity_database_edit.* +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread +import java.io.File +import java.util.* +import kotlin.collections.ArrayList + +class DatabaseEditActivity : BaseActivity() { + private var filePath: String? = "" + private lateinit var dbPath: File + private val tableNames = ArrayList() + private var dbMap: Map = TreeMap() + private val adapter = DatabaseTableAdapter(this) + private var selectedIndex = 0 + private val dstDir: File by lazy { + val file = File(externalCacheDir, "db") + if (file.exists()) { + file.mkdir() + } + file + } + + companion object { + fun start(context: Context, filePath: String) { + val intent = Intent(context, DatabaseEditActivity::class.java) + intent.putExtra("filePath", filePath) + context.startActivity(intent) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_database_edit) + intent?.run { + filePath = getStringExtra("filePath") + } + if (filePath.isNullOrEmpty()) { + showToast(getString(R.string.get_database_failed)) + finish() + return + } + dbPath = File(filePath) + setupActionBar(R.id.toolbar) { + title = dbPath.name + } + initTableView() + readDatabase() + } + + private fun initTableView() { + tableView.adapter = adapter + tableView.tableViewListener = object : ITableViewListener { + override fun onCellLongPressed(p0: RecyclerView.ViewHolder, p1: Int, p2: Int) { + + } + + override fun onColumnHeaderLongPressed(p0: RecyclerView.ViewHolder, p1: Int) { + } + + override fun onRowHeaderClicked(p0: RecyclerView.ViewHolder, p1: Int) { + } + + override fun onColumnHeaderClicked(p0: RecyclerView.ViewHolder, p1: Int) { + } + + override fun onCellClicked(p0: RecyclerView.ViewHolder, p1: Int, p2: Int) { + } + + override fun onRowHeaderLongPressed(p0: RecyclerView.ViewHolder, p1: Int) { + } + + } + } + + private fun readDatabase() { + doAsync { + if (dstDir.exists().not()) { + dstDir.mkdir() + } + val success = ShellManager.cpFile(dbPath.absolutePath, "${dstDir.absolutePath}/${dbPath.name}") + if (!success) { + uiThread { + showToast(getString(R.string.get_database_failed)) + finish() + } + return@doAsync + } + val file = File(dstDir, dbPath.name) + dbMap = DatabaseUtils.getDatabase(file.absolutePath) + file.delete() + tableNames.addAll(dbMap.keys) + uiThread { + setTableContainer() + } + selectTable(0) + } + } + + + private fun setTableContainer() { + tableNameContainer.removeAllViews() + val layoutParams = + LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) + val dp10 = dp2px(10F) + val dp20 = dp2px(20F) + tableNames.forEachIndexed { index, name -> + val tv = TextView(this) + tv.text = name + tv.tag = index + if (index == 0) { + tv.setTextColor(resources.getColor(R.color.colorPrimary)) + } else { + tv.setTextColor(resources.getColor(R.color.text_color_666666)) + } + tv.setPadding(dp10, dp10, dp10, dp20) + tv.setOnClickListener { + selectTable(it.tag as Int) + } + tableNameContainer.addView(tv, layoutParams) + } + } + + private fun selectTable(position: Int) { + doAsync { + if (tableNames.size <= position) { + return@doAsync + } + val tableName = tableNames[position] + val databaseTableInfo = dbMap[tableName] + databaseTableInfo?.run { + val keyList = arrayListOf(*keys) + val rowHeaders = ArrayList() + val cell = ArrayList>() + rows.forEach { + val list = ArrayList() + rowHeaders.add(rowHeaders.size + 1) + keys.forEach { key -> + list.add(it[key]) + } + cell.add(list) + } + uiThread { + setHasData(!cell.isEmpty()) + adapter.setAllItems(keyList, rowHeaders, cell) + var tv = tableNameContainer.getChildAt(selectedIndex) as TextView + tv.setTextColor(resources.getColor(R.color.text_color_666666)) + tv = tableNameContainer.getChildAt(position) as TextView + tv.setTextColor(resources.getColor(R.color.colorPrimary)) + selectedIndex = position + } + } + } + } + + private fun setHasData(hasData: Boolean) { + noDataTv.visibility = if (hasData) View.GONE else View.VISIBLE + tableView.visibility = if (hasData) View.VISIBLE else View.GONE + } +} diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/activity/databaseedit/DatabaseTableAdapter.kt b/app/src/main/java/com/wrbug/developerhelper/ui/activity/databaseedit/DatabaseTableAdapter.kt new file mode 100644 index 0000000..9ee8514 --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/activity/databaseedit/DatabaseTableAdapter.kt @@ -0,0 +1,103 @@ +package com.wrbug.developerhelper.ui.activity.databaseedit + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import com.evrencoskun.tableview.adapter.AbstractTableAdapter +import com.evrencoskun.tableview.adapter.recyclerview.holder.AbstractViewHolder +import android.widget.TextView +import com.wrbug.developerhelper.R +import android.view.LayoutInflater +import android.widget.LinearLayout + + +class DatabaseTableAdapter(val context: Context) : AbstractTableAdapter(context) { + override fun onCreateColumnHeaderViewHolder(parent: ViewGroup?, viewType: Int): AbstractViewHolder { + val layout = LayoutInflater.from(context).inflate( + R.layout + .table_view_column_header_layout, parent, false + ) + return ColumnHeaderViewHolder(layout) + } + + override fun onBindColumnHeaderViewHolder( + holder: AbstractViewHolder?, + columnHeaderItemModel: Any?, + columnPosition: Int + ) { + val viewHolder = holder as ColumnHeaderViewHolder? + viewHolder?.run { + cellText?.text = columnHeaderItemModel?.toString() + } + } + + + override fun onCreateRowHeaderViewHolder(parent: ViewGroup?, viewType: Int): AbstractViewHolder { + val layout = LayoutInflater.from(context).inflate( + R.layout.table_view_row_header_layout, parent, false + ) + return RowHeaderViewHolder(layout) + } + + override fun onBindRowHeaderViewHolder(holder: AbstractViewHolder?, rowHeaderItemModel: Any?, rowPosition: Int) { + val viewHolder = holder as RowHeaderViewHolder? + viewHolder?.run { + cellText?.text = rowHeaderItemModel?.toString() + } + } + + + override fun onCreateCellViewHolder(parent: ViewGroup?, viewType: Int): AbstractViewHolder { + val layout = LayoutInflater.from(context).inflate( + R.layout.table_view_cell_layout, + parent, false + ) + return CellViewHolder(layout) + } + + override fun onCreateCornerView(): View { + return LayoutInflater.from(context).inflate(R.layout.table_view_corner_layout, null) + } + + override fun onBindCellViewHolder( + holder: AbstractViewHolder?, + cellItemModel: Any?, + columnPosition: Int, + rowPosition: Int + ) { + val viewHolder = holder as CellViewHolder? + viewHolder?.run { + cellText?.text = cellItemModel?.toString() ?: "NULL" + itemView.layoutParams.width = LinearLayout.LayoutParams.WRAP_CONTENT + cellText?.requestLayout() + } + } + + override fun getColumnHeaderItemViewType(position: Int): Int { + return 0 + } + + override fun getRowHeaderItemViewType(position: Int): Int { + return 0 + } + + override fun getCellItemViewType(position: Int): Int { + return 0 + } + + internal inner class ColumnHeaderViewHolder(itemView: View) : AbstractViewHolder(itemView) { + val cellText: TextView? = itemView.findViewById(R.id.column_header_textView) + } + + + internal inner class RowHeaderViewHolder(itemView: View) : AbstractViewHolder(itemView) { + + val cellText: TextView? = itemView.findViewById(R.id.row_header_textview) + } + + internal inner class CellViewHolder(itemView: View) : AbstractViewHolder(itemView) { + + val cellText: TextView? = itemView.findViewById(R.id.cell_data) + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/activity/hierachy/AppInfoDialog.kt b/app/src/main/java/com/wrbug/developerhelper/ui/activity/hierachy/AppInfoDialog.kt index 72ce78b..4d4e06e 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/activity/hierachy/AppInfoDialog.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/activity/hierachy/AppInfoDialog.kt @@ -8,17 +8,16 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.DialogFragment import com.wrbug.developerhelper.R -import com.wrbug.developerhelper.model.entity.ApkInfo -import com.wrbug.developerhelper.model.entity.TopActivityInfo -import com.wrbug.developerhelper.ui.widget.layoutinfoview.infopage.InfoAdapter -import com.wrbug.developerhelper.util.UiUtils -import com.wrbug.developerhelper.util.dp2px +import com.wrbug.developerhelper.commonutil.entity.ApkInfo +import com.wrbug.developerhelper.commonutil.entity.TopActivityInfo +import com.wrbug.developerhelper.commonutil.UiUtils +import com.wrbug.developerhelper.commonutil.dp2px import kotlinx.android.synthetic.main.dialog_apk_info.* class AppInfoDialog : DialogFragment() { - var apkInfo: ApkInfo? = null - var topActivity: TopActivityInfo? = null - var listener: AppInfoDialogEventListener? = null + private var apkInfo: ApkInfo? = null + private var topActivity: TopActivityInfo? = null + private var listener: AppInfoDialogEventListener? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStyle(DialogFragment.STYLE_NORMAL, R.style.FullScreenDialog) diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/activity/hierachy/AppInfoPagerAdapter.kt b/app/src/main/java/com/wrbug/developerhelper/ui/activity/hierachy/AppInfoPagerAdapter.kt index eb98061..a118cb8 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/activity/hierachy/AppInfoPagerAdapter.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/activity/hierachy/AppInfoPagerAdapter.kt @@ -7,15 +7,16 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewpager.widget.PagerAdapter import com.wrbug.developerhelper.R -import com.wrbug.developerhelper.model.entity.ApkInfo -import com.wrbug.developerhelper.model.entity.TopActivityInfo -import com.wrbug.developerhelper.shell.ShellManager +import com.wrbug.developerhelper.commonutil.entity.ApkInfo +import com.wrbug.developerhelper.commonutil.entity.TopActivityInfo +import com.wrbug.developerhelper.commonutil.shell.ShellManager import com.wrbug.developerhelper.ui.decoration.SpaceItemDecoration import com.wrbug.developerhelper.ui.widget.appdatainfoview.AppDataInfoView +import com.wrbug.developerhelper.ui.widget.appsettingview.AppSettingView import com.wrbug.developerhelper.ui.widget.layoutinfoview.infopage.InfoAdapter import com.wrbug.developerhelper.ui.widget.layoutinfoview.infopage.ItemInfo import com.wrbug.developerhelper.util.EnforceUtils -import com.wrbug.developerhelper.util.UiUtils +import com.wrbug.developerhelper.commonutil.UiUtils import com.wrbug.developerhelper.util.format import com.wrbug.developerhelper.util.getString import org.jetbrains.anko.doAsync @@ -34,10 +35,19 @@ class AppInfoPagerAdapter( private val adapter = InfoAdapter(context) private val enforceItem = ItemInfo(context.getString(R.string.enforce_type), context.getString(R.string.analyzing)) var listener: AppInfoDialogEventListener? = null + private val itemInfos = ArrayList() init { initAppInfoTab() initAppDataInfoTab() + initAppSettingTab() + } + + private fun initAppSettingTab() { + tabList.add(context.getString(R.string.app_setting)) + val view = AppSettingView(context) + view.apkInfo = apkInfo + viewList.add(view) } private fun initAppDataInfoTab() { @@ -58,7 +68,6 @@ class AppInfoPagerAdapter( itemDecoration.setFirstTopPadding(UiUtils.dp2px(context, 10F)) rv.addItemDecoration(itemDecoration) apkInfo?.let { it -> - val itemInfos = ArrayList() val item = ItemInfo(getString(R.string.page_analyze), getString(R.string.click_to_analyze)) item.setOnClickListener(View.OnClickListener { listener?.showHierachyView() @@ -105,11 +114,15 @@ class AppInfoPagerAdapter( } } + private fun setEnforceType(type: EnforceUtils.EnforceType) { + enforceItem.content = type.type + } + private fun getEnforce(packageName: String) { doAsync { val type = EnforceUtils.getEnforceType(packageName) - enforceItem.content = type.type uiThread { + setEnforceType(type) adapter.notifyItemChanged(enforceItem) } diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/activity/hierachy/HierarchyActivity.kt b/app/src/main/java/com/wrbug/developerhelper/ui/activity/hierachy/HierarchyActivity.kt index 719b907..eed2cf4 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/activity/hierachy/HierarchyActivity.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/activity/hierachy/HierarchyActivity.kt @@ -1,21 +1,29 @@ package com.wrbug.developerhelper.ui.activity.hierachy +import android.app.Activity +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.os.Bundle import android.view.View import com.google.gson.reflect.TypeToken import com.wrbug.developerhelper.R import com.wrbug.developerhelper.basecommon.BaseActivity import com.wrbug.developerhelper.constant.ReceiverConstant -import com.wrbug.developerhelper.model.entity.ApkInfo +import com.wrbug.developerhelper.commonutil.entity.ApkInfo import com.wrbug.developerhelper.model.entity.HierarchyNode -import com.wrbug.developerhelper.model.entity.TopActivityInfo +import com.wrbug.developerhelper.commonutil.entity.TopActivityInfo import com.wrbug.developerhelper.ui.widget.hierarchyView.HierarchyView -import com.wrbug.developerhelper.util.JsonHelper +import com.wrbug.developerhelper.commonutil.JsonHelper +import com.wrbug.developerhelper.constant.ReceiverConstant.ACTION_FINISH_HIERACHY_Activity +import com.wrbug.developerhelper.service.FloatWindowService +import com.wrbug.developerhelper.ui.widget.layoutinfoview.LayoutInfoView +import com.wrbug.developerhelper.ui.widget.layoutinfoview.OnNodeChangedListener import kotlinx.android.synthetic.main.activity_hierarchy.* +import java.lang.ref.WeakReference -class HierarchyActivity : BaseActivity(), AppInfoDialogEventListener { +class HierarchyActivity : BaseActivity(), AppInfoDialogEventListener, OnNodeChangedListener { private var apkInfo: ApkInfo? = null @@ -40,6 +48,23 @@ class HierarchyActivity : BaseActivity(), AppInfoDialogEventListener { intent.putExtras(bundle) context?.startActivity(intent) } + + private var receiver = object : BroadcastReceiver() { + private var reference: WeakReference? = null + fun setActivity(activity: Activity) { + reference = WeakReference(activity) + } + + override fun onReceive(context: Context?, intent: Intent?) { + when (intent?.action) { + ReceiverConstant.ACTION_FINISH_HIERACHY_Activity -> { + reference?.get()?.finish() + } + + } + } + + } } override fun onCreate(savedInstanceState: Bundle?) { @@ -52,9 +77,11 @@ class HierarchyActivity : BaseActivity(), AppInfoDialogEventListener { val json = getStringExtra("nodeMap") nodeMap = JsonHelper.fromJson(json, object : TypeToken>() {}.type) } - + val filter = IntentFilter(ACTION_FINISH_HIERACHY_Activity) + receiver.setActivity(this) + registerReceiver(receiver, filter) showAppInfoDialog() - setFloatButtonVisible(false) + FloatWindowService.setFloatButtonVisible(this, false) } private fun showAppInfoDialog() { @@ -73,13 +100,25 @@ class HierarchyActivity : BaseActivity(), AppInfoDialogEventListener { override fun onClick(node: HierarchyNode, parentNode: HierarchyNode?) { hierarchyDetailView.visibility = View.VISIBLE hierarchyDetailView.setNode(node, parentNode) + val layoutInfoView = LayoutInfoView(context, nodeList, node) + layoutInfoView.setOnNodeChangedListener(this@HierarchyActivity) + layoutInfoView.show() } + override fun onSelectedNodeChanged(node: HierarchyNode, parentNode: HierarchyNode?) { + hierarchyDetailView.visibility = View.VISIBLE + hierarchyDetailView.setNode(node, parentNode) + } }) } + override fun onChanged(node: HierarchyNode, parentNode: HierarchyNode?) { + hierarchyDetailView.setNode(node, parentNode) + } + override fun onDestroy() { - setFloatButtonVisible(true) + FloatWindowService.setFloatButtonVisible(this, true) + unregisterReceiver(receiver) super.onDestroy() } @@ -98,9 +137,4 @@ class HierarchyActivity : BaseActivity(), AppInfoDialogEventListener { } - private fun setFloatButtonVisible(visible: Boolean) { - val intent = Intent(ReceiverConstant.ACTION_SET_FLOAT_BUTTON_VISIBLE) - intent.putExtra("visible", visible) - sendBroadcast(intent) - } } diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/activity/main/MainActivity.kt b/app/src/main/java/com/wrbug/developerhelper/ui/activity/main/MainActivity.kt index b9a5083..41f1901 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/activity/main/MainActivity.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/activity/main/MainActivity.kt @@ -9,20 +9,26 @@ import android.view.MenuItem import android.widget.CompoundButton import androidx.appcompat.app.AlertDialog import androidx.databinding.DataBindingUtil -import com.wrbug.developerhelper.DeveloperApplication +import com.wrbug.developerhelper.BuildConfig import com.wrbug.developerhelper.R import com.wrbug.developerhelper.basecommon.BaseVMActivity import com.wrbug.developerhelper.basecommon.obtainViewModel import com.wrbug.developerhelper.basecommon.setupActionBar +import com.wrbug.developerhelper.basecommon.showToast +import com.wrbug.developerhelper.commonutil.ClipboardUtils +import com.wrbug.developerhelper.commonutil.shell.Callback import com.wrbug.developerhelper.constant.ReceiverConstant import com.wrbug.developerhelper.databinding.ActivityMainBinding import com.wrbug.developerhelper.service.AccessibilityManager import com.wrbug.developerhelper.service.FloatWindowService -import com.wrbug.developerhelper.shell.ShellManager +import com.wrbug.developerhelper.commonutil.shell.ShellManager import com.wrbug.developerhelper.ui.activity.main.viewmodel.MainViewModel -import com.wrbug.developerhelper.ui.activity.sharedpreferencesedit.SharedPreferenceEditActivity -import com.wrbug.developerhelper.util.ClipboardUtils +import com.wrbug.developerhelper.ui.widget.settingitemview.SettingItemView import com.wrbug.developerhelper.util.DeviceUtils +import com.wrbug.developerhelper.commonutil.toInt +import com.wrbug.developerhelper.model.entity.VersionInfo +import com.wrbug.developerhelper.ui.activity.xposed.xposedsetting.XposedSettingActivity +import com.wrbug.developerhelper.util.UpdateUtils import kotlinx.android.synthetic.main.activity_main.* @@ -30,14 +36,16 @@ class MainActivity : BaseVMActivity() { lateinit var binding: ActivityMainBinding - + lateinit var xposedSettingView: SettingItemView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (DeviceUtils.isFloatWindowOpened()) { FloatWindowService.start(this) + "".toInt() } binding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.presenter = Presenter() + xposedSettingView = binding.xposedSettingView binding.mainVm = vm setupActionBar(R.id.toolbar) { @@ -57,10 +65,16 @@ class MainActivity : BaseVMActivity() { FloatWindowService.stop(this) } }) - rootSettingView.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { _, isChecked -> + rootSettingView.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { _, _ -> }) - + xposedSettingView.setOnClickListener { + if (xposedSettingView.isChecked().not()) { + showSnack(getString(R.string.open_xposed_first)) + return@setOnClickListener + } + startActivity(Intent(this, XposedSettingActivity::class.java)) + } } override fun getViewModel(): MainViewModel { @@ -75,8 +89,7 @@ class MainActivity : BaseVMActivity() { inner class Presenter { fun onAccessibilityClick() { if (!accessibilitySettingView.checked) { - showSnack(getString(R.string.waiting)) - AccessibilityManager.startService(context) + AccessibilityManager.startAccessibilitySetting(context) } else { showSnack(getString(R.string.accessibility_service_opened)) } @@ -136,12 +149,47 @@ class MainActivity : BaseVMActivity() { ClipboardUtils.saveClipboardText(this, "627962572") showSnack(R.string.copy_success) } + .setNeutralButton("检查更新") { _, _ -> + checkUpdate(true) + } .create().show() override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { } + private fun checkUpdate(showSnack: Boolean = false) { + if (showSnack) { + showSnack("正在检查新版本...") + } + UpdateUtils.checkUpdate(object : Callback { + override fun onSuccess(data: VersionInfo) { + if (BuildConfig.VERSION_NAME == data.versionName) { + showSnack("暂无新版本") + return + } + showUpdateDialog(data) + } + + override fun onFailed(msg: String) { + if (showSnack) { + showSnack("检查失败...") + } + } + }) + } + + private fun showUpdateDialog(data: VersionInfo) = AlertDialog.Builder(this) + .setTitle("发现新版本") + .setMessage("版本号:${data.versionName}\n更新时间:${data.updateDate}\n大小:${data.size}\n版本说明:\n${data.feature}") + .setPositiveButton("下载") { _, _ -> + val intent = Intent(Intent.ACTION_VIEW) + val uri = Uri.parse(data.downloadUrl) + intent.data = uri + startActivity(intent) + } + .create().show() + private val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { intent?.run { diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/activity/main/viewmodel/MainViewModel.kt b/app/src/main/java/com/wrbug/developerhelper/ui/activity/main/viewmodel/MainViewModel.kt index d7c58ec..c6959ae 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/activity/main/viewmodel/MainViewModel.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/activity/main/viewmodel/MainViewModel.kt @@ -6,8 +6,8 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.OnLifecycleEvent import com.wrbug.developerhelper.R import com.wrbug.developerhelper.basecommon.BaseViewModel -import com.wrbug.developerhelper.model.mmkv.ConfigKv -import com.wrbug.developerhelper.model.mmkv.manager.MMKVManager +import com.wrbug.developerhelper.mmkv.ConfigKv +import com.wrbug.developerhelper.mmkv.manager.MMKVManager import com.wrbug.developerhelper.service.DeveloperHelperAccessibilityService import com.wrbug.developerhelper.service.FloatWindowService import com.wrbug.developerhelper.util.DeviceUtils @@ -17,13 +17,18 @@ class MainViewModel @Inject constructor() : BaseViewModel() { val openAccessibility = ObservableBoolean() val openFloatWindow = ObservableBoolean() val openRoot = ObservableBoolean() - val openXposed = ObservableBoolean() private val configKv: ConfigKv = MMKVManager.get(ConfigKv::class.java) - fun checkStatus() { + private fun checkStatus() { openAccessibility.set(DeveloperHelperAccessibilityService.serviceRunning) openFloatWindow.set(DeviceUtils.isFloatWindowOpened()) - openRoot.set(configKv.getOpenRoot()) - openXposed.set(configKv.getOpenXposed()) + if (configKv.isOpenRoot()) { + if (DeviceUtils.isRoot()) { + openRoot.set(true) + } else { + openRoot.set(false) + configKv.setOpenRoot(false) + } + } if (DeviceUtils.isFloatWindowOpened()) { FloatWindowService.start(application) } diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/activity/sharedpreferencesedit/SharedPreferenceEditActivity.kt b/app/src/main/java/com/wrbug/developerhelper/ui/activity/sharedpreferencesedit/SharedPreferenceEditActivity.kt index ca1588c..1516235 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/activity/sharedpreferencesedit/SharedPreferenceEditActivity.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/activity/sharedpreferencesedit/SharedPreferenceEditActivity.kt @@ -10,11 +10,11 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.wrbug.developerhelper.R import com.wrbug.developerhelper.basecommon.BaseActivity import com.wrbug.developerhelper.basecommon.setupActionBar -import com.wrbug.developerhelper.shell.ShellManager +import com.wrbug.developerhelper.commonutil.shell.ShellManager import com.wrbug.developerhelper.ui.decoration.SpaceItemDecoration import com.wrbug.developerhelper.util.OutSharedPreferenceManager import com.wrbug.developerhelper.util.XmlUtil -import com.wrbug.developerhelper.util.dp2px +import com.wrbug.developerhelper.commonutil.dp2px import kotlinx.android.synthetic.main.activity_shared_preference_edit.* import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/activity/xposed/shellmanager/ShellAppListAdapter.kt b/app/src/main/java/com/wrbug/developerhelper/ui/activity/xposed/shellmanager/ShellAppListAdapter.kt new file mode 100644 index 0000000..8c7580e --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/activity/xposed/shellmanager/ShellAppListAdapter.kt @@ -0,0 +1,82 @@ +package com.wrbug.developerhelper.ui.activity.xposed.shellmanager + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.wrbug.developerhelper.R +import com.wrbug.developerhelper.commonutil.entity.ApkInfo +import com.wrbug.developerhelper.ui.widget.bottommenu.BottomMenu +import com.wrbug.developerhelper.ui.widget.bottommenu.OnItemClickListener +import com.wrbug.developerhelper.xposed.processshare.DumpDexListProcessData +import com.wrbug.developerhelper.xposed.processshare.ProcessDataManager + +class ShellAppListAdapter(val context: Context) : RecyclerView.Adapter() { + private val list = ArrayList() + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_shell_app_info, parent, false)) + + fun setData(data: List) { + list.clear() + list.addAll(data) + notifyDataSetChanged() + } + + override fun getItemCount(): Int = list.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val apkInfo = list[position] + holder.icoIv.setImageDrawable(apkInfo.getIco()) + holder.appNameTv.text = apkInfo.getAppName() + holder.packageNameTv.text = apkInfo.packageInfo.packageName + holder.apkInfo = apkInfo + } + + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + var icoIv: ImageView = itemView.findViewById(R.id.icoIv) + var appNameTv: TextView = itemView.findViewById(R.id.appNameTv) + var packageNameTv: TextView = itemView.findViewById(R.id.packageNameTv) + var apkInfo: ApkInfo? = null + + init { + itemView.setOnClickListener { + apkInfo?.apply { + showDialog(this) + } + } + } + + private fun showDialog(apkInfo: ApkInfo) { + val bottomMenu = BottomMenu.Builder(context) + .menuItems(arrayOf(context.getString(R.string.remove_item))) + .onItemClickListener(object : OnItemClickListener { + override fun onClick(position: Int) { + when (position) { + 0 -> { + removeItem(apkInfo) + } + } + } + }) + .build() + bottomMenu.show() + } + } + + private fun removeItem(apkInfo: ApkInfo) { + val dexListProcessData = ProcessDataManager.get(DumpDexListProcessData::class.java) + val packageNames = dexListProcessData.getData() + packageNames?.apply { + remove(apkInfo.packageInfo.packageName) + dexListProcessData.setData(this) + } + val index = list.indexOf(apkInfo) + list.remove(apkInfo) + notifyItemRemoved(index) + } + +} diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/activity/xposed/shellmanager/ShellAppManagerActivity.kt b/app/src/main/java/com/wrbug/developerhelper/ui/activity/xposed/shellmanager/ShellAppManagerActivity.kt new file mode 100644 index 0000000..06c5b8b --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/activity/xposed/shellmanager/ShellAppManagerActivity.kt @@ -0,0 +1,90 @@ +package com.wrbug.developerhelper.ui.activity.xposed.shellmanager + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import com.wrbug.developerhelper.R +import com.wrbug.developerhelper.basecommon.BaseActivity +import com.wrbug.developerhelper.basecommon.setupActionBar +import com.wrbug.developerhelper.commonutil.AppInfoManager +import com.wrbug.developerhelper.commonutil.entity.ApkInfo +import com.wrbug.developerhelper.xposed.processshare.DumpDexListProcessData +import com.wrbug.developerhelper.xposed.processshare.ProcessDataManager +import kotlinx.android.synthetic.main.activity_shell_app_manager.* +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread + +class ShellAppManagerActivity : BaseActivity() { + private var tmpList = ArrayList() + private val adapter: ShellAppListAdapter by lazy { + ShellAppListAdapter(this) + } + + companion object { + fun start(context: Context) { + context.startActivity(Intent(context, ShellAppManagerActivity::class.java)) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_shell_app_manager) + setupActionBar(R.id.toolbar) { + title = getString(R.string.shell_app_manager) + } + swipeLayout.setOnRefreshListener { + getShellApp() + } + initRv() + getShellApp() + } + + private fun initRv() { + shellAppRv.layoutManager = LinearLayoutManager(this) + shellAppRv.adapter = adapter + } + + private fun getShellApp() { + swipeLayout.isRefreshing = true + doAsync { + val dexListProcessData = ProcessDataManager.get(DumpDexListProcessData::class.java) + val packageNames = dexListProcessData.getData() + if (packageNames == null || packageNames.isEmpty()) { + uiThread { + swipeLayout.isRefreshing = false + emptyView.visibility = View.VISIBLE + shellAppRv.visibility = View.INVISIBLE + } + tmpList.clear() + return@doAsync + } + if (tmpList.size != packageNames.size || !tmpList.containsAll(packageNames)) { + val data = ArrayList() + packageNames.forEach { it -> + AppInfoManager.getAppByPackageName(it)?.let { + data.add(it) + } + } + tmpList = packageNames + uiThread { + setData(data) + } + } else { + uiThread { + swipeLayout.isRefreshing = false + } + } + } + } + + private fun setData(list: List) { + swipeLayout.isRefreshing = false + emptyView.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE + shellAppRv.visibility = if (list.isEmpty()) View.INVISIBLE else View.VISIBLE + if (list.isEmpty().not()) { + adapter.setData(list) + } + } +} diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/activity/xposed/xposedsetting/XposedSettingActivity.kt b/app/src/main/java/com/wrbug/developerhelper/ui/activity/xposed/xposedsetting/XposedSettingActivity.kt new file mode 100644 index 0000000..4a491ad --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/activity/xposed/xposedsetting/XposedSettingActivity.kt @@ -0,0 +1,22 @@ +package com.wrbug.developerhelper.ui.activity.xposed.xposedsetting + +import android.os.Bundle +import com.wrbug.developerhelper.R +import com.wrbug.developerhelper.basecommon.BaseActivity +import com.wrbug.developerhelper.basecommon.setupActionBar +import com.wrbug.developerhelper.ui.activity.xposed.shellmanager.ShellAppManagerActivity +import kotlinx.android.synthetic.main.activity_xposed_setting.* + +class XposedSettingActivity : BaseActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_xposed_setting) + setupActionBar(R.id.toolbar) { + title = getString(R.string.xposed_setting) + } + shellSettingItemView.setOnClickListener { + ShellAppManagerActivity.start(this) + } + } +} diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/appdatainfoview/AppDataInfoView.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/appdatainfoview/AppDataInfoView.kt index d4904f2..1d844e6 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/widget/appdatainfoview/AppDataInfoView.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/appdatainfoview/AppDataInfoView.kt @@ -7,12 +7,13 @@ import android.widget.FrameLayout import android.widget.LinearLayout import androidx.appcompat.widget.AppCompatTextView import com.wrbug.developerhelper.R -import com.wrbug.developerhelper.model.entity.ApkInfo -import com.wrbug.developerhelper.shell.ShellManager +import com.wrbug.developerhelper.commonutil.ApkUtils +import com.wrbug.developerhelper.commonutil.AppInfoManager +import com.wrbug.developerhelper.commonutil.entity.ApkInfo +import com.wrbug.developerhelper.commonutil.shell.ShellManager +import com.wrbug.developerhelper.ui.activity.databaseedit.DatabaseEditActivity import com.wrbug.developerhelper.ui.activity.sharedpreferencesedit.SharedPreferenceEditActivity -import com.wrbug.developerhelper.util.ApkUtils -import com.wrbug.developerhelper.util.AppInfoManager -import com.wrbug.developerhelper.util.UiUtils +import com.wrbug.developerhelper.commonutil.UiUtils import kotlinx.android.synthetic.main.view_app_data_info.view.* import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread @@ -53,9 +54,11 @@ class AppDataInfoView : FrameLayout { doAsync { val sqliteFiles = ShellManager.getSqliteFiles(packageName) uiThread { - if (sqliteFiles.isNotEmpty()) { - databaseContainer.removeAllViews() + if (sqliteFiles.isEmpty()) { + defaultDbTv.setText(R.string.none) + return@uiThread } + databaseContainer.removeAllViews() val params = LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) for (sqliteFile in sqliteFiles) { val textView = AppCompatTextView(context) @@ -65,7 +68,9 @@ class AppDataInfoView : FrameLayout { textView.setPadding(0, UiUtils.dp2px(context, 8F), 0, 0) textView.tag = sqliteFile databaseContainer.addView(textView, params) - + textView.setOnClickListener { + DatabaseEditActivity.start(context, (it.tag as File).absolutePath) + } } } @@ -75,10 +80,11 @@ class AppDataInfoView : FrameLayout { private fun getSharedPreferencesFiles(packageName: String) { doAsync { val files = AppInfoManager.getSharedPreferencesFiles(packageName) - if (files.isEmpty()) { - return@doAsync - } uiThread { + if (files.isEmpty()) { + defaultSpTv.setText(R.string.none) + return@uiThread + } sharedPreferenceContainer.removeAllViews() val params = LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) files.forEach { diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/appsettingview/AppSettingView.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/appsettingview/AppSettingView.kt new file mode 100644 index 0000000..26db862 --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/appsettingview/AppSettingView.kt @@ -0,0 +1,300 @@ +package com.wrbug.developerhelper.ui.widget.appsettingview + +import android.Manifest +import android.app.Activity +import android.app.AlertDialog +import android.content.Context +import android.content.DialogInterface +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.ScrollView +import com.wrbug.developerhelper.R +import com.wrbug.developerhelper.basecommon.showToast +import com.wrbug.developerhelper.commonutil.AppManagerUtils +import com.wrbug.developerhelper.commonutil.entity.ApkInfo +import com.wrbug.developerhelper.mmkv.ConfigKv +import com.wrbug.developerhelper.mmkv.manager.MMKVManager +import kotlinx.android.synthetic.main.view_app_setting.view.* +import android.content.Intent +import android.net.Uri +import androidx.appcompat.widget.AppCompatButton +import com.wrbug.developerhelper.basecommon.BaseActivity +import com.wrbug.developerhelper.commonutil.shell.ShellManager +import com.wrbug.developerhelper.commonutil.zip +import com.wrbug.developerhelper.util.BackupUtils +import com.wrbug.developerhelper.util.toUri +import gdut.bsx.share2.Share2 +import gdut.bsx.share2.ShareContentType +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread +import java.io.File + + +class AppSettingView : ScrollView { + var apkInfo: ApkInfo? = null + private val configKv = MMKVManager.get(ConfigKv::class.java) + private var exportDexBtn: AppCompatButton? = null + + constructor(context: Context) : super(context) { + initView() + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + initView() + } + + private fun initView() { + LayoutInflater.from(context).inflate(R.layout.view_app_setting, this) + exportDexBtn = findViewById(R.id.exportDexBtn) + if (configKv.isOpenRoot().not()) { + backupApkBtn.isEnabled = false + backupApkDataDirBtn.isEnabled = false + restartAppBtn.isEnabled = false + stopAppBtn.isEnabled = false + deleteAppDataBtn.isEnabled = false + exportDexBtn?.isEnabled = false + } + initListener() + } + + private fun initListener() { + backupApkBtn.setOnClickListener { + doBackupApk() + } + backupApkDataDirBtn.setOnClickListener { + doBackupDataDir() + } + restartAppBtn.setOnClickListener { + doRestartApp() + } + stopAppBtn.setOnClickListener { + doStopApp() + } + deleteAppDataBtn.setOnClickListener { + doDeleteAppData() + } + uninstallAppBtn.setOnClickListener { + doUninstallApp() + } + + exportDexBtn?.setOnClickListener { + doBackupDexData() + } + } + + private fun doBackupDexData() { + apkInfo?.apply { + showToast(context.getString(R.string.packing_files)) + doAsync { + val dir = File(context.externalCacheDir, "dex/${applicationInfo.packageName}") + if (dir.exists()) { + ShellManager.rmFile(dir.absolutePath) + } + dir.mkdirs() + val dexDir = "/data/data/${applicationInfo.packageName}/dump" + val lsDir = ShellManager.lsDir(dexDir) + if (lsDir.isEmpty()) { + uiThread { + showToast(context.getString(R.string.no_dex_files)) + } + return@doAsync + } + if (ShellManager.cpFile(dexDir, dir.absolutePath)) { + val zipFile = File(context.externalCacheDir, "${apkInfo?.getAppName() ?: ""}-dex.zip") + dir.zip(zipFile) + val uri = zipFile.toUri() + if (uri == null) { + showToast(R.string.export_failed) + return@doAsync + } + uiThread { + showShareDexNotice(uri) + } + } else { + uiThread { + showToast(context.getString(R.string.export_failed)) + } + } + } + } + } + + private fun showShareDexNotice(uri: Uri) { + Share2.Builder(context as Activity) + .setContentType(ShareContentType.FILE) + .setShareFileUri(uri) + .setOnActivityResult(10) + .build() + .shareBySystem() + } + + private fun doUninstallApp() { + apkInfo?.apply { + AppManagerUtils.uninstallApp(context, applicationInfo.packageName) + } + } + + private fun doDeleteAppData() { + if (checkRoot().not()) { + return + } + apkInfo?.apply { + showNotice( + context.getString(R.string.confirm_delete_app_data), + DialogInterface.OnClickListener { _, _ -> + if (AppManagerUtils.clearAppData(applicationInfo.packageName)) { + activityFinish() + showToast(context.getString(R.string.clear_complete)) + } + }) + + } + } + + private fun doStopApp() { + if (checkRoot().not()) { + return + } + apkInfo?.apply { + showNotice(context.getString(R.string.confirm_stop_app), DialogInterface.OnClickListener { _, _ -> + if (AppManagerUtils.forceStopApp(applicationInfo.packageName)) { + activityFinish() + } + }) + } + } + + private fun doRestartApp() { + if (checkRoot().not()) { + return + } + apkInfo?.apply { + showNotice(context.getString(R.string.confirm_restart_app), DialogInterface.OnClickListener { _, _ -> + if (!AppManagerUtils.forceStopApp(applicationInfo.packageName)) { + showToast(context.getString(R.string.restart_failed)) + return@OnClickListener + } + activityFinish() + val intent = context.packageManager.getLaunchIntentForPackage(applicationInfo.packageName) + intent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + }) + } + } + + private fun doBackupDataDir() { + if (checkRoot().not()) { + return + } + apkInfo?.apply { + val backupAppData = BackupUtils.backupAppData(applicationInfo.packageName, applicationInfo.dataDir) + if (backupAppData == null) { + showToast(context.getString(R.string.backup_failed)) + return + } + if (context !is BaseActivity) { + showToast(context.getString(R.string.backup_success_msg)) + return + } + showShareDataNotice(backupAppData) + } + } + + private fun showShareDataNotice(backupAppData: File) { + showNotice( + context.getString(R.string.backup_success_and_share_msg), + DialogInterface.OnClickListener { _, _ -> + (context as BaseActivity).requestPermission(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), + object : BaseActivity.PermissionCallback() { + override fun granted() { + val zipFile = File(context.externalCacheDir, "${apkInfo?.getAppName() ?: ""}-data.zip") + backupAppData.zip(zipFile) + val uri = zipFile.toUri() + if (uri == null) { + showToast(context.getString(R.string.share_failed)) + return + } + activityFinish() + Share2.Builder(context as Activity) + .setContentType(ShareContentType.FILE) + .setShareFileUri(uri) + .setOnActivityResult(10) + .build() + .shareBySystem() + } + + }) + + }) + } + + private fun doBackupApk() { + if (checkRoot().not()) { + return + } + apkInfo?.apply { + val uri = BackupUtils.backupApk( + applicationInfo.packageName, + applicationInfo.publicSourceDir, + "${getAppName()}_${packageInfo.versionName}.apk" + ) + if (uri == null) { + showToast(context.getString(R.string.backup_failed)) + return + } + if (context !is BaseActivity) { + showToast(context.getString(R.string.backup_success_msg)) + return + } + showShareApkDialog(uri) + + } + } + + private fun showShareApkDialog(uri: Uri) { + showNotice( + context.getString(R.string.backup_success_and_share_msg), + DialogInterface.OnClickListener { _, _ -> + (context as BaseActivity).requestPermission(arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ), + object : BaseActivity.PermissionCallback() { + override fun granted() { + activityFinish() + Share2.Builder(context as Activity) + .setContentType(ShareContentType.FILE) + .setShareFileUri(uri) + .setOnActivityResult(10) + .build() + .shareBySystem() + } + + }) + + }) + } + + private fun checkRoot(): Boolean { + if (configKv.isOpenRoot().not()) { + showToast(context.getString(R.string.please_open_root)) + return false + } + return true + } + + fun activityFinish() { + if (context is Activity) { + (context as Activity).finish() + } + } + + private fun showNotice(msg: String, listener: DialogInterface.OnClickListener) { + AlertDialog.Builder(context) + .setTitle(R.string.notice) + .setMessage(msg) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.ok, listener) + .create().show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/bottommenu/BottomMenu.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/bottommenu/BottomMenu.kt new file mode 100644 index 0000000..fcbc77c --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/bottommenu/BottomMenu.kt @@ -0,0 +1,93 @@ +package com.wrbug.developerhelper.ui.widget.bottommenu + +import android.content.Context +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.wrbug.developerhelper.R +import com.wrbug.developerhelper.commonutil.UiUtils +import com.wrbug.developerhelper.ui.decoration.SpaceItemDecoration +import kotlinx.android.synthetic.main.dialog_bottom_menu.* + +class BottomMenu(context: Context) : BottomSheetDialog(context), OnItemClickListener { + + private var title = "" + val adapter: BottomMenuItemAdapter by lazy { + BottomMenuItemAdapter(context) + } + + private var items: Array = arrayOf() + private var listener: OnItemClickListener? = null + + init { + setContentView(R.layout.dialog_bottom_menu) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initRv() + initView() + } + + private fun initView() { + titleTv.text = title + titleTv.visibility = if (title.isEmpty()) View.GONE else View.VISIBLE + } + + private fun initRv() { + menuListRv.layoutManager = LinearLayoutManager(context) + val decoration = SpaceItemDecoration(UiUtils.dp2px(context, 0F)) + decoration.setLastBottomPadding(UiUtils.dp2px(context, 10F)) + decoration.setFirstTopPadding(UiUtils.dp2px(context, 10F)) + menuListRv.addItemDecoration(decoration) + val dividerItemDecoration = DividerItemDecoration(context, DividerItemDecoration.VERTICAL) + dividerItemDecoration.setDrawable(ColorDrawable(context.resources.getColor(R.color.divider_color))) + menuListRv.addItemDecoration(dividerItemDecoration) + adapter.setOnItemClickListener(this) + menuListRv.adapter = adapter + adapter.list = items + } + + override fun onClick(position: Int) { + listener?.onClick(position) + dismiss() + } + + class Builder(val context: Context) { + private var title = "" + private var items: Array = arrayOf() + private var listener: OnItemClickListener? = null + + fun title(id: Int): Builder { + title = context.getString(id) + return this + } + + fun title(title: String): Builder { + this.title = title + return this + } + + fun menuItems(items: Array): Builder { + this.items = items + return this + } + + fun onItemClickListener(listener: OnItemClickListener?): Builder { + this.listener = listener + return this + } + + fun build(): BottomMenu { + val menu = BottomMenu(context) + menu.items = items + menu.title = title + menu.listener = listener + return menu + } + } + +} diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/bottommenu/BottomMenuItemAdapter.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/bottommenu/BottomMenuItemAdapter.kt new file mode 100644 index 0000000..a8e5b9c --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/bottommenu/BottomMenuItemAdapter.kt @@ -0,0 +1,51 @@ +package com.wrbug.developerhelper.ui.widget.bottommenu + +import android.content.Context +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.wrbug.developerhelper.R +import com.wrbug.developerhelper.commonutil.UiUtils + +class BottomMenuItemAdapter(val context: Context) : RecyclerView.Adapter() { + var list: Array = arrayOf() + set(value) { + field = value + notifyDataSetChanged() + } + var listener: OnItemClickListener? = null + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val tv = TextView(context) + tv.setTextColor(context.resources.getColor(R.color.text_color_666666)) + tv.gravity = Gravity.CENTER + val dp10 = UiUtils.dp2px(context, 10F) + tv.setPadding(dp10, dp10, dp10, dp10) + val params = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + tv.layoutParams = params + return ViewHolder(tv) + } + + override fun getItemCount(): Int = list.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.tv.text = list[position] + holder.index = position + } + + fun setOnItemClickListener(listener: OnItemClickListener) { + this.listener = listener + } + + inner class ViewHolder(val tv: TextView) : RecyclerView.ViewHolder(tv) { + var index = 0 + + init { + tv.setOnClickListener { + listener?.onClick(index) + } + } + + } +} diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/bottommenu/OnItemClickListener.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/bottommenu/OnItemClickListener.kt new file mode 100644 index 0000000..2a4f90b --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/bottommenu/OnItemClickListener.kt @@ -0,0 +1,5 @@ +package com.wrbug.developerhelper.ui.widget.bottommenu + +interface OnItemClickListener { + fun onClick(position: Int) +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/boundsinfoview/BoundsInfoView.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/boundsinfoview/BoundsInfoView.kt index ccb04a0..20d5d2e 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/widget/boundsinfoview/BoundsInfoView.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/boundsinfoview/BoundsInfoView.kt @@ -9,7 +9,7 @@ import android.util.AttributeSet import android.view.View import com.wrbug.developerhelper.R import com.wrbug.developerhelper.ui.widget.helper.CanvasHelper -import com.wrbug.developerhelper.util.UiUtils +import com.wrbug.developerhelper.commonutil.UiUtils class BoundsInfoView : View { var bounds: Rect? = null diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/emptyview/EmptyView.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/emptyview/EmptyView.kt new file mode 100644 index 0000000..e5c3504 --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/emptyview/EmptyView.kt @@ -0,0 +1,50 @@ +package com.wrbug.developerhelper.ui.widget.emptyview + +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.wrbug.developerhelper.R +import com.wrbug.developerhelper.commonutil.UiUtils + +class EmptyView : LinearLayout { + val icoIv: ImageView by lazy { + val icoIv = ImageView(context) + icoIv.setImageResource(R.drawable.ic_ic_empty_666666) + icoIv + } + val textTv: TextView by lazy { + val tv = TextView(context) + tv.setTextColor(resources.getColor(R.color.text_color_666666)) + tv.text = "无数据" + tv.textSize = 20F + tv.gravity = Gravity.CENTER + val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + params.topMargin = UiUtils.dp2px(context, 20F) + tv.layoutParams = params + tv + } + + constructor(context: Context) : super(context) { + } + + constructor(context: Context, attributeSet: AttributeSet?) : super(context, attributeSet) { + init() + initView() + } + + private fun init() { + orientation = VERTICAL + gravity = Gravity.CENTER + } + + private fun initView() { + addView(icoIv) + addView(textTv) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/hierarchyView/HierarchyDetailView.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/hierarchyView/HierarchyDetailView.kt index 0af5800..1309219 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/widget/hierarchyView/HierarchyDetailView.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/hierarchyView/HierarchyDetailView.kt @@ -9,8 +9,7 @@ import android.widget.FrameLayout import com.wrbug.developerhelper.model.entity.HierarchyNode import com.wrbug.developerhelper.R import com.wrbug.developerhelper.ui.widget.helper.CanvasHelper -import com.wrbug.developerhelper.ui.widget.layoutinfoview.LayoutInfoView -import com.wrbug.developerhelper.util.UiUtils +import com.wrbug.developerhelper.commonutil.UiUtils class HierarchyDetailView : FrameLayout { private val paint: Paint by lazy { @@ -44,7 +43,6 @@ class HierarchyDetailView : FrameLayout { this.hierarchyNode = hierarchyNode this.parentHierarchyNode = parentHierarchyNode invalidate() - LayoutInfoView(context, hierarchyNode).show() } private fun initView() { diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/hierarchyView/HierarchyView.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/hierarchyView/HierarchyView.kt index c04cbdb..fd4c119 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/widget/hierarchyView/HierarchyView.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/hierarchyView/HierarchyView.kt @@ -16,6 +16,8 @@ class HierarchyView(context: Context, attrs: AttributeSet?) : View(context, attr private var mHierarchyNodes = arrayListOf() private val nodeMap = hashMapOf() private var onHierarchyNodeClickListener: OnHierarchyNodeClickListener? = null + private var selectedNode: HierarchyNode? = null + private var selectedParentNode: HierarchyNode? = null constructor(context: Context) : this(context, null) @@ -44,15 +46,33 @@ class HierarchyView(context: Context, attrs: AttributeSet?) : View(context, attr override fun onTouchEvent(event: MotionEvent?): Boolean { when (event?.action) { MotionEvent.ACTION_DOWN -> { - val hierarchyNode = getNode(event.x, event.y) - if (hierarchyNode != null && onHierarchyNodeClickListener != null) { - onHierarchyNodeClickListener?.onClick(hierarchyNode.selectedNode, hierarchyNode.parentNode) + getSelectedNode(event.x, event.y) + return true + } + MotionEvent.ACTION_MOVE -> { + getSelectedNode(event.x, event.y) + } + MotionEvent.ACTION_UP -> { + selectedNode?.let { + onHierarchyNodeClickListener?.onClick(it, selectedParentNode) } } } return super.onTouchEvent(event) } + private fun getSelectedNode(x: Float, y: Float) { + val hierarchyNode = getNode(x, y) + if (hierarchyNode != null && onHierarchyNodeClickListener != null) { + if (selectedNode == hierarchyNode.selectedNode) { + return + } + selectedNode = hierarchyNode.selectedNode + selectedParentNode = hierarchyNode.parentNode + onHierarchyNodeClickListener?.onSelectedNodeChanged(hierarchyNode.selectedNode, hierarchyNode.parentNode) + } + } + override fun onDraw(canvas: Canvas?) { drawRect(canvas, mHierarchyNodes) } @@ -113,5 +133,6 @@ class HierarchyView(context: Context, attrs: AttributeSet?) : View(context, attr interface OnHierarchyNodeClickListener { fun onClick(node: HierarchyNode, parentNode: HierarchyNode?) + fun onSelectedNodeChanged(node: HierarchyNode, parentNode: HierarchyNode?) } } \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/HierarchyGraphView.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/HierarchyGraphView.kt new file mode 100644 index 0000000..e6a29ff --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/HierarchyGraphView.kt @@ -0,0 +1,29 @@ +package com.wrbug.developerhelper.ui.widget.layoutinfoview + +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.ViewParent +import androidx.viewpager.widget.ViewPager +import de.blox.graphview.GraphView +import java.util.jar.Attributes + +class HierarchyGraphView(context: Context, attributes: AttributeSet?) : GraphView(context, attributes) { + private var clickTime = 0L + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + var parent: ViewParent = this + while (parent !is ViewPager) { + parent = parent.parent + } + parent.requestDisallowInterceptTouchEvent(true) + if (ev?.action == MotionEvent.ACTION_DOWN) { + if (System.currentTimeMillis() - clickTime < 200) { + zoomBy(1.8F, true) + clickTime = 0 + } + clickTime = System.currentTimeMillis() + + } + return super.dispatchTouchEvent(ev) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/LayoutInfoView.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/LayoutInfoView.kt index 7ecf654..f077e16 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/LayoutInfoView.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/LayoutInfoView.kt @@ -4,27 +4,34 @@ import android.content.Context import com.google.android.material.bottomsheet.BottomSheetDialog import com.wrbug.developerhelper.R import com.wrbug.developerhelper.model.entity.HierarchyNode -import com.wrbug.developerhelper.util.UiUtils +import com.wrbug.developerhelper.commonutil.UiUtils import kotlinx.android.synthetic.main.view_layout_info.* -class LayoutInfoView(context: Context, private val hierarchyNode: HierarchyNode) : BottomSheetDialog(context) { +class LayoutInfoView( + context: Context, + private val nodeList: List?, + private val hierarchyNode: HierarchyNode +) : BottomSheetDialog(context) { + val adapter = LayoutInfoViewPagerAdapter(context, nodeList, hierarchyNode) init { init() } + fun setOnNodeChangedListener(listener: OnNodeChangedListener) { + adapter.setOnNodeChangedListener(listener) + } private fun init() { setContentView(R.layout.view_layout_info) val layoutParams = layoutInfoContainer.layoutParams layoutParams.height = UiUtils.getDeviceHeight(context) / 2 - layoutInfoContainer.layoutParams=layoutParams + layoutInfoContainer.layoutParams = layoutParams initViewpager() } private fun initViewpager() { - val adapter = LayoutInfoViewPagerAdapter(context, hierarchyNode) viewPager.adapter = adapter tabLayout.setupWithViewPager(viewPager) } diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/LayoutInfoViewPagerAdapter.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/LayoutInfoViewPagerAdapter.kt index 4a5f6ac..3c6c383 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/LayoutInfoViewPagerAdapter.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/LayoutInfoViewPagerAdapter.kt @@ -1,33 +1,130 @@ package com.wrbug.developerhelper.ui.widget.layoutinfoview import android.content.Context +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewpager.widget.PagerAdapter import androidx.viewpager.widget.ViewPager +import com.wrbug.developerhelper.R import com.wrbug.developerhelper.model.entity.HierarchyNode import com.wrbug.developerhelper.ui.widget.boundsinfoview.BoundsInfoView import com.wrbug.developerhelper.ui.widget.layoutinfoview.infopage.InfoAdapter import com.wrbug.developerhelper.ui.widget.layoutinfoview.infopage.ItemInfo +import de.blox.graphview.* +import de.blox.graphview.tree.BuchheimWalkerAlgorithm +import de.blox.graphview.tree.BuchheimWalkerConfiguration +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread + class LayoutInfoViewPagerAdapter( val context: Context, - private val hierarchyNode: HierarchyNode + private val nodeList: List?, + private var hierarchyNode: HierarchyNode ) : PagerAdapter() { private val tabList = arrayListOf() private val viewList = arrayListOf() private val infoAdapter: InfoAdapter = InfoAdapter(context) + private lateinit var graphView: GraphView + private val boundsInfoView = BoundsInfoView(context) + private var onNodeChangedListener: OnNodeChangedListener? = null init { initInfoTab() initLayoutTable() + initViewTreeTab() + } + + fun setOnNodeChangedListener(listener: OnNodeChangedListener) { + onNodeChangedListener = listener + } + + private fun initViewTreeTab() { + tabList.add("ViewTree") + graphView = + LayoutInflater.from(context).inflate(R.layout.layout_hierarchy_tree, null) as GraphView + val adapter = ViewTreeGraphAdapter(context, R.layout.item_tree_node_view) + graphView.adapter = adapter + adapter.setOnItemClickListener(object : ViewTreeGraphAdapter.OnItemClickListener { + override fun onClick(node: ViewTreeGraphNode, position: Int) { + hierarchyNode = node.node + resetInfoTab() + resetLayoutTable() + resetViewTreeTab(node) + onNodeChangedListener?.onChanged(node.node, node.parent?.node) + } + + }) + val configuration = BuchheimWalkerConfiguration.Builder() + .setSiblingSeparation(100) + .setLevelSeparation(300) + .setSubtreeSeparation(300) + .setOrientation(BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM) + .build() + adapter.algorithm = BuchheimWalkerAlgorithm(configuration) + viewList.add(graphView) + doAsync { + val graph = Graph() + initGraph(nodeList, null, graph) + uiThread { + adapter.graph = graph + } + } + } + + private fun resetViewTreeTab(node: ViewTreeGraphNode) { + graphView.adapter.graph = graphView.adapter.graph?.apply { + for (edge in nodes) { + (edge?.data as ViewTreeGraphNode?)?.apply { + selected = this == node + childSelected = false + if (selected) { + parent?.childSelected = true + } + } + } + } + + } + + private fun resetInfoTab() { + infoAdapter.setItems(setInfo()) + } + + private fun resetLayoutTable() { + boundsInfoView.bounds = hierarchyNode.screenBounds + } + + private fun initGraph(nodeList: List?, parentNode: Node?, graph: Graph) { + nodeList?.run { + val map = LinkedHashMap() + for (hNode in this) { + val graphNode = ViewTreeGraphNode(hNode) + val node = Node(graphNode) + if (hNode == hierarchyNode) { + graphNode.selected = true + } + parentNode?.let { + graph.addEdge(parentNode, node) + graphNode.parent = (it.data as ViewTreeGraphNode?)?.apply { + if (graphNode.selected) { + childSelected = true + } + } + } + map[hNode] = node + } + for ((k, v) in map) { + initGraph(k.childId, v, graph) + } + } } private fun initLayoutTable() { tabList.add("Layout") - val boundsInfoView = BoundsInfoView(context) boundsInfoView.bounds = hierarchyNode.screenBounds viewList.add(boundsInfoView) } @@ -36,33 +133,7 @@ class LayoutInfoViewPagerAdapter( tabList.add("Info") val recyclerView = RecyclerView(context) recyclerView.layoutManager = LinearLayoutManager(context) - val list = with(hierarchyNode) { - val list = arrayListOf() - list.add(ItemInfo("Package", packageName)) - list.add(ItemInfo("Widget", widget)) - list.add( - ItemInfo( - "Id", - "${resourceId.replace(packageName, "app")}[${idHex?.replace("#", "0x") ?: "NO_ID"}]" - ) - ) - if (!text.isEmpty()) { - list.add(ItemInfo("Text", text)) - } - list.add(ItemInfo("Enable", enabled)) - list.add(ItemInfo("Clickable", clickable)) - list.add(ItemInfo("Checkable", checkable)) - list.add(ItemInfo("Checked", checked)) - - list.add(ItemInfo("Focusable", focusable)) - list.add(ItemInfo("Focused", focused)) - list.add(ItemInfo("LongClickable", longClickable)) - list.add(ItemInfo("Bounds", screenBounds ?: "")) - list.add(ItemInfo("Password", password)) - list.add(ItemInfo("Selected", selected)) - list - } - + val list = setInfo() recyclerView.adapter = infoAdapter val params = ViewPager.LayoutParams() infoAdapter.setItems(list) @@ -70,6 +141,33 @@ class LayoutInfoViewPagerAdapter( viewList.add(recyclerView) } + private fun setInfo() = with(hierarchyNode) { + val list = arrayListOf() + list.add(ItemInfo("Package", packageName)) + list.add(ItemInfo("Widget", widget)) + list.add( + ItemInfo( + "Id", + "${resourceId.replace(packageName, "app")}[${idHex?.replace("#", "0x") ?: "NO_ID"}]" + ) + ) + if (!text.isEmpty()) { + list.add(ItemInfo("Text", text)) + } + list.add(ItemInfo("Enable", enabled)) + list.add(ItemInfo("Clickable", clickable)) + list.add(ItemInfo("Checkable", checkable)) + list.add(ItemInfo("Checked", checked)) + + list.add(ItemInfo("Focusable", focusable)) + list.add(ItemInfo("Focused", focused)) + list.add(ItemInfo("LongClickable", longClickable)) + list.add(ItemInfo("Bounds", screenBounds ?: "")) + list.add(ItemInfo("Password", password)) + list.add(ItemInfo("Selected", selected)) + list + } + override fun isViewFromObject(p0: View, p1: Any): Boolean { return p0 == p1 diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/ObserableNode.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/ObserableNode.kt new file mode 100644 index 0000000..b714b8c --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/ObserableNode.kt @@ -0,0 +1,20 @@ +package com.wrbug.developerhelper.ui.widget.layoutinfoview + +import de.blox.graphview.Node +import de.blox.graphview.Vector + +class ObserableNode(data: Any?) : Node(data) { + private var listener: OnPosChangedListener? = null + override fun setPos(pos: Vector?) { + super.setPos(pos) + listener?.onChanged(pos) + } + + fun setOnPosChangedListener(onPosChangedListener: OnPosChangedListener) { + listener = onPosChangedListener + } + + interface OnPosChangedListener { + fun onChanged(pos: Vector?) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/OnNodeChangedListener.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/OnNodeChangedListener.kt new file mode 100644 index 0000000..2aa5773 --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/OnNodeChangedListener.kt @@ -0,0 +1,7 @@ +package com.wrbug.developerhelper.ui.widget.layoutinfoview + +import com.wrbug.developerhelper.model.entity.HierarchyNode + +interface OnNodeChangedListener { + fun onChanged(node: HierarchyNode, parentNode: HierarchyNode?) +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/ViewTreeGraphAdapter.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/ViewTreeGraphAdapter.kt new file mode 100644 index 0000000..bafec3c --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/ViewTreeGraphAdapter.kt @@ -0,0 +1,74 @@ +package com.wrbug.developerhelper.ui.widget.layoutinfoview + +import android.content.Context +import android.view.View +import android.widget.TextView +import androidx.annotation.LayoutRes +import androidx.annotation.NonNull +import androidx.cardview.widget.CardView +import com.wrbug.developerhelper.R +import de.blox.graphview.BaseGraphAdapter + +class ViewTreeGraphAdapter(@NonNull val context: Context, @LayoutRes val layoutRes: Int) : + BaseGraphAdapter(context, layoutRes) { + private var listener: OnItemClickListener? = null + override fun onCreateViewHolder(view: View?) = ViewHolder(view!!) + override fun onBindViewHolder(viewHolder: ViewHolder?, data: Any?, position: Int) { + viewHolder?.run { + val node = data as ViewTreeGraphNode + widgetTv.text = node.node.widget + cardView.setOnClickListener { + listener?.onClick(node, position) + } + when { + node.selected -> cardView.setCardBackgroundColor(context.resources.getColor(R.color.colorAccent)) + node.childSelected -> cardView.setCardBackgroundColor(context.resources.getColor(R.color.colorAccentLight)) + else -> { + cardView.setCardBackgroundColor(context.resources.getColor(R.color.colorPrimary)) + if (node.shortName) { + widgetTv.text = toShortName(node.node.widget) + cardView.setOnClickListener { + widgetTv.text = node.node.widget + node.shortName = false + cardView.setOnClickListener{ + listener?.onClick(node, position) + } + } + } else { + widgetTv.text = node.node.widget + } + + } + } + } + + } + + fun setOnItemClickListener(onItemClickListener: OnItemClickListener) { + listener = onItemClickListener + } + + private fun toShortName(name: String): String { + if (name.contains(".").not()) { + return name + } + val splitName = name.split(".") + if (splitName.isNotEmpty()) { + val realName = splitName[splitName.size - 1] + if (realName.length> 4) { + return realName.substring(0, 4) + "..." + } + return realName + } + return name + } + + class ViewHolder(itemView: View) { + val widgetTv: TextView = itemView.findViewById(R.id.widgetTv) + val cardView: CardView = itemView.findViewById(R.id.cardView) + } + + interface OnItemClickListener { + fun onClick(node: ViewTreeGraphNode, position: Int) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/ViewTreeGraphNode.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/ViewTreeGraphNode.kt new file mode 100644 index 0000000..5827dc7 --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/ViewTreeGraphNode.kt @@ -0,0 +1,24 @@ +package com.wrbug.developerhelper.ui.widget.layoutinfoview + +import com.wrbug.developerhelper.model.entity.HierarchyNode + +class ViewTreeGraphNode(val node: HierarchyNode) { + var selected: Boolean = false + var childSelected = false + set(value) { + if (field != value) { + field = value + parent?.childSelected = value + } + + } + var parent: ViewTreeGraphNode? = null + var shortName = true + + fun isParentSelected(): Boolean { + if (parent?.selected == true) { + return true + } + return parent?.isParentSelected() ?: false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/infopage/InfoAdapter.kt b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/infopage/InfoAdapter.kt index 8b092f6..4a66f6f 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/infopage/InfoAdapter.kt +++ b/app/src/main/java/com/wrbug/developerhelper/ui/widget/layoutinfoview/infopage/InfoAdapter.kt @@ -6,7 +6,7 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.wrbug.developerhelper.R -import com.wrbug.developerhelper.util.print +import com.wrbug.developerhelper.commonutil.print import kotlinx.android.synthetic.main.item_view_info.view.* class InfoAdapter(val context: Context) : RecyclerView.Adapter() { @@ -27,6 +27,14 @@ class InfoAdapter(val context: Context) : RecyclerView.Adapter true } +// switcher.setOnTouchListener { _, _ -> true } + switcherMaskView.visibility = View.VISIBLE } } diff --git a/app/src/main/java/com/wrbug/developerhelper/util/BackupUtils.kt b/app/src/main/java/com/wrbug/developerhelper/util/BackupUtils.kt new file mode 100644 index 0000000..90f8bf3 --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/util/BackupUtils.kt @@ -0,0 +1,34 @@ +package com.wrbug.developerhelper.util + +import android.net.Uri +import android.os.Environment +import com.wrbug.developerhelper.commonutil.shell.ShellManager +import com.wrbug.developerhelper.commonutil.zip +import java.io.File + +object BackupUtils { + private val backupDir: File by lazy { + val file = File(Environment.getExternalStorageDirectory(), "com.wrbug.developerHelper/backup") + if (file.exists().not()) { + file.mkdirs() + } + file + } + + fun backupApk(packageName: String, apkPath: String, fileName: String): Uri? { + val apkDir = File(backupDir, "apks/$packageName/$fileName") + if (ShellManager.cpFile(apkPath, apkDir.absolutePath)) { + return apkDir.toUri() + } + return null + } + + fun backupAppData(packageName: String, dataDir: String): File? { + val backupDataDir = + File(backupDir, "datas/$packageName/${System.currentTimeMillis().format("yyyy-MM-dd-HH_mm_ss")}") + if (ShellManager.cpFile(dataDir, backupDataDir.absolutePath)) { + return backupDataDir + } + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/util/DatabaseUtils.kt b/app/src/main/java/com/wrbug/developerhelper/util/DatabaseUtils.kt new file mode 100644 index 0000000..ee83e32 --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/util/DatabaseUtils.kt @@ -0,0 +1,40 @@ +package com.wrbug.developerhelper.util + +import android.database.sqlite.SQLiteDatabase +import com.wrbug.developerhelper.model.entity.DatabaseTableInfo +import java.util.* + +object DatabaseUtils { + fun getDatabase(dbFile: String): Map { + val map = TreeMap() + val db = SQLiteDatabase.openDatabase(dbFile, null, SQLiteDatabase.OPEN_READWRITE) + db?.run { + val cursor = rawQuery("select name from sqlite_master where type='table' order by name", null) + while (cursor.moveToNext()) { + val name = cursor.getString(0) + if (name == "android_metadata") { + continue + } + val rawQuery = rawQuery("select * from $name", null) + val count = rawQuery.columnCount + val info = DatabaseTableInfo() + info.name = name + info.keys = rawQuery.columnNames + val list = ArrayList
      >() + while (rawQuery.moveToNext()) { + val dataMap = hashMapOf() + for (index in 0 until count) { + dataMap[rawQuery.getColumnName(index)] = rawQuery.getString(index) + } + list.add(dataMap) + } + rawQuery.close() + info.rows = list + map[name] = info + } + cursor.close() + close() + } + return map + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/util/DeviceUtils.kt b/app/src/main/java/com/wrbug/developerhelper/util/DeviceUtils.kt index 4469ce9..b44b4c9 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/DeviceUtils.kt +++ b/app/src/main/java/com/wrbug/developerhelper/util/DeviceUtils.kt @@ -4,13 +4,13 @@ import android.content.Context import android.os.Build import android.provider.Settings import android.text.TextUtils -import com.jaredrummler.android.shell.Shell import com.wrbug.developerhelper.basecommon.BaseApp +import com.wrbug.developerhelper.commonutil.ShellUtils object DeviceUtils { fun isRoot(): Boolean { - return Shell.SU.available() + return ShellUtils.isRoot() } fun isFloatWindowOpened(): Boolean { diff --git a/app/src/main/java/com/wrbug/developerhelper/util/EnforceUtils.kt b/app/src/main/java/com/wrbug/developerhelper/util/EnforceUtils.kt index 54c1ac6..c79da14 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/EnforceUtils.kt +++ b/app/src/main/java/com/wrbug/developerhelper/util/EnforceUtils.kt @@ -1,10 +1,9 @@ package com.wrbug.developerhelper.util - -import com.wrbug.developerhelper.shell.ShellManager +import com.wrbug.developerhelper.commonutil.shell.ShellManager object EnforceUtils { fun getEnforceType(packageName: String): EnforceType { - val files = ShellManager.getZipFileList(findApkDir(packageName)).toString().trim() + val files = ShellManager.getZipFileList(ShellManager.findApkDir(packageName)).toString().trim() return when { isIjiaMi(files) -> EnforceType.I_JIA_MI is360(files) -> EnforceType.QI_HOO @@ -31,12 +30,6 @@ object EnforceUtils { return files.contains("bangcle") } - private fun findApkDir(packageName: String): String { - val cmd = "ls /data/app/|grep $packageName" - val dir = ShellUtils.runWithSu(cmd).getStdout() - return "/data/app/$dir/base.apk" - } - enum class EnforceType(val type: String) { QI_HOO("360"), diff --git a/app/src/main/java/com/wrbug/developerhelper/util/FileUtils.kt b/app/src/main/java/com/wrbug/developerhelper/util/FileUtils.kt index f8d3f3e..ca7532c 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/FileUtils.kt +++ b/app/src/main/java/com/wrbug/developerhelper/util/FileUtils.kt @@ -1,13 +1,15 @@ package com.wrbug.developerhelper.util import android.media.MediaMetadataRetriever -import android.os.Environment +import android.net.Uri +import android.os.Build +import androidx.core.content.FileProvider +import com.wrbug.developerhelper.basecommon.BaseApp import org.dom4j.Document import org.dom4j.io.OutputFormat import org.dom4j.io.XMLWriter import java.io.* -import org.dom4j.io.OutputFormat.createPrettyPrint /** @@ -30,33 +32,6 @@ object FileUtils { } - fun readFile(file: File): String { - val builder = StringBuilder() - try { - val fr = FileReader(file) - var ch = fr.read() - while (ch != -1) { - builder.append(ch.toChar()) - ch = fr.read() - } - } catch (e: IOException) { - } - - return builder.toString() - } - - fun whiteFile(file: File, data: String) { - try { - if (!file.exists()) { - file.createNewFile() - } - val fw = FileWriter(file) - fw.write(data) - fw.flush() - } catch (e: IOException) { - } - - } fun whiteXml(file: File, document: Document) { try { @@ -111,3 +86,11 @@ object FileUtils { } } + +fun File.toUri(): Uri? { + return if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.N) { + FileProvider.getUriForFile(BaseApp.instance, "com.wrbug.developerhelper.fileprovider", this) + } else { + Uri.fromFile(this) + } +} diff --git a/app/src/main/java/com/wrbug/developerhelper/util/OutSharedPreferenceManager.kt b/app/src/main/java/com/wrbug/developerhelper/util/OutSharedPreferenceManager.kt index 881de9b..cc64336 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/OutSharedPreferenceManager.kt +++ b/app/src/main/java/com/wrbug/developerhelper/util/OutSharedPreferenceManager.kt @@ -1,6 +1,9 @@ package com.wrbug.developerhelper.util import android.content.Context +import com.wrbug.developerhelper.commonutil.toBoolean +import com.wrbug.developerhelper.commonutil.toInt +import com.wrbug.developerhelper.commonutil.toLong import com.wrbug.developerhelper.model.entity.SharedPreferenceItemInfo import java.io.File diff --git a/app/src/main/java/com/wrbug/developerhelper/util/ResourceUtilsExt.kt b/app/src/main/java/com/wrbug/developerhelper/util/ResourceUtilsExt.kt index 64e9a95..4effcd8 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/ResourceUtilsExt.kt +++ b/app/src/main/java/com/wrbug/developerhelper/util/ResourceUtilsExt.kt @@ -7,6 +7,6 @@ fun Int.toResString(context: Context = BaseApp.instance): String { return context.getString(this) } -fun Any.getString(resId: Int): String { +fun getString(resId: Int): String { return BaseApp.instance.getString(resId) } \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/util/Runnable.kt b/app/src/main/java/com/wrbug/developerhelper/util/Runnable.kt deleted file mode 100644 index 9fde975..0000000 --- a/app/src/main/java/com/wrbug/developerhelper/util/Runnable.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.wrbug.developerhelper.util - -import java.lang.Runnable - -abstract class Runnable(vararg args: Any) : Runnable { - protected var args: Array = args - -} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/util/UpdateUtils.kt b/app/src/main/java/com/wrbug/developerhelper/util/UpdateUtils.kt new file mode 100644 index 0000000..3010f62 --- /dev/null +++ b/app/src/main/java/com/wrbug/developerhelper/util/UpdateUtils.kt @@ -0,0 +1,44 @@ +package com.wrbug.developerhelper.util + +import com.wrbug.developerhelper.commonutil.HttpUtil +import com.wrbug.developerhelper.commonutil.shell.Callback +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread +import com.wrbug.developerhelper.commonutil.OkhttpUtils +import com.wrbug.developerhelper.model.entity.VersionInfo +import org.jsoup.Jsoup +import java.lang.Exception + + +object UpdateUtils { + private const val URL = "https://www.coolapk.com/apk/com.wrbug.developerhelper" + fun checkUpdate(callback: Callback) { + doAsync { + try { + val document = Jsoup.connect(URL) + .sslSocketFactory(OkhttpUtils.createSSLSocketFactory()).get() + val versionName = document.getElementsByClass("list_app_info").text() ?: "" + val feature = document.getElementsByClass("apk_left_title_info").first().html().replace("
      ", "\n") + val size = document.getElementsByClass("apk_topba_message").html().split("/")[0].trim() + val updateTime = + document.getElementsByClass("apk_left_title_info")[2].html().split("
      ")[1].replace( + "更新时间:", + "" + ) + val info = VersionInfo() + info.versionName = versionName + info.feature = feature + info.size = size + info.updateDate = updateTime + info.downloadUrl = URL + uiThread { + callback.onSuccess(info) + } + } catch (e: Exception) { + uiThread { callback.onFailed() } + } + + } + + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher_notify_gray.png b/app/src/main/res/drawable-xxhdpi/ic_launcher_notify_gray.png new file mode 100644 index 0000000..53a2261 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher_notify_gray.png differ diff --git a/app/src/main/res/drawable/bg_primary_button.xml b/app/src/main/res/drawable/bg_primary_button.xml new file mode 100644 index 0000000..44e0836 --- /dev/null +++ b/app/src/main/res/drawable/bg_primary_button.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_down.xml b/app/src/main/res/drawable/ic_down.xml new file mode 100644 index 0000000..74051bb --- /dev/null +++ b/app/src/main/res/drawable/ic_down.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_ic_empty_666666.xml b/app/src/main/res/drawable/ic_ic_empty_666666.xml new file mode 100644 index 0000000..c4ac4bd --- /dev/null +++ b/app/src/main/res/drawable/ic_ic_empty_666666.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_shield_666666.xml b/app/src/main/res/drawable/ic_shield_666666.xml new file mode 100644 index 0000000..76f40cc --- /dev/null +++ b/app/src/main/res/drawable/ic_shield_666666.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_wifi_gray.xml b/app/src/main/res/drawable/ic_wifi_gray.xml new file mode 100644 index 0000000..ea24375 --- /dev/null +++ b/app/src/main/res/drawable/ic_wifi_gray.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_wifi_primary.xml b/app/src/main/res/drawable/ic_wifi_primary.xml new file mode 100644 index 0000000..ffbb502 --- /dev/null +++ b/app/src/main/res/drawable/ic_wifi_primary.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_xposed_custom_666666.xml b/app/src/main/res/drawable/ic_xposed_custom_666666.xml new file mode 100644 index 0000000..80f08b0 --- /dev/null +++ b/app/src/main/res/drawable/ic_xposed_custom_666666.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_database_edit.xml b/app/src/main/res/layout/activity_database_edit.xml new file mode 100644 index 0000000..bfacaa4 --- /dev/null +++ b/app/src/main/res/layout/activity_database_edit.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 737b01f..17742be 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -87,8 +87,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" - app:checkable="false" - app:checked="@{mainVm.openXposed}" app:src="@drawable/ic_xposed_666666" app:summary="@string/open_xposed_summary" app:title="@string/open_xposed_title" /> diff --git a/app/src/main/res/layout/activity_shell_app_manager.xml b/app/src/main/res/layout/activity_shell_app_manager.xml new file mode 100644 index 0000000..5b1cc8a --- /dev/null +++ b/app/src/main/res/layout/activity_shell_app_manager.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_xposed_setting.xml b/app/src/main/res/layout/activity_xposed_setting.xml new file mode 100644 index 0000000..8b6e95f --- /dev/null +++ b/app/src/main/res/layout/activity_xposed_setting.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_bottom_menu.xml b/app/src/main/res/layout/dialog_bottom_menu.xml new file mode 100644 index 0000000..d30dabc --- /dev/null +++ b/app/src/main/res/layout/dialog_bottom_menu.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_shell_app_info.xml b/app/src/main/res/layout/item_shell_app_info.xml new file mode 100644 index 0000000..3e0a3d0 --- /dev/null +++ b/app/src/main/res/layout/item_shell_app_info.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_tree_node_view.xml b/app/src/main/res/layout/item_tree_node_view.xml new file mode 100644 index 0000000..96984a1 --- /dev/null +++ b/app/src/main/res/layout/item_tree_node_view.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_hierarchy_tree.xml b/app/src/main/res/layout/layout_hierarchy_tree.xml new file mode 100644 index 0000000..486790d --- /dev/null +++ b/app/src/main/res/layout/layout_hierarchy_tree.xml @@ -0,0 +1,25 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/table_view_cell_layout.xml b/app/src/main/res/layout/table_view_cell_layout.xml new file mode 100644 index 0000000..0eb197d --- /dev/null +++ b/app/src/main/res/layout/table_view_cell_layout.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/table_view_column_header_layout.xml b/app/src/main/res/layout/table_view_column_header_layout.xml new file mode 100644 index 0000000..3af6e95 --- /dev/null +++ b/app/src/main/res/layout/table_view_column_header_layout.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/table_view_corner_layout.xml b/app/src/main/res/layout/table_view_corner_layout.xml new file mode 100644 index 0000000..ff802f1 --- /dev/null +++ b/app/src/main/res/layout/table_view_corner_layout.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/table_view_row_header_layout.xml b/app/src/main/res/layout/table_view_row_header_layout.xml new file mode 100644 index 0000000..bc81f3d --- /dev/null +++ b/app/src/main/res/layout/table_view_row_header_layout.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/view_app_data_info.xml b/app/src/main/res/layout/view_app_data_info.xml index 813138b..bca1bbc 100644 --- a/app/src/main/res/layout/view_app_data_info.xml +++ b/app/src/main/res/layout/view_app_data_info.xml @@ -78,10 +78,11 @@ android:paddingBottom="8dp"> @@ -89,7 +90,7 @@ @@ -101,10 +102,11 @@ android:paddingBottom="8dp"> diff --git a/app/src/main/res/layout/view_app_setting.xml b/app/src/main/res/layout/view_app_setting.xml new file mode 100644 index 0000000..307d8c8 --- /dev/null +++ b/app/src/main/res/layout/view_app_setting.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_float_custom.xml b/app/src/main/res/layout/view_float_custom.xml new file mode 100644 index 0000000..8bf0a7d --- /dev/null +++ b/app/src/main/res/layout/view_float_custom.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_setting_item.xml b/app/src/main/res/layout/view_setting_item.xml index 6da7a77..03a5630 100644 --- a/app/src/main/res/layout/view_setting_item.xml +++ b/app/src/main/res/layout/view_setting_item.xml @@ -58,4 +58,14 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index cb02cbc..449df2b 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -14,4 +14,6 @@ #8a000000 #666666 #eaeaea + #fff + @android:color/holo_red_light diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 6ff3b0e..f833c81 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -5,4 +5,6 @@ 16dp 8dp 8dp + 36dp + 55dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 00dca17..059db9b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,10 +14,10 @@ 高级功能设置 开启无障碍辅助 开启悬浮窗权限 - 推荐开启,用于获取应用数据库等信息,不开启部分信息无法显示 + 推荐开启,用于获取应用数据库等信息,不开启部分信息无法显示,Xposed无法使用 开启root权限 - 启用xposed特性 - 开发中 + 启用Xposed特性 + 支持脱壳,更多功能持续开发中,点击进入管理 辅助功能用于界面布局分析等 关于 软件开发中,交流群:627962572 @@ -50,4 +50,37 @@ 请稍后... 正在获取应用信息 请开启无障碍功能 + + 该表数据为空 + 获取数据库信息失败 + 获取中... + 数据库列表 + + 应用设置 + 备份Apk文件 + 备份data目录 + 重启应用 + 停止应用 + 清理应用数据 + 卸载应用 + 请开启root权限 + 部分功能需要root权限,请打开后使用! + 是否清除数据和缓存? + 清理完成 + 是否停止该应用? + 是否重启该应用? + 重启失败 + 备份成功,文件已备份到内部存储下com.wrbug.developerHelper目录中 + 备份失败 + 备份成功,文件已备份到内部存储下com.wrbug.developerHelper目录中,是否立即分享给好友? + 分享失败 + 导出脱壳数据 + 正在打包文件 + 导出失败! + 无脱壳文件 + Xposed设置 + 管理、移除需要脱壳的应用 + 脱壳应用管理 + 移除该项 + 请先开启Xposed设置 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 4e7f47a..ddc3fd5 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -11,7 +11,7 @@ - diff --git a/app/src/main/res/xml/filepaths.xml b/app/src/main/res/xml/filepaths.xml new file mode 100644 index 0000000..94d126f --- /dev/null +++ b/app/src/main/res/xml/filepaths.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/basecommon/build.gradle b/basecommon/build.gradle index d50425b..922788e 100644 --- a/basecommon/build.gradle +++ b/basecommon/build.gradle @@ -24,15 +24,17 @@ android { } def lifecycle_version = "2.0.0" dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.appcompat:appcompat:1.0.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.google.android.material:material:1.1.0-alpha01' - implementation "android.arch.lifecycle:extensions:1.1.1" + implementation 'android.arch.lifecycle:extensions:1.1.1' annotationProcessor "android.arch.lifecycle:compiler:1.1.1" + implementation project(':commonutil') + implementation project(':commonwidget') } repositories { mavenCentral() diff --git a/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/AppCompatActivityExt.kt b/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/AppCompatActivityExt.kt index 81f90e3..e81bde5 100644 --- a/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/AppCompatActivityExt.kt +++ b/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/AppCompatActivityExt.kt @@ -47,4 +47,6 @@ private inline fun FragmentManager.transact(action: FragmentTransaction.() -> Un beginTransaction().apply { action() }.commit() -} \ No newline at end of file +} + + diff --git a/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/BaseActivity.kt b/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/BaseActivity.kt index 5a13be1..3dd8673 100644 --- a/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/BaseActivity.kt +++ b/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/BaseActivity.kt @@ -1,13 +1,23 @@ package com.wrbug.developerhelper.basecommon +import android.annotation.TargetApi +import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity import com.google.android.material.snackbar.Snackbar +import java.util.ArrayList abstract class BaseActivity : AppCompatActivity() { private lateinit var toastRootView: View protected lateinit var context: BaseActivity + private var mPermissionCallback: PermissionCallback? = null + + companion object { + private const val PERMISSION_REQUEST_CODE = 0xAADF1 + } + override fun onCreate(savedInstanceState: Bundle?) { context = this super.onCreate(savedInstanceState) @@ -25,4 +35,52 @@ abstract class BaseActivity : AppCompatActivity() { fun showSnack(id: Int) { Snackbar.make(toastRootView, id, Snackbar.LENGTH_SHORT).show() } + + + @TargetApi(Build.VERSION_CODES.M) + fun requestPermission(permissions: Array, callback: PermissionCallback) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + callback.granted() + return + } + val list = ArrayList() + for (permission in permissions) { + val hasPermission = checkSelfPermission(permission) + if (hasPermission != PackageManager.PERMISSION_GRANTED) { + list.add(permission) + } + } + if (list.isEmpty()) { + callback.granted() + return + } + mPermissionCallback = callback + requestPermissions(list.toTypedArray(), PERMISSION_REQUEST_CODE) + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + if (requestCode == PERMISSION_REQUEST_CODE) { + val list = ArrayList() + for (i in grantResults.indices) { + if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { + list.add(permissions[i]) + } + } + if (list.isEmpty()) { + mPermissionCallback?.granted() + return + } + mPermissionCallback?.denied(list) + return + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + } + + abstract class PermissionCallback { + abstract fun granted() + + open fun denied(permissions: List) { + + } + } } \ No newline at end of file diff --git a/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/BaseApp.kt b/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/BaseApp.kt index b247c37..3239594 100644 --- a/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/BaseApp.kt +++ b/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/BaseApp.kt @@ -14,4 +14,9 @@ abstract class BaseApp : Application() { abstract fun showToast(msg: String) + + fun showToast(id: Int){ + showToast(getString(id)) + } + } \ No newline at end of file diff --git a/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/ToastExts.kt b/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/ToastExts.kt index 00e8814..bd5c601 100644 --- a/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/ToastExts.kt +++ b/basecommon/src/main/java/com/wrbug/developerhelper/basecommon/ToastExts.kt @@ -1,14 +1,33 @@ package com.wrbug.developerhelper.basecommon -import android.app.Activity -import android.widget.Toast +import android.app.Application +import android.os.Handler +import android.os.Looper +import androidx.annotation.StringRes +import com.wrbug.developerhelper.commonwidget.flexibletoast.FlexibleToast -fun Activity.showToast(msg: CharSequence?) { - Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() - -} +//fun Activity.showToast(msg: CharSequence?) { +// Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() +// +//} fun showToast(msg: CharSequence) { BaseApp.instance.showToast(msg.toString()) -} \ No newline at end of file +} + +fun showToast(@StringRes id: Int) { + BaseApp.instance.showToast(id) +} + +fun Application.showToast(msg: CharSequence) { + val appHandler = Handler() + val flexibleToast = FlexibleToast(this) + val builder = FlexibleToast.Builder(this).setGravity(FlexibleToast.GRAVITY_BOTTOM) + builder.setSecondText(msg.toString()) + if (Looper.myLooper() !== Looper.getMainLooper()) { + appHandler.post { flexibleToast.toastShow(builder) } + } else { + flexibleToast.toastShow(builder) + } +} diff --git a/commonutil/.gitignore b/commonutil/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/commonutil/.gitignore @@ -0,0 +1 @@ +/build diff --git a/commonutil/build.gradle b/commonutil/build.gradle new file mode 100644 index 0000000..b18203f --- /dev/null +++ b/commonutil/build.gradle @@ -0,0 +1,45 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 28 + + + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'com.jaredrummler:android-shell:1.0.0' + implementation 'androidx.appcompat:appcompat:1.0.2' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.0-alpha3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha3' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation project(':mmkv') + implementation "org.jetbrains.anko:anko-commons:0.10.8" + implementation 'com.google.code.gson:gson:2.8.5' + implementation 'com.elvishew:xlog:1.6.1' + api 'com.squareup.okhttp3:okhttp:3.12.1' + api group: 'org.jsoup', name: 'jsoup', version: '1.11.3' +} +repositories { + mavenCentral() +} diff --git a/commonutil/proguard-rules.pro b/commonutil/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/commonutil/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/commonutil/src/androidTest/java/com/wrbug/developerhelper/commonutil/ExampleInstrumentedTest.java b/commonutil/src/androidTest/java/com/wrbug/developerhelper/commonutil/ExampleInstrumentedTest.java new file mode 100644 index 0000000..87b387b --- /dev/null +++ b/commonutil/src/androidTest/java/com/wrbug/developerhelper/commonutil/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.wrbug.developerhelper.commonutil; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.wrbug.developerhelper.commonutil.test", appContext.getPackageName()); + } +} diff --git a/commonutil/src/main/AndroidManifest.xml b/commonutil/src/main/AndroidManifest.xml new file mode 100644 index 0000000..68282c7 --- /dev/null +++ b/commonutil/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/app/src/main/java/com/wrbug/developerhelper/util/ApkUtils.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ApkUtils.kt similarity index 82% rename from app/src/main/java/com/wrbug/developerhelper/util/ApkUtils.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ApkUtils.kt index af8b3dc..e1c1ef0 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/ApkUtils.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ApkUtils.kt @@ -1,8 +1,8 @@ -package com.wrbug.developerhelper.util +package com.wrbug.developerhelper.commonutil import android.content.Context import android.content.pm.PackageManager -import com.wrbug.developerhelper.model.entity.ApkSignInfo +import com.wrbug.developerhelper.commonutil.entity.ApkSignInfo import java.security.MessageDigest @@ -12,8 +12,12 @@ object ApkUtils { val packageInfo = context.packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) val cert = packageInfo.signatures[0].toByteArray() val apkSignInfo = ApkSignInfo() - apkSignInfo.sha1 = byte2String(getSha1(cert)) - apkSignInfo.md5 = byte2String(getMd5(cert)) + apkSignInfo.sha1 = byte2String( + getSha1(cert) + ) + apkSignInfo.md5 = byte2String( + getMd5(cert) + ) return apkSignInfo } diff --git a/app/src/main/java/com/wrbug/developerhelper/util/AppInfoManager.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/AppInfoManager.kt similarity index 80% rename from app/src/main/java/com/wrbug/developerhelper/util/AppInfoManager.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/AppInfoManager.kt index 809b25f..ab0b1d1 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/AppInfoManager.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/AppInfoManager.kt @@ -1,9 +1,7 @@ -package com.wrbug.developerhelper.util +package com.wrbug.developerhelper.commonutil -import android.content.SharedPreferences -import com.wrbug.developerhelper.basecommon.BaseApp -import com.wrbug.developerhelper.model.entity.ApkInfo -import com.wrbug.developerhelper.shell.ShellManager +import com.wrbug.developerhelper.commonutil.entity.ApkInfo +import com.wrbug.developerhelper.commonutil.shell.ShellManager import java.io.File object AppInfoManager { @@ -14,7 +12,7 @@ object AppInfoManager { */ fun getAllApps(): HashMap { val apkMap = HashMap() - val pManager = BaseApp.instance.packageManager + val pManager = CommonUtils.application.packageManager // 获取手机内所有应用 val paklist = pManager.getInstalledPackages(0) for (packageInfo in paklist) { @@ -47,4 +45,6 @@ object AppInfoManager { } return files.toTypedArray() } + + } \ No newline at end of file diff --git a/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/AppManagerUtils.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/AppManagerUtils.kt new file mode 100644 index 0000000..1a50b09 --- /dev/null +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/AppManagerUtils.kt @@ -0,0 +1,23 @@ +package com.wrbug.developerhelper.commonutil + +import android.content.Context +import android.content.Intent +import android.net.Uri +import com.wrbug.developerhelper.commonutil.shell.ShellManager + + +object AppManagerUtils { + fun uninstallApp(context: Context, packageName: String) { + val uri = Uri.parse("package:$packageName") + val intent = Intent(Intent.ACTION_DELETE, uri) + context.startActivity(intent) + } + + fun clearAppData(packageName: String): Boolean { + return ShellManager.clearAppData(packageName) + } + + fun forceStopApp(packageName: String): Boolean { + return ShellManager.forceStopApp(packageName) + } +} \ No newline at end of file diff --git a/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/Base64.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/Base64.kt new file mode 100644 index 0000000..4443e3e --- /dev/null +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/Base64.kt @@ -0,0 +1,115 @@ +package com.wrbug.developerhelper.commonutil + +object Base64 { + + private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" + .toCharArray() + private val codes = ByteArray(256) + /* 将原始数据编码为base64编码 + */ + fun encodeAsString(data: ByteArray): String { + return String(encode(data)) + } + + fun encode(data: ByteArray): CharArray { + val out = CharArray((data.size + 2) / 3 * 4) + var i = 0 + var index = 0 + while (i < data.size) { + var quad = false + var trip = false + var value = 0xFF and data[i].toInt() + value = value shl 8 + if (i + 1 < data.size) { + value = value or (0xFF and data[i + 1].toInt()) + trip = true + } + value = value shl 8 + if (i + 2 < data.size) { + value = value or (0xFF and data[i + 2].toInt()) + quad = true + } + out[index + 3] = alphabet[if (quad) value and 0x3F else 64] + value = value shr 6 + out[index + 2] = alphabet[if (trip) value and 0x3F else 64] + value = value shr 6 + out[index + 1] = alphabet[value and 0x3F] + value = value shr 6 + out[index + 0] = alphabet[value and 0x3F] + i += 3 + index += 4 + } + return out + } + + /** + * 将base64编码的数据解码成原始数据 + */ + fun decode(data: CharArray): ByteArray { + var len = (data.size + 3) / 4 * 3 + if (data.size> 0 && data[data.size - 1] == '=') + --len + if (data.size> 1 && data[data.size - 2] == '=') + --len + val out = ByteArray(len) + var shift = 0 + var accum = 0 + var index = 0 + for (ix in data.indices) { + val value = codes[data[ix].toInt() and 0xFF].toInt() + if (value>= 0) { + accum = accum shl 6 + shift += 6 + accum = accum or value + if (shift>= 8) { + shift -= 8 + out[index++] = (accum shr shift and 0xff).toByte() + } + } + } + if (index != out.size) + throw Error("miscalculated data length!") + return out + } + + fun decode(data: String?): String { + if (data.isNullOrEmpty()) { + return "" + } + return String(decode(data.toCharArray())) + } + + init { + for (i in 0..255) + codes[i] = -1 + run { + var i: Int = 'A'.toInt() + while (i <= 'Z'.toInt()) { + codes[i] = (i - 'A'.toInt()).toByte() + i++ + } + } + run { + var i: Int = 'a'.toInt() + while (i <= 'z'.toInt()) { + codes[i] = (26 + i - 'a'.toInt()).toByte() + i++ + } + } + var i: Int = '0'.toInt() + while (i <= '9'.toInt()) { + codes[i] = (52 + i - '0'.toInt()).toByte() + i++ + } + codes['+'.toInt()] = 62 + codes['/'.toInt()] = 63 + } + // public static void main(String[] args) throws Exception { + // // 加密成base64 + // String strSrc = "林"; + // String strOut = new String(Base64.encode(strSrc.getBytes("GB18030"))); + // System.out.println(strOut); + // + // } + +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/util/ClipboardUtils.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ClipboardUtils.kt similarity index 95% rename from app/src/main/java/com/wrbug/developerhelper/util/ClipboardUtils.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ClipboardUtils.kt index c5d2312..79e719d 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/ClipboardUtils.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ClipboardUtils.kt @@ -1,4 +1,4 @@ -package com.wrbug.developerhelper.util +package com.wrbug.developerhelper.commonutil import android.content.ClipData import android.content.ClipboardManager diff --git a/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/CommonUtils.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/CommonUtils.kt new file mode 100644 index 0000000..f112bd2 --- /dev/null +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/CommonUtils.kt @@ -0,0 +1,11 @@ +package com.wrbug.developerhelper.commonutil + +import android.app.Application +import android.content.Context + +object CommonUtils { + lateinit var application: Application + fun register(ctx: Context) { + application = ctx.applicationContext as Application + } +} \ No newline at end of file diff --git a/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/HttpUtil.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/HttpUtil.kt new file mode 100644 index 0000000..40497b6 --- /dev/null +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/HttpUtil.kt @@ -0,0 +1,73 @@ +package com.wrbug.developerhelper.commonutil + +import okhttp3.* + +import java.io.IOException +import java.net.URLEncoder +import java.util.HashMap + +object HttpUtil { + + fun post(url: String, headers: Headers? = null, param: Param): Response? { + val client = OkhttpUtils.client + val mediaType = MediaType.parse("application/x-www-form-urlencoded") + val body = RequestBody.create(mediaType, param.toString()) + val builder = Request.Builder() + .url(url) + .post(body) + .addHeader("content-type", "application/x-www-form-urlencoded") + if (headers != null) { + builder.headers(headers) + } + try { + return client.newCall(builder.build()).execute() + } catch (e: IOException) { + e.printStackTrace() + } + + return null + } + + operator fun get(url: String, headers: Headers? = null, param: Param): Response? { + var url = url + val client = OkhttpUtils.client + url = url + "?" + param.toString() + val builder = Request.Builder() + .url(url) + .get() + if (headers != null) { + builder.headers(headers) + } + try { + return client.newCall(builder.build()).execute() + } catch (e: IOException) { + e.printStackTrace() + } + + return null + } + + class Param : HashMap() { + + override fun toString(): String { + val builder = StringBuilder() + if (containsKey("key")) { + remove("key") + } + for ((key, value) in this) { + builder.append(key).append("=").append(urlEncode(value)).append("&") + } + if (builder.isNotEmpty()) { + builder.deleteCharAt(builder.length - 1) + } + return builder.toString() + } + + fun urlEncode(str: Any?): String { + return if (str == null) { + "" + } else URLEncoder.encode(str.toString()) + } + } + +} diff --git a/app/src/main/java/com/wrbug/developerhelper/util/JsonHelper.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/JsonHelper.kt similarity index 92% rename from app/src/main/java/com/wrbug/developerhelper/util/JsonHelper.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/JsonHelper.kt index 4ec9dda..62be985 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/JsonHelper.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/JsonHelper.kt @@ -1,4 +1,4 @@ -package com.wrbug.developerhelper.util +package com.wrbug.developerhelper.commonutil import com.google.gson.Gson import com.google.gson.JsonElement @@ -17,7 +17,10 @@ object JsonHelper { fun toJson(o: Any): String { - return toJson(gson, o) + return toJson( + gson, + o + ) } fun toJson(gson: Gson, o: Any?): String { diff --git a/app/src/main/java/com/wrbug/developerhelper/util/LogUtilsExt.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/LogUtilsExt.kt similarity index 73% rename from app/src/main/java/com/wrbug/developerhelper/util/LogUtilsExt.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/LogUtilsExt.kt index 3add9e8..ef9a1a4 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/LogUtilsExt.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/LogUtilsExt.kt @@ -1,6 +1,7 @@ -package com.wrbug.developerhelper.util +package com.wrbug.developerhelper.commonutil import com.elvishew.xlog.XLog +import com.wrbug.developerhelper.commonutil.JsonHelper fun Any.print() { diff --git a/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/OkhttpUtils.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/OkhttpUtils.kt new file mode 100644 index 0000000..06b7311 --- /dev/null +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/OkhttpUtils.kt @@ -0,0 +1,87 @@ +package com.wrbug.developerhelper.commonutil + + +import okhttp3.Interceptor +import okhttp3.OkHttpClient + +import javax.net.ssl.* +import java.net.Proxy +import java.security.SecureRandom +import java.security.cert.CertificateException +import java.security.cert.X509Certificate +import java.util.concurrent.TimeUnit + +object OkhttpUtils { + private val CONNECT_TIMEOUT_SECOND: Long = 5 + + private var sOkHttpClient: OkHttpClient? = null + + + val client: OkHttpClient by lazy { + getClientFollowRedirects(true) + } + + fun getClientFollowRedirects(follow: Boolean): OkHttpClient { + val mBuilder = OkHttpClient.Builder() + mBuilder.followRedirects(follow).followSslRedirects(follow) + mBuilder.connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + return getClient(mBuilder) + } + + fun getProxyClient(proxy: Proxy?): OkHttpClient { + val builder = OkHttpClient.Builder() + builder.sslSocketFactory(createSSLSocketFactory()!!, TrustAllManager()) + builder.hostnameVerifier(TrustAllHostnameVerifier()) + if (proxy != null) { + builder.proxy(proxy) + } + return builder.build() + } + + fun getClient(builder: OkHttpClient.Builder): OkHttpClient { + builder.sslSocketFactory(createSSLSocketFactory()!!, TrustAllManager()) + builder.hostnameVerifier(TrustAllHostnameVerifier()) + return builder.build() + } + + + fun createSSLSocketFactory(): SSLSocketFactory? { + + var sSLSocketFactory: SSLSocketFactory? = null + + try { + val sc = SSLContext.getInstance("TLS") + sc.init( + null, arrayOf
      (TrustAllManager()), + SecureRandom() + ) + sSLSocketFactory = sc.socketFactory + } catch (e: Exception) { + } + + return sSLSocketFactory + } + + + private class TrustAllManager : X509TrustManager { + @Throws(CertificateException::class) + override fun checkClientTrusted(chain: Array, authType: String) { + } + + @Throws(CertificateException::class) + override fun checkServerTrusted(chain: Array, authType: String) { + } + + override fun getAcceptedIssuers(): Array { + return arrayOf() + } + } + + private class TrustAllHostnameVerifier : HostnameVerifier { + override fun verify(hostname: String, session: SSLSession): Boolean { + return true + } + } +} diff --git a/app/src/main/java/com/wrbug/developerhelper/util/ShaUtils.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ShaUtils.kt similarity index 86% rename from app/src/main/java/com/wrbug/developerhelper/util/ShaUtils.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ShaUtils.kt index e86896b..3429762 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/ShaUtils.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ShaUtils.kt @@ -1,8 +1,6 @@ -package com.wrbug.developerhelper.util +package com.wrbug.developerhelper.commonutil -import android.provider.SyncStateContract.Helpers.update import java.security.MessageDigest -import kotlin.experimental.and object ShaUtils { diff --git a/app/src/main/java/com/wrbug/developerhelper/util/ShellUtils.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ShellUtils.kt similarity index 80% rename from app/src/main/java/com/wrbug/developerhelper/util/ShellUtils.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ShellUtils.kt index caa12b6..8c36c0f 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/ShellUtils.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ShellUtils.kt @@ -1,14 +1,11 @@ -package com.wrbug.developerhelper.util +package com.wrbug.developerhelper.commonutil import com.jaredrummler.android.shell.CommandResult import com.jaredrummler.android.shell.Shell -import com.wrbug.developerhelper.model.mmkv.ConfigKv -import com.wrbug.developerhelper.model.mmkv.manager.MMKVManager +import com.wrbug.developerhelper.mmkv.ConfigKv +import com.wrbug.developerhelper.mmkv.manager.MMKVManager import org.jetbrains.anko.doAsync -import java.util.concurrent.Executor -import java.util.concurrent.Executors - object ShellUtils { val configKv = MMKVManager.get(ConfigKv::class.java) fun run(cmds: Array, callback: ShellResultCallback) { @@ -23,7 +20,7 @@ object ShellUtils { } fun runWithSu(cmds: Array, callback: ShellResultCallback) { - if (!configKv.getOpenRoot()) { + if (!configKv.isOpenRoot()) { callback.onError("未开启root权限") return } @@ -39,12 +36,16 @@ object ShellUtils { fun runWithSu(vararg cmds: String): CommandResult { - if (configKv.getOpenRoot().not()) { + if (configKv.isOpenRoot().not()) { return CommandResult(arrayListOf("未开启root权限"), arrayListOf("未开启root权限"), 1) } return Shell.SU.run(*cmds) } + fun isRoot(): Boolean { + return Shell.SU.available() + } + abstract class ShellResultCallback(vararg args: Any) { protected var args = args open fun onComplete(result: CommandResult) { diff --git a/app/src/main/java/com/wrbug/developerhelper/util/StringConvertUtils.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/StringConvertUtils.kt similarity index 76% rename from app/src/main/java/com/wrbug/developerhelper/util/StringConvertUtils.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/StringConvertUtils.kt index 72c3ea4..4aca828 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/StringConvertUtils.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/StringConvertUtils.kt @@ -1,4 +1,4 @@ -package com.wrbug.developerhelper.util +package com.wrbug.developerhelper.commonutil import java.math.BigDecimal import java.math.BigInteger @@ -13,19 +13,21 @@ fun String.isInt(): Boolean { } fun String.isDecimal(): Boolean { - if (this=="0") { + if (this == "0") { return true } val pattern = Pattern.compile("-?([0-9]+\\.0*)?[1-9][0-9]*$") return pattern.matcher(this).find() } + fun String.isNumber(): Boolean { - if (this=="0") { + if (this == "0") { return true } val pattern = Pattern.compile("-?[1-9][0-9]*$") return pattern.matcher(this).find() } + fun String.isBoolean(): Boolean { return this.toLowerCase() == "true" || this.toLowerCase() == "false" } @@ -52,3 +54,17 @@ fun String.toLong(): Long { return 0L } + +fun String.toFloat(): Float { + if (isDecimal()) { + return BigDecimal(this).toFloat() + } + return 0F +} + +fun String.toDouble(): Double { + if (isDecimal()) { + return BigDecimal(this).toDouble() + } + return 0.0 +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/util/UiUtils.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/UiUtils.kt similarity index 72% rename from app/src/main/java/com/wrbug/developerhelper/util/UiUtils.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/UiUtils.kt index ce897c8..be487bf 100644 --- a/app/src/main/java/com/wrbug/developerhelper/util/UiUtils.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/UiUtils.kt @@ -1,11 +1,10 @@ -package com.wrbug.developerhelper.util +package com.wrbug.developerhelper.commonutil import android.app.Dialog import android.content.Context import android.util.TypedValue import android.view.View import androidx.fragment.app.Fragment -import com.wrbug.developerhelper.basecommon.BaseApp fun Context.dp2px(dpVal: Float): Int = UiUtils.dp2px(this, dpVal) fun Fragment.dp2px(dpVal: Float): Int = UiUtils.dp2px(activity!!, dpVal) @@ -14,7 +13,7 @@ fun View.dp2px(dpVal: Float): Int = UiUtils.dp2px(context, dpVal) object UiUtils { - fun dp2px(context: Context = BaseApp.instance, dpVal: Float): Int { + fun dp2px(context: Context = CommonUtils.application, dpVal: Float): Int { return TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, dpVal, @@ -22,23 +21,23 @@ object UiUtils { ).toInt() } - fun sp2px(context: Context = BaseApp.instance, spVal: Float): Int { + fun sp2px(context: Context = CommonUtils.application, spVal: Float): Int { return TypedValue.applyDimension(2, spVal, context.resources?.displayMetrics).toInt() } - fun px2dp(context: Context = BaseApp.instance, pxVal: Float): Float { + fun px2dp(context: Context = CommonUtils.application, pxVal: Float): Float { val scale = context.resources?.displayMetrics?.density!! return pxVal / scale } - fun px2sp(context: Context = BaseApp.instance, pxVal: Float): Float { + fun px2sp(context: Context = CommonUtils.application, pxVal: Float): Float { return pxVal / context.resources?.displayMetrics?.scaledDensity!! } /** * 获取屏幕高度 */ - fun getDeviceHeight(context: Context = BaseApp.instance): Int { + fun getDeviceHeight(context: Context = CommonUtils.application): Int { val dm = context.resources?.displayMetrics return dm?.heightPixels ?: 0 } @@ -46,7 +45,7 @@ object UiUtils { /** * 获取屏幕宽度 */ - fun getDeviceWidth(context: Context = BaseApp.instance): Int { + fun getDeviceWidth(context: Context = CommonUtils.application): Int { val dm = context.resources?.displayMetrics return dm?.widthPixels ?: 0 } @@ -54,7 +53,7 @@ object UiUtils { /** * 获取状态栏的高度 */ - fun getStatusHeight(context: Context = BaseApp.instance): Int { + fun getStatusHeight(context: Context = CommonUtils.application): Int { var result: Int? = 10 val resourceId = context.resources?.getIdentifier("status_bar_height", "dimen", "android") ?: 0 diff --git a/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ZipUtils.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ZipUtils.kt new file mode 100644 index 0000000..eb380ef --- /dev/null +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/ZipUtils.kt @@ -0,0 +1,193 @@ +package com.wrbug.developerhelper.commonutil + +import java.io.* +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream + +object ZipUtils { + fun zip(src: File, outFile: File) { + //提供了一个数据项压缩成一个ZIP归档输出流 + var out: ZipOutputStream? = null + try { + out = ZipOutputStream(FileOutputStream(outFile)) + //如果此文件是一个文件,否则为false。 + if (src.isFile) { + zipFileOrDirectory(out, src, "") + } else { + //返回一个文件或空阵列。 + val entries = src.listFiles() + for (i in entries!!.indices) { + // 递归压缩,更新curPaths + zipFileOrDirectory(out, entries[i], "") + } + } + } catch (ex: IOException) { + ex.printStackTrace() + } finally { + //关闭输出流 + if (out != null) { + try { + out.close() + } catch (ex: IOException) { + ex.printStackTrace() + } + + } + } + } + + fun zip(src: File): File? { + val outFile = File(src.parent, src.name + ".zip")//源文件或者目录 + zip(src, outFile) + return outFile + } + + @Throws(IOException::class) + private fun zipFileOrDirectory( + out: ZipOutputStream, + fileOrDirectory: File, curPath: String + ) { + //从文件中读取字节的输入流 + var fileInputStream: FileInputStream? = null + try { + //如果此文件是一个目录,否则返回false。 + if (!fileOrDirectory.isDirectory) { + // 压缩文件 + val buffer = ByteArray(4096) + fileInputStream = FileInputStream(fileOrDirectory) + //实例代表一个条目内的ZIP归档 + val entry = ZipEntry(curPath + fileOrDirectory.name) + //条目的信息写入底层流 + out.putNextEntry(entry) + var bytesRead: Int = fileInputStream.read(buffer) + while (bytesRead != -1) { + out.write(buffer, 0, bytesRead) + bytesRead = fileInputStream.read(buffer) + } + out.closeEntry() + } else { + // 压缩目录 + val entries = fileOrDirectory.listFiles() + for (i in entries!!.indices) { + // 递归压缩,更新curPaths + zipFileOrDirectory( + out, entries[i], curPath + + fileOrDirectory.name + "/" + ) + } + } + } catch (ex: IOException) { + ex.printStackTrace() + // throw ex; + } finally { + if (fileInputStream != null) { + try { + fileInputStream.close() + } catch (ex: IOException) { + ex.printStackTrace() + } + + } + } + } + + @Throws(IOException::class) + fun unzip(zipFileName: String, outputDirectory: String) { + var zipFile: ZipFile? = null + try { + zipFile = ZipFile(zipFileName) + val e = zipFile.entries() + var zipEntry: ZipEntry? = null + val dest = File(outputDirectory) + dest.mkdirs() + while (e.hasMoreElements()) { + zipEntry = e.nextElement() as ZipEntry + val entryName = zipEntry.name + var inputStream: InputStream? = null + var out: FileOutputStream? = null + try { + if (zipEntry.isDirectory) { + var name = zipEntry.name + name = name.substring(0, name.length - 1) + val f = File( + outputDirectory + File.separator + + name + ) + f.mkdirs() + } else { + var index = entryName.lastIndexOf("\\") + if (index != -1) { + val df = File( + outputDirectory + File.separator + + entryName.substring(0, index) + ) + df.mkdirs() + } + index = entryName.lastIndexOf("/") + if (index != -1) { + val df = File( + outputDirectory + File.separator + + entryName.substring(0, index) + ) + df.mkdirs() + } + val f = File( + outputDirectory + File.separator + + zipEntry.name + ) + // f.createNewFile(); + inputStream = zipFile.getInputStream(zipEntry) + out = FileOutputStream(f) + val by = ByteArray(1024) + var c: Int = inputStream?.read(by) ?: -1 + while (c != -1) { + out.write(by, 0, c) + c = inputStream?.read(by) ?: -1 + } + out.flush() + } + } catch (ex: IOException) { + ex.printStackTrace() + throw IOException("解压失败:" + ex.toString()) + } finally { + if (inputStream != null) { + try { + inputStream.close() + } catch (ex: IOException) { + } + + } + if (out != null) { + try { + out.close() + } catch (ex: IOException) { + } + + } + } + } + } catch (ex: IOException) { + ex.printStackTrace() + throw IOException("解压失败:" + ex.toString()) + } finally { + if (zipFile != null) { + try { + zipFile.close() + } catch (ex: IOException) { + } + + } + } + } +} + + +fun File.zip(): File? { + return ZipUtils.zip(this) +} + + +fun File.zip(outFile: File) { + return ZipUtils.zip(this, outFile) +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/model/entity/ApkInfo.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/ApkInfo.kt similarity index 79% rename from app/src/main/java/com/wrbug/developerhelper/model/entity/ApkInfo.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/ApkInfo.kt index 700f235..5e8b861 100644 --- a/app/src/main/java/com/wrbug/developerhelper/model/entity/ApkInfo.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/ApkInfo.kt @@ -1,11 +1,11 @@ -package com.wrbug.developerhelper.model.entity +package com.wrbug.developerhelper.commonutil.entity import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.graphics.drawable.Drawable import android.os.Parcel import android.os.Parcelable -import com.wrbug.developerhelper.basecommon.BaseApp +import com.wrbug.developerhelper.commonutil.CommonUtils class ApkInfo(val packageInfo: PackageInfo, val applicationInfo: ApplicationInfo) : Parcelable { @@ -15,11 +15,11 @@ class ApkInfo(val packageInfo: PackageInfo, val applicationInfo: ApplicationInfo ) fun getIco(): Drawable { - return applicationInfo.loadIcon(BaseApp.instance.packageManager) + return applicationInfo.loadIcon(CommonUtils.application.packageManager) } fun getAppName(): String { - val label = BaseApp.instance.packageManager.getApplicationLabel(applicationInfo) + val label = CommonUtils.application.packageManager.getApplicationLabel(applicationInfo) return if (label == null) "" else label.toString() } diff --git a/app/src/main/java/com/wrbug/developerhelper/model/entity/ApkSignInfo.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/ApkSignInfo.kt similarity index 52% rename from app/src/main/java/com/wrbug/developerhelper/model/entity/ApkSignInfo.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/ApkSignInfo.kt index 9aef60c..4b3f2ed 100644 --- a/app/src/main/java/com/wrbug/developerhelper/model/entity/ApkSignInfo.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/ApkSignInfo.kt @@ -1,4 +1,4 @@ -package com.wrbug.developerhelper.model.entity +package com.wrbug.developerhelper.commonutil.entity class ApkSignInfo { var md5 = "" diff --git a/app/src/main/java/com/wrbug/developerhelper/model/entity/FragmentInfo.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/FragmentInfo.kt similarity index 97% rename from app/src/main/java/com/wrbug/developerhelper/model/entity/FragmentInfo.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/FragmentInfo.kt index a134f38..ab23081 100644 --- a/app/src/main/java/com/wrbug/developerhelper/model/entity/FragmentInfo.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/FragmentInfo.kt @@ -1,4 +1,4 @@ -package com.wrbug.developerhelper.model.entity +package com.wrbug.developerhelper.commonutil.entity import android.os.Parcel import android.os.Parcelable diff --git a/app/src/main/java/com/wrbug/developerhelper/model/entity/LsFileInfo.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/LsFileInfo.kt similarity index 66% rename from app/src/main/java/com/wrbug/developerhelper/model/entity/LsFileInfo.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/LsFileInfo.kt index 5a284a6..7ec45dc 100644 --- a/app/src/main/java/com/wrbug/developerhelper/model/entity/LsFileInfo.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/LsFileInfo.kt @@ -1,4 +1,4 @@ -package com.wrbug.developerhelper.model.entity +package com.wrbug.developerhelper.commonutil.entity class LsFileInfo { var type = "" diff --git a/app/src/main/java/com/wrbug/developerhelper/model/entity/TopActivityInfo.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/TopActivityInfo.kt similarity index 94% rename from app/src/main/java/com/wrbug/developerhelper/model/entity/TopActivityInfo.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/TopActivityInfo.kt index 6acb4f7..d5e415a 100644 --- a/app/src/main/java/com/wrbug/developerhelper/model/entity/TopActivityInfo.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/entity/TopActivityInfo.kt @@ -1,4 +1,4 @@ -package com.wrbug.developerhelper.model.entity +package com.wrbug.developerhelper.commonutil.entity import android.os.Parcel import android.os.Parcelable diff --git a/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/shell/Callback.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/shell/Callback.kt new file mode 100644 index 0000000..4bc41c9 --- /dev/null +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/shell/Callback.kt @@ -0,0 +1,6 @@ +package com.wrbug.developerhelper.commonutil.shell + +interface Callback { + fun onSuccess(data: T) + fun onFailed(msg: String = "") {} +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/shell/ShellManager.kt b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/shell/ShellManager.kt similarity index 81% rename from app/src/main/java/com/wrbug/developerhelper/shell/ShellManager.kt rename to commonutil/src/main/java/com/wrbug/developerhelper/commonutil/shell/ShellManager.kt index 1804a02..801eb3c 100644 --- a/app/src/main/java/com/wrbug/developerhelper/shell/ShellManager.kt +++ b/commonutil/src/main/java/com/wrbug/developerhelper/commonutil/shell/ShellManager.kt @@ -1,12 +1,11 @@ -package com.wrbug.developerhelper.shell +package com.wrbug.developerhelper.commonutil.shell import com.jaredrummler.android.shell.CommandResult -import com.wrbug.developerhelper.basecommon.BaseApp -import com.wrbug.developerhelper.model.entity.FragmentInfo -import com.wrbug.developerhelper.model.entity.LsFileInfo -import com.wrbug.developerhelper.model.entity.TopActivityInfo -import com.wrbug.developerhelper.util.ShellUtils -import com.yhao.floatwindow.FloatActivity +import com.wrbug.developerhelper.commonutil.CommonUtils +import com.wrbug.developerhelper.commonutil.ShellUtils +import com.wrbug.developerhelper.commonutil.entity.FragmentInfo +import com.wrbug.developerhelper.commonutil.entity.LsFileInfo +import com.wrbug.developerhelper.commonutil.entity.TopActivityInfo import java.io.File import java.util.regex.Pattern @@ -24,6 +23,10 @@ object ShellManager { private const val SHELL_GET_ZIP_FILE_LIST = "app_process -Djava.class.path=/data/local/tmp/zip.dex /data/local/tmp Zip %s" private const val SHELL_CHECK_IS_SQLITE = "od -An -tx %1\$s |grep '694c5153'" + private const val SHELL_UNINSTALL_APP = "pm uninstall %1\$s" + private const val SHELL_CLEAR_APP_DATA = "pm clear %1\$s" + private const val SHELL_FORCE_STOP_APP = "am force-stop %1\$s" + private val SHELL_OPEN_ADB_WIFI = arrayOf("setprop service.adb.tcp.port 5555", "stop adbd", "start adbd") fun getTopActivity(callback: Callback) { ShellUtils.runWithSu(arrayOf(SHELL_TOP_ACTIVITY), object : ShellUtils.ShellResultCallback() { override fun onComplete(result: CommandResult) { @@ -196,11 +199,26 @@ object ShellManager { return commandResult.getStdout() } + fun rmFile(file: String): Boolean { + val commandResult = ShellUtils.runWithSu("rm -rf $file") + return commandResult.isSuccessful + } + fun modifyFile(filaPath: String, content: String): Boolean { val commandResult = ShellUtils.runWithSu("echo $content>> $filaPath") return commandResult.isSuccessful } + fun cpFile(source: String, dst: String, mod: String = "666"): Boolean { + val dir = dst.substring(0, dst.lastIndexOf("/")) + var commandResult = ShellUtils.runWithSu("mkdir -p $dir") + if (commandResult.isSuccessful.not()) { + return false + } + commandResult = ShellUtils.runWithSu("cp -R $source $dst && chmod $mod $dst") + return commandResult.isSuccessful || commandResult.getStderr()?.contains("Operation not permitted") ?: false + } + fun catFile(source: String, dst: String, mod: String? = null): Boolean { val cmds = arrayListOf() cmds.add("cat $source> $dst") @@ -212,7 +230,7 @@ object ShellManager { } fun getZipFileList(path: String): List { - val file = File(BaseApp.instance.cacheDir, "zip.dex") + val file = File(CommonUtils.application.cacheDir, "zip.dex") if (file.exists()) { ShellUtils.runWithSu("cp ${file.absolutePath} /data/local/tmp", "rm -rf ${file.absolutePath}") } @@ -226,4 +244,29 @@ object ShellManager { return commandResult.stdout } + fun findApkDir(packageName: String): String { + val cmd = "ls /data/app/|grep $packageName" + val dir = ShellUtils.runWithSu(cmd).getStdout() + return "/data/app/$dir/base.apk" + } + + fun uninstallApp(packageName: String): Boolean { + val commandResult = ShellUtils.runWithSu(String.format(SHELL_UNINSTALL_APP, packageName)) + return commandResult.isSuccessful + } + + fun clearAppData(packageName: String): Boolean { + val commandResult = ShellUtils.runWithSu(String.format(SHELL_CLEAR_APP_DATA, packageName)) + return commandResult.isSuccessful + } + + fun forceStopApp(packageName: String): Boolean { + val commandResult = ShellUtils.runWithSu(String.format(SHELL_FORCE_STOP_APP, packageName)) + return commandResult.isSuccessful + } + + fun openAdbWifi(): Boolean { + val commandResult = ShellUtils.runWithSu(*SHELL_OPEN_ADB_WIFI) + return commandResult.isSuccessful + } } diff --git a/commonutil/src/main/res/values/strings.xml b/commonutil/src/main/res/values/strings.xml new file mode 100644 index 0000000..a900d65 --- /dev/null +++ b/commonutil/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + commonutil + diff --git a/commonutil/src/test/java/com/wrbug/developerhelper/commonutil/ExampleUnitTest.java b/commonutil/src/test/java/com/wrbug/developerhelper/commonutil/ExampleUnitTest.java new file mode 100644 index 0000000..e4cabfb --- /dev/null +++ b/commonutil/src/test/java/com/wrbug/developerhelper/commonutil/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.wrbug.developerhelper.commonutil; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/commonwidget/.gitignore b/commonwidget/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/commonwidget/.gitignore @@ -0,0 +1 @@ +/build diff --git a/commonwidget/build.gradle b/commonwidget/build.gradle new file mode 100644 index 0000000..9e215c4 --- /dev/null +++ b/commonwidget/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 28 + + + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.12' + implementation project(':commonutil') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() +} diff --git a/commonwidget/proguard-rules.pro b/commonwidget/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/commonwidget/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/commonwidget/src/androidTest/java/com/wrbug/developerhelper/commonwidget/ExampleInstrumentedTest.java b/commonwidget/src/androidTest/java/com/wrbug/developerhelper/commonwidget/ExampleInstrumentedTest.java new file mode 100644 index 0000000..78375d2 --- /dev/null +++ b/commonwidget/src/androidTest/java/com/wrbug/developerhelper/commonwidget/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.wrbug.developerhelper.commonwidget; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.wrbug.developerhelper.commonwidget.test", appContext.getPackageName()); + } +} diff --git a/commonwidget/src/main/AndroidManifest.xml b/commonwidget/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1099276 --- /dev/null +++ b/commonwidget/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/app/src/main/java/com/wrbug/developerhelper/ui/widget/flexibletoast/FlexibleToast.kt b/commonwidget/src/main/java/com/wrbug/developerhelper/commonwidget/flexibletoast/FlexibleToast.kt similarity index 96% rename from app/src/main/java/com/wrbug/developerhelper/ui/widget/flexibletoast/FlexibleToast.kt rename to commonwidget/src/main/java/com/wrbug/developerhelper/commonwidget/flexibletoast/FlexibleToast.kt index 9fc4c1b..f492466 100644 --- a/app/src/main/java/com/wrbug/developerhelper/ui/widget/flexibletoast/FlexibleToast.kt +++ b/commonwidget/src/main/java/com/wrbug/developerhelper/commonwidget/flexibletoast/FlexibleToast.kt @@ -1,4 +1,4 @@ -package com.wrbug.developerhelper.ui.widget.flexibletoast +package com.wrbug.developerhelper.commonwidget.flexibletoast import android.content.Context import android.view.Gravity @@ -7,8 +7,8 @@ import android.view.View import android.widget.ImageView import android.widget.TextView import android.widget.Toast -import com.wrbug.developerhelper.R -import com.wrbug.developerhelper.util.UiUtils +import com.wrbug.developerhelper.commonutil.UiUtils +import com.wrbug.developerhelper.commonwidget.R class FlexibleToast(private val mContext: Context) { diff --git a/app/src/main/res/drawable/toast_bg_circle_cornor_rect.xml b/commonwidget/src/main/res/drawable/toast_bg_circle_cornor_rect.xml similarity index 100% rename from app/src/main/res/drawable/toast_bg_circle_cornor_rect.xml rename to commonwidget/src/main/res/drawable/toast_bg_circle_cornor_rect.xml diff --git a/app/src/main/res/layout/layout_toast_flexible.xml b/commonwidget/src/main/res/layout/layout_toast_flexible.xml similarity index 100% rename from app/src/main/res/layout/layout_toast_flexible.xml rename to commonwidget/src/main/res/layout/layout_toast_flexible.xml diff --git a/commonwidget/src/main/res/values/strings.xml b/commonwidget/src/main/res/values/strings.xml new file mode 100644 index 0000000..f6c0897 --- /dev/null +++ b/commonwidget/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + commonwidget + diff --git a/commonwidget/src/test/java/com/wrbug/developerhelper/commonwidget/ExampleUnitTest.java b/commonwidget/src/test/java/com/wrbug/developerhelper/commonwidget/ExampleUnitTest.java new file mode 100644 index 0000000..0cf0c5a --- /dev/null +++ b/commonwidget/src/test/java/com/wrbug/developerhelper/commonwidget/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.wrbug.developerhelper.commonwidget; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/mmkv/.gitignore b/mmkv/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/mmkv/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mmkv/build.gradle b/mmkv/build.gradle new file mode 100644 index 0000000..1f89e36 --- /dev/null +++ b/mmkv/build.gradle @@ -0,0 +1,40 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 28 + + + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.tencent:mmkv:1.0.14' + implementation 'androidx.appcompat:appcompat:1.0.2' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.0-alpha3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha3' + implementation 'com.google.code.gson:gson:2.8.5' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() +} diff --git a/mmkv/proguard-rules.pro b/mmkv/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/mmkv/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/mmkv/src/androidTest/java/com/wrbug/developerhelper/mmkv/ExampleInstrumentedTest.java b/mmkv/src/androidTest/java/com/wrbug/developerhelper/mmkv/ExampleInstrumentedTest.java new file mode 100644 index 0000000..161e03c --- /dev/null +++ b/mmkv/src/androidTest/java/com/wrbug/developerhelper/mmkv/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.wrbug.developerhelper.mmkv; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.wrbug.developerhelper.mmkv.test", appContext.getPackageName()); + } +} diff --git a/mmkv/src/main/AndroidManifest.xml b/mmkv/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1b6e5f2 --- /dev/null +++ b/mmkv/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/mmkv/src/main/java/com/wrbug/developerhelper/mmkv/ConfigKv.kt b/mmkv/src/main/java/com/wrbug/developerhelper/mmkv/ConfigKv.kt new file mode 100644 index 0000000..bb1bb5f --- /dev/null +++ b/mmkv/src/main/java/com/wrbug/developerhelper/mmkv/ConfigKv.kt @@ -0,0 +1,6 @@ +package com.wrbug.developerhelper.mmkv + +interface ConfigKv { + fun setOpenRoot(openRoot: Boolean) + fun isOpenRoot(): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/com/wrbug/developerhelper/model/mmkv/manager/MMKVInvocationHandler.kt b/mmkv/src/main/java/com/wrbug/developerhelper/mmkv/manager/MMKVInvocationHandler.kt similarity index 76% rename from app/src/main/java/com/wrbug/developerhelper/model/mmkv/manager/MMKVInvocationHandler.kt rename to mmkv/src/main/java/com/wrbug/developerhelper/mmkv/manager/MMKVInvocationHandler.kt index 18d016a..f80b2f5 100644 --- a/app/src/main/java/com/wrbug/developerhelper/model/mmkv/manager/MMKVInvocationHandler.kt +++ b/mmkv/src/main/java/com/wrbug/developerhelper/mmkv/manager/MMKVInvocationHandler.kt @@ -1,7 +1,7 @@ -package com.wrbug.developerhelper.model.mmkv.manager +package com.wrbug.developerhelper.mmkv.manager +import com.google.gson.Gson import com.tencent.mmkv.MMKV -import com.wrbug.developerhelper.util.JsonHelper import java.lang.reflect.InvocationHandler import java.lang.reflect.Method @@ -12,14 +12,16 @@ class MMKVInvocationHandler(clazz: Class<*>) : InvocationHandler { if (it.name.startsWith("set") && args != null && args.size == 1) { setValue(it.name, args[0]) } else if (it.name.startsWith("get")) { - return getValue(it) + return getValue(it, 3) + } else if (it.name.startsWith("is")) { + return getValue(it, 2) } } return null } - private fun getValue(method: Method): Any? = with(method) { - val key = name.substring(3) + private fun getValue(method: Method, prefixLen: Int): Any? = with(method) { + val key = name.substring(prefixLen) return when (returnType) { Boolean::class.java -> mmkv.decodeBool(key) Int::class.java -> mmkv.decodeInt(key) @@ -28,7 +30,7 @@ class MMKVInvocationHandler(clazz: Class<*>) : InvocationHandler { Double::class.java -> mmkv.decodeDouble(key) String::class.java -> mmkv.decodeString(key) ByteArray::class.java -> mmkv.decodeBytes(key) - else -> JsonHelper.fromJson(mmkv.decodeString(key), returnType) + else -> Gson().fromJson(mmkv.decodeString(key), returnType) } } @@ -42,7 +44,7 @@ class MMKVInvocationHandler(clazz: Class<*>) : InvocationHandler { is Double -> mmkv.encode(key, data) is String -> mmkv.encode(key, data) is ByteArray -> mmkv.encode(key, data) - else -> mmkv.encode(key, JsonHelper.toJson(data)) + else -> mmkv.encode(key, Gson().toJson(data)) } } diff --git a/app/src/main/java/com/wrbug/developerhelper/model/mmkv/manager/MMKVManager.kt b/mmkv/src/main/java/com/wrbug/developerhelper/mmkv/manager/MMKVManager.kt similarity index 50% rename from app/src/main/java/com/wrbug/developerhelper/model/mmkv/manager/MMKVManager.kt rename to mmkv/src/main/java/com/wrbug/developerhelper/mmkv/manager/MMKVManager.kt index aab97f6..8fce616 100644 --- a/app/src/main/java/com/wrbug/developerhelper/model/mmkv/manager/MMKVManager.kt +++ b/mmkv/src/main/java/com/wrbug/developerhelper/mmkv/manager/MMKVManager.kt @@ -1,11 +1,20 @@ @file:Suppress("UNCHECKED_CAST") -package com.wrbug.developerhelper.model.mmkv.manager +package com.wrbug.developerhelper.mmkv.manager +import android.app.Application +import android.content.Context import androidx.collection.ArrayMap +import com.tencent.mmkv.MMKV import java.lang.reflect.Proxy object MMKVManager { + lateinit var application: Application + fun register(ctx: Context) { + application = ctx.applicationContext as Application + MMKV.initialize(application) + } + val map = ArrayMap, Any>() fun get(clazz: Class): T { if (map.containsKey(clazz)) { @@ -17,7 +26,10 @@ object MMKVManager { } private fun obtainImpl(clazz: Class): T { - val instance = Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz), MMKVInvocationHandler(clazz)) + val instance = Proxy.newProxyInstance( + clazz.classLoader, arrayOf(clazz), + MMKVInvocationHandler(clazz) + ) return instance as T } } diff --git a/mmkv/src/main/res/values/strings.xml b/mmkv/src/main/res/values/strings.xml new file mode 100644 index 0000000..687a971 --- /dev/null +++ b/mmkv/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + mmkv + diff --git a/mmkv/src/test/java/com/wrbug/developerhelper/mmkv/ExampleUnitTest.java b/mmkv/src/test/java/com/wrbug/developerhelper/mmkv/ExampleUnitTest.java new file mode 100644 index 0000000..9b42f39 --- /dev/null +++ b/mmkv/src/test/java/com/wrbug/developerhelper/mmkv/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.wrbug.developerhelper.mmkv; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/readme.md b/readme.md index 2b546fe..6daa287 100644 --- a/readme.md +++ b/readme.md @@ -12,6 +12,13 @@ Android 5.0+ ![](https://i.loli.net/2018/12/26/5c23070cda923.png)![](https://i.loli.net/2018/12/26/5c23070d9bd25.png) + + +### 新版本功能 v1.0.3 + +* 新增Android Pie (sdk28) [EdXposed](https://github.com/solohsu/EdXposed)支持 +* 新增脱壳应用管理 + ### 已支持功能 1. 应用基本信息查看包括: @@ -21,10 +28,12 @@ Android 5.0+ * Uid * 安装时间/更新时间 * Data目录 + * 应用脱壳(xposed) 2. apk信息: * apk目录 * MD5/Sha1 * sharedPreference查看和编辑 + * 数据库查看 3. 布局分析 * WidgetClass * ViewId @@ -33,11 +42,9 @@ Android 5.0+ * View位置 ### TODO - -1. 数据库查看与编辑 -2. root功能(adb wifi、重启应用......) -3. xposed功能(脱壳等) -4. 应用抓包 + +1. root功能(adb wifi、重启应用......) +2. 应用抓包 ### 下载 diff --git a/settings.gradle b/settings.gradle index 1a7119e..cf8020c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':basecommon' +include ':app', ':basecommon', ':commonutil', ':mmkv', ':xposedmodule', ':commonwidget' diff --git a/xposedmodule/.gitignore b/xposedmodule/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/xposedmodule/.gitignore @@ -0,0 +1 @@ +/build diff --git a/xposedmodule/CMakeLists.txt b/xposedmodule/CMakeLists.txt new file mode 100644 index 0000000..73bac34 --- /dev/null +++ b/xposedmodule/CMakeLists.txt @@ -0,0 +1,59 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.4.1) + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. + +add_library( # Sets the name of the library. + nativeDump + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + src/main/cpp/native.cpp + src/main/cpp/dlopen.c + src/main/cpp/inlineHook.c + src/main/cpp/relocate.c + src/main/cpp/And64InlineHook.cpp + src/main/cpp/util/deviceutils.cpp + src/main/cpp/util/fileutils.cpp) + +# Searches for a specified prebuilt library and stores the path as a +# variable. Because CMake includes system libraries in the search path by +# default, you only need to specify the name of the public NDK library +# you want to add. CMake verifies that the library exists before +# completing its build. + +find_library( # Sets the name of the path variable. + log-lib + + # Specifies the name of the NDK library that + # you want CMake to locate. + log ) + +# Specifies libraries CMake should link to your target library. You +# can link multiple libraries, such as libraries you define in this +# build script, prebuilt third-party libraries, or system libraries. + +target_link_libraries( # Specifies the target library. + nativeDump + + # Links the target library to the log library + # included in the NDK. + ${log-lib} ) + + +if(${ANDROID_ABI} STREQUAL "armeabi-v7a") + include_directories(${ANDROID_SYSROOT}/usr/include/arm-linux-androidabi) +elseif(${ANDROID_ABI} STREQUAL "arm64-v8a") + include_directories(${ANDROID_SYSROOT}/usr/include/aarch64-linux-android) +else() + include_directories(${ANDROID_SYSROOT}/usr/include/arm-linux-androidabi) +endif() \ No newline at end of file diff --git a/xposedmodule/build.gradle b/xposedmodule/build.gradle new file mode 100644 index 0000000..87277e8 --- /dev/null +++ b/xposedmodule/build.gradle @@ -0,0 +1,66 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 28 + + + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + cppFlags "-std=c++14", "-fms-extensions" + } + } + ndk { + abiFilters "armeabi", "armeabi-v7a", "arm64-v8a" + } + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + implementation 'org.jetbrains.anko:anko-commons:0.10.8' + implementation project(':commonutil') + implementation 'com.jaredrummler:android-shell:1.0.0' + implementation project(':mmkv') + compileOnly 'de.robv.android.xposed:api:82' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.google.code.gson:gson:2.8.5' + implementation project(':basecommon') +} +repositories { + mavenCentral() +} +task buildSo(dependsOn: "compileReleaseSources") { + doLast { + println "更新so" + exec { + ignoreExitValue true + commandLine "pwd" + commandLine "sh", "./script/so.sh" + } + } +} \ No newline at end of file diff --git a/xposedmodule/proguard-rules.pro b/xposedmodule/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/xposedmodule/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/xposedmodule/script/so.sh b/xposedmodule/script/so.sh new file mode 100644 index 0000000..5a3a845 --- /dev/null +++ b/xposedmodule/script/so.sh @@ -0,0 +1,11 @@ +result=$(ls build/intermediates/cmake/release/obj)|grep 'No such file or directory' +echo $result +if [ -z "${result}" ]; then + echo '目录已生成' + cp build/intermediates/cmake/release/obj/arm64-v8a/libnativeDump.so src/main/assets/nativeDumpV8a.so + cp build/intermediates/cmake/release/obj/armeabi-v7a/libnativeDump.so src/main/assets/nativeDumpV7a.so + cp build/intermediates/cmake/release/obj/armeabi/libnativeDump.so src/main/assets/nativeDump.so + echo '完成' +else + echo '目录未生成' +fi \ No newline at end of file diff --git a/xposedmodule/src/androidTest/java/com/wrbug/developerhelper/xposed/ExampleInstrumentedTest.java b/xposedmodule/src/androidTest/java/com/wrbug/developerhelper/xposed/ExampleInstrumentedTest.java new file mode 100644 index 0000000..178345d --- /dev/null +++ b/xposedmodule/src/androidTest/java/com/wrbug/developerhelper/xposed/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.wrbug.developerhelper.xposed; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.wrbug.developerhelper.xposed.test", appContext.getPackageName()); + } +} diff --git a/xposedmodule/src/main/AndroidManifest.xml b/xposedmodule/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c249e0a --- /dev/null +++ b/xposedmodule/src/main/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/xposedmodule/src/main/assets/xposed_init b/xposedmodule/src/main/assets/xposed_init new file mode 100644 index 0000000..06ffbeb --- /dev/null +++ b/xposedmodule/src/main/assets/xposed_init @@ -0,0 +1 @@ +com.wrbug.developerhelper.xposed.XposedInit \ No newline at end of file diff --git a/xposedmodule/src/main/cpp/And64InlineHook.cpp b/xposedmodule/src/main/cpp/And64InlineHook.cpp new file mode 100755 index 0000000..564fbeb --- /dev/null +++ b/xposedmodule/src/main/cpp/And64InlineHook.cpp @@ -0,0 +1,599 @@ +/* + * @date : 2018年01月24日 + * @author : rrrfff@foxmail.com + * https://github.com/rrrfff/And64InlineHook + */ +/* + MIT License + + Copyright (c) 2017 rrrfff@foxmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ +#define __STDC_FORMAT_MACROS + +#include +#include +#include +#include +#include +#include +#if defined(__aarch64__) + +#include "And64InlineHook.hpp" + +#define A64_MAX_INSTRUCTIONS 5 +#define A64_MAX_REFERENCES (A64_MAX_INSTRUCTIONS * 2) +#define A64_NOP 0xd503201fu +#define A64_JNIEXPORT __attribute__((visibility("default"))) +#define A64_LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "A64_HOOK", __VA_ARGS__)) +#ifndef NDEBUG +# define A64_LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "A64_HOOK", __VA_ARGS__)) +#else +# define A64_LOGI(...) ((void)0) +#endif // NDEBUG +typedef uint32_t *__restrict *__restrict instruction; +typedef struct { + struct fix_info { + uint32_t *bp; + uint32_t ls; // left-shift counts + uint32_t ad; // & operand + }; + struct insns_info { + union { + uint64_t insu; + int64_t ins; + void *insp; + }; + fix_info fmap[A64_MAX_REFERENCES]; + }; + int64_t basep; + int64_t endp; + insns_info dat[A64_MAX_INSTRUCTIONS]; + +public: + inline bool is_in_fixing_range(const int64_t absolute_addr) { + return absolute_addr>= this->basep && absolute_addr < this->endp; + } + + inline intptr_t get_ref_ins_index(const int64_t absolute_addr) { + return static_cast((absolute_addr - this->basep) / sizeof(uint32_t)); + } + + inline intptr_t get_and_set_current_index(uint32_t *__restrict inp, uint32_t *__restrict outp) { + intptr_t current_idx = this->get_ref_ins_index(reinterpret_cast(inp)); + this->dat[current_idx].insp = outp; + return current_idx; + } + + inline void reset_current_ins(const intptr_t idx, uint32_t *__restrict outp) { + this->dat[idx].insp = outp; + } + + void + insert_fix_map(const intptr_t idx, uint32_t *bp, uint32_t ls = 0u, uint32_t ad = 0xffffffffu) { + for (auto &f : this->dat[idx].fmap) { + if (f.bp == NULL) { + f.bp = bp; + f.ls = ls; + f.ad = ad; + return; + } //if + } + // What? GGing.. + } + + void process_fix_map(const intptr_t idx) { + for (auto &f : this->dat[idx].fmap) { + if (f.bp == NULL) break; + *(f.bp) = *(f.bp) | + (((int32_t(this->dat[idx].ins - reinterpret_cast(f.bp))>> 2) + << f.ls) & f.ad); + f.bp = NULL; + } + } +} context; + +//------------------------------------------------------------------------- + +static bool __fix_branch_imm(instruction inpp, instruction outpp, context *ctxp) { + constexpr uint32_t mbits = 6u; + constexpr uint32_t mask = 0xfc000000u; // 0b11111100000000000000000000000000 + constexpr uint32_t rmask = 0x03ffffffu; // 0b00000011111111111111111111111111 + constexpr uint32_t op_b = 0x14000000u; // "b" ADDR_PCREL26 + constexpr uint32_t op_bl = 0x94000000u; // "bl" ADDR_PCREL26 + + const uint32_t ins = *(*inpp); + const uint32_t opc = ins & mask; + switch (opc) { + case op_b: + case op_bl: { + intptr_t current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); + int64_t absolute_addr = reinterpret_cast(*inpp) + + (static_cast(ins << mbits) +>> (mbits - 2u)); // sign-extended + int64_t new_pc_offset = + static_cast(absolute_addr - reinterpret_cast(*outpp)) +>> 2; // shifted + bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); + // whether the branch should be converted to absolute jump + if (!special_fix_type && llabs(new_pc_offset)> rmask) { + bool b_aligned = (reinterpret_cast(*outpp + 2) & 7u) == 0u; + if (opc == op_b) { + if (b_aligned == true) { + (*outpp)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outpp)); + } //if + (*outpp)[0] = 0x58000051u; // LDR X17, #0x8 + (*outpp)[1] = 0xd61f0220u; // BR X17 + memcpy(*outpp + 2, &absolute_addr, sizeof(absolute_addr)); + *outpp += 4; + } else { + if (b_aligned == false) { + (*outpp)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outpp)); + } //if + (*outpp)[0] = 0x58000071u; // LDR X17, #12 + (*outpp)[1] = 0x1000009eu; // ADR X30, #16 + (*outpp)[2] = 0xd61f0220u; // BR X17 + memcpy(*outpp + 3, &absolute_addr, sizeof(absolute_addr)); + *outpp += 5; + } //if + } else { + if (special_fix_type) { + intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr); + if (ref_idx <= current_idx) { + new_pc_offset = static_cast(ctxp->dat[ref_idx].ins - + reinterpret_cast(*outpp)) +>> 2; + } else { + ctxp->insert_fix_map(ref_idx, *outpp, 0u, rmask); + new_pc_offset = 0; + } //if + } //if + + (*outpp)[0] = opc | (new_pc_offset & ~mask); + ++(*outpp); + } //if + + ++(*inpp); + return ctxp->process_fix_map(current_idx), true; + } + } + return false; +} + +//------------------------------------------------------------------------- + +static bool __fix_cond_comp_test_branch(instruction inpp, instruction outpp, context *ctxp) { + constexpr uint32_t lsb = 5u; + constexpr uint32_t lmask01 = 0xff00001fu; // 0b11111111000000000000000000011111 + constexpr uint32_t mask0 = 0xff000010u; // 0b11111111000000000000000000010000 + constexpr uint32_t op_bc = 0x54000000u; // "b.c" ADDR_PCREL19 + constexpr uint32_t mask1 = 0x7f000000u; // 0b01111111000000000000000000000000 + constexpr uint32_t op_cbz = 0x34000000u; // "cbz" Rt, ADDR_PCREL19 + constexpr uint32_t op_cbnz = 0x35000000u; // "cbnz" Rt, ADDR_PCREL19 + constexpr uint32_t lmask2 = 0xfff8001fu; // 0b11111111111110000000000000011111 + constexpr uint32_t mask2 = 0x7f000000u; // 0b01111111000000000000000000000000 + constexpr uint32_t op_tbz = 0x36000000u; // 0b00110110000000000000000000000000 "tbz" Rt, BIT_NUM, ADDR_PCREL14 + constexpr uint32_t op_tbnz = 0x37000000u; // 0b00110111000000000000000000000000 "tbnz" Rt, BIT_NUM, ADDR_PCREL14 + + const uint32_t ins = *(*inpp); + uint32_t lmask = lmask01; + if ((ins & mask0) != op_bc) { + uint32_t opc = ins & mask1; + if (opc != op_cbz && opc != op_cbnz) { + opc = ins & mask2; + if (opc != op_tbz && opc != op_tbnz) { + return false; + } //if + lmask = lmask2; + } //if + } //if + + intptr_t current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); + int64_t absolute_addr = reinterpret_cast(*inpp) + ((ins & ~lmask)>> (lsb - 2u)); + int64_t new_pc_offset = + static_cast(absolute_addr - reinterpret_cast(*outpp))>> 2; // shifted + bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); + if (!special_fix_type && llabs(new_pc_offset)> (~lmask>> lsb)) { + if ((reinterpret_cast(*outpp + 4) & 7u) != 0u) { + (*outpp)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outpp)); + } //if + (*outpp)[0] = (((8u>> 2u) << lsb) & ~lmask) | (ins & lmask); // B.C #0x8 + (*outpp)[1] = 0x14000005u; // B #0x14 + (*outpp)[2] = 0x58000051u; // LDR X17, #0x8 + (*outpp)[3] = 0xd61f0220u; // BR X17 + memcpy(*outpp + 4, &absolute_addr, sizeof(absolute_addr)); + *outpp += 6; + } else { + if (special_fix_type) { + intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr); + if (ref_idx <= current_idx) { + new_pc_offset = static_cast(ctxp->dat[ref_idx].ins - + reinterpret_cast(*outpp))>> 2; + } else { + ctxp->insert_fix_map(ref_idx, *outpp, lsb, ~lmask); + new_pc_offset = 0; + } //if + } //if + + (*outpp)[0] = (static_cast(new_pc_offset << lsb) & ~lmask) | (ins & lmask); + ++(*outpp); + } //if + + ++(*inpp); + return ctxp->process_fix_map(current_idx), true; +} + +//------------------------------------------------------------------------- + +static bool __fix_loadlit(instruction inpp, instruction outpp, context *ctxp) { + const uint32_t ins = *(*inpp); + + // memory prefetch("prfm"), just skip it + // http://infocenter.arm.com/help/topic/com.arm.doc.100069_0608_00_en/pge1427897420050.html + if ((ins & 0xff000000u) == 0xd8000000u) { + ctxp->process_fix_map(ctxp->get_and_set_current_index(*inpp, *outpp)); + ++(*inpp); + return true; + } //if + + constexpr uint32_t msb = 8u; + constexpr uint32_t lsb = 5u; + constexpr uint32_t mask_30 = 0x40000000u; // 0b01000000000000000000000000000000 + constexpr uint32_t mask_31 = 0x80000000u; // 0b10000000000000000000000000000000 + constexpr uint32_t lmask = 0xff00001fu; // 0b11111111000000000000000000011111 + constexpr uint32_t mask_ldr = 0xbf000000u; // 0b10111111000000000000000000000000 + constexpr uint32_t op_ldr = 0x18000000u; // 0b00011000000000000000000000000000 "LDR Wt/Xt, label" | ADDR_PCREL19 + constexpr uint32_t mask_ldrv = 0x3f000000u; // 0b00111111000000000000000000000000 + constexpr uint32_t op_ldrv = 0x1c000000u; // 0b00011100000000000000000000000000 "LDR St/Dt/Qt, label" | ADDR_PCREL19 + constexpr uint32_t mask_ldrsw = 0xff000000u; // 0b11111111000000000000000000000000 + constexpr uint32_t op_ldrsw = 0x98000000u; // "LDRSW Xt, label" | ADDR_PCREL19 | load register signed word + // LDR S0, #0 | 0b00011100000000000000000000000000 | 32-bit + // LDR D0, #0 | 0b01011100000000000000000000000000 | 64-bit + // LDR Q0, #0 | 0b10011100000000000000000000000000 | 128-bit + // INVALID | 0b11011100000000000000000000000000 | may be 256-bit + + uint32_t mask = mask_ldr; + uintptr_t faligned = (ins & mask_30) ? 7u : 3u; + if ((ins & mask_ldr) != op_ldr) { + mask = mask_ldrv; + if (faligned != 7u) + faligned = (ins & mask_31) ? 15u : 3u; + if ((ins & mask_ldrv) != op_ldrv) { + if ((ins & mask_ldrsw) != op_ldrsw) { + return false; + } //if + mask = mask_ldrsw; + faligned = 7u; + } //if + } //if + + intptr_t current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); + int64_t absolute_addr = reinterpret_cast(*inpp) + + ((static_cast(ins << msb)>> (msb + lsb - 2u)) & ~3u); + int64_t new_pc_offset = + static_cast(absolute_addr - reinterpret_cast(*outpp))>> 2; // shifted + bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); + // special_fix_type may encounter issue when there are mixed data and code + if (special_fix_type || (llabs(new_pc_offset) + (faligned + 1u - 4u))> (~lmask>> lsb)) { + while ((reinterpret_cast(*outpp + 2) & faligned) != 0u) { + *(*outpp)++ = A64_NOP; + } + ctxp->reset_current_ins(current_idx, *outpp); + + // Note that if memory at absolute_addr is writeable (non-const), we will fail to fetch it. + // And what's worse, we may unexpectedly overwrite something if special_fix_type is true... + uint32_t ns = static_cast((faligned + 1) / sizeof(uint32_t)); + (*outpp)[0] = (((8u>> 2u) << lsb) & ~mask) | (ins & lmask); // LDR #0x8 + (*outpp)[1] = 0x14000001u + ns; // B #0xc + memcpy(*outpp + 2, reinterpret_cast(absolute_addr), faligned + 1); + *outpp += 2 + ns; + } else { + faligned>>= 2; // new_pc_offset is shifted and 4-byte aligned + while ((new_pc_offset & faligned) != 0) { + *(*outpp)++ = A64_NOP; + new_pc_offset = + static_cast(absolute_addr - reinterpret_cast(*outpp))>> 2; + } + ctxp->reset_current_ins(current_idx, *outpp); + + (*outpp)[0] = (static_cast(new_pc_offset << lsb) & ~mask) | (ins & lmask); + ++(*outpp); + } //if + + ++(*inpp); + return ctxp->process_fix_map(current_idx), true; +} + +//------------------------------------------------------------------------- + +static bool __fix_pcreladdr(instruction inpp, instruction outpp, context *ctxp) { + // Load a PC-relative address into a register + // http://infocenter.arm.com/help/topic/com.arm.doc.100069_0608_00_en/pge1427897645644.html + constexpr uint32_t msb = 8u; + constexpr uint32_t lsb = 5u; + constexpr uint32_t mask = 0x9f000000u; // 0b10011111000000000000000000000000 + constexpr uint32_t rmask = 0x0000001fu; // 0b00000000000000000000000000011111 + constexpr uint32_t lmask = 0xff00001fu; // 0b11111111000000000000000000011111 + constexpr uint32_t fmask = 0x00ffffffu; // 0b00000000111111111111111111111111 + constexpr uint32_t max_val = 0x001fffffu; // 0b00000000000111111111111111111111 + constexpr uint32_t op_adr = 0x10000000u; // "adr" Rd, ADDR_PCREL21 + constexpr uint32_t op_adrp = 0x90000000u; // "adrp" Rd, ADDR_ADRP + + const uint32_t ins = *(*inpp); + intptr_t current_idx; + switch (ins & mask) { + case op_adr: { + current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); + int64_t lsb_bytes = static_cast(ins << 1u)>> 30u; + int64_t absolute_addr = reinterpret_cast(*inpp) + + (((static_cast(ins << msb)>> (msb + lsb - 2u)) & + ~3u) | lsb_bytes); + int64_t new_pc_offset = static_cast(absolute_addr - + reinterpret_cast(*outpp)); + bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); + if (!special_fix_type && llabs(new_pc_offset)> max_val) { + if ((reinterpret_cast(*outpp + 2) & 7u) != 0u) { + (*outpp)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outpp)); + } //if + + (*outpp)[0] = + 0x58000000u | (((8u>> 2u) << lsb) & ~mask) | (ins & rmask); // LDR #0x8 + (*outpp)[1] = 0x14000003u; // B #0xc + memcpy(*outpp + 2, &absolute_addr, sizeof(absolute_addr)); + *outpp += 4; + } else { + if (special_fix_type) { + intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr & ~3ull); + if (ref_idx <= current_idx) { + new_pc_offset = static_cast(ctxp->dat[ref_idx].ins - + reinterpret_cast(*outpp)); + } else { + ctxp->insert_fix_map(ref_idx, *outpp, lsb, fmask); + new_pc_offset = 0; + } //if + } //if + + // the lsb_bytes will never be changed, so we can use lmask to keep it + (*outpp)[0] = (static_cast(new_pc_offset << (lsb - 2u)) & fmask) | + (ins & lmask); + ++(*outpp); + } //if + } + break; + case op_adrp: { + current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); + int32_t lsb_bytes = static_cast(ins << 1u)>> 30u; + int64_t absolute_addr = (reinterpret_cast(*inpp) & ~0xfffll) + + ((((static_cast(ins << msb)>> (msb + lsb - 2u)) & + ~3u) | lsb_bytes) << 12); + A64_LOGI("ins = 0x%.8X, pc = %p, abs_addr = %p", + ins, *inpp, reinterpret_cast(absolute_addr)); + if (ctxp->is_in_fixing_range(absolute_addr)) { + intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr/* & ~3ull*/); + if (ref_idx> current_idx) { + // the bottom 12 bits of absolute_addr are masked out, + // so ref_idx must be less than or equal to current_idx! + A64_LOGE("ref_idx must be less than or equal to current_idx!"); + } //if + + // *absolute_addr may be changed due to relocation fixing + A64_LOGI("What is the correct way to fix this?"); + *(*outpp)++ = ins; // 0x90000000u; + } else { + if ((reinterpret_cast(*outpp + 2) & 7u) != 0u) { + (*outpp)[0] = A64_NOP; + ctxp->reset_current_ins(current_idx, ++(*outpp)); + } //if + + (*outpp)[0] = + 0x58000000u | (((8u>> 2u) << lsb) & ~mask) | (ins & rmask); // LDR #0x8 + (*outpp)[1] = 0x14000003u; // B #0xc + memcpy(*outpp + 2, &absolute_addr, sizeof(absolute_addr)); // potential overflow? + *outpp += 4; + } //if + } + break; + default: + return false; + } + + ctxp->process_fix_map(current_idx); + ++(*inpp); + return true; +} + +//------------------------------------------------------------------------- + +static void __fix_instructions(uint32_t *__restrict inp, int32_t count, uint32_t *__restrict outp) { + context ctx; + ctx.basep = reinterpret_cast(inp); + ctx.endp = reinterpret_cast(inp + count); + memset(ctx.dat, 0, sizeof(ctx.dat)); + static_assert(sizeof(ctx.dat) / sizeof(ctx.dat[0]) == A64_MAX_INSTRUCTIONS, + "please use A64_MAX_INSTRUCTIONS!"); +#ifndef NDEBUG + if (count> A64_MAX_INSTRUCTIONS) { + A64_LOGE("too many fixing instructions!"); + } //if +#endif // NDEBUG + + while (--count>= 0) { + if (__fix_branch_imm(&inp, &outp, &ctx)) continue; + if (__fix_cond_comp_test_branch(&inp, &outp, &ctx)) continue; + if (__fix_loadlit(&inp, &outp, &ctx)) continue; + if (__fix_pcreladdr(&inp, &outp, &ctx)) continue; + + // without PC-relative offset + ctx.process_fix_map(ctx.get_and_set_current_index(inp, outp)); + *(outp++) = *(inp++); + } + + constexpr uint_fast64_t mask = 0x03ffffffu; // 0b00000011111111111111111111111111 + auto callback = reinterpret_cast(inp); + auto pc_offset = static_cast(callback - reinterpret_cast(outp))>> 2; + if (llabs(pc_offset)> mask) { + if ((reinterpret_cast(outp + 2) & 7u) != 0u) { + outp[0] = A64_NOP; + ++outp; + } //if + outp[0] = 0x58000051u; // LDR X17, #0x8 + outp[1] = 0xd61f0220u; // BR X17 + *reinterpret_cast(outp + 2) = callback; +// outp += 4; + } else { + outp[0] = 0x14000000u | (pc_offset & mask); // "B" ADDR_PCREL26 +// ++outp; + } //if +} + +//------------------------------------------------------------------------- + +extern "C" { +#define __attribute __attribute__ +#define aligned(x) __aligned__(x) +#define __intval(p) reinterpret_cast(p) +#define __uintval(p) reinterpret_cast(p) +#define __ptr(p) reinterpret_cast(p) +#define __page_size 4096 +#define __page_align(n) __align_up(static_cast(n), __page_size) +#define __ptr_align(x) __ptr(__align_down(reinterpret_cast(x), __page_size)) +#define __align_up(x, n) (((x) + ((n) - 1)) & ~((n) - 1)) +#define __align_down(x, n) ((x) & -(n)) +#define __countof(x) static_cast(sizeof(x) / sizeof((x)[0])) // must be signed +#define __atomic_increase(p) __sync_add_and_fetch(p, 1) +#define __sync_cmpswap(p, v, n) __sync_bool_compare_and_swap(p, v, n) +#define __predict_true(exp) __builtin_expect((exp) != 0, 1) +#define __flush_cache(c, n) __builtin___clear_cache(reinterpret_cast(c), reinterpret_cast(c) + n) +#define __make_rwx(p, n) ::mprotect(__ptr_align(p), \ + __page_align(__uintval(p) + n) != __page_align(__uintval(p)) ? __page_align(n) + __page_size : __page_align(n), \ + PROT_READ | PROT_WRITE | PROT_EXEC) + +//------------------------------------------------------------------------- + +static __attribute((aligned(__page_size))) uint32_t __insns_pool[A64_MAX_BACKUPS][ + A64_MAX_INSTRUCTIONS * 10]; + +//------------------------------------------------------------------------- + +class A64HookInit { +public: + A64HookInit() { + __make_rwx(__insns_pool, sizeof(__insns_pool)); + A64_LOGI("insns pool initialized."); + } +}; +static A64HookInit __init; + +//------------------------------------------------------------------------- + +static uint32_t *FastAllocateTrampoline() { + static_assert((A64_MAX_INSTRUCTIONS * 10 * sizeof(uint32_t)) % 8 == 0, "8-byte align"); + static volatile int32_t __index = -1; + + int32_t i = __atomic_increase(&__index); + if (__predict_true(i>= 0 && i < __countof(__insns_pool))) { + return __insns_pool[i]; + } //if + + A64_LOGE("failed to allocate trampoline!"); + return NULL; +} + +//------------------------------------------------------------------------- + +A64_JNIEXPORT void *A64HookFunctionV(void *const symbol, void *const replace, + void *const rwx, const uintptr_t rwx_size) { + constexpr uint_fast64_t mask = 0x03ffffffu; // 0b00000011111111111111111111111111 + + uint32_t *trampoline = static_cast(rwx), *original = static_cast(symbol); + + static_assert(A64_MAX_INSTRUCTIONS>= 5, "please fix A64_MAX_INSTRUCTIONS!"); + auto pc_offset = static_cast(__intval(replace) - __intval(symbol))>> 2; + if (llabs(pc_offset)> mask) { + int32_t count = (reinterpret_cast(original + 2) & 7u) != 0u ? 5 : 4; + if (trampoline) { + if (rwx_size < count * 10u) { + return NULL; + } //if + __fix_instructions(original, count, trampoline); + } //if + + if (__make_rwx(original, 5 * sizeof(uint32_t)) == 0) { + if (count == 5) { + original[0] = A64_NOP; + ++original; + } //if + original[0] = 0x58000051u; // LDR X17, #0x8 + original[1] = 0xd61f0220u; // BR X17 + *reinterpret_cast(original + 2) = __intval(replace); + __flush_cache(symbol, 5 * sizeof(uint32_t)); + + A64_LOGI("inline hook %p->%p successfully! %zu bytes overwritten", + symbol, replace, 5 * sizeof(uint32_t)); + } else { + + trampoline = NULL; + } //if + } else { + if (trampoline) { + if (rwx_size < 1u * 10u) { + return NULL; + } //if + __fix_instructions(original, 1, trampoline); + } //if + + if (__make_rwx(original, 1 * sizeof(uint32_t)) == 0) { + __sync_cmpswap(original, *original, + 0x14000000u | (pc_offset & mask)); // "B" ADDR_PCREL26 + __flush_cache(symbol, 1 * sizeof(uint32_t)); + + A64_LOGI("inline hook %p->%p successfully! %zu bytes overwritten", + symbol, replace, 1 * sizeof(uint32_t)); + } else { + + trampoline = NULL; + } //if + } //if + + return trampoline; +} + +//------------------------------------------------------------------------- + +A64_JNIEXPORT void A64HookFunction(void *const symbol, void *const replace, void **result) { + void *trampoline = NULL; + if (result != NULL) { + trampoline = FastAllocateTrampoline(); + *result = trampoline; + if (trampoline == NULL) return; + } //if + + trampoline = A64HookFunctionV(symbol, replace, trampoline, A64_MAX_INSTRUCTIONS * 10u); + if (trampoline == NULL && result != NULL) { + *result = NULL; + } //if +} +} + +#endif // defined(__aarch64__) \ No newline at end of file diff --git a/xposedmodule/src/main/cpp/And64InlineHook.hpp b/xposedmodule/src/main/cpp/And64InlineHook.hpp new file mode 100755 index 0000000..2511754 --- /dev/null +++ b/xposedmodule/src/main/cpp/And64InlineHook.hpp @@ -0,0 +1,42 @@ +/* + * @date : 2018年01月24日 + * @author : rrrfff@foxmail.com + * https://github.com/rrrfff/And64InlineHook + */ +/* + MIT License + + Copyright (c) 2017 rrrfff@foxmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ +#pragma once +#define A64_MAX_BACKUPS 256 + +#ifdef __cplusplus +extern "C" { +#endif + + void A64HookFunction(void *const symbol, void *const replace, void **result); + void *A64HookFunctionV(void *const symbol, void *const replace, + void *const rwx, const uintptr_t rwx_size); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/xposedmodule/src/main/cpp/dlopen.c b/xposedmodule/src/main/cpp/dlopen.c new file mode 100644 index 0000000..c2a9714 --- /dev/null +++ b/xposedmodule/src/main/cpp/dlopen.c @@ -0,0 +1,191 @@ +/* + * + * @author : rrrfff@foxmail.com + * https://github.com/rrrfff/ndk_dlopen + * + */ +#include "dlopen.h" +#include +#include +#include +#include +#include +#include +#include + +#define LOG_TAG "ndk_dlopen" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) + +static volatile int SDK_INT = 0; +static void *quick_on_stack_back; +static union +{ + void *generic_stub; + void *(*quick_on_stack_replace)(const void *param1, const void *param2, + const void *fake_trampoline, const void *called); +} STUBS; + +void JNIEXPORT ndk_init(JNIEnv *env) +{ + if (SDK_INT <= 0) { + char sdk[PROP_VALUE_MAX]; + __system_property_get("ro.build.version.sdk", sdk); + SDK_INT = atoi(sdk); + LOGI("SDK_INT = %d", SDK_INT); + if (SDK_INT>= 24) { + static __attribute__((__aligned__(PAGE_SIZE))) uint8_t __insns[PAGE_SIZE]; + STUBS.generic_stub = __insns; + mprotect(__insns, sizeof(__insns), PROT_READ | PROT_WRITE | PROT_EXEC); + + // we are currently hijacking "FatalError" as a fake system-call trampoline + uintptr_t pv = (uintptr_t)(*env)->FatalError; + uintptr_t pu = (pv | (PAGE_SIZE - 1)) + 1u; + uintptr_t pd = (pv & ~(PAGE_SIZE - 1)); + mprotect((void *)pd, pv + 8u>= pu ? PAGE_SIZE * 2u : PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC); + quick_on_stack_back = (void *)pv; + +#if defined(__i386__) + /* + DEFINE_FUNCTION art_quick_on_stack_replace + movl 12(REG_VAR(esp)), REG_VAR(eax) + movl (REG_VAR(esp)), REG_VAR(edx) + movl REG_VAR(eax), (REG_VAR(esp)) + movl REG_VAR(edx), 12(REG_VAR(esp)) + pushl 16(REG_VAR(esp)) + ret + END_FUNCTION art_quick_on_stack_replace + */ + memcpy(__insns, "\x8B\x44\x24\x0C\x8B\x14\x24\x89\x04\x24\x89\x54\x24\x0C\xFF\x74\x24\x10\xC3", 19); + /* + DEFINE_FUNCTION art_quick_on_stack_back + push 8(REG_VAR(esp)) + ret + END_FUNCTION art_quick_on_stack_back + */ + memcpy(quick_on_stack_back, "\xC3\xFF\x74\x24\x08\xC3", 6); + quick_on_stack_back = (void *)(pv + 1); // inserts `ret` at first +#elif defined(__x86_64__) + // rdi, rsi, rdx, rcx, r8, r9 + /* + 0x0000000000000000: 52 push rdx + 0x0000000000000001: FF E1 jmp rcx + */ + memcpy(__insns, "\x52\xFF\xE1", 3); + /* + 0x0000000000000000: C3 ret + */ + memcpy(quick_on_stack_back, "\xC3", 1); +#elif defined(__aarch64__) + // x0~x7 + /* + 0x0000000000000000: FD 7B BF A9 stp x29, x30, [sp, #-0x10]! + 0x0000000000000004: FD 03 00 91 mov x29, sp + 0x0000000000000008: FE 03 02 AA mov x30, x2 + 0x000000000000000C: 60 00 1F D6 br x3 + */ + memcpy(__insns, "\xFD\x7B\xBF\xA9\xFD\x03\x00\x91\xFE\x03\x02\xAA\x60\x00\x1F\xD6", 16); + /* + 0x0000000000000000: FD 7B C1 A8 ldp x29, x30, [sp], #0x10 + 0x0000000000000004: C0 03 5F D6 ret + */ + memcpy(quick_on_stack_back, "\xFD\x7B\xC1\xA8\xC0\x03\x5F\xD6", 8); +#elif defined(__arm__) + // r0~r3 + /* + 0x0000000000000000: 04 E0 2D E5 str lr, [sp, #-4]! + 0x0000000000000004: 02 E0 A0 E1 mov lr, r2 + 0x0000000000000008: 13 FF 2F E1 bx r3 + */ + memcpy(__insns, "\x04\xE0\x2D\xE5\x02\xE0\xA0\xE1\x13\xFF\x2F\xE1", 12); + if ((pv & 1u) != 0u) { // Thumb + /* + 0x0000000000000000: 08 BC pop {r3} + 0x0000000000000002: 18 47 bx r3 + */ + memcpy((void *)(pv - 1), "\x08\xBC\x18\x47", 4); + } else { + /* + 0x0000000000000000: 04 30 9D E4 pop {r3} + 0x0000000000000004: 13 FF 2F E1 bx r3 + */ + memcpy(quick_on_stack_back, "\x04\x30\x9D\xE4\x13\xFF\x2F\xE1", 8); + } //if +#else +# error "not supported" +#endif + LOGI("init done! quick_on_stack_replace = %p, quick_on_stack_back = %p", + STUBS.generic_stub, quick_on_stack_back); + } //if + } //if +} + +void *JNIEXPORT ndk_dlopen(const char *filename, int flag) +{ + if (SDK_INT>= 24) { +#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__arm__) + return STUBS.quick_on_stack_replace(filename, (void *)flag, + quick_on_stack_back, dlopen); +#else +# error "not supported" +#endif + } //if + + return dlopen(filename, flag); +} + +int JNIEXPORT ndk_dlclose(void *handle) +{ + if (SDK_INT>= 24) { +#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__arm__) + return (int)STUBS.quick_on_stack_replace(handle, NULL, + quick_on_stack_back, dlclose); +#else +# error "not supported" +#endif + } //if + + return dlclose(handle); +} + +const char *JNIEXPORT ndk_dlerror(void) +{ + if (SDK_INT>= 24) { +#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__arm__) + return STUBS.quick_on_stack_replace(NULL, NULL, + quick_on_stack_back, dlerror); +#else +# error "not supported" +#endif + } //if + + return dlerror(); +} + +void *JNIEXPORT ndk_dlsym(void *handle, const char *symbol) +{ + if (SDK_INT>= 24) { +#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__arm__) + return STUBS.quick_on_stack_replace(handle, symbol, + quick_on_stack_back, dlsym); + +#else +# error "not supported" +#endif + } //if + + return dlsym(handle, symbol); +} + +int JNIEXPORT ndk_dladdr(const void *ddr, Dl_info *info) +{ + if (SDK_INT>= 24) { +#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__arm__) + return (int)STUBS.quick_on_stack_replace(ddr, info, + quick_on_stack_back, dladdr); +#else +# error "not supported" +#endif + } //if + + return dladdr(ddr, info); +} \ No newline at end of file diff --git a/xposedmodule/src/main/cpp/dlopen.h b/xposedmodule/src/main/cpp/dlopen.h new file mode 100644 index 0000000..58374ea --- /dev/null +++ b/xposedmodule/src/main/cpp/dlopen.h @@ -0,0 +1,25 @@ +/* + * + * @author : rrrfff@foxmail.com + * https://github.com/rrrfff/ndk_dlopen + * + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void ndk_init(JNIEnv *env); +void *ndk_dlopen(const char *filename, int flag); +int ndk_dlclose(void *handle); +const char *ndk_dlerror(void); +void *ndk_dlsym(void *handle, const char *symbol); +int ndk_dladdr(const void *ddr, Dl_info *info); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/xposedmodule/src/main/cpp/inlineHook.c b/xposedmodule/src/main/cpp/inlineHook.c new file mode 100755 index 0000000..004f13d --- /dev/null +++ b/xposedmodule/src/main/cpp/inlineHook.c @@ -0,0 +1,416 @@ +/* +thumb16 thumb32 arm32 inlineHook +author: ele7enxxh +mail: ele7enxxh@qq.com +website: ele7enxxh.com +modified time: 2015年01月23日 +created time: 2015年11月30日 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "relocate.h" +#include "inlineHook.h" + +#ifndef PAGE_SIZE +#define PAGE_SIZE 4096 +#endif +#define TAG "developerhelper.xposed.inlineHook.native-->" + +#define PAGE_START(addr) (~(PAGE_SIZE - 1) & (addr)) +#define SET_BIT0(addr) (addr | 1) +#define CLEAR_BIT0(addr) (addr & 0xFFFFFFFE) +#define TEST_BIT0(addr) (addr & 1) + +#define ACTION_ENABLE 0 +#define ACTION_DISABLE 1 + +enum hook_status { + REGISTERED, + HOOKED, +}; + +struct inlineHookItem { + uint32_t target_addr; + uint32_t new_addr; + uint32_t **proto_addr; + void *orig_instructions; + int orig_boundaries[4]; + int trampoline_boundaries[20]; + int count; + void *trampoline_instructions; + int length; + int status; + int mode; +}; + +struct inlineHookInfo { + struct inlineHookItem item[1024]; + int size; +}; + +static struct inlineHookInfo info = {0}; + +static int getAllTids(pid_t pid, pid_t *tids) { + char dir_path[32]; + DIR *dir; + int i; + struct dirent *entry; + pid_t tid; + + if (pid < 0) { + snprintf(dir_path, sizeof(dir_path), "/proc/self/task"); + } else { + snprintf(dir_path, sizeof(dir_path), "/proc/%d/task", pid); + } + + dir = opendir(dir_path); + if (dir == NULL) { + return 0; + } + + i = 0; + while ((entry = readdir(dir)) != NULL) { + tid = atoi(entry->d_name); + if (tid != 0 && tid != getpid()) { + tids[i++] = tid; + } + } + closedir(dir); + return i; +} + +static bool doProcessThreadPC(struct inlineHookItem *item, struct pt_regs *regs, int action) { + int offset; + int i; + + switch (action) { + case ACTION_ENABLE: + offset = regs->ARM_pc - CLEAR_BIT0(item->target_addr); + for (i = 0; i < item->count; ++i) { + if (offset == item->orig_boundaries[i]) { + regs->ARM_pc = (uint32_t) item->trampoline_instructions + + item->trampoline_boundaries[i]; + return true; + } + } + break; + case ACTION_DISABLE: + offset = regs->ARM_pc - (int) item->trampoline_instructions; + for (i = 0; i < item->count; ++i) { + if (offset == item->trampoline_boundaries[i]) { + regs->ARM_pc = CLEAR_BIT0(item->target_addr) + item->orig_boundaries[i]; + return true; + } + } + break; + } + + return false; +} + +static void processThreadPC(pid_t tid, struct inlineHookItem *item, int action) { + struct pt_regs regs; + + if (ptrace(PTRACE_GETREGS, tid, NULL, ®s) == 0) { + if (item == NULL) { + int pos; + + for (pos = 0; pos < info.size; ++pos) { + if (doProcessThreadPC(&info.item[pos], ®s, action) == true) { + break; + } + } + } else { + doProcessThreadPC(item, ®s, action); + } + + ptrace(PTRACE_SETREGS, tid, NULL, ®s); + } +} + +static pid_t freeze(struct inlineHookItem *item, int action) { + int count; + pid_t tids[1024]; + pid_t pid; + + pid = -1; + count = getAllTids(getpid(), tids); + if (count> 0) { + pid = fork(); + + if (pid == 0) { + int i; + + for (i = 0; i < count; ++i) { + if (ptrace(PTRACE_ATTACH, tids[i], NULL, NULL) == 0) { + waitpid(tids[i], NULL, WUNTRACED); + processThreadPC(tids[i], item, action); + } + } + + raise(SIGSTOP); + + for (i = 0; i < count; ++i) { + ptrace(PTRACE_DETACH, tids[i], NULL, NULL); + } + + raise(SIGKILL); + } else if (pid> 0) { + waitpid(pid, NULL, WUNTRACED); + } + } + + return pid; +} + +static void unFreeze(pid_t pid) { + if (pid < 0) { + return; + } + + kill(pid, SIGCONT); + wait(NULL); +} + +static bool isExecutableAddr(uint32_t addr) { + FILE *fp; + char line[1024]; + uint32_t start; + uint32_t end; + + fp = fopen("/proc/self/maps", "r"); + if (fp == NULL) { + return false; + } + + while (fgets(line, sizeof(line), fp)) { + if (strstr(line, "r-xp") || strstr(line, "rwxp")) { + start = strtoul(strtok(line, "-"), NULL, 16); + end = strtoul(strtok(NULL, " "), NULL, 16); + if (addr>= start && addr <= end) { + fclose(fp); + return true; + } + } + } + + fclose(fp); + + return false; +} + +static struct inlineHookItem *findInlineHookItem(uint32_t target_addr) { + int i; + + for (i = 0; i < info.size; ++i) { + if (info.item[i].target_addr == target_addr) { + return &info.item[i]; + } + } + + return NULL; +} + +static struct inlineHookItem *addInlineHookItem() { + struct inlineHookItem *item; + + if (info.size>= 1024) { + return NULL; + } + + item = &info.item[info.size]; + ++info.size; + + return item; +} + +static void deleteInlineHookItem(int pos) { + info.item[pos] = info.item[info.size - 1]; + --info.size; +} + +enum ele7en_status +registerInlineHook(uint32_t target_addr, uint32_t new_addr, uint32_t **proto_addr) { + struct inlineHookItem *item; + + if (!isExecutableAddr(target_addr) || !isExecutableAddr(new_addr)) { + return ELE7EN_ERROR_NOT_EXECUTABLE; + } + + item = findInlineHookItem(target_addr); + if (item != NULL) { + if (item->status == REGISTERED) { + return ELE7EN_ERROR_ALREADY_REGISTERED; + } else if (item->status == HOOKED) { + return ELE7EN_ERROR_ALREADY_HOOKED; + } else { + return ELE7EN_ERROR_UNKNOWN; + } + } + + item = addInlineHookItem(); + + item->target_addr = target_addr; + item->new_addr = new_addr; + item->proto_addr = proto_addr; + + item->length = TEST_BIT0(item->target_addr) ? 12 : 8; + item->orig_instructions = malloc(item->length); + __android_log_print(ANDROID_LOG_ERROR, TAG, "item->orig_instructions length=%d", item->length); + memcpy(item->orig_instructions, (void *) CLEAR_BIT0(item->target_addr), item->length); + __android_log_print(ANDROID_LOG_ERROR, TAG, "item->orig_instructions address=%p", + item->orig_instructions); + item->trampoline_instructions = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + relocateInstruction(item->target_addr, item->orig_instructions, item->length, + item->trampoline_instructions, item->orig_boundaries, + item->trampoline_boundaries, &item->count); + + item->status = REGISTERED; + + return ELE7EN_OK; +} + +static void doInlineUnHook(struct inlineHookItem *item, int pos) { + mprotect((void *) PAGE_START(CLEAR_BIT0(item->target_addr)), PAGE_SIZE * 2, + PROT_READ | PROT_WRITE | PROT_EXEC); + memcpy((void *) CLEAR_BIT0(item->target_addr), item->orig_instructions, item->length); + mprotect((void *) PAGE_START(CLEAR_BIT0(item->target_addr)), PAGE_SIZE * 2, + PROT_READ | PROT_EXEC); + munmap(item->trampoline_instructions, PAGE_SIZE); + free(item->orig_instructions); + + deleteInlineHookItem(pos); + + __builtin___clear_cache(CLEAR_BIT0(item->target_addr), + CLEAR_BIT0(item->target_addr) + item->length); +} + +enum ele7en_status inlineUnHook(uint32_t target_addr) { + int i; + + for (i = 0; i < info.size; ++i) { + if (info.item[i].target_addr == target_addr && info.item[i].status == HOOKED) { + pid_t pid; + + pid = freeze(&info.item[i], ACTION_DISABLE); + + doInlineUnHook(&info.item[i], i); + + unFreeze(pid); + + return ELE7EN_OK; + } + } + + return ELE7EN_ERROR_NOT_HOOKED; +} + +void inlineUnHookAll() { + pid_t pid; + int i; + + pid = freeze(NULL, ACTION_DISABLE); + + for (i = 0; i < info.size; ++i) { + if (info.item[i].status == HOOKED) { + doInlineUnHook(&info.item[i], i); + --i; + } + } + + unFreeze(pid); +} + +static void doInlineHook(struct inlineHookItem *item) { + mprotect((void *) PAGE_START(CLEAR_BIT0(item->target_addr)), PAGE_SIZE * 2, + PROT_READ | PROT_WRITE | PROT_EXEC); + + if (item->proto_addr != NULL) { + *(item->proto_addr) = TEST_BIT0(item->target_addr) ? (uint32_t *) SET_BIT0( + (uint32_t) item->trampoline_instructions) : item->trampoline_instructions; + } + + if (TEST_BIT0(item->target_addr)) { + int i; + + i = 0; + if (CLEAR_BIT0(item->target_addr) % 4 != 0) { + ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xBF00; // NOP + } + ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xF8DF; + ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = 0xF000; // LDR.W PC, [PC] + ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = item->new_addr & 0xFFFF; + ((uint16_t *) CLEAR_BIT0(item->target_addr))[i++] = item->new_addr>> 16; + } else { + ((uint32_t *) (item->target_addr))[0] = 0xe51ff004; // LDR PC, [PC, #-4] + ((uint32_t *) (item->target_addr))[1] = item->new_addr; + } + + mprotect((void *) PAGE_START(CLEAR_BIT0(item->target_addr)), PAGE_SIZE * 2, + PROT_READ | PROT_EXEC); + + item->status = HOOKED; + + __builtin___clear_cache(CLEAR_BIT0(item->target_addr), + CLEAR_BIT0(item->target_addr) + item->length); +} + +enum ele7en_status inlineHook(uint32_t target_addr) { + int i; + struct inlineHookItem *item; + + item = NULL; + for (i = 0; i < info.size; ++i) { + if (info.item[i].target_addr == target_addr) { + item = &info.item[i]; + break; + } + } + + if (item == NULL) { + return ELE7EN_ERROR_NOT_REGISTERED; + } + + if (item->status == REGISTERED) { + pid_t pid; + + pid = freeze(item, ACTION_ENABLE); + + doInlineHook(item); + + unFreeze(pid); + + return ELE7EN_OK; + } else if (item->status == HOOKED) { + return ELE7EN_ERROR_ALREADY_HOOKED; + } else { + return ELE7EN_ERROR_UNKNOWN; + } +} + +void inlineHookAll() { + pid_t pid; + int i; + + pid = freeze(NULL, ACTION_ENABLE); + + for (i = 0; i < info.size; ++i) { + if (info.item[i].status == REGISTERED) { + doInlineHook(&info.item[i]); + } + } + + unFreeze(pid); +} diff --git a/xposedmodule/src/main/cpp/inlineHook.h b/xposedmodule/src/main/cpp/inlineHook.h new file mode 100755 index 0000000..86bc6a8 --- /dev/null +++ b/xposedmodule/src/main/cpp/inlineHook.h @@ -0,0 +1,33 @@ +#ifndef _INLINEHOOK_H +#define _INLINEHOOK_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum ele7en_status { + ELE7EN_ERROR_UNKNOWN = -1, + ELE7EN_OK = 0, + ELE7EN_ERROR_NOT_INITIALIZED, + ELE7EN_ERROR_NOT_EXECUTABLE, + ELE7EN_ERROR_NOT_REGISTERED, + ELE7EN_ERROR_NOT_HOOKED, + ELE7EN_ERROR_ALREADY_REGISTERED, + ELE7EN_ERROR_ALREADY_HOOKED, + ELE7EN_ERROR_SO_NOT_FOUND, + ELE7EN_ERROR_FUNCTION_NOT_FOUND +}; + +enum ele7en_status registerInlineHook(uint32_t target_addr, uint32_t new_addr, uint32_t **proto_addr); +enum ele7en_status inlineUnHook(uint32_t target_addr); +void inlineUnHookAll(); +enum ele7en_status inlineHook(uint32_t target_addr); +void inlineHookAll(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/xposedmodule/src/main/cpp/native.cpp b/xposedmodule/src/main/cpp/native.cpp new file mode 100644 index 0000000..1305934 --- /dev/null +++ b/xposedmodule/src/main/cpp/native.cpp @@ -0,0 +1,55 @@ +// +// Created by WrBug on 2018年3月23日. +// +#include "native.h" +#include "inlineHook.h" + +#define TAG "developerhelper.xposed.native.native-->" + + +JNIEXPORT void JNICALL Java_com_wrbug_developerhelper_xposed_dumpdex_Native_dump + (JNIEnv *env, jclass obj, jstring packageName) { + + static bool is_hook = false; + char *p = (char *) env->GetStringUTFChars(packageName, 0); + __android_log_print(ANDROID_LOG_ERROR, TAG, "%s", p); + if (is_hook) { + __android_log_print(ANDROID_LOG_INFO, TAG, "hooked ignore"); + return; + } + init_package_name(p); + env->ReleaseStringChars(packageName, (const jchar *) p); + ndk_init(env); + void *handle = ndk_dlopen("libart.so", RTLD_NOW); + if (handle == NULL) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Error: unable to find the SO : libart.so"); + return; + } + void *open_common_addr = ndk_dlsym(handle, get_open_function_flag()); + if (open_common_addr == NULL) { + __android_log_print(ANDROID_LOG_ERROR, TAG, + "Error: unable to find the Symbol : "); + return; + } +#if defined(__aarch64__) + A64HookFunction(open_common_addr, get_new_open_function_addr(), get_old_open_function_addr()); + __android_log_print(ANDROID_LOG_DEFAULT, TAG, "loaded so: libart.so"); +#elif defined(__arm__) + if (registerInlineHook((uint32_t) open_common_addr, (uint32_t) get_new_open_function_addr(), + (uint32_t **) get_old_open_function_addr()) != ELE7EN_OK) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "register1 hook failed!"); + return; + } else { + __android_log_print(ANDROID_LOG_ERROR, TAG, "register1 hook success!"); + } + if (inlineHook((uint32_t) open_common_addr) != ELE7EN_OK) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "register2 hook failed!"); + return; + } else { + __android_log_print(ANDROID_LOG_ERROR, TAG, "register2 hook success!"); + } + __android_log_print(ANDROID_LOG_DEFAULT, TAG, "loaded so: libart.so"); +#endif + __android_log_print(ANDROID_LOG_INFO, TAG, "hook init complete"); + is_hook = true; +} diff --git a/xposedmodule/src/main/cpp/native.h b/xposedmodule/src/main/cpp/native.h new file mode 100644 index 0000000..ba727d4 --- /dev/null +++ b/xposedmodule/src/main/cpp/native.h @@ -0,0 +1,43 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dlopen.h" +#include "util/deviceutils.h" +#include "util/fileutils.h" + +#if defined(__aarch64__) + +#include "And64InlineHook.hpp" + +#elif defined(__arm__) +#include "inlineHook.h" +#endif + +#ifndef _Included_com_wrbug_xposeddemo_Native +#define _Included_com_wrbug_xposeddemo_Native +#ifdef __cplusplus +extern "C" { + +#endif +/* + * Class: com_wrbug_xposeddemo_Native + * Method: test + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT void JNICALL Java_com_wrbug_developerhelper_xposed_dumpdex_Native_dump + (JNIEnv *, jclass, jstring); + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/xposedmodule/src/main/cpp/readme.md b/xposedmodule/src/main/cpp/readme.md new file mode 100644 index 0000000..ff4f8fd --- /dev/null +++ b/xposedmodule/src/main/cpp/readme.md @@ -0,0 +1,10 @@ +# ndk说明 + +ndk主要针对android8.0及以上机型使用,通过hook底层的代码,将数据dump出来,目前360加固测试通过,其他正在寻找方案 + + +## 感谢 + +[https://github.com/ele7enxxh/Android-Inline-Hook](https://github.com/ele7enxxh/Android-Inline-Hook) + +[https://github.com/rrrfff/ndk_dlopen](https://github.com/rrrfff/ndk_dlopen) \ No newline at end of file diff --git a/xposedmodule/src/main/cpp/relocate.c b/xposedmodule/src/main/cpp/relocate.c new file mode 100755 index 0000000..36fd383 --- /dev/null +++ b/xposedmodule/src/main/cpp/relocate.c @@ -0,0 +1,612 @@ +/* +relocate instruction +author: ele7enxxh +mail: ele7enxxh@qq.com +website: ele7enxxh.com +modified time: 2016年10月17日 +created time: 2015年01月17日 +*/ + +#include "relocate.h" + +#define ALIGN_PC(pc) (pc & 0xFFFFFFFC) + +enum INSTRUCTION_TYPE { + // B

      AltStyle によって変換されたページ (->オリジナル) /