Skip to content

Commit

Permalink
Add WifiLock ability (#47)
Browse files Browse the repository at this point in the history
* Add WifiLock ability and improve IsolateHolderService
* Change example to specifically enable the WiFi lock
  • Loading branch information
JulianAssmann authored Nov 14, 2021
1 parent b18dece commit d96c64b
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 75 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.1.0

* Add capability to enable Android Wifi Lock in the initialize function.

## 1.0.2+2

* Fix crash when targeting Android S+ due to a missing immutable flag for pending intents
Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ PRs for iOS are very welcome, although I am not sure if a similiar effect can be

## Getting started

To use this plugin, add `flutter_background` as a [dependency in your `pubspec.yaml` file](https://flutter.dev/docs/development/packages-and-plugins/using-packages).
To use this plugin, add `flutter_background` as a [dependency in your `pubspec.yaml` file](https://pub.dev/packages/flutter_background/install).

### Android

Expand Down Expand Up @@ -62,9 +62,15 @@ This ensures all permissions are granted and requests them if necessary. It also
foreground notification. The configuration above results in the foreground notification shown below when
running `FlutterBackground.enableBackgroundExecution()`.

![The foreground notification created by the code above.](./images/notification.png "Foreground notification created by the code above.")
![The foreground notification created by the code above.](./images/notification.png "The foreground notification created by the code above.")

The arguments are:
- `notificationTitle`: The title used for the foreground service notification.
- `notificationText`: The body used for the foreground service notification.
- `notificationImportance`: The importance of the foreground service notification.
- `notificationIcon`: The icon used for the foreground service notification shown in the top left corner. This must be a drawable Android Resource (see [here](https://developer.android.com/reference/android/app/Notification.Builder#setSmallIcon(int,%20int)) for more). E. g. if the icon with name "background_icon" is in the "drawable" resource folder, it should be of value `AndroidResource(name: 'background_icon', defType: 'drawable').
- `enableWifiLock`: Indicates whether or not a WifiLock is acquired when background execution is started. This allows the application to keep the Wi-Fi radio awake, even when the user has not used the device in a while (e.g. for background network communications).

The notification icon is for the small icon displayed in the top left of a notification and must be a drawable Android Resource (see [here](https://developer.android.com/reference/android/app/Notification.Builder#setSmallIcon(int,%20int)) for more).
In this example, `background_icon` is a drawable resource in the `drawable` folders (see the example app).
For more information check out the [Android documentation for creating notification icons](https://developer.android.com/studio/write/image-asset-studio#create-notification) for more information how to create and store an icon.

Expand Down Expand Up @@ -100,7 +106,7 @@ you can stop the background execution of the app. You must call `FlutterBackgrou
To check whether background execution is currently enabled, use

```dart
bool enabled = FlutterBackground.isBackgroundExecutionEnabled
bool enabled = FlutterBackground.isBackgroundExecutionEnabled;
```

## Example
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package de.julianassmann.flutter_background

import android.annotation.TargetApi
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.annotation.NonNull;
import androidx.annotation.NonNull
import androidx.core.app.NotificationCompat

import io.flutter.embedding.engine.plugins.FlutterPlugin
Expand All @@ -32,54 +30,62 @@ class FlutterBackgroundPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
channel.setMethodCallHandler(FlutterBackgroundPlugin())
}

@JvmStatic
var notificationTitle: String? = "flutter_background foreground service"
@JvmStatic
val NOTIFICATION_TITLE_KEY = "android.notificationTitle"
@JvmStatic
var notificationText: String? = "Keeps the flutter app running in the background"
val NOTIFICATION_ICON_NAME_KEY = "android.notificationIconName"
@JvmStatic
val NOTIFICATION_TEXT_KEY = "android.notificationText"
val NOTIFICATION_ICON_DEF_TYPE_KEY = "android.notificationIconDefType"
@JvmStatic
var notificationImportance: Int? = NotificationCompat.PRIORITY_DEFAULT
val NOTIFICATION_TEXT_KEY = "android.notificationText"
@JvmStatic
val NOTIFICATION_IMPORTANCE_KEY = "android.notificationImportance"
@JvmStatic
var notificationIconName: String? = "ic_launcher"
val ENABLE_WIFI_LOCK_KEY = "android.enableWifiLock"

@JvmStatic
var notificationTitle: String = "flutter_background foreground service"
@JvmStatic
val NOTIFICATION_ICON_NAME_KEY = "android.notificationIconName"
var notificationText: String = "Keeps the flutter app running in the background"
@JvmStatic
var notificationIconDefType: String? = "mipmap"
var notificationImportance: Int = NotificationCompat.PRIORITY_DEFAULT
@JvmStatic
val NOTIFICATION_ICON_DEF_TYPE_KEY = "android.notificationIconDefType"
var notificationIconName: String = "ic_launcher"
@JvmStatic
var notificationIconDefType: String = "mipmap"
@JvmStatic
var enableWifiLock: Boolean = true


fun loadNotificationConfiguration(context: Context?) {
var sharedPref = context?.getSharedPreferences(context?.packageName + "_preferences", Context.MODE_PRIVATE)
notificationTitle = sharedPref?.getString(NOTIFICATION_TITLE_KEY, notificationTitle)
notificationText = sharedPref?.getString(NOTIFICATION_TEXT_KEY, notificationText)
notificationImportance = sharedPref?.getInt(NOTIFICATION_IMPORTANCE_KEY, notificationImportance!!)
notificationIconName = sharedPref?.getString(NOTIFICATION_ICON_NAME_KEY, notificationIconName)
notificationIconDefType = sharedPref?.getString(NOTIFICATION_ICON_DEF_TYPE_KEY, notificationIconDefType)
val sharedPref = context?.getSharedPreferences(context.packageName + "_preferences", Context.MODE_PRIVATE)
notificationTitle = sharedPref?.getString(NOTIFICATION_TITLE_KEY, notificationTitle) ?: notificationTitle
notificationText = sharedPref?.getString(NOTIFICATION_TEXT_KEY, notificationText) ?: notificationText
notificationImportance = sharedPref?.getInt(NOTIFICATION_IMPORTANCE_KEY, notificationImportance) ?: notificationImportance
notificationIconName = sharedPref?.getString(NOTIFICATION_ICON_NAME_KEY, notificationIconName) ?: notificationIconName
notificationIconDefType = sharedPref?.getString(NOTIFICATION_ICON_DEF_TYPE_KEY, notificationIconDefType) ?: notificationIconDefType
enableWifiLock = sharedPref?.getBoolean(ENABLE_WIFI_LOCK_KEY, false) ?: false
}

fun saveNotificationConfiguration(context: Context?) {
var sharedPref = context?.getSharedPreferences(context?.packageName + "_preferences", Context.MODE_PRIVATE)
val sharedPref = context?.getSharedPreferences(context.packageName + "_preferences", Context.MODE_PRIVATE)
with (sharedPref?.edit()) {
this?.putString(NOTIFICATION_TITLE_KEY, notificationTitle)
this?.putString(NOTIFICATION_TEXT_KEY, notificationText)
this?.putInt(NOTIFICATION_IMPORTANCE_KEY, notificationImportance!!)
this?.putInt(NOTIFICATION_IMPORTANCE_KEY, notificationImportance)
this?.putString(NOTIFICATION_ICON_NAME_KEY, notificationIconName)
this?.putString(NOTIFICATION_ICON_DEF_TYPE_KEY, notificationIconDefType)
this?.putBoolean(ENABLE_WIFI_LOCK_KEY, enableWifiLock)
this?.apply()
}
}
}


private fun isValidResource(context: Context, name: String, defType: String, result: Result, errorCode: String): Boolean {
val resourceId = context.getResources().getIdentifier(name, defType, context.getPackageName())
val resourceId = context.resources.getIdentifier(name, defType, context.packageName)
if (resourceId == 0) {
result.error("ResourceError", "The resource $defType/$name could not be found. Please make sure it has been added as a resource to your Android head project.", null)
result.error("ResourceError", "The resource $defType/$name could not be found. Please make sure it has been added as a resource to your Android head project.", errorCode)
return false
}
return true
Expand All @@ -95,7 +101,7 @@ class FlutterBackgroundPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
}
"hasPermissions" -> {
var hasPermissions = permissionHandler!!.isIgnoringBatteryOptimizations()
val hasPermissions = permissionHandler!!.isIgnoringBatteryOptimizations()
&& permissionHandler!!.isWakeLockPermissionGranted()
result.success(hasPermissions)
}
Expand All @@ -105,13 +111,15 @@ class FlutterBackgroundPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
val importance = call.argument<Int>(NOTIFICATION_IMPORTANCE_KEY)
val iconName = call.argument<String>(NOTIFICATION_ICON_NAME_KEY)
val iconDefType = call.argument<String>(NOTIFICATION_ICON_DEF_TYPE_KEY)
val wifiLock = call.argument<Boolean>(ENABLE_WIFI_LOCK_KEY)

// Set static values so the IsolateHolderService can use them later on to configure the notification
notificationImportance = importance ?: notificationImportance
notificationTitle = title ?: notificationTitle
notificationText = text ?: text
notificationText = text ?: notificationText
notificationIconName = iconName ?: notificationIconName
notificationIconDefType = iconDefType ?: notificationIconDefType
enableWifiLock = wifiLock ?: enableWifiLock

saveNotificationConfiguration(context)

Expand Down Expand Up @@ -144,6 +152,7 @@ class FlutterBackgroundPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
result.error("PermissionError", "The battery optimizations are not turned off.", "")
} else {
val intent = Intent(context, IsolateHolderService::class.java)
intent.action = IsolateHolderService.ACTION_START
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
context!!.startForegroundService(intent)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.net.wifi.WifiManager
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
Expand All @@ -21,25 +22,59 @@ class IsolateHolderService : Service() {
@JvmStatic
val WAKELOCK_TAG = "FlutterBackgroundPlugin:Wakelock"
@JvmStatic
val WIFILOCK_TAG = "FlutterBackgroundPlugin:WifiLock"
@JvmStatic
val CHANNEL_ID = "flutter_background"
@JvmStatic
private val TAG = "IsolateHolderService"
@JvmStatic
val EXTRA_NOTIFICATION_IMPORTANCE = "de.julianassmann.flutter_background:Importance"
@JvmStatic
val EXTRA_NOTIFICATION_TITLE = "de.julianassmann.flutter_background:Title"
@JvmStatic
val EXTRA_NOTIFICATION_TEXT = "de.julianassmann.flutter_background:Text"
}

private var wakeLock: PowerManager.WakeLock? = null
private var wifiLock: WifiManager.WifiLock? = null

override fun onBind(intent: Intent) : IBinder? {
return null
}

@SuppressLint("WakelockTimeout")
override fun onCreate() {
FlutterBackgroundPlugin.loadNotificationConfiguration(applicationContext)
}

override fun onDestroy() {
cleanupService()
super.onDestroy()
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) : Int {
if (intent?.action == ACTION_SHUTDOWN) {
cleanupService()
stopSelf()
} else if (intent?.action == ACTION_START) {
startService()
}
return START_STICKY
}

private fun cleanupService() {
wakeLock?.apply {
if (isHeld) {
release()
}
}

if (FlutterBackgroundPlugin.enableWifiLock) {
wifiLock?.apply {
if (isHeld) {
release()
}
}
}

stopForeground(true)
}

@SuppressLint("WakelockTimeout")
private fun startService() {
val pm = applicationContext.packageManager
val notificationIntent =
pm.getLaunchIntentForPackage(applicationContext.packageName)
Expand All @@ -55,54 +90,46 @@ class IsolateHolderService : Service() {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
FlutterBackgroundPlugin.notificationTitle,
FlutterBackgroundPlugin.notificationImportance ?: NotificationCompat.PRIORITY_DEFAULT).apply {
CHANNEL_ID,
FlutterBackgroundPlugin.notificationTitle,
FlutterBackgroundPlugin.notificationImportance).apply {
description = FlutterBackgroundPlugin.notificationText
}
// Register the channel with the system
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}

val imageId = resources.getIdentifier(FlutterBackgroundPlugin.notificationIconName, FlutterBackgroundPlugin.notificationIconDefType, packageName)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(FlutterBackgroundPlugin.notificationTitle)
.setContentText(FlutterBackgroundPlugin.notificationText)
.setSmallIcon(imageId)
.setContentIntent(pendingIntent)
.setPriority(FlutterBackgroundPlugin.notificationImportance ?: NotificationCompat.PRIORITY_DEFAULT)
.build()
.setContentTitle(FlutterBackgroundPlugin.notificationTitle)
.setContentText(FlutterBackgroundPlugin.notificationText)
.setSmallIcon(imageId)
.setContentIntent(pendingIntent)
.setPriority(FlutterBackgroundPlugin.notificationImportance)
.build()

(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG).apply {
wakeLock = newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG).apply {
setReferenceCounted(false)
acquire()
}
}
startForeground(1, notification)

super.onCreate()
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) : Int {
if (intent?.action == ACTION_SHUTDOWN) {
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG).apply {
if (isHeld) {
release()
}
}
(applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager).run {
wifiLock = createWifiLock(WifiManager.WIFI_MODE_FULL, WIFILOCK_TAG).apply {
setReferenceCounted(false)
acquire()
}
stopForeground(true)
stopSelf()
}
return START_STICKY
}

startForeground(1, notification)
}

override fun onTaskRemoved(rootIntent: Intent) {
super.onTaskRemoved(rootIntent)
cleanupService()
stopSelf()
}
}
1 change: 1 addition & 0 deletions example/lib/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ class _HomePageState extends State<HomePage> {
'Background notification for keeping the example app running in the background',
notificationIcon: AndroidResource(name: 'background_icon'),
notificationImportance: AndroidNotificationImportance.Default,
enableWifiLock: true,
);

var hasPermissions = await FlutterBackground.hasPermissions;
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:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.6.1"
version: "2.8.1"
bloc:
dependency: transitive
description:
Expand Down Expand Up @@ -42,7 +42,7 @@ packages:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.1"
clock:
dependency: transitive
description:
Expand Down Expand Up @@ -82,7 +82,7 @@ packages:
path: ".."
relative: true
source: path
version: "1.0.2+1"
version: "1.1.0"
flutter_bloc:
dependency: "direct main"
description:
Expand Down Expand Up @@ -122,7 +122,7 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.7.0"
nested:
dependency: transitive
description:
Expand Down Expand Up @@ -204,7 +204,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
version: "0.4.2"
timezone:
dependency: transitive
description:
Expand Down
Loading

0 comments on commit d96c64b

Please sign in to comment.