Browse Source

sdk(react-native-sdk): rnsdk screenshare android fix (#13884)

sdk(react-native-sdk): rnsdk screenshare android fix
factor2
Calinteodor 1 year ago
parent
commit
8a4990d9ae
No account linked to committer's email address

+ 4
- 0
.gitignore View File

99
 #
99
 #
100
 react-native-sdk/*.tgz
100
 react-native-sdk/*.tgz
101
 react-native-sdk/android/src
101
 react-native-sdk/android/src
102
+!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetOngoingConferenceService.java
103
+!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetReactNativePackage.java
104
+!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JMOngoingConferenceModule.java
105
+!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/RNOngoingNotification.java
102
 react-native-sdk/images
106
 react-native-sdk/images
103
 react-native-sdk/ios
107
 react-native-sdk/ios
104
 react-native-sdk/lang
108
 react-native-sdk/lang

+ 7
- 0
react-native-sdk/README.md View File

73
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
73
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
74
     <uses-permission android:name="android.permission.CAMERA" />
74
     <uses-permission android:name="android.permission.CAMERA" />
75
   ```
75
   ```
76
+- In `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag, include
77
+    ```xml
78
+   <service
79
+       android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
80
+       android:foregroundServiceType="mediaProjection" />
81
+    ```
82
+  This will take care of the screen share feature.
76
 
83
 
77
 If you want to test all the steps before applying them to your app, you can check our React Native SDK sample app here:
84
 If you want to test all the steps before applying them to your app, you can check our React Native SDK sample app here:
78
 https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native
85
 https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native

+ 44
- 0
react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JMOngoingConferenceModule.java View File

1
+package org.jitsi.meet.sdk;
2
+
3
+import android.app.Notification;
4
+import android.content.Context;
5
+
6
+import androidx.annotation.NonNull;
7
+
8
+import com.facebook.react.bridge.ReactApplicationContext;
9
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
10
+import com.facebook.react.bridge.ReactMethod;
11
+import com.facebook.react.module.annotations.ReactModule;
12
+
13
+
14
+@ReactModule(name = JMOngoingConferenceModule.NAME)
15
+class JMOngoingConferenceModule
16
+    extends ReactContextBaseJavaModule {
17
+
18
+    public static final String NAME = "JMOngoingConference";
19
+
20
+    public JMOngoingConferenceModule(ReactApplicationContext reactContext) {
21
+        super(reactContext);
22
+    }
23
+
24
+    @ReactMethod
25
+    public void launch() {
26
+        Context context = getReactApplicationContext();
27
+        Context activityContext = context.getCurrentActivity();
28
+
29
+        JitsiMeetOngoingConferenceService.launch(context, activityContext);
30
+    }
31
+
32
+    @ReactMethod
33
+    public void abort() {
34
+        Context context = getReactApplicationContext();
35
+
36
+        JitsiMeetOngoingConferenceService.abort(context);
37
+    }
38
+
39
+    @NonNull
40
+    @Override
41
+    public String getName() {
42
+        return NAME;
43
+    }
44
+}

+ 100
- 0
react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetOngoingConferenceService.java View File

1
+/*
2
+ * Copyright @ 2019-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk;
18
+
19
+import android.app.Notification;
20
+import android.app.NotificationManager;
21
+import android.app.Service;
22
+import android.content.ComponentName;
23
+import android.content.Context;
24
+import android.content.Intent;
25
+import android.content.IntentFilter;
26
+import android.os.Build;
27
+import android.os.Bundle;
28
+import android.os.IBinder;
29
+
30
+import org.jitsi.meet.sdk.log.JitsiMeetLogger;
31
+
32
+import java.util.HashMap;
33
+
34
+/**
35
+ * This class implements an Android {@link Service}, a foreground one specifically, and it's
36
+ * responsible for presenting an ongoing notification when a conference is in progress.
37
+ * The service will help keep the app running while in the background.
38
+ *
39
+ * See: https://developer.android.com/guide/components/services
40
+ */
41
+public class JitsiMeetOngoingConferenceService extends Service {
42
+    private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
43
+
44
+    public static void launch(Context context, Context activityContext) {
45
+
46
+        RNOngoingNotification.createOngoingConferenceNotificationChannel(activityContext);
47
+
48
+        Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
49
+
50
+        ComponentName componentName;
51
+
52
+        try {
53
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
54
+                componentName = context.startForegroundService(intent);
55
+            } else {
56
+                componentName = context.startService(intent);
57
+            }
58
+        } catch (RuntimeException e) {
59
+            // Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
60
+            // See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
61
+            JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
62
+            return;
63
+        }
64
+
65
+        if (componentName == null) {
66
+            JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
67
+        }
68
+    }
69
+
70
+    public static void abort(Context context) {
71
+        Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
72
+        context.stopService(intent);
73
+    }
74
+
75
+    @Override
76
+    public void onCreate() {
77
+        super.onCreate();
78
+
79
+        Notification notification = RNOngoingNotification.buildOngoingConferenceNotification(this);
80
+
81
+        if (notification == null) {
82
+            stopSelf();
83
+            JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
84
+        } else {
85
+            startForeground(RNOngoingNotification.NOTIFICATION_ID, notification);
86
+            JitsiMeetLogger.i(TAG + " Service started");
87
+        }
88
+    }
89
+
90
+    @Override
91
+    public IBinder onBind(Intent intent) {
92
+        return null;
93
+    }
94
+
95
+    @Override
96
+    public int onStartCommand(Intent intent, int flags, int startId) {
97
+
98
+        return START_NOT_STICKY;
99
+    }
100
+}

