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

Add WifiLock ability #47

Merged
merged 3 commits into from
Nov 14, 2021
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
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