├── .gitignore ├── .travis.yml ├── AUTHORS.txt ├── CHANGES.txt ├── CONTRIBUTIONS.txt ├── COPYRIGHT.txt ├── LICENSE.txt ├── NOTES.txt ├── README.md ├── app ├── .gitignore ├── app-release.apk ├── build.gradle ├── proguard-rules.pro ├── release │ ├── app-release.apk │ └── output.json └── src │ ├── androidTest │ └── java │ │ └── openscience │ │ └── crowdsource │ │ └── video │ │ └── experiments │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ ├── openscience │ │ │ └── crowdsource │ │ │ │ └── video │ │ │ │ └── experiments │ │ │ │ ├── AppConfigService.java │ │ │ │ ├── AppLogger.java │ │ │ │ ├── CaptureActivity.java │ │ │ │ ├── ConsoleActivity.java │ │ │ │ ├── CrowdSourceVideoExperimentsApplication.java │ │ │ │ ├── DeviceInfo.java │ │ │ │ ├── ExceptionHandler.java │ │ │ │ ├── InfoActivity.java │ │ │ │ ├── LoadScenarioFilesAsyncTask.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── PFInfo.java │ │ │ │ ├── PlatformFeaturesService.java │ │ │ │ ├── RecognitionScenario.java │ │ │ │ ├── RecognitionScenarioService.java │ │ │ │ ├── ReloadScenariosAsyncTask.java │ │ │ │ ├── RemoteCallTask.java │ │ │ │ ├── ResultActivity.java │ │ │ │ ├── ScenarioInfoActivity.java │ │ │ │ ├── ScenariosActivity.java │ │ │ │ ├── UpdatePlatformFeaturesAsyncTask.java │ │ │ │ └── Utils.java │ │ └── org │ │ │ └── ctuning │ │ │ ├── openme │ │ │ └── openme.java │ │ │ └── shared-computing-resources-json │ │ │ ├── README.txt │ │ │ ├── ck.json │ │ │ └── cm.json │ └── res │ │ ├── drawable │ │ ├── b_console_active.png │ │ ├── b_console_inactive.png │ │ ├── b_flip_cam.png │ │ ├── b_home_active.png │ │ ├── b_home_inactive.png │ │ ├── b_info_active.png │ │ ├── b_info_inactive.png │ │ ├── b_log.png │ │ ├── b_open_image.png │ │ ├── b_recognize.png │ │ ├── b_recognize_bg.xml │ │ ├── b_recognize_enabled.xml │ │ ├── b_start_cam.png │ │ ├── b_stop_cam.png │ │ ├── capture_shape.xml │ │ ├── capture_window.xml │ │ ├── ico_arrow_down.png │ │ ├── ico_arrow_up.png │ │ ├── ico_camera_rotate.png │ │ ├── ico_delete.png │ │ ├── ico_download.png │ │ ├── ico_outer_link.png │ │ └── ico_preloading.png │ │ ├── layout-land │ │ ├── activity_main.xml │ │ └── activity_result.xml │ │ ├── layout-large-land │ │ ├── activity_main.xml │ │ └── activity_result.xml │ │ ├── layout-large │ │ ├── activity_capture.xml │ │ ├── activity_main.xml │ │ └── activity_result.xml │ │ ├── layout │ │ ├── activity_capture.xml │ │ ├── activity_console.xml │ │ ├── activity_info.xml │ │ ├── activity_main.xml │ │ ├── activity_result.xml │ │ ├── activity_scenario_info.xml │ │ ├── activity_scenarios.xml │ │ ├── custom_spinner.xml │ │ ├── scenario_item.xml │ │ └── selected_scenario.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-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── openscience │ └── crowdsource │ └── video │ └── experiments │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea/ 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | android: 7 | components: 8 | - platform-tools 9 | - tools 10 | - build-tools-25.0.0 11 | - android-22 12 | - extra-android-m2repository 13 | - extra-google-m2repository 14 | licenses: 15 | - android-sdk-license-.+ 16 | 17 | before_cache: 18 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 19 | 20 | cache: 21 | directories: 22 | - $HOME/.gradle/caches/ 23 | - $HOME/.gradle/wrapper/ 24 | 25 | script: ./gradlew assembleRelease 26 | 27 | 28 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | N: Daniil Efremov 2 | E: daniil.efremov@xored.com 3 | H: https://ru.linkedin.com/in/daniil-efremov-203a0620 4 | O: Xored, Russia 5 | C: major update of the original Android app to support image recognition and new CK scenarios! 6 | W: 7 | 8 | N: Grigori Fursin 9 | E: grigori@dividiti.com / Grigori.Fursin@cTuning.org 10 | H: http://fursin.net/research 11 | O: dividiti, UK / cTuning foundation, France 12 | C: original concept and design of experiment crowdsourcing using CK framework and repository 13 | W: 14 | 15 | N: Anton Lokhmotov 16 | E: anton@dividiti.com 17 | H: https://uk.linkedin.com/in/lokhmotov 18 | O: dividiti, UK 19 | C: testing, suggestions 20 | W: 21 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | # V2.12 2 | * added possibility to unzip large files from scenarios 3 | (such as MobileNet v1 models for ArmCL with many subfiles) 4 | 5 | # V2.11 6 | * fixed wrong photo resolution selection 7 | * added MobileNets 8 | 9 | # V2.10 10 | * fixed "access deined" error when detecting platform info 11 | on new mobiles with Android 7+ (Samsung S8+, Huawei Mate, etc) 12 | 13 | # V2.9 14 | * improved layout for smaller displays (800x480) 15 | * now supports both TensorFlow and Caffe for ARM64 and ARMV7 16 | 17 | # V2.8 18 | * added version to log and button to send log to the authors 19 | (to report bugs) 20 | 21 | # V2.7 22 | * adding support for OpenCL scenarios! 23 | 24 | # V2.6 25 | * more minor fixes 26 | 27 | # V2.5 28 | * more minor fixes 29 | 30 | # V2.4 31 | * Improved support for cameras without autofocus 32 | * Fixed CPU ABI detection on latest models 33 | 34 | # V2.3 35 | * Removing autofocus requirement 36 | 37 | # V2.2 38 | * Making app compatible with tablets in Google Play 39 | 40 | # V2.1 41 | * Minor changes (mainly text update) 42 | 43 | # V2.0 44 | * Major redesign of the whole app implemented by Daniil Efremov 45 | 46 | # V0.7 47 | * Alpha release 48 | 49 | 2016.10.22 * added recording of results to Collective Knowledge repo: http://cknowledge.org/repo 50 | -------------------------------------------------------------------------------- /CONTRIBUTIONS.txt: -------------------------------------------------------------------------------- 1 | CK uses standard 3-clause (new) BSD license. Contributions are welcome to 2 | either improve existing functionality or add new functionality possibly using 3 | new modules. 4 | 5 | Contributors should clearly mark their contributions including date and remark. 6 | 7 | You can easily contribute to CK and all related repositories by forking them 8 | on GitHub and then submitting a pull request. Alternatively, you can submit 9 | patches via collective-knowledge@googlegroups.com . 10 | 11 | CK bugs can be reported here: 12 | * https://github.com/ctuning/ck/issues 13 | 14 | Bugs related to shared repositories (predictive analytics, reproducible 15 | experimentation, autotuning, etc) should be reported via their corresponding 16 | GitHub pages. For example, you can report bugs about autotuning here: 17 | * https://github.com/ctuning/ck-autotuning/issues 18 | 19 | Thank you for all your help to support this community project! 20 | 21 | ================================================================================= 22 | Acknowledgments: 23 | 24 | N: Grigori Fursin 25 | E: Grigori.Fursin@cTuning.org 26 | H: http://fursin.net/research.html 27 | O: dividiti/cTuning foundation 28 | C: original concept and design of experiment crowdsourcing using CK framework and repository 29 | W: 30 | 31 | N: Daniil Efremov 32 | E: 33 | O: Xored, Russia 34 | C: major update of the original Android app to support image recognition and new CK scenarios! 35 | W: 36 | 37 | N: Dmitry Savenko 38 | E: 39 | O: Xored, Russia 40 | C: added continuous integration 41 | W: 42 | 43 | N: Anton Lokhmotov 44 | E: 45 | H: 46 | O: dividiti (UK) 47 | C: suggestions to improve app 48 | W: 49 | -------------------------------------------------------------------------------- /COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | (C)opyright 2016-2018 dividiti 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2014-2018 dividiti, cTuning foundation and volunteers 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, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of DIVIDITI and CTUNING FOUNDATION 15 | nor the names of its contributors may be used to endorse 16 | or promote products derived from this software without 17 | specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /NOTES.txt: -------------------------------------------------------------------------------- 1 | Do not upgrade to new 3.0.0 gradle if asked, since it requires android tools with min ver 26, 2 | while we tested this project with ver 25. When checking ver 26, there were many errors. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/dividiti/crowdsource-video-experiments-on-android.svg?branch=master)](https://travis-ci.org/dividiti/crowdsource-video-experiments-on-android) 2 | 3 | NEWS 4 | ==== 5 | * We use CK technology to power [open and reproducible ACM ReQuEST tournaments](http://cKnowledge.org/request) on co-design of Pareto-efficient software/hardware stack for deep learning; 6 | * We are building a [collective training set](http://cknowledge.org/repo/web.php?wcid=42b9a1221eb50259:collective_training_set) from user mispredictions and correct labels to improve models 7 | * Our collaborative work with ARM was presented at [ARM TechCon'16 (Oct. 27)](http://schedule.armtechcon.com/session/know-your-workloads-design-more-efficient-systems); 8 | * ARM uses CK as a front-end for systematic and reproducible benchmarking and tuning of real workloads: [link](https://github.com/ctuning/ck-wa); 9 | * Open challenges in computer engineering have been updated: [link](https://github.com/ctuning/ck/wiki/Research-and-development-challenges); 10 | * General Motors and dividiti shared CK workflow to crowdsource benchmarking and optimization of CAFFE (DNN framework) [here](https://github.com/dividiti/ck-caffe); 11 | * We have moved related Open Science resources [here](http://github.com/ctuning/ck/wiki/Enabling-Open-Science); 12 | 13 | Introduction 14 | ============ 15 | 16 | This [CK-powered](http://github.com/ctuning/ck) open-source Android application 17 | lets the community participate in experiment crowdsourcing which require webcam 18 | (such as crowd-benchmarking and crowd-tuning Caffe, Tensorflow 19 | and other DNN frameworks or any realistic application for image processing 20 | and recognition) using their mobile devices (mobile phones, tablets, IoT, etc) 21 | and exchange knowledge via public CK servers. 22 | 23 | You can download this app from the [Google Play Store](https://play.google.com/store/apps/details?id=openscience.crowdsource.video.experiments). 24 | 25 | You can also find public results at [Live CK repo](http://cknowledge.org/repo)! 26 | 27 | Public scenarios are prepared using this [CK GitHub repo](https://github.com/ctuning/ck-crowd-scenarios). 28 | Caffe libraries are generated using [CK-Caffe framework](https://github.com/dividiti/ck-caffe). 29 | Collective training set is available [here](http://cknowledge.org/repo/web.php?wcid=42b9a1221eb50259:collective_training_set). 30 | 31 | Current scenarios include multi-dimensional and multi-objective 32 | optimization of benchmarks and real workloads such as 33 | Caffe, TensorFlow and other DNN frameworks in terms 34 | of performance, accuracy, energy, memory footprint, cost, etc. 35 | 36 | See our [vision paper](http://dx.doi.org/10.1145/2909437.2909449). 37 | 38 | Related outdated projects: 39 | * https://github.com/sh1r0/caffe-android-lib 40 | * https://github.com/sh1r0/caffe-android-demo 41 | 42 | License 43 | ======= 44 | * Permissive 3-clause BSD license. (See `LICENSE.txt` for more details). 45 | 46 | Minimal requirements 47 | ==================== 48 | Android 5.0+ (we hope to provide support for older Android versions soon)! 49 | 50 | Authors 51 | ======= 52 | * Daniil Efremov 53 | * Grigori Fursin (original crowd-tuner: https://github.com/ctuning/crowdsource-experiments-using-android-devices) 54 | * Anton Lokhmotov 55 | 56 | Privacy Policy 57 | ============== 58 | 59 | This application requires access to your Camera to let you 60 | capture images, recognize them and collect various performance 61 | statistics. Note that, by default, no images are sent to public servers! 62 | Only if misprediction happens, you are encouraged but not obliged (!) 63 | to submit incorrectly recognized image with the correct label 64 | to the public server to help the community enhance existing 65 | data sets with new images! 66 | 67 | Questions/comments/discussions? 68 | =============================== 69 | Please subscribe to our mailing lists: 70 | * Open, collaborative and reproducible R&D including knowledge preservation, sharing and reuse: 71 | http://groups.google.com/group/collective-knowledge 72 | * Software and hardware multi-objective (performance/energy/accuracy/size/reliability/cost) 73 | benchmarking, autotuning, crowdtuning and run-time adaptation: http://groups.google.com/group/ctuning-discussions 74 | 75 | Publications 76 | ============ 77 | The concepts have been described in the following publications: 78 | 79 | * http://arxiv.org/abs/1506.06256 (CPC'15) 80 | * http://bit.ly/ck-date16 (DATE'16) 81 | * http://hal.inria.fr/hal-01054763 (Journal of Scientific Programming'14) 82 | * https://hal.inria.fr/inria-00436029 (GCC Summit'09) 83 | 84 | If you found this app useful for your R&D, you are welcome 85 | to reference any of the above publications in your articles 86 | and reports. You can download all above references in one 87 | BibTex file [here](https://raw.githubusercontent.com/ctuning/ck-guide-images/master/collective-knowledge-refs.bib). 88 | 89 | Testimonials and awards 90 | ======================= 91 | * 2014: HiPEAC technology transfer award: [HiPEAC TT winners](https://www.hipeac.net/research/technology-transfer-awards/2014) 92 | * 2015: ARM and the cTuning foundation use CK to accelerate computer engineering: [HiPEAC Info'45 page 17](https://www.hipeac.net/assets/public/publications/newsletter/hipeacinfo45.pdf), [ARM TechCon'16 presentation and demo](http://schedule.armtechcon.com/session/know-your-workloads-design-more-efficient-systems), [public CK repo](https://github.com/ctuning/ck-wa) 93 | 94 | Acknowledgments 95 | =============== 96 | 97 | CK development is coordinated by [dividiti](http://dividiti.com) 98 | and the [cTuning foundation](http://cTuning.org) (non-profit research organization) 99 | We are also extremely grateful to all 100 | volunteers for their valuable feedback and contributions. 101 | 102 | ![logo](https://github.com/ctuning/ck-guide-images/blob/master/logo-validated-by-the-community-simple.png) 103 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/app-release.apk -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion "27.0.3" 6 | 7 | defaultConfig { 8 | applicationId "openscience.crowdsource.video.experiments" 9 | minSdkVersion 17 10 | targetSdkVersion 26 11 | versionCode 20 12 | versionName "2.12" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:22.2.1' 26 | compile 'com.google.android.gms:play-services-appindexing:8.4.0' 27 | } 28 | -------------------------------------------------------------------------------- /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 C:\Users\fgg\AppData\Local\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/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/release/app-release.apk -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":21,"versionName":"2.13","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /app/src/androidTest/java/openscience/crowdsource/video/experiments/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | android:versionCode="2" 5 | android:versionName="1.2" > 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 34 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | 58 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/AppLogger.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import android.widget.EditText; 4 | import android.widget.TextView; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.BufferedWriter; 8 | import java.io.File; 9 | import java.io.FileReader; 10 | import java.io.FileWriter; 11 | import java.io.IOException; 12 | 13 | /** 14 | * @author Daniil Efremov 15 | */ 16 | 17 | public class AppLogger { 18 | 19 | private final static String LOG_DIR = "/sdcard/openscience/tmp/"; //todo get log dir from common config service 20 | private final static String LOG_FILE_PATH = LOG_DIR + "log.txt"; 21 | 22 | synchronized public static void cleanup() { 23 | File logFile = new File(LOG_FILE_PATH); 24 | if (logFile.exists()) { 25 | logFile.delete(); 26 | } 27 | } 28 | 29 | private static Updater updater; 30 | 31 | synchronized public static void logMessage(String message) { 32 | File logFile = new File(LOG_FILE_PATH); 33 | if (!logFile.exists()) { 34 | try { 35 | File logDir = new File(LOG_DIR); 36 | if (!logDir.exists()) { 37 | if (!logDir.mkdirs()) { 38 | // some problem with storage 39 | return; 40 | } 41 | } 42 | logFile.createNewFile(); 43 | } 44 | catch (IOException e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | try { 49 | //BufferedWriter for performance, true to set append to file flag 50 | BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true)); 51 | buf.append(message + "\n\n"); 52 | buf.newLine(); 53 | buf.close(); 54 | } 55 | catch (IOException e) { 56 | e.printStackTrace(); 57 | } 58 | 59 | if (updater != null) { 60 | updater.update(message); 61 | } 62 | 63 | } 64 | 65 | 66 | public static void registerTextView(Updater updaterNew) { 67 | updater = updaterNew; 68 | } 69 | 70 | 71 | public static void unregisterTextView() { 72 | updater = null; 73 | } 74 | 75 | 76 | synchronized public static void updateTextView(EditText editText) { 77 | File logFile = new File(LOG_FILE_PATH); 78 | if (!logFile.exists()) { 79 | return; 80 | } 81 | try { 82 | //BufferedWriter for performance, true to set append to file flag 83 | editText.setText(""); 84 | BufferedReader buf = new BufferedReader(new FileReader(logFile)); 85 | while (true) { 86 | String text = buf.readLine(); 87 | if (text == null){ 88 | break; 89 | } 90 | editText.append(text + "\n"); 91 | } 92 | buf.close(); 93 | } catch (IOException e) { 94 | // TODO Auto-generated catch block 95 | e.printStackTrace(); 96 | } 97 | editText.setSelection(editText.getText().length()); 98 | } 99 | 100 | 101 | public interface Updater { 102 | void update(String message); 103 | } 104 | 105 | public static String getAllLogs() { 106 | StringBuilder allLogs = new StringBuilder(); 107 | File logFile = new File(LOG_FILE_PATH); 108 | if (!logFile.exists()) { 109 | return ""; 110 | } 111 | try { 112 | BufferedReader buf = new BufferedReader(new FileReader(logFile)); 113 | while (true) { 114 | String text = buf.readLine(); 115 | if (text == null){ 116 | break; 117 | } 118 | allLogs.append(text).append("\n"); 119 | } 120 | buf.close(); 121 | } catch (IOException e) { 122 | // TODO Auto-generated catch block 123 | e.printStackTrace(); 124 | } 125 | return allLogs.toString(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/CaptureActivity.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import android.content.Intent; 4 | import android.content.res.Configuration; 5 | import android.database.Cursor; 6 | import android.graphics.Bitmap; 7 | import android.graphics.BitmapFactory; 8 | import android.graphics.Matrix; 9 | import android.hardware.Camera; 10 | import android.net.Uri; 11 | import android.provider.MediaStore; 12 | import android.support.v7.app.AppCompatActivity; 13 | import android.os.Bundle; 14 | import android.view.SurfaceHolder; 15 | import android.view.SurfaceView; 16 | import android.view.View; 17 | import android.widget.Button; 18 | 19 | import java.io.File; 20 | import java.io.FileNotFoundException; 21 | import java.io.FileOutputStream; 22 | import java.io.IOException; 23 | import java.util.List; 24 | 25 | import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 26 | 27 | /** 28 | * Own camera capture screen with basic features like switch camera and capture buttons 29 | * 30 | * @author Daniil Efremov 31 | */ 32 | public class CaptureActivity extends AppCompatActivity { 33 | 34 | private Camera camera; 35 | private SurfaceView surfaceView; 36 | private SurfaceHolder surfaceHolder; 37 | private int currentCameraSide = Camera.CameraInfo.CAMERA_FACING_BACK; 38 | private boolean isCameraStarted = false; 39 | 40 | private Button switchCamera; 41 | private Button btnCapture; 42 | @Override 43 | protected void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | setContentView(R.layout.activity_capture); 46 | 47 | switchCamera = (Button) findViewById(R.id.btn_rotate); 48 | switchCamera.setOnClickListener(new View.OnClickListener() { 49 | @Override 50 | public void onClick(View v) { 51 | camera.release(); 52 | stopCameraPreview(); 53 | if (currentCameraSide == Camera.CameraInfo.CAMERA_FACING_BACK) { 54 | currentCameraSide = Camera.CameraInfo.CAMERA_FACING_FRONT; 55 | } else { 56 | currentCameraSide = Camera.CameraInfo.CAMERA_FACING_BACK; 57 | } 58 | startCameraPreview(); 59 | } 60 | }); 61 | 62 | btnCapture = (Button) findViewById(R.id.btn_Capture); 63 | btnCapture.setOnClickListener(new Button.OnClickListener() { 64 | public void onClick(View v) { 65 | captureImageFromCameraPreviewAndReturn(); 66 | } 67 | }); 68 | 69 | surfaceView = (SurfaceView) findViewById(R.id.surfaceView1); 70 | surfaceHolder = surfaceView.getHolder(); 71 | surfaceHolder.addCallback(new SurfaceHolder.Callback() { 72 | @Override 73 | public void surfaceCreated(SurfaceHolder holder) { 74 | stopCameraPreview(); 75 | } 76 | 77 | @Override 78 | public void surfaceChanged(SurfaceHolder holder, int format, 79 | int width, int height) { 80 | startCameraPreview(); 81 | } 82 | 83 | @Override 84 | public void surfaceDestroyed(SurfaceHolder holder) { 85 | stopCameraPreview(); 86 | } 87 | }); 88 | } 89 | 90 | private void startCameraPreview() { 91 | if (!isCameraStarted) { 92 | try { 93 | 94 | surfaceView.setVisibility(View.VISIBLE); 95 | surfaceView.setEnabled(true); 96 | 97 | camera = Camera.open(currentCameraSide); 98 | camera.setPreviewDisplay(surfaceHolder); 99 | if (CaptureActivity.this.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) { 100 | camera.setDisplayOrientation(90); 101 | } 102 | 103 | Camera.Parameters cameraParams = camera.getParameters(); 104 | List supportedFocusModes = cameraParams.getSupportedFocusModes(); 105 | AppLogger.logMessage("SupportedFocusModes: " + supportedFocusModes.toString()); 106 | if (supportedFocusModes != null && supportedFocusModes.size() > 0 && supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { 107 | cameraParams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); 108 | } 109 | Camera.Size pictureSize = getLargesPictureSize(cameraParams); 110 | cameraParams.setPictureSize(pictureSize.width, pictureSize.height); 111 | camera.setParameters(cameraParams); 112 | camera.startPreview(); 113 | isCameraStarted = true; 114 | } catch (Exception e) { 115 | AppLogger.logMessage("Error starting camera preview " + e.getLocalizedMessage() + " \n"); 116 | e.printStackTrace(); 117 | isCameraStarted = false; 118 | return; 119 | } 120 | } 121 | switchCamera.setEnabled(true); 122 | btnCapture.setEnabled(true); 123 | } 124 | 125 | static private Camera.Size getLargesPictureSize(Camera.Parameters parameters) { 126 | Camera.Size result = null; 127 | int maxArea = 0; 128 | for (Camera.Size size : parameters.getSupportedPictureSizes()) { 129 | int area = size.width * size.height; 130 | if (area > maxArea) { 131 | result = size; 132 | maxArea = area; 133 | } 134 | } 135 | return result; 136 | } 137 | 138 | private void stopCameraPreview() { 139 | if (camera != null) { 140 | camera.release(); 141 | } 142 | camera = null; 143 | isCameraStarted = false; 144 | switchCamera.setEnabled(false); 145 | btnCapture.setEnabled(false); 146 | } 147 | 148 | @Override 149 | protected void onPause() { 150 | super.onPause(); 151 | stopCameraPreview(); 152 | } 153 | 154 | @Override 155 | protected void onResume() { 156 | super.onResume(); 157 | startCameraPreview(); 158 | } 159 | 160 | /** 161 | * @return absolute path to image 162 | */ 163 | private void captureImageFromCameraPreviewAndReturn() { 164 | synchronized (camera) { 165 | camera.takePicture(null, null, new Camera.PictureCallback() { 166 | @Override 167 | public void onPictureTaken(byte[] data, Camera camera) { 168 | try { 169 | FileOutputStream fos = new FileOutputStream(AppConfigService.getActualImagePath()); 170 | fos.write(data); 171 | fos.close(); 172 | stopCameraPreview(); 173 | 174 | Bitmap bmp = BitmapFactory.decodeFile(AppConfigService.getActualImagePath()); 175 | Matrix rotationMatrix = new Matrix(); 176 | 177 | if (CaptureActivity.this.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) { 178 | if (currentCameraSide == Camera.CameraInfo.CAMERA_FACING_FRONT) { 179 | rotationMatrix.postRotate(-90); 180 | } else { 181 | rotationMatrix.postRotate(90); 182 | } 183 | } 184 | 185 | Bitmap rbmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), rotationMatrix, true); 186 | 187 | FileOutputStream out = null; 188 | try { 189 | out = new FileOutputStream(AppConfigService.getActualImagePath()); 190 | rbmp.compress(Bitmap.CompressFormat.JPEG, 60, out); // bmp is your Bitmap instance 191 | // PNG is a lossless format, the compression factor (100) is ignored 192 | } catch (Exception e) { 193 | e.printStackTrace(); 194 | AppLogger.logMessage("Error on picture taking " + e.getLocalizedMessage()); 195 | } finally { 196 | try { 197 | if (out != null) { 198 | out.close(); 199 | } 200 | } catch (IOException e) { 201 | e.printStackTrace(); 202 | AppLogger.logMessage("Error on picture taking " + e.getLocalizedMessage()); 203 | } 204 | } 205 | 206 | 207 | Intent aboutIntent = new Intent(CaptureActivity.this, MainActivity.class); 208 | startActivity(aboutIntent); 209 | 210 | } catch (OutOfMemoryError e) { 211 | AppLogger.logMessage("Error on image capture " + e.getLocalizedMessage()); 212 | } catch (FileNotFoundException e) { 213 | AppLogger.logMessage("Error on image capture " + e.getLocalizedMessage()); 214 | } catch (IOException e) { 215 | AppLogger.logMessage("Error on image capture " + e.getLocalizedMessage()); 216 | } 217 | } 218 | }); 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/ConsoleActivity.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.app.ProgressDialog; 6 | import android.content.ClipData; 7 | import android.content.ClipboardManager; 8 | import android.content.Context; 9 | import android.content.DialogInterface; 10 | import android.content.Intent; 11 | import android.content.pm.PackageInstaller; 12 | import android.net.Uri; 13 | import android.os.AsyncTask; 14 | import android.os.Bundle; 15 | import android.os.Message; 16 | import android.support.annotation.Nullable; 17 | import android.support.v7.app.AppCompatActivity; 18 | import android.view.View; 19 | import android.widget.Button; 20 | import android.widget.EditText; 21 | import android.widget.ImageView; 22 | import android.widget.Toast; 23 | 24 | import java.io.UnsupportedEncodingException; 25 | import java.net.PasswordAuthentication; 26 | import java.util.Properties; 27 | 28 | import static android.R.attr.password; 29 | import static openscience.crowdsource.video.experiments.AppConfigService.SUPPORT_EMAIL; 30 | import static openscience.crowdsource.video.experiments.BuildConfig.APPLICATION_ID; 31 | 32 | /** 33 | * Screen with app console log displays recognition and other processes details 34 | * 35 | * @author Daniil Efremov 36 | */ 37 | public class ConsoleActivity extends AppCompatActivity { 38 | 39 | private EditText consoleEditText; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.activity_console); 45 | 46 | MainActivity.setTaskBarColored(this); 47 | 48 | addToolbarListeners(); 49 | 50 | consoleEditText = (EditText) findViewById(R.id.consoleEditText); 51 | AppLogger.updateTextView(consoleEditText); 52 | MainActivity.setTaskBarColored(this); 53 | registerLoggerViewerUpdater(); 54 | 55 | String error = getIntent().getStringExtra(ExceptionHandler.ERROR); 56 | if (error != null) { 57 | AppLogger.logMessage(error); 58 | Button homeRecognize = (Button) findViewById(R.id.btn_home_recognizeConsole); 59 | homeRecognize.setEnabled(false); 60 | homeRecognize.setVisibility(View.GONE); 61 | } 62 | 63 | View copyToBufferButton = (View) findViewById(R.id.ico_copy_to_buffer); 64 | copyToBufferButton.setOnClickListener(new View.OnClickListener() { 65 | @Override 66 | public void onClick(View v) { 67 | ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); 68 | ClipData clip = ClipData.newPlainText(APPLICATION_ID + " logs", AppLogger.getAllLogs()); 69 | clipboard.setPrimaryClip(clip); 70 | 71 | AlertDialog.Builder builder = new AlertDialog.Builder(ConsoleActivity.this); 72 | builder.setMessage("Logs successfully copied to buffer.") 73 | .setCancelable(false) 74 | .setPositiveButton("OK", new DialogInterface.OnClickListener() { 75 | public void onClick(DialogInterface dialog, int id) { 76 | //do nothing 77 | } 78 | }); 79 | AlertDialog alert = builder.create(); 80 | alert.show(); 81 | } 82 | }); 83 | 84 | View sendLogByEmailButton = (View) findViewById(R.id.ico_send_by_email); 85 | sendLogByEmailButton.setOnClickListener(new View.OnClickListener() { 86 | @Override 87 | public void onClick(View v) { 88 | Intent emailIntent = new Intent(Intent.ACTION_SENDTO); 89 | emailIntent.setData(Uri.parse("mailto:" + SUPPORT_EMAIL)); 90 | emailIntent.putExtra(Intent.EXTRA_SUBJECT, APPLICATION_ID + " logs"); 91 | emailIntent.putExtra(Intent.EXTRA_TEXT, AppLogger.getAllLogs()); 92 | 93 | try { 94 | startActivity(Intent.createChooser(emailIntent, "Send email using...")); 95 | } catch (android.content.ActivityNotFoundException ex) { 96 | Toast.makeText(ConsoleActivity.this, "No email clients installed.", Toast.LENGTH_SHORT).show(); 97 | } 98 | } 99 | }); 100 | 101 | } 102 | 103 | private void registerLoggerViewerUpdater() { 104 | AppLogger.registerTextView(new AppLogger.Updater() { 105 | @Override 106 | public void update(final String message) { 107 | runOnUiThread(new Runnable() { 108 | @Override 109 | public void run() { 110 | AppLogger.updateTextView(consoleEditText); 111 | } 112 | }); 113 | } 114 | }); 115 | } 116 | 117 | @Override 118 | protected void onResume() { 119 | super.onResume(); 120 | registerLoggerViewerUpdater(); 121 | } 122 | 123 | @Override 124 | protected void onStop() { 125 | super.onStop(); 126 | AppLogger.unregisterTextView(); 127 | } 128 | 129 | @Override 130 | protected void onPause() { 131 | super.onPause(); 132 | AppLogger.unregisterTextView(); 133 | } 134 | 135 | private void addToolbarListeners() { 136 | Button infoButton = (Button) findViewById(R.id.btn_infoConsole); 137 | infoButton.setOnClickListener(new View.OnClickListener() { 138 | @Override 139 | public void onClick(View v) { 140 | final Intent aboutIntent = new Intent(ConsoleActivity.this, InfoActivity.class); 141 | startActivity(aboutIntent); 142 | } 143 | }); 144 | 145 | Button homeRecognize = (Button) findViewById(R.id.btn_home_recognizeConsole); 146 | homeRecognize.setOnClickListener(new View.OnClickListener() { 147 | @Override 148 | public void onClick(View v) { 149 | Intent aboutIntent = new Intent(ConsoleActivity.this, MainActivity.class); 150 | startActivity(aboutIntent); 151 | } 152 | }); 153 | } 154 | 155 | @Nullable 156 | @Override 157 | public Intent getSupportParentActivityIntent() { 158 | return super.getSupportParentActivityIntent(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/CrowdSourceVideoExperimentsApplication.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import android.app.Application; 4 | 5 | import static openscience.crowdsource.video.experiments.BuildConfig.APPLICATION_ID; 6 | 7 | /** 8 | * Crowd Source Video Experiments Application class with application startup initialization 9 | * 10 | * @author Daniil Efremov 11 | */ 12 | public class CrowdSourceVideoExperimentsApplication extends Application { 13 | @Override 14 | public void onCreate() { 15 | super.onCreate(); 16 | AppLogger.cleanup(); 17 | AppConfigService.updateState(AppConfigService.AppConfig.State.READY); 18 | AppLogger.logMessage("Start application " + APPLICATION_ID + " version " + BuildConfig.VERSION_NAME); 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/DeviceInfo.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | /** 4 | * Bean with device details used for caching and pass basic device info 5 | * 6 | * @author Daniil Efremov 7 | */ 8 | class DeviceInfo { 9 | private String j_os_uid = ""; 10 | private String j_cpu_uid = ""; 11 | private String j_gpu_uid = ""; 12 | private String j_sys_uid = ""; 13 | 14 | public String getJ_os_uid() { 15 | return j_os_uid; 16 | } 17 | 18 | public void setJ_os_uid(String j_os_uid) { 19 | this.j_os_uid = j_os_uid; 20 | } 21 | 22 | public String getJ_cpu_uid() { 23 | return j_cpu_uid; 24 | } 25 | 26 | public void setJ_cpu_uid(String j_cpu_uid) { 27 | this.j_cpu_uid = j_cpu_uid; 28 | } 29 | 30 | public String getJ_gpu_uid() { 31 | return j_gpu_uid; 32 | } 33 | 34 | public void setJ_gpu_uid(String j_gpu_uid) { 35 | this.j_gpu_uid = j_gpu_uid; 36 | } 37 | 38 | public String getJ_sys_uid() { 39 | return j_sys_uid; 40 | } 41 | 42 | public void setJ_sys_uid(String j_sys_uid) { 43 | this.j_sys_uid = j_sys_uid; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | 7 | import java.io.PrintWriter; 8 | import java.io.StringWriter; 9 | 10 | /** 11 | * @author Daniil Efremov 12 | */ 13 | 14 | public class ExceptionHandler implements 15 | java.lang.Thread.UncaughtExceptionHandler { 16 | public static final String ERROR = "error"; 17 | private final Activity myContext; 18 | private final String LINE_SEPARATOR = "\n"; 19 | 20 | public ExceptionHandler(Activity context) { 21 | myContext = context; 22 | } 23 | 24 | public void uncaughtException(Thread thread, Throwable exception) { 25 | StringWriter stackTrace = new StringWriter(); 26 | exception.printStackTrace(new PrintWriter(stackTrace)); 27 | StringBuilder errorReport = new StringBuilder(); 28 | errorReport.append("************ CAUSE OF ERROR ************\n\n"); 29 | errorReport.append(stackTrace.toString()); 30 | 31 | errorReport.append("\n************ DEVICE INFORMATION ***********\n"); 32 | errorReport.append("Brand: "); 33 | errorReport.append(Build.BRAND); 34 | errorReport.append(LINE_SEPARATOR); 35 | errorReport.append("Device: "); 36 | errorReport.append(Build.DEVICE); 37 | errorReport.append(LINE_SEPARATOR); 38 | errorReport.append("Model: "); 39 | errorReport.append(Build.MODEL); 40 | errorReport.append(LINE_SEPARATOR); 41 | errorReport.append("Id: "); 42 | errorReport.append(Build.ID); 43 | errorReport.append(LINE_SEPARATOR); 44 | errorReport.append("Product: "); 45 | errorReport.append(Build.PRODUCT); 46 | errorReport.append(LINE_SEPARATOR); 47 | errorReport.append("\n************ FIRMWARE ************\n"); 48 | errorReport.append("SDK: "); 49 | errorReport.append(Build.VERSION.SDK); 50 | errorReport.append(LINE_SEPARATOR); 51 | errorReport.append("Release: "); 52 | errorReport.append(Build.VERSION.RELEASE); 53 | errorReport.append(LINE_SEPARATOR); 54 | errorReport.append("Incremental: "); 55 | errorReport.append(Build.VERSION.INCREMENTAL); 56 | errorReport.append(LINE_SEPARATOR); 57 | 58 | // AppLogger.logMessage(errorReport.toString()); 59 | Intent intent = new Intent(myContext, ConsoleActivity.class); 60 | intent.putExtra(ERROR, errorReport.toString()); 61 | myContext.startActivity(intent); 62 | 63 | android.os.Process.killProcess(android.os.Process.myPid()); 64 | System.exit(10); 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/InfoActivity.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.DialogInterface; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.text.Html; 10 | import android.text.SpannableString; 11 | import android.text.style.UnderlineSpan; 12 | import android.view.View; 13 | import android.widget.Button; 14 | import android.widget.EditText; 15 | 16 | import java.net.URI; 17 | 18 | import static openscience.crowdsource.video.experiments.AppConfigService.ACKNOWLEDGE_YOUR_CONTRIBUTIONS; 19 | import static openscience.crowdsource.video.experiments.AppConfigService.URL_ABOUT; 20 | import static openscience.crowdsource.video.experiments.AppConfigService.URL_SDK; 21 | import static openscience.crowdsource.video.experiments.AppConfigService.URL_CROWD_RESULTS; 22 | import static openscience.crowdsource.video.experiments.AppConfigService.URL_USERS; 23 | 24 | /** 25 | * Screen with additional features like 26 | * * View crowd result 27 | * * View CK project info 28 | * * Edit Email 29 | * * Clean up lod and tmp dir 30 | * * etc 31 | * 32 | * @author Daniil Efremov 33 | */ 34 | public class InfoActivity extends AppCompatActivity { 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_info); 40 | 41 | MainActivity.setTaskBarColored(this); 42 | 43 | addToolbarListeners(); 44 | addListenersOnButtons(); 45 | } 46 | 47 | private void addToolbarListeners() { 48 | Button consoleButton = (Button) findViewById(R.id.btn_consoleInfo); 49 | consoleButton.setOnClickListener(new View.OnClickListener() { 50 | @Override 51 | public void onClick(View v) { 52 | final Intent logIntent = new Intent(InfoActivity.this, ConsoleActivity.class); 53 | startActivity(logIntent); 54 | } 55 | }); 56 | 57 | Button homeRecognize = (Button) findViewById(R.id.btn_home_recognizeInfo); 58 | homeRecognize.setOnClickListener(new View.OnClickListener() { 59 | @Override 60 | public void onClick(View v) { 61 | Intent aboutIntent = new Intent(InfoActivity.this, MainActivity.class); 62 | startActivity(aboutIntent); 63 | } 64 | }); 65 | } 66 | 67 | /*************************************************************************/ 68 | private void addListenersOnButtons() { 69 | Button b_sdk = (Button) findViewById(R.id.b_sdk); 70 | Button b_about = (Button) findViewById(R.id.b_about_app); 71 | Button b_results = (Button) findViewById(R.id.b_results); 72 | Button b_users = (Button) findViewById(R.id.b_users); 73 | 74 | b_sdk.setOnClickListener(new View.OnClickListener() { 75 | @SuppressWarnings({"unused", "unchecked"}) 76 | @Override 77 | public void onClick(View arg0) { 78 | Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(URL_SDK)); 79 | startActivity(browserIntent); 80 | } 81 | }); 82 | 83 | b_about.setOnClickListener(new View.OnClickListener() { 84 | @SuppressWarnings({"unused", "unchecked"}) 85 | @Override 86 | public void onClick(View arg0) { 87 | AppLogger.logMessage("Opening " + URL_ABOUT + " ..."); 88 | 89 | Intent browserIntent = 90 | new Intent(Intent.ACTION_VIEW, Uri.parse(URL_ABOUT)); 91 | 92 | startActivity(browserIntent); 93 | } 94 | }); 95 | 96 | b_results.setOnClickListener(new View.OnClickListener() { 97 | @SuppressWarnings({"unused", "unchecked"}) 98 | @Override 99 | public void onClick(View arg0) { 100 | AppLogger.logMessage("Opening " + URL_CROWD_RESULTS + " ..."); 101 | 102 | Intent browserIntent = 103 | new Intent(Intent.ACTION_VIEW, Uri.parse(AppConfigService.getURLCrowdResults())); 104 | 105 | startActivity(browserIntent); 106 | } 107 | }); 108 | 109 | b_users.setOnClickListener(new View.OnClickListener() { 110 | @SuppressWarnings({"unused", "unchecked"}) 111 | @Override 112 | public void onClick(View arg0) { 113 | AppLogger.logMessage("Opening " + URL_USERS + " ..."); 114 | 115 | Intent browserIntent = 116 | new Intent(Intent.ACTION_VIEW, Uri.parse(URL_USERS)); 117 | 118 | startActivity(browserIntent); 119 | } 120 | }); 121 | 122 | Button t_email = (Button) findViewById(R.id.b_email); 123 | t_email.setOnClickListener(new View.OnClickListener() { 124 | @Override 125 | public void onClick(View v) { 126 | final EditText edittext = new EditText(InfoActivity.this); 127 | edittext.setHint(ACKNOWLEDGE_YOUR_CONTRIBUTIONS); 128 | String email = AppConfigService.getEmail(); 129 | edittext.setText(email); 130 | AlertDialog.Builder clarifyDialogBuilder = new AlertDialog.Builder(InfoActivity.this); 131 | clarifyDialogBuilder.setTitle("Please enter your email:") 132 | .setCancelable(false) 133 | .setPositiveButton("Update", 134 | new DialogInterface.OnClickListener() { 135 | public void onClick(DialogInterface dialog, int id) { 136 | dialog.cancel(); 137 | String newEmail = edittext.getText().toString(); 138 | AppConfigService.updateEmail(newEmail); 139 | 140 | AlertDialog.Builder builder = new AlertDialog.Builder(InfoActivity.this); 141 | builder.setMessage("Successfully updated the email.") 142 | .setCancelable(false) 143 | .setPositiveButton("OK", new DialogInterface.OnClickListener() { 144 | public void onClick(DialogInterface dialog, int id) { 145 | //do nothing 146 | } 147 | }); 148 | AlertDialog alert = builder.create(); 149 | alert.show(); 150 | } 151 | }) 152 | .setNegativeButton("Cancel", 153 | new DialogInterface.OnClickListener() { 154 | public void onClick(DialogInterface dialog, int id) { 155 | dialog.cancel(); 156 | } 157 | }); 158 | final AlertDialog clarifyDialog = clarifyDialogBuilder.create(); 159 | 160 | clarifyDialog.setTitle(""); 161 | clarifyDialog.setMessage(Html.fromHtml("To acknowledge your contributions to the public repository (optional), please enter your email:")); 162 | 163 | SpannableString spanString = new SpannableString(email.trim()); 164 | spanString.setSpan(new UnderlineSpan(), 0, spanString.length(), 0); 165 | 166 | clarifyDialog.setView(edittext); 167 | clarifyDialog.show(); 168 | } 169 | }); 170 | 171 | Button b_update = (Button) findViewById(R.id.b_update); 172 | b_update.setOnClickListener(new View.OnClickListener() { 173 | @SuppressWarnings({"unused", "unchecked"}) 174 | @Override 175 | public void onClick(View arg0) { 176 | AlertDialog.Builder clarifyDialogBuilder = new AlertDialog.Builder(InfoActivity.this); 177 | clarifyDialogBuilder.setTitle("Please confirm you wish to reload scenarios?") 178 | .setCancelable(false) 179 | .setPositiveButton("Yes", 180 | new DialogInterface.OnClickListener() { 181 | public void onClick(DialogInterface dialog, int id) { 182 | dialog.cancel(); 183 | PlatformFeaturesService.cleanuCachedPlatformFeaturesF(); 184 | RecognitionScenarioService.cleanupCachedScenarios(); 185 | Intent aboutIntent = new Intent(InfoActivity.this, MainActivity.class); 186 | startActivity(aboutIntent); 187 | } 188 | }) 189 | .setNegativeButton("Cancel", 190 | new DialogInterface.OnClickListener() { 191 | public void onClick(DialogInterface dialog, int id) { 192 | dialog.cancel(); 193 | } 194 | }); 195 | final AlertDialog clarifyDialog = clarifyDialogBuilder.create(); 196 | clarifyDialog.show(); 197 | } 198 | }); 199 | 200 | Button b_cleanup = (Button) findViewById(R.id.b_cleanup); 201 | b_cleanup.setOnClickListener(new View.OnClickListener() { 202 | @SuppressWarnings({"unused", "unchecked"}) 203 | @Override 204 | public void onClick(View arg0) { 205 | AlertDialog.Builder clarifyDialogBuilder = new AlertDialog.Builder(InfoActivity.this); 206 | clarifyDialogBuilder.setTitle("Please confirm you wish to clean up temporary image files and logs?") 207 | .setCancelable(false) 208 | .setPositiveButton("yes", 209 | new DialogInterface.OnClickListener() { 210 | public void onClick(DialogInterface dialog, int id) { 211 | dialog.cancel(); 212 | AppConfigService.deleteTMPFiles(); 213 | AppLogger.cleanup(); 214 | 215 | AlertDialog.Builder builder = new AlertDialog.Builder(InfoActivity.this); 216 | builder.setMessage("Successfully cleaned up temporary image files and logs.") 217 | .setCancelable(false) 218 | .setPositiveButton("OK", new DialogInterface.OnClickListener() { 219 | public void onClick(DialogInterface dialog, int id) { 220 | //do nothing 221 | } 222 | }); 223 | AlertDialog alert = builder.create(); 224 | alert.show(); 225 | } 226 | }) 227 | .setNegativeButton("Cancel", 228 | new DialogInterface.OnClickListener() { 229 | public void onClick(DialogInterface dialog, int id) { 230 | dialog.cancel(); 231 | } 232 | }); 233 | final AlertDialog clarifyDialog = clarifyDialogBuilder.create(); 234 | clarifyDialog.show(); 235 | } 236 | }); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/LoadScenarioFilesAsyncTask.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.os.AsyncTask; 7 | 8 | import org.ctuning.openme.openme; 9 | import org.json.JSONArray; 10 | import org.json.JSONException; 11 | import org.json.JSONObject; 12 | 13 | import java.io.BufferedInputStream; 14 | import java.io.File; 15 | import java.io.FileInputStream; 16 | import java.io.FileOutputStream; 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.OutputStream; 20 | import java.util.zip.ZipEntry; 21 | import java.util.zip.ZipInputStream; 22 | 23 | import static openscience.crowdsource.video.experiments.AppConfigService.COMMAND_CHMOD_744; 24 | import static openscience.crowdsource.video.experiments.AppConfigService.externalSDCardOpensciencePath; 25 | import static openscience.crowdsource.video.experiments.RecognitionScenarioService.loadScenariosJSONObjectFromFile; 26 | import static openscience.crowdsource.video.experiments.RecognitionScenarioService.updateProgress; 27 | import static openscience.crowdsource.video.experiments.Utils.downloadFileAndCheckMd5; 28 | 29 | /** 30 | * Async process downloads scenario files from remote server 31 | * 32 | * @author Daniil Efremov 33 | */ 34 | public class LoadScenarioFilesAsyncTask extends AsyncTask { 35 | 36 | public void copy_bin_file(String src, String dst) throws IOException { 37 | File fin = new File(src); 38 | File fout = new File(dst); 39 | 40 | InputStream in = new FileInputStream(fin); 41 | OutputStream out = new FileOutputStream(fout); 42 | 43 | // Transfer bytes from in to out 44 | int l = 0; 45 | byte[] buf = new byte[16384]; 46 | while ((l = in.read(buf)) > 0) out.write(buf, 0, l); 47 | 48 | in.close(); 49 | out.close(); 50 | } 51 | 52 | protected void onPostExecute(String x) { 53 | AppConfigService.AppConfig.State state = AppConfigService.getState(); 54 | if (state == null || state.equals(AppConfigService.AppConfig.State.IN_PROGRESS)) { 55 | AppConfigService.updateState(AppConfigService.AppConfig.State.READY); 56 | } 57 | } 58 | 59 | protected void onProgressUpdate(String... values) { 60 | if (values[0] != "") { 61 | AppLogger.logMessage(values[0]); 62 | } else if (values[1] != "") { 63 | AppLogger.logMessage("Error onProgressUpdate " + values[1]); 64 | } 65 | } 66 | 67 | @Override 68 | protected String doInBackground(RecognitionScenario... arg0) { 69 | 70 | final RecognitionScenario recognitionScenario = arg0[0]; 71 | 72 | recognitionScenario.setState(RecognitionScenario.State.DOWNLOADING_IN_PROGRESS); 73 | 74 | try { 75 | 76 | JSONObject scenariosJSON = loadScenariosJSONObjectFromFile(); 77 | if (scenariosJSON == null) { 78 | publishProgress("\nUnfortunately, no scenarios found for your device ..."); 79 | return null; 80 | } 81 | JSONArray scenarios = scenariosJSON.getJSONArray("scenarios"); 82 | if (scenarios.length() == 0) { 83 | publishProgress("\nUnfortunately, no scenarios found for your device ..."); 84 | 85 | return null; 86 | } 87 | 88 | 89 | File externalSDCardFile = new File(externalSDCardOpensciencePath); 90 | if (!externalSDCardFile.exists()) { 91 | if (!externalSDCardFile.mkdirs()) { 92 | publishProgress("\nError creating dir (" + externalSDCardOpensciencePath + ") ...\n\n"); 93 | return null; 94 | } 95 | } 96 | 97 | if (scenarios.length() == 0) { 98 | publishProgress("\nUnfortunately, no scenarios found for your device ...\n\n"); 99 | return null; 100 | } 101 | 102 | for (int i = 0; i < scenarios.length(); i++) { 103 | JSONObject scenario = scenarios.getJSONObject(i); 104 | 105 | scenario.getJSONObject("search_dict"); 106 | scenario.getString("ignore_update"); 107 | scenario.getString("search_string"); 108 | JSONObject meta = scenario.getJSONObject("meta"); 109 | String title = meta.getString("title"); 110 | 111 | if (!recognitionScenario.getTitle().equalsIgnoreCase(title)) { 112 | // skip other scenarios 113 | continue; 114 | } 115 | 116 | JSONArray files = meta.getJSONArray("files"); 117 | for (int j = 0; j < files.length(); j++) { 118 | JSONObject file = files.getJSONObject(j); 119 | String fileName = file.getString("filename"); 120 | String fileDir = externalSDCardOpensciencePath + file.getString("path"); 121 | File fp = new File(fileDir); 122 | if (!fp.exists()) { 123 | if (!fp.mkdirs()) { 124 | publishProgress("\nError creating dir (" + fileDir + ") ...\n\n"); 125 | return null; 126 | } 127 | } 128 | 129 | final String targetFilePath = fileDir + File.separator + fileName; 130 | String finalTargetFilePath = targetFilePath; 131 | String finalTargetFileDir = fileDir; 132 | String url = file.getString("url"); 133 | String md5 = file.getString("md5"); 134 | 135 | if (downloadFileAndCheckMd5( 136 | url, 137 | targetFilePath, 138 | md5, 139 | new MainActivity.ProgressPublisher() { 140 | @Override 141 | public void setPercent(int percent) { 142 | String str=""; 143 | 144 | if (percent<0) { 145 | str+="\n * Downloading file " + targetFilePath + " ..."; 146 | } else { 147 | str += " * " + percent + "%"; 148 | } 149 | updateProgress(recognitionScenario); 150 | publishProgress(str); 151 | } 152 | 153 | @Override 154 | public void addBytes(long bytes) { 155 | recognitionScenario.setDownloadedTotalFileSizeBytes(recognitionScenario.getDownloadedTotalFileSizeBytes() + bytes); 156 | } 157 | 158 | @Override 159 | public void subBytes(long bytes) { 160 | recognitionScenario.setDownloadedTotalFileSizeBytes(recognitionScenario.getDownloadedTotalFileSizeBytes() - bytes); 161 | } 162 | 163 | @Override 164 | public void println(String text) { 165 | publishProgress("\n" + text + "\n"); 166 | } 167 | })) { 168 | 169 | String copyToAppSpace = null; 170 | try { 171 | copyToAppSpace = file.getString("copy_to_app_space"); 172 | } catch (JSONException e) { 173 | // copyToAppSpace is not mandatory 174 | } 175 | if (copyToAppSpace != null && copyToAppSpace.equalsIgnoreCase("yes")) { 176 | String fileAppDir = AppConfigService.getLocalAppPath() + file.getString("path"); 177 | File appfp = new File(fileAppDir); 178 | if (!appfp.exists()) { 179 | if (!appfp.mkdirs()) { 180 | publishProgress("\nError creating dir (" + fileAppDir + ") ...\n\n"); 181 | return null; 182 | } 183 | } 184 | 185 | final String targetAppFilePath = fileAppDir + File.separator + fileName; 186 | try { 187 | copy_bin_file(targetFilePath, targetAppFilePath); 188 | finalTargetFileDir = fileAppDir; 189 | finalTargetFilePath = targetAppFilePath; 190 | publishProgress("\n * File " + targetFilePath + " successfully copied to " + targetAppFilePath + "\n\n"); 191 | } catch (IOException e) { 192 | e.printStackTrace(); 193 | publishProgress("\nError copying file " + targetFilePath + " to " + targetAppFilePath + " ...\n\n"); 194 | return null; 195 | } 196 | } 197 | 198 | String unzip = null; 199 | try { 200 | unzip = file.getString("unzip"); 201 | } catch (JSONException e) { 202 | // unzip is not mandatory 203 | } 204 | if (unzip != null && unzip.equalsIgnoreCase("yes")) { 205 | String fileAppDir = AppConfigService.getLocalAppPath() + file.getString("path"); 206 | 207 | File appfp = new File(fileAppDir); 208 | 209 | if (!appfp.exists()) { 210 | if (!appfp.mkdirs()) { 211 | publishProgress("\nError creating dir (" + fileAppDir + ") ...\n\n"); 212 | return null; 213 | } 214 | } 215 | 216 | final String targetAppFilePath = fileAppDir + File.separator + fileName; 217 | 218 | InputStream is=null; 219 | BufferedInputStream bis=null; 220 | ZipInputStream zis=null; 221 | FileOutputStream fos=null; 222 | int cc=0; 223 | byte[] buf = new byte[2048]; 224 | 225 | try { 226 | is=new FileInputStream(targetFilePath); 227 | bis=new BufferedInputStream(is); 228 | zis=new ZipInputStream(bis); 229 | 230 | ZipEntry zz; 231 | 232 | while ((zz = zis.getNextEntry()) != null) 233 | { 234 | String new_file=zz.getName(); 235 | 236 | publishProgress("\n * Unzipping " + new_file); 237 | 238 | fos=new FileOutputStream(fileDir + File.separator + new_file); 239 | 240 | while ((cc=zis.read(buf))!=-1) 241 | fos.write(buf, 0, cc); 242 | 243 | fos.close(); 244 | zis.closeEntry(); 245 | 246 | } 247 | 248 | publishProgress("\n * Deleting " + targetAppFilePath + "\n\n"); 249 | 250 | File fd=new File(targetAppFilePath); 251 | fd.delete(); 252 | 253 | } catch (IOException e) { 254 | e.printStackTrace(); 255 | publishProgress("\nError unzipping file " + targetFilePath + " to " + targetAppFilePath + " ...\n\n"); 256 | return null; 257 | } 258 | } 259 | 260 | String executable = null; 261 | try { 262 | executable = file.getString("executable"); 263 | } catch (JSONException e) { 264 | // executable is not mandatory 265 | } 266 | if (executable != null && executable.equalsIgnoreCase("yes")) { 267 | String[] chmodResult = openme.openme_run_program(COMMAND_CHMOD_744 + " " + finalTargetFilePath, null, finalTargetFileDir); 268 | if (chmodResult[0].isEmpty() && chmodResult[1].isEmpty() && chmodResult[2].isEmpty()) { 269 | publishProgress(" * File " + finalTargetFilePath + " successfully set as executable ...\n"); 270 | } else { 271 | publishProgress("\nError setting file " + targetFilePath + " as executable ...\n\n"); 272 | return null; 273 | } 274 | } 275 | } else { 276 | publishProgress("\nError downloading file " + targetFilePath + " from URL " + url + ". Please try again later."); 277 | recognitionScenario.setState(RecognitionScenario.State.NEW); 278 | recognitionScenario.setDownloadedTotalFileSizeBytes(new Long(0)); 279 | AppConfigService.updateState(AppConfigService.AppConfig.State.READY); 280 | recognitionScenario.setLoadScenarioFilesAsyncTask(null); 281 | updateProgress(recognitionScenario); 282 | return null; 283 | } 284 | } 285 | 286 | publishProgress("\nPreloaded scenario info: " + recognitionScenario.toString() + "\n\n"); 287 | } 288 | 289 | } catch (JSONException e) { 290 | publishProgress("\nError obtaining key 'error' from OpenME output (" + e.getMessage() + ") ...\n\n"); 291 | return null; 292 | } 293 | 294 | recognitionScenario.setState(RecognitionScenario.State.DOWNLOADED); 295 | recognitionScenario.setDownloadedTotalFileSizeBytes(new Long(0)); 296 | AppConfigService.updateState(AppConfigService.AppConfig.State.READY); 297 | recognitionScenario.setLoadScenarioFilesAsyncTask(null); 298 | updateProgress(recognitionScenario); 299 | 300 | publishProgress("Finished downloading files for scenario " + recognitionScenario.getTitle() + " \n\n"); 301 | return null; 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/PFInfo.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | /** 4 | * Bean with platform feature details used for caching and pass info 5 | * 6 | * @author Daniil Efremov 7 | */ 8 | public class PFInfo { 9 | private String pf_system = ""; 10 | private String pf_system_vendor = ""; 11 | private String pf_system_model = ""; 12 | private String pf_cpu = ""; 13 | private String pf_cpu_subname = ""; 14 | private String pf_cpu_features = ""; 15 | private String pf_cpu_abi = ""; 16 | private String pf_cpu_num = ""; 17 | private String pf_gpu_opencl = ""; 18 | private String pf_gpu_openclx = ""; 19 | private String pf_memory = ""; 20 | private String pf_os = ""; 21 | private String pf_os_short = ""; 22 | private String pf_os_long = ""; 23 | private String pf_os_bits = "32"; 24 | 25 | public String getPf_system() { 26 | return pf_system; 27 | } 28 | 29 | public void setPf_system(String pf_system) { 30 | this.pf_system = pf_system; 31 | } 32 | 33 | public String getPf_system_vendor() { 34 | return pf_system_vendor; 35 | } 36 | 37 | public void setPf_system_vendor(String pf_system_vendor) { 38 | this.pf_system_vendor = pf_system_vendor; 39 | } 40 | 41 | public String getPf_system_model() { 42 | return pf_system_model; 43 | } 44 | 45 | public void setPf_system_model(String pf_system_model) { 46 | this.pf_system_model = pf_system_model; 47 | } 48 | 49 | public String getPf_cpu() { 50 | return pf_cpu; 51 | } 52 | 53 | public void setPf_cpu(String pf_cpu) { 54 | this.pf_cpu = pf_cpu; 55 | } 56 | 57 | public String getPf_cpu_subname() { 58 | return pf_cpu_subname; 59 | } 60 | 61 | public void setPf_cpu_subname(String pf_cpu_subname) { 62 | this.pf_cpu_subname = pf_cpu_subname; 63 | } 64 | 65 | public String getPf_cpu_features() { 66 | return pf_cpu_features; 67 | } 68 | 69 | public void setPf_cpu_features(String pf_cpu_features) { 70 | this.pf_cpu_features = pf_cpu_features; 71 | } 72 | 73 | public String getPf_cpu_abi() { 74 | return pf_cpu_abi; 75 | } 76 | 77 | public void setPf_cpu_abi(String pf_cpu_abi) { 78 | this.pf_cpu_abi = pf_cpu_abi; 79 | } 80 | 81 | public String getPf_cpu_num() { 82 | return pf_cpu_num; 83 | } 84 | 85 | public void setPf_cpu_num(String pf_cpu_num) { 86 | this.pf_cpu_num = pf_cpu_num; 87 | } 88 | 89 | public String getPf_gpu_opencl() { 90 | return pf_gpu_opencl; 91 | } 92 | 93 | public void setPf_gpu_opencl(String pf_gpu_opencl) { 94 | this.pf_gpu_opencl = pf_gpu_opencl; 95 | } 96 | 97 | public String getPf_gpu_openclx() { 98 | return pf_gpu_openclx; 99 | } 100 | 101 | public void setPf_gpu_openclx(String pf_gpu_openclx) { 102 | this.pf_gpu_openclx = pf_gpu_openclx; 103 | } 104 | 105 | public String getPf_memory() { 106 | return pf_memory; 107 | } 108 | 109 | public void setPf_memory(String pf_memory) { 110 | this.pf_memory = pf_memory; 111 | } 112 | 113 | public String getPf_os() { 114 | return pf_os; 115 | } 116 | 117 | public void setPf_os(String pf_os) { 118 | this.pf_os = pf_os; 119 | } 120 | 121 | public String getPf_os_short() { 122 | return pf_os_short; 123 | } 124 | 125 | public void setPf_os_short(String pf_os_short) { 126 | this.pf_os_short = pf_os_short; 127 | } 128 | 129 | public String getPf_os_long() { 130 | return pf_os_long; 131 | } 132 | 133 | public void setPf_os_long(String pf_os_long) { 134 | this.pf_os_long = pf_os_long; 135 | } 136 | 137 | public String getPf_os_bits() { 138 | return pf_os_bits; 139 | } 140 | 141 | public void setPf_os_bits(String pf_os_bits) { 142 | this.pf_os_bits = pf_os_bits; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/RecognitionScenario.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import org.json.JSONObject; 4 | 5 | /** 6 | * Bean used for caching and pass basic scenario info across the activities and async processes 7 | * 8 | * @author Daniil Efremov 9 | */ 10 | public class RecognitionScenario implements Comparable { 11 | 12 | public enum State { 13 | NEW, 14 | DOWNLOADING_IN_PROGRESS, 15 | DOWNLOADED 16 | } 17 | 18 | private String defaultImagePath; 19 | private String dataUOA; 20 | private String moduleUOA; 21 | private String title; 22 | private String totalFileSize; 23 | private Long totalFileSizeBytes; 24 | private LoadScenarioFilesAsyncTask loadScenarioFilesAsyncTask; 25 | 26 | private Long downloadedTotalFileSizeBytes = new Long(0); 27 | 28 | private State state = State.NEW; 29 | 30 | private JSONObject rawJSON; //todo move out to file 31 | 32 | private RecognitionScenarioService.Updater buttonUpdater; 33 | 34 | private RecognitionScenarioService.Updater progressUpdater; 35 | 36 | 37 | public String getTitle() { 38 | return title; 39 | } 40 | 41 | public void setTitle(String title) { 42 | this.title = title; 43 | } 44 | 45 | public String getDefaultImagePath() { 46 | return defaultImagePath; 47 | } 48 | 49 | public void setDefaultImagePath(String defaultImagePath) { 50 | this.defaultImagePath = defaultImagePath; 51 | } 52 | 53 | public String getDataUOA() { 54 | return dataUOA; 55 | } 56 | 57 | public void setDataUOA(String dataUOA) { 58 | this.dataUOA = dataUOA; 59 | } 60 | 61 | public String getModuleUOA() { 62 | return moduleUOA; 63 | } 64 | 65 | public void setModuleUOA(String moduleUOA) { 66 | this.moduleUOA = moduleUOA; 67 | } 68 | 69 | public JSONObject getRawJSON() { 70 | return rawJSON; 71 | } 72 | 73 | public void setRawJSON(JSONObject rawJSON) { 74 | this.rawJSON = rawJSON; 75 | } 76 | 77 | public String getTotalFileSize() { 78 | return totalFileSize; 79 | } 80 | 81 | public void setTotalFileSize(String totalFileSize) { 82 | this.totalFileSize = totalFileSize; 83 | } 84 | 85 | public Long getTotalFileSizeBytes() { 86 | return totalFileSizeBytes; 87 | } 88 | 89 | public void setTotalFileSizeBytes(Long totalFileSizeBytes) { 90 | this.totalFileSizeBytes = totalFileSizeBytes; 91 | } 92 | 93 | public State getState() { 94 | return state; 95 | } 96 | 97 | public void setState(State state) { 98 | this.state = state; 99 | } 100 | 101 | public Long getDownloadedTotalFileSizeBytes() { 102 | return downloadedTotalFileSizeBytes; 103 | } 104 | 105 | public void setDownloadedTotalFileSizeBytes(Long downloadedTotalFileSizeBytes) { 106 | this.downloadedTotalFileSizeBytes = downloadedTotalFileSizeBytes; 107 | } 108 | 109 | public RecognitionScenarioService.Updater getButtonUpdater() { 110 | return buttonUpdater; 111 | } 112 | 113 | public void setButtonUpdater(RecognitionScenarioService.Updater buttonUpdater) { 114 | this.buttonUpdater = buttonUpdater; 115 | } 116 | 117 | public RecognitionScenarioService.Updater getProgressUpdater() { 118 | return progressUpdater; 119 | } 120 | 121 | public void setProgressUpdater(RecognitionScenarioService.Updater progressUpdater) { 122 | this.progressUpdater = progressUpdater; 123 | } 124 | 125 | public LoadScenarioFilesAsyncTask getLoadScenarioFilesAsyncTask() { 126 | return loadScenarioFilesAsyncTask; 127 | } 128 | 129 | public void setLoadScenarioFilesAsyncTask(LoadScenarioFilesAsyncTask loadScenarioFilesAsyncTask) { 130 | this.loadScenarioFilesAsyncTask = loadScenarioFilesAsyncTask; 131 | } 132 | 133 | @Override 134 | public String toString() { 135 | return title; 136 | } 137 | 138 | @Override 139 | public boolean equals(Object o) { 140 | if (this == o) return true; 141 | if (o == null || getClass() != o.getClass()) return false; 142 | 143 | RecognitionScenario that = (RecognitionScenario) o; 144 | 145 | if (defaultImagePath != null ? !defaultImagePath.equals(that.defaultImagePath) : that.defaultImagePath != null) 146 | return false; 147 | if (dataUOA != null ? !dataUOA.equals(that.dataUOA) : that.dataUOA != null) 148 | return false; 149 | if (moduleUOA != null ? !moduleUOA.equals(that.moduleUOA) : that.moduleUOA != null) 150 | return false; 151 | if (title != null ? !title.equals(that.title) : that.title != null) return false; 152 | if (totalFileSize != null ? !totalFileSize.equals(that.totalFileSize) : that.totalFileSize != null) 153 | return false; 154 | return rawJSON != null ? rawJSON.equals(that.rawJSON) : that.rawJSON == null; 155 | 156 | } 157 | 158 | @Override 159 | public int hashCode() { 160 | int result = defaultImagePath != null ? defaultImagePath.hashCode() : 0; 161 | result = 31 * result + (dataUOA != null ? dataUOA.hashCode() : 0); 162 | result = 31 * result + (moduleUOA != null ? moduleUOA.hashCode() : 0); 163 | result = 31 * result + (title != null ? title.hashCode() : 0); 164 | result = 31 * result + (totalFileSize != null ? totalFileSize.hashCode() : 0); 165 | result = 31 * result + (rawJSON != null ? rawJSON.hashCode() : 0); 166 | return result; 167 | } 168 | 169 | @Override 170 | public int compareTo(Object another) { 171 | if (!(another instanceof RecognitionScenario)) { 172 | return -1; 173 | } 174 | RecognitionScenario enoRecognitionScenario = (RecognitionScenario) another; 175 | if ((this.getTotalFileSizeBytes() - enoRecognitionScenario.getTotalFileSizeBytes()) < 0) { 176 | return 1; 177 | } 178 | if ((this.getTotalFileSizeBytes() - enoRecognitionScenario.getTotalFileSizeBytes()) > 0) { 179 | return -1; 180 | } 181 | return 0; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/ReloadScenariosAsyncTask.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import android.os.AsyncTask; 4 | 5 | import org.ctuning.openme.openme; 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | import static openscience.crowdsource.video.experiments.Utils.validateReturnCode; 10 | 11 | /** 12 | * Async process reloads scenarios from remote server 13 | * Downloaded files is not removed for existed scenarios 14 | * 15 | * @author Daniil Efremov 16 | */ 17 | public class ReloadScenariosAsyncTask extends AsyncTask { 18 | 19 | protected void onPostExecute(String x) { 20 | AppConfigService.AppConfig.State state = AppConfigService.getState(); 21 | if (state == null || state.equals(AppConfigService.AppConfig.State.IN_PROGRESS)) { 22 | AppConfigService.updateState(AppConfigService.AppConfig.State.READY); 23 | } 24 | } 25 | 26 | protected void onProgressUpdate(String... values) { 27 | if (values[0] != "") { 28 | AppLogger.logMessage(values[0]); 29 | } else if (values[1] != "") { 30 | AppLogger.logMessage("Error onProgressUpdate " + values[1]); 31 | } 32 | } 33 | 34 | @Override 35 | protected String doInBackground(RecognitionScenarioService.ScenariosUpdater... arg0) { 36 | RecognitionScenarioService.loadRecognitionScenariosFromServer(); 37 | AppConfigService.updateState(AppConfigService.AppConfig.State.READY); 38 | if (arg0[0] != null) { 39 | arg0[0].update(); 40 | } 41 | if (RecognitionScenarioService.isRecognitionScenariosLoaded()) { 42 | publishProgress("Crowd engine is READY!\n"); 43 | } 44 | return null; 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/RemoteCallTask.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import android.os.AsyncTask; 4 | 5 | import org.ctuning.openme.openme; 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | /** 10 | * Created by daniil on 11/16/16. 11 | */ 12 | class RemoteCallTask extends AsyncTask { 13 | 14 | private Exception exception; 15 | 16 | 17 | protected JSONObject doInBackground(JSONObject... requests) { 18 | try { 19 | JSONObject response = openme.remote_access(requests[0]); 20 | if (validateReturnCode(response)) { 21 | publishProgress("\nError sending correct answer to server ...\n\n"); 22 | } 23 | 24 | int responseCode = 0; 25 | if (!response.has("return")) { 26 | publishProgress("\nError obtaining key 'return' from OpenME output ...\n\n"); 27 | return response; 28 | } 29 | 30 | try { 31 | Object rx = response.get("return"); 32 | if (rx instanceof String) responseCode = Integer.parseInt((String) rx); 33 | else responseCode = (Integer) rx; 34 | } catch (JSONException e) { 35 | publishProgress("\nError obtaining key 'return' from OpenME output (" + e.getMessage() + ") ...\n\n"); 36 | return response; 37 | } 38 | 39 | if (responseCode > 0) { 40 | String err = ""; 41 | try { 42 | err = (String) response.get("error"); 43 | } catch (JSONException e) { 44 | publishProgress("\nError obtaining key 'error' from OpenME output (" + e.getMessage() + ") ...\n\n"); 45 | return response; 46 | } 47 | 48 | publishProgress("\nProblem accessing CK server: " + err + "\n"); 49 | return response; 50 | } 51 | 52 | publishProgress("\nSuccessfully added correct answer to Collective Knowledge!\n\n"); 53 | 54 | return response; 55 | } catch (JSONException e) { 56 | publishProgress("\nError calling OpenME interface (" + e.getMessage() + ") ...\n\n"); 57 | return null; 58 | } catch (Exception e) { 59 | this.exception = e; 60 | 61 | return null; 62 | } 63 | } 64 | 65 | protected void onProgressUpdate(String... values) { 66 | if (values[0] != "") { 67 | AppLogger.logMessage(values[0]); 68 | } else if (values[1] != "") { 69 | AppLogger.logMessage("Error"); 70 | } 71 | } 72 | 73 | protected void onPostExecute(JSONObject response) { 74 | // TODO: check this.exception 75 | // TODO: do something with the response 76 | } 77 | 78 | private boolean validateReturnCode(JSONObject r) { 79 | int rr = 0; 80 | if (!r.has("return")) { 81 | publishProgress("\nError obtaining key 'return' from OpenME output ...\n\n"); 82 | return true; 83 | } 84 | 85 | try { 86 | Object rx = r.get("return"); 87 | if (rx instanceof String) rr = Integer.parseInt((String) rx); 88 | else rr = (Integer) rx; 89 | } catch (JSONException e) { 90 | publishProgress("\nError obtaining key 'return' from OpenME output (" + e.getMessage() + ") ...\n\n"); 91 | return true; 92 | } 93 | 94 | if (rr > 0) { 95 | String err = ""; 96 | try { 97 | err = (String) r.get("error"); 98 | } catch (JSONException e) { 99 | publishProgress("\nError obtaining key 'error' from OpenME output (" + e.getMessage() + ") ...\n\n"); 100 | return true; 101 | } 102 | 103 | publishProgress("\nProblem accessing CK server: " + err + "\n"); 104 | return true; 105 | } 106 | return false; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/ScenarioInfoActivity.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import android.content.Intent; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.os.Bundle; 6 | import android.text.Html; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.TextView; 10 | 11 | import org.json.JSONException; 12 | 13 | import java.util.ArrayList; 14 | 15 | import static openscience.crowdsource.video.experiments.MainActivity.setTaskBarColored; 16 | import static openscience.crowdsource.video.experiments.RecognitionScenarioService.getScenarioDescriptionHTML; 17 | 18 | /** 19 | * Screen displays selected scenario information 20 | * 21 | * @author Daniil Efremov 22 | */ 23 | public class ScenarioInfoActivity extends AppCompatActivity { 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_scenario_info); 29 | 30 | setTaskBarColored(this); 31 | 32 | View selectScenario = (View)findViewById(R.id.topSelectedScenario); 33 | selectScenario.setOnClickListener(new View.OnClickListener() { 34 | @Override 35 | public void onClick(View v) { 36 | Intent homeIntent = new Intent(ScenarioInfoActivity.this, ScenariosActivity.class); 37 | startActivity(homeIntent); 38 | } 39 | }); 40 | TextView selectScenarioText = (TextView)findViewById(R.id.topSelectedScenarioText); 41 | String selectedScenarioTitle = getIntent().getStringExtra(ScenariosActivity.SELECTED_SCENARIO_TITLE); 42 | selectScenarioText.setText(selectedScenarioTitle); 43 | 44 | RecognitionScenario selectedScenario = null; 45 | ArrayList sortedRecognitionScenarios = RecognitionScenarioService.getSortedRecognitionScenarios(); 46 | for (RecognitionScenario sortedRecognitionScenario : sortedRecognitionScenarios) { 47 | if (sortedRecognitionScenario.getTitle().equalsIgnoreCase(selectedScenarioTitle)) { 48 | selectedScenario = sortedRecognitionScenario; 49 | } 50 | } 51 | 52 | TextView scenarioInfoText = (TextView)findViewById(R.id.scenarioInfoText); 53 | try { 54 | scenarioInfoText.setText(Html.fromHtml(getScenarioDescriptionHTML(selectedScenario))); 55 | } catch (JSONException e) { 56 | AppLogger.logMessage("Error " + e.getLocalizedMessage()); 57 | } 58 | 59 | Button homeButton = (Button) findViewById(R.id.btn_home_recognizeMain); 60 | homeButton.setOnClickListener(new View.OnClickListener() { 61 | @Override 62 | public void onClick(View v) { 63 | Intent homeIntent = new Intent(ScenarioInfoActivity.this, MainActivity.class); 64 | startActivity(homeIntent); 65 | } 66 | }); 67 | 68 | 69 | Button consoleButton = (Button) findViewById(R.id.btn_consoleMain); 70 | consoleButton.setOnClickListener(new View.OnClickListener() { 71 | @Override 72 | public void onClick(View v) { 73 | Intent logIntent = new Intent(ScenarioInfoActivity.this, ConsoleActivity.class); 74 | startActivity(logIntent); 75 | } 76 | }); 77 | 78 | 79 | Button infoButton = (Button) findViewById(R.id.btn_infoMain); 80 | infoButton.setOnClickListener(new View.OnClickListener() { 81 | @Override 82 | public void onClick(View v) { 83 | Intent infoIntent = new Intent(ScenarioInfoActivity.this, InfoActivity.class); 84 | startActivity(infoIntent); 85 | } 86 | }); 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/openscience/crowdsource/video/experiments/UpdatePlatformFeaturesAsyncTask.java: -------------------------------------------------------------------------------- 1 | package openscience.crowdsource.video.experiments; 2 | 3 | import android.os.AsyncTask; 4 | 5 | /** 6 | * Async process reload platform features and scenario list like at first app start 7 | * to refresh related information 8 | * 9 | * @author Daniil Efremov 10 | */ 11 | public class UpdatePlatformFeaturesAsyncTask extends AsyncTask { 12 | 13 | protected void onPostExecute(String x) { 14 | AppConfigService.AppConfig.State state = AppConfigService.getState(); 15 | if (state == null || state.equals(AppConfigService.AppConfig.State.IN_PROGRESS)) { 16 | AppConfigService.updateState(AppConfigService.AppConfig.State.READY); 17 | } 18 | } 19 | 20 | /*************************************************************************/ 21 | protected void onProgressUpdate(String... values) { 22 | if (values[0] != "") { 23 | AppLogger.logMessage(values[0]); 24 | } else if (values[1] != "") { 25 | AppLogger.logMessage("Error onProgressUpdate " + values[1]); 26 | } 27 | } 28 | 29 | /*************************************************************************/ 30 | @Override 31 | protected String doInBackground(String... arg0) { 32 | PlatformFeaturesService.loadPlatformFeaturesFromServer(); 33 | return null; 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/org/ctuning/shared-computing-resources-json/README.txt: -------------------------------------------------------------------------------- 1 | Shared computing resources based on CK and cM frameworks: 2 | 3 | * http://github.com/ctuning/ck (cTuning4 plaform - in development) 4 | * http://sourceforge.net/projects/c-mind (cTuning3 platform - stable) 5 | 6 | Check out following resources for more details: 7 | 8 | * http://cTuning.org 9 | * https://hal.inria.fr/hal-01054763/document 10 | -------------------------------------------------------------------------------- /app/src/main/java/org/ctuning/shared-computing-resources-json/ck.json: -------------------------------------------------------------------------------- 1 | { 2 | "default":{"url":"http://cKnowledge.org/repo/json.php?", 3 | "note":"Grigori Fursin's personal server with newer Collective Knowledge framework to a) crowdsource program/compiler autotuning and performance modeling b) reproduce experiments during Artifact Evaluation", 4 | "weight":100} 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/org/ctuning/shared-computing-resources-json/cm.json: -------------------------------------------------------------------------------- 1 | { 2 | "default":{"url":"http://cknowledge.ddns.net/cm/repo/json.php?", 3 | "note":"Grigori Fursin's personal server with older Collective Mind framework - shared to crowdsource program/compiler autotuning and performance modeling", 4 | "weight":100} 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_console_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/b_console_active.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_console_inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/b_console_inactive.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_flip_cam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/b_flip_cam.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_home_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/b_home_active.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_home_inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/b_home_inactive.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_info_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/b_info_active.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_info_inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/b_info_inactive.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/b_log.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_open_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/b_open_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_recognize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/b_recognize.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_recognize_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_recognize_enabled.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_start_cam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/b_start_cam.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/b_stop_cam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/b_stop_cam.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/capture_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/capture_window.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ico_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/ico_arrow_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ico_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/ico_arrow_up.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ico_camera_rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/ico_camera_rotate.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ico_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/ico_delete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ico_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/ico_download.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ico_outer_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/ico_outer_link.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ico_preloading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dividiti/crowdsource-video-experiments-on-android/e60888bbe7b72d9d3cb02a003be8a62bfdae534f/app/src/main/res/drawable/ico_preloading.png -------------------------------------------------------------------------------- /app/src/main/res/layout-land/activity_main.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | 37 | 38 | 51 | 52 | 57 | 58 |