+ 1
- 0
react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetReactNativePackage.java View File

21
                 new AndroidSettingsModule(reactContext),
21
                 new AndroidSettingsModule(reactContext),
22
                 new AppInfoModule(reactContext),
22
                 new AppInfoModule(reactContext),
23
                 new AudioModeModule(reactContext),
23
                 new AudioModeModule(reactContext),
24
+                new JMOngoingConferenceModule(reactContext),
24
                 new JavaScriptSandboxModule(reactContext),
25
                 new JavaScriptSandboxModule(reactContext),
25
                 new LocaleDetector(reactContext),
26
                 new LocaleDetector(reactContext),
26
                 new LogBridgeModule(reactContext),
27
                 new LogBridgeModule(reactContext),

+ 97
- 0
react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/RNOngoingNotification.java View File

1
+/*
2
+ * Copyright @ 2019-present 8x8, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package org.jitsi.meet.sdk;
18
+
19
+import android.app.Notification;
20
+import android.app.NotificationChannel;
21
+import android.app.NotificationManager;
22
+import android.app.PendingIntent;
23
+import android.content.Context;
24
+import android.content.Intent;
25
+import android.os.Build;
26
+
27
+import androidx.annotation.StringRes;
28
+import androidx.core.app.NotificationCompat;
29
+
30
+import org.jitsi.meet.sdk.log.JitsiMeetLogger;
31
+
32
+import java.util.Random;
33
+
34
+/**
35
+ * Helper class for creating the ongoing notification which is used with
36
+ * {@link JitsiMeetOngoingConferenceService}. It allows the user to easily get back to the app
37
+ * and to hangup from within the notification itself.
38
+ */
39
+class RNOngoingNotification {
40
+    private static final String TAG = RNOngoingNotification.class.getSimpleName();
41
+
42
+    static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
43
+
44
+    static void createOngoingConferenceNotificationChannel(Context activityContext) {
45
+
46
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
47
+            return;
48
+        }
49
+
50
+        if (activityContext == null) {
51
+            JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
52
+            return;
53
+        }
54
+
55
+        NotificationManager notificationManager
56
+            = (NotificationManager) activityContext.getSystemService(Context.NOTIFICATION_SERVICE);
57
+
58
+        NotificationChannel channel
59
+            = notificationManager.getNotificationChannel("JitsiOngoingConferenceChannel");
60
+        if (channel != null) {
61
+            // The channel was already created, no need to do it again.
62
+            return;
63
+        }
64
+
65
+        channel = new NotificationChannel("JitsiOngoingConferenceChannel", activityContext.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT);
66
+        channel.enableLights(false);
67
+        channel.enableVibration(false);
68
+        channel.setShowBadge(false);
69
+
70
+        notificationManager.createNotificationChannel(channel);
71
+    }
72
+
73
+    static Notification buildOngoingConferenceNotification(Context context) {
74
+
75
+        if (context == null) {
76
+            JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
77
+            return null;
78
+        }
79
+
80
+        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "JitsiOngoingConferenceChannel");
81
+
82
+        builder
83
+            .setCategory(NotificationCompat.CATEGORY_CALL)
84
+            .setContentTitle(context.getString(R.string.ongoing_notification_title))
85
+            .setContentText(context.getString(R.string.ongoing_notification_text))
86
+            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
87
+            .setOngoing(true)
88
+            .setWhen(System.currentTimeMillis())
89
+            .setUsesChronometer(true)
90
+            .setAutoCancel(false)
91
+            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
92
+            .setOnlyAlertOnce(true)
93
+            .setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
94
+
95
+        return builder.build();
96
+    }
97
+}

+ 26
- 0
react-native-sdk/prepare_sdk.js View File

6
 const SDKPackageJSON = require('./package.json');
6
 const SDKPackageJSON = require('./package.json');
7
 
7
 
8
 const androidSourcePath = '../android/sdk/src/main/java/org/jitsi/meet/sdk';
8
 const androidSourcePath = '../android/sdk/src/main/java/org/jitsi/meet/sdk';
9
+const androidMainSourcePath = '../android/sdk/src/main/res';
9
 const androidTargetPath = './android/src/main/java/org/jitsi/meet/sdk';
