Skip to content

Commit b350b67

Browse files
committed
light vibration toggle support
1 parent ff7afda commit b350b67

10 files changed

Lines changed: 136 additions & 14 deletions

File tree

.claude/settings.local.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(gh issue view:*)",
5+
"Bash(.\\\\gradlew.bat assembleDebug:*)",
6+
"WebSearch"
7+
]
8+
}
9+
}

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
33

44
<uses-permission android:name="android.permission.ACCESSIBILITY_SERVICE" />
5+
<uses-permission android:name="android.permission.VIBRATE" />
56

67
<application
78
android:icon="@mipmap/ic_launcher"

app/src/main/java/com/ah/taplock/MainActivity.kt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ fun TapLockScreen() {
100100
val doubleTapTimeoutKey = stringResource(R.string.double_tap_timeout)
101101
val timeoutUpdatedMsg = stringResource(R.string.timeout_updated)
102102
val showWidgetIconKey = stringResource(R.string.show_widget_icon)
103+
val vibrateOnLockKey = stringResource(R.string.vibrate_on_lock)
103104
val customIconUpdatedMsg = stringResource(R.string.custom_icon_updated)
104105
val customIconResetMsg = stringResource(R.string.custom_icon_reset)
105106

@@ -114,11 +115,13 @@ fun TapLockScreen() {
114115

115116
var timeoutValue by remember { mutableStateOf("") }
116117
var showIcon by remember { mutableStateOf(false) }
118+
var vibrateOnLock by remember { mutableStateOf(true) }
117119

118120
LaunchedEffect(Unit) {
119121
val prefs = context.getSharedPreferences(sharedPrefName, Context.MODE_PRIVATE)
120122
timeoutValue = prefs.getInt(doubleTapTimeoutKey, 300).toString()
121123
showIcon = prefs.getBoolean(showWidgetIconKey, false)
124+
vibrateOnLock = prefs.getBoolean(vibrateOnLockKey, true)
122125
}
123126

124127
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
@@ -322,7 +325,27 @@ fun TapLockScreen() {
322325
}
323326

324327
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
325-
328+
329+
Row(
330+
modifier = Modifier.fillMaxWidth(),
331+
horizontalArrangement = Arrangement.SpaceBetween,
332+
verticalAlignment = Alignment.CenterVertically
333+
) {
334+
Text(stringResource(R.string.vibrate_on_lock_label))
335+
Switch(
336+
checked = vibrateOnLock,
337+
onCheckedChange = { isChecked ->
338+
vibrateOnLock = isChecked
339+
context.getSharedPreferences(sharedPrefName, Context.MODE_PRIVATE)
340+
.edit {
341+
putBoolean(vibrateOnLockKey, isChecked)
342+
}
343+
}
344+
)
345+
}
346+
347+
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
348+
326349
Row(
327350
modifier = Modifier.fillMaxWidth(),
328351
horizontalArrangement = Arrangement.SpaceBetween,

app/src/main/java/com/ah/taplock/TapLockTileService.kt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
package com.ah.taplock
22

33
import android.app.PendingIntent
4+
import android.content.Context
45
import android.content.Intent
56
import android.graphics.drawable.Icon
67
import android.os.Build
8+
import android.os.Handler
9+
import android.os.Looper
10+
import android.media.AudioAttributes
11+
import android.os.VibrationAttributes
12+
import android.os.VibrationEffect
13+
import android.os.Vibrator
714
import android.provider.Settings
815
import android.service.quicksettings.Tile
916
import android.service.quicksettings.TileService
@@ -19,10 +26,32 @@ class TapLockTileService : TileService() {
1926
override fun onClick() {
2027
super.onClick()
2128

29+
val prefs = getSharedPreferences(getString(R.string.shared_pref_name), Context.MODE_PRIVATE)
30+
val vibrateOnLock = prefs.getBoolean(getString(R.string.vibrate_on_lock), true)
31+
if (vibrateOnLock) {
32+
val vibrator = getSystemService(Vibrator::class.java)
33+
val effect = VibrationEffect.createOneShot(50, 80)
34+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
35+
val attrs = VibrationAttributes.Builder()
36+
.setUsage(VibrationAttributes.USAGE_ALARM)
37+
.build()
38+
vibrator.vibrate(effect, attrs)
39+
} else {
40+
val attrs = AudioAttributes.Builder()
41+
.setUsage(AudioAttributes.USAGE_ALARM)
42+
.build()
43+
vibrator.vibrate(effect, attrs)
44+
}
45+
}
46+
2247
// Try direct call first for speed
2348
val service = TapLockAccessibilityService.instance
2449
if (service != null) {
25-
service.lockScreen()
50+
if (vibrateOnLock) {
51+
Handler(Looper.getMainLooper()).postDelayed({ service.lockScreen() }, 100)
52+
} else {
53+
service.lockScreen()
54+
}
2655
} else {
2756
// Fallback checking if enabled
2857
if (isAccessibilityEnabled(this)) {

app/src/main/java/com/ah/taplock/TapLockWidgetProvider.kt

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ import android.appwidget.AppWidgetProvider
66
import android.content.Context
77
import android.content.Intent
88
import android.graphics.BitmapFactory
9+
import android.os.Handler
10+
import android.os.Looper
11+
import android.media.AudioAttributes
12+
import android.os.Build
13+
import android.os.VibrationAttributes
14+
import android.os.VibrationEffect
15+
import android.os.Vibrator
916
import android.util.Log
1017
import android.view.View
1118
import android.widget.RemoteViews
@@ -80,18 +87,43 @@ class TapLockWidgetProvider : AppWidgetProvider() {
8087

8188
val currentTime = System.currentTimeMillis()
8289
if (currentTime - lastTapTime < timeout) {
83-
// Try direct call first
84-
val service = TapLockAccessibilityService.instance
85-
if (service != null) {
86-
Log.d("TapLock", "Widget: Using direct instance - fast path")
87-
service.lockScreen()
90+
// Haptic feedback if enabled
91+
val vibrateOnLock = prefs.getBoolean(context.getString(R.string.vibrate_on_lock), true)
92+
if (vibrateOnLock) {
93+
val vibrator = context.getSystemService(Vibrator::class.java)
94+
val effect = VibrationEffect.createOneShot(50, 80)
95+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
96+
val attrs = VibrationAttributes.Builder()
97+
.setUsage(VibrationAttributes.USAGE_ALARM)
98+
.build()
99+
vibrator.vibrate(effect, attrs)
100+
} else {
101+
val attrs = AudioAttributes.Builder()
102+
.setUsage(AudioAttributes.USAGE_ALARM)
103+
.build()
104+
vibrator.vibrate(effect, attrs)
105+
}
106+
}
107+
108+
// Delay lock slightly so the haptic feedback can complete
109+
val lockRunnable = Runnable {
110+
val service = TapLockAccessibilityService.instance
111+
if (service != null) {
112+
Log.d("TapLock", "Widget: Using direct instance - fast path")
113+
service.lockScreen()
114+
} else {
115+
Log.d("TapLock", "Widget: Instance null - using slow startService path")
116+
Toast.makeText(context, "Locking screen...", Toast.LENGTH_SHORT).show()
117+
val accessibilityIntent = Intent(context, TapLockAccessibilityService::class.java)
118+
accessibilityIntent.action = Intent.ACTION_SCREEN_OFF
119+
context.startService(accessibilityIntent)
120+
}
121+
}
122+
123+
if (vibrateOnLock) {
124+
Handler(Looper.getMainLooper()).postDelayed(lockRunnable, 100)
88125
} else {
89-
// Fallback if instance is null
90-
Log.d("TapLock", "Widget: Instance null - using slow startService path")
91-
Toast.makeText(context, "Locking screen...", Toast.LENGTH_SHORT).show()
92-
val accessibilityIntent = Intent(context, TapLockAccessibilityService::class.java)
93-
accessibilityIntent.action = Intent.ACTION_SCREEN_OFF
94-
context.startService(accessibilityIntent)
126+
lockRunnable.run()
95127
}
96128
}
97129
lastTapTime = currentTime
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:color="#40FFFFFF">
4+
<item android:id="@android:id/mask">
5+
<shape android:shape="rectangle">
6+
<solid android:color="@android:color/white" />
7+
</shape>
8+
</item>
9+
</ripple>

app/src/main/res/layout/widget_layout.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
android:id="@+id/widget_container"
44
android:layout_width="match_parent"
55
android:layout_height="match_parent"
6-
android:background="@android:color/transparent">
6+
android:background="@android:color/transparent"
7+
android:foreground="@drawable/widget_ripple">
78

89
<ImageView
910
android:id="@+id/widget_icon"

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@
2929
<string name="not_now">Not now</string>
3030
<string name="tile_label">Lock Now</string>
3131
<string name="accessibility_permission_required">Accessibility permission required</string>
32+
<string name="vibrate_on_lock">vibrate_on_lock</string>
33+
<string name="vibrate_on_lock_label">Vibrate on Lock</string>
3234
</resources>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#This file is generated by updateDaemonJvm
2+
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect
3+
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/67a0fee3c4236b6397dcbe8575ca2011/redirect
4+
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect
5+
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
6+
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/10fc3bf1ee0001078a473afe6e43cfdb/redirect
7+
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/9c55677aff3966382f3d853c0959bfb2/redirect
8+
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect
9+
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
10+
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/39846e8427e64a3824c13e399d7d813c/redirect
11+
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/932015f6361ccaead0c6d9b8717ed96e/redirect
12+
toolchainVendor=JETBRAINS
13+
toolchainVersion=21

settings.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ pluginManagement {
1111
gradlePluginPortal()
1212
}
1313
}
14+
plugins {
15+
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
16+
}
1417
dependencyResolutionManagement {
1518
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
1619
repositories {

0 commit comments

Comments
 (0)