├── .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 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
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 | [](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 | [](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 |
--------------------------------------------------------------------------------