From dd41e40b876fce604563702f9acf4614dd4a1ee5 Mon Sep 17 00:00:00 2001 From: Raymond Oung Date: Tue, 21 Jul 2020 17:50:13 +0900 Subject: [PATCH] Adds video and webview activities --- android/app/build.gradle | 4 +- android/app/src/main/AndroidManifest.xml | 16 +- .../com/hapirobo/connect/MainActivity.java | 394 ++++++++++-------- .../com/hapirobo/connect/VideoActivity.java | 46 ++ .../com/hapirobo/connect/WebviewActivity.java | 28 ++ .../app/src/main/res/layout/activity_main.xml | 2 +- .../src/main/res/layout/activity_video.xml | 10 + .../src/main/res/layout/activity_webview.xml | 11 + android/build.gradle | 1 - 9 files changed, 325 insertions(+), 187 deletions(-) create mode 100644 android/app/src/main/java/com/hapirobo/connect/VideoActivity.java create mode 100644 android/app/src/main/java/com/hapirobo/connect/WebviewActivity.java create mode 100644 android/app/src/main/res/layout/activity_video.xml create mode 100644 android/app/src/main/res/layout/activity_webview.xml diff --git a/android/app/build.gradle b/android/app/build.gradle index e6be56e..6a9d21c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -58,7 +58,7 @@ dependencies { // Jitsi Meet // https://github.com/jitsi/jitsi-maven-repository/tree/master/releases/org/jitsi/react/jitsi-meet-sdk - implementation ('org.jitsi.react:jitsi-meet-sdk:2.+') { transitive = true } + implementation('org.jitsi.react:jitsi-meet-sdk:2.+') { transitive = true } // paho-mqtt // https://github.com/eclipse/paho.mqtt.java @@ -68,5 +68,5 @@ dependencies { // temi // https://github.com/robotemi/sdk - implementation 'com.robotemi:sdk:0.10.55' + implementation 'com.robotemi:sdk:0.10.+' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 885f0cf..773523c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -9,26 +9,22 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/SkillTheme"> + + - - - - - - + + - + android:value="@string/app_name" /> + android:value="TRUE" /> \ No newline at end of file diff --git a/android/app/src/main/java/com/hapirobo/connect/MainActivity.java b/android/app/src/main/java/com/hapirobo/connect/MainActivity.java index 70bc216..09ce687 100644 --- a/android/app/src/main/java/com/hapirobo/connect/MainActivity.java +++ b/android/app/src/main/java/com/hapirobo/connect/MainActivity.java @@ -2,10 +2,12 @@ import androidx.appcompat.app.AppCompatActivity; +import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; -import android.media.MediaPlayer; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -14,7 +16,6 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.Toast; -import android.widget.VideoView; import com.facebook.react.modules.core.PermissionListener; import com.robotemi.sdk.BatteryData; @@ -62,18 +63,21 @@ public class MainActivity extends AppCompatActivity implements OnGoToLocationStatusChangedListener, OnDetectionStateChangedListener, OnUserInteractionChangedListener { - private static final String TAG = "HRST"; + private static final String TAG = "MAIN"; + public static final String VIDEO_URL = "com.hapirobo.test_media.VIDEO_URL"; + public static final String WEBVIEW_URL = "com.hapirobo.test_media.WEBVIEW_URL"; private static Handler sHandler = new Handler(); private static JitsiMeetView sView; private static Robot sRobot; private static String sSerialNumber; + private MqttAndroidClient mMqttClient; private Runnable periodicTask = new Runnable() { // periodically publishes robot status to the MQTT broker. @Override public void run() { - Log.v(TAG, "Publish status"); + Log.i(TAG, "Publish status"); sHandler.postDelayed(this, 3000); try { @@ -84,10 +88,9 @@ public void run() { } }; - /** - * Initializes robot instance and default Jitsi-meet conference options. - * @param savedInstanceState - */ + //---------------------------------------------------------------------------------------------- + // ACTIVITY LIFE CYCLE METHODS + //---------------------------------------------------------------------------------------------- @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -96,28 +99,28 @@ protected void onCreate(Bundle savedInstanceState) { // initialize robot sRobot = Robot.getInstance(); - // initialize default options for Jitsi Meet conferences. - URL serverUrl; - try { - serverUrl = new URL("https://meet.jit.si"); - } catch (MalformedURLException e) { - e.printStackTrace(); - throw new RuntimeException("Invalid server URL!"); - } - JitsiMeetConferenceOptions defaultOptions - = new JitsiMeetConferenceOptions.Builder() - .setServerURL(serverUrl) - .setWelcomePageEnabled(false) - .build(); - JitsiMeet.setDefaultConferenceOptions(defaultOptions); +// // initialize default options for Jitsi activity +// URL serverUrl; +// try { +// serverUrl = new URL("https://meet.jit.si"); +// } catch (MalformedURLException e) { +// e.printStackTrace(); +// throw new RuntimeException("Invalid server URL!"); +// } +// JitsiMeetConferenceOptions defaultOptions +// = new JitsiMeetConferenceOptions.Builder() +// .setServerURL(serverUrl) +// .setWelcomePageEnabled(false) +// .build(); +// JitsiMeet.setDefaultConferenceOptions(defaultOptions); + } - /** - * Adds robot event listeners. - */ @Override protected void onStart() { super.onStart(); + + // add robot event listeners sRobot.addOnRobotReadyListener(this); sRobot.addOnBatteryStatusChangedListener(this); sRobot.addOnGoToLocationStatusChangedListener(this); @@ -125,24 +128,34 @@ protected void onStart() { sRobot.addOnUserInteractionChangedListener(this); } - /** - * Removes robot event listeners. - */ + @Override + protected void onResume() { + super.onResume(); + +// // resume Jitsi activity +// JitsiMeetActivityDelegate.onHostResume(this); + } + + @Override + protected void onPause() { + super.onPause(); + +// // pause Jitsi activity +// JitsiMeetActivityDelegate.onHostPause(this); + } + @Override protected void onStop() { super.onStop(); + + // remove robot event listeners sRobot.removeOnRobotReadyListener(this); sRobot.removeOnBatteryStatusChangedListener(this); sRobot.removeOnGoToLocationStatusChangedListener(this); sRobot.removeDetectionStateChangedListener(this); sRobot.removeOnUserInteractionChangedListener(this); - - JitsiMeetActivityDelegate.onHostPause(this); } - /** - * Disconnects MQTT client from broker. - */ @Override protected void onDestroy() { super.onDestroy(); @@ -159,61 +172,17 @@ protected void onDestroy() { } } - // remote Jitsi view +// // remove Jitsi activity // sView.dispose(); // sView = null; // JitsiMeetActivityDelegate.onHostDestroy(this); } + //---------------------------------------------------------------------------------------------- + // ROBOT EVENT LISTENERS + //---------------------------------------------------------------------------------------------- /** - * Play Audio. - * @param url Video URL - */ - public void playAudio(String url) { - MediaPlayer mediaPlayer; - mediaPlayer = new MediaPlayer(); - - Log.i(TAG, url); - try { - mediaPlayer.setDataSource(url); - mediaPlayer.prepare(); - } catch (Exception e) { - e.printStackTrace(); - } - - mediaPlayer.start(); - } - - /** - * Play Video. - * @param url Video URL - */ - public void playVideo(String url) { - Uri uri=Uri.parse(url); - VideoView video=(VideoView)findViewById(R.id.videoView); - video.setVideoURI(uri); - video.start(); - } - - /** - * Play YouTube. - * @param context Context - * @param videoId YouTube video ID - */ - public static void playYoutube(Context context, String videoId){ - Intent appIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("vnd.youtube:" + videoId)); - appIntent.putExtra("VIDEO_ID", videoId); - appIntent.putExtra("force_fullscreen", true); - - try { - context.startActivity(appIntent); - } catch (ActivityNotFoundException e) { - Log.i(TAG, "YouTube not found"); - } - } - - /** - * Configures robot after it is ready. + * Configures robot after it is ready * @param isReady True if robot initialized correctly; False otherwise */ @Override @@ -223,11 +192,19 @@ public void onRobotReady(boolean isReady) { sRobot.hideTopBar(); // hides temi's top menu bar sRobot.toggleNavigationBillboard(true); // hides navigation billboard Log.i(TAG, "[ROBOT][READY]"); + + // place app in temi's top bar menu + try { + final ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); + sRobot.onStart(activityInfo); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } } } /** - * Handles battery update events. + * Handles battery update events * @param batteryData Object containing battery state */ @Override @@ -257,7 +234,7 @@ public void onBatteryStatusChanged(@Nullable BatteryData batteryData) { } /** - * Handles go-to event updates. + * Handles go-to event updates * @param location Go-to location name * @param status Current status * @param descriptionId Description-identifier of the event @@ -301,6 +278,10 @@ public void onGoToLocationStatusChanged(@NotNull String location, @NotNull Strin } } + /** + * On user detection events + * @param state User detection state + */ @Override public void onDetectionStateChanged(int state) { JSONObject payload = new JSONObject(); @@ -321,6 +302,10 @@ public void onDetectionStateChanged(int state) { } } + /** + * On user interaction + * @param isInteracting + */ @Override public void onUserInteraction(boolean isInteracting) { JSONObject payload = new JSONObject(); @@ -341,11 +326,14 @@ public void onUserInteraction(boolean isInteracting) { } } + //---------------------------------------------------------------------------------------------- + // UI HANDLER + //---------------------------------------------------------------------------------------------- /** - * Connects to MQTT broker. + * Connects to MQTT broker * @param v View context */ - public void onButtonConnectClick(View v) { + public void onConnect(View v) { EditText hostNameView = findViewById(R.id.edit_text_host_name); String hostUri; @@ -371,34 +359,7 @@ public void onButtonConnectClick(View v) { } /** - * Publish robot status information. - * @throws JSONException - */ - public void robotPublishStatus() throws JSONException { - JSONObject payload = new JSONObject(); - JSONArray waypointArray = new JSONArray(); - - List waypointList = sRobot.getLocations(); - - // collect all waypoints - for (String waypoint : waypointList) { - waypointArray.put(waypoint); - } - - // generate payload - payload.put("waypoint_list", waypointArray); - payload.put("battery_percentage", Objects.requireNonNull(sRobot.getBatteryData()).getBatteryPercentage()); - - try { - MqttMessage message = new MqttMessage(payload.toString().getBytes(StandardCharsets.UTF_8)); - mMqttClient.publish("temi/" + sSerialNumber + "/status/info", message); - } catch (MqttException e) { - e.printStackTrace(); - } - } - - /** - * Initializes MQTT client. + * Initializes MQTT client * @param hostUri Host name / URI * @param clientId Identifier used to uniquely identify this client */ @@ -480,7 +441,37 @@ public void onFailure(IMqttToken asyncActionToken, Throwable exception) { } /** - * Parses MQTT messages received by this client. + * Publish robot status information + * @throws JSONException + */ + public void robotPublishStatus() throws JSONException { + JSONObject payload = new JSONObject(); + JSONArray waypointArray = new JSONArray(); + + List waypointList = sRobot.getLocations(); + + // collect all waypoints + for (String waypoint : waypointList) { + waypointArray.put(waypoint); + } + + // generate payload + payload.put("waypoint_list", waypointArray); + payload.put("battery_percentage", Objects.requireNonNull(sRobot.getBatteryData()).getBatteryPercentage()); + + try { + MqttMessage message = new MqttMessage(payload.toString().getBytes(StandardCharsets.UTF_8)); + mMqttClient.publish("temi/" + sSerialNumber + "/status/info", message); + } catch (MqttException e) { + e.printStackTrace(); + } + } + + //---------------------------------------------------------------------------------------------- + // MQTT MESSAGE PARSER + //---------------------------------------------------------------------------------------------- + /** + * Parses MQTT messages * @param topic Message topic * @param payload Message payload * @throws JSONException @@ -507,11 +498,7 @@ private void parseMessage(String topic, JSONObject payload) throws JSONException break; case "call": - startCall(); - break; - - case "hangup": - hangupCall(); + parseCall(topicTree[4], payload); break; case "tts": @@ -530,7 +517,7 @@ private void parseMessage(String topic, JSONObject payload) throws JSONException } /** - * Parses Waypoint messages. + * Parses waypoint messages * @param command Command type * @param payload Message Payload * @throws JSONException @@ -563,7 +550,7 @@ private void parseWaypoint(String command, JSONObject payload) throws JSONExcept } /** - * Parses Move messages. + * Parses Move messages * @param command Command type * @param payload Message Payload * @throws JSONException @@ -607,70 +594,88 @@ private void parseMove(String command, JSONObject payload) throws JSONException } /** - * Parses Move messages. - * @param media Media type + * Parses Call messages + * @param command Command type * @param payload Message Payload * @throws JSONException */ - private void parseMedia(String media, JSONObject payload) throws JSONException { + private void parseCall(String command, JSONObject payload) throws JSONException { + switch (command) { + case "start": +// startCall(this, payload.getString("room_name")); + break; - switch (media) { - case "audio": - playAudio(payload.getString("url")); + case "end": +// endCall(this); break; - case "image": + default: + Log.i(TAG, "[MOVE] Unknown Movement Command"); break; + } + } + /** + * Parses Media messages + * @param media Media type + * @param payload Message Payload + * @throws JSONException + */ + private void parseMedia(String media, JSONObject payload) throws JSONException { + switch (media) { case "video": - playVideo(payload.getString("url")); + playVideo(this, payload.getString("url")); break; case"youtube": playYoutube(this, payload.getString("video_id")); break; + case "webview": + showWebview(this, payload.getString("url")); + break; + default: Log.i(TAG, "[MOVE] Unknown Media"); break; } } - /** - * Start video-call - */ - private void startCall() { - Log.v(TAG, "[CMD][CALL]"); - - // build options object for joining the conference - // the SDK will merge the default one we set earlier and this one when joining - JitsiMeetConferenceOptions options - = new JitsiMeetConferenceOptions.Builder() - .setRoom("temi-" + sSerialNumber) - .build(); - - // Launch the new view with the given options -// sView = new JitsiMeetView(this); + //---------------------------------------------------------------------------------------------- + // JitsiMeetView Methods + // https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-android-sdk + //---------------------------------------------------------------------------------------------- +// /** +// * Start video-call +// */ +// private void startCall(Context context, String roomName) { +// // build options object for joining the conference +// // the SDK will merge the default one we set earlier and this one when joining +// JitsiMeetConferenceOptions options +// = new JitsiMeetConferenceOptions.Builder() +// .setRoom(roomName) +// .build(); +// +// // Launch the new view with the given options +// sView = new JitsiMeetView(context); // sView.join(options); // setContentView(sView); - JitsiMeetActivity.launch(this, options); - } - - /** - * Hangup video call - */ - private void hangupCall() { - Log.v(TAG, "[CMD][HANGUP]"); - - sView.leave(); - sView.dispose(); - sView = null; - JitsiMeetActivityDelegate.onHostDestroy(this); - - // set main menu - setContentView(R.layout.activity_main); - } - +// JitsiMeetActivity.launch(context, options); +// } +// +// /** +// * End video call +// */ +// private void endCall(Activity activity) { +// sView.leave(); +// sView.dispose(); +// sView = null; +// JitsiMeetActivityDelegate.onHostDestroy(activity); +// +// // set main menu +// setContentView(R.layout.activity_main); +// } +// // @Override // protected void onActivityResult( // int requestCode, @@ -680,18 +685,18 @@ private void hangupCall() { // JitsiMeetActivityDelegate.onActivityResult( // this, requestCode, resultCode, data); // } - +// // @Override // public void onBackPressed() { // JitsiMeetActivityDelegate.onBackPressed(); // } - +// // @Override // public void onNewIntent(Intent intent) { // super.onNewIntent(intent); // JitsiMeetActivityDelegate.onNewIntent(intent); // } - +// // @Override // public void onRequestPermissionsResult( // final int requestCode, @@ -699,16 +704,59 @@ private void hangupCall() { // final int[] grantResults) { // JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); // } - -// @Override -// protected void onResume() { -// super.onResume(); // -// JitsiMeetActivityDelegate.onHostResume(this); -// } - // @Override // public void requestPermissions(String[] strings, int i, PermissionListener permissionListener) { // // } + + //---------------------------------------------------------------------------------------------- + // MEDIA SERVICES + //---------------------------------------------------------------------------------------------- + /** + * Play YouTube from URL + * @param context Context + * @param videoId YouTube video ID + */ + public static void playYoutube(Context context, String videoId){ + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("vnd.youtube:" + videoId)); + intent.putExtra("VIDEO_ID", videoId); + intent.putExtra("force_fullscreen", true); + + try { + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.i(TAG, "YouTube not found"); + } + } + + /** + * Play video from URL + * @param url URL to compatible video (https://developer.android.com/guide/topics/media/media-formats) + */ + public void playVideo(Context context, String url) { + Intent intent = new Intent(this, VideoActivity.class); + intent.putExtra(VIDEO_URL, url); + + try { + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.i(TAG, "Video not found"); + } + } + + /** + * Show web-view from URL + * @param url URL to display + */ + public void showWebview(Context context, String url) { + Intent intent = new Intent(this, WebviewActivity.class); + intent.putExtra(WEBVIEW_URL, url); + + try { + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.i(TAG, "URL not found"); + } + } } diff --git a/android/app/src/main/java/com/hapirobo/connect/VideoActivity.java b/android/app/src/main/java/com/hapirobo/connect/VideoActivity.java new file mode 100644 index 0000000..a23c7be --- /dev/null +++ b/android/app/src/main/java/com/hapirobo/connect/VideoActivity.java @@ -0,0 +1,46 @@ +package com.hapirobo.connect; + +import androidx.appcompat.app.AppCompatActivity; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.media.MediaPlayer; +import android.os.Bundle; +import android.widget.VideoView; + +public class VideoActivity extends AppCompatActivity { + private static final String TAG = "VIDEO"; + + public static ProgressDialog pd; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_video); + + VideoView videoView = (VideoView)findViewById(R.id.videoView); + + // display video buffering message + // https://developer.android.com/reference/android/app/ProgressDialog + pd = new ProgressDialog(VideoActivity.this); + pd.setMessage("Buffering video please wait..."); + pd.show(); + + // get the intent that started this activity and extract the video URL + Intent intent = getIntent(); + String url = intent.getStringExtra(MainActivity.VIDEO_URL); + + // videoView settings + videoView.setVideoPath(url); + videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + // close the progress dialog when buffering is done + pd.dismiss(); + } + }); + + // play the video + videoView.start(); + } +} diff --git a/android/app/src/main/java/com/hapirobo/connect/WebviewActivity.java b/android/app/src/main/java/com/hapirobo/connect/WebviewActivity.java new file mode 100644 index 0000000..71ba2c6 --- /dev/null +++ b/android/app/src/main/java/com/hapirobo/connect/WebviewActivity.java @@ -0,0 +1,28 @@ +package com.hapirobo.connect; + +import androidx.appcompat.app.AppCompatActivity; + +import android.content.Intent; +import android.os.Bundle; +import android.webkit.WebSettings; +import android.webkit.WebView; + +public class WebviewActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_webview); + + // https://developer.android.com/guide/webapps/webview + WebView webView = (WebView)findViewById(R.id.webview); + WebSettings webSettings = webView.getSettings(); + webSettings.setJavaScriptEnabled(true); + + // get the intent that started this activity and extract the webview URL + Intent intent = getIntent(); + String url = intent.getStringExtra(MainActivity.WEBVIEW_URL); + + webView.loadUrl(url); + } +} diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index 6a218c6..159798a 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -16,7 +16,7 @@ android:layout_marginStart="160dp" android:layout_marginTop="300dp" android:layout_marginEnd="160dp" - android:onClick="onButtonConnectClick" + android:onClick="onConnect" android:text="@string/button_connect" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/android/app/src/main/res/layout/activity_video.xml b/android/app/src/main/res/layout/activity_video.xml new file mode 100644 index 0000000..e25470e --- /dev/null +++ b/android/app/src/main/res/layout/activity_video.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_webview.xml b/android/app/src/main/res/layout/activity_webview.xml new file mode 100644 index 0000000..c725d70 --- /dev/null +++ b/android/app/src/main/res/layout/activity_webview.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 131159d..841d10c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -9,7 +9,6 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:3.6.0' - // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files