From bf3bcd65d63e556baac30b2d25f0e323d4bcf35f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Thu, 7 Mar 2019 10:23:56 +0100 Subject: [PATCH] android: add JitsiMeetActivity It renders a single JitsiMeetFragment which holds the JitsiMeetView view. --- .../java/org/jitsi/meet/MainActivity.java | 201 ++++++++---------- android/sdk/src/main/AndroidManifest.xml | 73 ++++--- .../org/jitsi/meet/sdk/JitsiMeetActivity.java | 189 ++++++++++++++++ .../main/res/layout/activity_jitsi_meet.xml} | 6 +- 4 files changed, 315 insertions(+), 154 deletions(-) create mode 100644 android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java rename android/{app/src/main/res/layout/main_layout.xml => sdk/src/main/res/layout/activity_jitsi_meet.xml} (70%) diff --git a/android/app/src/main/java/org/jitsi/meet/MainActivity.java b/android/app/src/main/java/org/jitsi/meet/MainActivity.java index 3d919d6fe..5d8b0d93b 100644 --- a/android/app/src/main/java/org/jitsi/meet/MainActivity.java +++ b/android/app/src/main/java/org/jitsi/meet/MainActivity.java @@ -20,34 +20,31 @@ package org.jitsi.meet; import android.content.Intent; import android.net.Uri; import android.os.Build; -import android.os.Bundle; import android.provider.Settings; import android.support.annotation.Nullable; -import android.support.v4.app.FragmentActivity; +import android.util.Log; import android.view.KeyEvent; import org.jitsi.meet.sdk.JitsiMeet; -import org.jitsi.meet.sdk.JitsiMeetActivityInterface; -import org.jitsi.meet.sdk.JitsiMeetActivityDelegate; -import org.jitsi.meet.sdk.JitsiMeetFragment; +import org.jitsi.meet.sdk.JitsiMeetActivity; import org.jitsi.meet.sdk.JitsiMeetConferenceOptions; import com.crashlytics.android.Crashlytics; -import com.facebook.react.modules.core.PermissionListener; import com.google.firebase.dynamiclinks.FirebaseDynamicLinks; import io.fabric.sdk.android.Fabric; import java.net.MalformedURLException; 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 * created upon application initialization and there will be a single instance * 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 * 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 = (int) (Math.random() * Short.MAX_VALUE); - private JitsiMeetFragment getFragment() { - 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; - } + // JitsiMeetActivity 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); - } - - @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); - + protected boolean extraInitialize() { // Setup Crashlytics and Firebase Dynamic Links if (BuildConfig.GOOGLE_SERVICES_ENABLED) { Fabric.with(this, new Crashlytics()); @@ -184,20 +81,88 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI if (canRequestOverlayPermission() && !Settings.canDrawOverlays(this)) { Intent intent = new Intent( - Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + getPackageName())); + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + getPackageName())); startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE); - return; + + return true; } } - initialize(); + return false; } @Override - public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) { - JitsiMeetActivityDelegate.requestPermissions(this, permissions, requestCode, listener); + protected void initialize() { + // 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 data) { + Log.d(TAG, "Conference failed: " + data); + } + + @Override + public void onConferenceLeft(Map data) { + Log.d(TAG, "Conference left: " + data); + } + + @Override + public void onLoadConfigError(Map 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; + } } diff --git a/android/sdk/src/main/AndroidManifest.xml b/android/sdk/src/main/AndroidManifest.xml index 5ff31b4ab..1137f44a6 100644 --- a/android/sdk/src/main/AndroidManifest.xml +++ b/android/sdk/src/main/AndroidManifest.xml @@ -1,37 +1,42 @@ - + - - - - - - - - - - - + + + + + + + + + + + - - - + + + - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java new file mode 100644 index 000000000..2cb52515a --- /dev/null +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java @@ -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 data) { + Log.d(TAG, "Conference failed: " + data); + finish(); + } + + @Override + public void onConferenceJoined(Map data) { + Log.d(TAG, "Conference joined: " + data); + } + + @Override + public void onConferenceLeft(Map data) { + Log.d(TAG, "Conference left: " + data); + finish(); + } + + @Override + public void onConferenceWillJoin(Map data) { + Log.d(TAG, "Conference will join: " + data); + } + + @Override + public void onConferenceWillLeave(Map data) { + Log.d(TAG, "Conference will leave: " + data); + } + + @Override + public void onLoadConfigError(Map data) { + Log.d(TAG, "Error loading config: " + data); + finish(); + } +} diff --git a/android/app/src/main/res/layout/main_layout.xml b/android/sdk/src/main/res/layout/activity_jitsi_meet.xml similarity index 70% rename from android/app/src/main/res/layout/main_layout.xml rename to android/sdk/src/main/res/layout/activity_jitsi_meet.xml index 79e9aa196..46cf5d520 100644 --- a/android/app/src/main/res/layout/main_layout.xml +++ b/android/sdk/src/main/res/layout/activity_jitsi_meet.xml @@ -1,10 +1,12 @@ + android:layout_height="match_parent" + tools:context=".JitsiMeetActivity"> - + \ No newline at end of file