10
 const androidTargetPath = './android/src/main/java/org/jitsi/meet/sdk';
11
+const androidMainTargetPath = './android/src/main/res';
10
 const iosSrcPath = '../ios/sdk/src';
12
 const iosSrcPath = '../ios/sdk/src';
11
 const iosDestPath = './ios/src';
13
 const iosDestPath = './ios/src';
12
 
14
 
169
     `${androidSourcePath}/log`,
171
     `${androidSourcePath}/log`,
170
      `${androidTargetPath}/log`
172
      `${androidTargetPath}/log`
171
 );
173
 );
174
+copyFolderRecursiveSync(
175
+    `${androidMainSourcePath}/values`,
176
+    `${androidMainTargetPath}`
177
+);
178
+copyFolderRecursiveSync(
179
+    `${androidMainSourcePath}/drawable-hdpi`,
180
+     `${androidMainTargetPath}`
181
+);
182
+copyFolderRecursiveSync(
183
+    `${androidMainSourcePath}/drawable-mdpi`,
184
+     `${androidMainTargetPath}`
185
+);
186
+copyFolderRecursiveSync(
187
+    `${androidMainSourcePath}/drawable-xhdpi`,
188
+     `${androidMainTargetPath}`
189
+);
190
+copyFolderRecursiveSync(
191
+    `${androidMainSourcePath}/drawable-xxhdpi`,
192
+     `${androidMainTargetPath}`
193
+);
194
+copyFolderRecursiveSync(
195
+    `${androidMainSourcePath}/drawable-xxxhdpi`,
196
+     `${androidMainTargetPath}`
197
+);
172
 copyFolderRecursiveSync(
198
 copyFolderRecursiveSync(
173
     `${androidSourcePath}/net`,
199
     `${androidSourcePath}/net`,
174
     `${androidTargetPath}/log`
200
     `${androidTargetPath}/log`

+ 1
- 1
react/features/mobile/react-native-sdk/functions.js View File

2
 
2
 
3
 
3
 
4
 /**
4
 /**
5
- * Determimes if the ExternalAPI native module is available.
5
+ * Determines if the ExternalAPI native module is available.
6
  *
6
  *
7
  * @returns {boolean} If yes {@code true} otherwise {@code false}.
7
  * @returns {boolean} If yes {@code true} otherwise {@code false}.
8
  */
8
  */

+ 24
- 2
react/features/mobile/react-native-sdk/middleware.js View File

1
+import { NativeModules } from 'react-native';
2
+
1
 import { getAppProp } from '../../base/app/functions';
3
 import { getAppProp } from '../../base/app/functions';
2
 import {
4
 import {
3
     CONFERENCE_BLURRED,
5
     CONFERENCE_BLURRED,
8
 } from '../../base/conference/actionTypes';
10
 } from '../../base/conference/actionTypes';
9
 import { PARTICIPANT_JOINED } from '../../base/participants/actionTypes';
11
 import { PARTICIPANT_JOINED } from '../../base/participants/actionTypes';
10
 import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
12
 import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
13
+import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
11
 import { READY_TO_CLOSE } from '../external-api/actionTypes';
14
 import { READY_TO_CLOSE } from '../external-api/actionTypes';
12
 import { participantToParticipantInfo } from '../external-api/functions';
15
 import { participantToParticipantInfo } from '../external-api/functions';
13
 import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
16
 import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
15
 import { isExternalAPIAvailable } from './functions';
18
 import { isExternalAPIAvailable } from './functions';
16
 
19
 
17
 const externalAPIEnabled = isExternalAPIAvailable();
20
 const externalAPIEnabled = isExternalAPIAvailable();
21
+const { JMOngoingConference } = NativeModules;
22
+
18
 
23
 
19
 /**
24
 /**
20
- * Check if native modules are being used or not. If not then the init of middleware doesn't happen.
25
+ * Check if native modules are being used or not.
26
+ * If not, then the init of middleware doesn't happen.
21
  */
27
  */
22
 !externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
28
 !externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
23
     const result = next(action);
29
     const result = next(action);
56
     }
62
     }
57
 
63
 
58
     return result;
64
     return result;
59
-}
65
+});
66
+
67
+/**
68
+ * Check if native modules are being used or not.
69
+ */
70
+!externalAPIEnabled && StateListenerRegistry.register(
71
+    state => state['features/base/conference'].conference,
72
+    (conference, previousConference) => {
73
+        if (!conference) {
74
+            JMOngoingConference.abort();
75
+        } else if (conference && !previousConference) {
76
+            JMOngoingConference.launch();
77
+        } else if (conference !== previousConference) {
78
+            JMOngoingConference.abort();
79
+            JMOngoingConference.launch();
80
+        }
81
+    }
60
 );
82
 );

Loading…
Cancel
Save