diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/Exercise.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/Exercise.java index d8198b6..d0c3cb1 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/Exercise.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/Exercise.java @@ -40,4 +40,8 @@ public class Exercise { this.videoUrl = videoUrl; this.exerciseTimeInSeconds = exerciseTimeInSeconds; } + + public interface ExerciseFetchHandler { + void handle(Exercise exercise); + } } diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java index e4ad128..29fe954 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java @@ -18,23 +18,28 @@ public class ExerciseManager { // The value of these property variables must be equivalent of // the JSON data that the database sends back. // If this is not the case then the exercise retrieval will fail. - private static final String PROPERTY_MUSCLE_GROUP = "muscleGroup"; - private static final String PROPERTY_DESC = "description"; - private static final String PROPERTY_IMAGE_URL = "imageUrl"; - private static final String PROPERTY_VIDEO_URL = "videoUrl"; - private static final String PROPERTY_NAME = "name"; - private static final String PROPERTY_DATA = "data"; - private static final String PROPERTY_EXERCISE_DURATION = "exerciseDuration"; + private static final String PROPERTY_EXERCISE_DURATION = "duration"; + private static final String PROPERTY_EXERCISE_ID = "exerciseId"; + private static final String PROPERTY_MUSCLE_GROUP = "muscleGroup"; + private static final String PROPERTY_SHORT_DESC = "shortDescription"; + private static final String PROPERTY_IMAGE_URL = "imageUrl"; + private static final String PROPERTY_VIDEO_URL = "videoUrl"; + private static final String PROPERTY_DESC = "description"; + private static final String PROPERTY_PATH = "path"; + private static final String PROPERTY_NAME = "name"; public static final int SENSOR_COUNT = 2; private static final String[] REQUIRED_PROPERTIES = { PROPERTY_MUSCLE_GROUP, PROPERTY_DESC, PROPERTY_IMAGE_URL, - PROPERTY_VIDEO_URL, PROPERTY_NAME, PROPERTY_DATA, - PROPERTY_EXERCISE_DURATION + PROPERTY_VIDEO_URL, PROPERTY_NAME, PROPERTY_PATH, + PROPERTY_EXERCISE_DURATION, PROPERTY_EXERCISE_ID, + PROPERTY_SHORT_DESC }; - private static final float DEFAULT_SEGMENT_SPEED = 1.0f; + public static final int DEFAULT_EXERCISE_REPETITIONS = 10; + public static final float DEFAULT_SEGMENT_SPEED = 1.0f; + public static final float EXERCISE_ERROR_MARGIN = 1.0f; /** * Function for sending an HTTP request to the server. @@ -95,7 +100,7 @@ public class ExerciseManager { // Path data is split into two parts, due to the left and right hand. // If one wants to add support for more sensors, one will have to adjust the Exercise // class to support more paths. - String[] leftRightData = content.get(PROPERTY_DATA).getAsString().split(";"); + String[] leftRightData = content.get(PROPERTY_PATH).getAsString().split(";"); if ( leftRightData.length != SENSOR_COUNT) return null; diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/EndScreenActivity.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/EndScreenActivity.java index 703c53b..f2185de 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/EndScreenActivity.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/EndScreenActivity.java @@ -4,8 +4,7 @@ import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import com.example.fitbot.R; -import com.example.fitbot.ui.activities.FitnessActivity; -import com.example.fitbot.ui.activities.MainActivity; +import com.example.fitbot.util.NavigationManager; public class EndScreenActivity extends AppCompatActivity { @@ -14,7 +13,7 @@ public class EndScreenActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_end_screen); - com.example.fitbot.util.ButtonNavigation.setupButtonNavigation(this, R.id.homeButtonEndScreen, MainActivity.class); - com.example.fitbot.util.ButtonNavigation.setupButtonNavigation(this, R.id.startButtonEndScreen, FitnessActivity.class); + NavigationManager.setupButtonNavigation(this, R.id.homeButtonEndScreen, MainActivity.class); + NavigationManager.setupButtonNavigation(this, R.id.startButtonEndScreen, FitnessActivity.class); } } \ No newline at end of file diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/FitnessActivity.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/FitnessActivity.java index 5d2d6dd..d93e8a6 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/FitnessActivity.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/FitnessActivity.java @@ -1,8 +1,6 @@ package com.example.fitbot.ui.activities; import android.os.Bundle; -import android.util.Log; -import android.view.View; import android.widget.VideoView; import com.aldebaran.qi.sdk.QiContext; @@ -14,7 +12,7 @@ import com.example.fitbot.R; import com.example.fitbot.exercise.Exercise; import com.example.fitbot.exercise.ExerciseManager; import com.example.fitbot.ui.components.PersonalMotionPreviewElement; -import com.example.fitbot.util.ButtonNavigation; +import com.example.fitbot.util.NavigationManager; import com.example.fitbot.util.FitnessCycle; import com.example.fitbot.util.processing.InputProcessor; @@ -56,7 +54,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall // Find the VideoView by its ID VideoView videoView = findViewById(R.id.videoView); FitnessCycle.playVideo(videoView, this); - ButtonNavigation.setupButtonNavigation(this, R.id.homeButtonFitness, MainActivity.class); + NavigationManager.setupButtonNavigation(this, R.id.homeButtonFitness, MainActivity.class); // Implement your logic when the robot focus is gained } @@ -73,22 +71,24 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall // This will provide the element with the appropriate dimensions for drawing // the canvas properly. personalMotionPreviewElement.post(() -> { - Exercise exercise = this.acquireExercise(); - if ( exercise == null) { - return; - } - // Acquire paths from the exercise and provide them to the motion processor - Vector3f[][] vectors = new Vector3f[][] {exercise.leftPath.getVectors(), exercise.rightPath.getVectors()}; + this.acquireExercise((exercise) -> { + // Acquire paths from the exercise and provide them to the motion processor + Vector3f[][] vectors = new Vector3f[][] {exercise.leftPath.getVectors(), exercise.rightPath.getVectors()}; - motionProcessor = new InputProcessor(vectors, exercise.exerciseTimeInSeconds, SENSOR_SAMPLE_RATE); + motionProcessor = new InputProcessor(vectors, exercise.exerciseTimeInSeconds, SENSOR_SAMPLE_RATE); - personalMotionPreviewElement.provideQiContext(qiContext); - personalMotionPreviewElement.initialize(exercise, motionProcessor, EXERCISE_COUNT); + personalMotionPreviewElement.provideQiContext(qiContext); + personalMotionPreviewElement.initialize(exercise, motionProcessor, EXERCISE_COUNT); - motionProcessor.startListening(); - motionProcessor.setInputHandler(personalMotionPreviewElement); + motionProcessor.startListening(); + motionProcessor.setInputHandler(personalMotionPreviewElement); + }, (n) -> { + int randomMessageIndex = (int)Math.floor(Math.random() * EXERCISE_NOT_FOUND_MESSAGES.length); + FitnessCycle.say(EXERCISE_NOT_FOUND_MESSAGES[randomMessageIndex], qiContext); + FitnessCycle.say(EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE, qiContext); + + }); }); - personalMotionPreviewElement.provideQiContext(qiContext); // FitnessCycle.playVideo(qiContext, videoView, this); } @@ -97,28 +97,32 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall * Acquire an exercise from the ExerciseManager. * Whenever the retrieval failed, it will have the robot say something to the user * to inform them about the issue. - * - * @return The acquired exercise, or null if the exercise could not be retrieved. */ - public Exercise acquireExercise() { - Exercise exercise = ExerciseManager.retrieveExercise(); - if ( exercise == null && this.qiContext != null) - { - int randomMessageIndex = (int)Math.floor(Math.random() * EXERCISE_NOT_FOUND_MESSAGES.length); - FitnessCycle.say(EXERCISE_NOT_FOUND_MESSAGES[randomMessageIndex], qiContext); - FitnessCycle.say(EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE, qiContext); - } - return exercise; + public void acquireExercise(Exercise.ExerciseFetchHandler onSuccessfulFetch, Exercise.ExerciseFetchHandler onFailedFetch) { + // For some stupid reason we cannot perform network operations on the main thread. + // therefore we'll have to do it like this... + (new Thread(() -> { + Exercise exercise = ExerciseManager.retrieveExercise(); + if ( exercise == null) + { + onFailedFetch.handle(null); + } + else { + onSuccessfulFetch.handle(exercise); + } + })).start(); } @Override public void onRobotFocusLost() { - // Implement your logic when the robot focus is lost + QiSDK.unregister(this, this); + // Go to the main screen + NavigationManager.navigateToActivity(this, MainActivity.class); } @Override public void onRobotFocusRefused(String reason) { - // Implement your logic when the robot focus is refused + QiSDK.unregister(this, this); } @Override @@ -127,6 +131,5 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall QiSDK.unregister(this, this); this.motionProcessor.stopListening(); this.motionProcessor = null; - this.personalMotionPreviewElement.destroy(); } } \ No newline at end of file diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/HelpActivity.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/HelpActivity.java index cbc714c..77705cb 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/HelpActivity.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/HelpActivity.java @@ -5,7 +5,7 @@ import android.os.Bundle; import android.view.View; import com.example.fitbot.R; -import com.example.fitbot.util.ButtonNavigation; +import com.example.fitbot.util.NavigationManager; public class HelpActivity extends AppCompatActivity { @@ -14,7 +14,7 @@ public class HelpActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_help); - ButtonNavigation.setupButtonNavigation(this, R.id.homeButtonHelp, MainActivity.class); + NavigationManager.setupButtonNavigation(this, R.id.homeButtonHelp, MainActivity.class); // Hide system UI hideSystemUI(); diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java index 4edfa2e..61c07c1 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java @@ -10,13 +10,12 @@ import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; -import android.util.Log; import android.view.View; import android.view.WindowManager; import android.widget.Button; import com.example.fitbot.R; -import com.example.fitbot.util.ButtonNavigation; +import com.example.fitbot.util.NavigationManager; public class MainActivity extends AppCompatActivity { @@ -63,8 +62,8 @@ public class MainActivity extends AppCompatActivity { if (getSupportActionBar() != null) { getSupportActionBar().hide(); } - ButtonNavigation.setupButtonNavigation(this, R.id.startButtonMain, FitnessActivity.class); - ButtonNavigation.setupButtonNavigation(this, R.id.helpButtonMain, HelpActivity.class); + NavigationManager.setupButtonNavigation(this, R.id.startButtonMain, FitnessActivity.class); + NavigationManager.setupButtonNavigation(this, R.id.helpButtonMain, HelpActivity.class); /*---Tool Bar---*/ setSupportActionBar(toolbar); // Make the toolbar act as the action bar diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java index 92f4831..d8ac0ec 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java @@ -2,7 +2,6 @@ package com.example.fitbot.ui.components; import android.app.Activity; import android.content.Context; -import android.content.Intent; import android.graphics.Canvas; import android.graphics.Paint; import android.support.annotation.Nullable; @@ -15,6 +14,7 @@ import com.example.fitbot.exercise.Exercise; import com.example.fitbot.ui.activities.EndScreenActivity; import com.example.fitbot.ui.activities.FitnessActivity; import com.example.fitbot.ui.activities.MainActivity; +import com.example.fitbot.util.NavigationManager; import com.example.fitbot.util.FitnessCycle; import com.example.fitbot.util.processing.IInputHandler; import com.example.fitbot.util.processing.InputProcessor; @@ -105,25 +105,6 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler projectVertex(new Vector3f(0, 0, -5.0f), getWidth(), getHeight()), projectVertex(new Vector3f(0, 0, 5.0f), getWidth(), getHeight()) }; - } - - public void destroy() - { - if ( this.motionProcessor != null ) - this.motionProcessor.stopListening(); - - this.motionProcessor = null; - } - - /** - * Function for providing a QiContext to the PersonalMotionPreviewElement. - * This function will be called by the parent activity when the QiContext is available. - * Also say something nice to the user :) - * - * @param context The QiContext to provide. - */ - public void provideQiContext(QiContext context) { - this.qiContext = context; // Handler that is called every time the motion processor receives new data. this.motionProcessor.setInputHandler((rotationVector, deviceId) -> { @@ -133,27 +114,29 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler if ( this.motionProcessor.hasFinished()) { + // If for some reason the parent activity is not defined, + // move back to the main screen. if ( this.parentActivity == null) { // Move to main screen - this.destroy(); - Intent intent = new Intent(getContext(), MainActivity.class); - getContext().startActivity(intent); + NavigationManager.navigateToActivity(getContext(), MainActivity.class); return; } // Move on to the next exercise, or finish. if ( this.exerciseCount > 0 ) { this.exerciseCount--; - this.exercise = this.parentActivity.acquireExercise(); - this.motionProcessor.useExercise(this.exercise); + this.parentActivity.acquireExercise((newExercise) -> { + this.motionProcessor.useExercise(newExercise); + }, (failed) -> { + // Move to main screen + NavigationManager.navigateToActivity(getContext(), MainActivity.class); + }); } else { // Finish the exercise. - this.destroy(); - Intent intent = new Intent(getContext(), EndScreenActivity.class); - getContext().startActivity(intent); + NavigationManager.navigateToActivity(getContext(), EndScreenActivity.class); return; } } @@ -168,6 +151,17 @@ public class PersonalMotionPreviewElement extends View implements IInputHandler if (this.vectors.size() > 100) this.vectors.poll(); }); + } + + /** + * Function for providing a QiContext to the PersonalMotionPreviewElement. + * This function will be called by the parent activity when the QiContext is available. + * Also say something nice to the user :) + * + * @param context The QiContext to provide. + */ + public void provideQiContext(QiContext context) { + this.qiContext = context; if (this.qiContext == null) return; diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/FitnessCycle.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/FitnessCycle.java index a55b445..afc3988 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/FitnessCycle.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/FitnessCycle.java @@ -7,6 +7,8 @@ import android.util.Log; import android.widget.VideoView; import com.aldebaran.qi.sdk.builder.SayBuilder; +import com.aldebaran.qi.sdk.object.conversation.Phrase; +import com.aldebaran.qi.sdk.object.conversation.Say; import com.aldebaran.qi.sdk.object.locale.Language; import com.aldebaran.qi.sdk.object.locale.Locale; import com.aldebaran.qi.sdk.object.locale.Region; @@ -17,19 +19,25 @@ import com.aldebaran.qi.sdk.builder.AnimationBuilder; import com.aldebaran.qi.sdk.object.actuation.Animate; import com.aldebaran.qi.sdk.object.actuation.Animation; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; public class FitnessCycle extends AppCompatActivity { private static final Locale DUTCH_LOCALE = new Locale(Language.DUTCH, Region.NETHERLANDS); - + private static final BlockingQueue MESSAGE_QUEUE = new LinkedBlockingQueue<>(); + private static boolean SPEECH_ACTIVE = false; /** * Function for executing a movement animation a certain number of times * on the robot * - * @param Exercise The name of the exercise to perform - * @param Reps The number of repetitions to perform + * @param Exercise The name of the exercise to perform + * @param Reps The number of repetitions to perform * @param qiContext The QiContext to use */ public static void executeMovement(String Exercise, int Reps, QiContext qiContext) { @@ -59,35 +67,46 @@ public class FitnessCycle extends AppCompatActivity { /** * Function for making the robot say something with DUTCH_LOCALE as locale + * * @param phrase The phrase to make the robot say - * @param ctx The QiContext to use + * @param ctx The QiContext to use */ - public static void say(String phrase, QiContext ctx) - { + public static void say(String phrase, QiContext ctx) { say(phrase, ctx, DUTCH_LOCALE); } /** * Function for making the robot say something with a specific locale + * * @param phrase The phrase to make the robot say - * @param ctx The QiContext to use + * @param ctx The QiContext to use * @param locale The locale to use */ - public static void say(String phrase, QiContext ctx, Locale locale) - { - SayBuilder - .with(ctx) - .withLocale(locale) - .withText(phrase) - .build() - .run(); + public static void say(String phrase, QiContext ctx, Locale locale) { + // If the robot is already speaking, add the phrase to the queue + MESSAGE_QUEUE.add(new PepperSpeechEntry(phrase, ctx, locale)); + processMessageQueue(); + } + + private static synchronized void processMessageQueue() { + if (SPEECH_ACTIVE) + return; + PepperSpeechEntry nextPhrase = MESSAGE_QUEUE.poll(); + if (nextPhrase != null) { + SPEECH_ACTIVE = true; + Say say = nextPhrase.getSay(); + say.async().run().andThenConsume(future -> { + SPEECH_ACTIVE = false; + processMessageQueue(); // Process next item in the queue + }); + } } /** * Function for playing a video in a VideoView * * @param videoView The VideoView to play the video in - * @param context The context to use + * @param context The context to use */ public static void playVideo(VideoView videoView, Context context) { // Set up the video player @@ -105,4 +124,24 @@ public class FitnessCycle extends AppCompatActivity { Log.e("FitnessActivity", "VideoView is null. Check your layout XML."); } } + + private static class PepperSpeechEntry { + public String phrase; + public QiContext context; + public Locale locale; + + public PepperSpeechEntry(String phrase, QiContext context, Locale locale) { + this.phrase = phrase; + this.context = context; + this.locale = locale; + } + + public Say getSay() { + return SayBuilder + .with(context) + .withLocale(locale) + .withText(phrase) + .build(); + } + } } \ No newline at end of file diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/ButtonNavigation.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/NavigationManager.java similarity index 61% rename from code/src/Fitbot/app/src/main/java/com/example/fitbot/util/ButtonNavigation.java rename to code/src/Fitbot/app/src/main/java/com/example/fitbot/util/NavigationManager.java index b239b35..0b1ec75 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/ButtonNavigation.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/NavigationManager.java @@ -6,7 +6,10 @@ import android.content.Intent; import android.view.View; import android.widget.Button; -public class ButtonNavigation { +import com.example.fitbot.ui.activities.FitnessActivity; +import com.example.fitbot.ui.activities.MainActivity; + +public class NavigationManager { /** * Sets up a button to navigate to a different activity when clicked. @@ -23,4 +26,15 @@ public class ButtonNavigation { currentActivity.finish(); }); } + + /** + * Navigates to the target activity. + * + * @param currentContext The current context + * @param targetActivity The target activity + */ + public static void navigateToActivity(Context currentContext, Class targetActivity) { + Intent intent = new Intent(currentContext, targetActivity); + currentContext.startActivity(intent); + } } \ No newline at end of file