Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
YuliaGrigorieva committed Jan 16, 2019
0 parents commit fb26745
Show file tree
Hide file tree
Showing 11 changed files with 644 additions and 0 deletions.
48 changes: 48 additions & 0 deletions .gitignore
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

133 changes: 133 additions & 0 deletions README.md
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>&nbsp;0 – PRIORITY_DEFAULT (by default)</li><li>-1 – PRIORITY_LOW</li><li>-2 – PRIORITY_MIN</li><li>&nbsp;1 – PRIORITY_HIGH</li><li>&nbsp;2 – PRIORITY_MAX</li></ul> | no |
31 changes: 31 additions & 0 deletions android/build.gradle
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')}"
}

6 changes: 6 additions & 0 deletions android/src/main/AndroidManifest.xml
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>

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";

}
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;
}
}
Loading

0 comments on commit fb26745

Please sign in to comment.