Преглед на файлове

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

sdk(react-native-sdk): rnsdk screenshare android fix
factor2
Calinteodor преди 1 година
родител
ревизия
8a4990d9ae
No account linked to committer's email address

+ 4
- 0
.gitignore Целия файл

@@ -99,6 +99,10 @@ tsconfig.json
99 99
 #
100 100
 react-native-sdk/*.tgz
101 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 106
 react-native-sdk/images
103 107
 react-native-sdk/ios
104 108
 react-native-sdk/lang

+ 7
- 0
react-native-sdk/README.md Целия файл

@@ -73,6 +73,13 @@ cd ios && pod install && cd ..
73 73
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
74 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 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 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 Целия файл

@@ -0,0 +1,44 @@
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 Целия файл

@@ -0,0 +1,100 @@
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 Целия файл

@@ -21,6 +21,7 @@ public class JitsiMeetReactNativePackage implements ReactPackage {
21 21
                 new AndroidSettingsModule(reactContext),
22 22
                 new AppInfoModule(reactContext),
23 23
                 new AudioModeModule(reactContext),
24
+                new JMOngoingConferenceModule(reactContext),
24 25
                 new JavaScriptSandboxModule(reactContext),
25 26
                 new LocaleDetector(reactContext),
26 27
                 new LogBridgeModule(reactContext),

+ 97
- 0
react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/RNOngoingNotification.java Целия файл

@@ -0,0 +1,97 @@
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 Целия файл

@@ -6,7 +6,9 @@ const packageJSON = require('../package.json');
6 6
 const SDKPackageJSON = require('./package.json');
7 7
 
8 8
 const androidSourcePath = '../android/sdk/src/main/java/org/jitsi/meet/sdk';
9
+const androidMainSourcePath = '../android/sdk/src/main/res';
9 10
 const androidTargetPath = './android/src/main/java/org/jitsi/meet/sdk';
11
+const androidMainTargetPath = './android/src/main/res';
10 12
 const iosSrcPath = '../ios/sdk/src';
11 13
 const iosDestPath = './ios/src';
12 14
 
@@ -169,6 +171,30 @@ copyFolderRecursiveSync(
169 171
     `${androidSourcePath}/log`,
170 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 198
 copyFolderRecursiveSync(
173 199
     `${androidSourcePath}/net`,
174 200
     `${androidTargetPath}/log`

+ 1
- 1
react/features/mobile/react-native-sdk/functions.js Целия файл

@@ -2,7 +2,7 @@ import { NativeModules } from 'react-native';
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 7
  * @returns {boolean} If yes {@code true} otherwise {@code false}.
8 8
  */

+ 24
- 2
react/features/mobile/react-native-sdk/middleware.js Целия файл

@@ -1,3 +1,5 @@
1
+import { NativeModules } from 'react-native';
2
+
1 3
 import { getAppProp } from '../../base/app/functions';
2 4
 import {
3 5
     CONFERENCE_BLURRED,
@@ -8,6 +10,7 @@ import {
8 10
 } from '../../base/conference/actionTypes';
9 11
 import { PARTICIPANT_JOINED } from '../../base/participants/actionTypes';
10 12
 import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
13
+import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
11 14
 import { READY_TO_CLOSE } from '../external-api/actionTypes';
12 15
 import { participantToParticipantInfo } from '../external-api/functions';
13 16
 import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
@@ -15,9 +18,12 @@ import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
15 18
 import { isExternalAPIAvailable } from './functions';
16 19
 
17 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 28
 !externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
23 29
     const result = next(action);
@@ -56,5 +62,21 @@ const externalAPIEnabled = isExternalAPIAvailable();
56 62
     }
57 63
 
58 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…
Отказ
Запис