snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
Some checks failed
Prosody Plugin Tests / test (push) Failing after 10m11s
Close stale issues and PRs / stale (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-15 15:56:32 +05:00
commit 1d2bfcac0c
3159 changed files with 464524 additions and 0 deletions

View File

@@ -0,0 +1,156 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:$rootProject.ext.gradlePluginVersion"
}
}
def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
}
apply plugin: 'com.android.library'
if (isNewArchitectureEnabled()) {
apply plugin: 'com.facebook.react'
}
def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['JitsiMeetReactNative_' + name]
}
def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['JitsiMeetReactNative_' + name]).toInteger()
}
android {
namespace 'org.jitsi.meet.sdk'
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
defaultConfig {
minSdkVersion getExtOrIntegerDefault('minSdkVersion')
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
}
buildTypes {
release {
minifyEnabled false
}
}
lintOptions {
disable 'GradleCompatible'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
repositories {
mavenCentral()
google()
def found = false
def defaultDir = null
def androidSourcesName = 'React Native sources'
if (rootProject.ext.has('reactNativeAndroidRoot')) {
defaultDir = rootProject.ext.get('reactNativeAndroidRoot')
} else {
defaultDir = new File(
projectDir,
'/../../../node_modules/react-native/android'
)
}
if (defaultDir.exists()) {
maven {
url defaultDir.toString()
name androidSourcesName
}
logger.info(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}")
found = true
} else {
def parentDir = rootProject.projectDir
1.upto(5, {
if (found) return true
parentDir = parentDir.parentFile
def androidSourcesDir = new File(
parentDir,
'node_modules/react-native'
)
def androidPrebuiltBinaryDir = new File(
parentDir,
'node_modules/react-native/android'
)
if (androidPrebuiltBinaryDir.exists()) {
maven {
url androidPrebuiltBinaryDir.toString()
name androidSourcesName
}
logger.info(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}")
found = true
} else if (androidSourcesDir.exists()) {
maven {
url androidSourcesDir.toString()
name androidSourcesName
}
logger.info(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}")
found = true
}
})
}
if (!found) {
throw new GradleException(
"${project.name}: unable to locate React Native android sources. " +
"Ensure you have you installed React Native as a dependency in your project and try again."
)
}
}
dependencies {
// noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
implementation 'com.jakewharton.timber:timber:4.7.1'
// From node_modules
}
if (isNewArchitectureEnabled()) {
react {
jsRootDir = file("../")
libraryName = "JitsiMeetReactNative"
codegenJavaPackageName = "org.jitsi.meet.sdk"
}
}
// Copy sounds to assets directory
android.libraryVariants.all { def variant ->
def mergeAssetsTask = variant.mergeAssetsProvider.get()
def mergeResourcesTask = variant.mergeResourcesProvider.get()
mergeAssetsTask.doLast {
def assetsDir = mergeAssetsTask.outputDir.get()
def soundsDir = "${projectDir}/../sounds"
copy {
from("${soundsDir}")
include("*.wav")
include("*.mp3")
into("${assetsDir}/sounds")
}
}
}

View File

@@ -0,0 +1,5 @@
JitsiMeetReactNative_kotlinVersion=2.0.21
JitsiMeetReactNative_minSdkVersion=26
JitsiMeetReactNative_targetSdkVersion=34
JitsiMeetReactNative_compileSdkVersion=34
JitsiMeetReactNative_ndkversion=27.1.12297006

View File

@@ -0,0 +1,117 @@
package org.jitsi.meet.sdk;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.Manifest.permission.RECORD_AUDIO;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.facebook.react.ReactActivity;
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;
import com.facebook.react.modules.core.PermissionListener;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 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";
private static final int PERMISSIONS_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE);
public JMOngoingConferenceModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public void launch() {
Context context = getReactApplicationContext();
ReactActivity reactActivity = (ReactActivity) getCurrentActivity();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
JMOngoingConferenceService.launch(context);
JitsiMeetLogger.i(NAME + " launch");
return;
}
PermissionListener listener = new PermissionListener() {
@Override
public boolean onRequestPermissionsResult(int i, String[] strings, int[] results) {
JitsiMeetLogger.i(NAME + " Permission callback received");
if (results == null || results.length == 0) {
JitsiMeetLogger.w(NAME + " Permission results are null or empty");
return true;
}
int counter = 0;
for (int result : results) {
if (result == PackageManager.PERMISSION_GRANTED) {
counter++;
}
}
JitsiMeetLogger.i(NAME + " Permissions granted: " + counter + "/" + results.length);
if (counter == results.length) {
JitsiMeetLogger.i(NAME + " All permissions granted, launching service");
JMOngoingConferenceService.launch(context);
} else {
JitsiMeetLogger.w(NAME + " Not all permissions were granted");
}
return true;
}
};
JitsiMeetLogger.i(NAME + " Checking Tiramisu permissions");
List<String> permissionsList = new ArrayList<>();
permissionsList.add(POST_NOTIFICATIONS);
permissionsList.add(RECORD_AUDIO);
String[] permissionsArray = new String[ permissionsList.size() ];
permissionsArray = permissionsList.toArray( permissionsArray );
try {
JitsiMeetLogger.i(NAME + " Requesting permissions: " + Arrays.toString(permissionsArray));
reactActivity.requestPermissions(permissionsArray, PERMISSIONS_REQUEST_CODE, listener);
} catch (Exception e) {
JitsiMeetLogger.e(e, NAME + " Error requesting permissions");
}
}
@ReactMethod
public void abort() {
Context context = getReactApplicationContext();
JMOngoingConferenceService.abort(context);
JitsiMeetLogger.i(NAME + " abort");
}
@NonNull
@Override
public String getName() {
return NAME;
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.IBinder;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
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 JMOngoingConferenceService extends Service {
private static final String TAG = JMOngoingConferenceService.class.getSimpleName();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
public static void launch(Context context) {
try {
RNOngoingNotification.createOngoingConferenceNotificationChannel(context);
JitsiMeetLogger.i(TAG + " Notification channel creation completed");
} catch (Exception e) {
JitsiMeetLogger.e(e, TAG + " Error creating notification channel");
}
Intent intent = new Intent(context, JMOngoingConferenceService.class);
try {
context.startForegroundService(intent);
JitsiMeetLogger.i(TAG + " Starting foreground service");
} 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);
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JMOngoingConferenceService.class);
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
JitsiMeetLogger.i(TAG + " onCreate called");
try {
Notification notification = RNOngoingNotification.buildOngoingConferenceNotification(this);
JitsiMeetLogger.i(TAG + " Notification build result: " + (notification != null));
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
JitsiMeetLogger.i(TAG + " Starting service in foreground with notification");
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);
}
}
} catch (Exception e) {
JitsiMeetLogger.e(e, TAG + " Error in onCreate");
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -0,0 +1,38 @@
package org.jitsi.meet.sdk;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class JitsiMeetReactNativePackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules
= new ArrayList<>(Arrays.<NativeModule>asList(
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new JMOngoingConferenceModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),
new PictureInPictureModule(reactContext),
new ProximityModule(reactContext)
));
return modules;
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* Helper class for creating the ongoing notification which is used with
* {@link JMOngoingConferenceService}. 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 final String RN_ONGOING_CONFERENCE_CHANNEL_ID = "OngoingConferenceChannel";
static void createOngoingConferenceNotificationChannel(Context context) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Service.NOTIFICATION_SERVICE);
NotificationChannel channel = notificationManager.getNotificationChannel(RN_ONGOING_CONFERENCE_CHANNEL_ID);
if (channel != null) {
JitsiMeetLogger.i(TAG + " Notification channel already exists");
return;
}
channel = new NotificationChannel(
RN_ONGOING_CONFERENCE_CHANNEL_ID,
context.getString(R.string.ongoing_notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT
);
channel.enableVibration(true);
channel.setShowBadge(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
notificationManager.createNotificationChannel(channel);
JitsiMeetLogger.i(TAG + " Notification channel created with importance: " + channel.getImportance());
}
static Notification buildOngoingConferenceNotification(Context context) {
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
}
JitsiMeetLogger.i(TAG + " Creating notification with context: " + context);
// Creating an intent to launch app's main activity
Intent intent = context.getPackageManager()
.getLaunchIntentForPackage(context.getPackageName());
assert intent != null;
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
// Creating PendingIntent
PendingIntent pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, RN_ONGOING_CONFERENCE_CHANNEL_ID);
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(true)
.setWhen(System.currentTimeMillis())
.setUsesChronometer(true)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()))
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setContentIntent(pendingIntent);
return builder.build();
}
}