Skip to content

Commit

Permalink
Added app strategy editor
Browse files Browse the repository at this point in the history
  • Loading branch information
zhufucdev committed Nov 17, 2022
1 parent d606860 commit 7eb6e16
Show file tree
Hide file tree
Showing 21 changed files with 496 additions and 16 deletions.
4 changes: 4 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
// Xposed
implementation "com.highcapable.yukihookapi:api:${yuki_version}"
compileOnly 'de.robv.android.xposed:api:82'
Expand Down
10 changes: 7 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission"/>

<application
android:name="com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication"
Expand All @@ -22,7 +24,9 @@
android:supportsRtl="true"
android:theme="@style/Theme.MotionEmulator"
tools:targetApi="32">

<activity
android:name=".AppStrategyActivity"
android:exported="false"/>
<activity
android:name=".EmulateActivity"
android:exported="false"/>
Expand All @@ -42,10 +46,10 @@
android:name=".RecordActivity"
android:exported="false"
android:label="@string/title_activity_record"/>

<provider
android:authorities="com.zhufucdev.motion_emulator.event_provider"
android:name=".hook_frontend.EventProvider"
android:authorities="com.zhufucdev.motion_emulator.event_provider"
android:exported="true"/>

<meta-data
Expand Down
130 changes: 130 additions & 0 deletions app/src/main/java/com/zhufucdev/motion_emulator/AppStrategyActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.zhufucdev.motion_emulator

import android.database.MatrixCursor
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.BaseColumns
import android.view.Menu
import android.view.MenuItem
import android.widget.CursorAdapter
import android.widget.SearchView
import android.widget.SimpleCursorAdapter
import com.zhufucdev.motion_emulator.apps.AppItemAdapter
import com.zhufucdev.motion_emulator.apps.AppMetas
import com.zhufucdev.motion_emulator.apps.SnappingLinearSmoothScroller
import com.zhufucdev.motion_emulator.databinding.ActivityAppStrategyBinding

class AppStrategyActivity : AppCompatActivity() {
private lateinit var binding: ActivityAppStrategyBinding
private lateinit var mainAdapter: AppItemAdapter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAppStrategyBinding.inflate(layoutInflater)
setContentView(binding.root)

AppMetas.require(this)
initializeList()
}

private fun initializeList() {
mainAdapter = AppItemAdapter()
binding.listApps.adapter = mainAdapter

binding.swipeRefresh.setOnRefreshListener {
binding.swipeRefresh.isRefreshing = true
mainAdapter.refresh()
binding.swipeRefresh.isRefreshing = false
}
}

private fun initializeStrategy(menu: Menu) {
menu.findItem(R.id.bypass_mode).isChecked = AppMetas.bypassMode
menu.findItem(R.id.show_system).isChecked = AppMetas.showSystemApps
}

private fun initializeSearch(view: SearchView) {
val adapter = SimpleCursorAdapter(
this,
android.R.layout.simple_list_item_2,
null,
arrayOf("name", "packageName"),
intArrayOf(android.R.id.text1, android.R.id.text2),
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER
)

view.suggestionsAdapter = adapter

var lastResults = emptyList<Int>()
view.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}

override fun onQueryTextChange(newText: String): Boolean {
val cursor = MatrixCursor(arrayOf(BaseColumns._ID, "name", "packageName"))
var i = 0
val results = arrayListOf<Int>()
mainAdapter.appsSnapshot.forEachIndexed { index, meta ->
if (meta.name?.contains(newText, true) == true
|| meta.packageName.contains(newText, true)
) {
cursor.addRow(arrayOf(i, meta.name, meta.packageName))
results.add(index)
}
i++
}
lastResults = results
adapter.changeCursor(cursor)
return true
}
})

view.setOnSuggestionListener(object : SearchView.OnSuggestionListener {
override fun onSuggestionSelect(position: Int): Boolean {
return false
}

override fun onSuggestionClick(position: Int): Boolean {
val table = lastResults
if (table.size > position) {
val scroller = SnappingLinearSmoothScroller(this@AppStrategyActivity, table[position])
binding.listApps.layoutManager!!.startSmoothScroll(scroller)
}
view.isIconified = false
view.onActionViewCollapsed()
return true
}
})
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.app_strategy_actionbar, menu)
initializeStrategy(menu)
return true
}

