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

View file

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

View file

@ -1,15 +1,15 @@
package de.jrpie.android.launcher.actions package de.jrpie.android.launcher.actions.lock
import android.accessibilityservice.AccessibilityService import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo import android.accessibilityservice.AccessibilityServiceInfo
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.util.Log
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager
import android.widget.Toast import android.widget.Toast
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import de.jrpie.android.launcher.BuildConfig
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
class LauncherAccessibilityService : AccessibilityService() { class LauncherAccessibilityService : AccessibilityService() {
@ -23,9 +23,13 @@ class LauncherAccessibilityService : AccessibilityService() {
companion object { companion object {
const val ACTION_LOCK_SCREEN = "ACTION_LOCK_SCREEN" const val ACTION_LOCK_SCREEN = "ACTION_LOCK_SCREEN"
private fun lockScreen(context: Context){ fun lockScreen(context: Context) {
try { try {
context.startService(Intent(context, LauncherAccessibilityService::class.java).apply { context.startService(
Intent(
context,
LauncherAccessibilityService::class.java
).apply {
action = ACTION_LOCK_SCREEN action = ACTION_LOCK_SCREEN
}) })
} catch (e: Exception) { } catch (e: Exception) {
@ -36,19 +40,33 @@ class LauncherAccessibilityService : AccessibilityService() {
).show() ).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 { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.action?.let { action -> intent?.action?.let { action ->
if (!isServiceEnabled()) { if (!isEnabled(this)) {
Toast.makeText(this, getString(R.string.toast_accessibility_service_not_enabled), Toast.LENGTH_LONG).show() Toast.makeText(
startActivity(Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) 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 return START_NOT_STICKY
} }

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.DeviceAdminReceiver
import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager
@ -18,11 +18,17 @@ class LauncherDeviceAdmin : DeviceAdminReceiver() {
val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN).apply { val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN).apply {
putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, getComponentName(context)) putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, getComponentName(context))
putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, putExtra(
context.getString(R.string.device_admin_explanation)) DevicePolicyManager.EXTRA_ADD_EXPLANATION,
context.getString(R.string.device_admin_explanation)
)
} }
context.startActivity(intent) 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 { private fun assertDeviceAdmin(context: Context): Boolean {
@ -36,17 +42,15 @@ class LauncherDeviceAdmin : DeviceAdminReceiver() {
requestDeviceAdmin(context) requestDeviceAdmin(context)
return false return false
} }
return true return true
} }
fun lockScreen(context: Context) { fun lockScreen(context: Context) {
assertDeviceAdmin(context) || return assertDeviceAdmin(context) || return
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
dpm.lockNow() 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.R;
import de.jrpie.android.launcher.apps.AppInfo; 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.Background;
import de.jrpie.android.launcher.preferences.theme.ColorTheme; import de.jrpie.android.launcher.preferences.theme.ColorTheme;
import de.jrpie.android.launcher.preferences.theme.Font; 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", type = boolean.class, defaultValue = "true"),
@Preference(name = "edge_swipe_edge_width", type = int.class, defaultValue = "15"), @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 final class LauncherPreferences$Config {
public static class AppInfoSetSerializer implements PreferenceSerializer<Set<AppInfo>, Set<String>> { 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.R
import de.jrpie.android.launcher.actions.openAppsList import de.jrpie.android.launcher.actions.openAppsList
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.actions.lock.LockMethod
import de.jrpie.android.launcher.setDefaultHomeScreen import de.jrpie.android.launcher.setDefaultHomeScreen
@ -74,6 +75,15 @@ class SettingsFragmentLauncher : PreferenceFragmentCompat() {
openAppsList(requireContext(), favorite = false, hidden = true) openAppsList(requireContext(), favorite = false, hidden = true)
true true
} }
val lockMethod = findPreference<androidx.preference.Preference>(
LauncherPreferences.actions().keys().lockMethod()
)
lockMethod?.setOnPreferenceClickListener {
LockMethod.chooseMethod(requireContext())
true
}
updateVisibility() 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_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_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="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_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> </resources>

View file

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