Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Show app on lock screen when alarm rings #294

Merged
merged 2 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 4.1.1
* [Android] Show app on lock screen when alarm rings.

## 4.1.0
* Migrated to Pigeon.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@ package com.gdelataillade.alarm.alarm

import AlarmApi
import AlarmTriggerApi
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.os.Build
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import com.gdelataillade.alarm.api.AlarmApiImpl
import com.gdelataillade.alarm.services.AlarmRingingLiveData
import io.flutter.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding

class AlarmPlugin : FlutterPlugin, ActivityAware {
private var activity: Activity? = null

class AlarmPlugin : FlutterPlugin {
companion object {
@JvmStatic
var alarmTriggerApi: AlarmTriggerApi? = null
Expand All @@ -14,9 +26,51 @@ class AlarmPlugin : FlutterPlugin {
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
AlarmApi.setUp(binding.binaryMessenger, AlarmApiImpl(binding.applicationContext))
alarmTriggerApi = AlarmTriggerApi(binding.binaryMessenger)

}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
alarmTriggerApi = null
}

override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
AlarmRingingLiveData.instance.observe(
binding.activity as LifecycleOwner,
notificationObserver
)
}

override fun onDetachedFromActivity() {
activity = null
AlarmRingingLiveData.instance.removeObserver(notificationObserver)
}

override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
onAttachedToActivity(binding)
}

override fun onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity()
}

private val notificationObserver = Observer<Boolean> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
Log.w("AlarmPlugin", "Making app visible on lock screen is not supported on this version of Android.")
return@Observer
}
val activity = activity ?: return@Observer
if (it) {
Log.d("AlarmPlugin", "Making app visible on lock screen...")
activity.setShowWhenLocked(true)
activity.setTurnScreenOn(true)
val keyguardManager =
activity.applicationContext.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
keyguardManager.requestDismissKeyguard(activity, null)
} else {
Log.d("AlarmPlugin", "Reverting making app visible on lock screen...")
activity.setShowWhenLocked(false)
activity.setTurnScreenOn(false)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import android.content.pm.ServiceInfo
import android.os.IBinder
import android.os.PowerManager
import android.os.Build
import com.gdelataillade.alarm.services.AlarmRingingLiveData
import com.gdelataillade.alarm.services.NotificationHandler
import io.flutter.Log

Expand Down Expand Up @@ -106,6 +107,10 @@ class AlarmService : Service() {
return START_NOT_STICKY
}

if (fullScreenIntent) {
AlarmRingingLiveData.instance.update(true)
}

// Proceed with handling the new alarm
val assetAudioPath = intent.getStringExtra("assetAudioPath") ?: return START_NOT_STICKY
val loopAudio = intent.getBooleanExtra("loopAudio", true)
Expand Down Expand Up @@ -176,6 +181,7 @@ class AlarmService : Service() {
}

private fun stopAlarm(id: Int) {
AlarmRingingLiveData.instance.update(false)
try {
val playingIds = audioService?.getPlayingMediaPlayersIds() ?: listOf()
ringingAlarmIds = playingIds
Expand Down Expand Up @@ -208,6 +214,8 @@ class AlarmService : Service() {
volumeService?.restorePreviousVolume(showSystemUI)
volumeService?.abandonAudioFocus()

AlarmRingingLiveData.instance.update(false)

stopForeground(true)

// Call the superclass method
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.gdelataillade.alarm.services

import androidx.lifecycle.LiveData

class AlarmRingingLiveData : LiveData<Boolean>() {
companion object {
@JvmStatic
var instance: AlarmRingingLiveData = AlarmRingingLiveData()
}

fun update(alarmIsRinging: Boolean) {
postValue(alarmIsRinging)
}
}
38 changes: 18 additions & 20 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<application
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<application
android:label="alarm_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
Expand All @@ -22,20 +22,18 @@
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:showWhenLocked="true"
android:turnScreenOn="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
Expand All @@ -44,4 +42,4 @@
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
</manifest>
2 changes: 1 addition & 1 deletion example/lib/screens/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:alarm_example/widgets/tile.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

const version = '4.1.0';
const version = '4.1.1';

class ExampleAlarmHomeScreen extends StatefulWidget {
const ExampleAlarmHomeScreen({super.key});
Expand Down
10 changes: 5 additions & 5 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ packages:
path: ".."
relative: true
source: path
version: "4.1.0"
version: "4.1.1"
async:
dependency: transitive
description:
Expand Down Expand Up @@ -211,10 +211,10 @@ packages:
dependency: transitive
description:
name: permission_handler_html
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.dev"
source: hosted
version: "0.1.3+2"
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
Expand Down Expand Up @@ -259,10 +259,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab"
sha256: "7f172d1b06de5da47b6264c2692ee2ead20bbbc246690427cdb4fc301cd0c549"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
version: "2.3.4"
shared_preferences_foundation:
dependency: transitive
description:
Expand Down
13 changes: 2 additions & 11 deletions help/INSTALL-ANDROID.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,6 @@ Then, add the following permissions to your `AndroidManifest.xml` within the `<m
See more details on Android permissions [here](https://developer.android.com/reference/android/Manifest.permission).

## Step 3
Finally, if you want your notifications to show in full screen even when the device is locked (`androidFullScreenIntent` parameter), add these attributes in `<activity>`:

```xml
<activity
android:showWhenLocked="true"
android:turnScreenOn="true">
```

## Step 4
Inside the <application> tag of your `AndroidManifest.xml`, add the following declarations (if you need notification-on-kill feature):
```xml
<application>
Expand All @@ -55,7 +46,7 @@ Inside the <application> tag of your `AndroidManifest.xml`, add the following de

Necessary if you want to enable an optional notification with `Alarm.setWarningNotificationOnKill` to alert users if the app is terminated, hinting at a rare chance the alarm may not work.

## Step 5
## Step 4
To guarantee that your alarm's foreground service can trigger when the app is in the background, it's recommanded to verify and request the necessary permission for scheduling exact alarms on Android 12+ devices. This step is particularly important due to varying device policies.

Leverage the [permission_handler](https://pub.dev/packages/permission_handler) package to check and request this permission seamlessly within your Flutter application. Here's an example to integrate into your code:
Expand All @@ -72,7 +63,7 @@ Future<void> checkAndroidScheduleExactAlarmPermission() async {
}
```

## Step 6
## Step 5
If you want to use the `androidFullScreenIntent` feature, some OEM (Samsung, Honor, Huawei, Xiaomi, Oppo, Asus, etc...) will require to grant the [auto_start_flutter](https://pub.dev/packages/auto_start_flutter) permission.

## Additional Resources
Expand Down
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: alarm
description: A simple Flutter alarm manager plugin for both iOS and Android.
version: 4.1.0
version: 4.1.1
homepage: https://github.com/gdelataillade/alarm

environment:
Expand All @@ -17,7 +17,7 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
pigeon: ^22.6.1
pigeon: ^22.6.2
very_good_analysis: ^6.0.0

flutter:
Expand Down