Skip to content

Commit fd3adc0

Browse files
committed
improved shortcuts
1 parent 7eb92dd commit fd3adc0

13 files changed

Lines changed: 609 additions & 8 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Introducing FakeCall. Unlike other apps that merely mock a UI, this app integrat
3636
- **Recording:** record microphone audio of a Fake call
3737
- **Automation API:** trigger calls from Tasker, MacroDroid, or ADB via a broadcast intent
3838
- **Accessibility Shortcut:** schedule a fake call from the system accessibility button using saved defaults
39+
- **Quick Trigger Presets:** save up to 5 presets and expose them as launcher app actions + Quick Settings tiles
3940

4041
## Automation API
4142

@@ -72,6 +73,11 @@ You can configure these defaults inside:
7273

7374
`Settings -> Automation & Quick Trigger Defaults`
7475

76+
You can also save up to five quick trigger presets from the same section:
77+
78+
- presets appear as launcher app actions (long-press the app icon)
79+
- presets are available as Quick Settings tiles (`FakeCall Preset 1` ... `FakeCall Preset 5`)
80+
7581
## Screenshots
7682

7783
![Screenshot 1](https://github.com/DDOneApps/FakeCall/blob/main/Screenshots/Screenshot_20260308-211426_Fake%20Call.png)

app/release/app-release.apk

17.2 KB
Binary file not shown.
-21 Bytes
Binary file not shown.
-23 Bytes
Binary file not shown.

app/src/main/AndroidManifest.xml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,66 @@
6161
android:resource="@xml/accessibility_service_config" />
6262
</service>
6363

64+
<service
65+
android:name=".QuickTriggerTile1Service"
66+
android:enabled="true"
67+
android:exported="true"
68+
android:icon="@drawable/ic_quick_trigger_phone"
69+
android:label="FakeCall Preset 1"
70+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
71+
<intent-filter>
72+
<action android:name="android.service.quicksettings.action.QS_TILE" />
73+
</intent-filter>
74+
</service>
75+
76+
<service
77+
android:name=".QuickTriggerTile2Service"
78+
android:enabled="true"
79+
android:exported="true"
80+
android:icon="@drawable/ic_quick_trigger_phone"
81+
android:label="FakeCall Preset 2"
82+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
83+
<intent-filter>
84+
<action android:name="android.service.quicksettings.action.QS_TILE" />
85+
</intent-filter>
86+
</service>
87+
88+
<service
89+
android:name=".QuickTriggerTile3Service"
90+
android:enabled="true"
91+
android:exported="true"
92+
android:icon="@drawable/ic_quick_trigger_phone"
93+
android:label="FakeCall Preset 3"
94+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
95+
<intent-filter>
96+
<action android:name="android.service.quicksettings.action.QS_TILE" />
97+
</intent-filter>
98+
</service>
99+
100+
<service
101+
android:name=".QuickTriggerTile4Service"
102+
android:enabled="true"
103+
android:exported="true"
104+
android:icon="@drawable/ic_quick_trigger_phone"
105+
android:label="FakeCall Preset 4"
106+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
107+
<intent-filter>
108+
<action android:name="android.service.quicksettings.action.QS_TILE" />
109+
</intent-filter>
110+
</service>
111+
112+
<service
113+
android:name=".QuickTriggerTile5Service"
114+
android:enabled="true"
115+
android:exported="true"
116+
android:icon="@drawable/ic_quick_trigger_phone"
117+
android:label="FakeCall Preset 5"
118+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
119+
<intent-filter>
120+
<action android:name="android.service.quicksettings.action.QS_TILE" />
121+
</intent-filter>
122+
</service>
123+
64124
<receiver
65125
android:name=".FakeCallAlarmReceiver"
66126
android:exported="false" />
@@ -73,6 +133,15 @@
73133
</intent-filter>
74134
</receiver>
75135

136+
<activity
137+
android:name=".ShortcutTriggerActivity"
138+
android:excludeFromRecents="true"
139+
android:exported="true"
140+
android:launchMode="singleTask"
141+
android:noHistory="true"
142+
android:taskAffinity=""
143+
android:theme="@style/Theme.Fakecall.ShortcutTrigger" />
144+
76145
<activity
77146
android:name=".MainActivity"
78147
android:exported="true"

app/src/main/java/com/upnp/fakeCall/FakeCallAlarmReceiver.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import android.content.Intent
66

77
class FakeCallAlarmReceiver : BroadcastReceiver() {
88
override fun onReceive(context: Context, intent: Intent?) {
9+
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
10+
.edit()
11+
.remove(KEY_TIMER_ENDS_AT)
12+
.putInt(KEY_ACTIVE_PRESET_SLOT, -1)
13+
.apply()
14+
QuickTriggerManager.refreshQuickSettingsTiles(context)
915
val callerName = intent?.getStringExtra(EXTRA_CALLER_NAME).orEmpty()
1016
val callerNumber = intent?.getStringExtra(EXTRA_CALLER_NUMBER).orEmpty()
1117
val providerName = intent?.getStringExtra(EXTRA_PROVIDER_NAME).orEmpty()
@@ -20,6 +26,9 @@ class FakeCallAlarmReceiver : BroadcastReceiver() {
2026
}
2127

2228
companion object {
29+
private const val PREFS_NAME = "fake_call_prefs"
30+
private const val KEY_TIMER_ENDS_AT = "timer_ends_at"
31+
private const val KEY_ACTIVE_PRESET_SLOT = "quick_trigger_active_preset_slot"
2332
const val EXTRA_CALLER_NAME = "extra_caller_name"
2433
const val EXTRA_CALLER_NUMBER = "extra_caller_number"
2534
const val EXTRA_PROVIDER_NAME = "extra_provider_name"

app/src/main/java/com/upnp/fakeCall/FakeCallViewModel.kt

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ data class FakeCallUiState(
6565
val quickTriggerCallerName: String = "",
6666
val quickTriggerCallerNumber: String = "",
6767
val quickTriggerDelaySeconds: Int = QuickTriggerManager.DEFAULT_DELAY_SECONDS,
68+
val quickTriggerPresetName: String = "",
69+
val quickTriggerPresets: List<QuickTriggerPreset> = emptyList(),
6870
val startupUpdate: ReleaseInfo? = null
6971
)
7072

@@ -75,6 +77,7 @@ class FakeCallViewModel(application: Application) : AndroidViewModel(application
7577
private val ivrStore = IvrConfigStore()
7678
private val updateChecker = UpdateChecker()
7779
private val quickTriggerDefaults = QuickTriggerManager.loadDefaults(application)
80+
private val quickTriggerPresets = QuickTriggerManager.loadPresets(application)
7881

7982
private val _uiState = MutableStateFlow(
8083
FakeCallUiState(
@@ -101,7 +104,9 @@ class FakeCallViewModel(application: Application) : AndroidViewModel(application
101104
recordingsFolderName = prefs.getString(KEY_RECORDINGS_FOLDER_NAME, "Downloads/FakeCall").orEmpty(),
102105
quickTriggerCallerName = quickTriggerDefaults.callerName,
103106
quickTriggerCallerNumber = quickTriggerDefaults.callerNumber,
104-
quickTriggerDelaySeconds = quickTriggerDefaults.delaySeconds
107+
quickTriggerDelaySeconds = quickTriggerDefaults.delaySeconds,
108+
quickTriggerPresetName = prefs.getString(KEY_QUICK_TRIGGER_PRESET_NAME, "").orEmpty(),
109+
quickTriggerPresets = quickTriggerPresets
105110
)
106111
)
107112
val uiState: StateFlow<FakeCallUiState> = _uiState.asStateFlow()
@@ -122,6 +127,8 @@ class FakeCallViewModel(application: Application) : AndroidViewModel(application
122127
viewModelScope.launch {
123128
checkForUpdatesOnStartup()
124129
}
130+
131+
QuickTriggerManager.updateLauncherShortcuts(application)
125132
}
126133

127134
suspend fun checkForUpdatesManual(): UpdateCheckResult {
@@ -163,6 +170,56 @@ class FakeCallViewModel(application: Application) : AndroidViewModel(application
163170
saveQuickTriggerDefaults(uiState.value.copy(quickTriggerDelaySeconds = delaySeconds))
164171
}
165172

173+
fun onQuickTriggerPresetNameChange(value: String) {
174+
prefs.edit().putString(KEY_QUICK_TRIGGER_PRESET_NAME, value).apply()
175+
_uiState.update { it.copy(quickTriggerPresetName = value) }
176+
}
177+
178+
fun saveQuickTriggerPreset() {
179+
val customName = uiState.value.quickTriggerPresetName.trim()
180+
val result = QuickTriggerManager.saveCurrentDefaultsAsPreset(
181+
context = getApplication(),
182+
customTitle = customName
183+
)
184+
val status = when (result) {
185+
QuickTriggerPresetSaveResult.SAVED -> "Quick trigger preset saved."
186+
QuickTriggerPresetSaveResult.LIMIT_REACHED -> "You can only save up to 5 quick trigger presets."
187+
QuickTriggerPresetSaveResult.INVALID_DATA -> "Enter a caller number before saving a preset."
188+
}
189+
refreshQuickTriggerPresets(status)
190+
if (result == QuickTriggerPresetSaveResult.SAVED) {
191+
prefs.edit().putString(KEY_QUICK_TRIGGER_PRESET_NAME, "").apply()
192+
_uiState.update { it.copy(quickTriggerPresetName = "") }
193+
}
194+
}
195+
196+
fun applyQuickTriggerPreset(slot: Int) {
197+
val applied = QuickTriggerManager.applyPresetToDefaults(getApplication(), slot)
198+
if (!applied) {
199+
_uiState.update { it.copy(statusMessage = "Preset not found.") }
200+
return
201+
}
202+
val defaults = QuickTriggerManager.loadDefaults(getApplication())
203+
_uiState.update {
204+
it.copy(
205+
quickTriggerCallerName = defaults.callerName,
206+
quickTriggerCallerNumber = defaults.callerNumber,
207+
quickTriggerDelaySeconds = defaults.delaySeconds,
208+
statusMessage = "Preset applied to quick trigger defaults."
209+
)
210+
}
211+
}
212+
213+
fun removeQuickTriggerPreset(slot: Int) {
214+
val removed = QuickTriggerManager.removePreset(getApplication(), slot)
215+
val status = if (removed) {
216+
"Quick trigger preset removed."
217+
} else {
218+
"Preset not found."
219+
}
220+
refreshQuickTriggerPresets(status)
221+
}
222+
166223
fun onCallerNameChange(value: String) {
167224
_uiState.update { it.copy(callerName = value) }
168225
}
@@ -603,6 +660,7 @@ class FakeCallViewModel(application: Application) : AndroidViewModel(application
603660
}
604661

605662
prefs.edit().remove(KEY_TIMER_ENDS_AT).apply()
663+
prefs.edit().putInt(KEY_ACTIVE_PRESET_SLOT, -1).apply()
606664
_uiState.update {
607665
it.copy(
608666
isTimerRunning = false,
@@ -614,6 +672,7 @@ class FakeCallViewModel(application: Application) : AndroidViewModel(application
614672
}
615673
)
616674
}
675+
QuickTriggerManager.refreshQuickSettingsTiles(getApplication())
617676
return
618677
}
619678

@@ -633,26 +692,34 @@ class FakeCallViewModel(application: Application) : AndroidViewModel(application
633692
return
634693
}
635694

636-
prefs.edit().putLong(KEY_TIMER_ENDS_AT, triggerAtMillis).apply()
695+
prefs.edit()
696+
.putLong(KEY_TIMER_ENDS_AT, triggerAtMillis)
697+
.putInt(KEY_ACTIVE_PRESET_SLOT, -1)
698+
.apply()
637699
_uiState.update {
638700
it.copy(
639701
isTimerRunning = true,
640702
timerEndsAtMillis = triggerAtMillis,
641703
statusMessage = buildScheduleStatus(state.scheduleKind, selectedDelaySeconds, triggerAtMillis)
642704
)
643705
}
706+
QuickTriggerManager.refreshQuickSettingsTiles(getApplication())
644707
}
645708

646709
private fun cancelTimer() {
647710
FakeCallAlarmScheduler.cancel(getApplication())
648-
prefs.edit().remove(KEY_TIMER_ENDS_AT).apply()
711+
prefs.edit()
712+
.remove(KEY_TIMER_ENDS_AT)
713+
.putInt(KEY_ACTIVE_PRESET_SLOT, -1)
714+
.apply()
649715
_uiState.update {
650716
it.copy(
651717
isTimerRunning = false,
652718
timerEndsAtMillis = 0L,
653719
statusMessage = "Timer cancelled."
654720
)
655721
}
722+
QuickTriggerManager.refreshQuickSettingsTiles(getApplication())
656723
}
657724

658725
private fun syncRunningTimerState() {
@@ -726,6 +793,15 @@ class FakeCallViewModel(application: Application) : AndroidViewModel(application
726793
}
727794
}
728795

796+
private fun refreshQuickTriggerPresets(statusMessage: String) {
797+
_uiState.update {
798+
it.copy(
799+
quickTriggerPresets = QuickTriggerManager.loadPresets(getApplication()),
800+
statusMessage = statusMessage
801+
)
802+
}
803+
}
804+
729805
private fun customCountdownSeconds(state: FakeCallUiState): Int {
730806
val minutes = state.customCountdownMinutes.coerceAtLeast(0)
731807
val seconds = state.customCountdownSeconds.coerceAtLeast(0)
@@ -847,12 +923,14 @@ class FakeCallViewModel(application: Application) : AndroidViewModel(application
847923
private const val KEY_CUSTOM_EXACT_MINUTE = "custom_exact_minute"
848924
private const val KEY_CUSTOM_PRESETS = "custom_presets"
849925
private const val KEY_TIMER_ENDS_AT = "timer_ends_at"
926+
private const val KEY_ACTIVE_PRESET_SLOT = "quick_trigger_active_preset_slot"
850927
private const val KEY_AUDIO_URI = "audio_uri"
851928
private const val KEY_AUDIO_NAME = "audio_name"
852929
private const val KEY_RECORDING_ENABLED = "recording_enabled"
853930
private const val KEY_RECORDINGS_TREE_URI = "recordings_tree_uri"
854931
private const val KEY_RECORDINGS_FOLDER_NAME = "recordings_folder_name"
855932
private const val KEY_ONBOARDING_COMPLETE = "onboarding_complete"
933+
private const val KEY_QUICK_TRIGGER_PRESET_NAME = "quick_trigger_preset_name"
856934

857935
fun formatDelay(seconds: Int): String {
858936
return when {

0 commit comments

Comments
 (0)