add accessibility service as second method for locking the screen (cf. #65)

This commit is contained in:
Josia Pietsch 2024-11-09 00:36:07 +01:00
parent 9848785b3e
commit f61f861950
Signed by: jrpie
GPG key ID: E70B571D66986A2D
12 changed files with 206 additions and 27 deletions

View file

@ -53,7 +53,7 @@
</intent-filter>
</activity>
<receiver android:name=".actions.LauncherDeviceAdmin"
<receiver android:name=".actions.lock.LauncherDeviceAdmin"
android:exported="true"
android:label="@string/app_name"
android:description="@string/device_admin_description"
@ -66,7 +66,7 @@
</intent-filter>
</receiver>
<service android:name=".actions.LauncherAccessibilityService"
<service android:name=".actions.lock.LauncherAccessibilityService"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:label="@string/accessibility_service_name">

View file

@ -12,9 +12,9 @@ import android.view.KeyEvent
import android.widget.Toast
import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.actions.LauncherAccessibilityService.Companion.ACTION_LOCK_SCREEN
import de.jrpie.android.launcher.apps.AppFilter
import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.ui.list.ListActivity
import de.jrpie.android.launcher.ui.settings.SettingsActivity
@ -78,7 +78,7 @@ enum class LauncherAction(
"launcher:lockScreen",
R.string.list_other_lock_screen,
R.drawable.baseline_lock_24px,
LauncherDeviceAdmin::lockScreen
{ c -> LauncherPreferences.actions().lockMethod().lockOrEnable(c) }
),
TORCH(
"launcher:toggleTorch",

View file

@ -1,19 +1,19 @@
package de.jrpie.android.launcher.actions
package de.jrpie.android.launcher.actions.lock
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
import android.widget.Toast
import androidx.core.content.getSystemService
import de.jrpie.android.launcher.BuildConfig
import de.jrpie.android.launcher.R
class LauncherAccessibilityService : AccessibilityService() {
override fun onInterrupt() { }
override fun onInterrupt() {}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
// Intentionally left blank, we are not interested in any AccessibilityEvents.
@ -23,11 +23,15 @@ class LauncherAccessibilityService : AccessibilityService() {
companion object {
const val ACTION_LOCK_SCREEN = "ACTION_LOCK_SCREEN"
private fun lockScreen(context: Context){
fun lockScreen(context: Context) {
try {
context.startService(Intent(context, LauncherAccessibilityService::class.java).apply {
action = ACTION_LOCK_SCREEN
})
context.startService(
Intent(
context,
LauncherAccessibilityService::class.java
).apply {
action = ACTION_LOCK_SCREEN
})
} catch (e: Exception) {
Toast.makeText(
context,
@ -36,19 +40,33 @@ class LauncherAccessibilityService : AccessibilityService() {
).show()
}
}
fun isEnabled(context: Context): Boolean {
val accessibilityManager =
context.getSystemService<AccessibilityManager>() ?: return false
val enabledServices =
accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
return enabledServices.any {
it.id.startsWith(BuildConfig.APPLICATION_ID)
}
}
}
private fun isServiceEnabled(): Boolean {
val accessibilityManager = getSystemService<AccessibilityManager>() ?: return false
val enabledServices = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
return enabledServices.any { it.id.contains(packageName) }
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.action?.let { action ->
if (!isServiceEnabled()) {
Toast.makeText(this, getString(R.string.toast_accessibility_service_not_enabled), Toast.LENGTH_LONG).show()
startActivity(Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
if (!isEnabled(this)) {
Toast.makeText(
this,
getString(R.string.toast_accessibility_service_not_enabled),
Toast.LENGTH_LONG
).show()
startActivity(
Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS).addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
)
)
return START_NOT_STICKY
}
@ -59,7 +77,7 @@ class LauncherAccessibilityService : AccessibilityService() {
return super.onStartCommand(intent, flags, startId)
}
private fun handleLockScreen(){
private fun handleLockScreen() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
Toast.makeText(
this,

View file

@ -1,4 +1,4 @@
package de.jrpie.android.launcher.actions
package de.jrpie.android.launcher.actions.lock
import android.app.admin.DeviceAdminReceiver
import android.app.admin.DevicePolicyManager
@ -18,11 +18,17 @@ class LauncherDeviceAdmin : DeviceAdminReceiver() {
val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN).apply {
putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, getComponentName(context))
putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
context.getString(R.string.device_admin_explanation))
putExtra(
DevicePolicyManager.EXTRA_ADD_EXPLANATION,
context.getString(R.string.device_admin_explanation)
)
}
context.startActivity(intent)
}
fun isDeviceAdmin(context: Context): Boolean {
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
return dpm.isAdminActive(getComponentName(context))
}
private fun assertDeviceAdmin(context: Context): Boolean {
@ -36,17 +42,15 @@ class LauncherDeviceAdmin : DeviceAdminReceiver() {
requestDeviceAdmin(context)
return false
}
return true
}
fun lockScreen(context: Context) {
assertDeviceAdmin(context) || return
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
dpm.lockNow()
}
}
}

View file

@ -0,0 +1,74 @@
package de.jrpie.android.launcher.actions.lock
import android.content.Context
import android.os.Build
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.preferences.LauncherPreferences
@Suppress("unused")
enum class LockMethod(
private val lock: (Context) -> Unit,
private val isEnabled: (Context) -> Boolean,
private val enable: (Context) -> Unit
) {
DEVICE_ADMIN(
LauncherDeviceAdmin::lockScreen,
LauncherDeviceAdmin::isDeviceAdmin,
LauncherDeviceAdmin::lockScreen
),
ACCESSIBILITY_SERVICE(
LauncherAccessibilityService::lockScreen,
LauncherAccessibilityService::isEnabled,
LauncherAccessibilityService::lockScreen
),
;
fun lockOrEnable(context: Context) {
if (!this.isEnabled(context)) {
chooseMethod(context)
return
}
this.lock(context)
}
companion object {
fun chooseMethod(context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
// only device admin is available
setMethod(context, DEVICE_ADMIN)
return
}
val builder = AlertDialog.Builder(context, R.style.AlertDialogCustom)
builder.setNegativeButton("cancel") { _, _ -> }
builder.setCustomTitle(
LayoutInflater.from(context).inflate(R.layout.dialog_select_lock_method, null)
)
builder.setItems(
arrayOf(
context.getString(R.string.screen_lock_method_use_accessibility),
context.getString(R.string.screen_lock_method_use_device_admin)
)
) { _, i ->
val method = when (i) {
0 -> ACCESSIBILITY_SERVICE
1 -> DEVICE_ADMIN
else -> return@setItems
}
setMethod(context, method)
}
builder.show()
return
}
private fun setMethod(context: Context, m: LockMethod) {
LauncherPreferences.actions().lockMethod(m)
if (!m.isEnabled(context))
m.enable(context)
}
}
}

View file

@ -5,6 +5,7 @@ import java.util.Set;
import de.jrpie.android.launcher.R;
import de.jrpie.android.launcher.apps.AppInfo;
import de.jrpie.android.launcher.actions.lock.LockMethod;
import de.jrpie.android.launcher.preferences.theme.Background;
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
import de.jrpie.android.launcher.preferences.theme.Font;
@ -62,6 +63,9 @@ import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSeriali
@Preference(name = "edge_swipe", type = boolean.class, defaultValue = "true"),
@Preference(name = "edge_swipe_edge_width", type = int.class, defaultValue = "15"),
}),
@PreferenceGroup(name = "actions", prefix = "settings_actions_", suffix = "_key", value = {
@Preference(name = "lock_method", type = LockMethod.class, defaultValue = "DEVICE_ADMIN"),
}),
})
public final class LauncherPreferences$Config {
public static class AppInfoSetSerializer implements PreferenceSerializer<Set<AppInfo>, Set<String>> {

View file

@ -7,6 +7,7 @@ import androidx.preference.PreferenceFragmentCompat
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.actions.openAppsList
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.actions.lock.LockMethod
import de.jrpie.android.launcher.setDefaultHomeScreen
@ -74,6 +75,15 @@ class SettingsFragmentLauncher : PreferenceFragmentCompat() {
openAppsList(requireContext(), favorite = false, hidden = true)
true
}
val lockMethod = findPreference<androidx.preference.Preference>(
LauncherPreferences.actions().keys().lockMethod()
)
lockMethod?.setOnPreferenceClickListener {
LockMethod.chooseMethod(requireContext())
true
}
updateVisibility()
}
}

