From 5b3e8a9b5ec5e613fe0182be2f1300a9bfefa9a5 Mon Sep 17 00:00:00 2001 From: paweldomas Date: Fri, 8 Feb 2019 16:33:07 -0600 Subject: [PATCH] android: introduce JitsiMeetConferenceOptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Saúl Ibarra Corretgé --- .../java/org/jitsi/meet/MainActivity.java | 46 ++- .../java/org/jitsi/meet/sdk/JitsiMeet.java | 20 ++ .../meet/sdk/JitsiMeetConferenceOptions.java | 169 +++++++++++ .../org/jitsi/meet/sdk/JitsiMeetFragment.java | 129 +-------- .../org/jitsi/meet/sdk/JitsiMeetView.java | 268 ++++-------------- 5 files changed, 292 insertions(+), 340 deletions(-) create mode 100644 android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetConferenceOptions.java 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 92345f58a..85b9ac605 100644 --- a/android/app/src/main/java/org/jitsi/meet/MainActivity.java +++ b/android/app/src/main/java/org/jitsi/meet/MainActivity.java @@ -27,21 +27,25 @@ import android.support.v4.app.FragmentActivity; import android.view.KeyEvent; import org.jitsi.meet.sdk.JitsiMeet; -import org.jitsi.meet.sdk.JitsiMeetFragment; import org.jitsi.meet.sdk.JitsiMeetActivityInterface; import org.jitsi.meet.sdk.JitsiMeetActivityDelegate; +import org.jitsi.meet.sdk.JitsiMeetFragment; +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; + /** - * The one and only {@link Activity} that the Jitsi Meet app needs. The + * The one and only {@link FragmentActivity} 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 Activity#onNewIntent(Intent)} being called. + * launched will result in {@link FragmentActivity#onNewIntent(Intent)} being called. */ public class MainActivity extends FragmentActivity implements JitsiMeetActivityInterface { /** @@ -51,14 +55,40 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI private static final int OVERLAY_PERMISSION_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE); + private static final String TAG = "MainActivity"; + 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() { - JitsiMeetFragment fragment = getFragment(); - fragment.setWelcomePageEnabled(true); - fragment.getJitsiView().join(getIntentUrl(getIntent())); + // 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) { @@ -113,7 +143,7 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI String url; if ((url = getIntentUrl(intent)) != null) { - getFragment().getJitsiView().join(url); + join(url); return; } @@ -145,7 +175,7 @@ public class MainActivity extends FragmentActivity implements JitsiMeetActivityI } if (dynamicLink != null) { - getFragment().getJitsiView().join(dynamicLink.toString()); + join(dynamicLink.toString()); } }); } diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeet.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeet.java index 0797a8c09..659ed607b 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeet.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeet.java @@ -16,9 +16,29 @@ */ package org.jitsi.meet.sdk; +import android.os.Bundle; + import com.facebook.react.ReactInstanceManager; public class JitsiMeet { + private static JitsiMeetConferenceOptions defaultConferenceOptions; + + public static JitsiMeetConferenceOptions getDefaultConferenceOptions() { + return defaultConferenceOptions; + } + + static Bundle getDefaultProps() { + if (defaultConferenceOptions != null) { + return defaultConferenceOptions.asProps(); + } + + return new Bundle(); + } + + public static void setDefaultConferenceOptions(JitsiMeetConferenceOptions options) { + defaultConferenceOptions = options; + } + public static void showDevOptions() { ReactInstanceManager reactInstanceManager = ReactInstanceManagerHolder.getReactInstanceManager(); diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetConferenceOptions.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetConferenceOptions.java new file mode 100644 index 000000000..b1dc566a3 --- /dev/null +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetConferenceOptions.java @@ -0,0 +1,169 @@ +/* + * 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.os.Bundle; + +import java.net.URL; + + +public class JitsiMeetConferenceOptions { + private URL serverURL; + private String room; + private String token; + + private Bundle colorScheme; + + private Boolean audioMuted; + private Boolean audioOnly; + private Boolean videoMuted; + + private Boolean welcomePageEnabled; + + public static class Builder { + private URL serverURL; + private String room; + private String token; + + private Bundle colorScheme; + + private Boolean audioMuted; + private Boolean audioOnly; + private Boolean videoMuted; + + private Boolean welcomePageEnabled; + + public Builder() { + } + + public Builder setServerURL(URL url) { + this.serverURL = url; + + return this; + } + + public Builder setRoom(String room) { + this.room = room; + + return this; + } + + public Builder setToken(String token) { + this.token = token; + + return this; + } + + public Builder setColorScheme(Bundle colorScheme) { + this.colorScheme = colorScheme; + + return this; + } + + public Builder setAudioMuted(boolean muted) { + this.audioMuted = muted; + + return this; + } + + public Builder setAudioOnly(boolean audioOnly) { + this.audioOnly = audioOnly; + + return this; + } + + public Builder setVideoMuted(boolean videoMuted) { + this.videoMuted = videoMuted; + + return this; + } + + public Builder setWelcomePageEnabled(boolean enabled) { + this.welcomePageEnabled = enabled; + + return this; + } + + public JitsiMeetConferenceOptions build() { + JitsiMeetConferenceOptions options = new JitsiMeetConferenceOptions(); + + options.serverURL = this.serverURL; + options.room = this.room; + options.token = this.token; + options.colorScheme = this.colorScheme; + options.audioMuted = this.audioMuted; + options.audioOnly = this.audioOnly; + options.videoMuted = this.videoMuted; + options.welcomePageEnabled = this.welcomePageEnabled; + + return options; + } + } + + private JitsiMeetConferenceOptions() { + } + + Bundle asProps() { + Bundle props = new Bundle(); + + if (colorScheme != null) { + props.putBundle("colorScheme", colorScheme); + } + + if (welcomePageEnabled != null) { + props.putBoolean("welcomePageEnabled", welcomePageEnabled); + } + + // TODO: get rid of this. + props.putBoolean("pictureInPictureEnabled", true); + + Bundle config = new Bundle(); + + if (audioMuted != null) { + config.putBoolean("startWithAudioMuted", audioMuted); + } + if (audioOnly != null) { + config.putBoolean("startAudioOnly", audioOnly); + } + if (videoMuted != null) { + config.putBoolean("startWithVideoMuted", videoMuted); + } + + Bundle urlProps = new Bundle(); + + // The room is fully qualified + if (room != null && room.contains("://")) { + urlProps.putString("url", room); + } else { + if (serverURL != null) { + urlProps.putString("serverURL", serverURL.toString()); + } + if (room != null) { + urlProps.putString("room", room); + } + } + + if (token != null) { + urlProps.putString("jwt", token); + } + + urlProps.putBundle("config", config); + props.putBundle("url", urlProps); + + return props; + } +} diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetFragment.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetFragment.java index c432aa5d6..f2c857264 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetFragment.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetFragment.java @@ -41,95 +41,23 @@ import java.net.URL; */ public class JitsiMeetFragment extends Fragment { - /** - * A color scheme object to override the default color is the SDK. - */ - private Bundle colorScheme; - - /** - * The default base {@code URL} used to join a conference when a partial URL - * (e.g. a room name only) is specified. The value is used only while - * {@link #view} equals {@code null}. - */ - private URL defaultURL; - /** * Instance of the {@link JitsiMeetView} which this activity will display. */ private JitsiMeetView view; - /** - * Whether Picture-in-Picture is enabled. The value is used only while - * {@link #view} equals {@code null}. - */ - private Boolean pictureInPictureEnabled; - - /** - * Whether the Welcome page is enabled. The value is used only while - * {@link #view} equals {@code null}. - */ - private boolean welcomePageEnabled; - - /** - * - * @see JitsiMeetView#getDefaultURL() - */ - public URL getDefaultURL() { - return view == null ? defaultURL : view.getDefaultURL(); - } - @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - this.view = initializeView(); - - return this.view; - } - - /** - * Initializes a new {@link JitsiMeetView} instance. - * - * @return a new {@code JitsiMeetView} instance. - */ - protected JitsiMeetView initializeView() { - JitsiMeetView view = new JitsiMeetView(getActivity()); - - // XXX Before calling JitsiMeetView#loadURL, make sure to call whatever - // is documented to need such an order in order to take effect: - view.setColorScheme(colorScheme); - view.setDefaultURL(defaultURL); - if (pictureInPictureEnabled != null) { - view.setPictureInPictureEnabled( - pictureInPictureEnabled.booleanValue()); - } - view.setWelcomePageEnabled(welcomePageEnabled); - - return view; + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return this.view = new JitsiMeetView(getActivity()); } public JitsiMeetView getJitsiView() { return view; } - /** - * - * @see JitsiMeetView#isPictureInPictureEnabled() - */ - public boolean isPictureInPictureEnabled() { - return - view == null - ? pictureInPictureEnabled - : view.isPictureInPictureEnabled(); - } - - /** - * - * @see JitsiMeetView#isWelcomePageEnabled() - */ - public boolean isWelcomePageEnabled() { - return view == null ? welcomePageEnabled : view.isWelcomePageEnabled(); - } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { JitsiMeetActivityDelegate.onActivityResult( @@ -181,53 +109,4 @@ public class JitsiMeetFragment extends Fragment { view.enterPictureInPicture(); } } - - /** - * - * @see JitsiMeetView#setColorScheme(Bundle) - */ - public void setColorScheme(Bundle colorScheme) { - if (view == null) { - this.colorScheme = colorScheme; - } else { - view.setColorScheme(colorScheme); - } - } - - /** - * - * @see JitsiMeetView#setDefaultURL(URL) - */ - public void setDefaultURL(URL defaultURL) { - if (view == null) { - this.defaultURL = defaultURL; - } else { - view.setDefaultURL(defaultURL); - } - } - - /** - * - * @see JitsiMeetView#setPictureInPictureEnabled(boolean) - */ - public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) { - if (view == null) { - this.pictureInPictureEnabled - = Boolean.valueOf(pictureInPictureEnabled); - } else { - view.setPictureInPictureEnabled(pictureInPictureEnabled); - } - } - - /** - * - * @see JitsiMeetView#setWelcomePageEnabled(boolean) - */ - public void setWelcomePageEnabled(boolean welcomePageEnabled) { - if (view == null) { - this.welcomePageEnabled = welcomePageEnabled; - } else { - view.setWelcomePageEnabled(welcomePageEnabled); - } - } } diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java index 2ec22587a..c30650fe5 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java @@ -26,11 +26,10 @@ import android.util.Log; import com.facebook.react.bridge.ReadableMap; import java.lang.reflect.Method; -import java.net.URL; import java.util.Map; -public class JitsiMeetView - extends BaseReactView { + +public class JitsiMeetView extends BaseReactView { /** * The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e. @@ -45,25 +44,6 @@ public class JitsiMeetView */ private static final String TAG = JitsiMeetView.class.getSimpleName(); - /** - * A color scheme object to override the default color is the SDK. - */ - private Bundle colorScheme; - - /** - * The default base {@code URL} used to join a conference when a partial URL - * (e.g. a room name only) is specified to {@link #loadURLString(String)} or - * {@link #loadURLObject(Bundle)}. - */ - private URL defaultURL; - - /** - * Whether Picture-in-Picture is enabled. If {@code null}, defaults to - * {@code true} iff the Android platform supports Picture-in-Picture - * natively. - */ - private Boolean pictureInPictureEnabled; - /** * The URL of the current conference. */ @@ -71,10 +51,45 @@ public class JitsiMeetView // fine to have this field volatile without additional synchronization. private volatile String url; - /** - * Whether the Welcome page is enabled. - */ - private boolean welcomePageEnabled; + private static Bundle mergeProps(@Nullable Bundle a, @Nullable Bundle b) { + Bundle result = new Bundle(); + + if (a == null) { + if (b != null) { + result.putAll(b); + } + + return result; + } + + if (b == null) { + result.putAll(a); + + return result; + } + + // Start by putting all of a in the result. + result.putAll(a); + + // Iterate over each key in b and override if appropriate. + for (String key : b.keySet()) { + Object bValue = b.get(key); + Object aValue = a.get(key); + String valueType = bValue.getClass().getSimpleName(); + + if (valueType.contentEquals("Boolean")) { + result.putBoolean(key, (Boolean)bValue); + } else if (valueType.contentEquals("String")) { + result.putString(key, (String)bValue); + } else if (valueType.contentEquals("Bundle")) { + result.putBundle(key, mergeProps((Bundle)aValue, (Bundle)bValue)); + } + + // TODO: handle string arrays when the need arises. + } + + return result; + } public JitsiMeetView(@NonNull Context context) { super(context); @@ -96,134 +111,31 @@ public class JitsiMeetView * page. */ public void enterPictureInPicture() { - if (isPictureInPictureEnabled() && getURL() != null) { - PictureInPictureModule pipModule - = ReactInstanceManagerHolder.getNativeModule( + PictureInPictureModule pipModule + = ReactInstanceManagerHolder.getNativeModule( PictureInPictureModule.class); - - if (pipModule != null) { - try { - pipModule.enterPictureInPicture(); - } catch (RuntimeException re) { - Log.e(TAG, "onUserLeaveHint: failed to enter PiP mode", re); - } + if (pipModule != null + && PictureInPictureModule.isPictureInPictureSupported() + && this.url != null) { + try { + pipModule.enterPictureInPicture(); + } catch (RuntimeException re) { + Log.e(TAG, "failed to enter PiP mode", re); } } } - /** - * Gets the color scheme used in the SDK. - * - * @return The color scheme map. - */ - public Bundle getColorScheme() { - return colorScheme; - } - - /** - * Gets the default base {@code URL} used to join a conference when a - * partial URL (e.g. a room name only) is specified to - * {@link #loadURLString(String)} or {@link #loadURLObject(Bundle)}. If not - * set or if set to {@code null}, the default built in JavaScript is used: - * https://meet.jit.si - * - * @return The default base {@code URL} or {@code null}. - */ - public URL getDefaultURL() { - return defaultURL; - } - - /** - * Gets the URL of the current conference. - * - * XXX The method is meant for internal purposes only at the time of this - * writing because there is no equivalent API on iOS. - * - * @return the URL {@code String} of the current conference if any; - * otherwise, {@code null}. - */ - String getURL() { - return url; - } - - /** - * Gets whether Picture-in-Picture is enabled. Picture-in-Picture is - * natively supported on Android API >= 26 (Oreo), so it should not be - * enabled on older platform versions. - * - * @return If Picture-in-Picture is enabled, {@code true}; {@code false}, - * otherwise. - */ - public boolean isPictureInPictureEnabled() { - return - PictureInPictureModule.isPictureInPictureSupported() - && (pictureInPictureEnabled == null - || pictureInPictureEnabled); - } - - /** - * Gets whether the Welcome page is enabled. If {@code true}, the Welcome - * page is rendered when this {@code JitsiMeetView} is not at a URL - * identifying a Jitsi Meet conference/room. - * - * @return {@code true} if the Welcome page is enabled; otherwise, - * {@code false}. - */ - public boolean isWelcomePageEnabled() { - return welcomePageEnabled; - } - - public void join(@Nullable String url) { - Bundle urlObject; - - if (url == null) { - urlObject = null; - } else { - urlObject = new Bundle(); - urlObject.putString("url", url); - } - loadURL(urlObject); + public void join(@Nullable JitsiMeetConferenceOptions options) { + setProps(options != null ? options.asProps() : new Bundle()); } public void leave() { - loadURL(null); + setProps(new Bundle()); } - /** - * Loads a specific URL which may identify a conference to join. The URL is - * specified in the form of a {@link Bundle} of properties which (1) - * internally are sufficient to construct a URL {@code String} while (2) - * abstracting the specifics of constructing the URL away from API - * clients/consumers. If the specified URL is {@code null} and the Welcome - * page is enabled, the Welcome page is displayed instead. - * - * @param urlObject The URL to load which may identify a conference to join. - */ - private void loadURL(@Nullable Bundle urlObject) { - Bundle props = new Bundle(); - - // color scheme - if (colorScheme != null) { - props.putBundle("colorScheme", colorScheme); - } - - // defaultURL - if (defaultURL != null) { - props.putString("defaultURL", defaultURL.toString()); - } - - // pictureInPictureEnabled - props.putBoolean( - "pictureInPictureEnabled", - isPictureInPictureEnabled()); - - // url - if (urlObject != null) { - props.putBundle("url", urlObject); - } - - // welcomePageEnabled - props.putBoolean("welcomePageEnabled", welcomePageEnabled); + private void setProps(@NonNull Bundle newProps) { + // Merge the default options with the newly provided ones. + Bundle props = mergeProps(JitsiMeet.getDefaultProps(), newProps); // XXX The method loadURLObject: is supposed to be imperative i.e. // a second invocation with one and the same URL is expected to join @@ -248,18 +160,18 @@ public class JitsiMeetView * by/associated with the specified {@code eventName}. */ private void maybeSetViewURL(String eventName, ReadableMap eventData) { + String url = eventData.getString("url"); + switch(eventName) { case "CONFERENCE_WILL_JOIN": - setURL(eventData.getString("url")); + this.url = url; break; case "CONFERENCE_FAILED": case "CONFERENCE_WILL_LEAVE": case "LOAD_CONFIG_ERROR": - String url = eventData.getString("url"); - - if (url != null && url.equals(getURL())) { - setURL(null); + if (url != null && url.equals(this.url)) { + this.url = null; } break; } @@ -283,62 +195,4 @@ public class JitsiMeetView onExternalAPIEvent(LISTENER_METHODS, name, data); } - - /** - * Sets the color scheme to override the default colors of the SDK. - * - * @param colorScheme The color scheme map. - */ - public void setColorScheme(Bundle colorScheme) { - this.colorScheme = colorScheme; - } - - /** - * Sets the default base {@code URL} used to join a conference when a - * partial URL (e.g. a room name only) is specified to - * {@link #loadURLString(String)} or {@link #loadURLObject(Bundle)}. Must be - * called before {@link #loadURL(URL)} for it to take effect. - * - * @param defaultURL The {@code URL} to be set as the default base URL. - * @see #getDefaultURL() - */ - public void setDefaultURL(URL defaultURL) { - this.defaultURL = defaultURL; - } - - /** - * Sets whether Picture-in-Picture is enabled. Because Picture-in-Picture is - * natively supported only since certain platform versions, specifying - * {@code true} will have no effect on unsupported platform versions. - * - * @param pictureInPictureEnabled To enable Picture-in-Picture, - * {@code true}; otherwise, {@code false}. - */ - public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) { - this.pictureInPictureEnabled = pictureInPictureEnabled; - } - - /** - * Sets the URL of the current conference. - * - * XXX The method is meant for internal purposes only. It does not - * {@code loadURL}, it merely remembers the specified URL. - * - * @param url the URL {@code String} which to be set as the URL of the - * current conference. - */ - void setURL(String url) { - this.url = url; - } - - /** - * Sets whether the Welcome page is enabled. Must be called before - * {@link #loadURL(URL)} for it to take effect. - * - * @param welcomePageEnabled {@code true} to enable the Welcome page; - * otherwise, {@code false}. - */ - public void setWelcomePageEnabled(boolean welcomePageEnabled) { - this.welcomePageEnabled = welcomePageEnabled; - } }