├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── gradle.properties.sample ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── bridge_key.pem │ ├── html │ │ ├── app_privacy_policy.html │ │ ├── app_privacy_policy_style.css │ │ ├── asthma_fullconsent.html │ │ ├── consent_section_html_activities.html │ │ ├── consent_section_html_dataprocessing.html │ │ ├── consent_section_html_datause.html │ │ ├── consent_section_html_protectingdata.html │ │ ├── consent_section_html_quiz_headsup.html │ │ ├── consent_section_html_recontact.html │ │ ├── consent_section_html_security.html │ │ ├── consent_section_html_sensordata.html │ │ ├── consent_section_html_sharing_rsch.html │ │ ├── consent_section_html_study_survey.html │ │ ├── consent_section_html_study_task.html │ │ ├── consent_section_html_talktodoctor.html │ │ ├── consent_section_html_time.html │ │ ├── consent_section_html_welcome.html │ │ ├── consent_section_html_withdrawing.html │ │ ├── learn_aboutstudy.html │ │ ├── learn_eligibletoparticipate.html │ │ ├── learn_howstudyworks.html │ │ ├── learn_onlineresources.html │ │ ├── learn_overview.html │ │ ├── learn_style.css │ │ ├── learn_treatment.html │ │ ├── learn_whoisrunning.html │ │ ├── software_notices.html │ │ ├── study_overview_about.html │ │ ├── study_overview_app.html │ │ ├── study_overview_howstudyworks.html │ │ ├── study_overview_styles.css │ │ └── study_overview_whoisrunning.html │ ├── json │ │ ├── consent_section.json │ │ ├── learn.json │ │ ├── study_overview.json │ │ ├── survey │ │ │ ├── about_you.json │ │ │ ├── asthma_daily_prompt.json │ │ │ ├── asthma_history.json │ │ │ ├── asthma_medication_survey.json │ │ │ ├── asthma_weekly.json │ │ │ ├── medical_history.json │ │ │ ├── practice_survey.json │ │ │ └── your_asthma.json │ │ └── tasks_and_schedules.json │ ├── mp4 │ │ └── study_overview_video.mp4 │ └── pdf │ │ └── study_overview_consent_form.pdf │ ├── java │ └── org │ │ └── researchstack │ │ └── sampleapp │ │ ├── DashboardFragment.java │ │ ├── NotificationPermissionActivity.java │ │ ├── SampleApplication.java │ │ ├── SampleDataProvider.java │ │ ├── SamplePermissionResultManager.java │ │ ├── SampleResearchStack.java │ │ ├── SampleResourceManager.java │ │ ├── SampleSettingsActivity.java │ │ ├── SampleSettingsFragment.java │ │ ├── SampleTaskProvider.java │ │ ├── SampleUiManager.java │ │ └── bridge │ │ ├── BridgeDataArchive.java │ │ ├── BridgeDataInput.java │ │ ├── BridgeDataProvider.java │ │ ├── BridgeEncryptedDatabase.java │ │ ├── BridgeMessageResponse.java │ │ ├── FileInfo.java │ │ ├── IdentifierHolder.java │ │ ├── Info.java │ │ ├── UploadQueue.java │ │ ├── UploadRequest.java │ │ ├── UploadSession.java │ │ ├── UploadValidationStatus.java │ │ ├── UserSessionInfo.java │ │ └── body │ │ ├── ConsentSignatureBody.java │ │ ├── EmailBody.java │ │ ├── SharingOptionBody.java │ │ ├── SignInBody.java │ │ ├── SignUpBody.java │ │ ├── SurveyAnswer.java │ │ └── WithdrawalBody.java │ └── res │ ├── color │ ├── text_color_settings_summary.xml │ └── text_color_settings_title.xml │ ├── drawable-xhdpi │ ├── logo_disease_information_top.png │ ├── logo_disease_large.png │ ├── logo_institution.png │ ├── logo_lifemap.png │ ├── logo_msmc.png │ ├── logo_msmc_horiz.png │ ├── logo_mt_sinai_njh.png │ └── logo_research_institute.png │ ├── drawable │ └── bg_splash.xml │ ├── layout │ └── activity_permission_notification.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-v21 │ └── styles.xml │ └── values │ ├── colors.xml │ ├── ids.xml │ ├── strings.xml │ ├── styles.xml │ └── themes.xml ├── asthma_source_license ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mpower_source_license └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | /*/build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | app/crashlytics.properties 30 | /.idea/* 31 | /captures 32 | .DS_Store 33 | /tmp/ 34 | *.gz 35 | com_crashlytics_export_strings.xml 36 | *.iml 37 | 38 | app/gradle.properties 39 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'me.tatarka.retrolambda' 3 | apply plugin: 'com.neenbedankt.android-apt' 4 | 5 | android { 6 | compileSdkVersion 23 7 | buildToolsVersion '23.0.2' 8 | 9 | defaultConfig { 10 | applicationId 'org.researchstack.sampleapp' 11 | minSdkVersion 16 12 | targetSdkVersion 23 13 | versionCode 1 14 | versionName '1.0' 15 | multiDexEnabled true 16 | 17 | // To configure communications with Bridge Study Manager (https://research.sagebridge.org/), 18 | // create a 'gradle.properties' file in the same directory as this build.gradle and add 19 | // the needed values, for example studyId="bridge_study_id" to make them available as 20 | // BuildConfig.STUDY_ID, BuildConfig.STUDY_NAME, etc 21 | buildConfigField 'String', 'STUDY_ID', studyId 22 | buildConfigField 'String', 'STUDY_NAME', studyName 23 | buildConfigField 'String', 'STUDY_SUBPOPULATION_GUID', studyId // default subpopulation 24 | buildConfigField 'String', 'STUDY_BASE_URL', "\"https://webservices.sagebridge.org/\"" 25 | } 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 30 | } 31 | 32 | debug { 33 | debuggable true 34 | minifyEnabled false 35 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 36 | } 37 | } 38 | 39 | compileOptions { 40 | sourceCompatibility JavaVersion.VERSION_1_8 41 | targetCompatibility JavaVersion.VERSION_1_8 42 | } 43 | 44 | packagingOptions { 45 | exclude 'META-INF/LICENSE.txt' 46 | exclude 'META-INF/NOTICE.txt' 47 | } 48 | 49 | dexOptions { 50 | javaMaxHeapSize '4g' //specify the heap size for the dex process 51 | } 52 | } 53 | 54 | dependencies { 55 | compile fileTree(include: ['*.jar'], dir: 'libs') 56 | testCompile 'junit:junit:4.12' 57 | compile 'org.researchstack:skin:1.0.0.rc3' 58 | compile 'com.android.support:appcompat-v7:23.2.1' 59 | compile 'net.zetetic:android-database-sqlcipher:3.3.1-2@aar' 60 | apt 'co.touchlab.squeaky:squeaky-processor:0.4.0.0' 61 | compile 'com.android.support:multidex:1.0.1' 62 | compile 'com.madgag.spongycastle:core:1.54.0.0' 63 | compile 'com.madgag.spongycastle:prov:1.54.0.0' 64 | compile 'com.madgag.spongycastle:pkix:1.54.0.0' 65 | } 66 | -------------------------------------------------------------------------------- /app/gradle.properties.sample: -------------------------------------------------------------------------------- 1 | # if using Sage Bridge, add these two lines with your study info to gradle.properties 2 | studyId="STUDY_ID_HERE" 3 | studyName="STUDY_NAME_HERE" 4 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/bradleymcdermott/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 39 | 40 | 46 | 47 | 52 | 53 | 58 | 59 | 64 | 65 | 70 | 71 | 77 | 78 | 82 | 83 | 86 | 87 | 90 | 91 | 96 | 97 | 102 | 103 | 104 | 105 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /app/src/main/assets/bridge_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFBTCCA+2gAwIBAgIHA1kS9MliDzANBgkqhkiG9w0BAQUFADCBszELMAkGA1UE 3 | BhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxlMRkwFwYDVQQKDBBT 4 | YWdlIEJpb25ldHdvcmtzMQ8wDQYDVQQLDAZCcmlkZ2UxJDAiBgkqhkiG9w0BCQEW 5 | FWJyaWRnZUlUQHNhZ2ViYXNlLm9yZzEzMDEGA1UEAwwqaHR0cHM6Ly93ZWJzZXJ2 6 | aWNlcy1zdGFnaW5nLnNhZ2VicmlkZ2Uub3JnMB4XDTE1MDgxNTE4MTgzMVoXDTQz 7 | MDEwMTE4MTgzMVowgbMxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UE 8 | BwwHU2VhdHRsZTEZMBcGA1UECgwQU2FnZSBCaW9uZXR3b3JrczEPMA0GA1UECwwG 9 | QnJpZGdlMSQwIgYJKoZIhvcNAQkBFhVicmlkZ2VJVEBzYWdlYmFzZS5vcmcxMzAx 10 | BgNVBAMMKmh0dHBzOi8vd2Vic2VydmljZXMtc3RhZ2luZy5zYWdlYnJpZGdlLm9y 11 | ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMZrNwftQ9Op0MWv4ioR 12 | 3yqM69o7OqwHKZ22ZwVL1LK8cZqsOLhU9tfePX5GpHkwbiYpdnjKjkbFOQJi0Xa+ 13 | teQA2NLrmIKeIJNsyUNi5YJ9r0ws7hgziR7EtNm3JhtwPcQjtfgdUzZeabdV5xgz 14 | UWgIbRNGQ4UUjZfL/tYzeb4qaLiccrlx2dVcR7jBYMzZdBDgMR6czKo/hEX0F22f 15 | h0GpXg1anOHljHbyoK26JihbZ4v/DBnN12A+k/nU6PUNXQ5vtiWYM8olhOQB73ht 16 | OuVqCHKggFrawNJ3OOBsEctRcUHPnFvP6dhiipmwCHJxQGuXW83bTTJOrAkIsedH 17 | 2DMCAwEAAaOCARowggEWMAwGA1UdEwQFMAMBAf8wgeYGA1UdIwSB3jCB24AU4PdO 18 | tJF4/If6CsCB1L4UmRxlLPKhgbmkgbYwgbMxCzAJBgNVBAYTAlVTMQswCQYDVQQI 19 | DAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEZMBcGA1UECgwQU2FnZSBCaW9uZXR3b3Jr 20 | czEPMA0GA1UECwwGQnJpZGdlMSQwIgYJKoZIhvcNAQkBFhVicmlkZ2VJVEBzYWdl 21 | YmFzZS5vcmcxMzAxBgNVBAMMKmh0dHBzOi8vd2Vic2VydmljZXMtc3RhZ2luZy5z 22 | YWdlYnJpZGdlLm9yZ4IHA1kS9MliDzAdBgNVHQ4EFgQU4PdOtJF4/If6CsCB1L4U 23 | mRxlLPIwDQYJKoZIhvcNAQEFBQADggEBADtBFpCKGS3DkSq2reWFRdqkb7kCH97j 24 | dRWQgNWZ94NrKVvWFssIhPst/a48M88ZteK5EWpP0FeO/OMLVHIKPf8gW+FIy0HQ 25 | 133sJvWv92oPW+aZaDWgZK3dOOWkVJc5RJVoL6xAK9CKkqJ/Zrvc9Uj0eTwKzir0 26 | xTW7l65WmeKmJqh+ursAkgllWGT/JuQbVJXMvi+NZ9L89pc/xBmvd7czS7QWtI7M 27 | cBGDZCzEI7WP3s7v0TIXNpPHJfZ7yjtuoys80S+6V1saH8Zqx28/FxrCpQeLAFEA 28 | pOA5siYf1cimM438JN4EUacEw9czUa4gI3Ku6Azdx4lG4jJDescvVYA= 29 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /app/src/main/assets/html/app_privacy_policy.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Privacy Policy 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 |

Privacy Policy Here

18 | 19 |
Privacy Sub Header
20 | 21 |

22 | Lorem ipsum dolor sit amet, eu pro summo vivendo adipiscing, modo deserunt no qui. Eos 23 | aliquid referrentur ex, accumsan abhorreant eu ius. Ad pro magna explicari 24 | concludaturque, vis fastidii torquatos consetetur te. Cu eam tempor aperiri oporteat. 25 | Pro porro erant at. Eu nec melius epicurei. 26 |

27 | Unum repudiandae in vis, mel moderatius incorrupte ex. Sed eius ferri eloquentiam eu, 28 | mea eu sumo numquam. Quas dicunt ad pro, no per natum everti suscipiantur, est id 29 | integre fuisset dolores. Qui an deseruisse voluptatibus. Ei per habeo appareat 30 | omittantur, paulo fuisset patrioque nam ex. 31 |

32 | Errem adipisci ei has. Civibus comprehensam eu qui, paulo altera instructior duo at. At 33 | qui choro dicam malorum, ius ex latine scribentur. Eam eu option gloriatur, essent 34 | utroque copiosae ut duo, exerci neglegentur ea usu. Ferri error et qui, ridens 35 | consetetur repudiandae mel at. Nec ex meis viris. 36 |

37 | An timeam adversarium eos, ut nam choro tibique argumentum. Scripserit suscipiantur vim 38 | at. Vide veniam facilisi at duo, eum veri honestatis ei. Odio movet efficiendi cu eos, 39 | numquam delicata suscipiantur sit ne. Ea sea debet epicurei, altera nostro impedit eum 40 | et, ne adhuc omnium pertinax vim. 41 |

42 | Ne accusam ocurreret molestiae vel. Lorem democritum cum eu. Vix fugit melius saperet 43 | ei. Ei esse option efficiantur vis, sonet insolens no pri. At qui mazim omittam, ad 44 | petentium accusamus mea. 45 |

46 | 47 |
48 | 49 |
50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/assets/html/app_privacy_policy_style.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | body { 4 | font-family: sans-serif; 5 | font-style: normal; 6 | color:#000000; 7 | line-height:normal; 8 | font-weight:normal; 9 | } 10 | 11 | h3 { 12 | padding-top:12px; 13 | padding-bottom:12px; 14 | margin: 0px; 15 | } 16 | 17 | h5 { 18 | color:#009891; 19 | padding-top:16px; 20 | padding-bottom:16px; 21 | margin-top: 8px; 22 | margin-bottom: 0px; 23 | font-size:12pt; 24 | } 25 | 26 | p { 27 | font-size:12pt; 28 | padding-top:8px; 29 | padding-bottom:8px; 30 | margin: 0px; 31 | } 32 | 33 | p.a { 34 | padding-top:16px; 35 | padding-bottom:16px; 36 | } 37 | 38 | a, a:active { 39 | color:#255aa8; 40 | font-weight: bold; 41 | text-decoration: none; 42 | } 43 | 44 | .content { 45 | padding: 8px 16px 16px 50px; 46 | } 47 | 48 | img { 49 | display: block; 50 | padding: 0; 51 | margin: 0 auto; 52 | max-height: 100%; 53 | max-width: 100%; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_activities.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 |

We will ask you to:

13 | 20 |

21 | The 22 | MyHeartCounts 23 | app will ask you to do 3 activities: 1) use your phone, or any wearable activity device you 24 | have, to collect activity data for 7 days; 2) perform a 6-minute walk test of fitness; and 3) 25 | enter information about risk factors and blood tests to calculate your American Heart 26 | Association risk score. 27 |

28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_dataprocessing.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 | 14 | 15 |

If you use a personal health device with your iPhone (such as a Apple Watch) and use iOS8 or 16 | later (which includes Apple HealthKit), you can choose to include the data from the device in 17 | the study. You can set our app to get all, some, or none of the data. You can choose not to do 18 | this and still participate in the study.

19 | 20 |

21 | You will have the choice to use the asthma app to receive location-specific information such as 22 | weather and air quality in your area. If you choose to do so, the asthma app will use the 23 | location of your phone to send you information about your general area. The asthma app will 24 | record the location of your iPhone (in latitude/longitude coordinate form) as often as once per 25 | hour when the app is open in order to help identify location-specific triggers, such as point 26 | sources of industrial pollution that may make your asthma worse. You are free to turn off the 27 | asthma app's access to your location. If you do this, your location will not be recorded and you 28 | will not receive location-specific information about air quality.

29 | 30 |

We will NOT access your personal contacts, other applications, phone use habits, text messages, 31 | personal photos, or websites visited.

32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_datause.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 |

Your study data without information to identify you may be shared with researchers, but we will 14 | not share your personal information or study information with any commercial third parties such 15 | as advertisers.

16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_protectingdata.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 | 14 |

We will work hard to protect your privacy. A random code instead of your name or other 15 | information that could identify you will be used on all your study data. Researchers will only 16 | analyze data without information that identifies you.

17 | 18 |

Your study data will be coded and stored in a manner that keeps your information as safe as 19 | possible and prevents unauthorized people from getting to your data. Even with removal of your 20 | personal information and these procedures, it is sometimes possible to re-identify an 21 | individual. This risk, while very low, should still be considered before enrolling.

22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_quiz_headsup.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 | 14 |

Before completing the enrollment and creating your study account, we will assess your 15 | understanding and eligibility to provide consent. It is important that you understand what the 16 | study is about and what is involved. You should not join the research study until all of your 17 | questions are answered. Feel free to contact the study sponsor with any questions.

18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_recontact.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 | 14 |

The study team may ask permission to contact you if needed. If you do not want the study team to 15 | contact you, you will have a chance to opt out of being contacted.

16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_security.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 | 14 |

Study data will be stored on Amazon Web Services servers under the control of Mount Sinai and 15 | Sage Bionetworks, a non-profit research institution. No one at Amazon Inc. will have access to 16 | the data.

17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_sensordata.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 |

There are sensors in your phone that can help assess activity, plus Apple’s Health app on your 14 | phone can store health and activity data from other devices. 15 |

16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_sharing_rsch.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 | 14 |

This study gives you the option to share your data in 2 ways:

15 | 16 |

1- Share with the world: You can choose to share your coded study data with qualified researchers 17 | worldwide for use in this research and beyond. Coded study data is data that does not include 18 | personal information such as your name or email. Qualified researchers are registered users of 19 | Synapse who have agreed to use the data in an ethical manner for research purposes, and have 20 | agreed to not attempt to re-identify you. If you choose to share your coded study data, the 21 | coded data will be added to a shared dataset available to qualified researchers on the Sage 22 | Bionetworks Synapse servers. (www.synapse.org). 23 | Sage Bionetworks will have no oversight on the future research that qualified researchers may 24 | conduct with the coded study data.

25 | 26 |

 2- Share with the Institution and its partners only: You can choose to share your study data only 27 | with the study team and its partners. The study team includes the sponsor of the research and 28 | any other researchers or partners named in the consent document. Sharing your data only with the 29 | (Institution) means that your data will not be made available to anyone other than those listed 30 | in the consent document and for the purposes of this study only.

31 | 32 |

If required by law, your data (study data and account information), and the signed consent form 33 | may be disclosed to:

34 | 43 |

The results of this research study may be presented at meetings or in publications. If the 44 | results of this study are made public, only coded study data will be used, that is, your 45 | personal information will not be disclosed.

46 | 47 |

You can change the data sharing setting though the app preference at anytime.  For additional 48 | information review the study website at http://apps.icahn.mssm.edu/asthma/ 49 |

50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_study_survey.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 |

The study will take place completely by using this app. While you use the app to monitor your 14 | asthma, we will study how well the app works and use the information to make the app better.

15 | 16 |

The app will ask you questions about your background, your asthma, the asthma medicines you take, 17 | and other questions about your health. Certain questions have to be completed for the app to 18 | work as designed, such as entering your asthma medicines so the app can give you reminders.

19 | 20 |

You will be asked daily questions about your asthma symptoms and use of quick relief medicine. 21 | You can choose the time each day to get your reminder and do your questions. Be mindful of the 22 | alert sound for your notifications if you don’t want anyone to know you are in the study. You 23 | can turn off the sound if you want. Once each week you will be asked about any major things that 24 | happened such as visits to your doctor, the emergency department, the hospital, or changes in 25 | asthma medication. 26 | 27 |

We will not collect information from your medical records. By answering these questions, the app 28 | will keep track of how well your asthma is controlled and you can compare how you are doing to 29 | other app users. There will be questions and surveys at the beginning and end of the study. 30 | Apart from the daily and weekly questions and your medicines, you are free to skip anything that 31 | makes you uncomfortable.

32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_study_task.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 | 14 |

Possible benefits may be that what we learn from the study may help doctors better understand 15 | asthma and design better mobile applications. During the study you will be able to track what is 16 | happening with your asthma and your health overall. You may learn more about asthma and 17 | monitoring your health that may possibly help you and your doctor to keep you healthy. It is 18 | possible that neither you nor others may benefit from taking part in this research.

19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_talktodoctor.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 |

You should continue your asthma care with your usual doctor if you need help with your asthma 14 | during the study, after the study and also if you choose not to join the study. If you 15 | participate in this research study, you should also tell any health care providers who treat you 16 | that you are in the study.

17 | 18 |

There are risks, discomforts, and inconveniences with any research study.

19 | 20 |

This study does not provide any compensation, health or medical care to participants. If you are 21 | injured as a direct result of your participation in this study, the Principal Investigator and 22 | the research study staff will assist you in obtaining appropriate medical treatment. Email us at 23 | asthmamobilehealth@mssm.edu if you feel you 24 | have been injured as a direct result of your participation in this study. Your medical 25 | insurance, managed care plan, or other benefits program will be billed for this treatment. You 26 | will be responsible for any associated co-payment or deductibles as required by your insurance. 27 |

28 | 29 |

If costs of care related to such an injury are not covered by your medical insurance, managed 30 | care plan or other benefits program, you may be responsible for these costs. The sponsor will 31 | not pay charges that your insurance does not cover. No payment is available from the study 32 | sponsor.

33 | 34 |

Using the app will take some time. Some questions may make you feel uncomfortable. Other people 35 | may see the app or your reminders on your phone and realize you are enrolled in this study and 36 | that you have asthma. This could cause embarrassment or self-consciousness.

37 | 38 |

Be safe. Do not do study tasks while driving. Wait until you are in a safe place to perform 39 | tasks.

40 | 41 |

Data collected in this study will count against your existing mobile data plan, but you can set 42 | up the “asthma app” to only use Wi-Fi connections to limit impact on your data plan.

43 | 44 |

Participation in this study may involve risks that are not known at this time. 45 | 46 |

47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_time.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 | 14 |

Giving consent, registering for the study and answering the first set of questions should take 20 15 | minutes. You can stop and restart where you left off at any time. Using the app in this study 16 | will involve daily and weekly tasks that should only take a minute or two each day to complete, 17 | for a total of 15 minutes each week. There will also be special tasks you will be asked to do at 18 | 3 months and 6 months into the study. Your participation in the study will last for 6 months 19 | from the day you sign up.

20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_welcome.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 |

The purpose of this study is to understand whether using an asthma mobile health 18 | application Version 2.0 (AMHA2.0 or “asthma app”) that you download onto your iPhone 19 | will help you to monitor your asthma.

20 | 21 |

The study is funded by the Icahn School of Medicine at Mount Sinai with technology 22 | support from Apple, Sage Bionetworks, and LifeMap Solutions. We expect that hundreds or 23 | thousands of people may join this study.

24 | 25 |

26 | People with asthma do best if they actively manage their asthma. This app uses your 27 | mobile phone to help you do this. If you take daily asthma medicine, the app will help 28 | you remember to take it on schedule. There are links to learn more about asthma, how to 29 | use your medicine, what triggers your asthma, and how to recognize when your asthma is 30 | out of control. The information you put into the app will help you track how well your 31 | asthma is controlled and how you are doing compared to other app users with asthma. 32 | Although we will look at the data at different points during the study, we will not be 33 | looking at your asthma information each day. The app does not replace your usual medical 34 | care, so if your asthma is getting worse during the study, please reach out to your 35 | health care team.

36 | 37 |

By using this app, you will join a research study, so we will need you to formally give 38 | your consent. We will now tell you what is involved so you can make an informed choice. 39 | Take time to read the full written consent at the end of this signup process before 40 | making your choice. If you have any questions or anything is unclear, stop and email the 41 | study team at asthmamobilehealth@mssm.edu before you sign the consent. If you contact us 42 | by email, you can also ask us to call you with more information or answers to your 43 | questions.

44 | 45 |

You cannot participate in this study if you:

46 | 47 | 51 | 52 |

To join the study, you need to:

53 | 54 | 59 | 60 |

If you are younger than 18 or don’t meet these qualifications, please stop now.

61 | 62 |

If you have any questions or anything is unclear, stop and email the study team at 63 | asthmamobilehealth@mssm.edu before you sign the consent. If you contact us by email, you 64 | can also ask us to call you with more information or answers to your questions.

65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /app/src/main/assets/html/consent_section_html_withdrawing.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Consent Docs 9 | 10 | 11 | 12 | 13 |

There will be no penalty to you if you decide not to take part in this study. To stop being in 14 | the study at any time, choose “Leave Study” in the profile section, delete the “asthma app” from 15 | your phone or contact the study team by email. If you withdraw from the study, we will stop 16 | collecting new data, but any data already collected will remain as part of the study. The study 17 | team may also withdraw you from the study at any time for any reason.

18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/assets/html/learn_aboutstudy.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 |

Sample Title

18 | 19 |
Sample Sub Header
20 | 21 |

22 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt 23 | ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation 24 | ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 25 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur 26 | sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id 27 | est laborum 28 |

29 | 30 |

 

31 | 32 |

33 |

LOGO
36 |

37 | 38 |
39 |
40 | 41 |
42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/assets/html/learn_eligibletoparticipate.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |

Eligibility

16 | 17 |

18 | App Logo 21 |

22 | 23 |

24 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt 25 | ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation 26 | ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 27 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur 28 | sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id 29 | est laborum 30 | 31 |

    32 |
  • Be 18 years of age or older
  • 33 |
  • Have doctor-diagnosed robot
  • 34 |
  • Have been prescribed medication for robot (including robot pills)
  • 35 |
  • Not be pregnant
  • 36 |
  • Live in the United States of America
  • 37 |
38 |

39 |
40 |
LOGO
43 |
44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/assets/html/learn_howstudyworks.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |

How it works

16 | 17 |

App Logo 20 |

21 | 22 |

As a participant in our Robot research study, you’ll be prompted to complete daily and 23 | weekly tasks that should only take a minute or two a day to complete, for a total of 15 24 | minutes each week. The study lasts for 6 months.

25 | 26 |

The app helps you track your condition, and gives you feedback on your progress. 27 |

28 | 29 |

30 |

LOGO
33 |

34 |
35 |
36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/assets/html/learn_onlineresources.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 |

Resources

18 | 19 |
General Links
20 | 21 |

22 | National Robot Health Website - General Information 23 |

24 | 25 |
Informative Videos on Robots
26 | 27 |

28 | Robot 29 | Basics

30 | 31 |

32 | Robot 33 | Medication

34 | 35 |

36 | Robot 37 | Triggers and Management

38 | 39 |

40 | Monitoring 41 | Your Robot

42 | 43 |

44 | What 45 | Causes Robots and Robotics

46 | 47 | 48 |
Videos About robot lasers
49 | 50 |

Whats a Robot laser

51 | 52 |

Using an Robot laser Gun

53 | 54 |

How 55 | to use a robot laser with Mask

56 | 57 |

 

58 | 59 |
62 | 63 | 64 |
65 | 66 |
67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/assets/html/learn_overview.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |

Overview Title

17 | 18 |
Overview Sub Header
19 | 20 |

21 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt 22 | ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation 23 | ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 24 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur 25 | sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id 26 | est laborum 27 |

28 | 29 |

32 |
33 | 34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/assets/html/learn_style.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | body { 4 | font-family: sans-serif; 5 | font-style: normal; 6 | color:#000000; 7 | line-height:normal; 8 | font-weight:normal; 9 | } 10 | 11 | h3 { 12 | padding-top:12px; 13 | padding-bottom:12px; 14 | margin: 0px; 15 | } 16 | 17 | h5 { 18 | color:#009891; 19 | padding-top:16px; 20 | padding-bottom:16px; 21 | margin-top: 8px; 22 | margin-bottom: 0px; 23 | font-size:12pt; 24 | } 25 | 26 | p { 27 | font-size:12pt; 28 | padding-top:8px; 29 | padding-bottom:8px; 30 | margin: 0px; 31 | } 32 | 33 | p.a { 34 | padding-top:16px; 35 | padding-bottom:16px; 36 | } 37 | 38 | a, a:active { 39 | color:#255aa8; 40 | font-weight: bold; 41 | text-decoration: none; 42 | } 43 | 44 | .content { 45 | padding: 8px 16px 16px 50px; 46 | } 47 | 48 | img { 49 | display: block; 50 | padding: 0; 51 | margin: 0 auto; 52 | max-height: 100%; 53 | max-width: 100%; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/assets/html/learn_treatment.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |

Treatment Title

17 | 18 |
Treatment Sub Header
19 | 20 |

21 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt 22 | ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation 23 | ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 24 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur 25 | sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id 26 | est laborum 27 |

28 |
29 | 30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/assets/html/learn_whoisrunning.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 |

This Study Is Run By

18 | 19 |

22 | 23 |

24 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt 25 | ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation 26 | ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 27 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur 28 | sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id 29 | est laborum 30 |

31 | 32 |

With educational content provided by And technology powered by

33 | 34 |

36 | 37 |
38 | 39 |
40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/assets/html/study_overview_about.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |

Our team at the Icahn School of Medicine at Mount Sinai invites you to join our Asthma 16 | Mobile Health study. This app is a personalized tool that helps you to gain greater 17 | insight into your asthma, adhere to treatment plans, avoid triggers, and take charge of 18 | your health.

19 | 20 |

21 | This app is designed for research and educational purposes only. You should not rely on 22 | this information as a substitute for personal medical attention, diagnosis or hands-on 23 | treatment. If you are concerned about your health or that of a child, please consult 24 | your family's health provider immediately. Do not wait for a response from our 25 | professionals.

26 | 27 |

View privacy policy

28 | 29 |
30 | 31 |
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/assets/html/study_overview_app.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |

16 | This app is designed for research and educational purposes only. You should not rely on 17 | this information as a substitute for personal medical attention, diagnosis or hands-on 18 | treatment. If you are concerned about your health or that of a child, please consult 19 | your family's health provider immediately. Do not wait for a response from our 20 | professionals.

21 | 22 |

Please tap on the image below to watch a short video about the robot study and this app.

23 | 24 |
25 |
26 | 27 |

28 | 29 |
30 | 31 |
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/assets/html/study_overview_howstudyworks.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |

As a participant in our Asthma research study, you’ll be prompted to complete daily and 16 | weekly tasks that should only take a minute or two a day to complete, for a total of 15 17 | minutes each week. The study lasts for 6 months.

18 | 19 |

The app helps you track your condition, and gives you feedback on your progress.

20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/assets/html/study_overview_styles.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | body { 4 | font-family: sans-serif; 5 | font-style: normal; 6 | color:#000000; 7 | line-height:normal; 8 | font-weight:normal; 9 | } 10 | 11 | h3 { 12 | padding-top:12px; 13 | padding-bottom:12px; 14 | margin: 0px; 15 | } 16 | 17 | h5 { 18 | color:#009891; 19 | padding-top:16px; 20 | padding-bottom:16px; 21 | margin-top: 8px; 22 | margin-bottom: 0px; 23 | font-size:12pt; 24 | } 25 | 26 | p { 27 | font-size:12pt; 28 | padding-top:8px; 29 | padding-bottom:8px; 30 | margin: 0px; 31 | } 32 | 33 | p.a { 34 | padding-top:16px; 35 | padding-bottom:16px; 36 | } 37 | 38 | a, a:active { 39 | color:#2196f3; 40 | font-weight: bold; 41 | text-decoration: none; 42 | } 43 | 44 | .content { 45 | padding: 8px 8px 8px 8px; 46 | } 47 | 48 | img { 49 | display: block; 50 | padding: 0; 51 | margin: 0 auto; 52 | max-height: 100%; 53 | max-width: 100%; 54 | } 55 | 56 | 57 | /* 58 | a { 59 | color:#39F 60 | background-color: #ffffff; 61 | } 62 | 63 | a:active { 64 | color:#39F 65 | background-color: #ffffff; 66 | } 67 | 68 | body { 69 | font-family: sans-serif; 70 | font-style: normal; 71 | font-size:16pt; 72 | color:#000000; 73 | line-height:normal; 74 | font-weight:normal; 75 | padding-top:0px; 76 | padding-bottom:20px; 77 | } 78 | 79 | p, h2 { 80 | margin-top: 0; 81 | padding-right: 0px; 82 | padding-left: 0px; 83 | 84 | } 85 | 86 | h1 { 87 | font-family: sans-serif-light; 88 | font-size:19pt; 89 | color:#000000; 90 | line-height:normal; 91 | font-weight:normal; 92 | padding-top:0px; 93 | padding-bottom:0px; 94 | text-align:center; 95 | } 96 | 97 | .footer { 98 | background-color: #FFFFFF; 99 | width: 20%; 100 | margin: 0 auto; 101 | } 102 | 103 | .content { 104 | padding: 10px 10px 10px 10px; 105 | } 106 | 107 | .content ul, .content ol { 108 | padding: 0 10px 15px 15px; 109 | } 110 | 111 | img { 112 | display: block; 113 | padding: 0; 114 | margin: 0 auto; 115 | max-height: 100%; 116 | max-width: 100%; 117 | } 118 | 119 | bold { 120 | font-family:sans-serif-medium; 121 | font-size:16pt; 122 | color:#000000; 123 | line-height:normal; 124 | font-weight:normal; 125 | padding-top:0px; 126 | padding-bottom:0px; 127 | } 128 | 129 | ul, ol { 130 | font-family: sans-serif; 131 | font-style: normal; 132 | font-size:16pt; 133 | color:#000000; 134 | list-style-type: square; 135 | list-style-position: outside; 136 | padding-left:23px; 137 | line-height:115%; 138 | 139 | } 140 | 141 | .ExternalClass * { 142 | line-height: 100% 143 | } 144 | */ -------------------------------------------------------------------------------- /app/src/main/assets/html/study_overview_whoisrunning.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |

