├── .gitignore
├── .idea
└── vcs.xml
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── aidl
│ ├── android
│ │ └── content
│ │ │ └── om
│ │ │ └── OverlayInfo.aidl
│ └── tk
│ │ └── zwander
│ │ └── overlaymanager
│ │ ├── IRootBridge.aidl
│ │ └── proxy
│ │ └── OverlayInfo.aidl
│ ├── ic_launcher-web.png
│ ├── java
│ └── tk
│ │ └── zwander
│ │ └── overlaymanager
│ │ ├── App.kt
│ │ ├── MainActivity.kt
│ │ ├── data
│ │ ├── BatchedUpdate.kt
│ │ ├── ObservableHashMap.kt
│ │ └── TargetData.kt
│ │ ├── proxy
│ │ ├── IOverlayManager.kt
│ │ └── OverlayInfo.kt
│ │ ├── root
│ │ ├── IRootBridgeImpl.kt
│ │ └── RootBridgeService.kt
│ │ ├── ui
│ │ ├── OverlayAdapter.kt
│ │ └── TargetAdapter.kt
│ │ ├── util
│ │ ├── DividerItemDecoration.kt
│ │ └── Utils.kt
│ │ └── views
│ │ ├── CheckableImageButton.kt
│ │ └── TooltippedImageButton.kt
│ └── res
│ ├── drawable-v24
│ ├── arrow_collapse_vertical.xml
│ ├── arrow_expand_vertical.xml
│ ├── ic_launcher_foreground.xml
│ └── playlist_remove.xml
│ ├── drawable
│ ├── divider.xml
│ ├── highest_priority_selector.xml
│ ├── ic_baseline_error_24.xml
│ ├── ic_baseline_playlist_add_check_24.xml
│ ├── ic_baseline_vertical_align_bottom_24.xml
│ ├── ic_baseline_vertical_align_bottom_24_disabled.xml
│ ├── ic_baseline_vertical_align_top_24.xml
│ ├── ic_baseline_vertical_align_top_24_disabled.xml
│ ├── ic_done_black_24dp.xml
│ ├── ic_launcher_background.xml
│ ├── lowest_priority_selector.xml
│ └── title_border.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── overlay_item.xml
│ ├── progress.xml
│ └── target_item.xml
│ ├── menu
│ └── search.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-parcelize'
4 |
5 | android {
6 | compileSdkVersion 33
7 | defaultConfig {
8 | applicationId "tk.zwander.overlaymanager"
9 | minSdkVersion 26
10 | targetSdkVersion 33
11 | versionCode 4
12 | versionName versionCode.toString()
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | packagingOptions {
21 | exclude 'META-INF/atomicfu.kotlin_module'
22 | }
23 | kotlinOptions {
24 | jvmTarget = "11"
25 | }
26 | buildFeatures {
27 | viewBinding = true
28 | }
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_11
31 | targetCompatibility JavaVersion.VERSION_11
32 | }
33 | }
34 |
35 | dependencies {
36 | implementation fileTree(dir: 'libs', include: ['*.jar'])
37 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
38 |
39 | implementation 'androidx.appcompat:appcompat:1.5.1'
40 | implementation 'androidx.core:core-ktx:1.9.0'
41 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
42 | implementation 'androidx.recyclerview:recyclerview:1.2.1'
43 | implementation 'androidx.cardview:cardview:1.0.0'
44 | implementation 'com.google.android.material:material:1.8.0-beta01'
45 |
46 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
47 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
48 |
49 | implementation "com.github.topjohnwu.libsu:core:3.2.1"
50 | implementation "com.github.topjohnwu.libsu:service:3.1.2"
51 | implementation 'com.github.2hamed:ProgressCircula:v0.2.1'
52 | implementation 'com.squareup.picasso:picasso:2.71828'
53 | implementation 'com.github.sephiroth74:android-target-tooltip:2.0.4'
54 |
55 | implementation 'dev.rikka.shizuku:api:12.2.0'
56 | implementation 'dev.rikka.shizuku:provider:12.2.0'
57 | implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3'
58 | }
59 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
17 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/aidl/android/content/om/OverlayInfo.aidl:
--------------------------------------------------------------------------------
1 | package android.content.om;
2 |
3 | parcelable OverlayInfo;
--------------------------------------------------------------------------------
/app/src/main/aidl/tk/zwander/overlaymanager/IRootBridge.aidl:
--------------------------------------------------------------------------------
1 | package tk.zwander.overlaymanager;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 | import tk.zwander.overlaymanager.proxy.OverlayInfo;
6 |
7 | interface IRootBridge {
8 | void destroy() = 16777114;
9 |
10 | Map getAllOverlays() = 1;
11 | List getOverlayInfosForTarget(in String packageName) = 2;
12 | OverlayInfo getOverlayInfo(in String packageName) = 3;
13 | OverlayInfo getOverlayInfoByIdentifier(in String identifier) = 11;
14 |
15 | boolean setOverlayEnabled(in String packageName, boolean enabled) = 4;
16 | void setOverlayEnabledByIdentifier(in String identifier, boolean enabled) = 5;
17 | boolean setOverlayEnabledExclusive(in String packageName, boolean enabled) = 6;
18 | boolean setOverlayEnabledExclusiveInCategory(in String packageName) = 7;
19 | boolean setOverlayPriority(in String packageName, in String packageToOutrank) = 8;
20 | boolean setOverlayHighestPriority(in String packageName) = 9;
21 | boolean setOverlayLowestPriority(in String packageName) = 10;
22 | void clearCache(in String packageName) = 12;
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/aidl/tk/zwander/overlaymanager/proxy/OverlayInfo.aidl:
--------------------------------------------------------------------------------
1 | package tk.zwander.overlaymanager.proxy;
2 |
3 | parcelable OverlayInfo;
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/OverlayManager/950d9c07859c3c966ab6443fe7ff782100763c87/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/overlaymanager/App.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.overlaymanager
2 |
3 | import android.app.Application
4 | import android.content.ComponentName
5 | import android.content.Context
6 | import android.content.Intent
7 |
8 | import rikka.shizuku.Shizuku
9 | import rikka.shizuku.Shizuku.UserServiceArgs
10 | import android.os.IBinder
11 |
12 | import android.content.ServiceConnection
13 | import android.os.Build
14 | import android.util.Log
15 | import com.topjohnwu.superuser.Shell
16 | import com.topjohnwu.superuser.ipc.RootService
17 | import kotlinx.coroutines.*
18 | import org.lsposed.hiddenapibypass.HiddenApiBypass
19 | import tk.zwander.overlaymanager.root.IRootBridgeImpl
20 | import tk.zwander.overlaymanager.root.RootBridgeService
21 | import tk.zwander.overlaymanager.util.shizukuAvailable
22 |
23 |
24 | class App : Application(), CoroutineScope by MainScope() {
25 | companion object {
26 | init {
27 | Shell.enableVerboseLogging = BuildConfig.DEBUG
28 | Shell.setDefaultBuilder(Shell.Builder.create()
29 | .setFlags(Shell.FLAG_REDIRECT_STDERR)
30 | .setTimeout(10)
31 | )
32 | }
33 | }
34 |
35 | val receiver by lazy { Receiver() }
36 |
37 | override fun onCreate() {
38 | super.onCreate()
39 |
40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
41 | HiddenApiBypass.setHiddenApiExemptions("L")
42 | }
43 |
44 | async(Dispatchers.IO) {
45 | if (!shizukuAvailable && Shell.rootAccess()) {
46 | receiver.bindRoot(this@App)
47 | }
48 | }
49 | }
50 |
51 | class Receiver : CoroutineScope by MainScope() {
52 | private val userServiceStandaloneProcessArgs = UserServiceArgs(
53 | ComponentName(
54 | BuildConfig.APPLICATION_ID,
55 | IRootBridgeImpl::class.java.name
56 | )
57 | )
58 | .daemon(false)
59 | .tag("OverlayManager")
60 | .processNameSuffix("oms")
61 | .debuggable(BuildConfig.DEBUG)
62 | .version(BuildConfig.VERSION_CODE)
63 |
64 | private val userServiceConnection: ServiceConnection = object : ServiceConnection {
65 | override fun onServiceConnected(componentName: ComponentName, binder: IBinder?) {
66 | if (binder != null && binder.pingBinder()) {
67 | val service: IRootBridge = IRootBridge.Stub.asInterface(binder)
68 | ipc = service
69 | }
70 | }
71 |
72 | override fun onServiceDisconnected(componentName: ComponentName) {
73 | ipc = null
74 | }
75 | }
76 |
77 | private val queuedActions = ArrayList<(IRootBridge) -> Unit>()
78 |
79 | private val ipcLock = Any()
80 |
81 | private var ipc: IRootBridge? = null
82 | set(value) {
83 | launch {
84 | synchronized(ipcLock) {
85 | field = value
86 |
87 | if (value != null) {
88 | queuedActions.forEach { it.invoke(ipc!!) }
89 | queuedActions.clear()
90 | }
91 | }
92 | }
93 | }
94 |
95 | fun postAction(action: (IRootBridge) -> Unit) {
96 | if (ipc == null) {
97 | queuedActions.add(action)
98 | } else {
99 | action.invoke(ipc!!)
100 | }
101 | }
102 |
103 | fun tryBindShizuku() {
104 | try {
105 | Log.e("OverlayManager", "starting Shizuku")
106 | if (Shizuku.getVersion() >= 10) {
107 | Shizuku.bindUserService(userServiceStandaloneProcessArgs, userServiceConnection)
108 | }
109 | } catch (tr: Throwable) {
110 | Log.e("OverlayManager", "error starting Shizuku")
111 | tr.printStackTrace()
112 | }
113 | }
114 |
115 | fun bindRoot(context: Context) {
116 | RootService.bind(
117 | Intent(context, RootBridgeService::class.java),
118 | userServiceConnection
119 | )
120 | }
121 |
122 | suspend fun awaitBridge(): IRootBridge {
123 | return withContext(Dispatchers.IO) {
124 | while (ipc == null) {}
125 | ipc!!
126 | }
127 | }
128 | }
129 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/overlaymanager/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.overlaymanager
2 |
3 | import android.content.Context
4 | import android.content.pm.PackageManager
5 | import android.content.res.Configuration
6 | import android.os.Bundle
7 | import android.os.Handler
8 | import android.util.Log
9 | import android.view.Menu
10 | import android.view.MenuItem
11 | import android.view.View
12 | import android.view.ViewTreeObserver
13 | import android.view.inputmethod.InputMethodManager
14 | import androidx.appcompat.app.AppCompatActivity
15 | import androidx.appcompat.widget.SearchView
16 | import androidx.core.view.isVisible
17 | import androidx.recyclerview.widget.LinearLayoutManager
18 | import androidx.recyclerview.widget.RecyclerView
19 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
20 | import com.topjohnwu.superuser.Shell
21 | import kotlinx.coroutines.*
22 | import tk.zwander.overlaymanager.data.BatchedUpdate
23 | import tk.zwander.overlaymanager.data.ObservableHashMap
24 | import tk.zwander.overlaymanager.databinding.ActivityMainBinding
25 | import tk.zwander.overlaymanager.proxy.OverlayInfo
26 | import tk.zwander.overlaymanager.ui.TargetAdapter
27 | import tk.zwander.overlaymanager.util.*
28 | import java.util.*
29 | import kotlin.collections.HashMap
30 |
31 | class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
32 | private val batchedUpdates = ObservableHashMap()
33 | private val targetAdapter by lazy { TargetAdapter(this, batchedUpdates) }
34 | private val imm by lazy { getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager }
35 | private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
36 |
37 | private var progressItem: MenuItem? = null
38 | private var unappliedAlert: MenuItem? = null
39 | private var doneLoading = false
40 | private var searchView: SearchView? = null
41 |
42 | private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
43 | val closed = imm::class.java
44 | .getMethod("getInputMethodWindowVisibleHeight")
45 | .invoke(imm)!!.toString().toInt() <= 0
46 |
47 | if (closed) onKeyboardClose()
48 | else onKeyboardOpen()
49 | }
50 |
51 | override fun onCreate(savedInstanceState: Bundle?) {
52 | super.onCreate(savedInstanceState)
53 | setContentView(binding.root)
54 | setSupportActionBar(binding.bottomBar)
55 | setTitle(R.string.app_name)
56 |
57 | launch(Dispatchers.IO) {
58 | if (Shell.rootAccess() || shizukuGranted) {
59 | doLoad()
60 | } else if (shizukuAvailable && !shizukuGranted) {
61 | requestShizukuPermission(100) { _, result ->
62 | if (result == PackageManager.PERMISSION_GRANTED) {
63 | doLoad()
64 | } else {
65 | finish()
66 | }
67 | }
68 | } else {
69 | launch(Dispatchers.Main) {
70 | MaterialAlertDialogBuilder(this@MainActivity)
71 | .setTitle(R.string.shizuku_root_required_title)
72 | .setMessage(R.string.shizuku_root_required_msg)
73 | .setCancelable(false)
74 | .setPositiveButton(android.R.string.ok) { _, _ ->
75 | finish()
76 | }
77 | .setNegativeButton(R.string.shizuku_root_get_shizuku) { _, _ ->
78 | launchUrl("https://github.com/RikkaApps/Shizuku/releases")
79 | finish()
80 | }
81 | .show()
82 | }
83 | }
84 | }
85 | }
86 |
87 | override fun onRequestPermissionsResult(
88 | requestCode: Int,
89 | permissions: Array,
90 | grantResults: IntArray
91 | ) {
92 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
93 |
94 | if (requestCode == 100) {
95 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
96 | doLoad()
97 | } else {
98 | finish()
99 | }
100 | }
101 | }
102 |
103 | private fun doLoad() = launch(Dispatchers.Main) {
104 | if (shizukuAvailable) {
105 | app.receiver.tryBindShizuku()
106 | }
107 |
108 | binding.targetList.adapter = targetAdapter
109 |
110 | binding.targetList.layoutManager = LinearLayoutManager(this@MainActivity, RecyclerView.VERTICAL, false)
111 | binding.targetList.addItemDecoration(DividerItemDecoration(this@MainActivity, RecyclerView.VERTICAL))
112 |
113 | binding.content.layoutTransition = layoutTransition
114 |
115 | binding.matchOverlays.setOnCheckedChangeListener { _, isChecked ->
116 | targetAdapter.matchOverlays = isChecked
117 | }
118 |
119 | val rootBridge = app.receiver.awaitBridge()
120 |
121 | @Suppress("UNCHECKED_CAST")
122 | targetAdapter.setItems(
123 | packageManager,
124 | rootBridge.allOverlays as MutableMap>
125 | )
126 |
127 | doneLoading = true
128 | progressItem?.isVisible = false
129 | binding.changeAllWrapper.isVisible = true
130 | binding.apply.isVisible = true
131 |
132 | binding.apply.setOnClickListener {
133 | MaterialAlertDialogBuilder(this@MainActivity)
134 | .setTitle(R.string.apply_changes)
135 | .setMessage(R.string.apply_overlays_desc)
136 | .setPositiveButton(android.R.string.ok) {_, _ ->
137 | val copy = HashMap(batchedUpdates)
138 | batchedUpdates.clear()
139 |
140 | copy.forEach { (_, u) ->
141 | u(rootBridge)
142 | }
143 |
144 | targetAdapter.notifyChanged()
145 | }
146 | .setNegativeButton(android.R.string.cancel, null)
147 | .show()
148 | }
149 |
150 | binding.enableAll.setOnClickListener {
151 | targetAdapter.orig.forEach { td ->
152 | td.info.forEach { info ->
153 | val i = info.createEnabledUpdate(true)
154 | batchedUpdates[i.first] = i.second
155 |
156 | info.showEnabled = true
157 | }
158 | }
159 | targetAdapter.notifyChanged()
160 | }
161 |
162 | binding.disableAll.setOnClickListener {
163 | targetAdapter.orig.forEach { td ->
164 | td.info.forEach { info ->
165 | val i = info.createEnabledUpdate(false)
166 | batchedUpdates[i.first] = i.second
167 |
168 | info.showEnabled = false
169 | }
170 | }
171 | targetAdapter.notifyChanged()
172 | }
173 |
174 | binding.expandAll.setOnClickListener {
175 | targetAdapter.setAllExpanded(true)
176 | }
177 |
178 | binding.collapseAll.setOnClickListener {
179 | targetAdapter.setAllExpanded(false)
180 | }
181 |
182 | binding.content.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
183 | }
184 |
185 | override fun onDestroy() {
186 | super.onDestroy()
187 |
188 | cancel()
189 | binding.content.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
190 | }
191 |
192 | override fun setTitle(titleId: Int) {
193 | title = getText(titleId)
194 | }
195 |
196 | override fun setTitle(title: CharSequence?) {
197 | binding.titleText.text = title
198 | super.setTitle(null)
199 | }
200 |
201 | override fun onCreateOptionsMenu(m: Menu): Boolean {
202 | val menu = binding.actionMenu.menu
203 | menuInflater.inflate(R.menu.search, menu)
204 |
205 | val searchItem = menu.findItem(R.id.action_search)
206 | searchView = searchItem?.actionView as SearchView?
207 |
208 | searchView?.setOnSearchClickListener {
209 | binding.matchOverlays.isVisible = true
210 | binding.changeAllWrapper.isVisible = false
211 | }
212 |
213 | searchView?.setOnCloseListener {
214 | binding.matchOverlays.isVisible = false
215 | binding.changeAllWrapper.isVisible = true
216 | false
217 | }
218 |
219 | searchView?.setOnQueryTextListener(targetAdapter)
220 |
221 | progressItem = menu.findItem(R.id.progress)
222 | if (doneLoading) {
223 | progressItem?.isVisible = false
224 | }
225 |
226 | unappliedAlert = menu.findItem(R.id.unappliedChanges)
227 | unappliedAlert?.isVisible = batchedUpdates.isNotEmpty()
228 | unappliedAlert?.setOnMenuItemClickListener {
229 | MaterialAlertDialogBuilder(this)
230 | .setTitle(R.string.unapplied_changes)
231 | .setMessage(R.string.unapplied_changes_desc)
232 | .setPositiveButton(android.R.string.ok, null)
233 | .show()
234 | true
235 | }
236 |
237 | batchedUpdates.addObserver { _, _ ->
238 | val notEmpty = batchedUpdates.isNotEmpty()
239 | unappliedAlert?.isVisible = notEmpty
240 | }
241 |
242 | return true
243 | }
244 |
245 | private fun onKeyboardOpen() {
246 | binding.titleText.isVisible = false
247 | binding.titleBorder.isVisible = false
248 | }
249 |
250 | private fun onKeyboardClose() {
251 | binding.titleText.isVisible = true
252 | binding.titleBorder.isVisible = true
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/overlaymanager/data/BatchedUpdate.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.overlaymanager.data
2 |
3 | import tk.zwander.overlaymanager.IRootBridge
4 |
5 | data class BatchedUpdate(val key: String, val action: (IRootBridge) -> Unit): (IRootBridge) -> Unit {
6 | override fun invoke(p1: IRootBridge) {
7 | action(p1)
8 | }
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/overlaymanager/data/ObservableHashMap.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.overlaymanager.data
2 |
3 | import java.util.*
4 | import kotlin.collections.HashMap
5 |
6 | class ObservableHashMap : Observable(), MutableMap {
7 | private val wrapped = HashMap()
8 |
9 | override val size: Int
10 | get() = wrapped.size
11 | override val entries: MutableSet>
12 | get() = wrapped.entries
13 | override val keys: MutableSet
14 | get() = wrapped.keys
15 | override val values: MutableCollection
16 | get() = wrapped.values
17 |
18 | override fun containsKey(key: Key): Boolean {
19 | return wrapped.containsKey(key)
20 | }
21 |
22 | override fun containsValue(value: Value): Boolean {
23 | return wrapped.containsValue(value)
24 | }
25 |
26 | override fun get(key: Key): Value? {
27 | return wrapped[key]
28 | }
29 |
30 | override fun isEmpty(): Boolean {
31 | return wrapped.isEmpty()
32 | }
33 |
34 | override fun clear() {
35 | wrapped.clear()
36 | setChangedAndNotifyObservers()
37 | }
38 |
39 | override fun put(key: Key, value: Value): Value? {
40 | return wrapped.put(key, value).also {
41 | setChangedAndNotifyObservers()
42 | }
43 | }
44 |
45 | override fun putAll(from: Map) {
46 | wrapped.putAll(from)
47 | setChangedAndNotifyObservers()
48 | }
49 |
50 | override fun remove(key: Key): Value? {
51 | return wrapped.remove(key).also {
52 | setChangedAndNotifyObservers()
53 | }
54 | }
55 |
56 | private fun setChangedAndNotifyObservers() {
57 | setChanged()
58 | notifyObservers()
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/overlaymanager/data/TargetData.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.overlaymanager.data
2 |
3 | import android.content.Context
4 | import android.content.pm.ApplicationInfo
5 | import android.graphics.drawable.Drawable
6 | import tk.zwander.overlaymanager.proxy.OverlayInfo
7 |
8 | data class TargetData(
9 | val appInfo: ApplicationInfo,
10 | val info: List,
11 | var expanded: Boolean = false
12 | ) {
13 | private var label: CharSequence? = null
14 |
15 | fun getLabel(context: Context): CharSequence {
16 | if (label != null) return label!!
17 |
18 | return appInfo.loadLabel(context.packageManager)
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/overlaymanager/proxy/IOverlayManager.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.overlaymanager.proxy
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.IBinder
5 | import android.os.Parcelable
6 | import java.lang.reflect.Method
7 |
8 | @Suppress("UNCHECKED_CAST")
9 | @SuppressLint("PrivateApi")
10 | class IOverlayManager {
11 | companion object {
12 | private val clazz = Class.forName("android.content.om.IOverlayManager")
13 | private val stubClass = Class.forName("android.content.om.IOverlayManager\$Stub")
14 | private val omtbClass =
15 | Class.forName("android.content.om.OverlayManagerTransaction\$Builder")
16 | private val omtClass = Class.forName("android.content.om.OverlayManagerTransaction")
17 | private val oiClass = Class.forName("android.content.om.OverlayIdentifier")
18 |
19 | fun getIdentifier(packageName: String, overlayName: String?): Any {
20 | return oiClass.getConstructor(
21 | String::class.java,
22 | String::class.java
23 | ).newInstance(packageName, overlayName)
24 | }
25 |
26 | fun getIdentifier(source: String): Any {
27 | return oiClass.getMethod(
28 | "fromString",
29 | String::class.java
30 | ).invoke(
31 | null,
32 | source
33 | )!!
34 | }
35 | }
36 |
37 | private val obj = run {
38 | val svcManClass = Class.forName("android.os.ServiceManager")
39 | val getService = svcManClass.getMethod("getService", String::class.java)
40 | val asInterface = stubClass.getMethod("asInterface", IBinder::class.java)
41 |
42 | val b = getService.invoke(null, "overlay")
43 |
44 | asInterface.invoke(null, b)
45 | }
46 |
47 | fun getAllOverlays(): MutableMap> {
48 | val r = invokeMethod