From e94fc042070b0f5c87c4788f486e34ec934295ac Mon Sep 17 00:00:00 2001 From: Kapil Verma Date: Fri, 14 May 2021 23:58:07 +0530 Subject: [PATCH 1/2] Added Config and Logging Modules Integrated app pinning --- app/build.gradle | 14 +- app/src/main/AndroidManifest.xml | 49 +- .../cmu/xprize/rthomescreen/HomeActivity.java | 167 ++- .../rthomescreen/PermissionsActivity.java | 119 ++ .../startup/CMasterContainer.java | 2 +- .../rthomescreen/startup/CStartView.java | 2 +- .../main/res/layout/activity_permissions.xml | 9 + build.gradle | 10 +- comp_configuration/.gitignore | 1 + comp_configuration/build.gradle | 94 ++ comp_configuration/consumer-rules.pro | 0 comp_configuration/proguard-rules.pro | 21 + .../ExampleInstrumentedTest.java | 25 + .../src/main/AndroidManifest.xml | 5 + .../comp_configuration/Configuration.java | 131 ++ .../ConfigurationClassMap.java | 20 + .../ConfigurationItems.java | 130 ++ .../ConfigurationQuickOptions.java | 52 + .../comp_configuration/ExampleUnitTest.java | 17 + comp_logging/.gitignore | 1 + comp_logging/build.gradle | 89 ++ comp_logging/proguard-rules.pro | 25 + .../comp_logging/ExampleInstrumentedTest.java | 26 + comp_logging/src/main/AndroidManifest.xml | 10 + .../xprize/comp_logging}/CErrorManager.java | 9 +- .../comp_logging/CInterventionLogManager.java | 26 + .../cmu/xprize/comp_logging/CLogManager.java | 40 + .../xprize/comp_logging/CLogManagerBase.java | 654 ++++++++++ .../xprize/comp_logging/CPerfLogManager.java | 93 ++ .../comp_logging}/CPreferenceCache.java | 10 +- .../cmu/xprize/comp_logging/ILogManager.java | 41 + .../xprize/comp_logging/IPerfLogManager.java | 8 + .../cmu/xprize/comp_logging/ITutorLogger.java | 7 + .../comp_logging/InterventionLogItem.java | 41 + .../comp_logging/PerformanceLogItem.java | 489 ++++++++ .../cmu/xprize/comp_logging/TLOG_CONST.java | 21 + comp_logging/src/main/res/values/strings.xml | 3 + .../xprize/comp_logging/ExampleUnitTest.java | 17 + comp_pointtap/build.gradle | 48 + .../xprize/comp_pointtap/CHandAnimation.java | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- settings.gradle | 2 +- util/build.gradle | 71 +- .../java/cmu/xprize/util/CAnimatorUtil.java | 9 +- .../main/java/cmu/xprize/util/CAt_Data.java | 59 + .../java/cmu/xprize/util/CDisplayMetrics.java | 50 + .../java/cmu/xprize/util/CErrorDialog.java | 8 +- .../src/main/java/cmu/xprize/util/CEvent.java | 3 +- .../main/java/cmu/xprize/util/CEventMap.java | 26 - .../java/cmu/xprize/util/CFileNameHasher.java | 15 +- .../java/cmu/xprize/util/CLoaderView.java | 121 +- .../java/cmu/xprize/util/CLogManager.java | 462 ------- .../cmu/xprize/util/CMessageQueueFactory.java | 237 ++++ .../cmu/xprize/util/CPlacementTest_Tutor.java | 35 + .../cmu/xprize/util/CTutorData_Metadata.java | 1111 +++++++++++++++++ .../util/FailureInterventionHelper.java | 77 ++ .../cmu/xprize/util/GlobalStaticsEngine.java | 51 + .../cmu/xprize/util/IButtonController.java | 12 + .../src/main/java/cmu/xprize/util/IEvent.java | 4 +- .../cmu/xprize/util/IEventDispatcher.java | 9 +- .../java/cmu/xprize/util/IEventListener.java | 3 +- .../cmu/xprize/util/IInterventionSource.java | 12 + .../java/cmu/xprize/util/ILogManager.java | 20 - .../cmu/xprize/util/IMessageQueueRunner.java | 16 + .../cmu/xprize/util/IPerformanceTracker.java | 14 + .../main/java/cmu/xprize/util/IPublisher.java | 10 + .../java/cmu/xprize/util/IScriptable.java | 4 +- .../java/cmu/xprize/util/ImageLoader.java | 81 ++ .../java/cmu/xprize/util/JSON_Helper.java | 116 +- .../main/java/cmu/xprize/util/MathUtil.java | 42 + .../cmu/xprize/util/QueueConstructorVars.txt | 21 + .../src/main/java/cmu/xprize/util/TCJSGF.java | 3 +- .../src/main/java/cmu/xprize/util/TCONST.java | 410 +++++- .../java/cmu/xprize/util/TimerMaster.java | 155 +++ .../java/cmu/xprize/util/View_Helper.java | 2 +- .../java/cmu/xprize/util/Word2NumFSM.java | 3 +- .../util/consts/INTERVENTION_CONST.java | 22 + .../gesture/ExpectTapGestureListener.java | 82 ++ .../gesture/ExpectWriteGestureListener.java | 80 ++ .../main/res/drawable/robotutor_normal.xml | 1 - util/src/main/res/layout/progress_layout.xml | 46 +- .../common/CTutorData_Metadata_Test.java | 215 ++++ .../cmu/xprize/common/ExampleUnitTest.java | 22 +- 83 files changed, 5585 insertions(+), 689 deletions(-) create mode 100644 app/src/main/java/cmu/xprize/rthomescreen/PermissionsActivity.java create mode 100644 app/src/main/res/layout/activity_permissions.xml create mode 100644 comp_configuration/.gitignore create mode 100644 comp_configuration/build.gradle create mode 100644 comp_configuration/consumer-rules.pro create mode 100644 comp_configuration/proguard-rules.pro create mode 100644 comp_configuration/src/androidTest/java/com/xprize/comp_configuration/ExampleInstrumentedTest.java create mode 100644 comp_configuration/src/main/AndroidManifest.xml create mode 100644 comp_configuration/src/main/java/com/xprize/comp_configuration/Configuration.java create mode 100644 comp_configuration/src/main/java/com/xprize/comp_configuration/ConfigurationClassMap.java create mode 100644 comp_configuration/src/main/java/com/xprize/comp_configuration/ConfigurationItems.java create mode 100644 comp_configuration/src/main/java/com/xprize/comp_configuration/ConfigurationQuickOptions.java create mode 100644 comp_configuration/src/test/java/com/xprize/comp_configuration/ExampleUnitTest.java create mode 100644 comp_logging/.gitignore create mode 100644 comp_logging/build.gradle create mode 100644 comp_logging/proguard-rules.pro create mode 100644 comp_logging/src/androidTest/java/cmu/xprize/comp_logging/ExampleInstrumentedTest.java create mode 100644 comp_logging/src/main/AndroidManifest.xml rename {util/src/main/java/cmu/xprize/util => comp_logging/src/main/java/cmu/xprize/comp_logging}/CErrorManager.java (91%) create mode 100644 comp_logging/src/main/java/cmu/xprize/comp_logging/CInterventionLogManager.java create mode 100644 comp_logging/src/main/java/cmu/xprize/comp_logging/CLogManager.java create mode 100644 comp_logging/src/main/java/cmu/xprize/comp_logging/CLogManagerBase.java create mode 100644 comp_logging/src/main/java/cmu/xprize/comp_logging/CPerfLogManager.java rename {util/src/main/java/cmu/xprize/util => comp_logging/src/main/java/cmu/xprize/comp_logging}/CPreferenceCache.java (92%) create mode 100644 comp_logging/src/main/java/cmu/xprize/comp_logging/ILogManager.java create mode 100644 comp_logging/src/main/java/cmu/xprize/comp_logging/IPerfLogManager.java create mode 100644 comp_logging/src/main/java/cmu/xprize/comp_logging/ITutorLogger.java create mode 100644 comp_logging/src/main/java/cmu/xprize/comp_logging/InterventionLogItem.java create mode 100644 comp_logging/src/main/java/cmu/xprize/comp_logging/PerformanceLogItem.java create mode 100644 comp_logging/src/main/java/cmu/xprize/comp_logging/TLOG_CONST.java create mode 100644 comp_logging/src/main/res/values/strings.xml create mode 100644 comp_logging/src/test/java/cmu/xprize/comp_logging/ExampleUnitTest.java create mode 100644 util/src/main/java/cmu/xprize/util/CAt_Data.java create mode 100644 util/src/main/java/cmu/xprize/util/CDisplayMetrics.java delete mode 100644 util/src/main/java/cmu/xprize/util/CEventMap.java delete mode 100644 util/src/main/java/cmu/xprize/util/CLogManager.java create mode 100644 util/src/main/java/cmu/xprize/util/CMessageQueueFactory.java create mode 100644 util/src/main/java/cmu/xprize/util/CPlacementTest_Tutor.java create mode 100644 util/src/main/java/cmu/xprize/util/CTutorData_Metadata.java create mode 100644 util/src/main/java/cmu/xprize/util/FailureInterventionHelper.java create mode 100644 util/src/main/java/cmu/xprize/util/GlobalStaticsEngine.java create mode 100644 util/src/main/java/cmu/xprize/util/IButtonController.java create mode 100644 util/src/main/java/cmu/xprize/util/IInterventionSource.java delete mode 100644 util/src/main/java/cmu/xprize/util/ILogManager.java create mode 100644 util/src/main/java/cmu/xprize/util/IMessageQueueRunner.java create mode 100644 util/src/main/java/cmu/xprize/util/IPerformanceTracker.java create mode 100644 util/src/main/java/cmu/xprize/util/ImageLoader.java create mode 100644 util/src/main/java/cmu/xprize/util/MathUtil.java create mode 100644 util/src/main/java/cmu/xprize/util/QueueConstructorVars.txt create mode 100644 util/src/main/java/cmu/xprize/util/TimerMaster.java create mode 100644 util/src/main/java/cmu/xprize/util/consts/INTERVENTION_CONST.java create mode 100644 util/src/main/java/cmu/xprize/util/gesture/ExpectTapGestureListener.java create mode 100644 util/src/main/java/cmu/xprize/util/gesture/ExpectWriteGestureListener.java create mode 100644 util/src/test/java/cmu/xprize/common/CTutorData_Metadata_Test.java diff --git a/app/build.gradle b/app/build.gradle index de400d4..4c48101 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,8 +28,14 @@ android { buildTypes { + debug { + buildConfigField "String", "WIFI_CONFIG", "\"DEBUG\"" + debuggable true + } release { + buildConfigField "String", "WIFI_CONFIG", "\"RELEASE\"" + minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' @@ -68,11 +74,13 @@ android { variant.outputs.each { output -> def project = "RoboLaunch" def SEP = "." - def buildType = variant.variantData.variantConfiguration.buildType.name + //def buildType = variant.variantData.variantConfiguration.buildType.name + def buildType = variant.buildType.name def version = variant.versionName def apkName = project + SEP + buildType + SEP + version + ".apk" - output.outputFile = new File(output.outputFile.parent, apkName) + //output.outputFile = new File(output.outputFile.parent, apkName) + output.outputFileName = apkName } } @@ -81,6 +89,8 @@ android { } dependencies { + implementation project(path: ':comp_configuration') + implementation 'com.android.support.constraint:constraint-layout:2.0.4' compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3496ca9..c727b50 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,72 +4,71 @@ + + - - - + android:label="@string/app_name" + android:screenOrientation="sensorLandscape"> - - + + + android:theme="@android:style/Theme.NoDisplay" /> - + android:theme="@android:style/Theme.NoDisplay" /> - - + android:exported="true"/> - + android:exported="true" /> - + android:resource="@xml/admin_receiver" /> - + - \ No newline at end of file diff --git a/app/src/main/java/cmu/xprize/rthomescreen/HomeActivity.java b/app/src/main/java/cmu/xprize/rthomescreen/HomeActivity.java index 71d212b..b20b598 100644 --- a/app/src/main/java/cmu/xprize/rthomescreen/HomeActivity.java +++ b/app/src/main/java/cmu/xprize/rthomescreen/HomeActivity.java @@ -8,10 +8,12 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PermissionInfo; import android.os.Build; +import android.os.Environment; import android.provider.Settings; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; @@ -21,21 +23,37 @@ import android.view.ViewGroup; import android.widget.Toast; +import com.xprize.comp_configuration.Configuration; +import com.xprize.comp_configuration.ConfigurationItems; +import com.xprize.comp_configuration.ConfigurationQuickOptions; + import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; import java.util.Date; - +import java.util.Locale; + +import cmu.xprize.comp_logging.CErrorManager; +import cmu.xprize.comp_logging.CInterventionLogManager; +import cmu.xprize.comp_logging.CLogManager; +import cmu.xprize.comp_logging.CPerfLogManager; +import cmu.xprize.comp_logging.CPreferenceCache; +import cmu.xprize.comp_logging.ILogManager; +import cmu.xprize.comp_logging.IPerfLogManager; import cmu.xprize.rthomescreen.startup.CMasterContainer; import cmu.xprize.rthomescreen.startup.CStartView; import cmu.xprize.util.IRoboTutor; +import cmu.xprize.util.JSON_Helper; +import cmu.xprize.util.TCONST; import static android.os.UserManager.DISALLOW_ADD_USER; import static android.os.UserManager.DISALLOW_ADJUST_VOLUME; import static android.os.UserManager.DISALLOW_FACTORY_RESET; import static android.os.UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA; import static android.os.UserManager.DISALLOW_SAFE_BOOT; +import static cmu.xprize.util.TCONST.GRAPH_MSG; -public class HomeActivity extends Activity implements IRoboTutor{ +public class HomeActivity extends AppCompatActivity implements IRoboTutor{ @@ -58,15 +76,63 @@ public class HomeActivity extends Activity implements IRoboTutor{ private static final String DEBUG_TAG = "DEBUG_LAUNCH"; // do we launch FaceLogin first? Or RoboTutor? - private static final boolean LAUNCH_FACELOGIN = false; + private static final boolean LAUNCH_FACELOGIN = true; // launch vars public static final String STUDENT_ID_VAR = "studentId"; public static final String SESSION_ID_VAR = "sessionId"; + private static ConfigurationItems configurationItems; + + static public String VERSION_RT; + + static public ILogManager logManager; + static public IPerfLogManager perfLogManager; + + private static String hotLogPath; + private static String hotLogPathPerf; + private static String readyLogPath; + private static String readyLogPathPerf; + private static String audioLogPath; + private static String interventionLogPath; + + final static public String CacheSource = TCONST.ASSETS; // assets or extern + static public String APP_PRIVATE_FILES; + static public String LOG_ID = "STARTUP"; + + private static final String LOG_SEQUENCE_ID = "LOG_SEQUENCE_ID"; + + // for devs, this is faster than changing the config file + private static final boolean QUICK_DEBUG_CONFIG = false; + private static final ConfigurationItems QUICK_DEBUG_CONFIG_OPTION = ConfigurationQuickOptions.DEBUG_EN; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + APP_PRIVATE_FILES = getApplicationContext().getExternalFilesDir("").getPath(); + + // Initialize the JSON Helper STATICS - just throw away the object. + // + new JSON_Helper(getAssets(), CacheSource, APP_PRIVATE_FILES); + + // Gives the dev the option to override the stored config file. + configurationItems = QUICK_DEBUG_CONFIG ? QUICK_DEBUG_CONFIG_OPTION : new ConfigurationItems(); // OPEN_SOURCE opt to switch here. + Configuration.saveConfigurationItems(this, configurationItems); + + // Prep the CPreferenceCache + // Update the globally accessible id object for this engine instance. + // + LOG_ID = CPreferenceCache.initLogPreference(this); + VERSION_RT = BuildConfig.VERSION_NAME; + + //Start LogManager + // + initializeAndStartLogs(); + + //Log current config data + // + Configuration.logConfigurationItems(this); + // stuff needed for kiosk mode // TODO move this to one class mAdminComponentName = AdminReceiver.getComponentName(this); @@ -76,6 +142,7 @@ protected void onCreate(Bundle savedInstanceState) { // mKisokPackages mKioskPackages = new ArrayList<>(); + mKioskPackages.add(getPackageName()); mKioskPackages.add(flPackage); mKioskPackages.add(rtPackage); mKioskPackages.add(mPackageName); @@ -111,11 +178,72 @@ protected void onCreate(Bundle savedInstanceState) { Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); } catch(Exception e) { - + e.printStackTrace(); } launchFtpService(); } + + /** + * create log paths. + * initialize times and other IDs + * start logging + */ + private void initializeAndStartLogs() { + + hotLogPath = Environment.getExternalStorageDirectory() + TCONST.HOT_LOG_FOLDER; + readyLogPath = Environment.getExternalStorageDirectory() + TCONST.READY_LOG_FOLDER; + + hotLogPathPerf = Environment.getExternalStorageDirectory() + TCONST.HOT_LOG_FOLDER_PERF; + readyLogPathPerf = Environment.getExternalStorageDirectory() + TCONST.READY_LOG_FOLDER_PERF; + + audioLogPath = Environment.getExternalStorageDirectory() + TCONST.AUDIO_LOG_FOLDER; + + interventionLogPath = Environment.getExternalStorageDirectory() + TCONST.INTERVENTION_LOG_FOLDER; + + Calendar calendar = Calendar.getInstance(Locale.US); + String initTime = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss", Locale.US).format(calendar.getTime()); + String sequenceIdString = String.format(Locale.US, "%06d", getNextLogSequenceId()); + // NOTE: Need to include the configuration name when that is fully merged + String logFilename = "RTSuiteHome_" + // TODO TODO TODO there should be a version name in here!!! + Configuration.configVersion(this) + "_" + BuildConfig.VERSION_NAME + "_" + sequenceIdString + + "_" + initTime + "_" + Build.SERIAL; + + Log.w("LOG_DEBUG", "Beginning new session with LOG_FILENAME = " + logFilename); + + logManager = CLogManager.getInstance(); + logManager.transferHotLogs(hotLogPath, readyLogPath); + logManager.transferHotLogs(hotLogPathPerf, readyLogPathPerf); + + logManager.startLogging(hotLogPath, logFilename); + CErrorManager.setLogManager(logManager); + + perfLogManager = CPerfLogManager.getInstance(); + perfLogManager.startLogging(hotLogPathPerf, "PERF_" + logFilename); + + CInterventionLogManager.getInstance().startLogging(interventionLogPath, + "INT_" + logFilename); + + // TODO : implement time stamps + logManager.postDateTimeStamp(GRAPH_MSG, "RTSuiteHome:SessionStart"); + logManager.postEvent_I(GRAPH_MSG, "EngineVersion:" + VERSION_RT); + } + + private int getNextLogSequenceId() { + SharedPreferences prefs = getPreferences(MODE_PRIVATE); + + // grab the current sequence id (the one we should use for this current run + // of the app + final int logSequenceId = prefs.getInt(LOG_SEQUENCE_ID, 0); + + // increase the log sequence id by 1 for the next usage + prefs.edit() + .putInt(LOG_SEQUENCE_ID, logSequenceId + 1) + .apply(); + + return logSequenceId; + } + /** * launch RoboTransfer, a service to transfer log files */ @@ -238,6 +366,7 @@ public void onStartTutor() { } if(launchIntent != null) { + stopLockTask(); startActivity(launchIntent); } else { Toast.makeText(getApplicationContext(), "Please install FaceLogin", Toast.LENGTH_LONG).show(); @@ -257,24 +386,24 @@ private String generateSessionID() { } @Override - protected void onStart() { - super.onStart(); - - - // start lock task mode if it's not already active - ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); - // ActivityManager.getLockTaskModeState api is not available in pre-M - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - if (!am.isInLockTaskMode()) { - startLockTask(); - } - } else { - if (am.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_NONE) { - startLockTask(); + protected void onResume() { + super.onResume(); + + if (Configuration.getPinningMode(this)) { + // start lock task mode if it's not already active + ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + // ActivityManager.getLockTaskModeState api is not available in pre-M + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + if (!am.isInLockTaskMode()) { + startLockTask(); + } + } else { + if (am.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_NONE) { + startLockTask(); + } } } - setFullScreen(); } } diff --git a/app/src/main/java/cmu/xprize/rthomescreen/PermissionsActivity.java b/app/src/main/java/cmu/xprize/rthomescreen/PermissionsActivity.java new file mode 100644 index 0000000..9cd2d38 --- /dev/null +++ b/app/src/main/java/cmu/xprize/rthomescreen/PermissionsActivity.java @@ -0,0 +1,119 @@ +package cmu.xprize.rthomescreen; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.provider.Settings; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; +import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; + +public class PermissionsActivity extends AppCompatActivity { + + public static final String[] PERMISSIONS = new String[]{ + READ_EXTERNAL_STORAGE, + WRITE_EXTERNAL_STORAGE + }; + + private SharedPreferences mPrefs; + + private static final String TAG = "PermissionsActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //setContentView(R.layout.activity_permissions); + + mPrefs = getSharedPreferences(getPackageName(), MODE_PRIVATE); + + if (!hasPermissions(this, PERMISSIONS)) { + if (hasDenied(this, PERMISSIONS)) { + requestPermissions(PERMISSIONS); + } else { + if (isFirstTimeAsking(PERMISSIONS)) { + requestPermissions(PERMISSIONS); + } else { + //Launch App Page with settings + Toast.makeText(this, "Please grant the permissions through Settings!", Toast.LENGTH_SHORT).show(); + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + Uri uri = Uri.fromParts("package", getPackageName(), null); + intent.setData(uri); + startActivity(intent); + finish(); + } + } + } + else { + launchHomeActivity(); + } + } + + public static boolean hasPermissions(Context context, String... permissions) { + if (permissions != null) { + for (String permission : permissions) { + if (ContextCompat.checkSelfPermission(context, permission) != 0) { + return false; + } + } + } + return true; + } + + public static boolean hasDenied(Context context, String... permissions) { + if (permissions != null) { + for (String permission : permissions) { + if (ActivityCompat.shouldShowRequestPermissionRationale((Activity) context, permission)) { + return true; + } + } + } + return false; + } + + public boolean isFirstTimeAsking(String... permissions) { + if (permissions != null) { + for (String permission : permissions) { + if (mPrefs.getBoolean(permission, false)) return false; + } + } + return true; + } + + public void requestPermissions(String... permissions) { + if (permissions != null) { + SharedPreferences.Editor editor = mPrefs.edit(); + for (String permission : permissions) { + editor.putBoolean(permission, true); + } + editor.apply(); + ActivityCompat.requestPermissions(this, permissions, 1001); + } + } + + private void launchHomeActivity() { + Intent intent = new Intent(this, HomeActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode == 1001 && grantResults.length > 0) { + Log.e(TAG, "Permission results"); + if (hasPermissions(this, PERMISSIONS)) launchHomeActivity(); + else { +// Intent intent = new Intent(this, PermissionsActivity.class); +// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); +// startActivity(intent); + finish(); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/cmu/xprize/rthomescreen/startup/CMasterContainer.java b/app/src/main/java/cmu/xprize/rthomescreen/startup/CMasterContainer.java index bcb6589..72a443d 100644 --- a/app/src/main/java/cmu/xprize/rthomescreen/startup/CMasterContainer.java +++ b/app/src/main/java/cmu/xprize/rthomescreen/startup/CMasterContainer.java @@ -32,7 +32,7 @@ import java.util.ArrayList; import cmu.xprize.rthomescreen.R; -import cmu.xprize.util.ILogManager; +import cmu.xprize.comp_logging.ILogManager; public class CMasterContainer extends RelativeLayout { diff --git a/app/src/main/java/cmu/xprize/rthomescreen/startup/CStartView.java b/app/src/main/java/cmu/xprize/rthomescreen/startup/CStartView.java index d064f54..a681e17 100644 --- a/app/src/main/java/cmu/xprize/rthomescreen/startup/CStartView.java +++ b/app/src/main/java/cmu/xprize/rthomescreen/startup/CStartView.java @@ -40,7 +40,7 @@ import cmu.xprize.comp_pointtap.HA_CONST; import cmu.xprize.rthomescreen.R; -import cmu.xprize.util.CErrorManager; +import cmu.xprize.comp_logging.CErrorManager; import cmu.xprize.util.IRoboTutor; import cmu.xprize.util.TCONST; diff --git a/app/src/main/res/layout/activity_permissions.xml b/app/src/main/res/layout/activity_permissions.xml new file mode 100644 index 0000000..014565a --- /dev/null +++ b/app/src/main/res/layout/activity_permissions.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7759cc9..5f90281 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,10 @@ buildscript { repositories { jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:4.1.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -15,14 +16,15 @@ buildscript { allprojects { repositories { jcenter() + google() } // Global variables common to all modules // see: http://tools.android.com/tech-docs/new-build-system/tips // ext { - rtCompileSdkVersion=25 //Integer - rtBuildToolsVersion="25.0.1" //String + rtCompileSdkVersion=30 //Integer + rtBuildToolsVersion="30.0.3" //String rtMinSdkVersion=23 @@ -35,7 +37,7 @@ allprojects { // Semantic versioning description: // - rtVersionName="1.0.0" + rtVersionName="1.7.5.6" } } diff --git a/comp_configuration/.gitignore b/comp_configuration/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/comp_configuration/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/comp_configuration/build.gradle b/comp_configuration/build.gradle new file mode 100644 index 0000000..47c18b0 --- /dev/null +++ b/comp_configuration/build.gradle @@ -0,0 +1,94 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion rootProject.ext.rtCompileSdkVersion + buildToolsVersion rootProject.ext.rtBuildToolsVersion + defaultConfig { + minSdkVersion rootProject.ext.rtMinSdkVersion + targetSdkVersion rootProject.ext.rtTargetSdkVersion + versionCode rootProject.ext.rtVersionCode + versionName rootProject.ext.rtVersionName + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + def keystorePropertiesFile = rootProject.file("keystore.properties") + def keystoreProperties = new Properties() + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + + signingConfigs { + android { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + } + + buildTypes { + def BOOLEAN = "boolean" + def STRING = "String" + def TRUE = "true" + def FALSE = "false" + + def LANGUAGE_FEATURE_ID = "LANGUAGE_FEATURE_ID" + def LANGUAGE_ENGLISH = "\"LANG_EN\"" + def LANGUAGE_SWAHILI = "\"LANG_SW\"" + + debug { + buildConfigField "String", "WIFI_CONFIG", "\"DEBUG\"" + debuggable true + } + + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + + xprize { + buildConfigField "String", "WIFI_CONFIG", "\"XPRIZE\"" + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + + local { + buildConfigField "String", "WIFI_CONFIG", "\"LOCAL\"" + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + + vmc { + buildConfigField "String", "WIFI_CONFIG", "\"VMC\"" + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation project(path: ':comp_logging') + implementation project(path: ':util') + testImplementation 'junit:junit:4.+' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} \ No newline at end of file diff --git a/comp_configuration/consumer-rules.pro b/comp_configuration/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/comp_configuration/proguard-rules.pro b/comp_configuration/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/comp_configuration/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/comp_configuration/src/androidTest/java/com/xprize/comp_configuration/ExampleInstrumentedTest.java b/comp_configuration/src/androidTest/java/com/xprize/comp_configuration/ExampleInstrumentedTest.java new file mode 100644 index 0000000..6e18477 --- /dev/null +++ b/comp_configuration/src/androidTest/java/com/xprize/comp_configuration/ExampleInstrumentedTest.java @@ -0,0 +1,25 @@ +package com.xprize.comp_configuration; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.xprize.comp_configuration.test", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/comp_configuration/src/main/AndroidManifest.xml b/comp_configuration/src/main/AndroidManifest.xml new file mode 100644 index 0000000..cda33e6 --- /dev/null +++ b/comp_configuration/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/comp_configuration/src/main/java/com/xprize/comp_configuration/Configuration.java b/comp_configuration/src/main/java/com/xprize/comp_configuration/Configuration.java new file mode 100644 index 0000000..ac4b784 --- /dev/null +++ b/comp_configuration/src/main/java/com/xprize/comp_configuration/Configuration.java @@ -0,0 +1,131 @@ +package com.xprize.comp_configuration; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import java.util.Map; + +import cmu.xprize.comp_logging.CLogManager; + +import static android.content.Context.MODE_PRIVATE; + +public class Configuration { + + private static final String ROBOTUTOR_CONFIGURATION = "ROBOTUTOR_CONFIGURATION"; + + public static void saveConfigurationItems(Context context, ConfigurationItems configItems) { + SharedPreferences prefs = context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE); + prefs.edit() + .putString(ConfigurationItems.CONFIG_VERSION, configItems.config_version) + .putBoolean(ConfigurationItems.LANGUAGE_OVERRIDE, configItems.language_override) + .putBoolean(ConfigurationItems.SHOW_TUTOR_VERSION, configItems.show_tutorversion) + .putBoolean(ConfigurationItems.SHOW_DEBUG_LAUNCHER, configItems.show_debug_launcher) + .putBoolean(ConfigurationItems.LANGUAGE_SWITCHER, configItems.language_switcher) + .putBoolean(ConfigurationItems.NO_ASR_APPS, configItems.no_asr_apps) + .putString(ConfigurationItems.LANGUAGE_FEATURE_ID, configItems.language_feature_id) + .putBoolean(ConfigurationItems.SHOW_DEMO_VIDS, configItems.show_demo_vids) + .putBoolean(ConfigurationItems.USE_PLACEMENT, configItems.use_placement) + .putBoolean(ConfigurationItems.RECORD_AUDIO, configItems.record_audio) + .putString(ConfigurationItems.MENU_TYPE, configItems.menu_type) + .putBoolean(ConfigurationItems.SHOW_HELPER_BUTTON, configItems.show_helper_button) + .putBoolean(ConfigurationItems.RECORDING, configItems.recording) + .putString(ConfigurationItems.BASE_DIRECTORY, configItems.baseDirectory) + .putBoolean(ConfigurationItems.RECORDING_WITH_AUDIO_ENABLED, configItems.recording_with_audio_enabled) + .putBoolean(ConfigurationItems.PINNING_MODE, configItems.pinning_mode) + .apply(); + } + + public static String configVersion(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getString(ConfigurationItems.CONFIG_VERSION, "release_sw"); + } + + public static boolean languageOverride(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getBoolean(ConfigurationItems.LANGUAGE_OVERRIDE, true); + } + + public static boolean showTutorVersion(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getBoolean(ConfigurationItems.SHOW_TUTOR_VERSION, true); + } + + public static boolean showDebugLauncher(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getBoolean(ConfigurationItems.SHOW_DEBUG_LAUNCHER, false); + } + + public static boolean getLanguageSwitcher(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getBoolean(ConfigurationItems.LANGUAGE_SWITCHER, false); + } + + public static boolean noAsrApps(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getBoolean(ConfigurationItems.NO_ASR_APPS, false); + } + + public static String getLanguageFeatureID(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getString(ConfigurationItems.LANGUAGE_FEATURE_ID, "LANG_SW"); + } + + public static boolean showDemoVids(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getBoolean(ConfigurationItems.SHOW_DEMO_VIDS, true); + } + + public static boolean usePlacement(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getBoolean(ConfigurationItems.USE_PLACEMENT, true); + } + + public static boolean recordAudio(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getBoolean(ConfigurationItems.RECORD_AUDIO, false); + } + + public static String getMenuType(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getString(ConfigurationItems.MENU_TYPE, "CD1"); + } + + public static String getBaseDirectory(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getString(ConfigurationItems.BASE_DIRECTORY, "roboscreen"); + } + + public static boolean getRecording(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getBoolean(ConfigurationItems.RECORDING, true); + } + + public static boolean getRecordingWithAudioEnabled(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getBoolean(ConfigurationItems.RECORDING_WITH_AUDIO_ENABLED, false); + } + + public static boolean getShowHelperButton(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getBoolean(ConfigurationItems.SHOW_HELPER_BUTTON, false); + } + + public static boolean getPinningMode(Context context) { + return context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE) + .getBoolean(ConfigurationItems.PINNING_MODE, false); + } + + /** + * logs all the config items. + */ + public static void logConfigurationItems(Context context) { + StringBuilder config = new StringBuilder(); + Map allConfig = context.getSharedPreferences(ROBOTUTOR_CONFIGURATION, MODE_PRIVATE).getAll(); + for (Map.Entry entry : allConfig.entrySet()) { + config.append("\n").append(entry.getKey().toLowerCase()).append(" - ").append(entry.getValue().toString()); + } + Log.e(ROBOTUTOR_CONFIGURATION, "\n" + config.toString()); + CLogManager.getInstance().postEvent_I(ROBOTUTOR_CONFIGURATION, config.toString()); + } +} diff --git a/comp_configuration/src/main/java/com/xprize/comp_configuration/ConfigurationClassMap.java b/comp_configuration/src/main/java/com/xprize/comp_configuration/ConfigurationClassMap.java new file mode 100644 index 0000000..5099547 --- /dev/null +++ b/comp_configuration/src/main/java/com/xprize/comp_configuration/ConfigurationClassMap.java @@ -0,0 +1,20 @@ +package com.xprize.comp_configuration; + +import java.util.HashMap; + +public class ConfigurationClassMap { + static public HashMap classMap = new HashMap<>(); + + static { + classMap.put("ConfigurationItems", ConfigurationItems.class); + + classMap.put("string", String.class); + classMap.put("bool", Boolean.class); + classMap.put("int", Integer.class); + classMap.put("float", Float.class); + classMap.put("byte", Byte.class); + classMap.put("long", Long.class); + classMap.put("short", Short.class); + classMap.put("object", Object.class); + } +} diff --git a/comp_configuration/src/main/java/com/xprize/comp_configuration/ConfigurationItems.java b/comp_configuration/src/main/java/com/xprize/comp_configuration/ConfigurationItems.java new file mode 100644 index 0000000..5da9d88 --- /dev/null +++ b/comp_configuration/src/main/java/com/xprize/comp_configuration/ConfigurationItems.java @@ -0,0 +1,130 @@ +package com.xprize.comp_configuration; + +import android.util.Log; + +import org.json.JSONObject; + +import cmu.xprize.comp_logging.CErrorManager; +import cmu.xprize.util.ILoadableObject; +import cmu.xprize.util.IScope; +import cmu.xprize.util.JSON_Helper; +import cmu.xprize.util.TCONST; + +public class ConfigurationItems implements ILoadableObject { + + private static final String TAG = "ConfigurationItems"; + + public static final String CONFIG_VERSION = "CONFIG_VERSION"; + public static final String LANGUAGE_OVERRIDE = "LANGUAGE_OVERRIDE"; + public static final String SHOW_TUTOR_VERSION = "SHOW_TUTOR_VERSION"; + public static final String SHOW_DEBUG_LAUNCHER = "SHOW_DEBUG_LAUNCHER"; + public static final String LANGUAGE_SWITCHER = "LANGUAGE_SWITCHER"; + public static final String NO_ASR_APPS = "NO_ASR_APPS"; + public static final String LANGUAGE_FEATURE_ID = "LANGUAGE_FEATURE_ID"; + public static final String SHOW_DEMO_VIDS = "SHOW_DEMO_VIDS"; + public static final String USE_PLACEMENT = "USE_PLACEMENT"; + public static final String RECORD_AUDIO = "RECORD_AUDIO"; + public static final String MENU_TYPE = "MENU_TYPE"; + public static final String RECORDING = "RECORDING"; + public static final String RECORDING_WITH_AUDIO_ENABLED = "RECORDING_WITH_AUDIO_ENABLED"; + public static final String SHOW_HELPER_BUTTON = "SHOW_HELPER_BUTTON"; + public static final String BASE_DIRECTORY = "BASE_DIRECTORY"; + public static final String PINNING_MODE = "PINNING_MODE"; + + public String config_version; + public boolean language_override; + public boolean show_tutorversion; + public boolean show_debug_launcher; + public boolean language_switcher; + public boolean no_asr_apps; + public String language_feature_id; + public boolean show_demo_vids; + public boolean use_placement; + public boolean record_audio; + public String menu_type; + public boolean recording; + public boolean show_helper_button; + public String baseDirectory; + public boolean recording_with_audio_enabled; + public boolean pinning_mode; + + public ConfigurationItems() { + String dataPath = TCONST.DOWNLOAD_PATH + "/config.json"; + String jsonData = JSON_Helper.cacheDataByName(dataPath); + Log.e("jsonData", jsonData); + + try { + loadJSON(new JSONObject(jsonData), null); + /* + The JSON object is logged here to make the app logs more identifiable + and searchable. + */ + Log.i(TAG, new JSONObject(jsonData).toString(4)); + this.setConfigVersion(); + } catch (Exception e) { + Log.e(TAG, "Invalid Data Source for : " + dataPath, e); + setDefaults(); + } + } + + // used for QuickOptions + public ConfigurationItems(String config_version, boolean language_override, + boolean show_tutorversion, boolean show_debug_launcher, + boolean language_switcher, boolean no_asr_apps, + String language_feature_id, boolean show_demo_vids, + boolean use_placement, boolean record_audio, + String menu_type, boolean recording, boolean recording_with_audio_enabled, + boolean show_helper_button, String baseDirectory, boolean pinning_mode) { + +// this.config_version = config_version; + this.setConfigVersion(); + this.language_override = language_override; + this.show_tutorversion = show_tutorversion; + this.show_debug_launcher = show_debug_launcher; + this.language_switcher = language_switcher; + this.no_asr_apps = no_asr_apps; + this.language_feature_id = language_feature_id; + this.show_demo_vids = show_demo_vids; + this.use_placement = use_placement; + this.record_audio = record_audio; + this.menu_type = menu_type; + this.recording = recording; + this.recording_with_audio_enabled = recording_with_audio_enabled; + this.show_helper_button = show_helper_button; + this.baseDirectory = baseDirectory; + this.pinning_mode = pinning_mode; + } + + public void setDefaults() { + // use the swahili versions as default +// config_version = "release_sw"; // shouldn't it be fttt...? + this.setConfigVersion(); + language_override = true; + show_tutorversion = true; + show_debug_launcher = false; + language_switcher = false; + no_asr_apps = false; + language_feature_id = "LANG_SW"; + show_demo_vids = true; + use_placement = true; + record_audio = false; + menu_type = "CD1"; + show_helper_button = false; + baseDirectory = "roboscreen"; + recording = true; + recording_with_audio_enabled = false; + pinning_mode = false; + } + + private void setConfigVersion() { + String dataPath = TCONST.DOWNLOAD_PATH + "/config.json"; + String jsonData = JSON_Helper.cacheDataByName(dataPath); + String configAcronym = JSON_Helper.createValueAcronym(jsonData); + this.config_version = configAcronym; + } + + @Override + public void loadJSON(JSONObject jsonObj, IScope scope) { + JSON_Helper.parseSelf(jsonObj, this, ConfigurationClassMap.classMap, scope); + } +} diff --git a/comp_configuration/src/main/java/com/xprize/comp_configuration/ConfigurationQuickOptions.java b/comp_configuration/src/main/java/com/xprize/comp_configuration/ConfigurationQuickOptions.java new file mode 100644 index 0000000..fc85856 --- /dev/null +++ b/comp_configuration/src/main/java/com/xprize/comp_configuration/ConfigurationQuickOptions.java @@ -0,0 +1,52 @@ +package com.xprize.comp_configuration; + +/** + * RoboTutor + *

Similar to QuickDebugTutor, this class offers the developer a way to store multiple pre-set configuration + * option sets without changing the config file.

+ * Created by kevindeland on 5/9/19. + */ + +public class ConfigurationQuickOptions { + + + // Both SW and EN versions, and they both have the debugger menu. + public static ConfigurationItems DEBUG_SW_EN = new ConfigurationItems( + "debug_sw_en", + false, + true, + true, + true, + false, + "LANG_NULL", + false, + false, + false, + "CD1", + true, + false, + false, + "roboscreen", + false + ); + + // EN version, and they both have the debugger menu. + public static ConfigurationItems DEBUG_EN = new ConfigurationItems( + "debug_en", + true, + true, + true, + false, + false, + "LANG_EN", + false, + false, + false, + "CD1", + true, + false, + false, + "roboscreen", + false + ); +} diff --git a/comp_configuration/src/test/java/com/xprize/comp_configuration/ExampleUnitTest.java b/comp_configuration/src/test/java/com/xprize/comp_configuration/ExampleUnitTest.java new file mode 100644 index 0000000..ff270d8 --- /dev/null +++ b/comp_configuration/src/test/java/com/xprize/comp_configuration/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.xprize.comp_configuration; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/comp_logging/.gitignore b/comp_logging/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/comp_logging/.gitignore @@ -0,0 +1 @@ +/build diff --git a/comp_logging/build.gradle b/comp_logging/build.gradle new file mode 100644 index 0000000..350d762 --- /dev/null +++ b/comp_logging/build.gradle @@ -0,0 +1,89 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.rtCompileSdkVersion + buildToolsVersion rootProject.ext.rtBuildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.rtMinSdkVersion + targetSdkVersion rootProject.ext.rtTargetSdkVersion + versionCode rootProject.ext.rtVersionCode + versionName rootProject.ext.rtVersionName + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + def keystorePropertiesFile = rootProject.file("keystore.properties") + def keystoreProperties = new Properties() + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + + signingConfigs { + android { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + } + + buildTypes { + def BOOLEAN = "boolean" + def STRING = "String" + def TRUE = "true" + def FALSE = "false" + + def LANGUAGE_FEATURE_ID = "LANGUAGE_FEATURE_ID" + def LANGUAGE_ENGLISH = "\"LANG_EN\"" + def LANGUAGE_SWAHILI = "\"LANG_SW\"" + + debug { + buildConfigField "String", "WIFI_CONFIG", "\"DEBUG\"" + debuggable true + } + + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + + xprize { + buildConfigField "String", "WIFI_CONFIG", "\"XPRIZE\"" + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + + local { + buildConfigField "String", "WIFI_CONFIG", "\"LOCAL\"" + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + + vmc { + buildConfigField "String", "WIFI_CONFIG", "\"VMC\"" + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + } +} + +dependencies { + api fileTree(include: ['*.jar'], dir: 'libs') + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + implementation 'com.android.support:appcompat-v7:25.2.0' + implementation 'com.google.code.gson:gson:2.8.0' + api 'junit:junit:4.12' +} diff --git a/comp_logging/proguard-rules.pro b/comp_logging/proguard-rules.pro new file mode 100644 index 0000000..fac3064 --- /dev/null +++ b/comp_logging/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Dev\Android\AndroidSDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/comp_logging/src/androidTest/java/cmu/xprize/comp_logging/ExampleInstrumentedTest.java b/comp_logging/src/androidTest/java/cmu/xprize/comp_logging/ExampleInstrumentedTest.java new file mode 100644 index 0000000..084e88c --- /dev/null +++ b/comp_logging/src/androidTest/java/cmu/xprize/comp_logging/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package cmu.xprize.comp_logging; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("cmu.xprize.comp_logging.test", appContext.getPackageName()); + } +} diff --git a/comp_logging/src/main/AndroidManifest.xml b/comp_logging/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6837b8b --- /dev/null +++ b/comp_logging/src/main/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/util/src/main/java/cmu/xprize/util/CErrorManager.java b/comp_logging/src/main/java/cmu/xprize/comp_logging/CErrorManager.java similarity index 91% rename from util/src/main/java/cmu/xprize/util/CErrorManager.java rename to comp_logging/src/main/java/cmu/xprize/comp_logging/CErrorManager.java index 8383eae..62f7584 100644 --- a/util/src/main/java/cmu/xprize/util/CErrorManager.java +++ b/comp_logging/src/main/java/cmu/xprize/comp_logging/CErrorManager.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,10 +16,12 @@ // //********************************************************************************* -package cmu.xprize.util; +package cmu.xprize.comp_logging; import android.util.Log; +import cmu.xprize.comp_logging.ILogManager; + /** * This was added with Android Studio 2.1 and PIXEL C -- simple logEvent could cause the logcat * to miss the error message @@ -28,7 +29,7 @@ */ public class CErrorManager { - static ILogManager mLogManager; + static ILogManager mLogManager; static public void setLogManager(ILogManager manager) { mLogManager = manager; diff --git a/comp_logging/src/main/java/cmu/xprize/comp_logging/CInterventionLogManager.java b/comp_logging/src/main/java/cmu/xprize/comp_logging/CInterventionLogManager.java new file mode 100644 index 0000000..7a1867e --- /dev/null +++ b/comp_logging/src/main/java/cmu/xprize/comp_logging/CInterventionLogManager.java @@ -0,0 +1,26 @@ +package cmu.xprize.comp_logging; + +/** + * RoboTutor + *

+ * Created by kevindeland on 2019-10-25. + */ +public class CInterventionLogManager extends CLogManagerBase { + + private static String TAG = "CInterventionLogManager"; + + private CInterventionLogManager() { + super.TAG = TAG; + } + + // Singleton + private static CInterventionLogManager ourInstance = new CInterventionLogManager(); + + public static CInterventionLogManager getInstance() { + return ourInstance; + } + + public void postInterventionLog(InterventionLogItem event) { + postEvent_I(TLOG_CONST.PERFORMANCE_TAG, event.toString()); + } +} diff --git a/comp_logging/src/main/java/cmu/xprize/comp_logging/CLogManager.java b/comp_logging/src/main/java/cmu/xprize/comp_logging/CLogManager.java new file mode 100644 index 0000000..5421f5b --- /dev/null +++ b/comp_logging/src/main/java/cmu/xprize/comp_logging/CLogManager.java @@ -0,0 +1,40 @@ +//********************************************************************************* +// +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved +// +// 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 cmu.xprize.comp_logging; + + +public class CLogManager extends CLogManagerBase implements ILogManager { + private static String TAG = "CLogManager"; + + + // Singleton + private static CLogManager ourInstance = new CLogManager(); + + public static CLogManager getInstance() { + return ourInstance; + } + + private CLogManager() { + super.TAG = TAG; + } + + + + +} diff --git a/comp_logging/src/main/java/cmu/xprize/comp_logging/CLogManagerBase.java b/comp_logging/src/main/java/cmu/xprize/comp_logging/CLogManagerBase.java new file mode 100644 index 0000000..f832f49 --- /dev/null +++ b/comp_logging/src/main/java/cmu/xprize/comp_logging/CLogManagerBase.java @@ -0,0 +1,654 @@ +//********************************************************************************* +// +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved +// +// 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 cmu.xprize.comp_logging; + +import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; +import android.util.Log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.HashMap; + + +public class CLogManagerBase implements ILogManager { + + private static final int OBJ_PART = 0; + private static final int VAL_PART = 1; + + //private static final String LOG_VERSION = "1.0.0"; // initial release + private static final String LOG_VERSION = "1.0.1"; // Updated LTKPlus to use LTKPLUS tag + + private static String currenttutor = ""; + private String TERMINATING_PACKET = "{\"end\":\"end\"}]}"; + private byte[] TERMINATE_BYTES = TERMINATING_PACKET.getBytes(); + + private LogThread logThread; // background thread handling log data + private String log_Path; + private String log_Filename; + private boolean isLogging = false; + + private Handler logHandler; + private HashMap queueMap = new HashMap(); + private boolean mDisabled = false; + + private File logFile; + private FileOutputStream logStream; + private java.nio.channels.FileLock logLock; + + private FileWriter logWriter; + private RandomAccessFile seekableLogWriter; + private boolean seekable = true; + + private boolean logWriterValid = false; + + // Datashop specific + + private boolean loggingDS = false; + private File logDSFile; + private FileOutputStream logDSStream; + private java.nio.channels.FileLock logDSLock; + private FileWriter logDSWriter; + private boolean logDSWriterValid = false; + + + protected String TAG = "CLogManagerBase"; + + + protected CLogManagerBase() { + } + + public void startLogging(String logPath, String logFilename) { + + log_Path = logPath; + log_Filename = logFilename; + + // Restart the log if necessary + // + stopLogging(); + + isLogging = true; + mDisabled = false; + + logThread = new LogThread(TAG); + logThread.start(); + + try { + logHandler = new Handler(logThread.getLooper()); + } + catch(Exception e) { + Log.e(TAG, "Handler Create Failed:" + e); + } + + lockLog(); + } + + + /** + * Stop accepting new packets - + * Causes the thread to flush the input queue and then exit + * + */ + public void stopLogging() { + + if(isLogging) { + Log.i(TAG, "Shutdown begun"); + + isLogging = false; + mDisabled = true; + + // Terminate the log thread - flush the queue prior to exit + // + try { + + logThread.getLooper().quitSafely(); + + logThread.join(); // waits until it finishes + Log.i(TAG, "Shutdown complete"); + + } catch (InterruptedException e) { + } + + releaseLog(); + } + } + + public void transferHotLogs(String hotPath, String readyPath) { + + File hotDir = new File(hotPath); + + // our first time... + if (!hotDir.exists()) { + return; + } + + File readyDir = new File(readyPath); + + try { + + // make dir if necessary + if (!readyDir.exists()) { + readyDir.mkdir(); + } + + for (File f : hotDir.listFiles()) { + Log.w("LOG_DEBUG", "Moving file " + f.getName() + " between folders."); + + if (f.isDirectory()) { + // do nothing... there should not be any directories + } else { + File readyLog = new File(readyPath, f.getName()); + InputStream in = new FileInputStream(f); + OutputStream out = new FileOutputStream(readyLog); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + + in.close(); + out.close(); + + f.delete(); + } + } + } catch (IOException e) { + CErrorManager.logEvent(TAG, "Moving file Error:", e, false); + } + + } + + + public static void setTutor(String tutorid) { + currenttutor = tutorid; + } + + + /** + * This is a background thread on which to process all log data requests + * + */ + private final class LogThread extends HandlerThread { + + public LogThread(String name) { + super(name); + } + + public LogThread(String name, int priority) { + super(name, priority); + } + } + + + /** + * This is the central processsing point of the data log - this runs on an independent thread + * from the UI. + */ + public class Queue implements Runnable { + + protected String dataPacket; + protected String unEncodedPacket; + protected String statePacket; + + public Queue(String packet) { + + dataPacket = packet; + unEncodedPacket = null; + } + + public Queue(String _packet, String _target, String _state) { + dataPacket = _packet; + unEncodedPacket = _target; + statePacket = _state; + } + + // we can accept data with various object/value encodings (i.e. different delimiters) + // + private String parseData(String dataPacket, String delimiter) { + + String encodedPacket = "{"; + + String[] objvalPairs = dataPacket.split(","); + + for(int pair = 0 ; pair < objvalPairs.length ; pair++) { + + String[] objval = objvalPairs[pair].split(delimiter); + + if(objval.length > 1) { + encodedPacket = encodedPacket + "\"" + objval[OBJ_PART] + "\":\"" + objval[VAL_PART] + "\""; + } + else { + encodedPacket = encodedPacket + "\"" + objval[OBJ_PART] + "\":\"" + "" + "\""; + } + + if(pair < objvalPairs.length -1) { + encodedPacket = encodedPacket + ","; + } + } + encodedPacket = encodedPacket + "}"; + + return encodedPacket; + } + + @Override + public void run() { + + try { + queueMap.remove(this); + + // Don't do this JSON encoding on the UI Thread - + // if unEncodedPacket is not null then the packet is incomplete and unEncodedPacket + // contains a String containing a comma delimited set of obj:value pairs + // + // For statePackets + // e.g. "myobj1|itsvalue,myobj2|itsvalue" + // + // For unEncodedPacket + // e.g. "myobj1:itsvalue,myobj2:itsvalue" + // + // These need to be encoded into a JSON data subobject. + // + if(statePacket != null) { + + String encodedJSONPacket = parseData(statePacket, "#"); + + dataPacket = dataPacket + "\"data\":" + encodedJSONPacket + "},\n"; + } + else if(unEncodedPacket != null) { + + String encodedJSONPacket = parseData(unEncodedPacket, ":"); + + dataPacket = dataPacket + "\"data\":" + encodedJSONPacket + "},\n"; + } + + writePacketToLog(dataPacket); + + } catch (Exception e) { + CErrorManager.logEvent(TAG, "Write Error:", e, false); + } + } + } + + + /** + * We use file locks to keep the logs around until we are finished. The RoboTutor XPrize initiative + * used a Google Drive-Sync utility App that required locking the files so they weren't deleted while in + * use. So this is not a requirement otherwise. + * + */ + private void lockLog() { + + // Release previous log file if still locked + // + if(logWriterValid) { + releaseLog(); + } + + + // String oldPath = CPreferenceCache.getPrefID(TLOG_CONST.ENGINE_INSTANCE) + TLOG_CONST.JSONLOG; + String newPath = log_Filename + TLOG_CONST.JSONLOG; + // String oldDsPath = CPreferenceCache.getPrefID(TLOG_CONST.ENGINE_INSTANCE) + TLOG_CONST.DATASHOP + TLOG_CONST.JSONLOG; + String newDsPath = log_Filename + TLOG_CONST.DATASHOP + TLOG_CONST.JSONLOG; + + String state = Environment.getExternalStorageState(); + + if (Environment.MEDIA_MOUNTED.equals(state)) { + + String outPath; + String outDSPath; + + // Validate output folder + outPath = log_Path; + outDSPath = log_Path; + File outputFile = new File(outPath); + + if (!outputFile.exists()) + outputFile.mkdir(); + + // Generate a tutor instance-unique id for the log name + // + outPath += newPath; + + logFile = new File(outPath); + + try { + logStream = new FileOutputStream(logFile); + logLock = logStream.getChannel().lock(); + + if(seekable) { + seekableLogWriter = new RandomAccessFile(outPath, "rwd"); + } + else { + logWriter = new FileWriter(outPath, TLOG_CONST.APPEND); + } + + logWriterValid = true; + + // Begin the root JSON element + postPacket("{\"RT_log_version\":\"" + LOG_VERSION + "\",\"RT_log_data\":["); + + } catch (Exception e) { + Log.e(TAG, "lockLog Failed: " + e); + } + + + //**** DATASHOP + + if(loggingDS) { + + // Generate a tutor instance-unique id for DataShop + // + outDSPath += newDsPath; + + logDSFile = new File(outDSPath); + + try { + logDSStream = new FileOutputStream(logDSFile); + logDSLock = logDSStream.getChannel().lock(); + logDSWriter = new FileWriter(outDSPath, TLOG_CONST.APPEND); + + logDSWriterValid = true; + + } catch (Exception e) { + Log.e(TAG, "DataShop lockLog Failed: " + e); + } + } + } + } + + + private void releaseLog() { + + try { + if(logWriterValid) { + + if(seekable) { + logWriterValid = false; + + seekableLogWriter.close(); + } + else { + // Terminate the root JSON element + // + writePacketToLog(TERMINATING_PACKET); + + logWriterValid = false; + + logWriter.flush(); + logWriter.close(); + } + + logLock.release(); + logStream.close(); + } + } + catch(Exception e) { + Log.e(TAG, "releaseLog Failed: " + e); + } + + //**** DATASHOP + + if(loggingDS) { + try { + if (logDSWriterValid) { + + logDSWriterValid = false; + + logDSWriter.flush(); + logDSWriter.close(); + + logDSLock.release(); + logDSStream.close(); + } + } catch (Exception e) { + Log.e(TAG, "releaseLog Failed: " + e); + } + } + } + + + /** + * Note that this is currently XPrize log specific. + * TODO: make general Purpose + */ + private void writePacketToLog(String jsonPacket) { + + // Append Glyph Data to file + try { + // Throws if there is a JSON serializatin error + // + if(logWriterValid) { + + if(seekable) { + + if(seekableLogWriter.length() > TERMINATE_BYTES.length) { + seekableLogWriter.seek(seekableLogWriter.length() - TERMINATE_BYTES.length); + } + + seekableLogWriter.writeBytes(jsonPacket); + + seekableLogWriter.writeBytes(TERMINATING_PACKET); + } + else { + logWriter.write(jsonPacket); + logWriter.flush(); + } + } + } + catch(Exception e) { + Log.e(TAG, "Serialization Error: " + e); + } + } + + + /** + * Keep a mapping of pending messages so we can flush the queue if we want to terminate + * the tutor before it finishes naturally. + * + * @param qCommand + */ + private void enQueue(Queue qCommand) { + + if (!mDisabled) { + queueMap.put(qCommand, qCommand); + + logHandler.post(qCommand); + } + } + + + /** + * Post a command to this scenegraph queue + * + * @param command + */ + public void post(String command) { + + enQueue(new Queue(command)); + } + + + /** + * Post a command to this scenegraph queue + * + * @param command + */ + private void postUnencoded(String command, String target, String state) { + + enQueue(new Queue(command, target, state)); + } + + + @Override + public void postTutorState(String Tag, String Msg) { + Log.i(Tag, postEvent_BASE("TUTORSTATE", Tag, Msg)); + } + + @Override + public void postEvent_V(String Tag, String Msg) { + Log.v(Tag, postEvent_BASE("VERBOSE", Tag, Msg)); + } + @Override + public void postEvent_D(String Tag, String Msg) { + Log.d(Tag, postEvent_BASE("DEBUG", Tag, Msg)); + } + @Override + public void postEvent_I(String Tag, String Msg) { + Log.i(Tag, postEvent_BASE("INFO", Tag, Msg)); + } + @Override + public void postEvent_W(String Tag, String Msg) { + Log.w(Tag, postEvent_BASE("WARN", Tag, Msg)); + } + @Override + public void postEvent_E(String Tag, String Msg) { + Log.e(Tag, postEvent_BASE("ERROR", Tag, Msg)); + } + @Override + public void postEvent_A(String Tag, String Msg) { + Log.wtf(Tag, postEvent_BASE("ASSERT", Tag, Msg)); + } + + // Note that we leave the Msg JSON encoding to the Log thread where it can be processed off the + // UI thread. + // + private String postEvent_BASE(String classification, String Tag, String Msg) { + + String packet; + + packet = "{" + + "\"type\":\"LOG_DATA\"," + + "\"tutor\":\"" + currenttutor + "\"," + + "\"class\":\"" + classification + "\"," + + "\"tag\":\"" + Tag + "\"," + + "\"time\":\"" + System.currentTimeMillis() + "\","; + + // Note that tutor state is encoded with | object|value delimiters while + // Regular messages are delimited with : object:value delimiters. + // + switch(classification) { + case "TUTORSTATE": + postUnencoded(packet, null, Msg); + break; + case "VERBOSE": + case "DEBUG": + break; + default: + postUnencoded(packet, Msg, null); + break; + } + + return Msg; + } + + + @Override + public void postDateTimeStamp(String Tag, String Msg) { + + String packet; + + // #Mod 331 add calendar time to timestamp + // + DateFormat df = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"); + String formattedDate = df.format(Calendar.getInstance().getTime()); + + packet = "{" + + "\"class\":\"VERBOSE\"," + + "\"tag\":\"" + Tag + "\"," + + "\"type\":\"TimeStamp\"," + + "\"datetime\":\"" + formattedDate + "\"," + + "\"time\":\"" + System.currentTimeMillis() + "\","; + + postUnencoded(packet, Msg, null); + + // Emit to logcat as info class message + // + Log.i(Tag, packet + Msg); + } + + + @Override + public void postError(String Tag, String Msg) { + + String packet; + + packet = "{" + + "\"class\":\"ERROR\"," + + "\"tag\":\"" + Tag + "\"," + + "\"type\":\"Error\"," + + "\"time\":\"" + System.currentTimeMillis() + "\"," + + "\"msg\":\"" + Msg + "\"" + + "},\n"; + + post(packet); + } + + + @Override + public void postError(String Tag, String Msg, Exception e) { + + String packet; + + packet = "{" + + "\"class\":\"ERROR\"," + + "\"tag\":\"" + Tag + "\"," + + "\"type\":\"Exception\"," + + "\"time\":\"" + System.currentTimeMillis() + "\"," + + "\"msg\":\"" + Msg + "\"," + + "\"exception\":\"" + e.toString() + "\"" + + "},\n"; + + post(packet); + } + + @Override + public void postBattery(String Tag, String percent, String chargeType) { + + String packet; + + packet = "{" + + "\"class\":\"BATTERY\"," + + "\"tag\":\"" + Tag + "\"," + + "\"time\":\"" + System.currentTimeMillis() + "\"," + + "\"percent\":\"" + percent + "\"," + + "\"chargeType\":\"" + chargeType + "\"" + + "},\n"; + + post(packet); + + Log.i(Tag, packet); + } + + + @Override + public void postPacket(String packet) { + + post(packet); + } +} diff --git a/comp_logging/src/main/java/cmu/xprize/comp_logging/CPerfLogManager.java b/comp_logging/src/main/java/cmu/xprize/comp_logging/CPerfLogManager.java new file mode 100644 index 0000000..ba65565 --- /dev/null +++ b/comp_logging/src/main/java/cmu/xprize/comp_logging/CPerfLogManager.java @@ -0,0 +1,93 @@ +//********************************************************************************* +// +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved +// +// 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 cmu.xprize.comp_logging; + +import android.util.Log; + +public class CPerfLogManager extends CLogManagerBase implements IPerfLogManager { + private static String TAG = "CLogManager"; + + // Singleton + private static CPerfLogManager ourInstance = new CPerfLogManager(); + + public static CPerfLogManager getInstance() { + return ourInstance; + } + + private CPerfLogManager() { + super.TAG = TAG; + } + + private PerformanceLogItem lastEvent = new PerformanceLogItem(); + + @Override + public void postPerformanceLog(PerformanceLogItem event) { + lastEvent = event; + postEvent_I(TLOG_CONST.PERFORMANCE_TAG, event.toString()); + } + + // TODO: Super hacky. Need refactoring. + @Override + public void postPerformanceLogWithoutContext(PerformanceLogItem event) { + + if (event.getGameId() == null || event.getGameId().isEmpty()) { + event.setGameId(lastEvent.getGameId()); + } + + if (event.getTutorName() == null || event.getTutorName().isEmpty()) { + event.setTutorName(lastEvent.getTutorName()); + } + + if (event.getTutorId() == null || event.getTutorId().isEmpty()) { + event.setTutorId(lastEvent.getTutorId()); + } + + if (event.getPromotionMode() == null || event.getPromotionMode().isEmpty()) { + event.setPromotionMode(lastEvent.getPromotionMode()); + } + + if (event.getTaskName() == null || event.getTaskName().isEmpty()) { + event.setTaskName(lastEvent.getTaskName()); + } + + if (event.getLevelName() == null || event.getLevelName().isEmpty()) { + event.setLevelName(lastEvent.getLevelName()); + } + + if (event.getProblemName() == null || event.getProblemName().isEmpty()) { + event.setProblemName(lastEvent.getProblemName()); + } + + if (event.getProblemNumber() == 0) { + event.setProblemNumber(lastEvent.getProblemNumber()); + } + + if (event.getSubstepNumber() == 0) { + event.setSubstepNumber(lastEvent.getSubstepNumber()); + event.setSubstepNumber(-1); // 1=ones, 2=tens, 3=hundreds + } + + if (event.getAttemptNumber() == 0) { + event.setAttemptNumber(lastEvent.getAttemptNumber()); + } + + postEvent_I(TLOG_CONST.PERFORMANCE_TAG, event.toString()); + } +} + diff --git a/util/src/main/java/cmu/xprize/util/CPreferenceCache.java b/comp_logging/src/main/java/cmu/xprize/comp_logging/CPreferenceCache.java similarity index 92% rename from util/src/main/java/cmu/xprize/util/CPreferenceCache.java rename to comp_logging/src/main/java/cmu/xprize/comp_logging/CPreferenceCache.java index d8d2a54..0b0efeb 100644 --- a/util/src/main/java/cmu/xprize/util/CPreferenceCache.java +++ b/comp_logging/src/main/java/cmu/xprize/comp_logging/CPreferenceCache.java @@ -1,4 +1,4 @@ -package cmu.xprize.util; +package cmu.xprize.comp_logging; import android.app.Activity; import android.content.Context; @@ -22,7 +22,7 @@ static public String initLogPreference(Activity app) { context = app; idMap = new HashMap<>(); - return updateTutorGUID(TCONST.ENGINE_INSTANCE); + return updateTutorGUID(TLOG_CONST.ENGINE_INSTANCE); } @@ -46,7 +46,7 @@ static public int updateTutorInstance(String key) { // "tutor" may then be used to anonymously access the current tutors key // idMap.put(key, value); - idMap.put(TCONST.CURRENT_TUTOR, value); + idMap.put(TLOG_CONST.CURRENT_TUTOR, value); editor.putInt(key, ordinal + 1); editor.apply(); @@ -75,7 +75,7 @@ static public String updateTutorGUID(String key) { // "tutor" may then be used to anonymously access the current tutors key // idMap.put(key, value); - idMap.put(TCONST.CURRENT_TUTOR, value); + idMap.put(TLOG_CONST.CURRENT_TUTOR, value); editor.putString(key, GUID); editor.apply(); @@ -102,7 +102,7 @@ static private String getGUID() { // Find a unique GUID that doesn't exist in the preferences cache // do { - for (int i1 = 0; i1 < TCONST.GUID_LEN; i1++) { + for (int i1 = 0; i1 < TLOG_CONST.GUID_LEN; i1++) { try { guid += guidMap.charAt((int) (Math.random() * guidMap.length())); } diff --git a/comp_logging/src/main/java/cmu/xprize/comp_logging/ILogManager.java b/comp_logging/src/main/java/cmu/xprize/comp_logging/ILogManager.java new file mode 100644 index 0000000..d5e4e73 --- /dev/null +++ b/comp_logging/src/main/java/cmu/xprize/comp_logging/ILogManager.java @@ -0,0 +1,41 @@ +package cmu.xprize.comp_logging; + +public interface ILogManager { + + /** + * Transfer logs from one path to another. Separation of hot logs from ready logs prevents + * RoboTransfer from transferring a log while it is being written. + * + * @param hotPath + * @param readyPath + */ + public void transferHotLogs(String hotPath, String readyPath); + + public void startLogging(String logPath, String logFileName); + public void stopLogging(); + + public void postTutorState(String Tag, String Msg); + + public void postEvent_V(String Tag, String Msg); + + public void postEvent_D(String Tag, String Msg); + + public void postEvent_I(String Tag, String Msg); + + public void postEvent_W(String Tag, String Msg); + + public void postEvent_E(String Tag, String Msg); + + public void postEvent_A(String Tag, String Msg); + + public void postDateTimeStamp(String Tag, String Msg); + + public void post(String command); + + public void postError(String Tag, String Msg); + public void postError(String Tag, String Msg, Exception e); + + public void postBattery(String Tag, String percent, String chargeType); + + public void postPacket(String packet); +} diff --git a/comp_logging/src/main/java/cmu/xprize/comp_logging/IPerfLogManager.java b/comp_logging/src/main/java/cmu/xprize/comp_logging/IPerfLogManager.java new file mode 100644 index 0000000..e055540 --- /dev/null +++ b/comp_logging/src/main/java/cmu/xprize/comp_logging/IPerfLogManager.java @@ -0,0 +1,8 @@ +package cmu.xprize.comp_logging; + +public interface IPerfLogManager extends ILogManager { + + public void postPerformanceLog(PerformanceLogItem event); + + public void postPerformanceLogWithoutContext(PerformanceLogItem event); +} diff --git a/comp_logging/src/main/java/cmu/xprize/comp_logging/ITutorLogger.java b/comp_logging/src/main/java/cmu/xprize/comp_logging/ITutorLogger.java new file mode 100644 index 0000000..1b5d87d --- /dev/null +++ b/comp_logging/src/main/java/cmu/xprize/comp_logging/ITutorLogger.java @@ -0,0 +1,7 @@ +package cmu.xprize.comp_logging; + +public interface ITutorLogger { + + public void logState(String logData); + +} diff --git a/comp_logging/src/main/java/cmu/xprize/comp_logging/InterventionLogItem.java b/comp_logging/src/main/java/cmu/xprize/comp_logging/InterventionLogItem.java new file mode 100644 index 0000000..01fc690 --- /dev/null +++ b/comp_logging/src/main/java/cmu/xprize/comp_logging/InterventionLogItem.java @@ -0,0 +1,41 @@ +package cmu.xprize.comp_logging; + +import com.google.gson.Gson; + +import org.json.JSONObject; + +/** + * RoboTutor + *

+ * Created by kevindeland on 2019-10-25. + */ +public class InterventionLogItem { + + long timestamp; + String student; + String group; + String tutor; + String recommendStudent; + String errorType; + Boolean helpTaken; + + public InterventionLogItem( + long timestamp, String student, String group, + String tutor, String recommendStudent, + String errorType, Boolean helpTaken) { + this.timestamp = timestamp; + this.student = student; + this.group = group; + this.tutor = tutor; + this.recommendStudent = recommendStudent; + this.errorType = errorType; + this.helpTaken = helpTaken; + } + + @Override + public String toString() { + Gson gson = new Gson(); + + return gson.toJson(this); + } +} diff --git a/comp_logging/src/main/java/cmu/xprize/comp_logging/PerformanceLogItem.java b/comp_logging/src/main/java/cmu/xprize/comp_logging/PerformanceLogItem.java new file mode 100644 index 0000000..2bda026 --- /dev/null +++ b/comp_logging/src/main/java/cmu/xprize/comp_logging/PerformanceLogItem.java @@ -0,0 +1,489 @@ +package cmu.xprize.comp_logging; + +import java.lang.reflect.Field; + +import static cmu.xprize.comp_logging.PerformanceLogItem.MATRIX_TYPE.LITERACY_MATRIX; +import static cmu.xprize.comp_logging.PerformanceLogItem.MATRIX_TYPE.MATH_MATRIX; +import static cmu.xprize.comp_logging.PerformanceLogItem.MATRIX_TYPE.SONGS_MATRIX; +import static cmu.xprize.comp_logging.PerformanceLogItem.MATRIX_TYPE.STORIES_MATRIX; +import static cmu.xprize.comp_logging.PerformanceLogItem.MATRIX_TYPE.UNKNOWN_MATRIX; + +/** + * Created by kevindeland on 9/13/17. + */ + +public class PerformanceLogItem { + + private long timestamp; + private String userId; + private String sessionId; + private String gameId; + private String language; + private String tutorName; + private String tutorId; + private String problemName; + private int problemNumber; + private String levelName; + private int totalProblemsCount; + + private String matrixName; // could be literacy or math or whatever + + public final static class MATRIX_TYPE { + public final static String MATH_MATRIX = "math"; + public final static String LITERACY_MATRIX = "literacy"; + public final static String STORIES_MATRIX = "stories"; + public final static String UNKNOWN_MATRIX = "unknown"; + public final static String SONGS_MATRIX = "songs"; + } + + + private int totalSubsteps; + private int substepNumber; + private int substepProblem; + private int attemptNumber; + private String taskName; + + private String expectedAnswer; + private String userResponse; + private String correctness; + + private String distractors; + private String scaffolding; + private String promptType; + private String feedbackType; + + private String promotionMode; // placement or promotion + + private final static String DEBUG_TAG = "DEBUG_PERFORMANCE_LOG"; + + // + // iterative way to print fields in the desired order + private static final String[] orderedFieldsToPrint = {"timestamp", "userId", "sessionId", "gameId", "language", "tutorName", "tutorId", "matrixName", "levelName", "taskName", + "problemName", "problemNumber", "substepNumber", "substepProblem", "attemptNumber", "expectedAnswer", "userResponse", "correctness", "feedbackType", "totalProblemsCount", "promotionMode", "scaffolding"}; + + public PerformanceLogItem() { + } + + @Override + public String toString() { + StringBuilder msg = new StringBuilder(); + + for (String fieldName : orderedFieldsToPrint) { + try { + Field field = this.getClass().getDeclaredField(fieldName); + if(field != null) { + msg.append(fieldName); + msg.append(": "); + msg.append(field.get(this)); + msg.append(", "); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + + } + + String result = msg.toString(); + return result.substring(0, result.length() - 2); // don't forget to slice off that last comma + } + + + /** + * sets matrix based on which tutor.... + * TODO move into new class + * TODO even better, just pass the "skills" variable + * @param tutorId + */ + private void setMatrixNameByTutorId(String tutorId) { + + if (isAlwaysMathTutor(tutorId)) { + matrixName = MATH_MATRIX; + } else if (isAkiraTutor(tutorId)) { + setMatrixAkira(tutorId); + } else if (isBpopTutor(tutorId)) { + setMatrixBpop(tutorId); + } else if (isWriteTutor(tutorId)) { + setMatrixWrite(tutorId); + } else if (isStoryTutor(tutorId)) { + setMatrixStory(tutorId); + } else if (isAlwaysLitMatrix(tutorId)) { + matrixName = LITERACY_MATRIX; + } else { + matrixName = UNKNOWN_MATRIX; + } + + } + + /** + * set matrix name if it's a Story tutor + * @param tutorName + */ + private void setMatrixStory(String tutorName) { + if (tutorName.contains("A..Z") + || tutorName.contains("wrd") + || tutorName.contains("syl") + || tutorName.contains("vow") + || tutorName.contains("abcdefg_EnglishTune_LowerCase") + || tutorName.contains("ABCDEFG_EnglishTune_UpperCase") + || tutorName.contains("LC_Vowel_Song_1") + || tutorName.contains("LC_Vowel_Song_2") + || tutorName.contains("UC_Vowel_Song_1") + || tutorName.contains("UC_Vowel_Song_2") + || tutorName.contains("letters_alphabet_song") + || tutorName.contains("comm") + || tutorName.contains("ltr") + || tutorName.contains("syl") + || tutorName.contains("nafasi") + || tutorName.contains("herufi_kubwa") + || tutorName.contains("kituo")){ + matrixName = LITERACY_MATRIX; + + } else if (tutorName.contains("1..4") + || tutorName.contains("0..10") + || tutorName.contains("0..20") + || tutorName.contains("0..50") + || tutorName.contains("0..100") + || tutorName.contains("10..100") + || tutorName.contains("50..100") + || tutorName.contains("100..900") + || tutorName.contains("100..1000") + || tutorName.contains("Counting_Fingers_Toes") + || tutorName.contains("Number_Song_2") + || tutorName.contains("numbers_counting_song") + || tutorName.contains("word_problem")) { + matrixName = MATH_MATRIX; + + } else if (tutorName.contains("Garden_Song") + || tutorName.contains("Safari_Song") + || tutorName.contains("School_Welcome_Song") + || tutorName.contains("Kusoma_Welcome_Song")) { + matrixName = SONGS_MATRIX; + + } else if (tutorName.contains("story_")) { + + if (tutorName.startsWith("story.echo") + || tutorName.startsWith("story.parrot") + || tutorName.startsWith("story.read")) { + matrixName = LITERACY_MATRIX; + + } else if (tutorName.startsWith("story.hear") + || tutorName.startsWith("story.clo.hear") + || tutorName.startsWith("story.pic.hear") + || tutorName.startsWith("story.gen.hear")) { + matrixName = STORIES_MATRIX; + + } else { + matrixName = UNKNOWN_MATRIX; + } + + } else { + matrixName = UNKNOWN_MATRIX; + } + } + + /** + * set matrix name if it's a Write tutor + * @param tutorName + */ + private void setMatrixWrite(String tutorName) { + + if (tutorName.contains("ltr") + || tutorName.contains("missingLtr") + || tutorName.contains("wrd")) { + matrixName = LITERACY_MATRIX; + } else if (tutorName.contains("arith") + || tutorName.contains("num")){ + matrixName = MATH_MATRIX; + } else { + matrixName = UNKNOWN_MATRIX; + } + } + + /** + * set matrix name if it's a BubblePop tutor + * @param tutorName + */ + private void setMatrixBpop(String tutorName) { + + if (tutorName.contains("ltr") + || tutorName.contains("wrd") + || tutorName.contains("syl")) { + matrixName = LITERACY_MATRIX; + } + + else if (tutorName.contains("mn") + || tutorName.contains("gl") + || tutorName.contains("addsub") + || tutorName.contains("num")) { + matrixName = MATH_MATRIX; + } else { + matrixName = UNKNOWN_MATRIX; + } + + } + + /** + * set matrix name if it's an Akira tutor + * @param tutorName + */ + private void setMatrixAkira(String tutorName) { + if (tutorName.contains("ltr") + || tutorName.contains("wrd") + || tutorName.contains("syl")) { + matrixName = LITERACY_MATRIX; + } else { + matrixName = MATH_MATRIX; + } + } + + + // + // for checking tutor type based on name + // + private boolean isStoryTutor(String tutorName) { + return tutorName.startsWith("story"); + } + + private boolean isWriteTutor(String tutorName) { + return tutorName.startsWith("write"); + } + + private boolean isBpopTutor(String tutorName) { + return tutorName.startsWith("bpop"); + } + + private boolean isAlwaysLitMatrix(String tutorName) { + return tutorName.startsWith("spelling") + || tutorName.startsWith("picmatch"); + } + + private boolean isAkiraTutor(String tutorName) { + return tutorName.startsWith("akira"); + } + + private boolean isAlwaysMathTutor(String tutorName) { + return tutorName.startsWith("num.scale") + || tutorName.startsWith("math") + || tutorName.startsWith("countingx") + || tutorName.startsWith("place.value") + || tutorName.startsWith("placevalue") + || tutorName.startsWith("bigmath") + || tutorName.startsWith("numcompare"); + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public String getGameId() { + return gameId; + } + + public void setGameId(String gameId) { + this.gameId = gameId; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getTutorName() { + return tutorName; + } + + public void setTutorName(String tutorName) { + + this.tutorName = tutorName; + } + + public String getTutorId() { + return tutorId; + } + + public void setTutorId(String tutorId) { + if(tutorId == null) { + return; + } + + this.tutorId = tutorId.replaceAll(":", "_"); + + + if(tutorId != null) { + setMatrixNameByTutorId(tutorId); + } + } + + public void setMatrixNameBySkillId(String skill) { + if (skill == null) { + return; + } + switch (skill) { + + case "letters": + matrixName = LITERACY_MATRIX; + break; + + case "numbers": + matrixName = MATH_MATRIX; + break; + + case "stories": + matrixName = STORIES_MATRIX; + break; + } + } + + public String getPromotionMode() { + return promotionMode; + } + + public void setPromotionMode(String promotionMode) { + this.promotionMode = promotionMode; + } + + public String getMatrixName() { + return matrixName; + } + + public String getProblemName() { + return problemName; + } + + public void setProblemName(String problemName) { + this.problemName = problemName; + } + + public int getProblemNumber() { + return problemNumber; + } + + public void setProblemNumber(int problemNumber) { + this.problemNumber = problemNumber; + } + + public int getTotalProblemsCount() { return totalProblemsCount; } + + public void setTotalProblemsCount(int totalProblemsCount) { this.totalProblemsCount = totalProblemsCount; } + + public int getTotalSubsteps() { return totalSubsteps; } + + public void setTotalSubsteps(int totalSubsteps) { + this.totalSubsteps = totalSubsteps; + } + + public int getSubstepNumber() { + return substepNumber; + } + + public void setSubstepNumber(int substepNumber) { + this.substepNumber = substepNumber; + } + + public int getSubstepProblem() { + return substepProblem; + } + + public void setSubstepProblem(int substepProblem) { + this.substepProblem = substepProblem; + } + + public int getAttemptNumber() { + return attemptNumber; + } + + public void setAttemptNumber(int attemptNumber) { + this.attemptNumber = attemptNumber; + } + + public String getExpectedAnswer() { + return expectedAnswer; + } + + public void setExpectedAnswer(String expectedAnswer) { + this.expectedAnswer = expectedAnswer; + } + + public String getUserResponse() { + return userResponse; + } + + public void setUserResponse(String userResponse) { + this.userResponse = userResponse; + } + + public String getCorrectness() { + return correctness; + } + + public void setCorrectness(String correctness) { + this.correctness = correctness; + } + + public String getScaffolding() { + return scaffolding; + } + + public void setScaffolding(String scaffolding) { + this.scaffolding = scaffolding; + } + + public String getPromptType() { + return promptType; + } + + public void setPromptType(String promptType) { + this.promptType = promptType; + } + + public String getFeedbackType() { + return feedbackType; + } + + public void setFeedbackType(String feedbackType) { + this.feedbackType = feedbackType; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public String getDistractors() { + return distractors; + } + + public void setDistractors(String distractors) { + this.distractors = distractors; + } + + public void setLevelName(String levelName) { + this.levelName = levelName; + } + + public String getLevelName() { return levelName; } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + public String getTaskName() { return taskName; } +} diff --git a/comp_logging/src/main/java/cmu/xprize/comp_logging/TLOG_CONST.java b/comp_logging/src/main/java/cmu/xprize/comp_logging/TLOG_CONST.java new file mode 100644 index 0000000..817217f --- /dev/null +++ b/comp_logging/src/main/java/cmu/xprize/comp_logging/TLOG_CONST.java @@ -0,0 +1,21 @@ +package cmu.xprize.comp_logging; + + +public class TLOG_CONST { + + // Preference keys + public static final String ENGINE_INSTANCE = "RoboTutor"; + public static final String CURRENT_TUTOR = "tutor"; + + public static final String GLYPHLOG = "glyphlog_"; + public static final String DATASHOP = "-DS"; + public static final String JSONLOG = ".json"; + + public static final boolean APPEND = true; + public static final boolean REPLACE = false; + + public static final int GUID_LEN = 5; + public static final String GUID_UPDATE = "GUIDUPDATE"; + + public static final String PERFORMANCE_TAG = "PERFORMANCE_TAG"; +} diff --git a/comp_logging/src/main/res/values/strings.xml b/comp_logging/src/main/res/values/strings.xml new file mode 100644 index 0000000..82292cd --- /dev/null +++ b/comp_logging/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + comp_logging + diff --git a/comp_logging/src/test/java/cmu/xprize/comp_logging/ExampleUnitTest.java b/comp_logging/src/test/java/cmu/xprize/comp_logging/ExampleUnitTest.java new file mode 100644 index 0000000..71a9ad8 --- /dev/null +++ b/comp_logging/src/test/java/cmu/xprize/comp_logging/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package cmu.xprize.comp_logging; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/comp_pointtap/build.gradle b/comp_pointtap/build.gradle index b546b2b..5441133 100644 --- a/comp_pointtap/build.gradle +++ b/comp_pointtap/build.gradle @@ -11,10 +11,58 @@ android { versionName rootProject.ext.rtVersionName } + def keystorePropertiesFile = rootProject.file("keystore.properties") + def keystoreProperties = new Properties() + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + + signingConfigs { + android { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + } + buildTypes { + + debug { + buildConfigField "String", "WIFI_CONFIG", "\"DEBUG\"" + debuggable true + } + release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + + xprize { + buildConfigField "String", "WIFI_CONFIG", "\"XPRIZE\"" + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + + local { + buildConfigField "String", "WIFI_CONFIG", "\"LOCAL\"" + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + + vmc { + buildConfigField "String", "WIFI_CONFIG", "\"VMC\"" + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android } } diff --git a/comp_pointtap/src/main/java/cmu/xprize/comp_pointtap/CHandAnimation.java b/comp_pointtap/src/main/java/cmu/xprize/comp_pointtap/CHandAnimation.java index 9a0a1ac..a33e50d 100644 --- a/comp_pointtap/src/main/java/cmu/xprize/comp_pointtap/CHandAnimation.java +++ b/comp_pointtap/src/main/java/cmu/xprize/comp_pointtap/CHandAnimation.java @@ -44,7 +44,7 @@ import java.util.Map; import cmu.xprize.util.CAnimatorUtil; -import cmu.xprize.util.CErrorManager; +import cmu.xprize.comp_logging.CErrorManager; import cmu.xprize.util.TCONST; public class CHandAnimation extends PercentRelativeLayout implements Animator.AnimatorListener { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fc9b829..766ee8c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Dec 28 10:00:20 PST 2015 +#Wed May 12 03:26:56 IST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/settings.gradle b/settings.gradle index 8b0c146..5e48e6b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':util', ':comp_pointtap' +include ':app', ':util', ':comp_pointtap', ':comp_configuration', ':comp_logging' diff --git a/util/build.gradle b/util/build.gradle index 1ff83b6..658eeb1 100644 --- a/util/build.gradle +++ b/util/build.gradle @@ -11,10 +11,66 @@ android { versionName rootProject.ext.rtVersionName } + def keystorePropertiesFile = rootProject.file("keystore.properties") + def keystoreProperties = new Properties() + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + + signingConfigs { + android { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + } + buildTypes { + def BOOLEAN = "boolean" + def STRING = "String" + def TRUE = "true" + def FALSE = "false" + + def LANGUAGE_FEATURE_ID = "LANGUAGE_FEATURE_ID" + def LANGUAGE_ENGLISH = "\"LANG_EN\"" + def LANGUAGE_SWAHILI = "\"LANG_SW\"" + + debug { + buildConfigField "String", "WIFI_CONFIG", "\"DEBUG\"" + debuggable true + } + release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + + xprize { + buildConfigField "String", "WIFI_CONFIG", "\"XPRIZE\"" + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + + local { + buildConfigField "String", "WIFI_CONFIG", "\"LOCAL\"" + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android + } + + vmc { + buildConfigField "String", "WIFI_CONFIG", "\"VMC\"" + + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + signingConfig signingConfigs.android } } @@ -26,13 +82,18 @@ android { // if true, only report errors ignoreWarnings true } - buildToolsVersion '25.0.0' + + testOptions { + unitTests.returnDefaultValues = true + } } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:25.2.0' - compile 'com.android.support:percent:25.2.0' + api fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.12' + testImplementation 'org.json:json:20140107' + implementation 'com.android.support:appcompat-v7:25.2.0' + api 'com.android.support:percent:25.2.0' compile files('libs/datashop-logging.jar') + api project(':comp_logging') } diff --git a/util/src/main/java/cmu/xprize/util/CAnimatorUtil.java b/util/src/main/java/cmu/xprize/util/CAnimatorUtil.java index a8b6587..02eb808 100644 --- a/util/src/main/java/cmu/xprize/util/CAnimatorUtil.java +++ b/util/src/main/java/cmu/xprize/util/CAnimatorUtil.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -31,6 +30,8 @@ import android.view.animation.BounceInterpolator; import android.view.animation.LinearInterpolator; import android.view.animation.OvershootInterpolator; +import android.util.Log; +import java.util.Arrays; import java.util.ArrayList; @@ -49,9 +50,7 @@ static private Animator createFloatAnimator(View _tarView, String prop, long dur static private Animator createFloatAnimator(View _tarView, String prop, long duration, int repeat, int mode, TimeInterpolator interpolator, long delay, float... endPts) { ValueAnimator vAnimator = null; - vAnimator = ObjectAnimator.ofFloat(_tarView, prop, endPts).setDuration(duration); - vAnimator.setInterpolator(interpolator); vAnimator.setRepeatCount(repeat); @@ -236,7 +235,6 @@ static public AnimatorSet configStretch(View _tarView, String direction, long du static public AnimatorSet configZoomIn(View _tarView, long duration, long delay, TimeInterpolator interpolator, float... absScales) { ArrayList zoomAnimators = new ArrayList(); - AnimatorSet animation = new AnimatorSet(); zoomAnimators.add(createFloatAnimator(_tarView, "scaleX", duration, 0, 0, interpolator, delay, absScales)); @@ -260,7 +258,6 @@ static public AnimatorSet configTranslate(View _tarView, long duration, long del } AnimatorSet animation = new AnimatorSet(); - moveAnimators.add(createFloatAnimator(_tarView, "x", duration, 0, 0, new LinearInterpolator(), delay, wayPointsX)); moveAnimators.add(createFloatAnimator(_tarView, "y", duration, 0, 0, new LinearInterpolator(), delay, wayPointsY)); diff --git a/util/src/main/java/cmu/xprize/util/CAt_Data.java b/util/src/main/java/cmu/xprize/util/CAt_Data.java new file mode 100644 index 0000000..60cf457 --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/CAt_Data.java @@ -0,0 +1,59 @@ +//********************************************************************************* +// +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved +// +// 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 cmu.xprize.util; + +import org.json.JSONObject; + +import cmu.xprize.util.CClassMap; +import cmu.xprize.util.ILoadableObject; +import cmu.xprize.util.IScope; +import cmu.xprize.util.JSON_Helper; + +public class CAt_Data implements ILoadableObject { + + public int gridIndex; + public int row; + public int col; + + // json loadable + public String skill; + public String tutor_id; + public String tutor_desc; + public String tutor_data; + public String cell_row; + public String cell_column; + public String same; + public String next; + public String harder; + public String easier; + + + //************ Serialization + + + @Override + public void loadJSON(JSONObject jsonObj, IScope scope) { + + JSON_Helper.parseSelf(jsonObj, this, CClassMap.classMap, scope); + + row = Integer.parseInt(cell_row); + col = Integer.parseInt(cell_column); + } + +} diff --git a/util/src/main/java/cmu/xprize/util/CDisplayMetrics.java b/util/src/main/java/cmu/xprize/util/CDisplayMetrics.java new file mode 100644 index 0000000..dc94372 --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/CDisplayMetrics.java @@ -0,0 +1,50 @@ +/** + Copyright(c) 2015-2017 Kevin Willows + 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 cmu.xprize.util; + +import android.app.Activity; +import android.util.DisplayMetrics; + +public class CDisplayMetrics { + + private static final CDisplayMetrics ourInstance = new CDisplayMetrics(); + + static public float designDensity = 2.0f; + + static public float instanceHeight; + static public float instanceWidth; + static public float instanceDensity; + static public float densityRescale; + + public static CDisplayMetrics getInstance(Activity _activity) { + + // get the multiplier used for drawables at the current screen density and calc the + // correction rescale factor for design scale + // + DisplayMetrics displayMetrics = new DisplayMetrics(); + _activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + + instanceHeight = displayMetrics.heightPixels; + instanceWidth = displayMetrics.widthPixels; + instanceDensity = displayMetrics.density; + densityRescale = designDensity / instanceDensity; + + return ourInstance; + } + + public static CDisplayMetrics getInstance() { + return ourInstance; + } + +} diff --git a/util/src/main/java/cmu/xprize/util/CErrorDialog.java b/util/src/main/java/cmu/xprize/util/CErrorDialog.java index 1429643..dd95737 100644 --- a/util/src/main/java/cmu/xprize/util/CErrorDialog.java +++ b/util/src/main/java/cmu/xprize/util/CErrorDialog.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,12 +22,15 @@ import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; +import android.util.Log; import android.view.View; import android.view.Window; import android.widget.TextView; import cmu.xprize.common.R; +import static cmu.xprize.util.TCONST.QGRAPH_MSG; + public class CErrorDialog implements View.OnClickListener { private final Dialog dialog; @@ -62,6 +64,8 @@ public Boolean isShowing() { @Override public void onClick(View v) { + Log.v(QGRAPH_MSG, "event.click: " + " CErrorDialog:exit"); + System.exit(1); } } diff --git a/util/src/main/java/cmu/xprize/util/CEvent.java b/util/src/main/java/cmu/xprize/util/CEvent.java index f932e88..1c7edcc 100644 --- a/util/src/main/java/cmu/xprize/util/CEvent.java +++ b/util/src/main/java/cmu/xprize/util/CEvent.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/util/src/main/java/cmu/xprize/util/CEventMap.java b/util/src/main/java/cmu/xprize/util/CEventMap.java deleted file mode 100644 index 23f6b0c..0000000 --- a/util/src/main/java/cmu/xprize/util/CEventMap.java +++ /dev/null @@ -1,26 +0,0 @@ -package cmu.xprize.util; - -import java.util.HashMap; - -import cmu.xprize.util.TCONST; - -public class CEventMap { - - static public HashMap eventMap = new HashMap(); - - // - // This is used to map "type" (class names) used in json HashMap specs to real classes - - static { - eventMap.put("SILENCE_EVENT", TCONST.SILENCE_EVENT); - eventMap.put("SOUND_EVENT", TCONST.SOUND_EVENT); - eventMap.put("WORD_EVENT", TCONST.WORD_EVENT); - eventMap.put("SILENCE_TIMEOUT", TCONST.TIMEDSILENCE_EVENT); - eventMap.put("SOUND_TIMEOUT", TCONST.TIMEDSOUND_EVENT); - eventMap.put("WORD_TIMEOUT", TCONST.TIMEDWORD_EVENT); - eventMap.put("START_TIMEOUT", TCONST.TIMEDSTART_EVENT); - eventMap.put("ALL_TIMED_EVENTS",TCONST.ALLTIMED_EVENTS); - eventMap.put("ALL_STATIC_EVENTS",TCONST.ALL_EVENTS); - eventMap.put("ALL_EVENTS",TCONST.ALL_EVENTS); - } -} diff --git a/util/src/main/java/cmu/xprize/util/CFileNameHasher.java b/util/src/main/java/cmu/xprize/util/CFileNameHasher.java index 36d777a..be2f2cc 100644 --- a/util/src/main/java/cmu/xprize/util/CFileNameHasher.java +++ b/util/src/main/java/cmu/xprize/util/CFileNameHasher.java @@ -1,9 +1,14 @@ package cmu.xprize.util; +import android.util.Log; + import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import static cmu.xprize.util.TCONST.GRAPH_MSG; +import static cmu.xprize.util.TCONST.QGRAPH_MSG; + /** * Created by kevin on 11/7/2016. */ @@ -30,13 +35,11 @@ private CFileNameHasher() { public String generateHash(String filename) { String hashName; + String prunedName; - System.out.println("filename :" + filename); - - hashName = filename.replaceAll("[^a-zA-Z0-9]", "").toLowerCase(); - System.out.println("prunedname :" + hashName); + prunedName = filename.replaceAll("[^a-zA-Z0-9]", "").toLowerCase(); - byte[] nameBytes = hashName.getBytes( Charset.forName("UTF-8" )); + byte[] nameBytes = prunedName.getBytes( Charset.forName("UTF-8" )); nameBytes = md.digest(nameBytes); @@ -45,7 +48,7 @@ public String generateHash(String filename) { sb.append(Integer.toString((nameBytes[i] & 0xff) + 0x100, 16).substring(1)); } - System.out.println("hashname :" + sb.toString()); + Log.v(GRAPH_MSG, "target:CFileNameHasher,action:generatehash,filename:" + filename + " prunedname:" + prunedName + " hashname:" + sb.toString()); return sb.toString(); } diff --git a/util/src/main/java/cmu/xprize/util/CLoaderView.java b/util/src/main/java/cmu/xprize/util/CLoaderView.java index 092235a..4aa9132 100644 --- a/util/src/main/java/cmu/xprize/util/CLoaderView.java +++ b/util/src/main/java/cmu/xprize/util/CLoaderView.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,12 +18,17 @@ package cmu.xprize.util; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.support.v4.content.LocalBroadcastManager; import android.util.AttributeSet; -import android.view.View; +import android.util.Log; import android.view.ViewGroup; -import android.widget.FrameLayout; import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; import cmu.xprize.common.R; @@ -33,6 +37,17 @@ public class CLoaderView extends LinearLayout { private ViewGroup viewParent = null; + private LocalBroadcastManager bManager; + private LoadReceiver bReceiver; + + private TextView SprogressTitle; + private TextView SprogressMsg1; + private TextView SprogressMsg2; + + private ProgressBar SprogressBarI; + private ProgressBar SprogressBarD; + + public CLoaderView(Context context, ViewGroup parent) { super(context); viewParent = parent; @@ -56,7 +71,105 @@ public CLoaderView(Context context, AttributeSet attrs, int defStyleAttr) { public void init(Context context, AttributeSet attrs) { + + // Capture the local broadcast manager + bManager = LocalBroadcastManager.getInstance(getContext()); + + IntentFilter filter = new IntentFilter(TCONST.START_PROGRESSIVE_UPDATE); + filter.addAction(TCONST.UPDATE_PROGRESS); + filter.addAction(TCONST.PROGRESS_TITLE); + filter.addAction(TCONST.PROGRESS_MSG1); + filter.addAction(TCONST.PROGRESS_MSG2); + + bReceiver = new LoadReceiver(); + + bManager.registerReceiver(bReceiver, filter); + } + + + @Override + protected void onFinishInflate() { + + super.onFinishInflate(); + + SprogressTitle = (TextView) findViewById(R.id.SprogressTitle); + SprogressMsg1 = (TextView) findViewById(R.id.SprogressMsg1); + SprogressMsg2 = (TextView) findViewById(R.id.SprogressMsg2); + + SprogressBarI = (ProgressBar) findViewById(R.id.SprogressBarI); + SprogressBarD = (ProgressBar) findViewById(R.id.SprogressBarD); + } + + + /** + * Release resources and disconnect from broadcast Manager + */ + public void onDestroy() { + + try { + setOnClickListener(null); + bManager.unregisterReceiver(bReceiver); + } + catch(Exception e) { + } } + class LoadReceiver extends BroadcastReceiver { + + public void onReceive (Context context, Intent intent) { + + Log.d("Loader", "Broadcast received: "); + + switch(intent.getAction()) { + + case TCONST.START_INDETERMINATE_UPDATE: + SprogressBarI.setVisibility(VISIBLE); + SprogressBarD.setVisibility(GONE); + requestLayout(); + break; + + case TCONST.START_PROGRESSIVE_UPDATE: + SprogressBarD.setVisibility(VISIBLE); + SprogressBarI.setVisibility(GONE); + + String maxVal = intent.getStringExtra(TCONST.TEXT_FIELD); + + SprogressBarD.setProgress(0); + SprogressBarD.setMax(Integer.parseInt(maxVal)); + requestLayout(); + break; + + case TCONST.UPDATE_PROGRESS: + String curVal = intent.getStringExtra(TCONST.TEXT_FIELD); + + SprogressBarD.setProgress(Integer.parseInt(curVal)); + SprogressBarD.postInvalidate(); + break; + + case TCONST.PROGRESS_TITLE: + String titleText = intent.getStringExtra(TCONST.TEXT_FIELD); + + SprogressTitle.setText(titleText); + SprogressTitle.setVisibility(VISIBLE); + break; + + case TCONST.PROGRESS_MSG1: + String msgText1 = intent.getStringExtra(TCONST.TEXT_FIELD); + + SprogressMsg1.setText(msgText1); + SprogressMsg1.setVisibility(VISIBLE); + break; + + case TCONST.PROGRESS_MSG2: + String msgText2 = intent.getStringExtra(TCONST.TEXT_FIELD); + + SprogressMsg2.setText(msgText2); + SprogressMsg2.setVisibility(VISIBLE); + break; + + } + } + } + } diff --git a/util/src/main/java/cmu/xprize/util/CLogManager.java b/util/src/main/java/cmu/xprize/util/CLogManager.java deleted file mode 100644 index c9fa7a7..0000000 --- a/util/src/main/java/cmu/xprize/util/CLogManager.java +++ /dev/null @@ -1,462 +0,0 @@ -//********************************************************************************* -// -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved -// -// 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 cmu.xprize.util; - -import android.os.Environment; -import android.os.Handler; -import android.os.HandlerThread; -import android.util.Log; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.util.HashMap; - -public class CLogManager implements ILogManager { - - private LogThread logThread; // background thread handling log data - private String log_Path; - private boolean isLogging = false; - - private Handler logHandler; - private HashMap queueMap = new HashMap(); - private boolean mDisabled = false; - - private File logFile; - private FileOutputStream logStream; - private java.nio.channels.FileLock logLock; - private FileWriter logWriter; - private boolean logWriterValid = false; - - // Datashop specific - - private File logDSFile; - private FileOutputStream logDSStream; - private java.nio.channels.FileLock logDSLock; - private FileWriter logDSWriter; - private boolean logDSWriterValid = false; - - - final private String TAG = "CLogManager"; - - - // Singleton - private static CLogManager ourInstance = new CLogManager(); - - public static CLogManager getInstance() { - return ourInstance; - } - - private CLogManager() { - } - - - public void startLogging(String logPath) { - - log_Path = logPath; - - // Restart the log if necessary - // - stopLogging(); - - isLogging = true; - mDisabled = false; - Log.d(TAG, "Startup"); - - logThread = new LogThread(TAG); - logThread.start(); - - try { - logHandler = new Handler(logThread.getLooper()); - } - catch(Exception e) { - Log.d(TAG, "Handler Create Failed:" + e); - } - - lockLog(); - } - - - /** - * Stop accepting new packets - - * Causes the thread to flush the input queue and then exit - * - */ - public void stopLogging() { - - if(isLogging) { - Log.d(TAG, "Shutdown begun"); - - isLogging = false; - mDisabled = true; - - // Terminate the log thread - flush the queue prior to exit - // - try { - - logThread.getLooper().quitSafely(); - - logThread.join(); // waits until it finishes - Log.d(TAG, "Shutdown complete"); - - } catch (InterruptedException e) { - } - - releaseLog(); - } - } - - - /** - * This is a background thread on which to process all log data requests - * - */ - private final class LogThread extends HandlerThread { - - public LogThread(String name) { - super(name); - } - - public LogThread(String name, int priority) { - super(name, priority); - } - } - - - /** - * This is the central processsing point of the data log - this runs on an independent thread - * from the UI. - */ - public class Queue implements Runnable { - - protected String dataPacket; - protected String target = CPreferenceCache.getPrefID(TCONST.ENGINE_INSTANCE) + TCONST.JSONLOG; - - public Queue(String packet) { - dataPacket = packet; - } - - public Queue(String _packet, String _target) { - dataPacket = _packet; - target = _target; - } - - @Override - public void run() { - - try { - queueMap.remove(this); - - writePacketToLog(dataPacket, target); - - } catch (Exception e) { - CErrorManager.logEvent(TAG, "Write Error:", e, false); - } - } - } - - - /** - * We use file locks to keep the logs around until we are finished. The XPrize initiative used a - * Google Drive Sync utility that required locking the files so they weren't deleted while in - * use. So this is not a requirement otherwise. - * - */ - public void lockLog() { - - // Release previous log file if still locked - // - if(logWriterValid) { - releaseLog(); - } - - String path = CPreferenceCache.getPrefID(TCONST.ENGINE_INSTANCE) + TCONST.JSONLOG; - String dsPath = CPreferenceCache.getPrefID(TCONST.ENGINE_INSTANCE) + TCONST.DATASHOP + TCONST.JSONLOG; - - String state = Environment.getExternalStorageState(); - - if (Environment.MEDIA_MOUNTED.equals(state)) { - - String outPath; - String outDSPath; - - // Validate output folder - outPath = log_Path; - outDSPath = log_Path; - File outputFile = new File(outPath); - - if (!outputFile.exists()) - outputFile.mkdir(); - - // Generate a tutor instance-unique id for the log name - // - outPath += path; - - logFile = new File(outPath); - - try { - logStream = new FileOutputStream(logFile); - logLock = logStream.getChannel().lock(); - logWriter = new FileWriter(outPath, TCONST.APPEND); - - logWriterValid = true; - - // Begin the root JSON element - postPacket("{"); - - } catch (Exception e) { - Log.d(TAG, "lockLog Failed: " + e); - } - - - //**** DATASHOP - - // Generate a tutor instance-unique id for DataShop - // - outDSPath += dsPath; - - logDSFile = new File(outDSPath); - - try { - logDSStream = new FileOutputStream(logDSFile); - logDSLock = logDSStream.getChannel().lock(); - logDSWriter = new FileWriter(outDSPath, TCONST.APPEND); - - logDSWriterValid = true; - - } catch (Exception e) { - Log.d(TAG, "DataShop lockLog Failed: " + e); - } - - } - } - - - public void releaseLog() { - - try { - if(logWriterValid) { - - // Terminate the root JSON element - postPacket("}"); - - logWriterValid = false; - - logWriter.flush(); - logWriter.close(); - - logLock.release(); - logStream.close(); - } - } - catch(Exception e) { - Log.d(TAG, "releaseLog Failed: " + e); - } - - //**** DATASHOP - - try { - if(logDSWriterValid) { - - logDSWriterValid = false; - - logDSWriter.flush(); - logDSWriter.close(); - - logDSLock.release(); - logDSStream.close(); - } - } - catch(Exception e) { - Log.d(TAG, "releaseLog Failed: " + e); - } - - } - - - - /** - * Note that this is currently XPrize log specific. - * TODO: make general Purpose - */ - public void writePacketToLog(String jsonPacket, String path) { - - // Append Glyph Data to file - try { - // Throws if there is a JSON serializatin error - // - if(logWriterValid) { - logWriter.write(jsonPacket); - logWriter.flush(); - } - } - catch(Exception e) { - Log.e(TAG, "Serialization Error: " + e); - } - } - - - /** - * Keep a mapping of pending messages so we can flush the queue if we want to terminate - * the tutor before it finishes naturally. - * - * @param qCommand - */ - private void enQueue(Queue qCommand) { - - if (!mDisabled) { - queueMap.put(qCommand, qCommand); - - logHandler.post(qCommand); - } - } - - /** - * Post a command to this scenegraph queue - * - * @param command - */ - public void post(String command) { - - enQueue(new Queue(command)); - } - - - /** - * Post a command to this scenegraph queue - * - * @param command - */ - public void postTo(String command, String target) { - - enQueue(new Queue(command, target)); - } - - - @Override - public void postSystemEvent(String Tag, String Msg) { - - String packet; - - // Emit to LogCat - // - Log.i(TAG, Msg); - - packet = "{" + - "\"type\":\"Event\"," + - "\"time\":\"" + System.currentTimeMillis() + "\"," + - "\"tag\":\"" + Tag + "\"," + - "\"msg\":\"" + Msg + "\"," + - "},\n"; - - postTo(packet, TCONST.ENGINE_INSTANCE + TCONST.JSONLOG); - } - - - @Override - public void postSystemTimeStamp(String Tag) { - - String packet; - - packet = "{" + - "\"type\":\"TimeStamp\"," + - "\"time\":\"" + System.currentTimeMillis() + "\"," + - "\"tag\":\"" + Tag + "\"," + - "},\n"; - - postTo(packet, TCONST.ENGINE_INSTANCE + TCONST.JSONLOG); - } - - - @Override - public void postEvent(String Tag, String Msg) { - - String packet; - - // Emit to LogCat - // - Log.i(TAG, Msg); - - packet = "{" + - "\"type\":\"Event\"," + - "\"time\":\"" + System.currentTimeMillis() + "\"," + - "\"tag\":\"" + Tag + "\"," + - "\"msg\":\"" + Msg + "\"," + - "},\n"; - - post(packet); - } - - - @Override - public void postTimeStamp(String Tag) { - - String packet; - - packet = "{" + - "\"type\":\"TimeStamp\"," + - "\"time\":\"" + System.currentTimeMillis() + "\"," + - "\"tag\":\"" + Tag + "\"," + - "},\n"; - - post(packet); - } - - - @Override - public void postError(String Tag, String Msg) { - - String packet; - - packet = "{" + - "\"type\":\"Error\"," + - "\"time\":\"" + System.currentTimeMillis() + "\"," + - "\"tag\":\"" + Tag + "\"," + - "\"msg\":\"" + Msg + "\"," + - "},\n"; - - post(packet); - } - - - @Override - public void postError(String Tag, String Msg, Exception e) { - - String packet; - - packet = "{" + - "\"type\":\"Error\"," + - "\"time\":\"" + System.currentTimeMillis() + "\"," + - "\"tag\":\"" + Tag + "\"," + - "\"msg\":\"" + Msg + "\"," + - "\"exception\":\"" + e.toString() + "\"," + - "},\n"; - - post(packet); - } - - - @Override - public void postPacket(String packet) { - - post(packet); - } - -} diff --git a/util/src/main/java/cmu/xprize/util/CMessageQueueFactory.java b/util/src/main/java/cmu/xprize/util/CMessageQueueFactory.java new file mode 100644 index 0000000..a841252 --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/CMessageQueueFactory.java @@ -0,0 +1,237 @@ +package cmu.xprize.util; + +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import cmu.xprize.comp_logging.CErrorManager; + +/** + * CMessageQueue + * + *

Why does every single CComponent have a different Queue? That's dumb. + * Let's just put them all in the same class.

+ * Created by kevindeland on 8/27/19. + */ + +public class CMessageQueueFactory { + + private IMessageQueueRunner runner; + private String TAG; + + public CMessageQueueFactory(IMessageQueueRunner runner, String TAG) { + this.runner = runner; + this.TAG = TAG; + } + + + //************************************************************************ + //************************************************************************ + // Component Message Queue -- Start + + private final Handler mainHandler = new Handler(Looper.getMainLooper()); + private HashMap queueMap = new HashMap(); + private HashMap nameMap = new HashMap(); + private boolean _qDisabled = false; + + public class Queue implements Runnable { + + protected String _command; + String _name; // used to find a command and cancel it. + Object _targetObject; + String _targetString; + + public Queue(String command) { + _command = command; + } + + // needed for cancelling + public Queue (String name, String command) { + this._name = name; + this._command = command; + + if (name != null) { + nameMap.put(name, this); + } + } + + public Queue(String command, Object target) { + _command = command; + _targetObject = target; + } + + public Queue(String name, String command, String target) { + _name = name; + _command = command; + _targetString = target; + } + + public String getCommand() { + return _command; + } + + + @Override + public void run() { + + try { + + if (_name != null) { + nameMap.remove(_name); + } + + queueMap.remove(this); + Log.d("QUEUE_FACTORY", + String.format( + "CMessageQueue.run _name=%s;_command=%s;_targetObject=%s;_targetString=%s", + _name, _command, _targetObject, _targetString)); + + // ugh this is somewhat different for each file (see QueueConstructorVars.txt) + // could refactor + if(_targetObject != null) { + Log.wtf("QUEUE_FACTORY", "runCommand(object)"); + runner.runCommand(_command, _targetObject); + } else if(_targetString != null) { + runner.runCommand(_command, _targetString); + Log.wtf("QUEUE_FACTORY", "runCommand(string)"); + } else { + runner.runCommand(_command); + Log.wtf("QUEUE_FACTORY", String.format("runCommand(%s)", _command)); + } + + } + catch(Exception e) { + CErrorManager.logEvent(TAG, "Run Error: cmd:" + _command + " tar: " + _targetObject + " >", e, false); + } + } + } + + + /** + * Disable the input queues permenantly in prep for destruction + * walks the queue chain to diaable scene queue + * + */ + public void terminateQueue() { + + // disable the input queue permenantly in prep for destruction + // + _qDisabled = true; + flushQueue(); + } + + + /** + * Remove any pending scenegraph commands. + * + */ + private void flushQueue() { + + Iterator tObjects = queueMap.entrySet().iterator(); + + while(tObjects.hasNext() ) { + Map.Entry entry = (Map.Entry) tObjects.next(); + + Log.d(TAG, "Post Cancelled on Flush: " + ((Queue)entry.getValue()).getCommand()); + + mainHandler.removeCallbacks((Queue)(entry.getValue())); + } + } + + /** + * Remove named posts + * + */ + public void cancelPost(String name) { + + Log.d(TAG, "Cancel Post Requested: " + name); + + while(nameMap.containsKey(name)) { + + Log.d(TAG, "Post Cancelled: " + name); + + mainHandler.removeCallbacks((Queue) (nameMap.get(name))); + nameMap.remove(name); // JUDITH replicate + } + } + + public void postEvent(String event) { + postEvent(event, 0); + } + + public void postEvent(String event, Integer delay) { + + post(event, (long) delay); + } + + public void postNamed(String name, String command, String target, Long delay) { + enQueue(new Queue(name, command, target), delay); + } + + /** + * Keep a mapping of pending messages so we can flush the queue if we want to terminate + * the tutor before it finishes naturally. + * + * @param qCommand + */ + private void enQueue(Queue qCommand) { + enQueue(qCommand, 0); + } + private void enQueue(Queue qCommand, long delay) { + + if(!_qDisabled) { + queueMap.put(qCommand, qCommand); + + if(delay > 0) { + mainHandler.postDelayed(qCommand, delay); + } + else { + mainHandler.post(qCommand); + } + } + } + + /** + * Post a command to the tutorgraph queue + * + * @param command + */ + public void post(String command) { + post(command, 0); + } + public void post(String command, long delay) { + + enQueue(new Queue(command), delay); + } + + + public void postNamed(String name, String command, Long delay) { + enQueue(new Queue(name, command), delay); + } + + /** + * Post a command and target to this scenegraph queue + * + * @param command + */ + public void post(String command, Object target) { + post(command, target, 0); + } + public void post(String command, Object target, long delay) { + + enQueue(new Queue(command, target), delay); + } + + + // Component Message Queue -- End + //************************************************************************ + //************************************************************************ + +} + + + diff --git a/util/src/main/java/cmu/xprize/util/CPlacementTest_Tutor.java b/util/src/main/java/cmu/xprize/util/CPlacementTest_Tutor.java new file mode 100644 index 0000000..bda761d --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/CPlacementTest_Tutor.java @@ -0,0 +1,35 @@ +package cmu.xprize.util; + +import org.json.JSONObject; + +/** + * RoboTutor + *

+ * Created by kevindeland on 4/12/18. + */ + +public class CPlacementTest_Tutor implements ILoadableObject { + + + public int l; + + // json loadable + public String tutor; + public String level; + public String fail; + public String pass; + + + //************ Serialization + + + @Override + public void loadJSON(JSONObject jsonObj, IScope scope) { + + JSON_Helper.parseSelf(jsonObj, this, CClassMap.classMap, scope); + + l = Integer.parseInt(level); + + } + +} diff --git a/util/src/main/java/cmu/xprize/util/CTutorData_Metadata.java b/util/src/main/java/cmu/xprize/util/CTutorData_Metadata.java new file mode 100644 index 0000000..94ebb8e --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/CTutorData_Metadata.java @@ -0,0 +1,1111 @@ +package cmu.xprize.util; + +import android.content.Context; +import android.text.style.LocaleSpan; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Locale; + +import cmu.xprize.common.R; + +/** + * RoboTutor + *

+ * Created by kevindeland on 5/18/18. + */ + +public class CTutorData_Metadata { + + /** + * NEW_THUMBS why can't this return a string instead? + * @param tutor + * @return + */ + public static TCONST.Thumb getThumbImage(CAt_Data tutor) { + + + Log.d("CHUNT", "tutor_desc = " + tutor.tutor_desc); + // tutortype is first token... e.g. "story.hear" --> "story" + String[] tutorDesc = tutor.tutor_desc.split("\\."); + if (tutorDesc.length == 0) { + return TCONST.Thumb.NOTHING; + } + Log.d("CHUNT", "tutorDesc = " + tutorDesc + ", " + tutorDesc.length); + + + + String tutorType = tutorDesc[0]; + Log.d("CHUNT", "tutorType = " + tutorType); + + switch (tutorType) { + case "akira": + return TCONST.Thumb.AKIRA; + + case "bpop": + switch (tutor.tutor_desc) { + case "bpop.num": + case "bpop.addsub": + return TCONST.Thumb.BPOP_NUM; + + case "bpop.gl": + return TCONST.Thumb.GL; + + case "bpop.mn": + return TCONST.Thumb.MN; + + // bpop.ltr + default: + return TCONST.Thumb.BPOP_LTR; + } + + case "countingx": + + String startingNumber = tutor.tutor_id.split("[:_]")[1]; + + switch (startingNumber) { + case "1": + return TCONST.Thumb.CX_1; + + case "10": + return TCONST.Thumb.CX_10; + + case "100": + return TCONST.Thumb.CX_100; + + default: + return TCONST.Thumb.CX_1; + } + + + case "math": + // only one + return TCONST.Thumb.MATH; + + case "numberscale": + // only one + return TCONST.Thumb.NUMSCALE; + + + case "story": + + String storyPrefix = tutor.tutor_id.split(":")[2]; + // story or song + if(storyPrefix.toLowerCase().contains("song") || storyPrefix.toLowerCase().contains("tune")) { + return TCONST.Thumb.SONG; + } + else if(storyPrefix.startsWith("story")){ + String storyNum = storyPrefix.split("_")[1]; + int storyNumInt = Integer.parseInt(storyNum); + + switch (storyNumInt % 5) { + case 0: + return TCONST.Thumb.STORY_5; + + case 1: + return TCONST.Thumb.STORY_1; + + case 2: + return TCONST.Thumb.STORY_2; + + case 3: + return TCONST.Thumb.STORY_3; + + case 4: + return TCONST.Thumb.STORY_4; + } + + } else { + return TCONST.Thumb.STORY_NONSTORY; + } + + + case "write": + // only one... for now + return TCONST.Thumb.WRITE; + + + // Added Tutors Code Drop 2 + case "picmatch": + return TCONST.Thumb.PICMATCH; + + case "placevalue": + case "place": + return TCONST.Thumb.PLACEVALUE; + + case "numcompare": + return TCONST.Thumb.NUMCOMPARE; + + case "spelling": + return TCONST.Thumb.SPELLING; + + case "bigmath": + return TCONST.Thumb.BIGMATH; + + + + default: + return TCONST.Thumb.NOTHING; + + } + + } + + /** + * Return filename to a thumb image. + * + * @param tutor + * @return + */ + public static String getThumbName(CAt_Data tutor) { + // NEW_THUMBS (if returns null... then use the "getThumbImage" method") + + // check story + if (tutor.tutor_desc.startsWith("story")) { + + StringBuilder builder = new StringBuilder("thumb_"); + String suffix = null; + + switch(tutor.tutor_desc) { + + case "story.hear": + suffix = "_hear"; + break; + + case "story.echo": + case "story.read": + case "story.hide": + case "story.reveal": + case "story.parrot": + suffix = "_read"; + break; + + case "story.gen.hide": + case "story.clo.hear": + case "story.pic.hear": + case "story.gen.hear": + suffix = "_comp"; + break; + } + + String storyName = tutor.tutor_id.split("::")[1]; + builder.append(storyName) + .append(suffix) + .append(".png"); + + return builder.toString(); + } + + + switch(tutor.tutor_desc) { + case "numcompare": + return "thumb_number_discrimination.png"; + + case "picmatch": + return "thumb_picture_matching.png"; + + case "place.value": + case "placevalue": + return "thumb_place_value.png"; + + case "spelling": + return "thumb_spelling_tutor.png"; + + case "write.sen.corr.ltr": + case "write.sen.corr.wrd": + case "write.sen.corr.sen": + case "write.sen.copy.ltr": + case "write.sen.copy.wrd": + case "write.sen.copy.sen": + return "thumb_sentence_writing.png"; + + case "bigmath": + return "thumb_bigmath_1d.png"; + + } + + return null; + } + + /** + * Given a tutor, return a list of Strings that describes the tutor. + * @param tutor + * @return + */ + public static ArrayList parseNameIntoLabels(CAt_Data tutor) { + + // OPEN_SOURCE FIXME + // write.ltr.uc.trc:A..D_asc √√√ + // bpop.ltr.lc:A..D.asc.show.mc --> "null A to Z" + // bpop.wrd:m2M.noShow.rise --> nothing + // bpop.wrd:dolch_preprimer.noShow.mc --> nothing + // + // write.wrd:phon.m2M √√√ + + Log.d("CHUNT", "tutor_desc = " + tutor.tutor_desc); + // tutortype is first token... e.g. "story.hear" --> "story" + String[] tutorDesc = tutor.tutor_desc.split("\\."); + if (tutorDesc.length == 0) { + return null; + } + Log.d("CHUNT", "tutorDesc = " + tutorDesc + ", " + tutorDesc.length); + + int[] tutor_CONST; + + + String tutorType = tutorDesc[0]; + Log.d("CHUNT", "tutorType = " + tutorType); + + + ArrayList result = new ArrayList<>(); + + try { + + + switch (tutorType) { + case "akira": + result.add("Akira"); + + result = processAkiraTutorId(tutor, result); + break; + + case "bpop": + result = processBubblePopTutorId(tutor, result); + + break; + + case "countingx": + result.add("Tap to Count"); + String[] countingSplit = tutor.tutor_id.split("[:_]"); + result.add(String.format("Tap to count from %s to %s.", countingSplit[1], countingSplit[2])); + break; + + + case "math": + result.add("Math"); + String[] splitMe = tutor.tutor_id.split(":"); + String[] secondPart = splitMe[1].split("\\."); + + String[] thirdPart = secondPart[3].split("-"); + + StringBuilder descriptor = new StringBuilder(); + descriptor.append(thirdPart[0].equals("SUB") ? "Subtract " : "Add "); + descriptor.append(String.format("values between %s and %s", secondPart[0], secondPart[2])); + result.add(descriptor.toString()); + break; + + case "bigmath": + result.add("Math"); + break; + + case "place": + case "place.value": + result.add("Place Value"); + break; + + case "numcompare": + result.add("Number Comparison"); + break; + + case "picmatch": + result.add("Picture Match"); + break; + + case "spelling": + result.add("Spelling"); + break; + + case "numberscale": + result.add("Number Scale"); + String numscaleSuffix = tutor.tutor_id.split(":")[1]; + String[] numscaleDetails = numscaleSuffix.split("\\."); + String numscaleOffset = numscaleDetails[3].substring("off".length()); + result.add(String.format("Explore numbers %s to %s, counting by %s.", numscaleDetails[0], numscaleDetails[2], numscaleOffset)); + break; + + + case "story": + result.add("Story"); + result = processStoryTutorId(tutor, result); + break; + + + case "write": + result.add("Write"); + result = processWriteTutorId(tutor, result); + break; + + + default: + break; + } + }catch (Exception e) { + // result.add("Error generating name"); + // commenting out (for now) so that it doesn't show the error + } + + // give it some empty lines + for (int i = result.size(); i < 4; i++) { + result.add(""); // add blanks til we get to the end + } + result.add("" + tutor.tutor_id + ""); // add ID for each one + + return result; + } + + + private static ArrayList processStoryTutorId(CAt_Data tutor, ArrayList result) { + + // story.hear::1_1 --> "Error generating name" + // story.[hear|echo|read]::\d_\d --> "Error generating name" + + String[] splitStory = tutor.tutor_id.split(":"); + String storySuffix = splitStory[2]; + + if (storySuffix.split("[0-9]")[0].equals("")) { // if begins with number + // it's a number story + String[] storyNumberSuffix = storySuffix.split("[._]"); + + //System.out.println(Arrays.toString(splitStory)); + String[] storyMode = splitStory[0].split("\\."); + String storyModeCap = storyMode[1].substring(0, 1).toUpperCase() + storyMode[1].substring(1); + //System.out.println(Arrays.toString(storyMode)); + + result.add(String.format(Locale.US, "%s numbers from %d to %d", + storyModeCap, Integer.parseInt(storyNumberSuffix[0]), Integer.parseInt(storyNumberSuffix[2]))); + + } else { + + String[] storySuffixSplit = storySuffix.split("[_\\.\\-]"); + String syl1, syl2, letter, wordType; + StringBuilder descriptionBuilder = new StringBuilder(); + + boolean isLitStory = true; + + String storyType = tutor.tutor_desc.split("\\.")[1]; + descriptionBuilder.append(storyType.substring(0,1).toUpperCase() + storyType.substring(1) + " "); // capitalize first letter + + //System.out.println(Arrays.toString(storySuffixSplit)); + switch(storySuffixSplit[0]) { + + case "ltr": + letter = storySuffixSplit[1]; // "ltr-A.rand" + descriptionBuilder.append(String.format("the letter %s", letter)); + break; + + case "vow": + descriptionBuilder.append(String.format("all vowels")); + break; + + case "all": + descriptionBuilder.append(String.format("all letters")); + break; + + case "syl": + syl1 = storySuffixSplit[1]; + syl2 = storySuffixSplit[3]; + descriptionBuilder.append(String.format("the syllables %s through %s", syl1, syl2)); + break; + + case "begin": + syl1 = storySuffixSplit[2]; + descriptionBuilder.append(String.format("words beginning with %s", syl1)); + break; + + case "end": + syl1 = storySuffixSplit[2]; + descriptionBuilder.append(String.format("words ending with %s", syl1)); + break; + + case "comm": + String wordCategory = storySuffixSplit[3]; + if (wordCategory.equals("body")) wordCategory = "the " + wordCategory; + descriptionBuilder.append(String.format("common words about %s", wordCategory)); + break; + + case "HF": + descriptionBuilder.append(String.format(Locale.US, "common %d-letter words", Integer.parseInt(storySuffixSplit[3]))); + break; + + case "nonwrd": + descriptionBuilder.append(String.format(Locale.US, "%d-letter nonsense words", Integer.parseInt(storySuffixSplit[2]))); + break; + + default: + + isLitStory = false; + // it's a real story... + String storyName = storySuffix.split("__")[0]; // this removes the iteration at the end + result.add(storyName.replace("_", " ")); // "Kusoma_Song" --> "Kusoma Song" + break; + } + + if(isLitStory) { + //System.out.println(descriptionBuilder.toString()); + result.add(descriptionBuilder.toString()); + } + + + } + + return result; + } + + private static ArrayList processWriteTutorId(CAt_Data tutor, ArrayList result) { + // write.wrd.dic:phon.r2R √√√ + // write.wrd.trc:phon.m2M √√√ + // write.wrd:dolch_preprimer √√√ + // write.wrd:dolch_1st_grade √√√ + + // write.ltr.uc.trc:A..D_asc √√√ + + String splitDesc[] = tutor.tutor_desc.split("\\."); + String modeOfEntry = "Write"; + // check for trace + if(splitDesc.length > 2) { + switch(splitDesc[2]) { + + case "trc": + modeOfEntry = "Trace"; + break; + + case "dic": + default: + // do nothing + break; + } + } + + String splitSuffix[] = tutor.tutor_id.split(":")[1].split("[\\-\\.]"); + String str; + switch(splitDesc[1]) { + + // letters + // write.ltr.uc.trc:vow.asc.A..Z.1 + // write.ltr.lc:all.asc.A..Z.11 + case "ltr": + String caps; + switch(splitDesc[2]) { + case "uc": + caps = "uppercase"; + break; + case "lc": + caps = "lowercase"; + break; + default: + caps = null; + } + + result.add(String.format("%s %s letters", modeOfEntry, caps)); + + String vowels = null, order; + switch(splitSuffix[0]) { + case "vow": + vowels = "Vowels"; + break; + + case "all": + vowels = "All Letters"; + break; + + default: + break; + + } + + // THIS is a mess... fix + if (vowels == null) { + // write.ltr.uc:A..D_asc + order = splitSuffix[2].substring(2); + if (order.equals("asc")) order = "ascending"; + + result.add(String.format("Letters %s through %s, %s", splitSuffix[0], splitSuffix[2].substring(0, 1), order)); + } else { + switch(splitSuffix[1]) { + case "asc": + order = "Ascending"; + break; + + case "rand": + default: + order = "Random"; + + } + result.add(String.format("%s, %s", vowels, order)); + } + + break; + + // words + // write.wrd.trc:syl.2ch..2ch..1 + // write.wrd:syl.2ch..2ch..2 + // write.wrd:lc.wrd.cat-food.1 + // write.wrd:lc.wrd.cat-body.4 + // write.wrd:lc.wrd.len-4.1 + // write.wrd:lc.wrd.len-10 + // write.wrd:lc.nonwrd.len-4.lev1 + case "wrd": + + String wordToWrite = null; + + //System.out.println(Arrays.toString(splitSuffix)); + if(splitSuffix[0].equals("syl")) { + String syl = splitSuffix[1]; + wordToWrite = String.format("the syllable %s", syl); + } else if (splitSuffix[0].equals("phon")) { + String[] g2p = splitSuffix[1].split("2"); + wordToWrite = " words with g2p: " + g2p[0] + " to " + g2p[1] + ""; + + } else if (splitSuffix[1].startsWith("dolch")) { + wordToWrite = splitSuffix[1].substring(6) + " grade Dolch words"; + } else if (splitSuffix[1].equals("wrd")) { + + if(splitSuffix[2].equals("cat")) { + wordToWrite = String.format("common words about %s", splitSuffix[3]); + } else if (splitSuffix[2].equals("len")) { + wordToWrite = String.format(Locale.US, "common %d-letter words", Integer.parseInt(splitSuffix[3])); + } + + } else if (splitSuffix[1].equals("nonwrd")) { + + wordToWrite = String.format(Locale.US, "%d-letter nonsense words", Integer.parseInt(splitSuffix[3])); + } + + result.add(String.format("%s %s", modeOfEntry, wordToWrite)); + break; + + // missing letter + // write.missingLtr:lc.begin.ha.1 + // write.missingLtr:lc.end.ko.9 + // "write.missingLtr:0.1.5.fin.s" + case "missingLtr": + + modeOfEntry = "Complete"; + String wordToComplete = null;; + System.out.println(Arrays.toString(splitSuffix)); + if(splitSuffix[1].equals("begin")) { + wordToComplete = String.format(" the word beginning with %s", splitSuffix[2]); + } else if (splitSuffix[1].equals("end")) { + wordToComplete = String.format(" the word ending with %s", splitSuffix[2]); + } else { + wordToComplete = String.format(" the word containing %s", splitSuffix[3]); + } + result.add(String.format("%s %s", modeOfEntry, wordToComplete)); + break; + + // numbers + case "num": + + String numberToWrite = "the number"; + result.add(String.format("%s %s", modeOfEntry, numberToWrite)); + break; + + // arith + case "arith": + + String answerToWrite = "the equation"; + result.add(String.format("%s %s", modeOfEntry, answerToWrite)); + break; + + + case "dolch": + + str = "Write " + splitSuffix[2] + " grade Dolch words"; + result.add(str); + break; + + + case "phon": + + + + + + default: + } + + + // MATH + // write.num:WR-100s.8 + // write.num:WR-3D.9 + // write.num:WR-2D.6 + // write.num.trc:WR-1..4.1" + // write.num.trc:WR-1..10.2 + // write.num.dic:WR-1..20.13 + // write.num.dic:WR-2D.15 + + // write.arith:ADD-2D-H.3 -- write.arith.add + // write.arith:SUB-2D-H.4 -- write.arith.sub + // write.arith:ADD-3D-H + + + + return result; + } + + + private static ArrayList processAkiraTutorId(CAt_Data tutor, ArrayList result) { + + // + // LIT + // "akira:vow.ltr.lc:I..I.vow.10.rand.say.8" + // "akira:all.ltr.uc:CH..CH.all.10.rand.say.17" + // akira:syl.lc.say.nye..nye.noShow..19 + // akira:syl.lc.say.ku..ku.noShow..9 + // akira:begin.wrd.wa.show.8 + // akira:end.wrd.wa.show.15 + // akira:comm.wrd.body.noShow.4 + // akira:HF.wrd.7.noShow.4 + // akira:HF.wrd.4.noShow.1 + // akira:nonwrd.8.noShow.3 + // akira:comm.wrd.people.noShow.3 + + // MATH + // "akira:0..9.by.1.asc + // "akira:0..9.by.1.des" + // akira:10..100.by.10.asc + // akira:100..1000.by.100.asc + // "akira:100..1000.by.within.asc + + String suffix = tutor.tutor_id.substring("akira:".length()); + String[] splits = suffix.split("[:\\.\\-_]"); + + + StringBuilder identifyString = new StringBuilder("Identify "); + String str = null; + //System.out.println(Arrays.toString(splits)); + switch(tutor.skill) { + case "numbers": + + String num1 = splits[0]; + String num2 = splits[2]; + str = String.format(Locale.US, "numbers %d through %d", Integer.parseInt(num1), Integer.parseInt(num2)); + break; + + + case "letters": + + switch(splits[0]) { + case "vow": + if(splits[3].equals(splits[5])) { + str = String.format("the vowel %s", splits[3]); + } else { + str = String.format("all vowels"); + } + break; + + case "all": + if (splits[3].equals(splits[5])) { + str = String.format("the letter %s", splits[3]); + } else { + str = String.format("letters from A to Z"); + } + + break; + + case "syl": + str = String.format("the syllable %s", splits[3]); + break; + + + case "begin": + str = String.format("words beginning with %s", splits[2]); + break; + + case "end": + str = String.format("words ending with %s", splits[2]); + break; + + case "comm": + String wordCategory = splits[2]; + if (wordCategory.equals("body")) wordCategory = "the " + wordCategory; + str = String.format("common words about %s", wordCategory); + break; + + case "HF": + str = String.format(Locale.US, "common %d-letter words", Integer.parseInt(splits[2])); + break; + + + case "nonwrd": + str = String.format(Locale.US, "%d-letter nonsense words", Integer.parseInt(splits[1])); + break; + + // OPEN_SOURCE new English options... + // akira:wrd.a2AE √√√ + // akira:wrd.th2TH √√√ + // akira:wrd.dolch_preprimer √√√ + // akira:wrd.dolch_2nd_grade √√√ + case "wrd": + if (splits[1].startsWith("dolch")) { + str = splits[2] + " grade Dolch words"; + } else if (splits[1].contains("2")) { + String[] g2p = splits[1].split("2"); + str = " g2p mapping: " + g2p[0] + " to " + g2p[1] + ""; + } + + break; + + // akira:ltr.lc_A..D_rand + // akira:ltr.lc_E..G_rand --> "Identify null" + case "ltr": + //str = String.format() + switch(splits[1]) { + case "lc": + str = "lowercase"; + break; + case "uc": + str = "uppercase"; + } + str += " letters " + splits[2] + " to " + splits[4]; + break; + } + break; + } + + identifyString.append(str); + result.add(identifyString.toString()); + + return result; + } + + /** + * Specifically for processing Bubble Pop. + * @param tutor + * @param result + * @return + */ + private static ArrayList processBubblePopTutorId(CAt_Data tutor, ArrayList result) { + + // new cases for English Version: + + // OPEN_SOURCE Bpop metadata errors + // letters + + // change to bpop.ltr.lc:A..D.[all|vow].[asc|rand].... [show|noShow] should be in index 7 + + // phon words + // bpop.wrd:u2AH.show.mc --> Fail on first row + // bpop.wrd:oo2UH.show.mc + // change to bpop.wrd:phon.oo2UH.show.mc + + // common words + // bpop.wrd:dolch_preprimer.show.mc + // bpop.wrd:dolch_preprimer.noShow.mc + // bpop.wrd:dolch_1st_grade.show.rise + result.add("Bubble Pop"); + + + String suffix = tutor.tutor_id.split(":")[1]; + String[] suffixSplit = suffix.split("[_//.//-]"); + + + // global + String showWord; + // lit vars + String itemType, caps, wrdType, ltrOrder; + boolean lc = false; + // math vars + String startRange, endRange, numOrder, addSub, digits, av, orientation, mc, translate; + switch(tutor.tutor_desc) { + + case "bpop.ltr.lc": + lc = true; + case "bpop.ltr.uc": + if (!lc) { + caps = "uppercase"; + } else { + caps = "lowercase"; + } + + String ltrName; + if (suffixSplit[0].equals(suffixSplit[2])) { + ltrName = suffixSplit[0]; + if (lc) ltrName = ltrName.toLowerCase(); + } else { + ltrName = "letters"; // all letters + } + result.add(String.format("Identify the %s %s", caps, ltrName)); + + ltrOrder = suffixSplit[4]; + String ltrOrderText; + switch(ltrOrder) { + case "asc": + ltrOrderText = "Ascending"; + break; + + case "rand": + ltrOrderText = "Random"; + break; + + default: + ltrOrderText = null; + } + + String ltrType = suffixSplit[3]; + String ltrTypeText; + switch (ltrType) { + case "vow": + ltrTypeText = "vowels"; + break; + + case "all": + default: + ltrTypeText = "A to Z"; + break; + } + + if (ltrOrderText != null) { + result.add(String.format("%s %s", ltrOrderText, ltrTypeText)); + showWord = suffixSplit[7]; // wrong index? + } else { + // bpop.ltr.uc:A..D.asc.noShow.rise --> "null A to Z" + // bpop.ltr.lc:A..D.asc.show.rise --> "null A to Z" + result.add(String.format("Letters %s to %s", suffixSplit[0], suffixSplit[2])); + + showWord = suffixSplit[4]; + } + + + switch(showWord) { + case "show": + result.add("Audio plus visual stimuli"); + break; + + case "noShow": + result.add("Audio stimulus only"); + default: + break; + } + + + + break; + + case "bpop.wrd": + + // "bpop.wrd:2ch..2ch.syl.rand.all.stat.show.47__it_1", + // "bpop.wrd:cha..cha.syl.rand.sim.stat.show.52" + // "bpop.wrd:wrd.4.lc.rand.stat.show.1" + // "bpop.wrd:wrd.4.lc.rand.stat.noShow.2" + // "bpop.wrd:nonwrd.4.lc.rand.stat.show.1" + // "bpop.wrd:nonwrd.4.lc.rand.stat.noShow.1" + // "bpop.wrd:begin.wrd.ha.noShow.2" + // "bpop.wrd:begin.wrd.ki.noShow.2" + // "bpop.wrd:wrd.food.lc.rand.stat.show.1" + // "bpop.wrd:wrd.animals.lc.rand.stat.noShow.4" + // "bpop.wrd:wrd.people.lc.rand.stat.noShow.6" + + showWord = null; + + boolean syl = suffixSplit[3].equals("syl"); // OPEN_SOURCE e2EH.show.rise not enough + // check if syllable + if(syl) { + if (suffixSplit[0].substring(1).equals("ch")) { + String numCharsInSyllable = suffixSplit[0].substring(0, 1); // 3ch --> 3 + result.add(String.format("Identify syllables with %s characters", numCharsInSyllable)); + } else { + String syllable = suffixSplit[0]; + result.add(String.format("Practice identifying the syllable %s", syllable)); + } + showWord = suffixSplit[7]; + + } else { + switch(suffixSplit[0]) { + case "wrd": + try { + // can be either a number, or a category + int numChars = Integer.parseInt(suffixSplit[1]); + result.add(String.format("Identify words with %s letters", numChars)); + } catch (NumberFormatException e) { + result.add(String.format("Identify words about %s", suffixSplit[1])); + } + showWord = suffixSplit[5]; + break; + + case "begin": + case "end": + String suffixOrPrefix = suffixSplit[2]; + result.add(String.format("Identify words that %s with %s", suffixSplit[0], suffixOrPrefix)); + showWord = suffixSplit[3]; + break; + + case "nonwrd": + result.add(String.format("Identify nonsense words with %s letters", suffixSplit[1])); + showWord = suffixSplit[5]; + break; + + case "phon": + + result.add(String.format("Identify words that map grapheme %s", "GGG")); + result.add(String.format("to phoneme %s", "PPP")); + break; + } + + //System.out.println(Arrays.toString(suffixSplit)); + + } + + + switch(showWord) { + case "show": + result.add("Audio plus visual stimuli"); + break; + + case "noShow": + result.add("Audio stimulus only"); + default: + break; + } + + + break; + + case "bpop.num": + + // bpop.num:1..4.by.1.asc.q2q.AV.mc.1 + + startRange = suffixSplit[0]; + endRange = suffixSplit[2]; + String offset = suffixSplit[4]; + numOrder = suffixSplit[5]; + translate = suffixSplit[6]; + av = suffixSplit[7]; + mc = suffixSplit[8]; + + String translateText = null; + switch (translate) { + case "q2q": + translateText = "quantities to quantities"; + break; + + case "s2s": + translateText = "numerals to numerals"; + break; + + case "q2s": + translateText = "quantities to numerals"; + break; + + case "s2q": + translateText = "numerals to quantities"; + break; + + case "x2s": + translateText = "audio to numerals"; + break; + } + result.add(String.format("Identify numbers: %s", translateText)); + + result.add(String.format("Range: %s to %s", startRange, endRange)); + + String mcText; + switch(mc) { + + case "rise": + mcText = "Rising"; + break; + + case "mc": + default: + mcText = "Static"; + break; + } + result.add(String.format("%s bubbles", mcText)); + + break; + + case "bpop.addsub": + + // bpop.addsub:0..10. + startRange = suffixSplit[0]; + // empty string in [1] + endRange = suffixSplit[2]; + numOrder = suffixSplit[3]; // incr, decr, rand + String orderTxt; + + addSub = suffixSplit[4].equals("SUB") ? "subtraction" : "addition"; + digits = suffixSplit[5]; // [123]D or number + + orientation = suffixSplit[6].equals("V") ? "Vertical" : "Horizontal"; + + result.add(String.format("%s %s", orientation, addSub)); + + boolean incr = false; + switch(numOrder) { + + case "incr": + incr = true; + case "decr": + orderTxt = numOrder + "ementing"; + String start = incr ? startRange : endRange; + String end = incr ? endRange : startRange; + result.add(String.format("Numbers %s from %s to %s, by %s", + orderTxt, start, end, digits)); + break; + + case "rand": + orderTxt = "random"; + digits = digits.substring(0, digits.indexOf("D")); + result.add(String.format("Random %s-digit numbers between %s and %s", + digits, startRange, endRange)); + break; + } + + + break; + + case "bpop.gl": + + // bpop.gl:num.10..99.GL_DD_OFFw10_L + translate = suffixSplit[0]; + String representationText = null; + switch(translate) { + case "num": + representationText = "number"; + break; + + case "dot": + representationText = "quantity"; + break; + } + + startRange = suffixSplit[1]; + // empty string in [1] + endRange = suffixSplit[3]; + + String GL = suffixSplit[7]; + String GLtext = null; + switch(GL) { + case "M": + GLtext = "greater"; + break; + + case "L": + GLtext = "lesser"; + break; + } + + // System.out.println(Arrays.toString(suffixSplit)); + result.add(String.format("Which %s is %s?", representationText, GLtext)); + result.add(String.format("Numbers between %s and %s", startRange, endRange)); + + break; + case "bpop.mn": + + // bpop.mn:10..99.MN-DD-UP-OFF1-BL1 + startRange = suffixSplit[0]; + // empty string in [1] + endRange = suffixSplit[2]; + + // System.out.println(Arrays.toString(suffixSplit)); + result.add("Select the missing number"); + result.add(String.format("Numbers between %s and %s", startRange, endRange)); + + break; + } + + return result; + } +} diff --git a/util/src/main/java/cmu/xprize/util/FailureInterventionHelper.java b/util/src/main/java/cmu/xprize/util/FailureInterventionHelper.java new file mode 100644 index 0000000..2659f01 --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/FailureInterventionHelper.java @@ -0,0 +1,77 @@ +package cmu.xprize.util; + +import android.content.Intent; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; + +import static cmu.xprize.util.consts.INTERVENTION_CONST.BROADCAST_FAILURE_UPDATE; +import static cmu.xprize.util.consts.INTERVENTION_CONST.FAILS_HAPPENED; +import static cmu.xprize.util.consts.INTERVENTION_CONST.FAILS_NEEDED; + +/** + * FailureInterventionHelper + * + * How to add FailureInterventionHelper to a tutor + * 1. declare a `protected` Helper in a CComponent for a Tutor + * 2. construct the Helper in the TComponent class, after `dataSource` has been loaded via loadJSON (usually setDataSource) + * 3. look in `trackAndLogPerformance`, and call `shouldTriggerIntervention` when the student gets a question incorrect + * + * Created by kevindeland on 9/20/19. + */ + +public class FailureInterventionHelper { + + public enum Tutor {BPOP, AKIRA, SPELL, WRITE, PICMATCH, NUMCOMPARE} + + private Tutor _tutorType; + private int _dataSize; + + public FailureInterventionHelper(Tutor _tutorType, int _dataSize) { + this._tutorType = _tutorType; + this._dataSize = _dataSize; + } + + /** + * Returns whether a failure intervention should be triggered, based on the number of wrong + * attempts that have been committed. + * FAILSON Note that this should be changed to be dependent on the passing logic for each tutor, + * FAILSON and the total number of problems + * + * @param anyWrongAttempts how many the student has gotten wrong in total + * @return TRUE if an intervention should be triggered + */ + public boolean shouldTriggerIntervention(int anyWrongAttempts) { + + switch(_tutorType) { + case BPOP: + return anyWrongAttempts == 9; + + case AKIRA: + return anyWrongAttempts == TCONST.FAILURE_COUNT_AKIRA; + + case SPELL: + return anyWrongAttempts == 9; + + case WRITE: + return anyWrongAttempts == 9; + + case PICMATCH: + return anyWrongAttempts == 9; + + case NUMCOMPARE: + return anyWrongAttempts == 9; + + default: + Log.wtf("FailureInterventionHelper", "_tutorType not found: " + _tutorType); + return false; + } + } + + public void sendBroadcastUpdate(LocalBroadcastManager manager, int numWrong) { + Intent failureIntent = new Intent(BROADCAST_FAILURE_UPDATE); + failureIntent.putExtra(FAILS_HAPPENED, numWrong); + failureIntent.putExtra(FAILS_NEEDED, 9); + manager.sendBroadcast(failureIntent); + + } +} diff --git a/util/src/main/java/cmu/xprize/util/GlobalStaticsEngine.java b/util/src/main/java/cmu/xprize/util/GlobalStaticsEngine.java new file mode 100644 index 0000000..10dad67 --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/GlobalStaticsEngine.java @@ -0,0 +1,51 @@ +package cmu.xprize.util; + +/** + * GlobalStaticsEngine + *

CTutorEngine and RoboTutor hold some important variables that are impossible for + * classes to access if they are lower on the dependency tree hierarchy. This class is for holding + * variables that need to be accessed by those classes.

+ * Created by kevindeland on 2019-10-27. + */ +public class GlobalStaticsEngine { + + private static String currentTutorId; + + private static String currentDomain; + + private static int currentLevel; + + private static String currentTutorType; + + public static String getCurrentTutorId() { + return currentTutorId; + } + + public static void setCurrentTutorId(String currentTutorId) { + GlobalStaticsEngine.currentTutorId = currentTutorId; + } + + public static String getCurrentDomain() { + return currentDomain; + } + + public static void setCurrentDomain(String currentDomain) { + GlobalStaticsEngine.currentDomain = currentDomain; + } + + public static int getCurrentLevel() { + return currentLevel; + } + + public static void setCurrentLevel(int currentLevel) { + GlobalStaticsEngine.currentLevel = currentLevel; + } + + public static String getCurrentTutorType() { + return currentTutorType; + } + + public static void setCurrentTutorType(String currentTutorType) { + GlobalStaticsEngine.currentTutorType = currentTutorType; + } +} diff --git a/util/src/main/java/cmu/xprize/util/IButtonController.java b/util/src/main/java/cmu/xprize/util/IButtonController.java new file mode 100644 index 0000000..2ab6883 --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/IButtonController.java @@ -0,0 +1,12 @@ +package cmu.xprize.util; + +public interface IButtonController { + + public void doDebugLaunchAction(String debugTutor); + void doDebugTagLaunchAction(String tag); + + public void doButtonBehavior(String buttonid); + public void doAskButtonAction(String actionid); + + public void doLaunch(String intent, String intentData, String dataSource, String tutorId, String matrix); // WARRIOR_MAN +} diff --git a/util/src/main/java/cmu/xprize/util/IEvent.java b/util/src/main/java/cmu/xprize/util/IEvent.java index 96468ea..0ed5488 100644 --- a/util/src/main/java/cmu/xprize/util/IEvent.java +++ b/util/src/main/java/cmu/xprize/util/IEvent.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,6 +19,7 @@ package cmu.xprize.util; public interface IEvent { + public String getType(); public Object getString(String key); } diff --git a/util/src/main/java/cmu/xprize/util/IEventDispatcher.java b/util/src/main/java/cmu/xprize/util/IEventDispatcher.java index 22d2593..7c1f8ff 100644 --- a/util/src/main/java/cmu/xprize/util/IEventDispatcher.java +++ b/util/src/main/java/cmu/xprize/util/IEventDispatcher.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,9 +23,11 @@ public interface IEventDispatcher { - public List mListeners = new ArrayList(); + public boolean isGraphEventSource(); + + public void addEventListener(String listener); + public void addEventListener(IEventListener listener); - public void addEventListener(String linkedView); public void dispatchEvent(IEvent event); } diff --git a/util/src/main/java/cmu/xprize/util/IEventListener.java b/util/src/main/java/cmu/xprize/util/IEventListener.java index f8f8d06..0c4318c 100644 --- a/util/src/main/java/cmu/xprize/util/IEventListener.java +++ b/util/src/main/java/cmu/xprize/util/IEventListener.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/util/src/main/java/cmu/xprize/util/IInterventionSource.java b/util/src/main/java/cmu/xprize/util/IInterventionSource.java new file mode 100644 index 0000000..580bd6d --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/IInterventionSource.java @@ -0,0 +1,12 @@ +package cmu.xprize.util; + +/** + * RoboTutor + *

+ * Created by kevindeland on 6/19/19. + */ + +public interface IInterventionSource { + + void triggerIntervention(String type); +} diff --git a/util/src/main/java/cmu/xprize/util/ILogManager.java b/util/src/main/java/cmu/xprize/util/ILogManager.java deleted file mode 100644 index 5801516..0000000 --- a/util/src/main/java/cmu/xprize/util/ILogManager.java +++ /dev/null @@ -1,20 +0,0 @@ -package cmu.xprize.util; - -public interface ILogManager { - - public void startLogging(String logPath); - public void stopLogging(); - - public void postSystemEvent(String Tag, String Msg); - public void postSystemTimeStamp(String Tag); - - public void postEvent(String Tag, String Msg); - public void postTimeStamp(String Tag); - - public void post(String command); - - public void postError(String Tag, String Msg); - public void postError(String Tag, String Msg, Exception e); - - public void postPacket(String packet); -} diff --git a/util/src/main/java/cmu/xprize/util/IMessageQueueRunner.java b/util/src/main/java/cmu/xprize/util/IMessageQueueRunner.java new file mode 100644 index 0000000..b0700ab --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/IMessageQueueRunner.java @@ -0,0 +1,16 @@ +package cmu.xprize.util; + +/** + * RoboTutor + *

+ * Created by kevindeland on 8/27/19. + */ + +public interface IMessageQueueRunner { + + void runCommand(String command); + + void runCommand(String command, Object target); + + void runCommand(String command, String target); +} diff --git a/util/src/main/java/cmu/xprize/util/IPerformanceTracker.java b/util/src/main/java/cmu/xprize/util/IPerformanceTracker.java new file mode 100644 index 0000000..b695d23 --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/IPerformanceTracker.java @@ -0,0 +1,14 @@ +package cmu.xprize.util; + +/** + * IPerformanceTracker + * + *

currently only implemented by BigMath. Would ideally work for others as well.

+ * + * Created by kevindeland on 10/7/18. + */ + +public interface IPerformanceTracker { + + void trackAndLogPerformance(boolean correct, Object expected, Object actual); +} diff --git a/util/src/main/java/cmu/xprize/util/IPublisher.java b/util/src/main/java/cmu/xprize/util/IPublisher.java index 0acbbdd..d99c92c 100644 --- a/util/src/main/java/cmu/xprize/util/IPublisher.java +++ b/util/src/main/java/cmu/xprize/util/IPublisher.java @@ -1,5 +1,7 @@ package cmu.xprize.util; +import java.util.HashMap; + /** * Created by kevin on 10/27/2016. */ @@ -12,8 +14,16 @@ public interface IPublisher { public void publishValue(String varName, int value); + public void publishFeatureSet(String featureset); + + public void retractFeatureSet(String featureset); + public void publishFeature(String feature); public void retractFeature(String feature); + public void publishFeatureMap(HashMap featureMap); + + public void retractFeatureMap(HashMap featureMap); + } diff --git a/util/src/main/java/cmu/xprize/util/IScriptable.java b/util/src/main/java/cmu/xprize/util/IScriptable.java index 72e1105..ba1c171 100644 --- a/util/src/main/java/cmu/xprize/util/IScriptable.java +++ b/util/src/main/java/cmu/xprize/util/IScriptable.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -34,6 +33,7 @@ public interface IScriptable { public Object evaluate(boolean neg); public void preEnter(); + public boolean testFeatures(); public String applyNode(); } diff --git a/util/src/main/java/cmu/xprize/util/ImageLoader.java b/util/src/main/java/cmu/xprize/util/ImageLoader.java new file mode 100644 index 0000000..f9a93aa --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/ImageLoader.java @@ -0,0 +1,81 @@ +package cmu.xprize.util; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.widget.ImageView; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + + +public class ImageLoader { + + public static RequestLoader with(Context context) { + return new RequestLoader(context); + } + + static class RequestLoader { + private Context context; + private int imageResource; + + RequestLoader(@NonNull Context context) { + this.context = context; + } + + /** + * Load a Drawable into the RequestLoader + * + * @param imagePath + * @return + */ + public RequestLoader loadDrawable(String imagePath) { + this.imageResource = context.getResources().getIdentifier(imagePath, "drawable", context.getPackageName()); + return this; + } + + + + public void into(ImageView imageView) { + + Drawable image = ContextCompat.getDrawable(context, imageResource); + imageView.setImageDrawable(image); + + } + } + + public static BitmapLoader makeBitmapLoader(String path) { return new BitmapLoader(path);} + + // NEW_THUMBS (3) copy this BitmapLoader. Use it to load images. + public static class BitmapLoader { + + private Bitmap bitmapResource; + private String _path; + + BitmapLoader(String path) { + this._path = path; + } + + /** + * Load a Bitmap + * @param imageName + * @return + */ + public BitmapLoader loadBitmap(String imageName) throws FileNotFoundException { + String fullPath = _path + imageName; + + InputStream in = new FileInputStream(_path + imageName); + bitmapResource = BitmapFactory.decodeStream(in); + + return this; + } + + public void into(ImageView imageView) { + imageView.setImageBitmap(this.bitmapResource); + } + } +} diff --git a/util/src/main/java/cmu/xprize/util/JSON_Helper.java b/util/src/main/java/cmu/xprize/util/JSON_Helper.java index ff1f413..0c0bea2 100644 --- a/util/src/main/java/cmu/xprize/util/JSON_Helper.java +++ b/util/src/main/java/cmu/xprize/util/JSON_Helper.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2021 RoboTutorLLC All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -38,6 +37,8 @@ import java.util.HashMap; import java.util.Iterator; +import cmu.xprize.comp_logging.CErrorManager; + /** * Global helper to cache spec files from the tutor assets folder @@ -77,6 +78,74 @@ static public String cacheDataByName(String fileName) { return cacheData(fileName, TCONST.DEFINED); } + static public String createValueAcronym(String jsonData) { + /* + @params: jsonData - A serialised jsonString, e.g. + { + "config_version": "ftttfNULLfCD2", → compute instead + "language_override": false, + "show_tutorversion": true, + "show_debug_launcher": true, + "language_switcher": true, + "no_asr_apps": false, + "language_feature_id": "LANG_NULL", → NULL | EN | SW + "show_demo_vids": false, + "menu_type": "CD2" → 1 or 2 in acronym + + } + + The config_version will be replaced with a custom acronym, which is generated below. + + */ + String outputAcronym = new String(); + try { + JSONObject jsonObject = new JSONObject(jsonData); + JSONArray keys = jsonObject.names(); + + for (int i=0; i elemClass = fieldClass.getComponentType(); - - Object field_Array = Array.newInstance(elemClass, nArr.length()); + try { + nArr = jsonObj.getJSONArray(fieldName); + Class elemClass = fieldClass.getComponentType(); + Object field_Array = Array.newInstance(elemClass, nArr.length()); + field.set(self, parseArray(jsonObj, self, classMap, scope, nArr, elemClass, field_Array)); + } catch(Exception e){ + JSONArray emptyArray = new JSONArray(); + Object field_Array = Array.newInstance(String.class, 0); + field.set(self, parseArray(jsonObj, self, classMap, scope, emptyArray, String.class, field_Array)); + } - field.set(self, parseArray(jsonObj, self, classMap, scope, nArr, elemClass, field_Array)); } // otherwise assume it is a discrete object of ILoadable type @@ -455,7 +537,9 @@ else if (fieldClass.equals(HashMap.class)) { } } catch (Exception e) { + CErrorManager.logEvent(TAG, "ERROR: parseSelf:", e, true); + } } } @@ -541,7 +625,7 @@ static Object parseArray(JSONObject jsonObj, Object self, HashMap } } catch(Exception e) { - CErrorManager.logEvent(TAG, "Json Array Format Error: ", e, false); + CErrorManager.logEvent(TAG, "Json Array Format Error: ", e, true); } return field_Array; diff --git a/util/src/main/java/cmu/xprize/util/MathUtil.java b/util/src/main/java/cmu/xprize/util/MathUtil.java new file mode 100644 index 0000000..da9a6f2 --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/MathUtil.java @@ -0,0 +1,42 @@ +package cmu.xprize.util; + +/** + * RoboTutor + *

+ * Created by kevindeland on 7/17/18. + */ + +public class MathUtil { + + + /** + * Get hundreds digit of a 3-digit number. + * + * @param numberValue must be 3 digits or less. + * @return hundreds digit + */ + public static int getHunsDigit(int numberValue) { + return numberValue / 100; + } + + /** + * Get tens digit of a number. + * + * @param numberValue must be positive. + * @return tens digit + */ + public static int getTensDigit(int numberValue) { + return (numberValue / 10) % 10; + } + + /** + * Get ones digit of a number. + * + * @param numberValue must be positive. + * @return ones digit + */ + public static int getOnesDigit(int numberValue) { + return numberValue % 10; + } + +} diff --git a/util/src/main/java/cmu/xprize/util/QueueConstructorVars.txt b/util/src/main/java/cmu/xprize/util/QueueConstructorVars.txt new file mode 100644 index 0000000..fe43094 --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/QueueConstructorVars.txt @@ -0,0 +1,21 @@ +CMessageQueueFactory,CBP_Component,CNumberScale_Component,CCountX_Component, +CAk_Component,CNd_Component,CBigMathComponent + String:command + String:name,String:command + String:command,Object:target*** + +/* Anything can be added to this */ +CPicMatch_Component,CSpellingComponent + null + +CWritingComponent** + String:name,String:command + String:name,String:command,String:target + + +/* Might just keep these separate */ +CQn_Component,CRt_Component**** + String:command + String:command,Object:target + +****These do NOT have the (name, command) constructor, don't have nameMap diff --git a/util/src/main/java/cmu/xprize/util/TCJSGF.java b/util/src/main/java/cmu/xprize/util/TCJSGF.java index 2c89687..07bfa0a 100644 --- a/util/src/main/java/cmu/xprize/util/TCJSGF.java +++ b/util/src/main/java/cmu/xprize/util/TCJSGF.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/util/src/main/java/cmu/xprize/util/TCONST.java b/util/src/main/java/cmu/xprize/util/TCONST.java index 0f71d78..efaa884 100644 --- a/util/src/main/java/cmu/xprize/util/TCONST.java +++ b/util/src/main/java/cmu/xprize/util/TCONST.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,27 +20,36 @@ // global tutor constants -import android.content.Context; +import android.os.Environment; -import java.io.File; import java.util.HashMap; public class TCONST { -// sdcard/robotutor_assets/assets/audio/en/cmu/xprize/activity_selector/d39950ec96e6a5361508996ce7ae6444.mp3 - // These features are based on the current tutor selection model // When no tutor has been selected it should run the tutor select // and when it finishes it should run the difficulty select until // the user wants to select another tutor. // - public static final String FTR_TUTOR_SELECT = "FTR_TUTOR_SELECT"; - public static final String FTR_DIFFICULTY_SELECT = "FTR_DIFFICULTY_SELECT"; + public static final String FTR_TUTOR_SELECT = "FTR_TUTOR_SELECT"; // these are never read anymore, but are still used as features + public static final String FTR_DEBUG_SELECT = "FTR_DEBUG_SELECT"; + public static final String FTR_DEBUG_LAUNCH = "FTR_DEBUG_LAUNCH"; + + public static final String FTR_GOODBYE = "GOODBYE"; + public static final int NUM_GOODBYE_SOUND_CLIPS = 9; // make sure this matches the number of sound clips in + // the "EXIT_BUTTON_BEHAVIOR" object in activity_selector/animator_graph.json + + public static final String SKILL_WRITING = "letters"; + public static final String SKILL_STORIES = "stories"; + public static final String SKILL_MATH = "numbers"; - // RoboTutor Version spec index meaning 0.1.2.3 - // Given 4.23.2.3 - // Major release 4 | Feature release 23 | Fix release 2 | compatible Asset Version 3 + public static final String FINISH = "FINISH"; + + + // RoboTutor Version spec Index meaning 0.1.2.3 + // Given 4.23.9.8 + // Major release 4 | Feature release 23 | Fix release 9 | compatible Asset Version 8 // public static final int MAJOR_VERSION = 0; public static final int FEATURE_RELEASE = 1; @@ -62,18 +70,23 @@ public class TCONST { // They will arrive in files named - RoboTutor_AssetA.0.1.0.zip // - public static final String ROBOTUTOR_ASSET_PATTERN = "RTAsset_"; + public static final String ROBOTUTOR_ASSET_PATTERN = "rtasset_"; + public static final String CODE_DROP_1_ASSET_PATTERN = "codedrop1_"; + public static final String CODE_DROP_2_ASSET_PATTERN = "codedrop2_"; + public static final String PROTOTYPE_ASSET_PATTERN = "protoassets_"; + public static final String QA_ASSET_PATTERN = "qa_assets"; + public static final String ENGLISH_ASSET_PATTERN = "English_"; + public static final String SWAHILI_ASSET_PATTERN = "Swahili_"; public static final String COMMAND = "COMMAND"; public static final String MODULE = "MODULE"; + public static final String QUEUE = "QUEUE"; public static final String NODE = "NODE"; public static final String CONDITION = "CONDITION"; public static final String NUMDATA_HEADER = "{\n" + "\"dataSource\": "; public static final boolean ADD_FEATURE = true; public static final boolean DEL_FEATURE = false; - public static final int GUID_LEN = 5; - public static final String GUID_UPDATE = "GUIDUPDATE"; public static final String FTR_PLACE_ = "FTR_PLACE_"; public static final String _USED = "_USED"; @@ -85,7 +98,7 @@ public class TCONST { public static final int MAX_DIGITS = 4; public static final String NO_DATASOURCE = ""; public static final String DATA_PREFIX = "DATA_"; - public static final String DATA_PATH = "data"; + public static final String DATA_PATH = "data"; public static final String FW_PREPLISTENER = "FW_PREPLISTENER"; public static final String FW_TTS = "FW_TTS"; @@ -95,12 +108,34 @@ public class TCONST { public static final String SAY_STIMULUS = "FTR_SAY"; public static final String SHOW_STIMULUS = "FTR_SHOW"; + + public static final String ASM_DIGIT_OR_OVERHEAD_CORRECT = "ASM_DIGIT_OR_OVERHEAD_CORRECT"; + public static final String ASM_DIGIT_OR_OVERHEAD_WRONG = "ASM_DIGIT_OR_OVERHEAD_WRONG"; + public static final String ASM_CLICK_ON_DOT = "ASM_CLICK_ON_DOT"; + public static final String ASM_ALL_DOTS_DOWN = "ASM_ALL_DOTS_DOWN"; + + public static final String ASM_ADD = "ASM_ADD"; + public static final String ASM_ADD_PROMPT = "ASM_ADD_PROMPT"; + public static final String ASM_ADD_PROMPT_COUNT_FROM = "ASM_ADD_PROMPT_COUNT_FROM"; + + public static final String ASM_SUB = "ASM_SUB"; + public static final String ASM_SUB_PROMPT = "ASM_SUB_PROMPT"; + + public static final String ASM_MULTI = "ASM_MULTI"; + public static final String ASM_MULTI_PROMPT = "ASM_MULTI_PROMPT"; + public static final String ASM_RA_START = "ASM_RA_START"; + public static final String ASM_NEXT_NUMBER = "ASM_NEXT_NUMBER"; + public static final String ASM_NEXT_RESULT = "ASM_NEXT_RESULT"; + public static final String ASM_RESULT_FIRST_TWO = "ASM_RESULT_FIRST_TWO"; + public static final String ASM_RESULT_NEXT_OR_LAST = "ASM_RESULT_NEXT_OR_LAST"; + public static final String ASM_REPEATED_ADD_DOWN = "ASM_REPEATED_ADD_DOWN"; + public static final String TYPE_CTUTOR = "CTutor"; public static final String TYPE_CSCENEGRAPH = "CSceneGraph"; public static final String TYPE_CTUTORGRAPH = "CTutorGraph"; public static final String EVENT_SCENEQUEUE = "Scene Queue Event"; public static final String EVENT_TUTORGRAPH = "Tutor Graph Event"; - public static final String AUDIO_REF = "audio_ref"; +// public static final String AUDIO_REF = "audio_ref"; public static final String SET_BANNER_COLOR = "SET_BANNER_COLOR"; public static final String LAST_ATTEMPT = "FTR_LASTATTEMPT"; @@ -146,8 +181,6 @@ public class TCONST { public static final String INVISIBLE = "INVISIBLE"; public static final String GONE = "GONE"; - public static final String ENGINEMESSAGE = "EngineMessage: "; - public static final String ASK_SELECTION = "ASK_SELECTION"; public static final String ASK_BUTTON_ID = "ASK_BUTTON_ID"; public static final String CANCEL_POINTAT = "CANCEL_POINTAT"; @@ -157,13 +190,114 @@ public class TCONST { public static final String RIGHTLANE = "RIGHT"; public static final String STORY_INTENT = "story_reading"; + public static final String QUESTIONS_INTENT = "story_questions"; public static final String ON_CLICK = "ON_CLICK"; - - public static final String SKILL_WRITING = "letters"; - public static final String SKILL_READING = "stories"; - public static final String SKILL_MATH = "numbers"; - public static final String SKILL_SHAPES = "shapes"; - public static final String SKILL_UNSET = "SKILL_UNSET"; + public static final String ENCODED_FOLDER = "[encfolder]"; + public static final String SHARED_MATH = "[sharedmath]"; + public static final String SHARED_MATH_FOLDER = "shared/shared_math"; + public static final String SHARED_LITERACY = "[sharedliteracy]"; + public static final String SHARED_LITERACY_AUDIO_FOLDER = "shared/shared_lit"; // TODO fix this abominable inconsistency + public static final String SHARED_LITERACY_IMAGE_FOLDER = "shared/shared_literacy"; + public static final String SONG = "[song]"; + public static final String SONG_FOLDER = "?"; + public static final String STORY_PATH = "cmu/xprize/story_reading/"; + public static final String WORD_PROBLEMS = "[wordproblems]"; + public static final String PUNC_STORY = "[punc]"; + + public static final String LOCAL_FILE = "[local_file]"; + public static final String DOWNLOAD_PATH = "/sdcard/Download"; + public static final String DOWNLOAD_RT_PATH = "/sdcard/Download/RoboTutor"; + public static final String DOWNLOAD_RT_TUTOR = "/sdcard/Download/RoboTutor/assets"; + + public static final String DEBUG_FILE_PREFIX = "[debug_file]"; + public static final String DEBUG_RT_PATH = "/sdcard/robo_debug"; + + public static final String ARITHMETIC_DATA = "tutors/add_subtract"; + public static final String AKIRA_DATA = "tutors/akira"; + public static final String BUBBLEPOP_DATA = "tutors/bubble_pop"; + public static final String STORY_DATA = "tutors/story_reading"; + public static final String WRITING_DATA = "tutors/word_copy"; + + public static final String FTR_USER_HEAR = "FTR_USER_HEAR"; + public static final String FTR_USER_READ = "FTR_USER_READ"; + public static final String FTR_USER_ECHO = "FTR_USER_ECHO"; + public static final String FTR_USER_HIDE = "FTR_USER_HIDE"; + public static final String FTR_USER_REVEAL = "FTR_USER_REVEAL"; + public static final String FTR_USER_PARROT = "FTR_USER_PARROT"; + public static final String FTR_USER_READING = "FTR_USER_READING"; + // UHQ + public static final String FTR_GEN = "FTR_GEN"; + public static final String FTR_PIC = "FTR_PIC"; + public static final String FTR_CLO = "FTR_CLO"; + public static final String STOP_AUDIO = "STOP_AUDIO"; + public static final String RTC_VAR_CLOZEWORD = ".clozeWord"; + public static final String REMOVE_CLOZE_FROM_BLANK = "REMOVE_CLOZE_FROM_BLANK"; + + + public static final String NARRATE_STORY = "NARRATE_STORY"; + public static final String TRACK_NARRATION = "TRACK_NARRATION"; + public static final String START_NARRATION = "START_NARRATION"; + public static final String SPEAK_UTTERANCE = "SPEAK_UTTERANCE"; + + public static final String SPEAK_EVENT = "SPEAK_EVENT"; + + public static final int INITSPLIT = -1; + public static final String SENTENCE_SPACE = " "; + public static final String WORD_SPACE = " "; + public static final String NO_SPACE = ""; + public static final int MAX_AKDATA = 10; + + public static final String FTR_COMPLETE = "FTR_COMPLETE"; + public static final String FTR_PROMPT = "FTR_PROMPT"; + public static final String FTR_PAGE_PROMPT = "FTR_PAGE_PROMPT"; + + public static final String START_PROGRESSIVE_UPDATE = "START_PROGRESSIVE_UPDATE"; + public static final String START_INDETERMINATE_UPDATE = "START_INDETERMINATE_UPDATE"; + public static final String UPDATE_PROGRESS = "UPDATE_PROGRESS"; + public static final String PROGRESS_TITLE = "PROGRESS_TITLE"; + public static final String PROGRESS_MSG1 = "PROGRESS_MSG1"; + public static final String PROGRESS_MSG2 = "PROGRESS_MSG2"; + public static final String ASSET_UPDATE_MSG = "Installing Assets: "; + public static final String INT_FIELD = "INT_FIELD"; + public static final String PLEASE_WAIT = " - Please Wait."; + + public static final String TUTOR_NATIVE = "native"; + public static final String TRACK_COMPLETE = "TRACK_COMPLETE"; + + public static final String NEXT_PAGE = "NEXT_PAGE"; + public static final String CLOZE_CORRECT = "CLOZE_CORRECT"; + public static final String CLOZE_WRONG = "CLOZE_WRONG"; + public static final String PICMATCH_CORRECT = "PICMATCH_CORRECT"; + public static final String PICMATCH_WRONG = "PICMATCH_WRONG"; + public static final String NEXT_SCENE = "NEXT_SCENE"; + public static final String NEXT_WORD = "NEXT_WORD"; + + + // Core log message types - anumation scenegraph and queued scenegraph + // + public static final String TUTOR_STATE_MSG = "TSTag"; + public static final String GRAPH_MSG = "RTag"; + public static final String QGRAPH_MSG = "RQTag"; + public static final String LTKPLUS_MSG = "RLTag"; + public static final String LOGSTATE = "logState"; + public static final String BATTERY_MSG = "Battery"; + + public static final String AUDIO_EVENT = "AUDIO_EVENT"; + public static final String TYPE_AUDIO = "type_audio"; + public static final String TRACK_SEGMENT = "TRACK_SEGMENT"; + + // for logging and tracking student performance + public static final String PERFORMANCE_TAG = "PERFORMANCE_TAG"; + public static final boolean CONSIDER_STUDENT_PERFORMANCE = true; + public static final boolean OVERRIDE_SELF_ASSESSMENT = false; + public static final double HIGH_PERFORMANCE_THRESHOLD = 0.9; // percent to be upgraded a level + public static final double MID_PERFORMANCE_THRESHOLD = 0.5; // percent to pass + public static final int MIN_ATTEMPTS_TO_GRADE = 5; // minimum number of attempts to be graded + + public static final String DEFAULT_STUDENT_ID = "DEBUG"; + public static final String STUDENT_ID_VAR = "studentId"; + public static final String SESSION_ID_VAR = "sessionId"; + public static final String LAST_TUTOR = "LAST_TUTOR_PLAYED"; static public HashMap colorMap = new HashMap(); @@ -187,6 +321,8 @@ public class TCONST { public static final String COLORNORMAL = "normal"; public static final String COLORNONE = "none"; + public static final int STROKE_STIM_UNDERLINE = 5; + static public HashMap fontMap = new HashMap(); @@ -217,6 +353,7 @@ public class TCONST { public static final String LANG_EFFECT = "LANG_EFFECT"; public static final String LANG_EN = "LANG_EN"; public static final String LANG_SW = "LANG_SW"; + public static final String MEDIA_STORY = "story"; // This maps features to 2 letter codes used to build filepaths. static { @@ -233,9 +370,10 @@ public class TCONST { static final public String BASE_ASSETS = "assets"; public static final String STORY_ASSETS = "story"; + public static final String ICON_ASSETS = "icons"; static final public String EXTERNAL = "external"; static final public String ROBOTUTOR_ASSETS = "sdcard/robotutor_assets/assets"; - + static final public String LOCAL_STORY_AUDIO = "sdcard/Download/RoboTutor/assets/story_questions"; static final public String LTK_PROJECT_ASSETS = "projects"; static final public String LTK_GLYPH_ASSETS = "glyphs"; @@ -255,6 +393,7 @@ public class TCONST { static final public String DEFAULT = "default"; // CTutorNavigator Constants + // TODO this is so annoying... different objects use the same ENDTUTOR var public static final String ENDTUTOR = "END_TUTOR"; // Terminate a tutor from within public static final String KILLTUTOR = "KILL_TUTOR"; // Kill a tutor exteranlly public static final String CONTINUETUTOR = "CONTINUE_TUTOR"; @@ -269,8 +408,13 @@ public class TCONST { public static final String ASSETS = "ASSETS"; public static final String RESOURCES = "RESOURCE"; public static final String EXTERN = "EXTERN"; + public static final String EXTERN_SHARED = "EXTERN_SHARED"; public static final String DEFINED = "DEFINED"; + public static final String DEBUG_STORY_TAG = "GENERAL_TSO"; + public static final String DEBUG_HESITATE = "MATH_HESITATE"; + public static final String DEBUG_MENU = "DEBUG_MENU"; + // Navigator types final static public String SIMPLENAV = "SIMPLE_NAVIGATOR"; final static public String GRAPHNAV = "GRAPH_NAVIGATOR"; @@ -282,7 +426,6 @@ public class TCONST { public static final String REC_GLYPH = "REC_GLYPH"; - // CActionTrack track types // Note these must case-match the layer names in the Flash // timeline specification from which CActionTrack is derived @@ -310,6 +453,11 @@ public class TCONST { public static final String READY = "READY"; public static final String PLAY = "PLAY"; + //UHQ + public static final String PLAY_CLOZE = "PLAY_CLOZE"; + public static final String CLZ_ANIM_INCOMPLETE = "CLZ_ANIM_INCOMPLETE"; + public static final String CLZ_ANIM_COMPLETE = "CLZ_ANIM_COMPLETE"; + public static long CLOZE_END = 0L; public static final String STOP = "STOP"; public static final String NEXT = "NEXT"; public static final String GOTO_NODE = "GOTO_NODE"; @@ -323,6 +471,7 @@ public class TCONST { public static final String ENTER_SCENE = "ENTER_SCENE"; public static final String END_OF_GRAPH = "END_OF_GRAPH"; + public static final String APPLY_NODE = "APPLY_NODE"; public static final String APPLY_BEHAVIOR = "APPLY_BEHAVIOR"; @@ -365,8 +514,6 @@ public class TCONST { public static final String CMD_GOTO = "GOTONODE"; public static final String CMD_NEXT = "NEXT"; public static final String CMD_LAUNCH = "LAUNCH-TUTOR"; - public static final String CMD_SET_FEATURE = "FEATURE-ADD"; - public static final String CMD_DEL_FEATURE = "FEATURE-DEL"; // Intrinsic types @@ -384,6 +531,58 @@ public class TCONST { public static final String STARE_START = "STARE_START"; public static final String STARE_STOP = "STARE_STOP"; + + // Broadcasts for Intervention + public static final String I_TRIGGER_GESTURE = "GESTURE"; + public static final String I_TRIGGER_STUCK = "STUCK"; + public static final String I_TRIGGER_HESITATE = "HESITATE"; + public static final String I_TRIGGER_FAILURE = "FAILURE"; + + // Intervention types + public static final String I_TYPE_KNOWLEDGE = "KNOWLEDGE"; + public static final String I_TYPE_APPLICATION = "APPLICATION"; + + // Cancel broadcasts + public static final String I_CANCEL_STUCK = "X_STUCK"; + public static final String I_CANCEL_HESITATE = "X_HESITATE"; + public static final String I_CANCEL_GESTURE = "X_GESTURE"; + + public static final String I_MODAL_EXTRA = "MODAL"; + + public static final String EXIT_FROM_INTERVENTION = "EXIT_FROM_INTERVENTION"; // might be needed to resume paused activity, e.g. Akira + public static final String INTERVENTION_FOLDER = "sdcard/intervention"; + public static final String INTERVENTION_TIMES_FILE = "TimeSpentPerAttempt.csv"; + public static final String INTERVENTION_STUDENT_FILE = "intervention.csv"; + public static final String UPDATE_INTERVENTION_FILE = "update_intervention.csv"; + + // CONSTANTS -- replace with Judith's thing. + public static final int FAILURE_COUNT_BPOP = 9; + public static final Long HESITATE_TIME_BPOP = 6000L; // NOTE THAT THIS IS NOT RESETTING + public static final Long STUCK_TIME_BPOP = 20000L; + public static final Long GESTURE_TIME_BPOP = 6000L; + + public static final int FAILURE_COUNT_AKIRA = 9; + public static final Long HESITATE_TIME_AKIRA = 10000L; + public static final Long STUCK_TIME_AKIRA = -1L; // not applicable... + public static final Long GESTURE_TIME_AKIRA = 10000L; + + + public static final Long HESITATE_TIME_SPELL = 15000L; + public static final Long STUCK_TIME_SPELL = 18000L; + public static final Long GESTURE_TIME_SPELL = 13000L; + + public static final Long HESITATE_TIME_PICMATCH = 18000L; + public static final Long STUCK_TIME_PICMATCH = 20000L; + public static final Long GESTURE_TIME_PICMATCH = 13000L; + + public static final Long HESITATE_TIME_WRITE = 18000L; + public static final Long STUCK_TIME_WRITE = 20000L; + public static final long GESTURE_TIME_WRITE = 18000L; + + public static final Long HESITATE_TIME_NUMCOMPARE = 9000L; + public static final Long STUCK_TIME_NUMCOMPARE = 12000L; + public static final Long GESTURE_TIME_NUMCOMPARE = 9000L; + public static final String FTR_STORY_STARTING = "FTR_STORY_STARTING"; public static final String FWCORRECT = "FTR_RIGHT"; @@ -391,8 +590,12 @@ public class TCONST { public static final String FWUNKNOWN = "FTR_UNRECOGNIZED"; public static final String FTR_EOI = "FTR_NOWORDS"; public static final String FTR_EOD = "FTR_EOD"; + public static final String CONTINUE = "CONTINUE"; public static final String ALL_CORRECT = "ALL_CORRECT"; + public static final String LOG_CORRECT = "CORRECT"; + public static final String LOG_INCORRECT = "INCORRECT"; + public static final String FALSE = "FALSE"; public static final String TRUE = "TRUE"; public static final String OVALICON = "OVALICON"; @@ -423,11 +626,47 @@ public class TCONST { public static final int ALL_EVENTS = 0xFFFFFFFF; + public static final String ASR_TIMED_START_EVENT = "ASR_TIMED_START_EVENT"; + public static final String ASR_RECOGNITION_EVENT = "ASR_RECOGNITION_EVENT"; + public static final String ASR_ERROR_EVENT = "ASR_ERROR_EVENT"; + public static final String ASR_SILENCE_EVENT = "ASR_SILENCE_EVENT"; + public static final String ASR_SOUND_EVENT = "ASR_SOUND_EVENT"; + public static final String ASR_WORD_EVENT = "ASR_WORD_EVENT"; + public static final String ASR_TIMEDSILENCE_EVENT = "ASR_TIMEDSILENCE_EVENT"; + public static final String ASR_TIMEDSOUND_EVENT = "ASR_TIMEDSOUND_EVENT"; + public static final String ASR_TIMEDWORD_EVENT = "ASR_TIMEDWORD_EVENT"; + public static final String UTTERANCE_COMPLETE_EVENT = "UTTERANCE_COMPLETE_EVENT"; + + public static final String ASR_ALL_TIMED_EVENTS = "ASR_ALL_TIMED_EVENTS"; + public static final String ASR_ALL_STATIC_EVENTS = "ASR_ALL_STATIC_EVENTS"; + public static final String ASR_ALL_EVENTS = "ASR_ALL_EVENTS"; + + // Map script event names to bitmap ASR Listener conatants. + // + static public HashMap ASREventMap = new HashMap(); + + static { + ASREventMap.put(ASR_SILENCE_EVENT, TCONST.SILENCE_EVENT); + ASREventMap.put(ASR_SOUND_EVENT, TCONST.SOUND_EVENT); + ASREventMap.put(ASR_WORD_EVENT, TCONST.WORD_EVENT); + ASREventMap.put(ASR_TIMEDSILENCE_EVENT, TCONST.TIMEDSILENCE_EVENT); + ASREventMap.put(ASR_TIMEDSOUND_EVENT, TCONST.TIMEDSOUND_EVENT); + ASREventMap.put(ASR_TIMEDWORD_EVENT, TCONST.TIMEDWORD_EVENT); + ASREventMap.put(ASR_TIMED_START_EVENT, TCONST.TIMEDSTART_EVENT); + + ASREventMap.put(ASR_ALL_TIMED_EVENTS,TCONST.ALLTIMED_EVENTS); + ASREventMap.put(ASR_ALL_STATIC_EVENTS,TCONST.ALL_EVENTS); + ASREventMap.put(ASR_ALL_EVENTS,TCONST.ALL_EVENTS); + } + + + public static final int NOINTERVENTION = 0; public static final int INSPEECH = 1; public static final int SAYWORD = 2; public static final String STORYDATA = "storydata.json"; + public static final String STORYMCQ = "mcq.json"; public static final String STORYINDEX = "story_index.json"; public static final String SOURCEFILE = "[file]"; public static final String ASSETFILE = "[asset]"; @@ -436,13 +675,7 @@ public class TCONST { public static final String TTS = "TTS"; public static final String ASR = "ASR"; - public static final String GLYPHLOG = "glyphlog_"; - public static final String DATASHOP = "-DS"; - public static final String JSONLOG = ".json"; - public static final boolean APPEND = true; - public static final boolean REPLACE = false; - - public static final String GLYPH_DATA = "glyphdata"; + public static final String GLYPH_DATA = "GLYPH_DATA"; // LTK messaging constants @@ -452,7 +685,16 @@ public class TCONST { public static final String FW_RESPONSE = "FW_RESPONSE"; public static final String WRITINGTUTOR_FOLDER = "/WritingTutor/"; - public static final String ROBOTUTOR_FOLDER = "/RoboTutor/"; + public static final String HOT_LOG_FOLDER = "/RTFace_Login_HOT/"; + public static final String READY_LOG_FOLDER = "/RTFace_Login/"; + + public static final String HOT_LOG_FOLDER_PERF = "/RTFace_Login_HOT/"; // use same as normal logs + public static final String READY_LOG_FOLDER_PERF = "/RTFace_Login/"; // use same as normal logs + + public static final String INTERVENTION_LOG_FOLDER = "/InterventionLogs/"; + + public static final String AUDIO_LOG_FOLDER = "/RTFace_Login_Audio/"; + public static final String ROBOTUTOR_ASSET_FOLDER = "/robotutor_assets/"; public static final String GLYPHS_FOLDER = "/glyphs/"; @@ -466,11 +708,6 @@ public class TCONST { public static final String SET_RATE = "SET_RATE"; - // Preference keys - public static final String ENGINE_INSTANCE = "RoboTutor"; - public static final String CURRENT_TUTOR = "tutor"; - - // Number Listeneing Component private static final String[] placeValue = {".ones",".tens",".hundreds",".thousands",".millions",".billions"}; @@ -496,10 +733,10 @@ public class TCONST { public static final String DIGIT2_WORDS_VAR = ".digit2Words"; public static final String DIGIT1_WORDS_VAR = ".digit1Words"; - // Generic error codes public static final String GENERIC_RIGHT = "FTR_RIGHT"; public static final String GENERIC_WRONG = "FTR_WRONG"; + public static final String NEXTTURN = "THRD_WRONG"; public static final String GENERIC_SUCCESSIVEWRONG = "FTR_SWRONG"; public static final boolean TRUE_ERROR = true; public static final boolean TRUE_NOERROR = true; @@ -592,33 +829,52 @@ public class TCONST { // READING Tutor State names -- RTC Reading Tutor Component - public static final String PAGEFLIP_BUTTON = "PAGE_FLIP_BUTTON"; - public static final String SPEAK_BUTTON = "SPEAK_BUTTON"; + public static final String PAGEFLIP_BUTTON = "PAGE_FLIP_CLICK"; + public static final String SPEAK_BUTTON = "SPEAK_CLICK"; public static final String EMPTY = ""; public static final int INCR = 1; public static final int DECR = -1; - public static final String RTC_VAR_PAGESTATE = ".pageState"; - public static final String RTC_VAR_PARASTATE = ".paraState"; - public static final String RTC_VAR_LINESTATE = ".lineState"; - public static final String RTC_VAR_WORDSTATE = ".wordState"; - public static final String RTC_VAR_ATTEMPT = ".attempt"; - public static final String LAST = "LAST"; - public static final String NOT_LAST = "NOT_LAST"; + public static final String RTC_VAR_ECHOSTATE = ".echoState"; + public static final String RTC_VAR_PARROTSTATE = ".parrotState"; + // Generic question state flag + public static final String RTC_VAR_QUESTIONSTATE = ".questionState"; + public static final String RTC_VAR_CLOZESTATE = ".clozeState"; + public static final String TO_CLOZE = ".toCloze"; + + public static final String RTC_VAR_SILENCESTATE = ".silenceState"; + // Generic question audio file + public static final String RTC_QUESTION_AUDIO = ".questionAudio"; + public static final String RTC_SILENCE = ".silence"; + public static final String COMPLETE = "COMPLETE"; + public static final String RTC_VAR_PAGESTATE = ".pageState"; + public static final String RTC_VAR_PARASTATE = ".paraState"; + public static final String RTC_VAR_LINESTATE = ".lineState"; + public static final String RTC_VAR_WORDSTATE = ".wordState"; + public static final String RTC_VAR_ATTEMPT = ".attempt"; + public static final String LAST = "LAST"; + public static final String NOT_LAST = "NOT_LAST"; + //UHQ + public static final String HAS_DISTRACTOR = ".distractor"; + public static final String RTC_VAR_STATE = ".storyState"; public static final String RTC_PARAGRAPHCOMPLETE = "PARAGRAPH_COMPLETE"; public static final String RTC_PAGECOMPLETE = "PAGE_COMPLETE"; public static final String RTC_STORYCMPLETE = "STORY_COMPLETE"; public static final String RTC_LINECOMPLETE = "LINE_COMPLETE"; + //Generic question state of completion public static final String RTC_CLEAR = ""; - public static final String RTC_VAR_WORDVALUE = ".currentWord"; - public static final String RTC_VAR_INDEX = ".wordindex"; - public static final String RTC_VAR_REMAINING = ".remainingWords"; - public static final String RTC_VAR_SENTENCE = ".sentence"; + public static final String RTC_VAR_PROMPT = ".prompt"; + public static final String RTC_VAR_PAGE_PROMPT = ".page_prompt"; + public static final String RTC_VAR_WORDVALUE = ".currentWord"; + public static final String RTC_VAR_INDEX = ".wordindex"; + public static final String RTC_VAR_REMAINING = ".remainingWords"; + public static final String RTC_VAR_SENTENCE = ".sentence"; + public static final String RTC_VAR_UTTERANCE = ".utterance"; //Akira Game Prompt Situation public static final String PROMPT_1LEFT = "PROMPT_1LEFT"; @@ -630,4 +886,48 @@ public class TCONST { public static final String PROMPT_3 = "PROMPT_3"; public static final String PROMPT_3V = "PROMPT_3V"; + + // Writing behavior... + public static final int WRITING_DATA_LIMIT = 10; + + // Counting + public static final String COUNTING_DEBUG_LOG = "COUNTING_DEBUG_LOG"; + + + // Data source debugger + + public static final String TAG_DEBUG_AKIRA = "akira"; + public static final String TAG_DEBUG_ASM = "math"; + public static final String TAG_DEBUG_TAP_COUNT = "countingx"; + public static final String TAG_DEBUG_BPOP_LTR = "bpop.ltr.mix"; + public static final String TAG_DEBUG_BPOP_WRD = "bpop.wrd"; + public static final String TAG_DEBUG_BPOP_PHON = "bpop.phon"; + public static final String TAG_DEBUG_BPOP_NUM = "bpop.num"; + public static final String TAG_DEBUG_BPOP_SHP = "bpop.shp"; + public static final String TAG_DEBUG_BPOP_EX = "bpop.ex"; + + public static final String ROBO_DEBUG_FILE_TAP_COUNT = "countingx_test.json"; + public static final String ROBO_DEBUG_FILE_AKIRA = "akira_test.json"; + public static final String ROBO_DEBUG_FILE_ASM = "math_test.json"; + public static final String ROBO_DEBUG_FILE_BPOP = "bpop.json"; + + + + // Debugger Thumb key words + // NEW_THUMBS trace me + public enum Thumb { + AKIRA, BPOP_NUM, GL, MN, BPOP_LTR, CX_1, CX_10, CX_100, MATH, NUMSCALE, STORY_1, STORY_2, STORY_3, STORY_4, STORY_5, SONG, STORY_NONSTORY, WRITE, SPELLING, PICMATCH, NUMCOMPARE, COMPREHENSION, PLACEVALUE, BIGMATH, NOTHING + } + + // debug vals + public static final String DEBUG_AUDIO_FILE = "DEBUG_AUDIO"; + + public static final String PLACEMENT_TAG = "DEBUG_PLACEMENT"; + public static final String MENU_BUG_TAG = "MENU_BUG"; + public static final String MATH_PLACEMENT = "MATH_PLACEMENT"; // TOGGLE_PLACEMENT trace me... + public static final String MATH_PLACEMENT_INDEX = "MATH_PLACEMENT_INDEX"; + public static final String WRITING_PLACEMENT = "WRITING_PLACEMENT"; + public static final String WRITING_PLACEMENT_INDEX = "WRITING_PLACEMENT_INDEX"; + + public static final String DEBUG_CSV = "DEBUG_CSV"; } diff --git a/util/src/main/java/cmu/xprize/util/TimerMaster.java b/util/src/main/java/cmu/xprize/util/TimerMaster.java new file mode 100644 index 0000000..7431112 --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/TimerMaster.java @@ -0,0 +1,155 @@ +package cmu.xprize.util; + +import android.content.Intent; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; + +import java.util.Date; + +import static cmu.xprize.util.consts.INTERVENTION_CONST.BROADCAST_GESTURE_UPDATE; +import static cmu.xprize.util.consts.INTERVENTION_CONST.BROADCAST_HESITATION_UPDATE; +import static cmu.xprize.util.consts.INTERVENTION_CONST.BROADCAST_STUCK_UPDATE; +import static cmu.xprize.util.consts.INTERVENTION_CONST.EXTRA_TIME_EXPECT; +import static cmu.xprize.util.TCONST.I_CANCEL_GESTURE; +import static cmu.xprize.util.TCONST.I_CANCEL_HESITATE; +import static cmu.xprize.util.TCONST.I_CANCEL_STUCK; +import static cmu.xprize.util.TCONST.I_TRIGGER_GESTURE; +import static cmu.xprize.util.TCONST.I_TRIGGER_HESITATE; +import static cmu.xprize.util.TCONST.I_TRIGGER_STUCK; + +/** + * TimerMaster + *

Use this class to manage timers for Interventions

+ * Created by kevindeland on 9/4/19. + */ + +public class TimerMaster { + + private IInterventionSource _intervention; + private CMessageQueueFactory _queue; + private LocalBroadcastManager _manager; + private long _hesitateDelay, _stuckDelay, _gestureDelay; + + private static final String HESITATION_TIMER_RUNNABLE = "HESITATION_TIMER"; + private static final String STUCK_TIMER_RUNNABLE = "STUCK_TIMER"; + private static final String GESTURE_TIMER_RUNNABLE = "GESTURE_TIMER"; + + // need a boolean so we only trigger this once + private boolean gestureTriggered; + + private String _TAG; + + + /** + * Constructor + * @param intervention has a "triggerIntervention" command + * @param queue responsible for posting delayed commands to the Queue. Has an IMessageQueueRunner + * associated with it that will know what to do when the timer has expired. + * @param hesitateDelay delay time to trigger HESITATE + * @param stuckDelay delay time to trigger STUCK + * @param gestureDelay delay time to trigger GESTURE + */ + public TimerMaster(IInterventionSource intervention, CMessageQueueFactory queue, + LocalBroadcastManager manager, String TAG, + long hesitateDelay, long stuckDelay, long gestureDelay) { + + this._intervention = intervention; + this._queue = queue; + this._manager = manager; + this._hesitateDelay = hesitateDelay; + this._stuckDelay = stuckDelay; + this._gestureDelay = gestureDelay; + this._TAG = TAG; + } + + + /** + * Stuck Timer only reset when a new problem begins + */ + public void resetStuckTimer() { + cancelStuckTimer(); + triggerStuckTimer(); + } + + private void cancelStuckTimer() { + Log.v(_TAG, "cancel stuck timer"); + _queue.cancelPost(STUCK_TIMER_RUNNABLE); + _intervention.triggerIntervention(I_CANCEL_STUCK); + } + + private void triggerStuckTimer() { + Log.v(_TAG, String.format("trigger stuck timer: %s, %s, %d", + STUCK_TIMER_RUNNABLE, I_TRIGGER_STUCK, _stuckDelay)); + _queue.postNamed(STUCK_TIMER_RUNNABLE, I_TRIGGER_STUCK, _stuckDelay); + + Intent stuckIntent = new Intent(BROADCAST_STUCK_UPDATE); + long expectedTrigger = (new Date()).getTime() + _stuckDelay; + stuckIntent.putExtra(EXTRA_TIME_EXPECT, expectedTrigger); + _manager.sendBroadcast(stuckIntent); + } + + /** + * This should be called whenever ANY View is touched... without overriding existing functions. + */ + public void resetHesitationTimer() { + cancelHesitationTimer(); + triggerHesitationTimer(); + } + + private void cancelHesitationTimer() { + Log.v(_TAG, "cancel hesitation timer"); + _queue.cancelPost(HESITATION_TIMER_RUNNABLE); + _intervention.triggerIntervention(I_CANCEL_HESITATE); + } + + private void triggerHesitationTimer() { + Log.v(_TAG, String.format("trigger hesitation timer: %s, %s, %d", + HESITATION_TIMER_RUNNABLE, I_TRIGGER_HESITATE, _hesitateDelay)); + _queue.postNamed(HESITATION_TIMER_RUNNABLE, I_TRIGGER_HESITATE, _hesitateDelay); + + Intent hesitateIntent = new Intent(BROADCAST_HESITATION_UPDATE); + long expectedTrigger = (new Date()).getTime() + _hesitateDelay; + hesitateIntent.putExtra(EXTRA_TIME_EXPECT, expectedTrigger); + _manager.sendBroadcast(hesitateIntent); + } + + /** + * This should be called when a normal gesture is made + */ + public void resetGestureTimer() { + _queue.cancelPost(GESTURE_TIMER_RUNNABLE); + _intervention.triggerIntervention(I_CANCEL_GESTURE); + + // note: must remove all gesture timers + _queue.postNamed(GESTURE_TIMER_RUNNABLE, I_TRIGGER_GESTURE, _gestureDelay); + gestureTriggered = true; + } + + /** + * trigger gesture timer + */ + public void triggerGestureTimer() { + if (gestureTriggered) return; // only trigger once + + _queue.postNamed(GESTURE_TIMER_RUNNABLE, I_TRIGGER_GESTURE, _gestureDelay); + gestureTriggered = true; + + Intent gestureIntent = new Intent(BROADCAST_GESTURE_UPDATE); + long expectedTrigger = (new Date()).getTime() + _gestureDelay; + gestureIntent.putExtra(EXTRA_TIME_EXPECT, expectedTrigger); + _manager.sendBroadcast(gestureIntent); + } + + /** + * cancel gesture timer + */ + public void cancelGestureTimer() { + if (!gestureTriggered) return; // don't need to cancel if not triggered + + _queue.cancelPost(GESTURE_TIMER_RUNNABLE); + _intervention.triggerIntervention(I_CANCEL_GESTURE); + gestureTriggered = false; + } + + +} diff --git a/util/src/main/java/cmu/xprize/util/View_Helper.java b/util/src/main/java/cmu/xprize/util/View_Helper.java index 7ae37f0..aee72b4 100644 --- a/util/src/main/java/cmu/xprize/util/View_Helper.java +++ b/util/src/main/java/cmu/xprize/util/View_Helper.java @@ -1,5 +1,5 @@ /** - Copyright 2015 Kevin Willows + Copyright(c) 2015-2017 Kevin Willows 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 diff --git a/util/src/main/java/cmu/xprize/util/Word2NumFSM.java b/util/src/main/java/cmu/xprize/util/Word2NumFSM.java index 79636c8..f065463 100644 --- a/util/src/main/java/cmu/xprize/util/Word2NumFSM.java +++ b/util/src/main/java/cmu/xprize/util/Word2NumFSM.java @@ -1,7 +1,6 @@ //********************************************************************************* // -// Copyright(c) 2016 Carnegie Mellon University. All Rights Reserved. -// Copyright(c) Kevin Willows All Rights Reserved +// Copyright(c) 2016-2017 Kevin Willows All Rights Reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/util/src/main/java/cmu/xprize/util/consts/INTERVENTION_CONST.java b/util/src/main/java/cmu/xprize/util/consts/INTERVENTION_CONST.java new file mode 100644 index 0000000..031116f --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/consts/INTERVENTION_CONST.java @@ -0,0 +1,22 @@ +package cmu.xprize.util.consts; + +/** + * RoboTutor + *

+ * Created by kevindeland on 2019-11-04. + */ +public class INTERVENTION_CONST { + + // whether to show the intervention button + public static final boolean CONFIG_INTERVENTION = true; + public static final boolean CONFIG_INTERVENTION_DEBUGGER = false; + + // broadcasting intervention updates + public static final String EXTRA_TIME_EXPECT = "TIME_EXPECT"; + public static final String BROADCAST_STUCK_UPDATE = "STUCK_UPDATE"; + public static final String BROADCAST_HESITATION_UPDATE = "HESITATE_UPDATE"; + public static final String BROADCAST_GESTURE_UPDATE = "GESTURE_UPDATE"; + public static final String BROADCAST_FAILURE_UPDATE = "FAILURE_UPDATE"; + public static final String FAILS_NEEDED = "FAILS_NEEDED"; + public static final String FAILS_HAPPENED = "FAILS_HAPPENED"; +} diff --git a/util/src/main/java/cmu/xprize/util/gesture/ExpectTapGestureListener.java b/util/src/main/java/cmu/xprize/util/gesture/ExpectTapGestureListener.java new file mode 100644 index 0000000..bdc124b --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/gesture/ExpectTapGestureListener.java @@ -0,0 +1,82 @@ +package cmu.xprize.util.gesture; + +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; + +import cmu.xprize.util.IInterventionSource; +import cmu.xprize.util.TCONST; +import cmu.xprize.util.TimerMaster; + +/** + * ExpectTapGestureListener + *

Expects a tap

+ * Created by kevindeland on 9/1/19. + */ + +public class ExpectTapGestureListener extends GestureDetector.SimpleOnGestureListener { + + private TimerMaster iTimer; + private String TAG = "TAP_GESTURE"; + + /** + * Intervention should not be triggered immediately, so the listener should trigger the + * gesture timer within the TimerMaster. + * + * @param timer a TimerMaster that triggers and resets the gesture timer + */ + public ExpectTapGestureListener(TimerMaster timer) { + this.iTimer = timer; + } + + @Override + public boolean onDown(MotionEvent event) { + Log.d(TAG,"onDown: "); + + // don't return false here or else none of the other + // gestures will work + return true; + } + + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + Log.i(TAG, "onSingleTapConfirmed: "); + iTimer.cancelGestureTimer(); + + return true; + } + + @Override + public void onLongPress(MotionEvent e) { + Log.i(TAG, "onLongPress: "); + iTimer.triggerGestureTimer(); + + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + Log.i(TAG, "onDoubleTap: "); + iTimer.triggerGestureTimer(); + + + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + Log.i(TAG, "onScroll: "); + + return true; + } + + @Override + public boolean onFling(MotionEvent event1, MotionEvent event2, + float velocityX, float velocityY) { + Log.d(TAG, "onFling: "); + iTimer.triggerGestureTimer(); + + return true; + } +} \ No newline at end of file diff --git a/util/src/main/java/cmu/xprize/util/gesture/ExpectWriteGestureListener.java b/util/src/main/java/cmu/xprize/util/gesture/ExpectWriteGestureListener.java new file mode 100644 index 0000000..36d526a --- /dev/null +++ b/util/src/main/java/cmu/xprize/util/gesture/ExpectWriteGestureListener.java @@ -0,0 +1,80 @@ +package cmu.xprize.util.gesture; + +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; + +import cmu.xprize.util.IInterventionSource; +import cmu.xprize.util.TCONST; +import cmu.xprize.util.TimerMaster; + +/** + * ExpectWriteGestureListener + *

Expects a write (aka fling/scroll)

+ * Created by kevindeland on 9/1/19. + */ + +public class ExpectWriteGestureListener extends GestureDetector.SimpleOnGestureListener { + + private TimerMaster iTimer; + private String TAG = "WRITE_GESTURE"; + + /** + * Intervention should not be triggered immediately, so the listener should trigger the + * gesture timer within the TimerMaster. + * + * @param timer a TimerMaster that triggers and resets the gesture timer + */ + public ExpectWriteGestureListener(TimerMaster timer) { + this.iTimer = timer; + } + + @Override + public boolean onDown(MotionEvent event) { + Log.d(TAG,"onDown: "); + + // don't return false here or else none of the other + // gestures will work + return true; + } + + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + Log.i(TAG, "onSingleTapConfirmed: "); + iTimer.triggerGestureTimer(); + return true; + } + + @Override + public void onLongPress(MotionEvent e) { + Log.i(TAG, "onLongPress: "); + iTimer.triggerGestureTimer(); + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + Log.i(TAG, "onDoubleTap: "); + iTimer.triggerGestureTimer(); + + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + Log.i(TAG, "onScroll: "); + + + return true; + } + + @Override + public boolean onFling(MotionEvent event1, MotionEvent event2, + float velocityX, float velocityY) { + Log.d(TAG, "onFling: "); + iTimer.resetGestureTimer(); + + return true; + } +} diff --git a/util/src/main/res/drawable/robotutor_normal.xml b/util/src/main/res/drawable/robotutor_normal.xml index 1f15dcf..7402709 100644 --- a/util/src/main/res/drawable/robotutor_normal.xml +++ b/util/src/main/res/drawable/robotutor_normal.xml @@ -13,7 +13,6 @@ android:pathData="M568,318H14c-6.6,0-12-5.4-12-12V14C2,7.4,7.4,2,14,2h554c6.6,0,12,5.4,12,12v292C580,312.6,574.6,318,568,318z" /> + + + android:layout_gravity="center" + android:indeterminate="false" + android:visibility="visible"/> + + + + + android:layout_margin="20dp" + android:gravity="center" + android:textSize="10sp" + android:textStyle="normal" + android:textColor="#FFFFFF" + android:visibility="invisible"/> \ No newline at end of file diff --git a/util/src/test/java/cmu/xprize/common/CTutorData_Metadata_Test.java b/util/src/test/java/cmu/xprize/common/CTutorData_Metadata_Test.java new file mode 100644 index 0000000..105557b --- /dev/null +++ b/util/src/test/java/cmu/xprize/common/CTutorData_Metadata_Test.java @@ -0,0 +1,215 @@ +package cmu.xprize.common; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; + +import cmu.xprize.util.CAt_Data; +import cmu.xprize.util.CFileNameHasher; +import cmu.xprize.util.CTutorData_Metadata; +import cmu.xprize.util.TCONST; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class CTutorData_Metadata_Test { + + + private CTutorData_Metadata metadata; + + private JSONObject devData; + private JSONObject mathTransitions; + private JSONObject writeTransitions; + private JSONObject storyTransitions; + + @Before + public void setup() throws IOException, JSONException { + + metadata = new CTutorData_Metadata(); + parseDevData(); + } + + @Test + public void testThumbs() throws Exception { + + JSONObject whichTransitions = storyTransitions; + + Iterator it = whichTransitions.keys(); + while(it.hasNext()) { + + String tutorKey = it.next(); + + // --- begin tutor Data --- + CAt_Data data = new CAt_Data(); + + + + data.loadJSON((JSONObject) whichTransitions.get(tutorKey), null); + + //System.out.println(tutorKey); + + TCONST.Thumb thumb = CTutorData_Metadata.getThumbImage(data); + + assertNotNull("Tutor: " + tutorKey + " bad result", thumb); + + } + } + + @Test + public void testWriteTransitions() throws Exception { + + JSONObject whichTransitions = writeTransitions; + + Iterator it = whichTransitions.keys(); + while(it.hasNext()) { + String tutorKey = it.next(); + + // --- begin tutor Data --- + CAt_Data data = new CAt_Data(); + + + + data.loadJSON((JSONObject) whichTransitions.get(tutorKey), null); + + //System.out.println(tutorKey); + + ArrayList displayText = CTutorData_Metadata.parseNameIntoLabels(data); + + assertNotNull("Tutor: " + tutorKey + " bad result", displayText); + + //assertEquals("Tutor: " + tutorKey + " doesn't begin with ID.", displayText.get(0), "" + tutorKey + ""); + + //assertTrue("Tutor: " + tutorKey + " has size of " + displayText.size(), displayText.size() > 1); + + + + if(true) continue; + + // --- begin tokenizing tutorID --- + String[] tokens = tutorKey.split(":"); + String prefix = tokens[0]; + String[] prefixTokens = prefix.split("\\."); + + String suffix = tokens[1]; + String[] suffixTokens = suffix.split("\\."); + + switch(prefixTokens[0]) { + + case "write": + + switch (prefixTokens[1]) { + case "ltr": + + System.out.println(data.tutor_id); + break; + + + case "wrd": + + System.out.println(Arrays.toString(suffixTokens)); + + switch (suffixTokens[0]) { + case "lc": + System.out.println(String.format("tutor: %s -- (%s, %s) -- %s", data.tutor_id, data.cell_column, data.cell_row, "lowercase")); + break; + + case "syl": + System.out.println(String.format("tutor: %s -- (%s, %s) -- %s", data.tutor_id, data.cell_column, data.cell_row, "syllables")); + } + + + break; + } + + break; + + + case "bpop": + + switch (prefixTokens[1]) { + + case "ltr": + + break; + + case "wrd": + + String wordCase; + + //System.out.println(String.format("tutor: %s\nBubblePop Words\nCase=%s", data.tutor_id, wordCase)); + break; + } + break; + + + } + } + } + + @Test + public void testMathTransitions() throws Exception { + + JSONObject whichTransitions = mathTransitions; + + Iterator it = whichTransitions.keys(); + while(it.hasNext()) { + + String tutorKey = it.next(); + + // --- begin tutor Data --- + CAt_Data data = new CAt_Data(); + + + + data.loadJSON((JSONObject) whichTransitions.get(tutorKey), null); + + //System.out.println(tutorKey); + + ArrayList displayText = CTutorData_Metadata.parseNameIntoLabels(data); + + assertNotNull("Tutor: " + tutorKey + " bad result", displayText); + + } + } + + private void parseDevData() throws IOException, JSONException { + + InputStream inputStream = null; + + inputStream = new FileInputStream("/Users/kevindeland/RoboTutor/RoboTutor/app/src/main/assets/tutors/activity_selector/assets/data/sw/dev_data.json"); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"), 8); + StringBuilder sb = new StringBuilder(); + + String line = null; + while ((line = reader.readLine()) != null) + { + sb.append(line + "\n"); + } + String result = sb.toString(); + + devData = new JSONObject(result); + + mathTransitions = (JSONObject) devData.get("mathTransitions"); + writeTransitions = (JSONObject) devData.get("writeTransitions"); + storyTransitions = (JSONObject) devData.get("storyTransitions"); + } + + +} \ No newline at end of file diff --git a/util/src/test/java/cmu/xprize/common/ExampleUnitTest.java b/util/src/test/java/cmu/xprize/common/ExampleUnitTest.java index 5a520dc..f42815b 100644 --- a/util/src/test/java/cmu/xprize/common/ExampleUnitTest.java +++ b/util/src/test/java/cmu/xprize/common/ExampleUnitTest.java @@ -1,7 +1,11 @@ -package cmu.xprize.util; +package cmu.xprize.common; + +import android.util.Log; import org.junit.Test; +import cmu.xprize.util.CFileNameHasher; + import static org.junit.Assert.*; /** @@ -12,4 +16,20 @@ public class ExampleUnitTest { public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } + + @Test + public void checkFileNameHasher() { + + CFileNameHasher hasher = CFileNameHasher.getInstance(); + + String[] inputs = {"nge", "n'ge", "n_ge"}; + + for (String input: + inputs) { + String hash = hasher.generateHash(input); + System.out.println("HASH_TEST for " + input + ":\t\t" + hash); + } + + } + } \ No newline at end of file From 0691463617a8dce7cfb8ef861e65eaf42b23d8c9 Mon Sep 17 00:00:00 2001 From: Kapil Verma Date: Sat, 15 May 2021 02:11:33 +0530 Subject: [PATCH 2/2] Changed TargetSDK Version to 22 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 5f90281..e6a18a5 100644 --- a/build.gradle +++ b/build.gradle @@ -26,12 +26,12 @@ allprojects { rtCompileSdkVersion=30 //Integer rtBuildToolsVersion="30.0.3" //String - rtMinSdkVersion=23 + rtMinSdkVersion=19 // Note that using target version 22 bypasses the new run-time permissions found // in Marshmallow 23 // - rtTargetSdkVersion=23 + rtTargetSdkVersion=22 rtVersionCode=1