View file

@ -0,0 +1,18 @@
package de.jrpie.android.launcher.ui.util
import android.content.Context
import android.text.Html
import android.text.method.LinkMovementMethod
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
class HtmlTextView(context: Context, attr: AttributeSet?, int: Int) :
AppCompatTextView(context, attr, int) {
constructor(context: Context, attr: AttributeSet?) : this(context, attr, 0)
constructor(context: Context) : this(context, null, 0)
init {
text = Html.fromHtml(text.toString())
movementMethod = LinkMovementMethod.getInstance()
}
}

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="match_parent">
<de.jrpie.android.launcher.ui.util.HtmlTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/screen_lock_method_dialog_text"
android:id="@+id/dialog_select_lock_method_text"
android:textColor="@color/material_dynamic_primary0"
/>
</LinearLayout>

View file

@ -102,6 +102,8 @@
<string name="settings_functionality_search_auto_open_keyboard_key" translatable="false">functionality.search_auto_keyboard</string>
<string name="settings_actions_lock_method_key">settings_action_lock_method</string>
<!--
-

View file

@ -247,6 +247,37 @@
<string name="toast_accessibility_service_not_enabled">μLauncher\'s accessibility service is not enabled. Please enable it in settings</string>
<string name="toast_lock_screen_not_supported">Error: Locking the screen using accessibility is not supported on this device. Please use device admin instead.</string>
<string name="accessibility_service_name">µLauncher - lock screen</string>
<string name="accessibility_service_description">Setting µLauncher as an accessibility service allows it to lock the screen. Note that excessive permissions are required, you should never grant such permissions lightly to any app. µLauncher will use the accessibility service only for locking the screen. You should check the source code to make sure. Note that locking the screen can also be accomplished by granting µLauncher device administrator permissions. Those are more fine grained. However that method doesn\'t work with fingerprint and facial recognition.</string>
<string name="accessibility_service_description">
Setting µLauncher as an accessibility service allows it to lock the screen.
Note that excessive permissions are required. You should never grant such permissions lightly to any app.
µLauncher will use the accessibility service only for locking the screen. You can check the source code to make sure.
Note that locking the screen can also be accomplished by granting µLauncher device administrator permissions. However that method doesn\'t work with fingerprint and face unlock.
</string>
<string name="screen_lock_method_dialog_title">Choose Method for Locking</string>
<string name="screen_lock_method_dialog_text"><![CDATA[
<h1>Choose locking method</h1>
There are two methods to lock the screen.
Unfortunately both have downsides:<br/><br/>
<h3>Accessibility Service</h3>
Requires excessive privileges.
µLauncher will use those privileges only for locking the screen.
<br/>
(You really should not trust a random app you just downloaded with such a claim, but you can check the <a href=\"https://github.com/jrpie/Launcher\">source code</a>.)
<br/>
<br/>
<h3>Device Admin</h3>
Doesn\'t work with unlocking by fingerprint or face recognition.
<br/><br/><br/><br/>
You can change your selection later in settings.
]]>
</string>
<string name="screen_lock_method_use_accessibility">Use Accessibility Service</string>
<string name="screen_lock_method_use_device_admin">Use Device Admin</string>
<string name="settings_actions_lock_method">Choose method for locking the screen</string>
</resources>

View file

@ -107,6 +107,10 @@
android:title="@string/settings_enabled_gestures_edge_swipe_edge_width"/>
</PreferenceCategory>
<Preference
android:key="@string/settings_actions_lock_method_key"
android:title="@string/settings_actions_lock_method" />
</PreferenceCategory>