This Study Is Run By

16 | 17 |

18 | 19 |

20 | 21 |

22 |

With educational content provided by
23 |

24 | 25 |

26 |

27 |

28 | 29 |

30 |

And technology powered by
31 |

32 | 33 |

34 | 35 |

36 | 37 |
38 | 39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/assets/json/consent_section.json: -------------------------------------------------------------------------------- 1 | { 2 | "documentProperties": { 3 | "htmlDocument": "asthma_fullconsent", 4 | "investigatorShortDescription": "Mount Sinai", 5 | "investigatorLongDescription": "Mount Sinai and its partners", 6 | "htmlContent": "consent_section_html_sharing_rsch", 7 | "requiresSignature": true, 8 | "requiresName": true, 9 | "requiresBirthdate": true 10 | }, 11 | "quiz": { 12 | "questions": [ 13 | { 14 | "identifier": "instruction", 15 | "prompt": "Comprehension", 16 | "text": "Let's do a quick and simple test of your understanding of this study.", 17 | "type": "instruction" 18 | }, 19 | { 20 | "identifier": "purpose", 21 | "prompt": "What is the purpose of this study?", 22 | "type": "singleChoiceText", 23 | "textChoices": [ 24 | "Understand the fluctuations of Parkinson disease symptoms", 25 | "Treating Parkinson disease" 26 | ], 27 | "expectedAnswer": "0" 28 | }, 29 | { 30 | "identifier": "question1", 31 | "prompt": "If my asthma gets worse during the study I should stop relying on the app and see my doctor.", 32 | "type": "boolean", 33 | "positiveFeedback": "The app does not replace your usual medical care. Continue asthma care with your usual doctor at all times and let him/her know you are in the study. If your asthma is getting worse at any time, including during the study, please contact your doctor.", 34 | "negativeFeedback": "The app does not replace your usual medical care. Continue asthma care with your usual doctor at all times and let him/her know you are in the study. If your asthma is getting worse at any time, including during the study, please contact your doctor.", 35 | "expectedAnswer": "true" 36 | }, 37 | { 38 | "identifier": "question2", 39 | "prompt": "Once I start participating in the study, I am free to withdraw at any time but the data I contributed will not be deleted.", 40 | "type": "boolean", 41 | "positiveFeedback": "You are free to withdraw at any time from the study. To withdraw, choose “Leave Study” on the app's “Profile” page, delete the app from your phone, or contact the researchers. Data you have contributed up until you withdraw will stay in the study but no further data will be gathered.", 42 | "negativeFeedback": "You are free to withdraw at any time from the study. To withdraw, choose “Leave Study” on the app's “Profile” page, delete the app from your phone, or contact the researchers. Data you have contributed up until you withdraw will stay in the study but no further data will be gathered.", 43 | "expectedAnswer": "true" 44 | }, 45 | { 46 | "identifier": "question3", 47 | "prompt": "This app is a research study and not a commercial application.", 48 | "positiveFeedback": "This is a research study and not a commercial application.", 49 | "negativeFeedback": "This is a research study and not a commercial application.", 50 | "type": "boolean", 51 | "expectedAnswer": "true" 52 | } 53 | ], 54 | "successTitle": "Great Job!", 55 | "successMessage": "You answered all of the questions correctly. Tap Next to continue.", 56 | "failureTitle": "Try again", 57 | "failureMessage": "Unfortunately, you answered one or more questions incorrectly on the quiz. We need you to repeat the quiz to make sure you understand what you need to know about the study. Please read the consent information and take the quiz again.", 58 | "allowedFailures": 0, 59 | "incorrectIcon": "rss_ic_quiz_retry", 60 | "correctIcon": "rss_ic_quiz_valid" 61 | }, 62 | "sections": [ 63 | { 64 | "sectionType": "overview", 65 | "sectionSummary": "Mount Sinai is doing a research study about whether an asthma app on your iPhone will help you to monitor your asthma. This simple walkthrough will help you to understand the study, the impact it will have on your life, and will allow you to provide consent to participate.", 66 | "sectionHtmlContent": "consent_section_html_welcome" 67 | }, 68 | { 69 | "sectionType": "custom", 70 | "sectionTitle": "We'll Test Your Understanding", 71 | "sectionSummary": "We’ll walk you through some information on our Asthma research study, and provide a short quiz at the end to confirm your understanding.", 72 | "sectionHtmlContent": "consent_section_html_quiz_headsup" 73 | }, 74 | { 75 | "sectionType": "dataGathering", 76 | "sectionSummary": "This study will gather location and sensor data from your phone and personal fitness devices (such as Apple Watch) with your permission. You can choose not to do this and still participate in the study.", 77 | "sectionHtmlContent": "consent_section_html_dataprocessing" 78 | }, 79 | { 80 | "sectionType": "privacy", 81 | "sectionSummary": "To protect your privacy, we will use a random code instead of your name on your study data.", 82 | "sectionHtmlContent": "consent_section_html_protectingdata" 83 | }, 84 | { 85 | "sectionType": "dataUse", 86 | "sectionTitle": "Data Use", 87 | "sectionSummary": "We will not share your information with any commercial third parties such as advertisers.", 88 | "sectionHtmlContent": "consent_section_html_datause" 89 | }, 90 | { 91 | "sectionType": "timeCommitment", 92 | "sectionSummary": "Your participation in this study will average 15 minutes each week for 6 months.", 93 | "sectionHtmlContent": "consent_section_html_time" 94 | }, 95 | { 96 | "sectionType": "studySurvey", 97 | "sectionTitle": "Study Tasks", 98 | "sectionSummary": "You will answer a few simple daily and weekly questions about your asthma. These questions should only take a few minutes.", 99 | "sectionHtmlContent": "consent_section_html_study_survey" 100 | }, 101 | { 102 | "sectionType": "studyTasks", 103 | "sectionTitle": "Potential Benefits", 104 | "sectionSummary": "You could learn more about asthma and monitoring your health that may help you to be healthier. It is possible that neither you nor others may benefit from taking part in this research.", 105 | "sectionHtmlContent": "consent_section_html_study_task" 106 | }, 107 | { 108 | "sectionType": "withdrawing", 109 | "sectionTitle": "Withdrawing", 110 | "sectionSummary": "There will be no penalty to you if you decide not to take part in this study. You can withdraw your consent and discontinue participation at any time.", 111 | "sectionHtmlContent": "consent_section_html_withdrawing" 112 | }, 113 | { 114 | "sectionType": "custom", 115 | "sectionTitle": "Secure Database", 116 | "sectionSummary": "Your study data will be stored in a manner that maintains strict information technology procedures to safeguard your information and to prevent improper access.", 117 | "sectionImage": "rsb_consent_section_potential_benefits_alt", 118 | "sectionHtmlContent": "consent_section_html_security", 119 | "sectionAnimationUrl": "10_PotentialBenifits_Alt" 120 | }, 121 | { 122 | "sectionType": "custom", 123 | "sectionTitle": "Issues to Consider", 124 | "sectionSummary": "This Asthma app does not replace your usual medical care. Continue your asthma care with your usual doctor.", 125 | "sectionImage": "rsb_consent_section_visiting_doctor", 126 | "sectionHtmlContent": "consent_section_html_talktodoctor", 127 | "sectionAnimationUrl": "11_VisitingDoctor" 128 | }, 129 | { 130 | "sectionType": "custom", 131 | "sectionTitle": "Follow Up", 132 | "sectionSummary": "Study investigators may contact you as needed for feedback and further mobile health app development needs if you opt in at the conclusion of the study.", 133 | "sectionImage": "rsb_consent_section_recontact", 134 | "sectionHtmlContent": "consent_section_html_recontact", 135 | "sectionAnimationUrl": "12_Recontact" 136 | } 137 | ] 138 | } 139 | -------------------------------------------------------------------------------- /app/src/main/assets/json/learn.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | 4 | { 5 | "section_title": "Info", 6 | "row_items": [ 7 | { 8 | "title": "About This Study", 9 | "details": "learn_aboutstudy" 10 | }, 11 | { 12 | "title": "How This Study Works", 13 | "details": "learn_howstudyworks" 14 | }, 15 | { 16 | "title": "Who Can Participate?", 17 | "details": "learn_eligibletoparticipate" 18 | }, 19 | { 20 | "title": "Who Is Running This Study?", 21 | "details": "learn_whoisrunning" 22 | } 23 | ] 24 | }, 25 | { 26 | "section_title": "Resources", 27 | "row_items": [ 28 | { 29 | "title": "Robot Overview", 30 | "details": "learn_overview" 31 | }, 32 | { 33 | "title": "Treatment Options", 34 | "details": "learn_treatment" 35 | }, 36 | { 37 | "title": "Online Resources", 38 | "details": "learn_onlineresources" 39 | } 40 | ] 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /app/src/main/assets/json/study_overview.json: -------------------------------------------------------------------------------- 1 | { 2 | "disease_name": "Asthma", 3 | "from_date": "December 2014", 4 | "to_date": "January 2015", 5 | "logo_name": "logo_research_institute", 6 | "questions": [ 7 | { 8 | "title": "Welcome to our ResearchStack study", 9 | "details": "This is where you would put more details about your study that you would like the user to see on the first page of the study overview.", 10 | "show_consent": "yes" 11 | }, 12 | { 13 | "title": "About This Study", 14 | "details": "study_overview_about" 15 | }, 16 | { 17 | "title": "How This Study Works", 18 | "details": "study_overview_howstudyworks" 19 | }, 20 | { 21 | "title": "Who is Running This Study", 22 | "details": "study_overview_whoisrunning" 23 | }, 24 | { 25 | "title": "About the App", 26 | "details": "study_overview_app" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/assets/json/survey/about_you.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "AboutYou", 3 | "type": "Survey", 4 | "name": "AboutYou", 5 | "elements": [ 6 | { 7 | "identifier": "ethnicity", 8 | "prompt": "Ethnicity", 9 | "uiHint": "MultiValueConstraints", 10 | "guid": "854afd00-3d2b-41c2-9f36-17c72287810f", 11 | "type": "SurveyQuestion", 12 | "constraints": { 13 | "dataType": "integer", 14 | "type": "MultiValueConstraints", 15 | "enumeration": [ 16 | { 17 | "type": "SurveyQuestionOption", 18 | "value": 1, 19 | "label": "Hispanic/Latino" 20 | }, 21 | { 22 | "type": "SurveyQuestionOption", 23 | "value": 2, 24 | "label": "Non-Hispanic/Latino" 25 | }, 26 | { 27 | "type": "SurveyQuestionOption", 28 | "value": 3, 29 | "label": "I choose not to answer" 30 | } 31 | ] 32 | } 33 | }, 34 | { 35 | "identifier": "race", 36 | "prompt": "Race (check any that apply)", 37 | "uiHint": "MultiValueConstraints", 38 | "guid": "854aed00-3d2c-41a2-9f36-17b72287819b", 39 | "type": "SurveyQuestion", 40 | "constraints": { 41 | "dataType": "integer", 42 | "allowMultiple": true, 43 | "type": "MultiValueConstraints", 44 | "enumeration": [ 45 | { 46 | "type": "SurveyQuestionOption", 47 | "value": 1, 48 | "label": "Black/African American" 49 | }, 50 | { 51 | "type": "SurveyQuestionOption", 52 | "value": 2, 53 | "label": "Asian" 54 | }, 55 | { 56 | "type": "SurveyQuestionOption", 57 | "value": 3, 58 | "label": "American Indian or Alaskan Native" 59 | }, 60 | { 61 | "type": "SurveyQuestionOption", 62 | "value": 4, 63 | "label": "Hawaiian or other Pacific Islander" 64 | }, 65 | { 66 | "type": "SurveyQuestionOption", 67 | "value": 5, 68 | "label": "White" 69 | }, 70 | { 71 | "type": "SurveyQuestionOption", 72 | "value": 6, 73 | "label": "Other" 74 | }, 75 | { 76 | "type": "SurveyQuestionOption", 77 | "value": 7, 78 | "label": "I choose not to answer" 79 | } 80 | ] 81 | } 82 | }, 83 | { 84 | "identifier": "Income", 85 | "prompt": "Which of the following best describes the total annual income of all members of your household?", 86 | "uiHint": "list", 87 | "guid": "1992b80e-912c-4b05-b29b-5c35f02688b4", 88 | "type": "SurveyQuestion", 89 | "constraints": { 90 | "dataType": "integer", 91 | "type": "MultiValueConstraints", 92 | "enumeration": [ 93 | { 94 | "type": "SurveyQuestionOption", 95 | "value": 1, 96 | "label": "<$14,999" 97 | }, 98 | { 99 | "type": "SurveyQuestionOption", 100 | "value": 2, 101 | "label": "$15,000-21,999" 102 | }, 103 | { 104 | "type": "SurveyQuestionOption", 105 | "value": 3, 106 | "label": "$22,000-43,999" 107 | }, 108 | { 109 | "type": "SurveyQuestionOption", 110 | "value": 4, 111 | "label": "$44,000-60,000" 112 | }, 113 | { 114 | "type": "SurveyQuestionOption", 115 | "value": 5, 116 | "label": ">$60,000" 117 | }, 118 | { 119 | "type": "SurveyQuestionOption", 120 | "value": 6, 121 | "label": "I don't know" 122 | }, 123 | { 124 | "type": "SurveyQuestionOption", 125 | "value": 7, 126 | "label": "I choose not to answer" 127 | } 128 | ] 129 | } 130 | }, 131 | { 132 | "identifier": "education", 133 | "prompt": "What is the highest level of education you have completed?", 134 | "uiHint": "MultiValueConstraints", 135 | "guid": "854aed00-3d2c-41c2-9a36-17b72287819b", 136 | "type": "SurveyQuestion", 137 | "constraints": { 138 | "dataType": "integer", 139 | "type": "MultiValueConstraints", 140 | "enumeration": [ 141 | { 142 | "type": "SurveyQuestionOption", 143 | "value": 1, 144 | "label": "8th grade or less" 145 | }, 146 | { 147 | "type": "SurveyQuestionOption", 148 | "value": 2, 149 | "label": "More than 8th grade but did not graduate high school" 150 | }, 151 | { 152 | "type": "SurveyQuestionOption", 153 | "value": 3, 154 | "label": "High school graduate or equivalent" 155 | }, 156 | { 157 | "type": "SurveyQuestionOption", 158 | "value": 4, 159 | "label": "Some college" 160 | }, 161 | { 162 | "type": "SurveyQuestionOption", 163 | "value": 5, 164 | "label": "Graduate of Two Year College or Technical School" 165 | }, 166 | { 167 | "type": "SurveyQuestionOption", 168 | "value": 6, 169 | "label": "Graduate of Four Year College" 170 | }, 171 | { 172 | "type": "SurveyQuestionOption", 173 | "value": 7, 174 | "label": "Post graduate studies" 175 | }, 176 | { 177 | "type": "SurveyQuestionOption", 178 | "value": 8, 179 | "label": "I choose not to answer" 180 | } 181 | ] 182 | } 183 | }, 184 | { 185 | "identifier": "smoking_status", 186 | "prompt": "What is your smoking status?", 187 | "uiHint": "MultiValueConstraints", 188 | "guid": "854cef00-3d2c-41c2-9b36-17b72287819b", 189 | "type": "SurveyQuestion", 190 | "constraints": { 191 | "dataType": "integer", 192 | "type": "MultiValueConstraints", 193 | "enumeration": [ 194 | { 195 | "type": "SurveyQuestionOption", 196 | "value": 1, 197 | "label": "Never (<100 Cigarettes in lifetime)" 198 | }, 199 | { 200 | "type": "SurveyQuestionOption", 201 | "value": 2, 202 | "label": "Current" 203 | }, 204 | { 205 | "type": "SurveyQuestionOption", 206 | "value": 3, 207 | "label": "Former" 208 | } 209 | ], 210 | "rules": [ 211 | { 212 | "operator": "eq", 213 | "skipTo": "health_insurance", 214 | "type": "SurveyRule", 215 | "value": 1 216 | } 217 | ] 218 | } 219 | }, 220 | { 221 | "identifier": "avg_cigarettes", 222 | "prompt": "On average, how many cigarettes per day did you smoke daily?", 223 | "uiHint": "numberfield", 224 | "guid": "BE571E9D-6CE8-4C09-92EC-F38ECF5B4BDD", 225 | "type": "SurveyQuestion", 226 | "constraints": { 227 | "dataType": "integer", 228 | "type": "IntegerConstraints", 229 | "maxValue": 250, 230 | "minValue": 0 231 | } 232 | }, 233 | { 234 | "identifier": "smoking_years", 235 | "prompt": "How many years in total did you smoke?", 236 | "uiHint": "numberfield", 237 | "guid": "BE571E9D-2BE8-4C09-92EC-F38ECF5B4BDD", 238 | "type": "SurveyQuestion", 239 | "constraints": { 240 | "dataType": "integer", 241 | "type": "IntegerConstraints", 242 | "maxValue": 125, 243 | "minValue": 0 244 | } 245 | }, 246 | { 247 | "identifier": "health_insurance", 248 | "prompt": "Do you have health insurance?", 249 | "uiHint": "MultiValueConstraints", 250 | "guid": "854fff00-3d2c-41c2-9b36-17b72287819b", 251 | "type": "SurveyQuestion", 252 | "constraints": { 253 | "dataType": "integer", 254 | "type": "MultiValueConstraints", 255 | "enumeration": [ 256 | { 257 | "type": "SurveyQuestionOption", 258 | "value": 1, 259 | "label": "Private (bought by you or your employer)" 260 | }, 261 | { 262 | "type": "SurveyQuestionOption", 263 | "value": 2, 264 | "label": "Public (Medicare or Medicaid)" 265 | }, 266 | { 267 | "type": "SurveyQuestionOption", 268 | "value": 3, 269 | "label": "I have no health insurance" 270 | }, 271 | { 272 | "type": "SurveyQuestionOption", 273 | "value": 4, 274 | "label": "I choose not to answer" 275 | } 276 | ] 277 | } 278 | } 279 | ] 280 | } -------------------------------------------------------------------------------- /app/src/main/assets/json/survey/asthma_daily_prompt.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "AsthmaDailyPrompt", 3 | "type": "Survey", 4 | "name": "AsthmaDailyPrompt", 5 | "elements": [ 6 | { 7 | "identifier": "day_symptoms", 8 | "prompt": "In the last 24 hours, did you have any daytime asthma symptoms (cough, wheeze, shortness of breath or chest tightness)?", 9 | "uiHint": "checkbox", 10 | "guid": "e872f8fa-c157-457b-890f-9e28eeed6efa", 11 | "type": "SurveyQuestion", 12 | "constraints": { 13 | "dataType": "boolean", 14 | "type": "BooleanConstraints" 15 | } 16 | }, 17 | { 18 | "identifier": "night_symptoms", 19 | "prompt": "In the last 24 hours, did you have any nighttime waking from asthma symptoms (cough, wheeze, shortness of breath or chest tightness)?", 20 | "uiHint": "checkbox", 21 | "guid": "e872f8fa-c157-457b-890f-9e28eeed6efa", 22 | "type": "SurveyQuestion", 23 | "constraints": { 24 | "dataType": "boolean", 25 | "type": "BooleanConstraints" 26 | } 27 | }, 28 | { 29 | "identifier": "use_qr", 30 | "prompt": "Did you use your quick relief inhaler in the last 24 hours, except before exercise?", 31 | "uiHint": "checkbox", 32 | "guid": "e872f8fa-c157-457b-890f-9e28eeed6efa", 33 | "type": "SurveyQuestion", 34 | "constraints": { 35 | "dataType": "boolean", 36 | "rules": [ 37 | { 38 | "operator": "eq", 39 | "skipTo": "get_worse", 40 | "type": "SurveyRule", 41 | "value": 0 42 | } 43 | ], 44 | "type": "BooleanConstraints" 45 | } 46 | }, 47 | { 48 | "identifier": "quick_relief_puffs", 49 | "prompt": "Except for use before exercise, how many total puffs of your quick relief medicine did you take over the past 24 hours ?", 50 | "uiHint": "numberfield", 51 | "guid": "e27b4728-32e7-4066-985a-bee71e2580c3", 52 | "type": "SurveyQuestion", 53 | "constraints": { 54 | "dataType": "integer", 55 | "step": null, 56 | "type": "IntegerConstraints", 57 | "maxValue": 20, 58 | "minValue": 0 59 | } 60 | }, 61 | { 62 | "identifier": "get_worse", 63 | "prompt": "Did any of the following cause your asthma to get worse today? (check all that apply):", 64 | "uiHint": "MultiValueConstraints", 65 | "guid": "c374b293-f060-4ec9-fd99-2738837967e8", 66 | "type": "SurveyQuestion", 67 | "constraints": { 68 | "dataType": "integer", 69 | "allowMultiple": true, 70 | "type": "MultiValueConstraints", 71 | "enumeration": [ 72 | { 73 | "type": "SurveyQuestionOption", 74 | "value": 1, 75 | "label": "A cold" 76 | }, 77 | { 78 | "type": "SurveyQuestionOption", 79 | "value": 2, 80 | "label": "Exercise" 81 | }, 82 | { 83 | "type": "SurveyQuestionOption", 84 | "value": 3, 85 | "label": "Being more active than usual (walking, running, climbing stairs)" 86 | }, 87 | { 88 | "type": "SurveyQuestionOption", 89 | "value": 4, 90 | "label": "Strong smells (perfume, chemicals, sprays, paint)" 91 | }, 92 | { 93 | "type": "SurveyQuestionOption", 94 | "value": 5, 95 | "label": "Exhaust fumes" 96 | }, 97 | { 98 | "type": "SurveyQuestionOption", 99 | "value": 6, 100 | "label": "House dust" 101 | }, 102 | { 103 | "type": "SurveyQuestionOption", 104 | "value": 7, 105 | "label": "Dogs" 106 | }, 107 | { 108 | "type": "SurveyQuestionOption", 109 | "value": 8, 110 | "label": "Cats" 111 | }, 112 | { 113 | "type": "SurveyQuestionOption", 114 | "value": 9, 115 | "label": "Other furry/feathered animals" 116 | }, 117 | { 118 | "type": "SurveyQuestionOption", 119 | "value": 10, 120 | "label": "Mold" 121 | }, 122 | { 123 | "type": "SurveyQuestionOption", 124 | "value": 11, 125 | "label": "Pollen from trees, grass or weeds" 126 | }, 127 | { 128 | "type": "SurveyQuestionOption", 129 | "value": 12, 130 | "label": "Extreme heat" 131 | }, 132 | { 133 | "type": "SurveyQuestionOption", 134 | "value": 13, 135 | "label": "Extreme cold" 136 | }, 137 | { 138 | "type": "SurveyQuestionOption", 139 | "value": 14, 140 | "label": "Changes in weather" 141 | }, 142 | { 143 | "type": "SurveyQuestionOption", 144 | "value": 15, 145 | "label": "Around the time of my period" 146 | }, 147 | { 148 | "type": "SurveyQuestionOption", 149 | "value": 16, 150 | "label": "Poor air quality" 151 | }, 152 | { 153 | "type": "SurveyQuestionOption", 154 | "value": 17, 155 | "label": "Someone smoking near me" 156 | }, 157 | { 158 | "type": "SurveyQuestionOption", 159 | "value": 18, 160 | "label": "Stress" 161 | }, 162 | { 163 | "type": "SurveyQuestionOption", 164 | "value": 19, 165 | "label": "Feeling sad, angry, excited, tense" 166 | }, 167 | { 168 | "type": "SurveyQuestionOption", 169 | "value": 20, 170 | "label": "Laughter" 171 | }, 172 | { 173 | "type": "SurveyQuestionOption", 174 | "value": 21, 175 | "label": "I don't know what triggers my asthma" 176 | }, 177 | { 178 | "type": "SurveyQuestionOption", 179 | "value": 22, 180 | "label": "None of these things trigger my asthma" 181 | } 182 | ] 183 | } 184 | }, 185 | { 186 | "identifier": "peakflow", 187 | "prompt": "Enter your peak flow today? (L/min)", 188 | "uiHint": "numberfield", 189 | "guid": "21987ad2-4846-48a4-aa80-27c3371186c0", 190 | "type": "SurveyQuestion", 191 | "constraints": { 192 | "dataType": "integer", 193 | "type": "IntegerConstraints", 194 | "step": null, 195 | "maxValue": 900, 196 | "minValue": 60 197 | } 198 | }, 199 | { 200 | "identifier": "medicine", 201 | "prompt": "Did you take your asthma control medicine in the last 24 hours?", 202 | "uiHint": "MultiValueConstraints", 203 | "guid": "c374b293-c060-47c9-bd99-2738837967a8", 204 | "type": "SurveyQuestion", 205 | "constraints": { 206 | "dataType": "integer", 207 | "type": "MultiValueConstraints", 208 | "enumeration": [ 209 | { 210 | "type": "SurveyQuestionOption", 211 | "value": 1, 212 | "label": "Yes, all of my prescribed doses" 213 | }, 214 | { 215 | "type": "SurveyQuestionOption", 216 | "value": 2, 217 | "label": "Yes, some but not all of my prescribed doses" 218 | }, 219 | { 220 | "type": "SurveyQuestionOption", 221 | "value": 3, 222 | "label": "No, I did not take them" 223 | }, 224 | { 225 | "type": "SurveyQuestionOption", 226 | "value": 4, 227 | "label": "I'm not sure" 228 | } 229 | ] 230 | } 231 | } 232 | ] 233 | } 234 | -------------------------------------------------------------------------------- /app/src/main/assets/json/survey/medical_history.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "MedicalHistory", 3 | "type": "Survey", 4 | "name": "MedicalHistory", 5 | "elements": [ 6 | { 7 | "identifier": "heart_attack", 8 | "prompt": "Have you ever had a heart attack?", 9 | "uiHint": "checkbox", 10 | "guid": "854cff00-332f-41c2-9b36-17b72287819b", 11 | "type": "SurveyQuestion", 12 | "constraints": { 13 | "dataType": "boolean", 14 | "type": "BooleanConstraints" 15 | } 16 | }, 17 | { 18 | "identifier": "Congestive", 19 | "prompt": "Have you ever had congestive heart failure?", 20 | "uiHint": "checkbox", 21 | "guid": "854eff00-3b2f-41b2-9b36-17b72287819b", 22 | "type": "SurveyQuestion", 23 | "constraints": { 24 | "dataType": "boolean", 25 | "type": "BooleanConstraints" 26 | } 27 | }, 28 | { 29 | "identifier": "peripheral", 30 | "prompt": "Do you have or have you had peripheral vascular disease (disease of your circulation, including an aortic aneurysm)?", 31 | "uiHint": "checkbox", 32 | "guid": "854bff00-3b2f-41c2-9b36-17b72287819b", 33 | "type": "SurveyQuestion", 34 | "constraints": { 35 | "dataType": "boolean", 36 | "type": "BooleanConstraints" 37 | } 38 | }, 39 | { 40 | "identifier": "dementia", 41 | "prompt": "Do you have dementia?", 42 | "uiHint": "checkbox", 43 | "guid": "854cff00-3e2f-41c2-9b36-17b72287819b", 44 | "type": "SurveyQuestion", 45 | "constraints": { 46 | "dataType": "boolean", 47 | "type": "BooleanConstraints" 48 | } 49 | }, 50 | { 51 | "identifier": "chronic_pulmonary_diesease", 52 | "prompt": "Do you have or have you had chronic pulmonary disease (emphysema or chronic bronchitis)?", 53 | "uiHint": "checkbox", 54 | "guid": "854eff00-3b2f-41a2-9b36-17b72287819b", 55 | "type": "SurveyQuestion", 56 | "constraints": { 57 | "dataType": "boolean", 58 | "type": "BooleanConstraints" 59 | } 60 | }, 61 | { 62 | "identifier": "other_lung_disease", 63 | "prompt": "Do you have or have you had another lung disease other than asthma?", 64 | "uiHint": "checkbox", 65 | "guid": "854eff00-311f-41a2-9b36-17b72287819b", 66 | "type": "SurveyQuestion", 67 | "constraints": { 68 | "dataType": "boolean", 69 | "type": "BooleanConstraints" 70 | } 71 | }, 72 | { 73 | "identifier": "tissue", 74 | "prompt": "Do you have or have you had connective tissue disease (lupus, rheumatoid arthritis, or similar diseases)?", 75 | "uiHint": "checkbox", 76 | "guid": "854cff00-3b2f-41d2-9b36-17b72287819b", 77 | "type": "SurveyQuestion", 78 | "constraints": { 79 | "dataType": "boolean", 80 | "type": "BooleanConstraints" 81 | } 82 | }, 83 | { 84 | "identifier": "ulcer", 85 | "prompt": "Do you have or have you had peptic ulcer disease?", 86 | "uiHint": "checkbox", 87 | "guid": "854cff00-3b2f-41c2-9b36-17b72287819d", 88 | "type": "SurveyQuestion", 89 | "constraints": { 90 | "dataType": "boolean", 91 | "type": "BooleanConstraints" 92 | } 93 | }, 94 | { 95 | "identifier": "kidney", 96 | "prompt": "Do you have or have you had moderate or severe renal (kidney) disease?", 97 | "uiHint": "checkbox", 98 | "guid": "854cff00-3b2f-41c2-9b36-17b72287819c", 99 | "type": "SurveyQuestion", 100 | "constraints": { 101 | "dataType": "boolean", 102 | "type": "BooleanConstraints" 103 | } 104 | }, 105 | { 106 | "identifier": "leukemia", 107 | "prompt": "Do you have or have you had leukemia (acute or chronic)?", 108 | "uiHint": "checkbox", 109 | "guid": "854cff00-3b2f-41c2-9b36-17b72287819a", 110 | "type": "SurveyQuestion", 111 | "constraints": { 112 | "dataType": "boolean", 113 | "type": "BooleanConstraints" 114 | } 115 | }, 116 | { 117 | "identifier": "malignant_lymphoma", 118 | "prompt": "Do you have or have you had malignant lymphoma?", 119 | "uiHint": "checkbox", 120 | "guid": "854fff00-3a2a-41c2-9b36-17b72287819a", 121 | "type": "SurveyQuestion", 122 | "constraints": { 123 | "dataType": "boolean", 124 | "type": "BooleanConstraints" 125 | } 126 | }, 127 | { 128 | "identifier": "tumor", 129 | "prompt": "Have you had any malignant solid tumors within the past 5 years (including melanoma but excluding other skin cancer)?", 130 | "uiHint": "MultiValueConstraints", 131 | "guid": "854acf00-3a2c-41a2-9b36-17b72287819b", 132 | "type": "SurveyQuestion", 133 | "constraints": { 134 | "dataType": "integer", 135 | "type": "MultiValueConstraints", 136 | "enumeration": [ 137 | { 138 | "type": "SurveyQuestionOption", 139 | "value": 1, 140 | "label": "Metastatic (spread to other organs besides the starting organ)" 141 | }, 142 | { 143 | "type": "SurveyQuestionOption", 144 | "value": 2, 145 | "label": "Non-metastatic (not spread to other organs)" 146 | }, 147 | { 148 | "type": "SurveyQuestionOption", 149 | "value": 3, 150 | "label": "No" 151 | } 152 | ] 153 | } 154 | }, 155 | { 156 | "identifier": "stroke", 157 | "prompt": "Have you ever had any cerebrovascular disease (including a stroke or embolism)?", 158 | "uiHint": "MultiValueConstraints", 159 | "guid": "854ccf00-3e2c-44c2-9b36-17b72287819b", 160 | "type": "SurveyQuestion", 161 | "constraints": { 162 | "dataType": "integer", 163 | "type": "MultiValueConstraints", 164 | "enumeration": [ 165 | { 166 | "type": "SurveyQuestionOption", 167 | "value": 1, 168 | "label": "With paralysis of one side of the body" 169 | }, 170 | { 171 | "type": "SurveyQuestionOption", 172 | "value": 2, 173 | "label": "Without paralysis of one side of the body" 174 | }, 175 | { 176 | "type": "SurveyQuestionOption", 177 | "value": 3, 178 | "label": "No" 179 | } 180 | ] 181 | } 182 | }, 183 | { 184 | "identifier": "liver", 185 | "prompt": "Do you have or have you had liver disease?", 186 | "uiHint": "MultiValueConstraints", 187 | "guid": "854ccf00-3e23-41c2-9b36-17b72287819b", 188 | "type": "SurveyQuestion", 189 | "constraints": { 190 | "dataType": "integer", 191 | "type": "MultiValueConstraints", 192 | "enumeration": [ 193 | { 194 | "type": "SurveyQuestionOption", 195 | "value": 1, 196 | "label": "Moderate or severe" 197 | }, 198 | { 199 | "type": "SurveyQuestionOption", 200 | "value": 2, 201 | "label": "Mild" 202 | }, 203 | { 204 | "type": "SurveyQuestionOption", 205 | "value": 3, 206 | "label": "No" 207 | } 208 | ] 209 | } 210 | }, 211 | { 212 | "identifier": "arthritis", 213 | "prompt": "Do you have or have you had arthritis?", 214 | "uiHint": "checkbox", 215 | "guid": "8543af00-3b2f-41c2-9b36-17b72287819a", 216 | "type": "SurveyQuestion", 217 | "constraints": { 218 | "dataType": "boolean", 219 | "type": "BooleanConstraints" 220 | } 221 | }, 222 | { 223 | "identifier": "tested", 224 | "prompt": "Have you been tested for allergies with skin tests or blood tests?", 225 | "uiHint": "checkbox", 226 | "guid": "854cff90-3b55-41c2-9b36-17b72287349a", 227 | "type": "SurveyQuestion", 228 | "constraints": { 229 | "dataType": "boolean", 230 | "type": "BooleanConstraints" 231 | } 232 | }, 233 | { 234 | "identifier": "allergic_to", 235 | "prompt": "Which of the folowing cause you to have an allergic reaction? (check all that apply)", 236 | "uiHint": "MultiValueConstraints", 237 | "guid": "854fff00-3d2c-41c2-9c06-17b72287819b", 238 | "type": "SurveyQuestion", 239 | "constraints": { 240 | "dataType": "integer", 241 | "allowMultiple": true, 242 | "type": "MultiValueConstraints", 243 | "enumeration": [ 244 | { 245 | "type": "SurveyQuestionOption", 246 | "value": 1, 247 | "label": "House dust mites" 248 | }, 249 | { 250 | "type": "SurveyQuestionOption", 251 | "value": 2, 252 | "label": "Mold" 253 | }, 254 | { 255 | "type": "SurveyQuestionOption", 256 | "value": 3, 257 | "label": "Weed pollen" 258 | }, 259 | { 260 | "type": "SurveyQuestionOption", 261 | "value": 4, 262 | "label": "Grass pollen" 263 | }, 264 | { 265 | "type": "SurveyQuestionOption", 266 | "value": 5, 267 | "label": "Tree pollen" 268 | }, 269 | { 270 | "type": "SurveyQuestionOption", 271 | "value": 6, 272 | "label": "Rodents (rats, mice)" 273 | }, 274 | { 275 | "type": "SurveyQuestionOption", 276 | "value": 7, 277 | "label": "Cockroaches" 278 | }, 279 | { 280 | "type": "SurveyQuestionOption", 281 | "value": 8, 282 | "label": "Dogs" 283 | }, 284 | { 285 | "type": "SurveyQuestionOption", 286 | "value": 9, 287 | "label": "Cats" 288 | }, 289 | { 290 | "type": "SurveyQuestionOption", 291 | "value": 10, 292 | "label": "Other" 293 | } 294 | ] 295 | } 296 | } 297 | ] 298 | } 299 | -------------------------------------------------------------------------------- /app/src/main/assets/json/survey/practice_survey.json: -------------------------------------------------------------------------------- 1 | { 2 | "guid": "1c5ee4e2-baab-47db-b515-2fbe9fdf50a6", 3 | "createdOn": "2016-03-11T16:47:26.014Z", 4 | "modifiedOn": "2016-03-11T16:47:36.276Z", 5 | "version": 3, 6 | "name": "Practice Survey", 7 | "identifier": "practiceSurvey", 8 | "published": true, 9 | "deleted": false, 10 | "schemaRevision": 2, 11 | "elements": [ 12 | { 13 | "guid": "38a64547-cf97-4bd8-952c-77685b4082fd", 14 | "identifier": "areyou", 15 | "type": "SurveyQuestion", 16 | "prompt": "Are you you?", 17 | "promptDetail": "This is a prompt detail", 18 | "fireEvent": false, 19 | "constraints": { 20 | "rules": [ 21 | ], 22 | "dataType": "boolean", 23 | "type": "BooleanConstraints" 24 | }, 25 | "uiHint": "checkbox" 26 | }, 27 | { 28 | "guid": "d996d61f-4ace-4f5a-bb24-c3c3260c0e10", 29 | "identifier": "age", 30 | "type": "SurveyQuestion", 31 | "prompt": "Current age?", 32 | "fireEvent": false, 33 | "constraints": { 34 | "rules": [ 35 | ], 36 | "dataType": "integer", 37 | "minValue": 18.0, 38 | "maxValue": 99.0, 39 | "step": 1.0, 40 | "type": "IntegerConstraints" 41 | }, 42 | "uiHint": "numberfield" 43 | }, 44 | { 45 | "guid": "6EC74824-ACF7-4D44-852F-B56A9FE5ACFB", 46 | "identifier": "dateWhen", 47 | "prompt": "At what date?", 48 | "uiHint": "datepicker", 49 | "type": "SurveyQuestion", 50 | "constraints": { 51 | "dataType": "string", 52 | "type": "DateConstraints" 53 | } 54 | }, 55 | { 56 | "guid": "06d619df-a147-471a-b1ed-429f47d404bf", 57 | "identifier": "infoText", 58 | "type": "SurveyQuestion", 59 | "prompt": "More info?", 60 | "fireEvent": false, 61 | "constraints": { 62 | "rules": [ 63 | ], 64 | "dataType": "string", 65 | "minLength": 0, 66 | "maxLength": 255, 67 | "multipleLines": true, 68 | "type": "StringConstraints" 69 | }, 70 | "uiHint": "multilinetext" 71 | } 72 | ], 73 | "type": "Survey" 74 | } -------------------------------------------------------------------------------- /app/src/main/assets/json/tasks_and_schedules.json: -------------------------------------------------------------------------------- 1 | { 2 | "schedules": [ 3 | { 4 | "scheduleType": "once", 5 | "delay": "P3D", 6 | "tasks": [ 7 | { 8 | "taskTitle": "About You", 9 | "taskID": "AboutYou-27829fa5-d731-4372-ba30-a5859f688297", 10 | "taskFileName": "about_you", 11 | "taskClassName": "APHDailyTaskViewController", 12 | "taskCompletionTimeString": "8 Questions" 13 | } 14 | ] 15 | }, 16 | { 17 | "scheduleType": "recurring", 18 | "scheduleString": "* * * * *", 19 | "tasks": [ 20 | { 21 | "taskTitle": "Test Survey", 22 | "taskID": "1c5ee4e2-baab-47db-b515-2fbe9fdf50a6", 23 | "taskFileName": "practice_survey", 24 | "taskCompletionTimeString": "4 Questions" 25 | } 26 | ] 27 | }, 28 | { 29 | "scheduleType": "recurring", 30 | "scheduleString": "0 5 * * *", 31 | "tasks": [ 32 | { 33 | "taskTitle": "Daily Survey", 34 | "taskID": "DailyPrompt-27829fa5-d731-4372-ba30-a5859f655297", 35 | "taskFileName": "asthma_daily_prompt", 36 | "taskClassName": "APHDailyTaskViewController", 37 | "taskCompletionTimeString": "8 Questions" 38 | } 39 | ] 40 | }, 41 | { 42 | "scheduleType": "recurring", 43 | "scheduleString": "0 5 * * 6", 44 | "tasks": [ 45 | { 46 | "taskTitle": "Weekly Survey", 47 | "taskID": "WeeklySurvey-b573a78-8917-4582-8f1f-0552d0bfd28a", 48 | "taskFileName": "asthma_weekly", 49 | "taskClassName": "APHWeeklyTaskViewController", 50 | "taskCompletionTimeString": "16 Questions" 51 | } 52 | ] 53 | }, 54 | { 55 | "scheduleType": "once", 56 | "delay": "P4D", 57 | "tasks": [ 58 | { 59 | "taskTitle": "Medical History", 60 | "taskID": "MedicalHistory-b3cd0d66-b943-11e4-a71e-12e3f512a338", 61 | "taskFileName": "medical_history", 62 | "taskClassName": "APCGenericSurveyTaskViewController", 63 | "taskCompletionTimeString": "17 Questions" 64 | } 65 | ] 66 | }, 67 | { 68 | "scheduleType": "once", 69 | "tasks": [ 70 | { 71 | "taskTitle": "Asthma Medication", 72 | "taskID": "AsthmaMedication-c2379e84-b943-11e4-a71e-12e3f512a338", 73 | "taskFileName": "asthma_medication_survey", 74 | "taskClassName": "APCGenericSurveyTaskViewController", 75 | "taskCompletionTimeString": "19 Questions" 76 | } 77 | ] 78 | }, 79 | { 80 | "scheduleType": "once", 81 | "delay": "P2D", 82 | "tasks": [ 83 | { 84 | "taskTitle": "Your Asthma", 85 | "taskID": "YourAsthma-cc06cd68-b943-11e4-a71e-12e3f512a338", 86 | "taskFileName": "your_asthma", 87 | "taskClassName": "APCGenericSurveyTaskViewController", 88 | "taskCompletionTimeString": "9 Questions" 89 | } 90 | ] 91 | }, 92 | { 93 | "scheduleType": "once", 94 | "tasks": [ 95 | { 96 | "taskTitle": "Asthma History", 97 | "taskID": "AsthmaHistory-d6d07ba4-b943-11e4-a71e-12e3f512a338", 98 | "taskFileName": "asthma_history", 99 | "taskClassName": "APCGenericSurveyTaskViewController", 100 | "taskCompletionTimeString": "13 Questions" 101 | } 102 | ] 103 | }, 104 | { 105 | "scheduleType": "once", 106 | "tasks": [ 107 | { 108 | "taskTitle": "Enrollment for Recontact", 109 | "taskID": "APHEnrollmentForRecontactTaskViewController-1E174065-5B02-11E4-8ED6-0800200C9A66", 110 | "taskClassName": "APHEnrollmentForRecontactTaskViewController", 111 | "taskCompletionTimeString": "1 Question" 112 | } 113 | ] 114 | } 115 | ] 116 | } 117 | -------------------------------------------------------------------------------- /app/src/main/assets/mp4/study_overview_video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/assets/mp4/study_overview_video.mp4 -------------------------------------------------------------------------------- /app/src/main/assets/pdf/study_overview_consent_form.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/assets/pdf/study_overview_consent_form.pdf -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/DashboardFragment.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp; 2 | 3 | import android.graphics.Color; 4 | import android.graphics.Typeface; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.design.widget.Snackbar; 8 | import android.support.v4.app.Fragment; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import com.github.mikephil.charting.data.BarData; 14 | import com.github.mikephil.charting.data.BarDataSet; 15 | import com.github.mikephil.charting.data.BarEntry; 16 | import com.github.mikephil.charting.data.Entry; 17 | import com.github.mikephil.charting.data.LineData; 18 | import com.github.mikephil.charting.data.LineDataSet; 19 | import com.github.mikephil.charting.data.PieData; 20 | import com.github.mikephil.charting.data.PieDataSet; 21 | import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; 22 | 23 | import org.researchstack.backbone.ui.graph.BarChartCard; 24 | import org.researchstack.backbone.ui.graph.LineChartCard; 25 | import org.researchstack.backbone.ui.graph.PieChartCard; 26 | import org.researchstack.backbone.ui.graph.ProgressChartCard; 27 | import org.researchstack.backbone.utils.ThemeUtils; 28 | import org.researchstack.skin.R; 29 | 30 | import java.text.NumberFormat; 31 | import java.util.ArrayList; 32 | import java.util.Calendar; 33 | import java.util.List; 34 | import java.util.Locale; 35 | 36 | 37 | public class DashboardFragment extends Fragment 38 | { 39 | private View emptyView; 40 | 41 | @Nullable 42 | @Override 43 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 44 | { 45 | return inflater.inflate(R.layout.rss_fragment_dashboard, container, false); 46 | } 47 | 48 | @Override 49 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) 50 | { 51 | super.onViewCreated(view, savedInstanceState); 52 | 53 | emptyView = view.findViewById(R.id.dashboard_empty); 54 | 55 | initProgressChart(view); 56 | } 57 | 58 | private void initProgressChart(View view) 59 | { 60 | ProgressChartCard progressCard = (ProgressChartCard) view.findViewById(R.id.dashboard_chart_progress); 61 | progressCard.setTitle("Amount of pie eaten"); 62 | progressCard.setData(createProgressChartData()); 63 | progressCard.setFinishAction(o -> { 64 | Snackbar.make(view, "Finish Action", Snackbar.LENGTH_SHORT).show(); 65 | }); 66 | 67 | PieChartCard pieCard = (PieChartCard) view.findViewById(R.id.dashboard_chart_pie); 68 | pieCard.setTitle("Pie Flavors"); 69 | pieCard.setData(createPieChartData()); 70 | 71 | BarChartCard barCard = (BarChartCard) view.findViewById(R.id.dashboard_chart_bar); 72 | barCard.setTitle("Pie Flavors"); 73 | barCard.setData(createBarChartData(), false); 74 | barCard.setExpandAction(o -> { 75 | Snackbar.make(view, "Expand Action", Snackbar.LENGTH_SHORT).show(); 76 | }); 77 | 78 | BarChartCard barStackedCard = (BarChartCard) view.findViewById(R.id.dashboard_chart_bar_stacked); 79 | barStackedCard.setTitle("Pie Flavors"); 80 | barStackedCard.setData(createStackedBarChartData(), true); 81 | barStackedCard.setExpandAction(o -> { 82 | Snackbar.make(view, "Expand Action", Snackbar.LENGTH_SHORT).show(); 83 | }); 84 | 85 | LineChartCard lineCard = (LineChartCard) view.findViewById(R.id.dashboard_chart_line); 86 | lineCard.setTitle("Daily steps"); 87 | lineCard.setData(createLineChartData()); 88 | lineCard.setExpandAction(o -> { 89 | Snackbar.make(view, "Expand Action", Snackbar.LENGTH_SHORT).show(); 90 | }); 91 | } 92 | 93 | public List createProgressChartData() 94 | { 95 | List items = new ArrayList<>(); 96 | for(int i = 0, size = 12; i <= size; i++) 97 | { 98 | List entries = new ArrayList<>(); 99 | entries.add(new Entry(i, 0)); // Complete 100 | entries.add(new Entry(size - i, 1)); // Incomplete 101 | 102 | Calendar calendar = Calendar.getInstance(); 103 | calendar.add(Calendar.MONTH, - i); 104 | 105 | String month = calendar.getDisplayName(Calendar.MONTH, 106 | Calendar.SHORT, 107 | Locale.getDefault()); 108 | 109 | PieDataSet data = new PieDataSet(entries, month + " '16"); 110 | data.setDrawValues(false); 111 | data.setColors(new int[] { 112 | ThemeUtils.getAccentColor(getContext()), 0xFFe5e5e5 113 | }); 114 | 115 | String[] labels = new String[] {"Incomplete", "Complete"}; 116 | 117 | items.add(new PieData(labels, data)); 118 | } 119 | return items; 120 | } 121 | 122 | public PieData createPieChartData() 123 | { 124 | List entries = new ArrayList<>(); 125 | entries.add(new Entry(50, 0)); 126 | entries.add(new Entry(25, 1)); 127 | entries.add(new Entry(12.5f, 2)); 128 | entries.add(new Entry(12.5f, 3)); 129 | 130 | PieDataSet data = new PieDataSet(entries, null); 131 | data.setDrawValues(false); 132 | data.setColors(new int[] { 133 | 0xFF673ab7, 0xFF2196f3, 0xFF4caf50, 0xFF009688 134 | }); 135 | 136 | String[] labels = new String[] {"Blackberry", "Blueberry", "Green apple", "Seaweed"}; 137 | 138 | return new PieData(labels, data); 139 | } 140 | 141 | public BarData createBarChartData() 142 | { 143 | NumberFormat numberFormat = NumberFormat.getInstance(); 144 | numberFormat.setMinimumFractionDigits(0); 145 | numberFormat.setMaximumFractionDigits(2); 146 | 147 | ArrayList xVals = new ArrayList<>(); 148 | for(int i = 0; i < 12; i++) 149 | { 150 | xVals.add(i + ""); 151 | } 152 | 153 | ArrayList yVals1 = new ArrayList<>(); 154 | 155 | for(int i = 0; i < 12; i++) 156 | { 157 | float mult = (5 + 1); 158 | int val = (int) (Math.random() * mult); 159 | yVals1.add(new BarEntry((val == 0 ? 1 : val), i)); 160 | } 161 | 162 | BarDataSet set1 = new BarDataSet(yVals1, "DataSet"); 163 | set1.setColor(0xFF2196f3); 164 | set1.setBarSpacePercent(40f); 165 | 166 | ArrayList dataSets = new ArrayList<>(); 167 | dataSets.add(set1); 168 | 169 | BarData data = new BarData(xVals, dataSets); 170 | data.setValueTextSize(10); 171 | data.setValueTypeface(Typeface.create("sans-serif-medium", Typeface.BOLD)); 172 | data.setValueTextColor(Color.WHITE); 173 | data.setValueFormatter((value, entry, dataSetIndex, viewPortHandler) -> numberFormat.format( 174 | value)); 175 | return data; 176 | } 177 | 178 | public BarData createStackedBarChartData() 179 | { 180 | NumberFormat numberFormat = NumberFormat.getInstance(); 181 | numberFormat.setMinimumFractionDigits(0); 182 | numberFormat.setMaximumFractionDigits(2); 183 | 184 | ArrayList xVals = new ArrayList<>(); 185 | for(int i = 0; i < 12; i++) 186 | { 187 | xVals.add(i + ""); 188 | } 189 | 190 | ArrayList yVals1 = new ArrayList<>(); 191 | 192 | for(int i = 0; i < 12; i++) 193 | { 194 | float mult = (5 + 1); 195 | int val = (int) (Math.random() * mult); 196 | yVals1.add(new BarEntry(new float[] {(val == 0 ? 1 : val - 1), 1}, i)); 197 | } 198 | 199 | BarDataSet set1 = new BarDataSet(yVals1, "DataSet"); 200 | set1.setColors(new int[] {0xFF2196f3, 0xFF3f51b5}); 201 | set1.setBarSpacePercent(40f); 202 | 203 | ArrayList dataSets = new ArrayList<>(); 204 | dataSets.add(set1); 205 | 206 | BarData data = new BarData(xVals, dataSets); 207 | data.setValueTextSize(10); 208 | data.setValueTypeface(Typeface.create("sans-serif-medium", Typeface.BOLD)); 209 | data.setValueTextColor(Color.WHITE); 210 | data.setValueFormatter((value, entry, dataSetIndex, viewPortHandler) -> numberFormat.format( 211 | value)); 212 | return data; 213 | } 214 | 215 | public LineData createLineChartData() 216 | { 217 | ArrayList xValues = new ArrayList<>(); 218 | for(int i = 0; i < 12; i++) 219 | { 220 | xValues.add(i + ""); 221 | } 222 | 223 | ArrayList entries = new ArrayList<>(); 224 | 225 | for(int i = 0; i < 12; i++) 226 | { 227 | float mult = (5 + 1); 228 | int val = (int) (Math.random() * mult) + 1; 229 | entries.add(new Entry(val, i)); 230 | } 231 | 232 | LineDataSet set = new LineDataSet(entries, ""); 233 | set.setCircleColor(0xFF2196f3); 234 | set.setCircleRadius(4f); 235 | set.setDrawCircleHole(false); 236 | set.setColor(0xFF2196f3); 237 | set.setLineWidth(2f); 238 | set.setDrawValues(false); 239 | 240 | return new LineData(xValues, set); 241 | } 242 | } -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/NotificationPermissionActivity.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp; 2 | import android.os.Bundle; 3 | import android.support.annotation.Nullable; 4 | import android.support.v7.app.ActionBar; 5 | import android.support.v7.widget.Toolbar; 6 | import android.view.MenuItem; 7 | 8 | import org.researchstack.backbone.ui.PinCodeActivity; 9 | import org.researchstack.backbone.ui.views.SubmitBar; 10 | 11 | public class NotificationPermissionActivity extends PinCodeActivity 12 | { 13 | 14 | @Override 15 | protected void onCreate(@Nullable Bundle savedInstanceState) 16 | { 17 | super.onCreate(savedInstanceState); 18 | super.setContentView(R.layout.activity_permission_notification); 19 | super.setResult(RESULT_CANCELED); 20 | 21 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 22 | setSupportActionBar(toolbar); 23 | 24 | ActionBar actionBar = getSupportActionBar(); 25 | actionBar.setDisplayHomeAsUpEnabled(true); 26 | actionBar.setDisplayShowTitleEnabled(true); 27 | 28 | SubmitBar submitBar = (SubmitBar) findViewById(R.id.submit_bar); 29 | submitBar.setPositiveAction(o -> { 30 | setResult(RESULT_OK); 31 | finish(); 32 | }); 33 | submitBar.setNegativeAction(o -> { 34 | finish(); 35 | }); 36 | } 37 | 38 | @Override 39 | public boolean onOptionsItemSelected(MenuItem item) 40 | { 41 | if(item.getItemId() == android.R.id.home) 42 | { 43 | finish(); 44 | return true; 45 | } 46 | 47 | return super.onOptionsItemSelected(item); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/SampleApplication.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp; 2 | 3 | import android.Manifest; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.os.Build; 7 | import android.support.multidex.MultiDex; 8 | 9 | import org.researchstack.skin.PermissionRequestManager; 10 | import org.researchstack.skin.ResearchStack; 11 | 12 | public class SampleApplication extends Application 13 | { 14 | 15 | public static final String PERMISSION_NOTIFICATIONS = "SampleApp.permission.NOTIFICATIONS"; 16 | 17 | @Override 18 | public void onCreate() 19 | { 20 | super.onCreate(); 21 | 22 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 23 | // Init RS Singleton 24 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 25 | 26 | ResearchStack.init(this, new SampleResearchStack()); 27 | 28 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 29 | // Init permission objects 30 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 31 | 32 | // If Build is M or >, add needed permissions 33 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 34 | { 35 | PermissionRequestManager.PermissionRequest location = new PermissionRequestManager.PermissionRequest(Manifest.permission.ACCESS_FINE_LOCATION, 36 | R.drawable.rss_ic_location_24dp, 37 | R.string.rss_permission_location_title, 38 | R.string.rss_permission_location_desc); 39 | location.setIsBlockingPermission(true); 40 | location.setIsSystemPermission(true); 41 | 42 | PermissionRequestManager.getInstance().addPermission(location); 43 | } 44 | 45 | // We have some unique permissions that tie into Settings. You will need 46 | // to handle the UI for this permission along w/ storing the result. 47 | PermissionRequestManager.PermissionRequest notifications = 48 | new PermissionRequestManager.PermissionRequest( 49 | PERMISSION_NOTIFICATIONS, 50 | R.drawable.rss_ic_notification_24dp, 51 | R.string.rss_permission_notification_title, 52 | R.string.rss_permission_notification_desc 53 | ); 54 | 55 | PermissionRequestManager.getInstance().addPermission(notifications); 56 | } 57 | 58 | @Override 59 | protected void attachBaseContext(Context base) 60 | { 61 | // This is needed for android versions < 5.0 or you can extend MultiDexApplication 62 | super.attachBaseContext(base); 63 | MultiDex.install(this); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/SampleDataProvider.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp; 2 | import android.content.Context; 3 | 4 | import org.researchstack.backbone.ResourcePathManager; 5 | import org.researchstack.backbone.result.TaskResult; 6 | import org.researchstack.sampleapp.bridge.BridgeDataProvider; 7 | import org.researchstack.skin.ResourceManager; 8 | 9 | 10 | public class SampleDataProvider extends BridgeDataProvider 11 | { 12 | public SampleDataProvider() 13 | { 14 | super(); 15 | } 16 | 17 | @Override 18 | public void processInitialTaskResult(Context context, TaskResult taskResult) 19 | { 20 | // handle result from initial task (save profile info to disk, upload to your server, etc) 21 | } 22 | 23 | @Override 24 | protected ResourcePathManager.Resource getPublicKeyResId() 25 | { 26 | return new SampleResourceManager.PemResource("bridge_key"); 27 | } 28 | 29 | @Override 30 | protected ResourcePathManager.Resource getTasksAndSchedules() 31 | { 32 | return ResourceManager.getInstance().getTasksAndSchedules(); 33 | } 34 | 35 | @Override 36 | protected String getBaseUrl() 37 | { 38 | return BuildConfig.STUDY_BASE_URL; 39 | } 40 | 41 | @Override 42 | protected String getStudyId() 43 | { 44 | return BuildConfig.STUDY_ID; 45 | } 46 | 47 | @Override 48 | protected String getStudyName() { 49 | return BuildConfig.STUDY_NAME; 50 | } 51 | 52 | @Override 53 | protected int getAppVersion() { 54 | return BuildConfig.VERSION_CODE; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/SamplePermissionResultManager.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp; 2 | import android.app.Activity; 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.support.v4.content.ContextCompat; 7 | 8 | import org.researchstack.skin.AppPrefs; 9 | import org.researchstack.skin.PermissionRequestManager; 10 | 11 | public class SamplePermissionResultManager extends PermissionRequestManager 12 | { 13 | private static final int RESULT_REQUEST_CODE_NOTIFICATION = 143; 14 | 15 | @Override 16 | public boolean hasPermission(Context context, String permissionId) 17 | { 18 | if (permissionId.equals(SampleApplication.PERMISSION_NOTIFICATIONS)) 19 | { 20 | return AppPrefs.getInstance(context).isTaskReminderEnabled(); 21 | } 22 | else 23 | { 24 | return ContextCompat.checkSelfPermission(context, permissionId) == PackageManager.PERMISSION_GRANTED; 25 | } 26 | } 27 | 28 | /** 29 | * Used to tell if the permission-id should be handled by the system (using 30 | * {@link Activity#requestPermissions(String[], int)}) or through our own custom implementation 31 | * in {@link #onRequestNonSystemPermission} 32 | * @param permissionId 33 | * @return 34 | */ 35 | @Override 36 | public boolean isNonSystemPermission(String permissionId) 37 | { 38 | // SampleApplication.PERMISSION_NOTIFICATIONS is our non-system permission so we return true 39 | // if permissionId's are the same 40 | return permissionId.equals(SampleApplication.PERMISSION_NOTIFICATIONS); 41 | } 42 | 43 | /** 44 | * This method is called when {@link #isNonSystemPermission} returns true. For example, if using 45 | * Google+ Sign In, you would create your signIn-Intent and start that activity. Any result 46 | * will then be passed through to {#link onNonSystemPermissionResult} 47 | * @param permissionId 48 | */ 49 | @Override 50 | public void onRequestNonSystemPermission(Activity activity, String permissionId) 51 | { 52 | Intent intent = new Intent(activity, NotificationPermissionActivity.class); 53 | activity.startActivityForResult(intent, RESULT_REQUEST_CODE_NOTIFICATION); 54 | } 55 | 56 | /** 57 | * Method is called when your Activity called in {@link #onRequestNonSystemPermission} has 58 | * returned with a result 59 | * @param requestCode 60 | * @param resultCode 61 | * @param data 62 | * @return 63 | */ 64 | @Override 65 | public boolean onNonSystemPermissionResult(Activity activity, int requestCode, int resultCode, Intent data) 66 | { 67 | if (requestCode == RESULT_REQUEST_CODE_NOTIFICATION) 68 | { 69 | AppPrefs.getInstance(activity).setTaskReminderComplete(resultCode == Activity.RESULT_OK); 70 | return true; 71 | } 72 | 73 | return false; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/SampleResearchStack.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp; 2 | 3 | import android.content.Context; 4 | 5 | import net.sqlcipher.database.SQLiteDatabase; 6 | 7 | import org.researchstack.backbone.storage.database.AppDatabase; 8 | import org.researchstack.backbone.storage.database.sqlite.SqlCipherDatabaseHelper; 9 | import org.researchstack.backbone.storage.database.sqlite.UpdatablePassphraseProvider; 10 | import org.researchstack.backbone.storage.file.EncryptionProvider; 11 | import org.researchstack.backbone.storage.file.FileAccess; 12 | import org.researchstack.backbone.storage.file.PinCodeConfig; 13 | import org.researchstack.backbone.storage.file.SimpleFileAccess; 14 | import org.researchstack.backbone.storage.file.aes.AesProvider; 15 | import org.researchstack.sampleapp.bridge.BridgeEncryptedDatabase; 16 | import org.researchstack.skin.AppPrefs; 17 | import org.researchstack.skin.DataProvider; 18 | import org.researchstack.skin.PermissionRequestManager; 19 | import org.researchstack.skin.ResearchStack; 20 | import org.researchstack.skin.ResourceManager; 21 | import org.researchstack.skin.TaskProvider; 22 | import org.researchstack.skin.UiManager; 23 | import org.researchstack.skin.notification.NotificationConfig; 24 | import org.researchstack.skin.notification.SimpleNotificationConfig; 25 | 26 | public class SampleResearchStack extends ResearchStack 27 | { 28 | 29 | @Override 30 | protected AppDatabase createAppDatabaseImplementation(Context context) 31 | { 32 | SQLiteDatabase.loadLibs(context); 33 | return new BridgeEncryptedDatabase(context, 34 | SqlCipherDatabaseHelper.DEFAULT_NAME, 35 | null, 36 | SqlCipherDatabaseHelper.DEFAULT_VERSION, 37 | new UpdatablePassphraseProvider()); 38 | } 39 | 40 | @Override 41 | protected FileAccess createFileAccessImplementation(Context context) 42 | { 43 | return new SimpleFileAccess(); 44 | } 45 | 46 | @Override 47 | protected PinCodeConfig getPinCodeConfig(Context context) 48 | { 49 | long autoLockTime = AppPrefs.getInstance(context).getAutoLockTime(); 50 | return new PinCodeConfig(autoLockTime); 51 | } 52 | 53 | @Override 54 | protected EncryptionProvider getEncryptionProvider(Context context) 55 | { 56 | return new AesProvider(); 57 | } 58 | 59 | @Override 60 | protected ResourceManager createResourceManagerImplementation(Context context) 61 | { 62 | return new SampleResourceManager(); 63 | } 64 | 65 | @Override 66 | protected UiManager createUiManagerImplementation(Context context) 67 | { 68 | return new SampleUiManager(); 69 | } 70 | 71 | @Override 72 | protected DataProvider createDataProviderImplementation(Context context) 73 | { 74 | return new SampleDataProvider(); 75 | } 76 | 77 | @Override 78 | protected TaskProvider createTaskProviderImplementation(Context context) 79 | { 80 | return new SampleTaskProvider(context); 81 | } 82 | 83 | @Override 84 | protected NotificationConfig createNotificationConfigImplementation(Context context) 85 | { 86 | return new SimpleNotificationConfig(); 87 | } 88 | 89 | @Override 90 | protected PermissionRequestManager createPermissionRequestManagerImplementation(Context context) 91 | { 92 | return new SamplePermissionResultManager(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/SampleResourceManager.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp; 2 | import android.text.TextUtils; 3 | 4 | import org.researchstack.backbone.ResourcePathManager; 5 | import org.researchstack.skin.ResourceManager; 6 | import org.researchstack.skin.model.ConsentSectionModel; 7 | import org.researchstack.skin.model.SchedulesAndTasksModel; 8 | import org.researchstack.skin.model.SectionModel; 9 | import org.researchstack.skin.model.StudyOverviewModel; 10 | import org.researchstack.skin.model.TaskModel; 11 | 12 | public class SampleResourceManager extends ResourceManager 13 | { 14 | private static final String BASE_PATH_HTML = "html"; 15 | private static final String BASE_PATH_JSON = "json"; 16 | private static final String BASE_PATH_JSON_SURVEY = "json/survey"; 17 | private static final String BASE_PATH_PDF = "pdf"; 18 | private static final String BASE_PATH_VIDEO = "mp4"; 19 | 20 | public static final int PEM = 4; 21 | public static final int SURVEY = 5; 22 | 23 | @Override 24 | public Resource getStudyOverview() 25 | { 26 | return new Resource(Resource.TYPE_JSON, 27 | BASE_PATH_JSON, 28 | "study_overview", 29 | StudyOverviewModel.class); 30 | } 31 | 32 | @Override 33 | public Resource getConsentHtml() 34 | { 35 | return new Resource(Resource.TYPE_HTML, BASE_PATH_HTML, "asthma_fullconsent"); 36 | } 37 | 38 | @Override 39 | public Resource getConsentPDF() 40 | { 41 | return new Resource(Resource.TYPE_PDF, BASE_PATH_HTML, "study_overview_consent_form"); 42 | } 43 | 44 | @Override 45 | public Resource getConsentSections() 46 | { 47 | return new Resource(Resource.TYPE_JSON, 48 | BASE_PATH_JSON, 49 | "consent_section", 50 | ConsentSectionModel.class); 51 | } 52 | 53 | @Override 54 | public Resource getLearnSections() 55 | { 56 | return new Resource(Resource.TYPE_JSON, BASE_PATH_JSON, "learn", SectionModel.class); 57 | } 58 | 59 | @Override 60 | public Resource getPrivacyPolicy() 61 | { 62 | return new Resource(Resource.TYPE_HTML, BASE_PATH_HTML, "app_privacy_policy"); 63 | } 64 | 65 | @Override 66 | public Resource getSoftwareNotices() 67 | { 68 | return new Resource(Resource.TYPE_HTML, BASE_PATH_HTML, "software_notices"); 69 | } 70 | 71 | @Override 72 | public Resource getTasksAndSchedules() 73 | { 74 | return new Resource(Resource.TYPE_JSON, 75 | BASE_PATH_JSON, 76 | "tasks_and_schedules", 77 | SchedulesAndTasksModel.class); 78 | } 79 | 80 | @Override 81 | public Resource getTask(String taskFileName) 82 | { 83 | return new Resource(Resource.TYPE_JSON, 84 | BASE_PATH_JSON_SURVEY, 85 | taskFileName, 86 | TaskModel.class); 87 | } 88 | 89 | @Override 90 | public String generatePath(int type, String name) 91 | { 92 | String dir; 93 | switch(type) 94 | { 95 | default: 96 | dir = null; 97 | break; 98 | case Resource.TYPE_HTML: 99 | dir = BASE_PATH_HTML; 100 | break; 101 | case Resource.TYPE_JSON: 102 | dir = BASE_PATH_JSON; 103 | break; 104 | case Resource.TYPE_PDF: 105 | dir = BASE_PATH_PDF; 106 | break; 107 | case Resource.TYPE_MP4: 108 | dir = BASE_PATH_VIDEO; 109 | break; 110 | case SURVEY: 111 | dir = BASE_PATH_JSON_SURVEY; 112 | break; 113 | } 114 | 115 | StringBuilder path = new StringBuilder(); 116 | if(! TextUtils.isEmpty(dir)) 117 | { 118 | path.append(dir).append("/"); 119 | } 120 | 121 | return path.append(name).append(".").append(getFileExtension(type)).toString(); 122 | } 123 | 124 | @Override 125 | public String getFileExtension(int type) 126 | { 127 | switch(type) 128 | { 129 | case PEM: 130 | return "pem"; 131 | case SURVEY: 132 | return "json"; 133 | default: 134 | return super.getFileExtension(type); 135 | } 136 | } 137 | 138 | public static class PemResource extends ResourcePathManager.Resource 139 | { 140 | 141 | public PemResource(String name) 142 | { 143 | super(SampleResourceManager.PEM, null, name); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/SampleSettingsActivity.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp; 2 | import android.os.Bundle; 3 | import android.support.v7.app.ActionBar; 4 | import android.support.v7.widget.Toolbar; 5 | import android.view.MenuItem; 6 | 7 | import org.researchstack.skin.ui.BaseActivity; 8 | 9 | public class SampleSettingsActivity extends BaseActivity 10 | { 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) 13 | { 14 | super.onCreate(savedInstanceState); 15 | setContentView(org.researchstack.skin.R.layout.rss_activity_fragment); 16 | 17 | Toolbar toolbar = (Toolbar) findViewById(org.researchstack.skin.R.id.toolbar); 18 | setSupportActionBar(toolbar); 19 | 20 | ActionBar actionBar = getSupportActionBar(); 21 | actionBar.setDisplayHomeAsUpEnabled(true); 22 | 23 | if(savedInstanceState == null) 24 | { 25 | getSupportFragmentManager().beginTransaction() 26 | .add(org.researchstack.skin.R.id.container, new SampleSettingsFragment()) 27 | .commit(); 28 | } 29 | } 30 | 31 | 32 | @Override 33 | public boolean onOptionsItemSelected(MenuItem item) 34 | { 35 | if(item.getItemId() == android.R.id.home) 36 | { 37 | onBackPressed(); 38 | return true; 39 | } 40 | 41 | return super.onOptionsItemSelected(item); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/SampleSettingsFragment.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp; 2 | import android.os.Bundle; 3 | import android.support.v7.preference.Preference; 4 | import android.support.v7.preference.PreferenceCategory; 5 | import android.support.v7.preference.PreferenceScreen; 6 | 7 | import org.researchstack.skin.ui.fragment.SettingsFragment; 8 | 9 | public class SampleSettingsFragment extends SettingsFragment 10 | { 11 | public static final String KEY_EXAMPLE = "Sample.EXAMPLE"; 12 | 13 | @Override 14 | public void onCreatePreferences(Bundle bundle, String s) 15 | { 16 | super.onCreatePreferences(bundle, s); 17 | 18 | // Get our screen which is created in Skin SettingsFragment 19 | PreferenceScreen screen = getPreferenceScreen(); 20 | 21 | // Get profile preference 22 | PreferenceCategory category = (PreferenceCategory) screen.findPreference(KEY_PROFILE); 23 | 24 | // If category exists, we should add mole mapper specific things. If not, that means we 25 | // are not consented so we have no data to set. 26 | if(category != null) 27 | { 28 | // Example Preference 29 | Preference checkBoxPref = new Preference(screen.getContext()); 30 | checkBoxPref.setKey(KEY_EXAMPLE); 31 | checkBoxPref.setTitle("Example Title"); 32 | checkBoxPref.setSummary("You need to extend your settings fragment from Skin's " + 33 | "Settings fragment and then modify any preferences that you'd like"); 34 | category.addPreference(checkBoxPref); 35 | } 36 | } 37 | 38 | @Override 39 | public String getVersionString() 40 | { 41 | return getString(org.researchstack.skin.R.string.rss_settings_version, 42 | BuildConfig.VERSION_NAME, 43 | BuildConfig.VERSION_CODE); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/SampleTaskProvider.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp; 2 | import android.content.Context; 3 | 4 | import org.researchstack.backbone.answerformat.AnswerFormat; 5 | import org.researchstack.backbone.answerformat.BooleanAnswerFormat; 6 | import org.researchstack.backbone.answerformat.ChoiceAnswerFormat; 7 | import org.researchstack.backbone.answerformat.DateAnswerFormat; 8 | import org.researchstack.backbone.answerformat.IntegerAnswerFormat; 9 | import org.researchstack.backbone.model.Choice; 10 | import org.researchstack.backbone.step.FormStep; 11 | import org.researchstack.backbone.step.InstructionStep; 12 | import org.researchstack.backbone.step.QuestionStep; 13 | import org.researchstack.backbone.step.Step; 14 | import org.researchstack.backbone.task.OrderedTask; 15 | import org.researchstack.backbone.task.Task; 16 | import org.researchstack.skin.TaskProvider; 17 | import org.researchstack.skin.task.ConsentTask; 18 | import org.researchstack.skin.task.SignInTask; 19 | import org.researchstack.skin.task.SignUpTask; 20 | 21 | import java.util.ArrayList; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | 25 | public class SampleTaskProvider extends TaskProvider 26 | { 27 | private HashMap map = new HashMap<>(); 28 | 29 | public SampleTaskProvider(Context context) 30 | { 31 | put(TASK_ID_INITIAL, createInitialTask(context)); 32 | put(TASK_ID_CONSENT, ConsentTask.create(context, TASK_ID_CONSENT)); 33 | put(TASK_ID_SIGN_IN, new SignInTask(context)); 34 | put(TASK_ID_SIGN_UP, new SignUpTask(context)); 35 | } 36 | 37 | @Override 38 | public Task get(String taskId) 39 | { 40 | return map.get(taskId); 41 | } 42 | 43 | @Override 44 | public void put(String id, Task task) 45 | { 46 | map.put(id, task); 47 | } 48 | 49 | private Task createInitialTask(Context context) 50 | { 51 | List steps = new ArrayList<>(); 52 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 53 | // Intro step 54 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 55 | 56 | InstructionStep step = new InstructionStep("intro", 57 | "About You", 58 | "We'd like to ask you a few questions to better understand potential robot risks\n\nThese questions should take less than 5 minutes"); 59 | step.setStepTitle(R.string.task_inital_toolbar_title); 60 | 61 | // Add to Task 62 | steps.add(step); 63 | 64 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 65 | // Basic Info Form step 66 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 67 | 68 | FormStep basicInfoForm = new FormStep("basicInfo", "About You", ""); 69 | basicInfoForm.setStepTitle(R.string.task_inital_toolbar_title); 70 | 71 | // Date of Birth 72 | DateAnswerFormat dateOfBirthFormat = new DateAnswerFormat(AnswerFormat.DateAnswerStyle.Date); 73 | QuestionStep dateOfBirthStep = new QuestionStep("dateOfBirth", 74 | "Date of Birth", 75 | dateOfBirthFormat); 76 | 77 | // Gender 78 | AnswerFormat genderFormat = new ChoiceAnswerFormat(AnswerFormat.ChoiceAnswerStyle.SingleChoice, 79 | new Choice<>("Female", 0), 80 | new Choice<>("Male", 1), 81 | new Choice<>("Other", 2)); 82 | QuestionStep genderStep = new QuestionStep("gender", "Gender", genderFormat); 83 | 84 | // Zip Code 85 | IntegerAnswerFormat zipCodeFormat = new IntegerAnswerFormat(0, 99999); 86 | QuestionStep zipCodeStep = new QuestionStep("zipCode", 87 | "What is your zip code?", 88 | zipCodeFormat); 89 | 90 | // Set items on FormStep 91 | basicInfoForm.setOptional(true); 92 | basicInfoForm.setFormSteps(dateOfBirthStep, genderStep, zipCodeStep); 93 | 94 | // Add to Task 95 | steps.add(basicInfoForm); 96 | 97 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 98 | // Eye and Hair color form step 99 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 100 | 101 | FormStep hairEyesForm = new FormStep("hairEyesInfo", "Natural Hair and Eye Color", ""); 102 | hairEyesForm.setStepTitle(R.string.task_inital_toolbar_title); 103 | 104 | // Hair Color 105 | AnswerFormat hairColorFormat = new ChoiceAnswerFormat(AnswerFormat.ChoiceAnswerStyle.SingleChoice, 106 | new Choice<>("Red Hair", "redHair"), 107 | new Choice<>("Blond Hair", "blondeHair"), 108 | new Choice<>("Brown Hair", "brownHair"), 109 | new Choice<>("Black Hair", "blackHair")); 110 | QuestionStep hairColorStep = new QuestionStep("hairColor", "Hair Color", hairColorFormat); 111 | 112 | AnswerFormat eyeColorFormat = new ChoiceAnswerFormat(AnswerFormat.ChoiceAnswerStyle.SingleChoice, 113 | new Choice<>("Blue Eyes", "blueEyes"), 114 | new Choice<>("Green Eyes", "greenEyes"), 115 | new Choice<>("Brown Eyes", "brownEyes")); 116 | QuestionStep eyeColorStep = new QuestionStep("eyeColor", "Eye Color", eyeColorFormat); 117 | 118 | // Set items on FormStep 119 | hairEyesForm.setOptional(true); 120 | hairEyesForm.setFormSteps(hairColorStep, eyeColorStep); 121 | 122 | // Add to Task 123 | steps.add(hairEyesForm); 124 | 125 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 126 | // Profession Step 127 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 128 | 129 | // iOS defines this as a single choice, should be MultiChoice 130 | AnswerFormat professionFormat = new ChoiceAnswerFormat(AnswerFormat.ChoiceAnswerStyle.SingleChoice, 131 | new Choice<>("Chocolate", "chocolate"), 132 | new Choice<>("Vanilla", "vanilla"), 133 | new Choice<>("Strawberry", "strawberry"), 134 | new Choice<>("Cookies & Cream", "cookies_cream"), 135 | new Choice<>("I am Robot, what is ice cream?", "robot")); 136 | QuestionStep professionStep = new QuestionStep("profession", 137 | "What is your favorite flavor of ice cream?", 138 | professionFormat); 139 | professionStep.setStepTitle(R.string.task_inital_toolbar_title); 140 | professionStep.setOptional(true); 141 | 142 | // Add to Task 143 | steps.add(professionStep); 144 | 145 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 146 | // Medical Info Form Step 147 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 148 | 149 | FormStep medicalInfoForm = new FormStep("medicalInfo", "Medical Information", ""); 150 | medicalInfoForm.setStepTitle(R.string.task_inital_toolbar_title); 151 | 152 | BooleanAnswerFormat booleanAnswerFormat = new BooleanAnswerFormat(context.getString(R.string.rsb_yes), 153 | context.getString(R.string.rsb_no)); 154 | 155 | QuestionStep robotStep = new QuestionStep("confirmRobot", 156 | "Are you a robot?", 157 | booleanAnswerFormat); 158 | QuestionStep autoImmuneStep = new QuestionStep("feelings", 159 | "Does your robot body feel?", 160 | booleanAnswerFormat); 161 | QuestionStep immunocompromisedStep = new QuestionStep("arnold", 162 | "Are you stronger than a T-1000?", 163 | booleanAnswerFormat); 164 | 165 | // Set items on FormStep 166 | medicalInfoForm.setOptional(true); 167 | medicalInfoForm.setFormSteps(robotStep, autoImmuneStep, immunocompromisedStep); 168 | 169 | // Add to Task 170 | steps.add(medicalInfoForm); 171 | 172 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 173 | // Thank You Step 174 | //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 175 | 176 | InstructionStep thankYouStep = new InstructionStep("thankYou", 177 | "Thank You!", 178 | "Your participation in this study is helping us to better understand risks of becoming a robot\n\nYour task now is to take robot surveys each month. You don't have to get them all, but the more the better!\n\nHappy robot-ing!"); 179 | thankYouStep.setStepTitle(R.string.task_inital_toolbar_title); 180 | // Add to Task 181 | steps.add(thankYouStep); 182 | 183 | return new OrderedTask(TASK_ID_INITIAL, steps); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/SampleUiManager.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp; 2 | import android.content.Context; 3 | 4 | import org.researchstack.backbone.answerformat.AnswerFormat; 5 | import org.researchstack.backbone.answerformat.ChoiceAnswerFormat; 6 | import org.researchstack.backbone.answerformat.BooleanAnswerFormat; 7 | import org.researchstack.backbone.model.Choice; 8 | import org.researchstack.backbone.result.StepResult; 9 | import org.researchstack.backbone.step.QuestionStep; 10 | import org.researchstack.backbone.step.FormStep; 11 | import org.researchstack.backbone.step.Step; 12 | import org.researchstack.skin.ActionItem; 13 | import org.researchstack.skin.UiManager; 14 | import org.researchstack.skin.task.OnboardingTask; 15 | import org.researchstack.skin.ui.LearnActivity; 16 | import org.researchstack.skin.ui.fragment.ActivitiesFragment; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Map.Entry; 22 | import java.util.Iterator; 23 | 24 | public class SampleUiManager extends UiManager 25 | { 26 | /** 27 | * @return List of ActionItems w/ Fragment class items 28 | */ 29 | @Override 30 | public List getMainTabBarItems() 31 | { 32 | List navItems = new ArrayList<>(); 33 | 34 | navItems.add(new ActionItem.ActionItemBuilder().setId(R.id.nav_activities) 35 | .setTitle(R.string.rss_activities) 36 | .setIcon(R.drawable.rss_ic_tab_activities) 37 | .setClass(ActivitiesFragment.class) 38 | .build()); 39 | 40 | navItems.add(new ActionItem.ActionItemBuilder().setId(R.id.nav_dashboard) 41 | .setTitle(R.string.rss_dashboard) 42 | .setIcon(R.drawable.rss_ic_tab_dashboard) 43 | .setClass(DashboardFragment.class) 44 | .build()); 45 | 46 | return navItems; 47 | } 48 | 49 | /** 50 | * @return List of ActionItems w/ Activity class items. The class items are then used to 51 | * construct an intent for a MenuItem when {@link org.researchstack.skin.ui.MainActivity#onCreateOptionsMenu} 52 | * is called 53 | */ 54 | @Override 55 | public List getMainActionBarItems() 56 | { 57 | List navItems = new ArrayList<>(); 58 | 59 | navItems.add(new ActionItem.ActionItemBuilder().setId(R.id.nav_learn) 60 | .setTitle(R.string.rss_learn) 61 | .setIcon(R.drawable.rss_ic_action_learn) 62 | .setClass(LearnActivity.class) 63 | .build()); 64 | 65 | navItems.add(new ActionItem.ActionItemBuilder().setId(R.id.nav_settings) 66 | .setTitle(R.string.rss_settings) 67 | .setIcon(R.drawable.rss_ic_action_settings) 68 | .setClass(SampleSettingsActivity.class) 69 | .build()); 70 | 71 | return navItems; 72 | } 73 | 74 | /** 75 | * This needs to change, strange that UIManager is handling step creation of OnboardingTask. 76 | * We should have a place where all the steps are created and returned if dev wants to edit any 77 | * step within said task. 78 | * 79 | * @return InclusionCriteria Step 80 | * @param context 81 | */ 82 | @Override 83 | public Step getInclusionCriteriaStep(Context context) 84 | { 85 | BooleanAnswerFormat booleanAnswerFormat = new BooleanAnswerFormat("true","false"); 86 | 87 | QuestionStep ageStep = new QuestionStep("signupInclusionAgeStep", 88 | "Are you over 18 years of age?", 89 | booleanAnswerFormat); 90 | 91 | QuestionStep diagnosisStep = new QuestionStep("signupInclusionDiagnosisStep", 92 | "Have you been diagnosed with pre-diabetes or diabetes?", 93 | booleanAnswerFormat); 94 | 95 | QuestionStep englishStep = new QuestionStep("signupInclusionEnglishStep", 96 | "Can you read and understand English in order to provide informed consent and to follow the instructions?", 97 | booleanAnswerFormat); 98 | 99 | QuestionStep usaStep = new QuestionStep("signupInclusionUsaStep", 100 | "Do you live in the United States of America?", 101 | booleanAnswerFormat); 102 | 103 | 104 | 105 | FormStep eligibilityFormStep = new FormStep(OnboardingTask.SignUpInclusionCriteriaStepIdentifier, "", ""); 106 | // Set items on FormStep 107 | eligibilityFormStep.setStepTitle(R.string.rss_eligibility); 108 | eligibilityFormStep.setOptional(false); 109 | eligibilityFormStep.setFormSteps(ageStep, diagnosisStep, englishStep, usaStep); 110 | 111 | return eligibilityFormStep; 112 | 113 | } 114 | 115 | private Boolean getBooleanAnswer(Map mapStepResult, String id){ 116 | StepResult stepResult = (StepResult)mapStepResult.get(id); 117 | if (stepResult == null) return false; 118 | Map mapResult = stepResult.getResults(); 119 | if (mapResult == null) return false; 120 | Boolean answer = (Boolean)mapResult.get("answer"); 121 | System.out.println("id = : " + id + ", answer = " + answer); 122 | if (answer == null || answer == false) return false; 123 | else return true; 124 | 125 | } 126 | 127 | //If all answers are true, result is true 128 | //If any answer is false, result is false 129 | @Override 130 | public boolean isInclusionCriteriaValid(StepResult stepResult) 131 | { 132 | if(stepResult != null) 133 | { 134 | Map mapStepResult = stepResult.getResults(); 135 | Boolean answer = getBooleanAnswer(mapStepResult, "signupInclusionAgeStep"); 136 | if (answer == false) return false; 137 | answer = getBooleanAnswer(mapStepResult, "signupInclusionDiagnosisStep"); 138 | if (answer == false) return false; 139 | answer = getBooleanAnswer(mapStepResult, "signupInclusionEnglishStep"); 140 | if (answer == false) return false; 141 | answer = getBooleanAnswer(mapStepResult, "signupInclusionUsaStep"); 142 | if (answer == false) return false; 143 | return true; 144 | } 145 | return false; 146 | } 147 | 148 | @Override 149 | public boolean isConsentSkippable() 150 | { 151 | return true; 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/BridgeDataArchive.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge; 2 | import android.content.Context; 3 | import android.support.annotation.NonNull; 4 | import android.util.Base64; 5 | 6 | import com.google.gson.Gson; 7 | 8 | import org.researchstack.backbone.ResourcePathManager; 9 | import org.researchstack.backbone.utils.FileUtils; 10 | import org.researchstack.backbone.utils.LogExt; 11 | import org.spongycastle.cms.CMSAlgorithm; 12 | import org.spongycastle.cms.CMSEnvelopedData; 13 | import org.spongycastle.cms.CMSEnvelopedDataGenerator; 14 | import org.spongycastle.cms.CMSException; 15 | import org.spongycastle.cms.CMSProcessableFile; 16 | import org.spongycastle.cms.jcajce.JceCMSContentEncryptorBuilder; 17 | import org.spongycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator; 18 | import org.spongycastle.jcajce.provider.asymmetric.x509.CertificateFactory; 19 | import org.spongycastle.operator.OutputEncryptor; 20 | 21 | import java.io.BufferedInputStream; 22 | import java.io.BufferedOutputStream; 23 | import java.io.ByteArrayInputStream; 24 | import java.io.File; 25 | import java.io.FileNotFoundException; 26 | import java.io.FileOutputStream; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.security.DigestInputStream; 30 | import java.security.MessageDigest; 31 | import java.security.NoSuchAlgorithmException; 32 | import java.security.Security; 33 | import java.security.cert.CertificateException; 34 | import java.security.cert.X509Certificate; 35 | import java.util.zip.ZipEntry; 36 | import java.util.zip.ZipOutputStream; 37 | 38 | 39 | public class BridgeDataArchive 40 | { 41 | public static final String INFO_JSON_FILENAME = "info.json"; 42 | 43 | Info info; 44 | String filename; 45 | private ZipOutputStream outputStream; 46 | private File tempFile; 47 | 48 | public BridgeDataArchive(Info info) 49 | { 50 | this.info = info; 51 | this.filename = info.getFileName(); 52 | } 53 | 54 | public void start(File baseDir) throws FileNotFoundException 55 | { 56 | tempFile = new File(baseDir, filename + ".temp"); 57 | FileUtils.makeParent(tempFile); 58 | 59 | FileOutputStream dest = new FileOutputStream(tempFile); 60 | outputStream = new ZipOutputStream(new BufferedOutputStream(dest)); 61 | } 62 | 63 | public UploadRequest finishAndEncrypt(Context context, ResourcePathManager.Resource publicKey, File baseDir) throws IOException 64 | { 65 | try 66 | { 67 | // all sage bridge file uploads need an info.json describing the files in the zip 68 | addZipEntry(INFO_JSON_FILENAME, 69 | new ByteArrayInputStream(new Gson().toJson(info).getBytes())); 70 | } 71 | finally 72 | { 73 | if(outputStream != null) 74 | { 75 | outputStream.close(); 76 | } 77 | } 78 | 79 | LogExt.e(getClass(), "SAVED temp file: " + tempFile.getAbsolutePath()); 80 | 81 | try 82 | { 83 | InputStream encryptedInputStream = getEncryptedInputStream(context, 84 | tempFile, 85 | publicKey); 86 | 87 | File encryptedFile = new File(baseDir, filename); 88 | 89 | // might as well get the md5 hash while we're doing this 90 | MessageDigest md5 = MessageDigest.getInstance("MD5"); 91 | DigestInputStream inputStream = new DigestInputStream(encryptedInputStream, md5); 92 | 93 | FileUtils.copy(inputStream, encryptedFile); 94 | 95 | LogExt.e(getClass(), "SAVED Encrypted: " + encryptedFile.getAbsolutePath()); 96 | 97 | String md5Hash = Base64.encodeToString(md5.digest(), Base64.NO_WRAP); 98 | 99 | return new UploadRequest(encryptedFile.getName(), 100 | encryptedFile.length(), 101 | md5Hash, 102 | "application/zip"); 103 | } 104 | catch(NoSuchAlgorithmException e) 105 | { 106 | throw new RuntimeException("MD5 hashing not supported on this device", e); 107 | } 108 | finally 109 | { 110 | if(tempFile != null && tempFile.exists()) 111 | { 112 | tempFile.delete(); 113 | LogExt.e(getClass(), "DELETED TEMP FILE"); 114 | } 115 | } 116 | } 117 | 118 | public void addFile(Context context, BridgeDataInput dataFile) throws IOException 119 | { 120 | info.addFileInfo(new FileInfo(dataFile.filename, dataFile.endDate)); 121 | addZipEntry(dataFile.filename, dataFile.getInputStream(context)); 122 | } 123 | 124 | 125 | private void addZipEntry(String filename, InputStream in) throws IOException 126 | { 127 | ZipEntry entryOne = new ZipEntry(filename); 128 | 129 | BufferedInputStream origin = null; 130 | 131 | try 132 | { 133 | 134 | outputStream.putNextEntry(entryOne); 135 | 136 | origin = new BufferedInputStream(in); 137 | byte[] bytes = new byte[1024]; 138 | 139 | int read1; 140 | while((read1 = origin.read(bytes)) != - 1) 141 | { 142 | outputStream.write(bytes, 0, read1); 143 | } 144 | } 145 | finally 146 | { 147 | if(origin != null) 148 | { 149 | origin.close(); 150 | } 151 | 152 | } 153 | } 154 | 155 | static 156 | { 157 | Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1); 158 | } 159 | 160 | @NonNull 161 | private InputStream getEncryptedInputStream(Context context, File tempFile, ResourcePathManager.Resource publicKey) 162 | { 163 | // Creating a CMS encrypted input stream that only recipients can decrypt 164 | CMSEnvelopedDataGenerator gen = new CMSEnvelopedDataGenerator(); 165 | 166 | // Load bridge public key certificate from R.raw and add to recipients list 167 | try 168 | { 169 | CertificateFactory factory = new CertificateFactory(); 170 | InputStream keyInputStream = publicKey.open(context); 171 | X509Certificate cert = (X509Certificate) factory.engineGenerateCertificate( 172 | keyInputStream); 173 | JceKeyTransRecipientInfoGenerator recipientInfoGenerator = new JceKeyTransRecipientInfoGenerator( 174 | cert).setProvider("SC"); 175 | gen.addRecipientInfoGenerator(recipientInfoGenerator); 176 | 177 | // Generate encrypted input stream in AES-256-CBC format, output is DER, not S/MIME or PEM 178 | CMSProcessableFile content = new CMSProcessableFile(tempFile); 179 | OutputEncryptor encryptor = new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES256_CBC).setProvider( 180 | "SC").build(); 181 | CMSEnvelopedData envelopedData = gen.generate(content, encryptor); 182 | return new BufferedInputStream(new ByteArrayInputStream(envelopedData.getEncoded())); 183 | } 184 | catch(CertificateException | IOException | CMSException e) 185 | { 186 | throw new RuntimeException("Error encrypting with CMS", e); 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/BridgeDataInput.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge; 2 | import android.content.Context; 3 | 4 | import com.google.gson.Gson; 5 | 6 | import org.researchstack.backbone.StorageAccess; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.FileNotFoundException; 10 | import java.io.InputStream; 11 | 12 | 13 | public class BridgeDataInput 14 | { 15 | private static Gson gson = new Gson(); 16 | private Object gsonableObject; 17 | private Class clazz; 18 | private String inputFilename; 19 | String filename; 20 | String endDate; 21 | 22 | public BridgeDataInput(Object gsonableObject, Class clazz, String filename, String endDate) 23 | { 24 | this.gsonableObject = gsonableObject; 25 | this.clazz = clazz; 26 | this.filename = filename; 27 | this.endDate = endDate; 28 | } 29 | 30 | public BridgeDataInput(String inputFilename, String filename, String endDate) 31 | { 32 | this.inputFilename = inputFilename; 33 | this.filename = filename; 34 | this.endDate = endDate; 35 | } 36 | 37 | public InputStream getInputStream(Context context) throws FileNotFoundException 38 | { 39 | if(gsonableObject != null) 40 | { 41 | return new ByteArrayInputStream(gson.toJson(gsonableObject, clazz).getBytes()); 42 | } 43 | else 44 | { 45 | return new ByteArrayInputStream(StorageAccess.getInstance() 46 | .getFileAccess() 47 | .readData(context, inputFilename)); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/BridgeEncryptedDatabase.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge; 2 | import android.content.Context; 3 | 4 | import net.sqlcipher.database.SQLiteDatabase; 5 | 6 | import org.researchstack.backbone.storage.database.sqlite.SqlCipherDatabaseHelper; 7 | import org.researchstack.backbone.storage.database.sqlite.UpdatablePassphraseProvider; 8 | import org.researchstack.backbone.utils.LogExt; 9 | 10 | import java.sql.SQLException; 11 | import java.util.List; 12 | 13 | import co.touchlab.squeaky.db.sqlcipher.SQLiteDatabaseImpl; 14 | import co.touchlab.squeaky.table.TableUtils; 15 | 16 | 17 | public class BridgeEncryptedDatabase extends SqlCipherDatabaseHelper implements UploadQueue 18 | { 19 | public BridgeEncryptedDatabase(Context context, String name, SQLiteDatabase.CursorFactory cursorFactory, int version, UpdatablePassphraseProvider passphraseProvider) 20 | { 21 | super(context, name, cursorFactory, version, passphraseProvider); 22 | } 23 | 24 | @Override 25 | public void onCreate(SQLiteDatabase sqLiteDatabase) 26 | { 27 | super.onCreate(sqLiteDatabase); 28 | try 29 | { 30 | TableUtils.createTables(new SQLiteDatabaseImpl(sqLiteDatabase), UploadRequest.class); 31 | } 32 | catch(SQLException e) 33 | { 34 | throw new RuntimeException(e); 35 | } 36 | } 37 | 38 | @Override 39 | public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) 40 | { 41 | super.onUpgrade(sqLiteDatabase, oldVersion, newVersion); 42 | // handle future db upgrades here 43 | } 44 | 45 | public void saveUploadRequest(UploadRequest uploadRequest) 46 | { 47 | LogExt.d(this.getClass(), "saveUploadRequest() id: " + uploadRequest.id); 48 | 49 | try 50 | { 51 | this.getDao(UploadRequest.class).createOrUpdate(uploadRequest); 52 | } 53 | catch(SQLException e) 54 | { 55 | throw new RuntimeException(e); 56 | } 57 | } 58 | 59 | public List loadUploadRequests() 60 | { 61 | try 62 | { 63 | return this.getDao(UploadRequest.class).queryForAll().orderBy("id DESC").list(); 64 | } 65 | catch(SQLException e) 66 | { 67 | throw new RuntimeException(e); 68 | } 69 | } 70 | 71 | public void deleteUploadRequest(UploadRequest request) 72 | { 73 | 74 | LogExt.d(this.getClass(), "deleteUploadRequest() id: " + request.id); 75 | 76 | try 77 | { 78 | this.getDao(UploadRequest.class).delete(request); 79 | } 80 | catch(SQLException e) 81 | { 82 | throw new RuntimeException(e); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/BridgeMessageResponse.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge; 2 | public class BridgeMessageResponse 3 | { 4 | private String message; 5 | 6 | public String getMessage() 7 | { 8 | return message; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/FileInfo.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge; 2 | 3 | public class FileInfo 4 | { 5 | private String filename; 6 | private String timestamp; 7 | 8 | public FileInfo(String filename, String timestamp) 9 | { 10 | this.filename = filename; 11 | this.timestamp = timestamp; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/IdentifierHolder.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge; 2 | 3 | public class IdentifierHolder 4 | { 5 | public String type; 6 | public String identifier; 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/Info.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge; 2 | import android.content.Context; 3 | import android.content.pm.PackageInfo; 4 | import android.content.pm.PackageManager; 5 | import android.os.Build; 6 | 7 | import org.researchstack.backbone.utils.LogExt; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.UUID; 12 | 13 | 14 | public class Info 15 | { 16 | public static final int BRIDGE_PHONE_INFO_LIMIT = 48; 17 | 18 | private List files; 19 | private String item; 20 | private String surveyGuid; 21 | private String surveyCreatedOn; 22 | private int schemaRevision = 1; 23 | // since this buildconfig is in skin, this won't be correct 24 | private String appVersion; 25 | private String phoneInfo; 26 | 27 | public Info(Context context, String item, int schemaRevision) 28 | { 29 | this.item = item; 30 | this.schemaRevision = schemaRevision; 31 | this.files = new ArrayList<>(); 32 | initDetails(context); 33 | } 34 | 35 | public Info(Context context, String surveyGuid, String surveyCreatedOn) 36 | { 37 | this.surveyGuid = surveyGuid; 38 | this.surveyCreatedOn = surveyCreatedOn; 39 | this.files = new ArrayList<>(); 40 | initDetails(context); 41 | } 42 | 43 | private void initDetails(Context context) 44 | { 45 | String versionName; 46 | PackageManager manager = context.getPackageManager(); 47 | 48 | try 49 | { 50 | PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0); 51 | versionName = info.versionName; 52 | } 53 | catch(PackageManager.NameNotFoundException e) 54 | { 55 | LogExt.e(getClass(), "Could not find package version info"); 56 | versionName = "Unknown version"; 57 | } 58 | 59 | String fullPhoneInfo = Build.MANUFACTURER + " " + Build.MODEL; 60 | phoneInfo = fullPhoneInfo.substring(0, 61 | Math.min(fullPhoneInfo.length(), BRIDGE_PHONE_INFO_LIMIT)); 62 | appVersion = versionName; 63 | } 64 | 65 | public void addFileInfo(FileInfo fileInfo) 66 | { 67 | files.add(fileInfo); 68 | } 69 | 70 | public String getFileName() 71 | { 72 | return UUID.randomUUID().toString() + "_" + (item == null ? surveyGuid : item) + ".zip"; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/UploadQueue.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge; 2 | 3 | import java.util.List; 4 | 5 | 6 | public interface UploadQueue 7 | { 8 | List loadUploadRequests(); 9 | 10 | void saveUploadRequest(UploadRequest request); 11 | 12 | void deleteUploadRequest(UploadRequest request); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/UploadRequest.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge; 2 | import co.touchlab.squeaky.field.DatabaseField; 3 | import co.touchlab.squeaky.table.DatabaseTable; 4 | 5 | 6 | @DatabaseTable 7 | public class UploadRequest 8 | { 9 | @DatabaseField(generatedId = true) 10 | public int id; 11 | @DatabaseField 12 | public String name; 13 | @DatabaseField 14 | public long contentLength; 15 | @DatabaseField 16 | public String contentMd5; 17 | @DatabaseField 18 | public String contentType; 19 | @DatabaseField(canBeNull = true) 20 | public String bridgeId; 21 | 22 | public UploadRequest() 23 | { 24 | 25 | } 26 | 27 | public UploadRequest(String name, long contentLength, String contentMd5, String contentType) 28 | { 29 | this.name = name; 30 | this.contentLength = contentLength; 31 | this.contentMd5 = contentMd5; 32 | this.contentType = contentType; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/UploadSession.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge; 2 | 3 | public class UploadSession 4 | { 5 | public String id; 6 | public String url; 7 | public String expires; 8 | public String type; 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/UploadValidationStatus.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge; 2 | import com.google.gson.annotations.SerializedName; 3 | 4 | import java.util.List; 5 | 6 | public final class UploadValidationStatus 7 | { 8 | /** 9 | * Represents the lifecycle of an upload object. 10 | */ 11 | public enum UploadStatus 12 | { 13 | /** 14 | * Upload status is unknown. 15 | */ 16 | @SerializedName("unknown") 17 | UNKNOWN, 18 | 19 | /** 20 | * Initial state. Upload is requested. User needs to upload to specified URL and call uploadComplete. 21 | */ 22 | @SerializedName("requested") 23 | REQUESTED, 24 | 25 | /** 26 | * User has called uploadComplete. Upload validation is currently taking place. 27 | */ 28 | @SerializedName("validation_in_progress") 29 | VALIDATION_IN_PROGRESS, 30 | 31 | /** 32 | * Upload validation has failed. 33 | */ 34 | @SerializedName("validation_failed") 35 | VALIDATION_FAILED, 36 | 37 | /** 38 | * Upload has succeeded, including validation. 39 | */ 40 | @SerializedName("succeeded") 41 | SUCCEEDED 42 | } 43 | 44 | private final String id; 45 | private final List messageList; 46 | private final UploadStatus status; 47 | 48 | /** 49 | * Private constructor. All construction should go through the builder. 50 | */ 51 | private UploadValidationStatus(String id, List messageList, UploadStatus status) 52 | { 53 | this.id = id; 54 | this.messageList = messageList; 55 | this.status = status; 56 | } 57 | 58 | /** 59 | * Unique upload ID, as generated by the request upload API. Always non-null and non-empty. 60 | * 61 | * @return id 62 | */ 63 | public String getId() 64 | { 65 | return id; 66 | } 67 | 68 | /** 69 | *

70 | * List of validation messages, generally contains error messages. Since a single upload file may fail validation 71 | * in multiple ways, Bridge server will attempt to return all messages to the user. For example, the upload file 72 | * might be unencrypted, uncompressed, and it might not fit any of the expected schemas for the study. 73 | *

74 | *

75 | * This field is always non-null, but it may be empty. The list is immutable and contains non-null, non-empty 76 | * strings. 77 | *

78 | * 79 | * @return messageList 80 | */ 81 | public List getMessageList() 82 | { 83 | return messageList; 84 | } 85 | 86 | /** 87 | * Represents upload status, such as requested, validation in progress, validation failed, or succeeded. Always 88 | * non-null. 89 | * 90 | * @return uploadStatus 91 | */ 92 | public UploadStatus getStatus() 93 | { 94 | return status; 95 | } 96 | } -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/UserSessionInfo.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge; 2 | import java.util.Map; 3 | 4 | public class UserSessionInfo 5 | { 6 | /** 7 | * User's username 8 | */ 9 | private String username; 10 | 11 | /** 12 | * The users session token 13 | */ 14 | private String sessionToken; 15 | 16 | /** 17 | * True if the user has consented to some form of data sharing; false otherwise (in other words, 18 | * false if sharingScope is set to "no_sharing") 19 | */ 20 | private boolean dataSharing; 21 | 22 | /** 23 | * String representing the scope defined by possible values: 24 | *
    25 | *
  • no_sharing don't share data generated by this participant
  • 26 | *
  • sponsors_and_partners share data in data sets that are available to the study 27 | * researchers and their researcher partners only
  • 28 | *
  • all_qualified_researchers share data with researchers who qualify under the 29 | * specific governance qualifications to access the study's data set.
  • 30 | *
31 | */ 32 | private String sharingScope; 33 | 34 | /** 35 | * Does the user currently consent to participate in the research study? This is true if, for 36 | * all the subpopulations that apply for this user, the user has consented (provide a signature 37 | * for) every consent that is required. Participants do not need to consent to optional 38 | * consents and their consents do not need to be up-to-date in order to be considered consented 39 | * for the purpose of communicating with the Bridge server. 40 | */ 41 | private boolean consented; 42 | 43 | /** 44 | * True if the the user currently authenticated 45 | */ 46 | private boolean authenticated; 47 | 48 | /** 49 | * String representing the server environment defined by possible values: 50 | *
    51 | *
  • local development only
  • 52 | *
  • develop again, development only, won't be seen in the wild
  • 53 | *
  • staging the staging servers used to test studies and evaluate the Bridge 54 | * server before releasing an application in production
  • 55 | *
  • production the production servers
  • 56 | *
57 | */ 58 | private String environment; 59 | 60 | /** 61 | * True if all the required consents are signed and signed against an up-to-date version of 62 | * the consent. 63 | */ 64 | private boolean signedMostRecentConsent; 65 | 66 | /** 67 | * An array of roles assigned to this user. Possibly values defined: 68 | *
    69 | *
  • administrator Bridge administrators. These are Sage employees and the API for 70 | * these users is documented separately.
  • 71 | *
  • researcher Researchers have rights to access study consents (to add, edit and 72 | * delete them, as well as publish them), and they have the right to view study participant 73 | * information. Users with the researcher role will be granted developer privileges as well 74 | * .
  • 75 | *
  • developer Developers have the right to edit studies, upload schemas, surveys, 76 | * and schedules. They have complete control over a study's configuration, but they do not 77 | * have access to the consents or to information about participants in a study. Researchers 78 | * will be given developer privileges as well as the researcher role.
  • 79 | *
  • consented users Any participant in a study who has consented to research.
  • 80 | *
  • all users A publicly-accessible endpoint. No role required.
  • 81 | *
82 | */ 83 | private String[] roles; 84 | 85 | /** 86 | * An array of any data groups that have been assigned to this user. These are defined by a 87 | * user with developer role 88 | */ 89 | private String[] dataGroups; 90 | 91 | /** 92 | * Detailed information about all consents that apply to this user due to their assignment into 93 | * one or more study subpopulations. A map of object property/ConsentStatus object pairs, where 94 | * each property is the GUID of a subpopulation, and each consent status object is the consent 95 | * status for that subpopulation. 96 | */ 97 | private Map consentStatuses; 98 | 99 | public Map getConsentStatuses() 100 | { 101 | return consentStatuses; 102 | } 103 | 104 | public void setConsentStatuses(Map consentStatuses) 105 | { 106 | this.consentStatuses = consentStatuses; 107 | } 108 | 109 | public String getUsername() 110 | { 111 | return username; 112 | } 113 | 114 | public void setUsername(String username) 115 | { 116 | this.username = username; 117 | } 118 | 119 | public String getSessionToken() 120 | { 121 | return sessionToken; 122 | } 123 | 124 | public void setSessionToken(String sessionToken) 125 | { 126 | this.sessionToken = sessionToken; 127 | } 128 | 129 | public boolean isDataSharing() 130 | { 131 | return dataSharing; 132 | } 133 | 134 | public void setDataSharing(boolean dataSharing) 135 | { 136 | this.dataSharing = dataSharing; 137 | } 138 | 139 | public String getSharingScope() 140 | { 141 | return sharingScope; 142 | } 143 | 144 | public void setSharingScope(String sharingScope) 145 | { 146 | this.sharingScope = sharingScope; 147 | } 148 | 149 | public boolean isConsented() 150 | { 151 | return consented; 152 | } 153 | 154 | public void setConsented(boolean consented) 155 | { 156 | this.consented = consented; 157 | } 158 | 159 | public boolean isAuthenticated() 160 | { 161 | return authenticated; 162 | } 163 | 164 | public void setAuthenticated(boolean authenticated) 165 | { 166 | this.authenticated = authenticated; 167 | } 168 | 169 | public String getEnvironment() 170 | { 171 | return environment; 172 | } 173 | 174 | public void setEnvironment(String environment) 175 | { 176 | this.environment = environment; 177 | } 178 | 179 | public boolean isSignedMostRecentConsent() 180 | { 181 | return signedMostRecentConsent; 182 | } 183 | 184 | public void setSignedMostRecentConsent(boolean signedMostRecentConsent) 185 | { 186 | this.signedMostRecentConsent = signedMostRecentConsent; 187 | } 188 | 189 | public String[] getRoles() 190 | { 191 | return roles; 192 | } 193 | 194 | public void setRoles(String[] roles) 195 | { 196 | this.roles = roles; 197 | } 198 | 199 | public String[] getDataGroups() 200 | { 201 | return dataGroups; 202 | } 203 | 204 | public void setDataGroups(String[] dataGroups) 205 | { 206 | this.dataGroups = dataGroups; 207 | } 208 | 209 | 210 | /** 211 | * The consented and signedMostRecentConsent flags in the user's session give the general state 212 | * of the account in the study; the consent statuses provide a detailed breakdown of all 213 | * consents that apply to a participant based upon the subpopulations that apply to the 214 | * participant. 215 | *

216 | * When a client application receives a 412, the JSON payload also includes a session that will 217 | * include this information. At a minimum, the user will need to be presented with each required 218 | * consent and given an opportunity to sign each one in turn. Optional consents can also be 219 | * shown and may change the behavior of the client application, so it may also be necessary to 220 | * examine this collection for application behavior as well. Optional consents can be marked 221 | * declined, so that in the future, the app will know not to present UI around a request to 222 | * participate in that aspect of the study. 223 | *

224 | * For example, an optional consent might ask if the participant is willing to donate genetic 225 | * information to the study. If agreed to, the app might present a new tab with information on 226 | * how to do that. If declined, the application can prevent this request from being made in the 227 | * future. 228 | */ 229 | public static class ConsentStatus 230 | { 231 | 232 | /** 233 | * The GUID for the subpopulation of this consent 234 | */ 235 | private String subpopulationGuid; 236 | 237 | /** 238 | * The name of the subpopulation 239 | */ 240 | private String name; 241 | 242 | /** 243 | * Is this consent required? If required, the user must consent to it before being given 244 | * access to the server (until signed, a 412 response is returned from the server). 245 | */ 246 | private boolean required; 247 | 248 | /** 249 | * Has the participant consented to this consent agreement? 250 | */ 251 | private boolean consented; 252 | 253 | /** 254 | * True if the consent to participate made against the most recently published version of 255 | * this consent 256 | */ 257 | private boolean signedMostRecentConsent; 258 | 259 | public String getSubpopulationGuid() 260 | { 261 | return subpopulationGuid; 262 | } 263 | 264 | public void setSubpopulationGuid(String subpopulationGuid) 265 | { 266 | this.subpopulationGuid = subpopulationGuid; 267 | } 268 | 269 | public String getName() 270 | { 271 | return name; 272 | } 273 | 274 | public void setName(String name) 275 | { 276 | this.name = name; 277 | } 278 | 279 | public boolean isRequired() 280 | { 281 | return required; 282 | } 283 | 284 | public void setRequired(boolean required) 285 | { 286 | this.required = required; 287 | } 288 | 289 | public boolean isConsented() 290 | { 291 | return consented; 292 | } 293 | 294 | public void setConsented(boolean consented) 295 | { 296 | this.consented = consented; 297 | } 298 | 299 | public boolean isSignedMostRecentConsent() 300 | { 301 | return signedMostRecentConsent; 302 | } 303 | 304 | public void setSignedMostRecentConsent(boolean signedMostRecentConsent) 305 | { 306 | this.signedMostRecentConsent = signedMostRecentConsent; 307 | } 308 | 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/body/ConsentSignatureBody.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge.body; 2 | 3 | import org.researchstack.backbone.utils.FormatHelper; 4 | 5 | import java.util.Date; 6 | 7 | public class ConsentSignatureBody 8 | { 9 | /** 10 | * The identifier for the study under which the user is signing in 11 | */ 12 | public String study; 13 | 14 | /** 15 | * User's name 16 | */ 17 | public String name; 18 | 19 | /** 20 | * User's birthdate 21 | */ 22 | public String birthdate; 23 | 24 | /** 25 | * User's signature image data 26 | */ 27 | public String imageData; 28 | 29 | /** 30 | * User's signature image mime type 31 | */ 32 | public String imageMimeType; 33 | 34 | /** 35 | * User's sharing scope choice 36 | */ 37 | public String scope; 38 | 39 | public ConsentSignatureBody(String study, String name, Date birthdate, String imageData, String imageMimeType, String scope) 40 | { 41 | this.study = study; 42 | this.name = name; 43 | this.birthdate = FormatHelper.SIMPLE_FORMAT_DATE.format(birthdate); 44 | this.imageData = imageData; 45 | this.imageMimeType = imageMimeType; 46 | this.scope = scope; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/body/EmailBody.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge.body; 2 | public class EmailBody 3 | { 4 | 5 | /** 6 | * The identifier for the study under which the user is signing in 7 | */ 8 | private String study; 9 | 10 | /** 11 | * The email address of the user's account 12 | */ 13 | private String email; 14 | 15 | public EmailBody(String study, String email) 16 | { 17 | this.study = study; 18 | this.email = email; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/body/SharingOptionBody.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge.body; 2 | public class SharingOptionBody 3 | { 4 | /** 5 | * One of three string values: 6 | *

  • no_sharing: Don't share data generated by this participant;
  • 7 | *
  • sponsors_and_partners: share data in data sets that are available to the study 8 | * researchers and their researcher partners only
  • 9 | *
  • all_qualified_researchers: Share data with researchers who qualify under the 10 | * specific governance qualifications to access the study's data set. These researchers may do 11 | * research on new questions after the lifetime of the initial research
  • 12 | */ 13 | private String scope; 14 | 15 | public SharingOptionBody(String scope) 16 | { 17 | this.scope = scope; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/body/SignInBody.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge.body; 2 | public class SignInBody 3 | { 4 | 5 | /** 6 | * The identifier for the study under which the user is signing in 7 | */ 8 | private String study; 9 | 10 | /** 11 | * User's username or email address 12 | */ 13 | private String username; 14 | 15 | /** 16 | * User's password 17 | */ 18 | private String password; 19 | 20 | public SignInBody(String study, String username, String password) 21 | { 22 | this.study = study; 23 | this.username = username; 24 | this.password = password; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/body/SignUpBody.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge.body; 2 | public class SignUpBody 3 | { 4 | 5 | /** 6 | * The identifier for the study under which the user is signing in 7 | */ 8 | private String study; 9 | 10 | /** 11 | * User's email address, cannot be change once created 12 | */ 13 | private String email; 14 | 15 | /** 16 | * User's username 17 | */ 18 | private String username; 19 | 20 | /** 21 | * User's password. Constraints for an acceptable password can be set per study. 22 | */ 23 | private String password; 24 | 25 | /** 26 | * An array of roles to assign to this user (admins only) 27 | */ 28 | private String[] roles; 29 | 30 | /** 31 | * An array of data group tags to assign to this user. Client applications can set this up 32 | * during sign up, it's not required to be an admin to add these. 33 | */ 34 | private String[] dataGroups; 35 | 36 | private String type = "SignUp"; 37 | 38 | public SignUpBody(String study, String email, String username, String password, String[] roles, String[] dataGroups) 39 | { 40 | this.study = study; 41 | this.email = email; 42 | this.username = username; 43 | this.password = password; 44 | this.roles = roles; 45 | this.dataGroups = dataGroups; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/body/SurveyAnswer.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge.body; 2 | import org.researchstack.backbone.answerformat.AnswerFormat; 3 | import org.researchstack.backbone.result.StepResult; 4 | import org.researchstack.backbone.utils.FormatHelper; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Date; 8 | import java.util.List; 9 | 10 | 11 | public class SurveyAnswer 12 | { 13 | public int questionType; 14 | public String startDate; 15 | public String questionTypeName; 16 | public String item; 17 | public String endDate; 18 | 19 | public SurveyAnswer(StepResult stepResult) 20 | { 21 | AnswerFormat.Type type = (AnswerFormat.Type) stepResult.getAnswerFormat().getQuestionType(); 22 | this.questionType = type.ordinal(); 23 | this.questionTypeName = type.name(); 24 | this.startDate = FormatHelper.DEFAULT_FORMAT.format(stepResult.getStartDate()); 25 | this.item = stepResult.getIdentifier(); 26 | this.endDate = FormatHelper.DEFAULT_FORMAT.format(stepResult.getEndDate()); 27 | 28 | } 29 | 30 | public static SurveyAnswer create(StepResult stepResult) 31 | { 32 | AnswerFormat.Type type = (AnswerFormat.Type) stepResult.getAnswerFormat().getQuestionType(); 33 | SurveyAnswer answer; 34 | switch(type) 35 | { 36 | case SingleChoice: 37 | case MultipleChoice: 38 | answer = new ChoiceSurveyAnswer(stepResult); 39 | break; 40 | case Integer: 41 | answer = new NumericSurveyAnswer(stepResult); 42 | break; 43 | case Boolean: 44 | answer = new BooleanSurveyAnswer(stepResult); 45 | break; 46 | case Text: 47 | answer = new TextSurveyAnswer(stepResult); 48 | break; 49 | case Date: 50 | answer = new DateSurveyAnswer(stepResult); 51 | break; 52 | case None: 53 | case Scale: 54 | case Decimal: 55 | case Eligibility: 56 | case TimeOfDay: 57 | case DateAndTime: 58 | case TimeInterval: 59 | case Location: 60 | case Form: 61 | default: 62 | throw new RuntimeException("Cannot upload this question type to bridge"); 63 | } 64 | return answer; 65 | } 66 | 67 | public static class BooleanSurveyAnswer extends SurveyAnswer 68 | { 69 | 70 | private final Boolean booleanAnswer; 71 | 72 | public BooleanSurveyAnswer(StepResult result) 73 | { 74 | super(result); 75 | booleanAnswer = (Boolean) result.getResult(); 76 | } 77 | } 78 | 79 | public static class ChoiceSurveyAnswer extends SurveyAnswer 80 | { 81 | 82 | private List choiceAnswers; 83 | 84 | public ChoiceSurveyAnswer(StepResult stepResult) 85 | { 86 | super(stepResult); 87 | 88 | Object result = stepResult.getResult(); 89 | if(result instanceof List) 90 | { 91 | choiceAnswers = (List) result; 92 | } 93 | else 94 | { 95 | List list = new ArrayList<>(); 96 | list.add(result); 97 | choiceAnswers = list; 98 | } 99 | } 100 | } 101 | 102 | public static class NumericSurveyAnswer extends SurveyAnswer 103 | { 104 | 105 | private final Integer numericAnswer; 106 | 107 | public NumericSurveyAnswer(StepResult result) 108 | { 109 | super(result); 110 | numericAnswer = (Integer) result.getResult(); 111 | } 112 | } 113 | 114 | public static class TextSurveyAnswer extends SurveyAnswer 115 | { 116 | 117 | private final String textAnswer; 118 | 119 | public TextSurveyAnswer(StepResult result) 120 | { 121 | super(result); 122 | textAnswer = (String) result.getResult(); 123 | } 124 | } 125 | 126 | public static class DateSurveyAnswer extends SurveyAnswer 127 | { 128 | 129 | private final String dateAnswer; 130 | 131 | public DateSurveyAnswer(StepResult result) 132 | { 133 | super(result); 134 | Long dateResult = (Long) result.getResult(); 135 | dateAnswer = dateResult == null ? null : FormatHelper.DEFAULT_FORMAT.format(new Date(dateResult)); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /app/src/main/java/org/researchstack/sampleapp/bridge/body/WithdrawalBody.java: -------------------------------------------------------------------------------- 1 | package org.researchstack.sampleapp.bridge.body; 2 | public class WithdrawalBody 3 | { 4 | 5 | private final String reason; 6 | 7 | public WithdrawalBody(String reason) 8 | { 9 | this.reason = reason; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/res/color/text_color_settings_summary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/color/text_color_settings_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logo_disease_information_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/drawable-xhdpi/logo_disease_information_top.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logo_disease_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/drawable-xhdpi/logo_disease_large.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logo_institution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/drawable-xhdpi/logo_institution.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logo_lifemap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/drawable-xhdpi/logo_lifemap.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logo_msmc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/drawable-xhdpi/logo_msmc.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logo_msmc_horiz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/drawable-xhdpi/logo_msmc_horiz.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logo_mt_sinai_njh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/drawable-xhdpi/logo_mt_sinai_njh.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logo_research_institute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/drawable-xhdpi/logo_research_institute.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_permission_notification.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 24 | 25 | 36 | 37 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2196f3 4 | #1f77c0 5 | #2196f3 6 | 7 | #cccccc 8 | #EEEEEE 9 | 10 | #66FFFFFF 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SampleApp 3 | Yes 4 | No 5 | Initial Task 6 | Yes, I am a human. 7 | No, I am a robot but I am sentient and concerned about my health. 8 | 9 | No, I’m an alien. 10 | Were you born somewhere on planet earth and are you a 11 | human-ish? 12 | 13 | Notification Permission 14 | Grant 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 17 | 18 | 21 | 22 | 25 | 26 | 30 | 31 | 35 | 36 | 39 | 40 | 45 | 46 | 49 | 50 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 26 | 27 | 31 | 32 | 37 | 38 | 43 | 44 | 47 | 48 | 51 | 52 | 57 | 58 | 65 | 66 | 71 | 72 | 76 | 77 | 82 | 83 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /asthma_source_license: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Icahn School of Medicine at Mount Sinai. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder(s) nor the names of any contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. No license is granted to the trademarks of 16 | the copyright holders even if such marks are included in this software. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | jcenter() 7 | maven { url 'https://maven.fabric.io/public' } 8 | } 9 | 10 | dependencies { 11 | // change this to 1.5.x if you're not on Android Studio 2.0.0 beta 12 | classpath 'com.android.tools.build:gradle:2.1.3' 13 | classpath 'me.tatarka:gradle-retrolambda:3.2.3' 14 | classpath "com.neenbedankt.gradle.plugins:android-apt:1.4" 15 | 16 | // NOTE: Do not place your application dependencies here; they belong 17 | // in the individual module build.gradle files 18 | } 19 | } 20 | 21 | allprojects { 22 | repositories { 23 | jcenter() 24 | maven { url "https://jitpack.io" } // for MPAndroidChard, not on jcenter yet 25 | mavenLocal() // for updates to local rstack 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResearchStack/SampleApp/9bf30ba4dc62fad4f83d00a65e4be3e2b277539d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Sep 01 21:25:55 PDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /mpower_source_license: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Sage Bionetworks, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------