├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── demoapplication │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── classes.txt │ │ ├── demoApplication.gif │ │ ├── droneControl_initial.png │ │ └── yolov5s.torchscript.ptl │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── demoapplication │ │ │ ├── DetectionResult.java │ │ │ ├── MainActivity.java │ │ │ └── droneController.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── outline_camera_24.xml │ │ ├── outline_flight_land_24.xml │ │ ├── outline_flight_takeoff_24.xml │ │ ├── outline_play_circle_20.xml │ │ ├── outline_play_circle_24.xml │ │ ├── outline_rocket_20.xml │ │ ├── outline_rocket_24.xml │ │ ├── outline_videocam_24.xml │ │ ├── round_back.xml │ │ ├── rounded_corner.xml │ │ ├── rounded_corner_green.xml │ │ └── rounded_corner_red.xml │ │ ├── layout │ │ ├── activity_drone_controller.xml │ │ ├── activity_main.xml │ │ └── texture_view.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── example │ └── demoapplication │ └── 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/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tello drone-based object detection app for Android 2 | 3 | ### This repository will walk you through android app development process for object detection using Tello Drone. 4 | 5 |

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 | 16 |

17 | 18 | 21 | 22 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 |

40 | 41 | [![App banner](./app/src/main/assets/droneControl_initial.png)](https://github.com/jithin8mathew) 42 | 43 | ### This project will demonstrate how to: 44 | - Create a UI for controlling Tello Drone 45 | - Handle drone using virtual controls 46 | - Process H.264 (AVC) encoded video from Tello and display it on app 47 | - Perform near real-time object detection from Tello video frame 48 | 49 | ### Things to fix: 50 | - 0. Video feed keeps getting stuck during flight (Temporary fix: turn the video switch at the top left of the screen on and off again). A permanent fix is needed. 51 | - 1. I could not figure out a way to toggle between and SurfaceView and BitMap displaying for simply viewing video frames vs performing object detection 52 | - 2. Some of the calculations like drone acceleration etc. needs more attention and correction. 53 | - 3. Improve the efficiency of the code and simplify it. 54 | - 4. Closing datagram sockets while not in use or going back to the main page is not working. 55 | - 5. Capture image and record video buttons does not work at this point, but I will be adding these features soon. 56 | 57 | [![App demo](./app/src/main/assets/demoApplication.gif)](https://github.com/jithin8mathew) 58 | 59 | ### How to install this application? 60 | 61 | Open android studio> open demoApplication project > Build > run app (preferably connecting your phone) 62 | 63 | ### Things to keep in mind: 64 | 65 | - While testing on Pixel 4 (Android 12) I faced issues associated with UDP connection. After connecting to Tello's wifi, under internet> wifi> TelloNetworkName> Privacy, there are two options. 1) Use randomized MAC (default) 2) Use device MAC. After connecting to Tello netwok, swith between these options before using the app. 66 | - The above step will disconnect from the wifi and reconnect again, only then will this app work on Pixel 4 (Android 12), this could be only a problem on my device Pixel 4 in general (I don't know it yet) 67 | - There could be several bugs, things could be done in an easier and efficient way, possibly. Please report bugs and issues. 68 | - If you face any issues during development or installation, please raise an issue in my GitHub page for this project 69 | 70 | More importantly, I am excited to see cool application of this project. And finally, if you liked this project and find it interesting, please :star: the project so that it can reach more people. Thank you and hope you have some fun with this!. 71 | 72 | This project is explained in detail at https://medium.com/@jithin8mathew/building-an-android-application-to-control-tello-drone-flight-and-perform-real-time-object-ab953f6c5f5b 73 | 74 | # LICENSE 75 | 76 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 77 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdk 31 7 | 8 | defaultConfig { 9 | applicationId "com.example.demoapplication" 10 | minSdk 27 11 | targetSdk 31 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | } 29 | 30 | dependencies { 31 | 32 | implementation 'androidx.appcompat:appcompat:1.4.1' 33 | implementation 'com.google.android.material:material:1.5.0' 34 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3' 35 | testImplementation 'junit:junit:4.13.2' 36 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 37 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 38 | 39 | implementation 'org.pytorch:pytorch_android_lite:1.10.0' 40 | implementation 'org.pytorch:pytorch_android_torchvision_lite:1.10.0' 41 | 42 | implementation 'io.github.controlwear:virtualjoystick:1.10.1' 43 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/demoapplication/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.demoapplication; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.example.demoapplication", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/assets/classes.txt: -------------------------------------------------------------------------------- 1 | person 2 | bicycle 3 | car 4 | motorcycle 5 | airplane 6 | bus 7 | train 8 | truck 9 | boat 10 | traffic light 11 | fire hydrant 12 | stop sign 13 | parking meter 14 | bench 15 | bird 16 | cat 17 | dog 18 | horse 19 | sheep 20 | cow 21 | elephant 22 | bear 23 | zebra 24 | giraffe 25 | backpack 26 | umbrella 27 | handbag 28 | tie 29 | suitcase 30 | frisbee 31 | skis 32 | snowboard 33 | sports ball 34 | kite 35 | baseball bat 36 | baseball glove 37 | skateboard 38 | surfboard 39 | tennis racket 40 | bottle 41 | wine glass 42 | cup 43 | fork 44 | knife 45 | spoon 46 | bowl 47 | banana 48 | apple 49 | sandwich 50 | orange 51 | broccoli 52 | carrot 53 | hot dog 54 | pizza 55 | donut 56 | cake 57 | chair 58 | couch 59 | potted plant 60 | bed 61 | dining table 62 | toilet 63 | tv 64 | laptop 65 | mouse 66 | remote 67 | keyboard 68 | cell phone 69 | microwave 70 | oven 71 | toaster 72 | sink 73 | refrigerator 74 | book 75 | clock 76 | vase 77 | scissors 78 | teddy bear 79 | hair drier 80 | toothbrush -------------------------------------------------------------------------------- /app/src/main/assets/demoApplication.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/assets/demoApplication.gif -------------------------------------------------------------------------------- /app/src/main/assets/droneControl_initial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/assets/droneControl_initial.png -------------------------------------------------------------------------------- /app/src/main/assets/yolov5s.torchscript.ptl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/assets/yolov5s.torchscript.ptl -------------------------------------------------------------------------------- /app/src/main/java/com/example/demoapplication/DetectionResult.java: -------------------------------------------------------------------------------- 1 | package com.example.demoapplication; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.Rect; 9 | import android.graphics.RectF; 10 | import android.util.AttributeSet; 11 | import android.util.Log; 12 | import android.view.View; 13 | 14 | import android.os.Bundle; 15 | 16 | import androidx.core.content.ContextCompat; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.Collections; 21 | import java.util.Comparator; 22 | 23 | class Result { 24 | int classIndex; 25 | Float score; 26 | Rect rect; 27 | 28 | public Result(int cls, Float output, Rect rect) { 29 | this.classIndex = cls; 30 | this.score = output; 31 | this.rect = rect; 32 | } 33 | }; 34 | 35 | class ImageProcessing{ 36 | static float[] NO_MEAN_RGB = new float[] {0.0f, 0.0f, 0.0f}; 37 | static float[] NO_STD_RGB = new float[] {1.0f, 1.0f, 1.0f}; 38 | 39 | // model input image size 40 | static int jInputW = 640; 41 | static int jInputH = 640; 42 | 43 | // model output is of size 25200*(num_of_class+5) 44 | private static int mOutputRow = 25200; // as decided by the YOLOv5 model for input image of size 640*640 45 | private static int mOutputColumn = 85; // left, top, right, bottom, score and 80 class probability 46 | // private static float mThreshold = 0.30f; // score above which a detection is generated 47 | private static int mNmsLimit = 1000; 48 | 49 | static String[] jClasses; 50 | 51 | // The two methods nonMaxSuppression and IOU below are ported from https://github.com/hollance/YOLO-CoreML-MPSNNGraph/blob/master/Common/Helpers.swift 52 | /** 53 | Removes bounding boxes that overlap too much with other boxes that have 54 | a higher score. 55 | - Parameters: 56 | - boxes: an array of bounding boxes and their scores 57 | - limit: the maximum number of boxes that will be selected 58 | - threshold: used to decide whether boxes overlap too much 59 | */ 60 | static ArrayList nonMaxSuppression(ArrayList boxes, int limit, float threshold) { 61 | 62 | // Do an argsort on the confidence scores, from high to low. 63 | Collections.sort(boxes, 64 | new Comparator() { 65 | @Override 66 | public int compare(Result o1, Result o2) { 67 | return o1.score.compareTo(o2.score); 68 | } 69 | }); 70 | 71 | ArrayList selected = new ArrayList<>(); 72 | boolean[] active = new boolean[boxes.size()]; 73 | Arrays.fill(active, true); 74 | int numActive = active.length; 75 | 76 | // The algorithm is simple: Start with the box that has the highest score. 77 | // Remove any remaining boxes that overlap it more than the given threshold 78 | // amount. If there are any boxes left (i.e. these did not overlap with any 79 | // previous boxes), then repeat this procedure, until no more boxes remain 80 | // or the limit has been reached. 81 | boolean done = false; 82 | for (int i=0; i= limit) break; 87 | 88 | for (int j=i+1; j threshold) { 92 | active[j] = false; 93 | numActive -= 1; 94 | if (numActive <= 0) { 95 | done = true; 96 | break; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | return selected; 104 | } 105 | 106 | /** 107 | Computes intersection-over-union overlap between two bounding boxes. 108 | */ 109 | static float IOU(Rect a, Rect b) { 110 | float areaA = (a.right - a.left) * (a.bottom - a.top); 111 | if (areaA <= 0.0) return 0.0f; 112 | 113 | float areaB = (b.right - b.left) * (b.bottom - b.top); 114 | if (areaB <= 0.0) return 0.0f; 115 | 116 | float intersectionMinX = Math.max(a.left, b.left); 117 | float intersectionMinY = Math.max(a.top, b.top); 118 | float intersectionMaxX = Math.min(a.right, b.right); 119 | float intersectionMaxY = Math.min(a.bottom, b.bottom); 120 | float intersectionArea = Math.max(intersectionMaxY - intersectionMinY, 0) * 121 | Math.max(intersectionMaxX - intersectionMinX, 0); 122 | return intersectionArea / (areaA + areaB - intersectionArea); 123 | } 124 | 125 | static ArrayList outputsNMSFilter(float[] outputs, float mThreshold, float imgScaleX, float imgScaleY, float ivScaleX, float ivScaleY, float startX, float startY) { 126 | ArrayList results = new ArrayList<>(); 127 | for (int i = 0; i< mOutputRow; i++) { 128 | if (outputs[i* mOutputColumn +4] > mThreshold) { 129 | float x = outputs[i* mOutputColumn]; 130 | float y = outputs[i* mOutputColumn +1]; 131 | float w = outputs[i* mOutputColumn +2]; 132 | float h = outputs[i* mOutputColumn +3]; 133 | 134 | float left = imgScaleX * (x - w/2); 135 | float top = imgScaleY * (y - h/2); 136 | float right = imgScaleX * (x + w/2); 137 | float bottom = imgScaleY * (y + h/2); 138 | 139 | float max = outputs[i* mOutputColumn +5]; 140 | int cls = 0; 141 | for (int j = 0; j < mOutputColumn -5; j++) { 142 | if (outputs[i* mOutputColumn +5+j] > max) { 143 | max = outputs[i* mOutputColumn +5+j]; 144 | cls = j; 145 | } 146 | } 147 | 148 | Rect rect = new Rect((int)(startX+ivScaleX*left), (int)(startY+top*ivScaleY), (int)(startX+ivScaleX*right), (int)(startY+ivScaleY*bottom)); 149 | Result result = new Result(cls, outputs[i*mOutputColumn+4], rect); 150 | results.add(result); 151 | } 152 | } 153 | return nonMaxSuppression(results, mNmsLimit, mThreshold); 154 | } 155 | 156 | } 157 | 158 | public class DetectionResult extends View { 159 | 160 | private final static int TX = 5; 161 | private final static int TY = 10; 162 | private final static int TW = 52; 163 | private final static int TH = 10; 164 | 165 | private Paint jPaintRect; 166 | private Paint jPaintTxt; 167 | private ArrayList jResults; 168 | 169 | public DetectionResult(Context context) { 170 | super(context); 171 | } 172 | 173 | public DetectionResult(Context context, AttributeSet attrs) { 174 | super(context, attrs); 175 | jPaintRect = new Paint(); 176 | jPaintRect.setColor(Color.WHITE); 177 | jPaintTxt = new Paint(); 178 | } 179 | 180 | @Override 181 | protected void onDraw(Canvas canvas) { 182 | super.onDraw(canvas); 183 | 184 | if (jResults == null) 185 | return; 186 | for (Result result : jResults) { 187 | jPaintRect.setStrokeWidth(8); 188 | jPaintRect.setStyle(Paint.Style.STROKE); 189 | RectF recT = new RectF(result.rect); 190 | canvas.drawRoundRect(recT,10,10, jPaintRect); 191 | 192 | Path jPath = new Path(); 193 | RectF jRectF = new RectF(result.rect.left, result.rect.top, result.rect.left + TW, result.rect.top + TH); 194 | jPath.addRect(jRectF, Path.Direction.CW); 195 | jPaintTxt.setColor(Color.WHITE); 196 | canvas.drawPath(jPath, jPaintTxt); 197 | 198 | jPaintTxt.setColor(Color.BLACK); 199 | jPaintTxt.setStrokeWidth(0); 200 | jPaintTxt.setStyle(Paint.Style.FILL_AND_STROKE); 201 | jPaintTxt.setTextSize(22); 202 | try { 203 | canvas.drawText(String.format("%s %.2f", ImageProcessing.jClasses[result.classIndex], result.score), result.rect.left + TX, result.rect.top + TY, jPaintTxt); 204 | } catch (Exception e) { 205 | e.printStackTrace(); 206 | } 207 | 208 | } 209 | 210 | jPaintTxt.setColor(Color.GRAY); 211 | jPaintTxt.setStrokeWidth(3); 212 | jPaintTxt.setStyle(Paint.Style.FILL); 213 | jPaintTxt.setTextSize(32); 214 | canvas.drawText(String.format("%s %d","Count :",jResults.size()), TX +30, TY+35, jPaintTxt); 215 | 216 | } 217 | public void setResults(ArrayList results){ 218 | // String nameofCurrMethod = new Throwable().getStackTrace()[0].getMethodName(); 219 | // Log.d(nameofCurrMethod, ""+java.time.LocalTime.now()); 220 | 221 | jResults = results; 222 | } 223 | } 224 | 225 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/demoapplication/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.demoapplication; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | import androidx.core.content.ContextCompat; 5 | 6 | import android.content.Intent; 7 | import android.graphics.Color; 8 | import android.os.Build; 9 | import android.os.Bundle; 10 | import android.view.Window; 11 | import android.view.WindowManager; 12 | 13 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 14 | 15 | public class MainActivity extends AppCompatActivity { 16 | 17 | private FloatingActionButton droneControlScreen; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | requestWindowFeature(Window.FEATURE_NO_TITLE); // remove title bar from android screen 23 | getSupportActionBar().hide(); 24 | setContentView(R.layout.activity_main); 25 | 26 | if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 27 | getWindow().setNavigationBarColor(Color.parseColor("#000000")); 28 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 29 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 30 | getWindow().setStatusBarColor(Color.parseColor("#000000")); 31 | } 32 | 33 | droneControlScreen = findViewById(R.id.introImage); 34 | droneControlScreen.setOnClickListener(v -> { 35 | Intent droneControlScreenIntent = new Intent(MainActivity.this, droneController.class); 36 | startActivity(droneControlScreenIntent); 37 | }); 38 | 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/demoapplication/droneController.java: -------------------------------------------------------------------------------- 1 | package com.example.demoapplication; 2 | 3 | import static android.os.SystemClock.sleep; 4 | import static java.lang.Thread.interrupted; 5 | 6 | import androidx.annotation.Nullable; 7 | import androidx.annotation.WorkerThread; 8 | import androidx.appcompat.app.AppCompatActivity; 9 | 10 | import android.content.Context; 11 | import android.graphics.Bitmap; 12 | import android.graphics.BitmapFactory; 13 | import android.graphics.Color; 14 | import android.graphics.ImageFormat; 15 | import android.graphics.Matrix; 16 | import android.graphics.Rect; 17 | import android.graphics.YuvImage; 18 | import android.media.Image; 19 | import android.media.MediaCodec; 20 | import android.media.MediaCodecInfo; 21 | import android.media.MediaFormat; 22 | import android.os.Build; 23 | import android.os.Bundle; 24 | import android.os.Handler; 25 | import android.util.Log; 26 | import android.view.View; 27 | import android.view.Window; 28 | import android.view.WindowManager; 29 | import android.widget.ImageView; 30 | import android.widget.SeekBar; 31 | import android.widget.Switch; 32 | import android.widget.TextView; 33 | import android.widget.Toast; 34 | 35 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 36 | 37 | import org.pytorch.IValue; 38 | import org.pytorch.LiteModuleLoader; 39 | import org.pytorch.Module; 40 | import org.pytorch.Tensor; 41 | import org.pytorch.torchvision.TensorImageUtils; 42 | 43 | import java.io.BufferedReader; 44 | import java.io.ByteArrayOutputStream; 45 | import java.io.File; 46 | import java.io.FileOutputStream; 47 | import java.io.IOException; 48 | import java.io.InputStream; 49 | import java.io.InputStreamReader; 50 | import java.io.OutputStream; 51 | import java.io.UnsupportedEncodingException; 52 | import java.net.DatagramPacket; 53 | import java.net.DatagramSocket; 54 | import java.net.InetAddress; 55 | import java.net.InetSocketAddress; 56 | import java.net.SocketException; 57 | import java.net.UnknownHostException; 58 | import java.nio.ByteBuffer; 59 | import java.util.ArrayList; 60 | import java.util.Arrays; 61 | import java.util.List; 62 | import java.util.concurrent.BlockingQueue; 63 | import java.util.concurrent.LinkedBlockingQueue; 64 | import java.util.regex.Matcher; 65 | import java.util.regex.Pattern; 66 | 67 | import io.github.controlwear.virtual.joystick.android.JoystickView; 68 | 69 | public class droneController extends AppCompatActivity { 70 | 71 | private TextView jdroneWIFI; 72 | private TextView jdroneTOF; 73 | private TextView jdroneBaro; 74 | private TextView jdroneTemperature; 75 | private TextView jdroneHeight; 76 | private TextView jdroneBattery; 77 | private TextView jdroneSpeed; 78 | private TextView jdroneAccleration; 79 | private TextView droneObjectCount; 80 | 81 | private Switch videoFeedaction; 82 | private FloatingActionButton DroneObjectDetection; 83 | private SeekBar setDroneSpeedBar; 84 | 85 | private FloatingActionButton connection; 86 | private ImageView actionTakeOff; // button to get the drone to takeoff 87 | private ImageView actionLnad; // button to get the drone to land 88 | private ImageView bitImageView; // this is the imageview to which the drone video feed will be displayed 89 | 90 | private int connectionClickCounter = 1; // for counting the number of times the button is clicked 91 | private boolean connectionFlag = false; // to check and maintain the connection status of the drone. Initially the drone is not conected, so the status is false 92 | 93 | Pattern statePattern = Pattern.compile("-*\\d{0,3}\\.?\\d{0,2}[^\\D\\W\\s]"); // a regex pattern to read the tello state 94 | private int RC[] = {0,0,0,0}; // initialize an array of variables for remote control 95 | private Handler telloStateHandler; // and handler needs to be created to display the tello state values in the UI in realtime 96 | long startMs; // variable to calculate the time difference for video codec 97 | private MediaCodec m_codec; // MediaCodec is used to decode the incoming H.264 stream from tello drone 98 | private boolean detectionFlag = false; // detectionFlag is used to track if the user wants to perform object detection 99 | private boolean videoStreamFlag = false; // keeps track of the video streaming status from the drone 100 | 101 | // object detection variables and constants 102 | private Module jMod = null; 103 | private DetectionResult jResults; 104 | private float rtThreshold = 0.30f; 105 | // 106 | 107 | public static String assetFilePath(Context context, String assetName) throws IOException { 108 | File file = new File(context.getFilesDir(), assetName); 109 | if (file.exists() && file.length() > 0) { 110 | return file.getAbsolutePath(); 111 | } 112 | 113 | try (InputStream is = context.getAssets().open(assetName)) { 114 | try (OutputStream os = new FileOutputStream(file)) { 115 | byte[] buffer = new byte[4 * 1024]; 116 | int read; 117 | while ((read = is.read(buffer)) != -1) { 118 | os.write(buffer, 0, read); 119 | } 120 | os.flush(); 121 | } 122 | return file.getAbsolutePath(); 123 | } 124 | } 125 | 126 | @Override 127 | protected void onCreate(Bundle savedInstanceState) { 128 | super.onCreate(savedInstanceState); 129 | requestWindowFeature(Window.FEATURE_NO_TITLE); // remove title bar from android screen 130 | getSupportActionBar().hide(); 131 | this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 132 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 133 | setContentView(R.layout.activity_drone_controller); 134 | 135 | if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 136 | getWindow().setNavigationBarColor(Color.parseColor("#000000")); 137 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 138 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 139 | getWindow().setStatusBarColor(Color.parseColor("#000000")); 140 | } 141 | 142 | telloStateHandler = new Handler(); 143 | 144 | jdroneBattery = findViewById(R.id.droneBattery); // reads drone battery level 145 | jdroneTOF = findViewById(R.id.droneTOF); // reading Time of Flight 146 | jdroneBaro = findViewById(R.id.droneBaro); // reading Barometric pressure 147 | jdroneTemperature = findViewById(R.id.droneTemperature); // reading drone temperature 148 | jdroneHeight = findViewById(R.id.droneHeight); // reading the current drone height 149 | jdroneSpeed = findViewById(R.id.droneSpeed); // reading drone speed 150 | jdroneAccleration = findViewById(R.id.droneAccleration); // reading drone accleration 151 | jdroneWIFI = findViewById(R.id.droneWIFI); // getting the wifi status 152 | jdroneWIFI.setBackgroundResource(R.drawable.rounded_corner_red); 153 | 154 | bitImageView = findViewById(R.id.bitView); // ImageView on which the drone video will be projected 155 | droneObjectCount = (TextView) findViewById(R.id.objectCountDrone); 156 | jResults = findViewById(R.id.DetectionResultView); // this is a custom view that will display the object detection results (bounding boxes) on top of video Feed 157 | DroneObjectDetection = findViewById(R.id.startDroneDetection); 158 | 159 | DroneObjectDetection.setOnClickListener(new View.OnClickListener() { 160 | @Override 161 | public void onClick(View view) { 162 | if (connectionFlag) { 163 | if (videoStreamFlag){ 164 | Toast.makeText(droneController.this, "Starting object detection", Toast.LENGTH_SHORT).show(); 165 | detectionFlag = true; 166 | } 167 | } else { 168 | detectionFlag = false; 169 | Toast.makeText(droneController.this, "Stopping object detection", Toast.LENGTH_SHORT); 170 | } 171 | } 172 | 173 | }); 174 | 175 | connection = findViewById(R.id.connectToDrone); // a button to initiate establishing SDK mode with the drone by sending 'command' command 176 | connection.setOnClickListener(new View.OnClickListener(){ 177 | public void onClick(View v){ 178 | if (connectionClickCounter % 2 == 1){ // to enable swith like behavior to connect and disconnect from the drone 179 | telloConnect("command"); 180 | Toast.makeText(droneController.this,"Drone connected",Toast.LENGTH_SHORT).show(); 181 | connectionFlag = true; // set the connection status to true 182 | } 183 | if (connectionClickCounter % 2 == 0){ 184 | telloConnect("disconnect"); 185 | connectionFlag = false; 186 | Toast.makeText(droneController.this,"Drone disconnected",Toast.LENGTH_SHORT).show(); 187 | } 188 | connectionClickCounter++; 189 | } 190 | }); 191 | 192 | actionTakeOff = findViewById(R.id.takeoff); 193 | actionTakeOff.setOnClickListener(v -> { 194 | if (connectionFlag){ 195 | telloConnect("takeoff"); // send takeoff command 196 | } 197 | }); 198 | 199 | actionLnad = findViewById(R.id.land); 200 | actionLnad.setOnClickListener(v -> { 201 | if (connectionFlag){ 202 | telloConnect("land"); // send land command 203 | } 204 | }); 205 | 206 | JoystickView leftjoystick = (JoystickView) findViewById(R.id.joystickViewLeft); // left joystick where the angle is the movement angle and strength is the extend to which you push the joystick 207 | leftjoystick.setOnMoveListener((angle, strength) -> { 208 | 209 | if (angle >45 && angle <=135){ 210 | RC[2]= strength; 211 | } 212 | if (angle >226 && angle <=315){ 213 | strength *= -1; 214 | RC[2]= strength; 215 | } 216 | if (angle >135 && angle <=225){ 217 | strength *= -1; 218 | RC[3]= strength; 219 | } 220 | if (angle >316 && angle <=359 || angle >0 && angle <=45){ 221 | RC[3]= strength; 222 | } 223 | 224 | telloConnect("rc "+ RC[0] +" "+ RC[1] +" "+ RC[2] +" "+ RC[3]); // send the command eg,. 'rc 10 00 32 00' 225 | Arrays.fill(RC, 0); // reset the array with 0 after every virtual joystick move 226 | 227 | }); 228 | 229 | JoystickView rightjoystick = (JoystickView) findViewById(R.id.joystickViewRight); 230 | rightjoystick.setOnMoveListener((angle, strength) -> { 231 | if (angle >45 && angle <=135){ 232 | RC[1]= strength; 233 | } 234 | if (angle >226 && angle <=315){ 235 | strength *= -1; 236 | RC[1]= strength; 237 | } 238 | if (angle >135 && angle <=225){ 239 | strength *= -1; 240 | RC[0]= strength; 241 | } 242 | if (angle >316 && angle <=359 || angle >0 && angle <=45){ 243 | RC[0]= strength; 244 | } 245 | 246 | telloConnect("rc "+ RC[0] +" "+ RC[1] +" "+ RC[2] +" "+ RC[3]); 247 | Arrays.fill(RC, 0); // reset the array with 0 after every virtual joystick move 248 | }); 249 | 250 | videoFeedaction = findViewById(R.id.videoFeed); 251 | videoFeedaction.setOnClickListener(view -> { 252 | if (connectionFlag) { 253 | if (videoFeedaction.isChecked()) { 254 | videoStreamFlag = true; 255 | try { 256 | BlockingQueue frameV = new LinkedBlockingQueue<>(2); // you can increase the blocking queue capacity, it dosen't matter 257 | videoHandler("streamon", frameV); 258 | Runnable DLV = new displayBitmap(frameV); 259 | new Thread(DLV).start(); 260 | Runnable r = new objectDetectionThread(frameV); 261 | new Thread(r).start(); 262 | } catch (IOException e) { 263 | e.printStackTrace(); 264 | } 265 | } 266 | if (!videoFeedaction.isChecked()) { 267 | telloConnect("streamoff"); 268 | videoStreamFlag = false; 269 | } 270 | } else { 271 | Toast.makeText(droneController.this, "Drone disconnected", Toast.LENGTH_SHORT); 272 | videoFeedaction.setChecked(false); 273 | } 274 | }); 275 | 276 | } // end of oncreate 277 | 278 | public void telloConnect(final String strCommand){ 279 | new Thread(new Runnable() { // create a new runnable thread to handle tello state 280 | public void run() { 281 | Boolean run = true; // always keep running once initiated 282 | try { 283 | if (strCommand == "disconnect"){ 284 | run = false; 285 | } 286 | DatagramSocket udpSocket = new DatagramSocket(null); // create a datagram socket with null attribute so that a dynamic port address can be chosen later on 287 | 288 | InetAddress serverAddr = InetAddress.getByName("192.168.10.1"); // set the tello IP address (refer Tello SDK 1.3) 289 | byte[] buf = (strCommand).getBytes("UTF-8"); // command needs to be in UTF-8 290 | DatagramPacket packet = new DatagramPacket(buf, buf.length,serverAddr, 8889); // crate new datagram packet 291 | udpSocket.send(packet); // send packets to port 8889 292 | while (run){ 293 | byte[] message = new byte[1518]; // create a new byte message (you can change the size) 294 | DatagramPacket rpacket = new DatagramPacket(message,message.length); 295 | Log.i("UDP client: ", "about to wait to receive"); 296 | udpSocket.setSoTimeout(2000); // set a timeout to close the connection 297 | udpSocket.receive(rpacket); // receive the response packet from tello 298 | String text = new String(message, 0, rpacket.getLength()); // convert the message to text 299 | Log.d("Received text", text); // display the text as log in Logcat 300 | new Thread(new Runnable() { // create a new thread to stream tello state 301 | @Override 302 | public void run() { 303 | while (!interrupted()){ 304 | sleep(2000); // I chose 2 seconds as the delay 305 | byte[] buf = new byte[0]; 306 | try { 307 | buf = ("battery?").getBytes("UTF-8"); 308 | DatagramPacket packet = new DatagramPacket(buf, buf.length,serverAddr, 8889); 309 | udpSocket.send(packet); 310 | 311 | DatagramSocket socket = new DatagramSocket(null); // create a new datagram socket 312 | socket.setReuseAddress(true); // set the reuse 313 | socket.setBroadcast(true); 314 | socket.bind(new InetSocketAddress(8890)); // bind to tello state port (refer to SDK 1.3) 315 | 316 | byte[] message = new byte[1518]; 317 | DatagramPacket rpacket = new DatagramPacket(message,message.length); //, serverAddr, 8890 318 | socket.receive(rpacket); 319 | String text = new String(message, 0, rpacket.getLength()); 320 | Matcher DCML = statePattern.matcher(text); // use the regex pattern initiated at the beginning of the code to parse the response from tell drone 321 | List dec = new ArrayList(); // parse the response and store it in an array 322 | while (DCML.find()) { 323 | dec.add(DCML.group()); 324 | } 325 | 326 | Log.d("Battery Charge : ",text+"%"); 327 | telloStateHandler.post(new Runnable() { // use the initiated handler to post the tello state output the drone controller UI 328 | @Override 329 | public void run() { 330 | try{ 331 | jdroneBattery.setText("Battery: "+ dec.get(10)+"%"); 332 | if (Integer.parseInt(dec.get(10)) <= 15){ 333 | jdroneBattery.setBackgroundResource(R.drawable.rounded_corner_red); // if battery percentage is below 15 set the background of text to red 334 | } 335 | else { 336 | jdroneBattery.setBackgroundResource(R.drawable.rounded_corner_green); // else display batter percentage with green background 337 | } 338 | if (Integer.parseInt(dec.get(10)) != 0){ 339 | jdroneWIFI.setBackgroundResource(R.drawable.rounded_corner_green); // if wifi is connected and is active then display with green background 340 | jdroneWIFI.setText("WIFI: connected"); 341 | } 342 | jdroneTOF.setText("TOF: "+dec.get(8)+"cm"); 343 | jdroneBaro.setText("Baro: "+dec.get(11)+"m"); 344 | jdroneHeight.setText("Height: "+dec.get(9)); 345 | jdroneTemperature.setText("Temperature: "+dec.get(7)+"C"); 346 | jdroneSpeed.setText("Speed :"+ Integer.parseInt(dec.get(3)) + Integer.parseInt(dec.get(4)) + Integer.parseInt(dec.get(5))+"cm/s"); 347 | jdroneAccleration.setText("Acceleration: "+Math.round(Math.sqrt(Math.pow(Double.parseDouble(dec.get(13)),2)+Math.pow(Double.parseDouble(dec.get(14)),2)+Math.pow(Double.parseDouble(dec.get(15)),2)))+"g"); 348 | // https://physics.stackexchange.com/questions/41653/how-do-i-get-the-total-acceleration-from-3-axes 349 | // for calculating acceleration I referred to the above link 350 | 351 | telloStateHandler.removeCallbacks(this); 352 | 353 | }catch (Exception e){ 354 | Log.e("Array out of bounds", "error",e); 355 | } 356 | } 357 | }); 358 | 359 | } catch (UnsupportedEncodingException e) { 360 | e.printStackTrace(); 361 | } catch (IOException e) { 362 | e.printStackTrace(); 363 | } 364 | 365 | } 366 | } 367 | }).start(); 368 | 369 | } 370 | 371 | } catch (SocketException | UnknownHostException e) { 372 | Log.e("Socket Open:", "Error:", e); 373 | } 374 | catch (IOException e){ 375 | Log.e("IOException","error",e); 376 | } 377 | 378 | } 379 | }).start(); 380 | } 381 | 382 | public void videoHandler(final String strCommand, final BlockingQueue frameV) throws IOException { // add this for surfaceView : , Surface surface 383 | telloConnect(strCommand); 384 | 385 | BlockingQueue queue = frameV; // create a BlockingQueue since this function creates a thread and outputs a video frame which has to be displayed on the UI thread 386 | // populating a queue and withdrawing from the queue outside the thread seem to be the best way to get the individual frames. 387 | if (strCommand == "streamon"){ 388 | new Thread(new Runnable() { 389 | Boolean streamon = true; // keeps track if the video stream is on or off 390 | 391 | @Override 392 | public void run() { 393 | // SPS and PPS are the golden key (it is like the right combination of keys used to unlock a lock) to decoding the video and displaying the stream 394 | byte[] header_sps = {0, 0, 0, 1, 103, 77, 64, 40, (byte) 149, (byte) 160, 60, 5, (byte) 185}; // the correct SPS NAL 395 | byte[] header_pps = {0, 0, 0, 1, 104, (byte) 238, 56, (byte) 128}; // the correct PPS NAL 396 | 397 | MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 960, 720); // H.264 is also know as AVC or Advanced Video Coding 398 | format.setByteBuffer("csd-0", ByteBuffer.wrap(header_sps)); // pass the SPS keys 399 | format.setByteBuffer("csd-1", ByteBuffer.wrap(header_pps)); // pass the PPS keys 400 | format.setInteger(MediaFormat.KEY_WIDTH, 960); // by default the tello outputs 960 x 720 video 401 | format.setInteger(MediaFormat.KEY_HEIGHT, 720); 402 | format.setInteger(MediaFormat.KEY_CAPTURE_RATE,30); // 25-30 fps is good. Feel free to experiment 403 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); // the output is a YUV420 format which need to be converted later 404 | format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 960 * 720); 405 | 406 | try { 407 | m_codec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); // initialize the decoder with AVC format. 408 | m_codec.configure(format, null ,null,0); // pass the format configuration to media codec with surface 'null' if you are processing the video for tasks like object detection, if not set to true 409 | startMs = System.currentTimeMillis(); //calculate time to pass to the codec 410 | m_codec.start(); 411 | 412 | } catch (IOException e) { 413 | e.printStackTrace(); 414 | } 415 | 416 | ByteArrayOutputStream output = new ByteArrayOutputStream(); // this is were the data from video stream will be stored before passing it to media codec 417 | DatagramSocket socketVideo = null; 418 | try { 419 | socketVideo = new DatagramSocket(null); // similar to telloState() we create datagram socket with null parameter for address 420 | socketVideo.setReuseAddress(true); // we will be reusing the address 421 | socketVideo.setBroadcast(true); 422 | socketVideo.bind(new InetSocketAddress(11111)); // based on tell SDK 1.3, the port for receiving the video frames is 11111 423 | 424 | 425 | byte[] videoBuf = new byte[2048]; // create an empty byte buffer of size 2018 (you can change the size) 426 | DatagramPacket videoPacket = new DatagramPacket(videoBuf, videoBuf.length); // create a datagram packet 427 | int destPos = 0; 428 | byte[] data_new = new byte[60000]; // 1460 + 3 // create another byte buffer of size 600000 429 | while (streamon) { // infinite loop to continuously receive 430 | socketVideo.receive(videoPacket); // receive packets from socket 431 | System.arraycopy(videoPacket.getData(), videoPacket.getOffset(), data_new, destPos, videoPacket.getLength()); 432 | destPos += videoPacket.getLength(); // get the length of the packet 433 | byte[] pacMan = new byte[videoPacket.getLength()]; // create a temporary byte buffer with the received packet size 434 | System.arraycopy(videoPacket.getData(), videoPacket.getOffset(), pacMan, 0, videoPacket.getLength()); 435 | int len = videoPacket.getLength(); 436 | output.write(pacMan); 437 | if (len < 1460) { // generally, each frame of video from tello is 1460 bytes in size, with the ending frame that is usually less than <1460 bytes which indicate end of a sequence 438 | destPos=0; 439 | byte[] data = output.toByteArray(); // one the stream reaches the end of sequence, the entire byte array containing one complete frame is passed to data and the output variable is reset to receive newer frames 440 | output.reset(); // reset to receive newer frame 441 | output.flush(); 442 | output.close(); // close 443 | int inputIndex = m_codec.dequeueInputBuffer(-1); // dosen't matter of its -1 of 10000 444 | if (inputIndex >= 0) { 445 | ByteBuffer buffer = m_codec.getInputBuffer(inputIndex); 446 | if (buffer != null){ 447 | buffer.clear(); // exp 448 | buffer.put(data); // Caused by: java.lang.NullPointerException: Attempt to get length of null array // if nothing else pass: data 449 | // if you change buffer.put also change queueInputBuffer 3rd prameter (lenth of passed byffer array) 450 | long presentationTimeUs = System.currentTimeMillis() - startMs; 451 | m_codec.queueInputBuffer(inputIndex, 0, data.length, presentationTimeUs, 0); // MediaCodec.BUFFER_FLAG_END_OF_STREAM -> produce green screen // MediaCodec.BUFFER_FLAG_KEY_FRAME and 0 works too // MediaCodec.BUFFER_FLAG_PARTIAL_FRAME 452 | } 453 | } 454 | 455 | MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 456 | int outputIndex = m_codec.dequeueOutputBuffer(info, 100); // set it back to 0 if there is error associate with this change in value 457 | 458 | if (outputIndex >= 0){ 459 | 460 | if (!detectionFlag){ 461 | m_codec.releaseOutputBuffer(outputIndex, false); // true if the surfaceView is available 462 | } 463 | 464 | else if (detectionFlag){ 465 | try { 466 | Image image = m_codec.getOutputImage(outputIndex); // store the decoded (decoded by Mediacodec) data to Image format 467 | Bitmap BM = imgToBM(image); // convert from image format to BitMap format 468 | try { 469 | if (!queue.isEmpty()){ 470 | queue.clear(); 471 | } 472 | queue.put(BM); // pass the data to the queue created earlier 473 | } catch (InterruptedException e) { 474 | e.printStackTrace(); 475 | } 476 | } catch (Exception e) { 477 | e.printStackTrace(); 478 | } 479 | m_codec.releaseOutputBuffer(outputIndex, false); // true if the surface is available 480 | } 481 | } 482 | } // end of if to send full frame 483 | } 484 | } catch (SocketException socketException) { 485 | socketException.printStackTrace(); 486 | } catch (IOException ioException) { 487 | ioException.printStackTrace(); 488 | } 489 | } 490 | }).start(); 491 | 492 | } 493 | if (strCommand == "streamoff"){ 494 | Log.d("Codec State","stopped and released called..."); 495 | m_codec.stop(); // stop and release the codec 496 | m_codec.release(); 497 | } 498 | 499 | } 500 | 501 | private Bitmap imgToBM(Image image){ // convert from Image to Bitmap format for neural network processing. 502 | Image.Plane[] p = image.getPlanes(); 503 | ByteBuffer y = p[0].getBuffer(); 504 | ByteBuffer u = p[1].getBuffer(); 505 | ByteBuffer v = p[2].getBuffer(); 506 | 507 | int ySz = y.remaining(); 508 | int uSz = u.remaining(); 509 | int vSz = v.remaining(); 510 | 511 | byte[] jm8 = new byte[ySz + uSz + vSz]; 512 | y.get(jm8, 0, ySz); 513 | v.get(jm8, ySz, vSz); 514 | u.get(jm8, ySz + vSz, uSz); 515 | 516 | YuvImage yuvImage = new YuvImage(jm8, ImageFormat.NV21, image.getWidth(), image.getHeight(), null); 517 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 518 | yuvImage.compressToJpeg(new Rect(0,0, yuvImage.getWidth(), yuvImage.getHeight()), 75, out); 519 | byte[] imgBytes = out.toByteArray(); 520 | return BitmapFactory.decodeByteArray(imgBytes, 0 , imgBytes.length); 521 | } 522 | 523 | public class displayBitmap implements Runnable{ 524 | 525 | protected BlockingQueue displayQueue; // create a blocking queue to get the data from queue 526 | protected Bitmap displayBitmap_; // create a bitmap variable for displaying bitmap 527 | 528 | public displayBitmap(BlockingQueue displayQueue_){ 529 | this.displayQueue = displayQueue_; 530 | } 531 | 532 | @Override 533 | public void run(){ 534 | 535 | while (true){ 536 | try { 537 | displayBitmap_ = (Bitmap) displayQueue.take(); // take data (video frame) from blocking queue 538 | displayQueue.clear(); // clear the queue after taking 539 | if (displayQueue != null){ 540 | runOnUiThread(() -> { // needs to be on UI thread 541 | bitImageView.setImageBitmap(displayBitmap_); // set the bitmap to current frame in the queue 542 | bitImageView.invalidate(); 543 | }); 544 | } 545 | } catch (InterruptedException e) { 546 | e.printStackTrace(); 547 | } 548 | } 549 | } 550 | } // end of displayBitmap 551 | 552 | public class objectDetectionThread implements Runnable{ 553 | 554 | private Bitmap threadBM; // create a bitmap variable 555 | private volatile ArrayList results; // create an array list to store the object detection result 556 | protected BlockingQueue threadFrame = null; // blocking queue variable to take the data from blocking queue 557 | 558 | public objectDetectionThread(BlockingQueue consumerQueue){ 559 | this.threadFrame = consumerQueue; // retrieve element from queue 560 | } 561 | 562 | @WorkerThread 563 | @Nullable 564 | public void run(){ 565 | while (true){ 566 | try { 567 | threadBM = (Bitmap) threadFrame.take(); 568 | threadFrame.clear(); // clear queue after getting the frame 569 | analyseImage(threadBM); // function that will analyze the image for object detection 570 | sleep(250); // change to 1000 if error arises 571 | } catch (Exception e) { 572 | e.printStackTrace(); 573 | } 574 | } 575 | } 576 | public ArrayList getValue(){ 577 | return results; 578 | } 579 | } // end of objectDetectionThread function 580 | 581 | static class ResA{ 582 | private final ArrayList jResults; 583 | public ResA(ArrayList results){ 584 | jResults = results; 585 | } 586 | } 587 | 588 | @WorkerThread 589 | @Nullable 590 | protected droneController.ResA analyseImage(Bitmap BMtest){ 591 | try { 592 | if (jMod == null){ 593 | jMod = LiteModuleLoader.load(droneController.assetFilePath(getApplicationContext(),"yolov5s.torchscript.ptl")); // load pre-trained pytorch module from the assets folder 594 | BufferedReader br = new BufferedReader(new InputStreamReader(getAssets().open("classes.txt"))); // similarly load the classes.txt from assets 595 | String line; 596 | List classes = new ArrayList<>(); 597 | while ((line = br.readLine()) != null) { 598 | classes.add(line); 599 | } 600 | ImageProcessing.jClasses = new String[classes.size()]; 601 | classes.toArray(ImageProcessing.jClasses); 602 | } 603 | }catch (IOException e){ 604 | Log.e("Object detection: ", "Unable to load model..."); 605 | return null; 606 | } 607 | 608 | Matrix M = new Matrix(); 609 | // M.postRotate(90.0f); 610 | BMtest = Bitmap.createBitmap(BMtest, 0,0, BMtest.getWidth(), BMtest.getHeight(), M, true); 611 | Bitmap resizedBM = Bitmap.createScaledBitmap(BMtest, ImageProcessing.jInputW, ImageProcessing.jInputH, true); // crate a bitmap of 640x640 dimension 612 | 613 | // DL model inference starts here 614 | final Tensor inputTensor = TensorImageUtils.bitmapToFloat32Tensor(resizedBM, ImageProcessing.NO_MEAN_RGB, ImageProcessing.NO_STD_RGB); 615 | IValue[] oTuple = jMod.forward(IValue.from(inputTensor)).toTuple(); 616 | final Tensor oTensor = oTuple[0].toTensor(); 617 | final float[] O = oTensor.getDataAsFloatArray(); 618 | 619 | float imSX = (float)BMtest.getWidth() / ImageProcessing.jInputW; 620 | float imSY = (float)BMtest.getHeight() / ImageProcessing.jInputH; 621 | float ivSX = (float)jResults.getWidth() /BMtest.getWidth(); 622 | float ivSY = (float)jResults.getHeight() /BMtest.getHeight(); 623 | 624 | final ArrayList results = ImageProcessing.outputsNMSFilter(O, rtThreshold, imSX, imSY, ivSX, ivSY, 0, 0); 625 | int listSize =results.size(); 626 | if (results != null){ 627 | runOnUiThread(() -> { 628 | droneObjectCount.setText(listSize+""); 629 | jResults.setResults(results); 630 | jResults.invalidate(); 631 | }); 632 | } 633 | return new droneController.ResA(results); 634 | 635 | } 636 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_camera_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_flight_land_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_flight_takeoff_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_play_circle_20.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_play_circle_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_rocket_20.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_rocket_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_videocam_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_back.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corner_green.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corner_red.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_drone_controller.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 19 | 27 | 28 | 36 | 37 | 49 | 50 | 66 | 67 | 83 | 84 | 100 | 101 | 117 | 118 | 135 | 136 | 151 | 152 | 165 | 166 | 178 | 179 | 191 | 192 | 204 | 205 | 217 | 218 | 230 | 242 | 243 | 255 | 256 | 268 | 269 | 288 | 289 | 302 | 303 | 314 | 315 | 326 | 327 | 336 | 337 | 347 | 348 | 358 | 359 | 370 | 371 | 382 | 383 | 394 | 395 | 405 | 406 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 26 | 27 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/texture_view.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | demoApplication 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/test/java/com/example/demoapplication/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.example.demoapplication; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | //plugins { 3 | // id 'com.android.application' version '7.1.1' apply false 4 | // id 'com.android.library' version '7.1.1' apply false 5 | //} 6 | 7 | buildscript { 8 | repositories { 9 | google() 10 | mavenCentral() 11 | jcenter() 12 | } 13 | dependencies { 14 | classpath 'com.android.tools.build:gradle:7.1.1' 15 | 16 | // NOTE: Do not place your application dependencies here; they belong 17 | // in the individual module build.gradle files 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | 25 | //allprojects { 26 | // repositories { 27 | // google() 28 | // mavenCentral() 29 | // jcenter() 30 | // } 31 | //} -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jithin8mathew/Tello_object_detection_demo_application/bd42d571a7af988d28413a0b7eba67fbd553062b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Apr 02 00:54:03 CDT 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | jcenter() 7 | } 8 | } 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | maven { url 'https://jitpack.io' } 15 | jcenter() 16 | } 17 | } 18 | rootProject.name = "demoApplication" 19 | include ':app' 20 | --------------------------------------------------------------------------------