-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit fb26745
Showing
11 changed files
with
644 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
|
||
# OSX | ||
# | ||
.DS_Store | ||
|
||
# node.js | ||
# | ||
node_modules/ | ||
npm-debug.log | ||
yarn-error.log | ||
|
||
|
||
# Xcode | ||
# | ||
build/ | ||
*.pbxuser | ||
!default.pbxuser | ||
*.mode1v3 | ||
!default.mode1v3 | ||
*.mode2v3 | ||
!default.mode2v3 | ||
*.perspectivev3 | ||
!default.perspectivev3 | ||
xcuserdata | ||
*.xccheckout | ||
*.moved-aside | ||
DerivedData | ||
*.hmap | ||
*.ipa | ||
*.xcuserstate | ||
project.xcworkspace | ||
|
||
|
||
# Android/IntelliJ | ||
# | ||
build/ | ||
.idea | ||
.gradle | ||
local.properties | ||
*.iml | ||
|
||
# BUCK | ||
buck-out/ | ||
\.buckd/ | ||
*.keystore | ||
|
||
.npmrc | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
|
||
# react-native-foreground-service | ||
|
||
A foreground service performs some operation that is noticeable to the user. | ||
For example, an audio app would use a foreground service to play an audio track. | ||
Foreground services must display a notification. | ||
Foreground services continue running even when the user isn't interacting with the app. | ||
|
||
See [the Android official documentation](https://developer.android.com/guide/components/services) for details on the concept. | ||
|
||
|
||
## Getting started | ||
|
||
`$ npm install @voximplant/react-native-foreground-service --save` | ||
|
||
### Mostly automatic installation (Android only) | ||
|
||
`$ react-native link @voximplant/react-native-foreground-service` | ||
|
||
Next follow steps #4 and #5 from the manual installation section. | ||
|
||
### Manual installation (Android only) | ||
1. Open up `android/app/src/main/java/[...]/MainActivity.java` | ||
- Add `import com.voximplant.VIForegroundServicePackage;` to the imports at the top of the file | ||
- Add `new VIForegroundServicePackage()` to the list returned by the `getPackages()` method | ||
2. Append the following lines to `android/settings.gradle`: | ||
``` | ||
include ':@voximplant_react-native-foreground-service' | ||
project(':@voximplant_react-native-foreground-service').projectDir = new File(rootProject.projectDir, '../node_modules/@voximplant/react-native-foreground-service/android') | ||
``` | ||
3. Insert the following lines inside the dependencies block in `android/app/build.gradle`: | ||
``` | ||
implementation project(':@voximplant_react-native-foreground-service') | ||
``` | ||
4. Add the FOREGROUND_SERVICE permission to the application's `AndroidManifest.xml`: | ||
``` | ||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> | ||
``` | ||
5. Add VIForegroundService as a service to the application's `AndroidManifest.xml`: | ||
``` | ||
<service android:name="com.voximplant.foregroundservice.VIForegroundService"> </service> | ||
``` | ||
|
||
## Usage | ||
|
||
### Import module | ||
```javascript | ||
import VIForegroundService from '@voximplant/react-native-foreground-service'; | ||
``` | ||
|
||
### Create notification channel (Android 8+) | ||
Since the foreground service must display a notification, for Android 8+ it is required to create a notification | ||
channel first: | ||
```javascript | ||
const channelConfig = { | ||
id: 'channelId', | ||
name: 'Channel name', | ||
description: 'Channel description', | ||
enableVibration: false | ||
}; | ||
VIForegroundService.createNotificationChannel(channelConfig); | ||
``` | ||
|
||
### Start foreground service | ||
```javascript | ||
async startForegroundService() { | ||
const notificationConfig = { | ||
channelId: 'channelId', | ||
id: 3456, | ||
title: 'Title', | ||
text: 'Some text', | ||
icon: 'ic_icon' | ||
}; | ||
try { | ||
await VIForegroundService.startService(notificationConfig); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
} | ||
``` | ||
|
||
### Stop foreground service | ||
```javascript | ||
VIForegroundService.stopService(); | ||
``` | ||
|
||
## Reference | ||
|
||
### Methods | ||
```javascript | ||
static async startService(notificationConfig) | ||
``` | ||
Starts the foreground service and displays a notification with the defined configuration | ||
|
||
------------------------------ | ||
|
||
```javascript | ||
static async stopService() | ||
``` | ||
Stops the foreground service | ||
|
||
------------------------------ | ||
|
||
```javascript | ||
static async createNotificationChannel(channelConfig) | ||
``` | ||
Creates a notification channel for the foreground service. | ||
For Android 8+ the notification channel should be created before starting the foreground service | ||
|
||
### Configs | ||
```javascript | ||
NotificationChannelConfig | ||
``` | ||
| Property name | Description | Required | | ||
|-----------------|-----------------------------------------------------------------------------------------------------------------------|----------| | ||
| id | Unique channel id | yes | | ||
| name | Notification channel name | yes | | ||
| description | Notification channel description | no | | ||
| importance | Notification channel importance. One of:<ul><li>1 – 'min'</li> <li>2 – 'low' (by default)</li><li>3 – 'default'</li><li>4 – 'high'</li><li>5 – 'max'</li></ul> | no | | ||
| enableVibration | Sets whether notification posted to this channel should vibrate. False by default. | no | | ||
|
||
```javascript | ||
NotificationConfig | ||
``` | ||
|
||
| Property name | Description | Required | | ||
|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|----------| | ||
| channelId | Notification channel id to display the notification | yes | | ||
| id | Unique notification id | yes | | ||
| title | Notification title | yes | | ||
| text | Notification text | yes | | ||
| icon | Icon name | yes | | ||
| priority | Priority of this notification. One of: <ul><li> 0 – PRIORITY_DEFAULT (by default)</li><li>-1 – PRIORITY_LOW</li><li>-2 – PRIORITY_MIN</li><li> 1 – PRIORITY_HIGH</li><li> 2 – PRIORITY_MAX</li></ul> | no | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
|
||
apply plugin: 'com.android.library' | ||
|
||
def safeExtGet(prop, fallback) { | ||
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback | ||
} | ||
|
||
android { | ||
compileSdkVersion 27 | ||
buildToolsVersion '28.0.3' | ||
|
||
defaultConfig { | ||
minSdkVersion 16 | ||
targetSdkVersion 27 | ||
versionCode 1 | ||
versionName "1.0" | ||
} | ||
lintOptions { | ||
abortOnError false | ||
} | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
implementation 'com.facebook.react:react-native:+' | ||
api "com.android.support:appcompat-v7:${safeExtGet('supportLibVersion', '27.1.1')}" | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
package="com.voximplant.foregroundservice"> | ||
|
||
</manifest> | ||
|
17 changes: 17 additions & 0 deletions
17
android/src/main/java/com/voximplant/foregroundservice/Constants.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
* Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. | ||
*/ | ||
|
||
package com.voximplant.foregroundservice; | ||
|
||
class Constants { | ||
static final String ACTION_FOREGROUND_SERVICE_START = "com.voximplant.foregroundservice.service_start"; | ||
static final String ACTION_FOREGROUND_SERVICE_STOP = "com.voximplant.foregroundservice.service_stop"; | ||
|
||
static final String NOTIFICATION_CONFIG = "com.voximplant.foregroundservice.notif_config"; | ||
|
||
static final String ERROR_INVALID_CONFIG = "ERROR_INVALID_CONFIG"; | ||
static final String ERROR_SERVICE_ERROR = "ERROR_SERVICE_ERROR"; | ||
static final String ERROR_ANDROID_VERSION = "ERROR_ANDROID_VERSION"; | ||
|
||
} |
137 changes: 137 additions & 0 deletions
137
android/src/main/java/com/voximplant/foregroundservice/NotificationHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* | ||
* Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved. | ||
*/ | ||
|
||
package com.voximplant.foregroundservice; | ||
|
||
import android.app.Notification; | ||
import android.app.NotificationChannel; | ||
import android.app.NotificationManager; | ||
import android.app.PendingIntent; | ||
import android.content.Context; | ||
import android.content.Intent; | ||
import android.os.Build; | ||
import android.os.Bundle; | ||
import android.support.v4.app.NotificationCompat; | ||
import android.util.Log; | ||
|
||
import com.facebook.react.bridge.Promise; | ||
import com.facebook.react.bridge.ReadableMap; | ||
|
||
import static com.voximplant.foregroundservice.Constants.ERROR_ANDROID_VERSION; | ||
import static com.voximplant.foregroundservice.Constants.ERROR_INVALID_CONFIG; | ||
|
||
class NotificationHelper { | ||
private static NotificationHelper instance = null; | ||
private NotificationManager mNotificationManager; | ||
|
||
public static synchronized NotificationHelper getInstance(Context context) { | ||
if (instance == null) { | ||
instance = new NotificationHelper(context); | ||
} | ||
return instance; | ||
} | ||
|
||
private NotificationHelper(Context context) { | ||
mNotificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); | ||
|
||
} | ||
|
||
void createNotificationChannel(ReadableMap channelConfig, Promise promise) { | ||
if (channelConfig == null) { | ||
Log.e("NotificationHelper", "createNotificationChannel: invalid config"); | ||
promise.reject(ERROR_INVALID_CONFIG, "VIForegroundService: Channel config is invalid"); | ||
return; | ||
} | ||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||
if (!channelConfig.hasKey("id")) { | ||
promise.reject(ERROR_INVALID_CONFIG, "VIForegroundService: Channel id is required"); | ||
return; | ||
} | ||
String channelId = channelConfig.getString("id"); | ||
if (!channelConfig.hasKey("name")) { | ||
promise.reject(ERROR_INVALID_CONFIG, "VIForegroundService: Channel name is required"); | ||
return; | ||
} | ||
String channelName = channelConfig.getString("name"); | ||
String channelDescription = channelConfig.getString("description"); | ||
int channelImportance = channelConfig.hasKey("importance") ? | ||
channelConfig.getInt("importance") : NotificationManager.IMPORTANCE_LOW; | ||
boolean enableVibration = channelConfig.hasKey("enableVibration") && channelConfig.getBoolean("enableVibration"); | ||
if (channelId == null || channelName == null) { | ||
promise.reject(ERROR_INVALID_CONFIG, "VIForegroundService: Channel id or name is not specified"); | ||
return; | ||
} | ||
NotificationChannel channel = new NotificationChannel(channelId, channelName, channelImportance); | ||
channel.setDescription(channelDescription); | ||
channel.enableVibration(enableVibration); | ||
mNotificationManager.createNotificationChannel(channel); | ||
promise.resolve(null); | ||
} else { | ||
promise.reject(ERROR_ANDROID_VERSION, "VIForegroundService: Notification channel can be created on Android O+"); | ||
} | ||
} | ||
|
||
Notification buildNotification(Context context, Bundle notificationConfig) { | ||
if (notificationConfig == null) { | ||
Log.e("NotificationHelper", "buildNotification: invalid config"); | ||
return null; | ||
} | ||
Class mainActivityClass = getMainActivityClass(context); | ||
if (mainActivityClass == null) { | ||
return null; | ||
} | ||
Intent notificationIntent = new Intent(context, mainActivityClass); | ||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); | ||
|
||
NotificationCompat.Builder notificationBuilder; | ||
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||
String channelId = notificationConfig.getString("channelId"); | ||
if (channelId == null) { | ||
Log.e("NotificationHelper", "buildNotification: invalid channelId"); | ||
return null; | ||
} | ||
notificationBuilder = new NotificationCompat.Builder(context, channelId); | ||
} else { | ||
notificationBuilder = new NotificationCompat.Builder(context); | ||
} | ||
|
||
int priority = notificationConfig.containsKey("priority") ? notificationConfig.getInt("priority") : NotificationCompat.PRIORITY_HIGH; | ||
|
||
notificationBuilder.setContentTitle(notificationConfig.getString("title")) | ||
.setContentText(notificationConfig.getString("text")) | ||
.setPriority(priority) | ||
.setContentIntent(pendingIntent); | ||
|
||
String iconName = notificationConfig.getString("icon"); | ||
if (iconName != null) { | ||
notificationBuilder.setSmallIcon(getResourceIdForResourceName(context, iconName)); | ||
} | ||
|
||
return notificationBuilder.build(); | ||
} | ||
|
||
private Class getMainActivityClass(Context context) { | ||
String packageName = context.getPackageName(); | ||
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName); | ||
if (launchIntent == null || launchIntent.getComponent() == null) { | ||
Log.e("NotificationHelper", "Failed to get launch intent or component"); | ||
return null; | ||
} | ||
try { | ||
return Class.forName(launchIntent.getComponent().getClassName()); | ||
} catch (ClassNotFoundException e) { | ||
Log.e("NotificationHelper", "Failed to get main activity class"); | ||
return null; | ||
} | ||
} | ||
|
||
private int getResourceIdForResourceName(Context context, String resourceName) { | ||
int resourceId = context.getResources().getIdentifier(resourceName, "drawable", context.getPackageName()); | ||
if (resourceId == 0) { | ||
resourceId = context.getResources().getIdentifier(resourceName, "mipmap", context.getPackageName()); | ||
} | ||
return resourceId; | ||
} | ||
} |
Oops, something went wrong.