android: add JitsiMeetActivity

It renders a single JitsiMeetFragment which holds the JitsiMeetView view.
This commit is contained in:
Saúl Ibarra Corretgé 2019-03-07 10:23:56 +01:00
parent a7018970ca
commit bf3bcd65d6
4 changed files with 315 additions and 154 deletions

View File

@ -20,34 +20,31 @@ package org.jitsi.meet;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import android.provider.Settings; import android.provider.Settings;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import org.jitsi.meet.sdk.JitsiMeet; import org.jitsi.meet.sdk.JitsiMeet;
import org.jitsi.meet.sdk.JitsiMeetActivityInterface; import org.jitsi.meet.sdk.JitsiMeetActivity;
import org.jitsi.meet.sdk.JitsiMeetActivityDelegate;
import org.jitsi.meet.sdk.JitsiMeetFragment;
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions; import org.jitsi.meet.sdk.JitsiMeetConferenceOptions;
import com.crashlytics.android.Crashlytics; import com.crashlytics.android.Crashlytics;
import com.facebook.react.modules.core.PermissionListener;
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks; import com.google.firebase.dynamiclinks.FirebaseDynamicLinks;
import io.fabric.sdk.android.Fabric; import io.fabric.sdk.android.Fabric;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Map;
/** /**
* The one and only {@link FragmentActivity} that the Jitsi Meet app needs. The * The one and only Activity that the Jitsi Meet app needs. The
* {@code Activity} is launched in {@code singleTask} mode, so it will be * {@code Activity} is launched in {@code singleTask} mode, so it will be
* created upon application initialization and there will be a single instance * created upon application initialization and there will be a single instance
* of it. Further attempts at launching the application once it was already * of it. Further attempts at launching the application once it was already
* launched will result in {@link FragmentActivity#onNewIntent(Intent)} being called. * launched will result in {@link MainActivity#onNewIntent(Intent)} being called.
*/ */
public class MainActivity extends FragmentActivity implements JitsiMeetActivityInterface { public class MainActivity extends JitsiMeetActivity {
/** /**
* The request code identifying requests for the permission to draw on top * The request code identifying requests for the permission to draw on top
* of other apps. The value must be 16-bit and is arbitrarily chosen here. * of other apps. The value must be 16-bit and is arbitrarily chosen here.
@ -55,111 +52,11 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI
private static final int OVERLAY_PERMISSION_REQUEST_CODE private static final int OVERLAY_PERMISSION_REQUEST_CODE
= (int) (Math.random() * Short.MAX_VALUE); = (int) (Math.random() * Short.MAX_VALUE);
private JitsiMeetFragment getFragment() { // JitsiMeetActivity overrides
return (JitsiMeetFragment) getSupportFragmentManager().findFragmentById(R.id.jitsiFragment); //
}
private @Nullable URL buildURL(String urlStr) {
try {
return new URL(urlStr);
} catch (MalformedURLException e) {
return null;
}
}
private void initialize() {
// Set default options
JitsiMeetConferenceOptions defaultOptions
= new JitsiMeetConferenceOptions.Builder()
.setWelcomePageEnabled(true)
.setServerURL(buildURL("https://meet.jit.si"))
.build();
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
// Join the room specified by the URL the app was launched with.
// Joining without the room option displays the welcome page.
join(getIntentUrl(getIntent()));
}
private void join(@Nullable String url) {
JitsiMeetConferenceOptions options
= new JitsiMeetConferenceOptions.Builder()
.setRoom(url)
.build();
getFragment().getJitsiView().join(options);
}
private @Nullable String getIntentUrl(Intent intent) {
Uri uri;
if (Intent.ACTION_VIEW.equals(intent.getAction())
&& (uri = intent.getData()) != null) {
return uri.toString();
}
return null;
}
private boolean canRequestOverlayPermission() {
return
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
}
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected boolean extraInitialize() {
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE
&& canRequestOverlayPermission()) {
if (Settings.canDrawOverlays(this)) {
initialize();
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onBackPressed() {
JitsiMeetActivityDelegate.onBackPressed();
}
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_MENU) {
JitsiMeet.showDevOptions();
return true;
}
return super.onKeyUp(keyCode, event);
}
@Override
public void onNewIntent(Intent intent) {
String url;
if ((url = getIntentUrl(intent)) != null) {
join(url);
return;
}
JitsiMeetActivityDelegate.onNewIntent(intent);
}
@Override
protected void onUserLeaveHint() {
getFragment().getJitsiView().enterPictureInPicture();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the Activity's content view.
setContentView(R.layout.main_layout);
// Setup Crashlytics and Firebase Dynamic Links // Setup Crashlytics and Firebase Dynamic Links
if (BuildConfig.GOOGLE_SERVICES_ENABLED) { if (BuildConfig.GOOGLE_SERVICES_ENABLED) {
Fabric.with(this, new Crashlytics()); Fabric.with(this, new Crashlytics());
@ -184,20 +81,88 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI
if (canRequestOverlayPermission() && !Settings.canDrawOverlays(this)) { if (canRequestOverlayPermission() && !Settings.canDrawOverlays(this)) {
Intent intent Intent intent
= new Intent( = new Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName())); Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE); startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE);
return;
return true;
} }
} }
initialize(); return false;
} }
@Override @Override
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) { protected void initialize() {
JitsiMeetActivityDelegate.requestPermissions(this, permissions, requestCode, listener); // Set default options
JitsiMeetConferenceOptions defaultOptions
= new JitsiMeetConferenceOptions.Builder()
.setWelcomePageEnabled(true)
.setServerURL(buildURL("https://meet.jit.si"))
.build();
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
super.initialize();
} }
@Override
public void onConferenceFailed(Map<String, Object> data) {
Log.d(TAG, "Conference failed: " + data);
}
@Override
public void onConferenceLeft(Map<String, Object> data) {
Log.d(TAG, "Conference left: " + data);
}
@Override
public void onLoadConfigError(Map<String, Object> data) {
Log.d(TAG, "Error loading config: " + data);
}
// Activity lifecycle method overrides
//
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE
&& canRequestOverlayPermission()) {
if (Settings.canDrawOverlays(this)) {
initialize();
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_MENU) {
JitsiMeet.showDevOptions();
return true;
}
return super.onKeyUp(keyCode, event);
}
// Helper methods
//
private @Nullable URL buildURL(String urlStr) {
try {
return new URL(urlStr);
} catch (MalformedURLException e) {
return null;
}
}
private boolean canRequestOverlayPermission() {
return
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
}
} }

View File

@ -1,37 +1,42 @@
<manifest <?xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jitsi.meet.sdk"> package="org.jitsi.meet.sdk">
<!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. --> <!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/> <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" /> <uses-feature
<uses-feature android:glEsVersion="0x00020000"
android:name="android.hardware.camera" android:required="true" />
android:required="false" /> <uses-feature
<uses-feature android:name="android.hardware.camera"
android:name="android.hardware.camera.autofocus" android:required="false" />
android:required="false" /> <uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true"> android:supportsRtl="true">
<activity <activity android:name=".JitsiMeetActivity"></activity>
android:name="com.facebook.react.devsupport.DevSettingsActivity" /> <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service android:name="org.jitsi.meet.sdk.ConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"> <service
<intent-filter> android:name=".ConnectionService"
<action android:name="android.telecom.ConnectionService" /> android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
</intent-filter> <intent-filter>
</service> <action android:name="android.telecom.ConnectionService" />
</application> </intent-filter>
</manifest> </service>
</application>
</manifest>

View File

@ -0,0 +1,189 @@
/*
* 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.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import com.facebook.react.modules.core.PermissionListener;
import java.util.Map;
/**
* A base activity for SDK users to embed. It uses {@link JitsiMeetFragment} to do the heavy
* lifting and wires the remaining Activity lifecycle methods so it works out of the box.
*/
public class JitsiMeetActivity extends FragmentActivity
implements JitsiMeetActivityInterface, JitsiMeetViewListener {
protected static final String TAG = JitsiMeetActivity.class.getSimpleName();
// Overrides
//
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jitsi_meet);
if (!extraInitialize()) {
initialize();
}
}
@Override
public void finish() {
getJitsiView().leave();
super.finish();
}
// Helper methods
//
protected JitsiMeetView getJitsiView() {
JitsiMeetFragment fragment
= (JitsiMeetFragment) getSupportFragmentManager().findFragmentById(R.id.jitsiFragment);
return fragment.getJitsiView();
}
protected void join(@Nullable String url) {
JitsiMeetConferenceOptions options
= new JitsiMeetConferenceOptions.Builder()
.setRoom(url)
.build();
join(options);
}
protected void join(JitsiMeetConferenceOptions options) {
getJitsiView().join(options);
}
private @Nullable JitsiMeetConferenceOptions getConferenceOptions(Intent intent) {
Uri uri;
if (Intent.ACTION_VIEW.equals(intent.getAction())
&& (uri = intent.getData()) != null) {
JitsiMeetConferenceOptions options
= new JitsiMeetConferenceOptions.Builder()
.setRoom(uri.toString())
.build();
return options;
}
// TODO: accept JitsiMeetConferenceOptions directly.
return null;
}
/**
* Helper function called during activity initialization. If {@code true} is returned, the
* initialization is delayed and the {@link JitsiMeetActivity#initialize()} method is not
* called. In this case, it's up to the subclass to call the initialize method when ready.
*
* This is mainly required so we do some extra initialization in the Jitsi Meet app.
*
* @return {@code true} if the initialization will be delayed, {@code false} otherwise.
*/
protected boolean extraInitialize() {
return false;
}
protected void initialize() {
// Listen for conference events.
getJitsiView().setListener(this);
// Join the room specified by the URL the app was launched with.
// Joining without the room option displays the welcome page.
join(getConferenceOptions(getIntent()));
}
// Activity lifecycle methods
//
@Override
public void onBackPressed() {
JitsiMeetActivityDelegate.onBackPressed();
}
@Override
public void onNewIntent(Intent intent) {
JitsiMeetConferenceOptions options;
if ((options = getConferenceOptions(intent)) != null) {
join(options);
return;
}
JitsiMeetActivityDelegate.onNewIntent(intent);
}
@Override
protected void onUserLeaveHint() {
getJitsiView().enterPictureInPicture();
}
// JitsiMeetActivityInterface
//
@Override
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
JitsiMeetActivityDelegate.requestPermissions(this, permissions, requestCode, listener);
}
// JitsiMeetViewListener
//
@Override
public void onConferenceFailed(Map<String, Object> data) {
Log.d(TAG, "Conference failed: " + data);
finish();
}
@Override
public void onConferenceJoined(Map<String, Object> data) {
Log.d(TAG, "Conference joined: " + data);
}
@Override
public void onConferenceLeft(Map<String, Object> data) {
Log.d(TAG, "Conference left: " + data);
finish();
}
@Override
public void onConferenceWillJoin(Map<String, Object> data) {
Log.d(TAG, "Conference will join: " + data);
}
@Override
public void onConferenceWillLeave(Map<String, Object> data) {
Log.d(TAG, "Conference will leave: " + data);
}
@Override
public void onLoadConfigError(Map<String, Object> data) {
Log.d(TAG, "Error loading config: " + data);
finish();
}
}

View File

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
tools:context=".JitsiMeetActivity">
<fragment <fragment
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:name="org.jitsi.meet.sdk.JitsiMeetFragment" android:name="org.jitsi.meet.sdk.JitsiMeetFragment"
android:id="@+id/jitsiFragment"/> android:id="@+id/jitsiFragment"/>
</FrameLayout> </FrameLayout>