-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(react-native-sdk/android): force permissions to launch RNOngoing…
…Notification
- Loading branch information
1 parent
aabc50d
commit 7da0850
Showing
6 changed files
with
315 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
41 changes: 41 additions & 0 deletions
41
react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JMOngoingConferenceModule.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,41 @@ | ||
package org.jitsi.meet.sdk; | ||
import android.app.Activity; | ||
import android.content.Context; | ||
import androidx.annotation.NonNull; | ||
import com.facebook.react.bridge.ReactApplicationContext; | ||
import com.facebook.react.bridge.ReactContextBaseJavaModule; | ||
import com.facebook.react.bridge.ReactMethod; | ||
import com.facebook.react.module.annotations.ReactModule; | ||
|
||
/** | ||
* This class implements a ReactModule and it's | ||
* responsible for launching/aborting a service when a conference is in progress. | ||
*/ | ||
@ReactModule(name = JMOngoingConferenceModule.NAME) | ||
class JMOngoingConferenceModule extends ReactContextBaseJavaModule { | ||
|
||
public static final String NAME = "JMOngoingConference"; | ||
|
||
public JMOngoingConferenceModule(ReactApplicationContext reactContext) { | ||
super(reactContext); | ||
} | ||
|
||
@ReactMethod | ||
public void launch() { | ||
Context context = getReactApplicationContext(); | ||
Activity currentActivity = getCurrentActivity(); | ||
JitsiMeetOngoingConferenceService.launch(context, currentActivity); | ||
} | ||
|
||
@ReactMethod | ||
public void abort() { | ||
Context context = getReactApplicationContext(); | ||
JitsiMeetOngoingConferenceService.abort(context); | ||
} | ||
|
||
@NonNull | ||
@Override | ||
public String getName() { | ||
return NAME; | ||
} | ||
} |
161 changes: 161 additions & 0 deletions
161
...ative-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetOngoingConferenceService.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,161 @@ | ||
/* | ||
* Copyright @ 2019-present 8x8, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.jitsi.meet.sdk; | ||
import static android.Manifest.permission.POST_NOTIFICATIONS; | ||
import static android.Manifest.permission.RECORD_AUDIO; | ||
|
||
import android.app.Activity; | ||
import android.app.Notification; | ||
import android.app.Service; | ||
import android.content.ComponentName; | ||
import android.content.Context; | ||
import android.content.Intent; | ||
import android.content.pm.PackageManager; | ||
import android.content.pm.ServiceInfo; | ||
import android.os.Build; | ||
import android.os.IBinder; | ||
|
||
import com.facebook.react.modules.core.PermissionListener; | ||
|
||
import org.jitsi.meet.sdk.log.JitsiMeetLogger; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Random; | ||
|
||
/** | ||
* This class implements an Android {@link Service}, a foreground one specifically, and it's | ||
* responsible for presenting an ongoing notification when a conference is in progress. | ||
* The service will help keep the app running while in the background. | ||
* | ||
* See: https://developer.android.com/guide/components/services | ||
*/ | ||
public class JitsiMeetOngoingConferenceService extends Service { | ||
private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName(); | ||
|
||
private static final int PERMISSIONS_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE); | ||
|
||
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000; | ||
|
||
private static PermissionListener permissionListener; | ||
|
||
|
||
public static void doLaunch(Context context, Activity currentActivity) { | ||
|
||
RNOngoingNotification.createOngoingConferenceNotificationChannel(currentActivity); | ||
|
||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class); | ||
|
||
ComponentName componentName; | ||
|
||
try { | ||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||
componentName = context.startForegroundService(intent); | ||
} else { | ||
componentName = context.startService(intent); | ||
} | ||
} catch (RuntimeException e) { | ||
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31). | ||
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions | ||
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e); | ||
return; | ||
} | ||
if (componentName == null) { | ||
JitsiMeetLogger.w(TAG + " Ongoing conference service not started"); | ||
} | ||
} | ||
|
||
public static void launch(Context context, Activity currentActivity) { | ||
List<String> permissionsList = new ArrayList<>(); | ||
|
||
PermissionListener listener = new PermissionListener() { | ||
@Override | ||
public boolean onRequestPermissionsResult(int i, String[] strings, int[] results) { | ||
int counter = 0; | ||
|
||
if (results.length > 0) { | ||
for (int result : results) { | ||
if (result == PackageManager.PERMISSION_GRANTED) { | ||
counter++; | ||
} | ||
} | ||
|
||
if (counter == results.length){ | ||
doLaunch(context, currentActivity); | ||
JitsiMeetLogger.w(TAG + " Service launched, permissions were granted"); | ||
} else { | ||
JitsiMeetLogger.w(TAG + " Couldn't launch service, permissions were not granted"); | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
}; | ||
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { | ||
permissionsList.add(POST_NOTIFICATIONS); | ||
permissionsList.add(RECORD_AUDIO); | ||
} | ||
|
||
String[] permissionsArray = new String[ permissionsList.size() ]; | ||
permissionsArray = permissionsList.toArray( permissionsArray ); | ||
|
||
if (permissionsArray.length > 0) { | ||
try { | ||
currentActivity.requestPermissions(permissionsArray, PERMISSIONS_REQUEST_CODE); | ||
} catch (Exception e) { | ||
JitsiMeetLogger.e(e, "Error requesting permissions"); | ||
listener.onRequestPermissionsResult(PERMISSIONS_REQUEST_CODE, permissionsArray, new int[0]); | ||
} | ||
} else { | ||
doLaunch(context, currentActivity); | ||
JitsiMeetLogger.w(TAG + " Service launched"); | ||
} | ||
} | ||
|
||
public static void abort(Context context) { | ||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class); | ||
context.stopService(intent); | ||
} | ||
|
||
@Override | ||
public void onCreate() { | ||
super.onCreate(); | ||
Notification notification = RNOngoingNotification.buildOngoingConferenceNotification(this); | ||
if (notification == null) { | ||
stopSelf(); | ||
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null"); | ||
} else { | ||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { | ||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE); | ||
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { | ||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK); | ||
} else { | ||
startForeground(NOTIFICATION_ID, notification); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public IBinder onBind(Intent intent) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public int onStartCommand(Intent intent, int flags, int startId) { | ||
return START_NOT_STICKY; | ||
} | ||
} |
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
87 changes: 87 additions & 0 deletions
87
react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/RNOngoingNotification.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,87 @@ | ||
/* | ||
* Copyright @ 2019-present 8x8, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.jitsi.meet.sdk; | ||
import android.app.Activity; | ||
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 androidx.annotation.StringRes; | ||
import androidx.core.app.NotificationCompat; | ||
import org.jitsi.meet.sdk.log.JitsiMeetLogger; | ||
import java.util.Random; | ||
/** | ||
* Helper class for creating the ongoing notification which is used with | ||
* {@link JitsiMeetOngoingConferenceService}. It allows the user to easily get back to the app | ||
* and to hangup from within the notification itself. | ||
*/ | ||
class RNOngoingNotification { | ||
private static final String TAG = RNOngoingNotification.class.getSimpleName(); | ||
|
||
static void createOngoingConferenceNotificationChannel(Activity currentActivity) { | ||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { | ||
return; | ||
} | ||
if (currentActivity == null) { | ||
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context"); | ||
return; | ||
} | ||
|
||
NotificationManager notificationManager | ||
= (NotificationManager) currentActivity.getSystemService(Context.NOTIFICATION_SERVICE); | ||
|
||
NotificationChannel channel | ||
= notificationManager.getNotificationChannel("OngoingConferenceChannel"); | ||
|
||
if (channel != null) { | ||
// The channel was already created, no need to do it again. | ||
return; | ||
} | ||
|
||
channel = new NotificationChannel("OngoingConferenceChannel", currentActivity.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT); | ||
channel.enableLights(false); | ||
channel.enableVibration(false); | ||
channel.setShowBadge(false); | ||
|
||
notificationManager.createNotificationChannel(channel); | ||
} | ||
static Notification buildOngoingConferenceNotification(Context context) { | ||
if (context == null) { | ||
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context"); | ||
return null; | ||
} | ||
|
||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "OngoingConferenceChannel"); | ||
|
||
builder | ||
.setCategory(NotificationCompat.CATEGORY_CALL) | ||
.setContentTitle(context.getString(R.string.ongoing_notification_title)) | ||
.setContentText(context.getString(R.string.ongoing_notification_text)) | ||
.setPriority(NotificationCompat.PRIORITY_DEFAULT) | ||
.setOngoing(true) | ||
.setWhen(System.currentTimeMillis()) | ||
.setUsesChronometer(true) | ||
.setAutoCancel(false) | ||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) | ||
.setOnlyAlertOnce(true) | ||
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName())); | ||
|
||
return builder.build(); | ||
} | ||
} |
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