override fun onOptionsItemSelected(item: MenuItem) =
when (item.itemId) {
R.id.show_system -> {
item.isChecked = !item.isChecked
AppMetas.showSystemApps = item.isChecked
mainAdapter.refresh()
true
}

R.id.bypass_mode -> {
item.isChecked = !item.isChecked
AppMetas.bypassMode = item.isChecked
true
}

else -> false
}

override fun onPrepareOptionsMenu(menu: Menu): Boolean {
val actionView = menu.findItem(R.id.app_bar_search).actionView as SearchView
initializeSearch(actionView)
return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
}

private fun registerListeners() {
binding.statusCard.setOnClickListener {
startActivity(
Intent(this, AppStrategyActivity::class.java)
)
}
binding.recordCard.setOnClickListener {
startActivity(
Intent(this, RecordActivity::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.zhufucdev.motion_emulator
import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.database.MatrixCursor
import android.graphics.Point
import android.location.Location
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/java/com/zhufucdev/motion_emulator/Utility.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.zhufucdev.motion_emulator

import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.res.Configuration
import android.content.res.Resources
import android.util.TypedValue
Expand Down Expand Up @@ -156,4 +157,6 @@ fun Point.lenTo(other: Point): Double =

fun AMap.unifyTheme(resources: Resources) {
mapType = if (isDarkModeEnabled(resources)) AMap.MAP_TYPE_NIGHT else AMap.MAP_TYPE_NORMAL
}
}

val ApplicationInfo.isSystemApp get() = flags and ApplicationInfo.FLAG_SYSTEM != 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.zhufucdev.motion_emulator.apps

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.AppCompatCheckBox
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView
import com.zhufucdev.motion_emulator.R

class AppItemAdapter : RecyclerView.Adapter<AppItemViewHolder>() {
var appsSnapshot = emptyList<AppMeta>()

init {
refresh()
setHasStableIds(true)
}

fun refresh() {
appsSnapshot = AppMetas.list()
notifyDataSetChanged()
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
AppItemViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_app, parent, false)
)

override fun getItemCount(): Int = appsSnapshot.size

override fun onBindViewHolder(holder: AppItemViewHolder, position: Int) {
val meta = appsSnapshot[position]
holder.use(meta)
holder.onToggle {
if (it) {
AppMetas.markPositive(meta.packageName)
}
else {
AppMetas.markNegative(meta.packageName)
}
}
}

override fun getItemId(position: Int): Long {
return appsSnapshot[position].packageName.hashCode().toLong()
}
}

class AppItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val title by lazy { itemView.findViewById<AppCompatTextView>(R.id.title_app_name) }
private val packageName by lazy { itemView.findViewById<AppCompatTextView>(R.id.text_package_name) }
private val icon by lazy { itemView.findViewById<AppCompatImageView>(R.id.icon_app) }
private val toggle by lazy { itemView.findViewById<AppCompatCheckBox>(R.id.toggle_app) }

fun use(meta: AppMeta) {
toggle.setOnCheckedChangeListener(null)
itemView.setOnClickListener(null)
title.text = meta.name ?: title.context.getText(R.string.title_unknown)
packageName.text = meta.packageName
icon.setImageDrawable(meta.icon ?: AppCompatResources.getDrawable(icon.context, R.drawable.ic_baseline_help_24))
toggle.isChecked = meta.positive
}

fun onToggle(l: (Boolean) -> Unit) {
toggle.setOnCheckedChangeListener { _, isChecked ->
l(isChecked)
}

itemView.setOnClickListener {
toggle.isChecked = !toggle.isChecked
l(toggle.isChecked)
}
}
}

class SnappingLinearSmoothScroller(context: Context, position: Int) : LinearSmoothScroller(context) {
init {
targetPosition = position
}

override fun getVerticalSnapPreference(): Int = SNAP_TO_START
}
Loading

0 comments on commit 7eb6e16

Please sign in to comment.