├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_start.png
│ │ │ │ ├── ic_stop.png
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_settings.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_stop.png
│ │ │ │ ├── ic_start.png
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_settings.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_start.png
│ │ │ │ ├── ic_stop.png
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_settings.png
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── xml
│ │ │ │ └── accessory_filter.xml
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ ├── raw
│ │ │ │ └── models.json
│ │ │ └── layout
│ │ │ │ ├── activity_connection.xml
│ │ │ │ ├── activity_camera.xml
│ │ │ │ └── activity_settings.xml
│ │ ├── java
│ │ │ └── unmannedairlines
│ │ │ │ └── dronepan
│ │ │ │ ├── BaseActivity.java
│ │ │ │ ├── logic
│ │ │ │ ├── SettingsConstants.java
│ │ │ │ ├── SettingsManager.java
│ │ │ │ ├── StringReader.java
│ │ │ │ ├── DJIConnection.java
│ │ │ │ ├── Settings.java
│ │ │ │ ├── PanoramaShoot.java
│ │ │ │ └── SettingsFactory.java
│ │ │ │ ├── DronePanApplication.java
│ │ │ │ ├── mission
│ │ │ │ ├── DelayAction.java
│ │ │ │ ├── WaitForCameraReadyAction.java
│ │ │ │ ├── helpers
│ │ │ │ │ └── CameraSystemStateController.java
│ │ │ │ └── CustomAircraftYawAction.java
│ │ │ │ ├── SettingsActivity.java
│ │ │ │ ├── ConnectionActivity.java
│ │ │ │ └── CameraActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── unmannedairlines
│ │ │ └── dronepan
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── unmannedairlines
│ │ └── dronepan
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
├── build.gradle
└── app.iml
├── settings.gradle
├── .idea
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── vcs.xml
├── runConfigurations.xml
├── modules.xml
├── compiler.xml
├── gradle.xml
└── misc.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── DronePan-Android.iml
├── .gitignore
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':android-uilib-release'
2 | include ':dJISDKLIB'
3 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-mdpi/ic_start.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-mdpi/ic_stop.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-xhdpi/ic_stop.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-xhdpi/ic_start.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_start.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_stop.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-mdpi/ic_settings.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-xhdpi/ic_settings.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_settings.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dbaldwin/DronePan-Android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/xml/accessory_filter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | DronePan
3 | Disconnected
4 |
5 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 0dp
4 | 0dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Apr 27 22:19:18 PDT 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/unmannedairlines/dronepan/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan;
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() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/db/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.View;
6 |
7 | public abstract class BaseActivity extends Activity {
8 |
9 | @Override
10 | public void onCreate(Bundle savedInstanceState) {
11 | super.onCreate(savedInstanceState);
12 |
13 | View decorView = getWindow().getDecorView();
14 | decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
15 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
16 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
17 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
18 | | View.SYSTEM_UI_FLAG_FULLSCREEN
19 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/logic/SettingsConstants.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan.logic;
2 |
3 | public class SettingsConstants {
4 | public static final String PhotosPerRow = "photosPerRow";
5 | public static final String NumberOfRows = "numberOfRows";
6 | public static final String NumberOfNadirShots = "numberOfNadirShots";
7 | public static final String DelayBeforeEachShot = "delayBeforeEachShot";
8 | public static final String UseImperial = "useImperial";
9 | public static final String AebPhotoMode = "aebPhotoMode";
10 | public static final String ShootRowByRow = "shootRowByRow";
11 | public static final String AllowsAboveHorizon = "allowsAboveHorizon";
12 | public static final String CanGimbalYaw = "canGimbalYaw";
13 | public static final String UseGimbalToYaw = "useGimbalToYaw";
14 | }
15 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/DronePanApplication.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.os.Build;
6 |
7 | import unmannedairlines.dronepan.logic.DJIConnection;
8 |
9 | public class DronePanApplication extends Application {
10 |
11 | private static Context context;
12 |
13 | @Override
14 | public void onCreate() {
15 | super.onCreate();
16 |
17 | context = getApplicationContext();
18 | DJIConnection.getInstance().initialize(context);
19 | }
20 |
21 | public static boolean isRunningOnEmulator()
22 | {
23 | return Build.PRODUCT.startsWith("sdk_google");
24 | }
25 |
26 | public static String getBuildVersion()
27 | {
28 | return BuildConfig.VERSION_NAME;
29 | }
30 |
31 | public static Context getContext() { return context; }
32 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/unmannedairlines/dronepan/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("unmannedairlines.dronepan", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/models.json:
--------------------------------------------------------------------------------
1 | {
2 | "default": {
3 | "allowsAboveHorizon": true,
4 | "numberOfRows": 4,
5 | "photosPerRow": 6,
6 | "numberOfNadirShots": 1,
7 | "canGimbalYaw": false,
8 | "useGimbalToYaw": false
9 | },
10 | "MAVIC_PRO": {
11 | "allowsAboveHorizon": false,
12 | "numberOfRows": 3,
13 | "photosPerRow": 8
14 | },
15 | "INSPIRE_1": {
16 | "canGimbalYaw": true,
17 | "useGimbalToYaw": true,
18 | "photosPerRow": 7
19 | },
20 | "INSPIRE_1_PRO": {
21 | "canGimbalYaw": true,
22 | "useGimbalToYaw": true,
23 | "photosPerRow": 7
24 | },
25 | "INSPIRE_1_RAW": {
26 | "canGimbalYaw": true,
27 | "useGimbalToYaw": true,
28 | "photosPerRow": 7
29 | },
30 | "INSPIRE_2": {
31 | "canGimbalYaw": true,
32 | "useGimbalToYaw": true,
33 | "photosPerRow": 7
34 | }
35 | }
--------------------------------------------------------------------------------
/DronePan-Android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/android
3 |
4 | ### Android ###
5 | # Built application files
6 | *.apk
7 | *.ap_
8 |
9 | # Files for the ART/Dalvik VM
10 | *.dex
11 |
12 | # Java class files
13 | *.class
14 |
15 | # Generated files
16 | bin/
17 | gen/
18 | out/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 |
24 | # Local configuration file (sdk path, etc)
25 | local.properties
26 |
27 | # Proguard folder generated by Eclipse
28 | proguard/
29 |
30 | # Log Files
31 | *.log
32 |
33 | # Android Studio Navigation editor temp files
34 | .navigation/
35 |
36 | # Android Studio captures folder
37 | captures/
38 |
39 | # Intellij
40 | *.iml
41 | .idea/workspace.xml
42 | .idea/tasks.xml
43 | .idea/libraries
44 |
45 | # Keystore files
46 | *.jks
47 |
48 | # External native build folder generated in Android Studio 2.2 and later
49 | .externalNativeBuild
50 |
51 | ### Android Patch ###
52 | gen-external-apklibs
53 |
54 | # End of https://www.gitignore.io/api/android
55 |
56 | dJISDKLIB/
57 | android-uilib-release/android-uilib-release.aar
58 | android-uilib-release/build.gradle
59 | import-summary.txt
60 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/logic/SettingsManager.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan.logic;
2 |
3 | import java.util.HashMap;
4 |
5 | import dji.common.product.Model;
6 |
7 | public class SettingsManager {
8 | private static final String TAG = SettingsManager.class.getName();
9 |
10 | private static SettingsManager instance;
11 | public static SettingsManager getInstance() {
12 | if (instance == null) {
13 | instance = new SettingsManager();
14 | }
15 |
16 | return instance;
17 | }
18 |
19 | HashMap loadedSettings;
20 |
21 | private SettingsManager() {
22 | loadedSettings = new HashMap();
23 | }
24 |
25 | public Settings getSettings(Model model) {
26 | if (loadedSettings.containsKey(model.name())) {
27 | return loadedSettings.get(model.name());
28 | }
29 |
30 | SettingsFactory factory = new SettingsFactory(model);
31 | Settings settings = factory.create();
32 |
33 | loadedSettings.put(model.name(), settings);
34 |
35 | return settings;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.2"
6 | defaultConfig {
7 | applicationId "unmannedairlines.dronepan"
8 | minSdkVersion 19
9 | targetSdkVersion 25
10 | versionCode 3
11 | versionName "2.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | dataBinding {
21 | enabled = true
22 | }
23 | }
24 |
25 | repositories {
26 | mavenCentral()
27 | }
28 |
29 | dependencies {
30 | compile 'com.googlecode.plist:dd-plist:1.8'
31 | compile fileTree(dir: 'libs', include: ['*.jar'])
32 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
33 | exclude group: 'com.android.support', module: 'support-annotations'
34 | })
35 | compile 'com.android.support:appcompat-v7:25.1.0'
36 | testCompile 'junit:junit:4.12'
37 | //compile project(':dJISDKLIB')
38 | compile project(':android-uilib-release')
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/logic/StringReader.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan.logic;
2 |
3 | import android.util.Log;
4 |
5 | import org.json.JSONException;
6 | import org.json.JSONObject;
7 |
8 | import java.io.BufferedReader;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.io.InputStreamReader;
12 |
13 | public class StringReader {
14 | private static final String TAG = StringReader.class.getName();
15 |
16 | public static String Read(InputStream inputStream) {
17 | try {
18 | InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
19 | BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
20 | StringBuilder stringBuilder = new StringBuilder();
21 |
22 | String receiveString = "";
23 | while ((receiveString = bufferedReader.readLine()) != null) {
24 | stringBuilder.append(receiveString);
25 | }
26 |
27 | inputStream.close();
28 |
29 | return stringBuilder.toString();
30 | }
31 | catch (IOException e) {
32 | Log.e(TAG, "Read() - Unexpected IO error.", e);
33 | }
34 |
35 | return "";
36 | }
37 |
38 | public static JSONObject ReadJson(InputStream inputStream) throws JSONException
39 | {
40 | String result = Read(inputStream);
41 | if (result.length() == 0)
42 | {
43 | result = "{}";
44 | }
45 |
46 | return new JSONObject(result);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/mission/DelayAction.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan.mission;
2 |
3 | import android.os.Handler;
4 |
5 | import dji.common.error.DJIError;
6 | import dji.sdk.mission.MissionControl;
7 | import dji.sdk.mission.timeline.TimelineElement;
8 | import dji.sdk.mission.timeline.TimelineElementFeedback;
9 |
10 |
11 | public class DelayAction extends TimelineElement {
12 |
13 | Handler handler;
14 | TimelineElementFeedback feedback;
15 | int delayInMilliseconds;
16 | boolean cancelled;
17 |
18 | public DelayAction(int delayInMilliseconds) {
19 | this.delayInMilliseconds = delayInMilliseconds;
20 | this.handler = new Handler();
21 | this.feedback = MissionControl.getInstance();
22 | }
23 |
24 | @Override
25 | public void run() {
26 | this.cancelled = false;
27 |
28 | final DelayAction self = this;
29 | final Runnable r = new Runnable() {
30 | @Override
31 | public void run() {
32 | if (self.cancelled) {
33 | return;
34 | }
35 |
36 | feedback.onFinishWithError(self, null);
37 | }
38 | };
39 |
40 | handler.postDelayed(r, this.delayInMilliseconds);
41 | }
42 |
43 | @Override
44 | public boolean isPausable() {
45 | return false;
46 | }
47 |
48 | @Override
49 | public void stop() {
50 | this.cancelled = true;
51 | this.feedback.onStopWithError(this, null);
52 | }
53 |
54 | @Override
55 | public DJIError checkValidity() {
56 | return null;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/mission/WaitForCameraReadyAction.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan.mission;
2 |
3 | import dji.common.error.DJIError;
4 | import dji.sdk.mission.MissionControl;
5 | import dji.sdk.mission.timeline.TimelineElement;
6 | import dji.sdk.mission.timeline.TimelineElementFeedback;
7 | import unmannedairlines.dronepan.mission.helpers.CameraSystemStateController;
8 |
9 | public class WaitForCameraReadyAction extends TimelineElement implements CameraSystemStateController.Listener {
10 |
11 | CameraSystemStateController stateController;
12 | TimelineElementFeedback feedback;
13 |
14 | public WaitForCameraReadyAction(CameraSystemStateController stateController) {
15 | this.stateController = stateController;
16 | this.feedback = MissionControl.getInstance();
17 | }
18 |
19 | @Override
20 | public void run() {
21 | this.feedback.onStart(this);
22 | this.stateController.registerListener(this);
23 | this.checkIfCameraIsReadyAndNotify();
24 | }
25 |
26 | @Override
27 | public boolean isPausable() {
28 | return false;
29 | }
30 |
31 | @Override
32 | public void stop() {
33 | this.stateController.unregisterListener(this);
34 | this.feedback.onStopWithError(this, null);
35 | }
36 |
37 | @Override
38 | public DJIError checkValidity() {
39 | return null;
40 | }
41 |
42 | @Override
43 | public void onCameraStateChanged() {
44 | this.checkIfCameraIsReadyAndNotify();
45 | }
46 |
47 | private void checkIfCameraIsReadyAndNotify() {
48 | if (this.stateController.isReady()) {
49 | this.feedback.onFinishWithError(this, null);
50 | this.stateController.unregisterListener(this);
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan;
2 |
3 | import android.databinding.DataBindingUtil;
4 | import android.databinding.ViewDataBinding;
5 | import android.os.Bundle;
6 | import android.view.View;
7 | import android.widget.Button;
8 | import android.widget.TextView;
9 |
10 | import dji.common.product.Model;
11 | import unmannedairlines.dronepan.logic.DJIConnection;
12 | import unmannedairlines.dronepan.logic.Settings;
13 | import unmannedairlines.dronepan.logic.SettingsManager;
14 |
15 | public class SettingsActivity extends BaseActivity implements View.OnClickListener {
16 |
17 | @Override
18 | public void onCreate(Bundle savedInstanceState)
19 | {
20 | super.onCreate(savedInstanceState);
21 |
22 | ViewDataBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_settings);
23 |
24 | Model connectedModel = DJIConnection.getInstance().getModelSafely();
25 | Settings settings = SettingsManager.getInstance().getSettings(connectedModel);
26 | binding.setVariable(BR.settings, settings);
27 |
28 | Button button = (Button)findViewById(R.id.goBackButton);
29 | button.setOnClickListener(this);
30 |
31 | TextView versionTextView = (TextView)findViewById(R.id.versionTextView);
32 | versionTextView.setText("DronePan Version: " + DronePanApplication.getBuildVersion());
33 |
34 | TextView sdkVersionTextView = (TextView)findViewById(R.id.sdkVersionTextView);
35 | sdkVersionTextView.setText("SDK Version: " + DJIConnection.getInstance().getSdkVersion());
36 | }
37 |
38 | @Override
39 | public void onClick(View view)
40 | {
41 | switch (view.getId())
42 | {
43 | case R.id.goBackButton:
44 | super.onBackPressed();
45 | break;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_connection.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
25 |
26 |
33 |
34 |
42 |
43 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/mission/helpers/CameraSystemStateController.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan.mission.helpers;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.util.Log;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | import dji.common.camera.SystemState;
10 | import dji.sdk.camera.Camera;
11 |
12 | public class CameraSystemStateController implements SystemState.Callback {
13 | private static final String TAG = CameraSystemStateController.class.getName();
14 |
15 | Camera camera;
16 | SystemState currentSystemState;
17 |
18 | public interface Listener {
19 | void onCameraStateChanged();
20 | }
21 |
22 | private List listeners = new ArrayList();
23 |
24 | public CameraSystemStateController(Camera camera) {
25 | this.camera = camera;
26 | this.camera.setSystemStateCallback(this);
27 | }
28 |
29 | @Override
30 | protected void finalize() {
31 | this.camera.setSystemStateCallback(null);
32 | }
33 |
34 | public void registerListener(Listener listener) {
35 | this.listeners.add(listener);
36 | }
37 |
38 | public void unregisterListener(Listener listener) {
39 | this.listeners.remove(listener);
40 | }
41 |
42 | @Override
43 | public void onUpdate(@NonNull SystemState systemState) {
44 | this.currentSystemState = systemState;
45 | //Log.i(TAG, "isBusy? " + this.isBusy() + " isStoring? " + this.isStoringPhoto() + " isShooting? " + this.isShootingPhoto());
46 |
47 | this.notifyListener();
48 | }
49 | private void notifyListener() {
50 | for (Listener l : this.listeners) {
51 | l.onCameraStateChanged();
52 | }
53 | }
54 |
55 | public boolean isReady() {
56 | return !isBusy();
57 | }
58 |
59 | public boolean isBusy() {
60 | return //isRecordingVideo() ||
61 | //isShootingPhoto() ||
62 | isStoringPhoto() ||
63 | hasError();
64 | }
65 |
66 | public boolean hasError() {
67 | if (this.currentSystemState == null) return false;
68 | return this.currentSystemState.hasError() || this.currentSystemState.isOverheating();
69 | }
70 |
71 | public boolean isRecordingVideo() {
72 | if (this.currentSystemState == null) return false;
73 | return this.currentSystemState.isRecording();
74 | }
75 |
76 | public boolean isShootingPhoto() {
77 | if (this.currentSystemState == null) return false;
78 | return this.currentSystemState.isShootingBurstPhoto() ||
79 | this.currentSystemState.isShootingIntervalPhoto() ||
80 | this.currentSystemState.isShootingRAWBurstPhoto() ||
81 | this.currentSystemState.isShootingSinglePhoto() ||
82 | this.currentSystemState.isShootingSinglePhotoInRAWFormat();
83 | }
84 |
85 | public boolean isStoringPhoto() {
86 | if (this.currentSystemState == null) return false;
87 | return this.currentSystemState.isStoringPhoto();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
32 |
33 |
34 |
41 |
42 |
43 |
44 |
45 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
78 |
79 |
82 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/ConnectionActivity.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan;
2 |
3 | import android.Manifest;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.IntentFilter;
8 | import android.os.Build;
9 | import android.os.Handler;
10 | import android.support.v4.app.ActivityCompat;
11 | import android.os.Bundle;
12 | import android.widget.TextView;
13 | import dji.sdk.base.BaseProduct;
14 | import unmannedairlines.dronepan.logic.DJIConnection;
15 |
16 | public class ConnectionActivity extends BaseActivity {
17 |
18 | private static final String TAG = ConnectionActivity.class.getName();
19 |
20 | private TextView mTextConnectionStatus;
21 | private boolean mCameraLaunched;
22 |
23 | protected BroadcastReceiver mReceiver = new BroadcastReceiver() {
24 |
25 | @Override
26 | public void onReceive(Context context, Intent intent) {
27 | updateUI();
28 | }
29 | };
30 |
31 | @Override
32 | public void onCreate(Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 |
35 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
36 | ActivityCompat.requestPermissions(this,
37 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.VIBRATE,
38 | Manifest.permission.INTERNET, Manifest.permission.ACCESS_WIFI_STATE,
39 | Manifest.permission.WAKE_LOCK, Manifest.permission.ACCESS_COARSE_LOCATION,
40 | Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.ACCESS_FINE_LOCATION,
41 | Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS,
42 | Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.SYSTEM_ALERT_WINDOW,
43 | Manifest.permission.READ_PHONE_STATE,
44 | }
45 | , 1);
46 | }
47 |
48 | setContentView(R.layout.activity_connection);
49 |
50 | // Get notified of the device connection change
51 | IntentFilter filter = new IntentFilter();
52 | filter.addAction(DJIConnection.FLAG_CONNECTION_CHANGE);
53 | registerReceiver(mReceiver, filter);
54 |
55 | mTextConnectionStatus = (TextView) findViewById(R.id.text_connection_status);
56 |
57 | // For testing in emulator
58 | if (DronePanApplication.isRunningOnEmulator()) {
59 | launchCameraActivity();
60 | }
61 |
62 | // Show version info.
63 | TextView versionTextView = (TextView)findViewById(R.id.versionTextView);
64 | versionTextView.setText("DronePan Version: " + DronePanApplication.getBuildVersion());
65 |
66 | TextView sdkVersionTextView = (TextView)findViewById(R.id.sdkVersionTextView);
67 | sdkVersionTextView.setText("SDK Version: " + DJIConnection.getInstance().getSdkVersion());
68 |
69 | mCameraLaunched = false;
70 | }
71 |
72 | @Override
73 | protected void onResume() {
74 | super.onResume();
75 | mCameraLaunched = false;
76 | }
77 |
78 | @Override
79 | protected void onDestroy() {
80 | unregisterReceiver(mReceiver);
81 | super.onDestroy();
82 | }
83 |
84 | private void updateUI() {
85 | BaseProduct mProduct = DJIConnection.getInstance().getProduct();
86 | if (null != mProduct && mProduct.isConnected()) {
87 | if (null != mProduct.getModel()) {
88 | mTextConnectionStatus.setText(mProduct.getModel().getDisplayName() + " connected ...");
89 | }
90 | else {
91 | mTextConnectionStatus.setText("Model unavailable.");
92 | }
93 |
94 | // Let's take them to the camera view
95 | this.launchCameraActivity();
96 | }
97 | else {
98 | mTextConnectionStatus.setText("No product connected.");
99 | }
100 | }
101 |
102 | private void launchCameraActivity() {
103 | if (mCameraLaunched)
104 | {
105 | return;
106 | }
107 |
108 | mCameraLaunched = true;
109 |
110 | final Handler h = new Handler();
111 |
112 | final Runnable begin = new Runnable() {
113 | @Override
114 | public void run() {
115 | Intent intent = new Intent(ConnectionActivity.this, CameraActivity.class);
116 | startActivity(intent);
117 | }
118 | };
119 |
120 | // Let's delay for 1 second and then we'll display the camera view
121 | h.postDelayed(begin, 1000);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/logic/DJIConnection.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan.logic;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Handler;
6 | import android.os.Looper;
7 | import android.util.Log;
8 | import android.widget.Toast;
9 |
10 | import dji.common.error.DJIError;
11 | import dji.common.error.DJISDKError;
12 | import dji.common.product.Model;
13 | import dji.sdk.base.BaseComponent;
14 | import dji.sdk.base.BaseProduct;
15 | import dji.sdk.camera.Camera;
16 | import dji.sdk.products.Aircraft;
17 | import dji.sdk.products.HandHeld;
18 | import dji.sdk.sdkmanager.DJISDKManager;
19 |
20 | public class DJIConnection {
21 | private static final String TAG = DJIConnection.class.getName();
22 | public static final String FLAG_CONNECTION_CHANGE = "dji_sdk_connection_change";
23 |
24 | private static DJIConnection instance;
25 | public static DJIConnection getInstance() {
26 | if (instance == null) {
27 | instance = new DJIConnection();
28 | }
29 |
30 | return instance;
31 | }
32 |
33 | private BaseProduct connectedProduct;
34 | private Context context;
35 | private Handler handler;
36 |
37 | public DJIConnection() {
38 |
39 | }
40 |
41 | public void initialize(Context context) {
42 | this.context = context;
43 |
44 | // Initialize DJI SDK Manager
45 | this.handler = new Handler(Looper.getMainLooper());
46 | DJISDKManager.getInstance().registerApp(this.context, mDJISDKManagerCallback);
47 | }
48 |
49 | private DJISDKManager.SDKManagerCallback mDJISDKManagerCallback = new DJISDKManager.SDKManagerCallback() {
50 | @Override
51 | public void onRegister(DJIError error) {
52 | Log.d(TAG, error == null ? "success" : error.getDescription());
53 | if(error == DJISDKError.REGISTRATION_SUCCESS) {
54 | DJISDKManager.getInstance().startConnectionToProduct();
55 | Handler handler = new Handler(Looper.getMainLooper());
56 | handler.post(new Runnable() {
57 | @Override
58 | public void run() {
59 | Toast.makeText(context, "SDK registered successfully.", Toast.LENGTH_LONG).show();
60 | }
61 | });
62 | } else {
63 | Handler handler = new Handler(Looper.getMainLooper());
64 | handler.post(new Runnable() {
65 | @Override
66 | public void run() {
67 | Toast.makeText(context, "SDK registration failed. Please check your network connection.", Toast.LENGTH_LONG).show();
68 | }
69 | });
70 | }
71 |
72 | Log.e("TAG", error.toString());
73 | }
74 | @Override
75 | public void onProductChange(BaseProduct oldProduct, BaseProduct newProduct) {
76 | connectedProduct = newProduct;
77 | if(connectedProduct != null) {
78 | connectedProduct.setBaseProductListener(mDJIBaseProductListener);
79 | }
80 |
81 | notifyStatusChange();
82 | }
83 | };
84 |
85 | private BaseProduct.BaseProductListener mDJIBaseProductListener = new BaseProduct.BaseProductListener() {
86 | @Override
87 | public void onComponentChange(BaseProduct.ComponentKey key, BaseComponent oldComponent, BaseComponent newComponent) {
88 | if(newComponent != null) {
89 | newComponent.setComponentListener(mDJIComponentListener);
90 | }
91 | notifyStatusChange();
92 | }
93 |
94 | @Override
95 | public void onConnectivityChange(boolean isConnected) {
96 | notifyStatusChange();
97 | }
98 | };
99 |
100 | private BaseComponent.ComponentListener mDJIComponentListener = new BaseComponent.ComponentListener() {
101 | @Override
102 | public void onConnectivityChange(boolean isConnected) {
103 | notifyStatusChange();
104 | }
105 | };
106 |
107 | private void notifyStatusChange() {
108 | handler.removeCallbacks(updateRunnable);
109 | handler.postDelayed(updateRunnable, 500);
110 | }
111 |
112 | private Runnable updateRunnable = new Runnable() {
113 | @Override
114 | public void run() {
115 | Intent intent = new Intent(FLAG_CONNECTION_CHANGE);
116 | context.sendBroadcast(intent);
117 | }
118 | };
119 |
120 | public BaseProduct getProduct() {
121 | return this.connectedProduct;
122 | }
123 |
124 | public Model getModelSafely()
125 | {
126 | Model model = null;
127 |
128 | BaseProduct product = getProduct();
129 | if (product != null)
130 | {
131 | model = product.getModel();
132 | }
133 |
134 | if (model == null)
135 | {
136 | model = Model.UNKNOWN_AIRCRAFT;
137 | }
138 |
139 | return model;
140 | }
141 |
142 | public boolean isAircraftConnected() {
143 | return connectedProduct != null && connectedProduct instanceof Aircraft;
144 | }
145 |
146 | public boolean isHandHeldConnected() {
147 | return connectedProduct != null && connectedProduct instanceof HandHeld;
148 | }
149 |
150 | public synchronized Camera getCamera() {
151 |
152 | if (connectedProduct == null) return null;
153 |
154 | Camera camera = null;
155 |
156 | if (isAircraftConnected()){
157 | camera = ((Aircraft)connectedProduct).getCamera();
158 |
159 | } else if (isHandHeldConnected()) {
160 | camera = ((HandHeld)connectedProduct).getCamera();
161 | }
162 |
163 | return camera;
164 | }
165 |
166 | public String getSdkVersion()
167 | {
168 | return DJISDKManager.getInstance().getSDKVersion();
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/CameraActivity.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.IntentFilter;
7 | import android.os.Bundle;
8 | import android.os.Handler;
9 | import android.util.Log;
10 | import android.view.View;
11 | import android.widget.ImageButton;
12 | import android.widget.TextView;
13 | import android.widget.Toast;
14 |
15 | import unmannedairlines.dronepan.logic.DJIConnection;
16 | import unmannedairlines.dronepan.logic.PanoramaShoot;
17 | import unmannedairlines.dronepan.logic.SettingsManager;
18 |
19 | public class CameraActivity extends BaseActivity implements View.OnClickListener, PanoramaShoot.Listener {
20 |
21 | private static final String TAG = CameraActivity.class.getName();
22 |
23 | protected BroadcastReceiver broadcastReceiver;
24 |
25 | private ImageButton settingsButton;
26 | private ImageButton panoButton;
27 |
28 | private TextView photosTakenStatus;
29 | private PanoramaShoot panoramaShoot;
30 |
31 | private boolean isPanoramaShootRunning;
32 |
33 | @Override
34 | public void onCreate(Bundle savedInstanceState) {
35 | super.onCreate(savedInstanceState);
36 | setContentView(R.layout.activity_camera);
37 |
38 | panoramaShoot = new PanoramaShoot(DJIConnection.getInstance());
39 |
40 | initUi();
41 | registerForDeviceChanges();
42 | }
43 |
44 | private void initUi() {
45 | panoButton = (ImageButton) findViewById(R.id.panoButton);
46 | panoButton.setOnClickListener(this);
47 |
48 | settingsButton = (ImageButton) findViewById(R.id.btn_settings);
49 | settingsButton.setOnClickListener(this);
50 |
51 | photosTakenStatus = (TextView)findViewById(R.id.photosTakenStatus);
52 | }
53 |
54 | private void registerForDeviceChanges() {
55 | broadcastReceiver = new BroadcastReceiver() {
56 | @Override
57 | public void onReceive(Context context, Intent intent) {
58 | onProductChange();
59 | }
60 | };
61 |
62 | // Register the broadcast receiver for receiving the device connection's changes.
63 | IntentFilter filter = new IntentFilter();
64 | filter.addAction(DJIConnection.FLAG_CONNECTION_CHANGE);
65 | registerReceiver(broadcastReceiver, filter);
66 | }
67 |
68 | @Override
69 | public void onResume() {
70 | Log.e(TAG, "onResume");
71 | super.onResume();
72 | panoramaShoot.setListener(this);
73 | onProductChange();
74 | }
75 |
76 | @Override
77 | protected void onDestroy() {
78 | Log.e(TAG, "onDestroy");
79 | panoramaShoot.setListener(null);
80 | unregisterReceiver(broadcastReceiver);
81 | super.onDestroy();
82 | }
83 |
84 | protected void onProductChange() {
85 |
86 | }
87 |
88 | private void updatePhotoCountUi()
89 | {
90 | runOnUiThread(new Runnable() {
91 | public void run() {
92 | photosTakenStatus.setText(String.format("Photo: %s/%s", panoramaShoot.getNumberOfPhotosTaken(), panoramaShoot.getTotalNumberOfPhotos()));
93 | }
94 | });
95 | }
96 |
97 | public void showToast(final String msg) {
98 | runOnUiThread(new Runnable() {
99 | public void run() {
100 | Toast.makeText(CameraActivity.this, msg, Toast.LENGTH_SHORT).show();
101 | }
102 | });
103 | }
104 |
105 | @Override
106 | public void onClick(View v) {
107 |
108 | switch (v.getId()) {
109 | case R.id.btn_settings:{
110 | Intent intent = new Intent(CameraActivity.this, SettingsActivity.class);
111 | startActivity(intent);
112 | break;
113 | }
114 | case R.id.panoButton: {
115 | startStopPanorama();
116 | break;
117 | }
118 | default:
119 | break;
120 | }
121 | }
122 |
123 | private void startStopPanorama()
124 | {
125 | if (this.panoramaShoot.isMissionRunning()) {
126 | this.panoramaShoot.stop();
127 | Log.i(TAG, "Panorama shoot stopped.");
128 | }
129 | else {
130 | Log.e(TAG, "Setting up panorama shoot ...");
131 | this.panoramaShoot.setup(SettingsManager.getInstance().getSettings(DJIConnection.getInstance().getModelSafely()));
132 | this.panoramaShoot.start();
133 | Log.i(TAG, "Panorama shoot started.");
134 | }
135 | }
136 |
137 | private void stopPanorama() {
138 | this.panoramaShoot.stop();
139 | }
140 |
141 | private void navigateToConnectionActivity() {
142 |
143 | if (DronePanApplication.isRunningOnEmulator()) {
144 | return;
145 | }
146 |
147 | final Handler h = new Handler();
148 |
149 | final Runnable begin = new Runnable() {
150 | @Override
151 | public void run() {
152 | Intent intent = new Intent(CameraActivity.this, ConnectionActivity.class);
153 | startActivity(intent);
154 | }
155 | };
156 |
157 | // Let's delay for 1 second (we show the toast) and then navigate back to connection view.
158 | h.postDelayed(begin, 1000);
159 | }
160 |
161 | @Override
162 | public void onPanoramaShootUpdate() {
163 | this.updatePhotoCountUi();
164 |
165 | if (isPanoramaShootRunning != this.panoramaShoot.isMissionRunning())
166 | {
167 | isPanoramaShootRunning = this.panoramaShoot.isMissionRunning();
168 | if (isPanoramaShootRunning)
169 | {
170 | runOnUiThread(new Runnable() {
171 | public void run() {
172 | panoButton.setImageResource(R.mipmap.ic_stop);
173 | }
174 | });
175 | }
176 | else
177 | {
178 | runOnUiThread(new Runnable() {
179 | public void run() {
180 | panoButton.setImageResource(R.mipmap.ic_start);
181 | }
182 | });
183 | }
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/logic/Settings.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan.logic;
2 |
3 | import android.databinding.BaseObservable;
4 | import android.databinding.Bindable;
5 | import android.view.View;
6 | import android.widget.CompoundButton;
7 | import android.widget.SeekBar;
8 |
9 | import dji.common.product.Model;
10 | import unmannedairlines.dronepan.BR;
11 | import unmannedairlines.dronepan.R;
12 |
13 | public class Settings extends BaseObservable {
14 | private static final String TAG = Settings.class.getName();
15 |
16 | private Model model;
17 |
18 | private int photosPerRow;
19 | private int numberOfRows;
20 | private int numberOfNadirShots;
21 | private int delayBeforeEachShotInMs;
22 | private boolean allowsAboveHorizon;
23 |
24 | private boolean canGimbalYaw;
25 | private boolean useGimbalToYaw;
26 |
27 | private boolean useImperial;
28 | private boolean aebPhotoMode;
29 | private boolean shootRowByRow;
30 |
31 | public Settings(Model model)
32 | {
33 | this.model = model;
34 | setDefaults();
35 | }
36 |
37 | public Model getModel()
38 | {
39 | return model;
40 | }
41 |
42 | @Bindable
43 | public String getModelDisplayName() {
44 | return model.getDisplayName();
45 | }
46 |
47 | @Bindable
48 | public int getNumberOfRows() {
49 | return numberOfRows;
50 | }
51 |
52 | @Bindable
53 | public int getPhotosPerRow() {
54 | return photosPerRow;
55 | }
56 |
57 | @Bindable
58 | public int getNumberOfNadirShots() {
59 | return numberOfNadirShots;
60 | }
61 |
62 | @Bindable
63 | public int getDelayBeforeEachShotInMs() {
64 | return delayBeforeEachShotInMs;
65 | }
66 |
67 | @Bindable
68 | public boolean getAllowsAboveHorizon()
69 | {
70 | return allowsAboveHorizon;
71 | }
72 |
73 | @Bindable
74 | public boolean getUseGimbalToYaw()
75 | {
76 | return useGimbalToYaw;
77 | }
78 |
79 | @Bindable
80 | public boolean getCanGimbalYaw()
81 | {
82 | return canGimbalYaw;
83 | }
84 |
85 | @Bindable
86 | public boolean getUseImperial() {
87 | return useImperial;
88 | }
89 |
90 | @Bindable
91 | public boolean getAebPhotoMode() {
92 | return aebPhotoMode;
93 | }
94 |
95 | @Bindable
96 | public boolean getShootRowByRow() {
97 | return shootRowByRow;
98 | }
99 |
100 | @Bindable
101 | public int getNumberOfPhotos() {
102 | return numberOfRows * photosPerRow + numberOfNadirShots;
103 | }
104 |
105 | @Bindable
106 | public float getYawAngle() {
107 | float yawAngle = 360.0f / getPhotosPerRow();
108 | return yawAngle;
109 | }
110 |
111 | @Bindable
112 | public float getPitchAngle() {
113 | // TODO: adjust max pitch angle based on settings.
114 | //float maxPitchAngle = this.allowsAboveHorizon ? 90.0f : 0.0f;
115 | float maxPitchAngle = 0.0f;
116 | float minPitchAngle = -90.0f;
117 |
118 | float totalPitchAngle = maxPitchAngle - minPitchAngle;
119 | float pitchAngle = totalPitchAngle / getNumberOfRows();
120 | return pitchAngle;
121 | }
122 |
123 | public void setNumberOfRows(int numberOfRows) {
124 | this.numberOfRows = numberOfRows;
125 | notifyPropertyChanged(BR.numberOfRows);
126 | notifyPropertyChanged(BR.numberOfPhotos);
127 | notifyPropertyChanged(BR.pitchAngle);
128 | }
129 |
130 | public void setPhotosPerRow(int photosPerRow) {
131 | this.photosPerRow = photosPerRow;
132 | notifyPropertyChanged(BR.photosPerRow);
133 | notifyPropertyChanged(BR.numberOfPhotos);
134 | notifyPropertyChanged(BR.yawAngle);
135 | }
136 |
137 | public void setNumberOfNadirShots(int numberOfNadirShots) {
138 | this.numberOfNadirShots = numberOfNadirShots;
139 | notifyPropertyChanged(BR.numberOfNadirShots);
140 | notifyPropertyChanged(BR.numberOfPhotos);
141 | }
142 |
143 | public void setDelayBeforeEachShotInMs(int delayBeforeEachShotInMs) {
144 | this.delayBeforeEachShotInMs = delayBeforeEachShotInMs;
145 | notifyPropertyChanged(BR.delayBeforeEachShotInMs);
146 | }
147 |
148 | public void setAllowsAboveHorizon(boolean allowsAboveHorizon) {
149 | this.allowsAboveHorizon = allowsAboveHorizon;
150 | notifyPropertyChanged(BR.allowsAboveHorizon);
151 | }
152 |
153 | public void setUseGimbalToYaw(boolean useGimbalToYaw) {
154 | this.useGimbalToYaw = useGimbalToYaw;
155 | notifyPropertyChanged(BR.useGimbalToYaw);
156 | }
157 |
158 | public void setCanGimbalYaw(boolean canGimbalYaw) {
159 | this.canGimbalYaw = canGimbalYaw;
160 | notifyPropertyChanged(BR.canGimbalYaw);
161 | }
162 |
163 | public void setAebPhotoMode(boolean aebPhotoMode) {
164 | this.aebPhotoMode = aebPhotoMode;
165 | notifyPropertyChanged(BR.aebPhotoMode);
166 | }
167 |
168 | public void setShootRowByRow(boolean shootRowByRow) {
169 | this.shootRowByRow = shootRowByRow;
170 | notifyPropertyChanged(BR.shootRowByRow);
171 | }
172 |
173 | public void setUseImperial(boolean useImperial) {
174 | this.useImperial = useImperial;
175 | notifyPropertyChanged(BR.useImperial);
176 | }
177 |
178 | public void valueChanged(SeekBar s, int progressValue, boolean fromUser) {
179 | if (fromUser)
180 | {
181 | switch (s.getId())
182 | {
183 | case R.id.picturesPerRowSeekBar:
184 | setPhotosPerRow(progressValue);
185 | break;
186 |
187 | case R.id.numberOfRowsSeekBar:
188 | setNumberOfRows(progressValue);
189 | break;
190 |
191 | case R.id.numberOfNadirShotsSeekBar:
192 | setNumberOfNadirShots(progressValue);
193 | break;
194 |
195 | case R.id.delayBeforeEachShotSeekBar:
196 | setDelayBeforeEachShotInMs(progressValue / 10000);
197 | break;
198 | }
199 | }
200 | }
201 |
202 | public void checkedChanged(CompoundButton buttonView, boolean isChecked) {
203 | switch (buttonView.getId()) {
204 | case R.id.imperialMetricSwitch:
205 | setUseImperial(isChecked);
206 | break;
207 |
208 | case R.id.aebSwitch:
209 | setAebPhotoMode(isChecked);
210 | break;
211 |
212 | case R.id.shootBySwitch:
213 | setShootRowByRow(isChecked);
214 | break;
215 | }
216 | }
217 |
218 | public void onClicked(View v) {
219 | switch (v.getId()) {
220 | case R.id.saveSettingsButton:
221 | saveToDisk();
222 | break;
223 |
224 | case R.id.cancelSettingsButton:
225 | revertSettings();
226 | break;
227 | }
228 | }
229 |
230 | public void saveToDisk() {
231 | SettingsFactory factory = new SettingsFactory(this);
232 | factory.saveUserSettings();
233 | }
234 |
235 | public void revertSettings() {
236 | SettingsFactory factory = new SettingsFactory(this);
237 | factory.revertToDefaultSettings();
238 | }
239 |
240 | private void setDefaults() {
241 | photosPerRow = 1;
242 | numberOfRows = 1;
243 | numberOfNadirShots = 0;
244 | delayBeforeEachShotInMs = 0;
245 | allowsAboveHorizon = false;
246 | canGimbalYaw = false;
247 | useGimbalToYaw = false;
248 | useImperial = false;
249 | aebPhotoMode = false;
250 | shootRowByRow = false;
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/logic/PanoramaShoot.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan.logic;
2 |
3 | import android.support.annotation.Nullable;
4 | import android.util.Log;
5 |
6 | import dji.common.error.DJIError;
7 | import dji.common.gimbal.Attitude;
8 | import dji.common.gimbal.Rotation;
9 | import dji.sdk.flightcontroller.FlightController;
10 | import dji.sdk.mission.MissionControl;
11 | import dji.sdk.mission.timeline.TimelineElement;
12 | import dji.sdk.mission.timeline.TimelineEvent;
13 | import dji.sdk.mission.timeline.actions.GimbalAttitudeAction;
14 | import dji.sdk.mission.timeline.actions.ShootPhotoAction;
15 | import unmannedairlines.dronepan.mission.CustomAircraftYawAction;
16 | import unmannedairlines.dronepan.mission.DelayAction;
17 | import unmannedairlines.dronepan.mission.WaitForCameraReadyAction;
18 | import unmannedairlines.dronepan.mission.helpers.CameraSystemStateController;
19 |
20 | public class PanoramaShoot implements MissionControl.Listener {
21 | private static final String TAG = PanoramaShoot.class.getName();
22 |
23 | private FlightController flightController;
24 | private MissionControl missionControl;
25 | private Settings settings;
26 | private CameraSystemStateController cameraStateController;
27 |
28 | private int numberOfPhotosTaken;
29 | private int totalNumberOfPhotos;
30 |
31 | public interface Listener {
32 | void onPanoramaShootUpdate();
33 | }
34 |
35 | private Listener listener;
36 |
37 | public PanoramaShoot(DJIConnection djiConnection) {
38 | this.missionControl = MissionControl.getInstance();
39 | this.missionControl.addListener(this);
40 |
41 | this.cameraStateController = new CameraSystemStateController(DJIConnection.getInstance().getCamera());
42 | }
43 |
44 | public void setListener(Listener listener) {
45 | this.listener = listener;
46 | this.notifyListener();
47 | }
48 |
49 | public int getNumberOfPhotosTaken() {
50 | return this.numberOfPhotosTaken;
51 | }
52 |
53 | public int getTotalNumberOfPhotos() {
54 | return this.totalNumberOfPhotos;
55 | }
56 |
57 | public void setup(Settings settings) {
58 | this.numberOfPhotosTaken = 0;
59 | this.totalNumberOfPhotos = 0;
60 |
61 | this.missionControl.unscheduleEverything();
62 |
63 | this.settings = settings;
64 |
65 | if (this.settings.getShootRowByRow()) {
66 | this.setupRowByRow();
67 | }
68 | else {
69 | this.setupColumnByColumn();
70 | }
71 |
72 | this.setupNadirShots();
73 | this.notifyListener();
74 | }
75 |
76 | private void setupRowByRow() {
77 | int photosPerRow = settings.getPhotosPerRow();
78 | int numberOfRows = settings.getNumberOfRows();
79 | boolean useGimbalYaw = settings.getUseGimbalToYaw();
80 |
81 | for (int r = 0; r < numberOfRows; r++) {
82 | this.addGimbalPitchAction(settings.getPitchAngle() * -r);
83 |
84 | for (int i = 0; i < photosPerRow; i++) {
85 | this.addPhotoShootAction();
86 |
87 | if (useGimbalYaw) {
88 | this.addGimbalYawAction(settings.getYawAngle() * (i+1));
89 | }
90 | else {
91 | this.addAircraftYawAction(settings.getYawAngle());
92 | }
93 | }
94 | }
95 | }
96 |
97 | private void setupColumnByColumn() {
98 | int photosPerRow = settings.getPhotosPerRow();
99 | int numberOfRows = settings.getNumberOfRows();
100 | boolean useGimbalYaw = settings.getUseGimbalToYaw();
101 |
102 | for (int c = 0; c < photosPerRow; c++) {
103 | for (int i = 0; i < numberOfRows; i++) {
104 | this.addGimbalPitchAction(settings.getPitchAngle() * -i);
105 | this.addPhotoShootAction();
106 | }
107 |
108 | if (useGimbalYaw) {
109 | this.addGimbalYawAction(settings.getYawAngle() * (c + 1));
110 | }
111 | else {
112 | this.addAircraftYawAction(settings.getYawAngle());
113 | }
114 | }
115 | }
116 |
117 | private void setupNadirShots() {
118 | int numberOfNadirShots = settings.getNumberOfNadirShots();
119 | boolean useGimbalYaw = settings.getUseGimbalToYaw();
120 |
121 | if (numberOfNadirShots == 0) {
122 | return;
123 | }
124 |
125 | float nadirAngle = 360.0f / numberOfNadirShots;
126 |
127 | this.addGimbalPitchAction(-90.0f);
128 | this.addPhotoShootAction();
129 |
130 | for (int i = 1; i < numberOfNadirShots; i++) {
131 | if (useGimbalYaw) {
132 | this.addGimbalYawAction(nadirAngle * i);
133 | }
134 | else {
135 | this.addAircraftYawAction(nadirAngle);
136 | }
137 |
138 | this.addPhotoShootAction();
139 | }
140 | }
141 |
142 | private void addAircraftYawAction(float relativeYaw) {
143 | CustomAircraftYawAction aircraftYawAction = new CustomAircraftYawAction(relativeYaw, 20);
144 | this.missionControl.scheduleElement(aircraftYawAction);
145 | }
146 |
147 | private void addGimbalYawAction(float absoluteYaw) {
148 | Attitude attitude = new Attitude(Rotation.NO_ROTATION, Rotation.NO_ROTATION, absoluteYaw);
149 | GimbalAttitudeAction gimbalAttitudeAction = new GimbalAttitudeAction(attitude);
150 | this.missionControl.scheduleElement(gimbalAttitudeAction);
151 | }
152 |
153 | private void addGimbalPitchAction(float pitch) {
154 | Attitude attitude = new Attitude(pitch, Rotation.NO_ROTATION, Rotation.NO_ROTATION);
155 | GimbalAttitudeAction gimbalAttitudeAction = new GimbalAttitudeAction(attitude);
156 | this.missionControl.scheduleElement(gimbalAttitudeAction);
157 | }
158 |
159 | private void addPhotoShootAction() {
160 | WaitForCameraReadyAction waitForCameraReadyAction = new WaitForCameraReadyAction(this.cameraStateController);
161 | this.missionControl.scheduleElement(waitForCameraReadyAction);
162 |
163 | int delayInMilliseconds = this.settings.getDelayBeforeEachShotInMs();
164 | if (delayInMilliseconds > 0) {
165 | DelayAction delayAction = new DelayAction(delayInMilliseconds);
166 | this.missionControl.scheduleElement(delayAction);
167 | }
168 |
169 | ShootPhotoAction photoAction = new ShootPhotoAction();
170 | this.missionControl.scheduleElement(photoAction);
171 |
172 | this.totalNumberOfPhotos++;
173 | }
174 |
175 | public boolean isMissionRunning() {
176 | return missionControl.isTimelineRunning();
177 | }
178 |
179 | public void start() {
180 | this.missionControl.startTimeline();
181 | }
182 |
183 | public void stop() {
184 | if (this.missionControl.isTimelineRunning()) {
185 | this.missionControl.stopTimeline();
186 | }
187 | }
188 |
189 | @Override
190 | public void onEvent(@Nullable TimelineElement element, TimelineEvent event, @Nullable DJIError error) {
191 | if (error != null) {
192 | Log.e(TAG, error.toString());
193 | }
194 |
195 | String message = "";
196 | if (event != null) {
197 | message += "Mission Event: " + event.toString();
198 | }
199 | if (element != null) {
200 | message += " Element: " + element.toString();
201 | }
202 |
203 | Log.i(TAG, message);
204 |
205 | if (event == TimelineEvent.STARTED ||
206 | event == TimelineEvent.FINISHED ||
207 | event == TimelineEvent.STOPPED ||
208 | event == TimelineEvent.STOP_ERROR) {
209 | this.notifyListener();
210 | }
211 |
212 | if (element instanceof ShootPhotoAction && event == TimelineEvent.ELEMENT_FINISHED) {
213 | this.numberOfPhotosTaken++;
214 | Log.e(TAG, "Picture taken: " + this.numberOfPhotosTaken);
215 | this.notifyListener();
216 | }
217 | }
218 |
219 | private void notifyListener() {
220 | if (this.listener != null) {
221 | this.listener.onPanoramaShootUpdate();
222 | }
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/mission/CustomAircraftYawAction.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan.mission;
2 |
3 | import android.util.Log;
4 |
5 | import java.lang.ref.WeakReference;
6 |
7 | import dji.common.error.DJIError;
8 | import dji.common.error.DJIMissionError;
9 | import dji.common.error.DJISDKError;
10 | import dji.common.flightcontroller.virtualstick.FlightControlData;
11 | import dji.common.flightcontroller.virtualstick.RollPitchControlMode;
12 | import dji.common.flightcontroller.virtualstick.VerticalControlMode;
13 | import dji.common.flightcontroller.virtualstick.YawControlMode;
14 | import dji.common.util.CommonCallbacks;
15 | import dji.midware.data.model.P3.DataOsdGetPushCommon;
16 | import dji.sdk.base.BaseProduct;
17 | import dji.sdk.flightcontroller.FlightController;
18 | import dji.sdk.mission.MissionControl;
19 | import dji.sdk.mission.error.AircraftYawActionError;
20 | import dji.sdk.mission.timeline.actions.MissionAction;
21 | import dji.sdk.products.Aircraft;
22 | import dji.sdk.sdkmanager.DJISDKManager;
23 | import dji.thirdparty.eventbus.EventBus;
24 | import unmannedairlines.dronepan.mission.helpers.CameraSystemStateController;
25 |
26 | public class CustomAircraftYawAction extends MissionAction {
27 | private static final String TAG = CustomAircraftYawAction.class.getName();
28 | private static final double AllowedErrorInDegrees = 0.5;
29 |
30 |
31 | private float angle;
32 | private float targetAngle;
33 |
34 | private boolean targetAngleCalulationNeeded;
35 |
36 | private float currentAngle;
37 | private float lastRotateAngle;
38 | private boolean isStop;
39 | private int stopCounter;
40 |
41 | private RollPitchControlMode oldRollPitchControlMode;
42 | private YawControlMode oldYawControlMode;
43 | private VerticalControlMode oldVerticalControlMode;
44 |
45 | private FlightController flightController;
46 |
47 | private WeakReference target;
48 | private InnerEventBus innerEventBus;
49 |
50 | public CustomAircraftYawAction(float relativeAngle, float angularVelocity) {
51 | this.angle = relativeAngle;
52 | this.targetAngle = relativeAngle;
53 | }
54 |
55 | private FlightController getFlightController() {
56 | BaseProduct product = DJISDKManager.getInstance().getProduct();
57 | return product != null && product instanceof Aircraft ?((Aircraft)product).getFlightController() : null;
58 | }
59 |
60 | private void startExecution() {
61 | Log.i(TAG, "startExecution()");
62 | this.targetAngleCalulationNeeded = true;
63 |
64 | this.flightController = this.getFlightController();
65 | if(this.flightController == null) {
66 | MissionControl.getInstance().onStartWithError(this, DJISDKError.DEVICE_NOT_FOUND);
67 | }
68 | else {
69 | this.target = new WeakReference(this);
70 | this.backupControlMode();
71 | this.flightController.setVirtualStickAdvancedModeEnabled(true);
72 | this.flightController.setVirtualStickModeEnabled(true, new CommonCallbacks.CompletionCallback() {
73 | @Override
74 | public void onResult(DJIError djiError) {
75 | if (djiError == null) {
76 | (CustomAircraftYawAction.this.target.get()).startRun();
77 | }
78 | else {
79 | MissionControl.getInstance().onStartWithError((CustomAircraftYawAction.this.target.get()), djiError);
80 | }
81 | }
82 | });
83 | }
84 | }
85 |
86 | private void rotate() {
87 | Log.i(TAG, "rotate() to target=" + this.targetAngle);
88 |
89 | FlightControlData controlData = new FlightControlData(0.0F, 0.0F, 0.0F, 0.0F);
90 | this.flightController.setRollPitchControlMode(RollPitchControlMode.VELOCITY);
91 | this.flightController.setVerticalControlMode(VerticalControlMode.VELOCITY);
92 | this.flightController.setYawControlMode(YawControlMode.ANGLE);
93 | controlData.setYaw(this.targetAngle);
94 |
95 | this.flightController.sendVirtualStickFlightControlData(controlData, new CommonCallbacks.CompletionCallback() {
96 | public void onResult(DJIError error) {
97 | if(error != null && CustomAircraftYawAction.this.target.get() != null) {
98 | (CustomAircraftYawAction.this.target.get()).finishRun(error);
99 | }
100 | }
101 | });
102 | }
103 |
104 | private void stopRotate() {
105 | if(this.flightController.isVirtualStickControlModeAvailable()) {
106 | FlightControlData controlData = new FlightControlData(0.0F, 0.0F, 0.0F, 0.0F);
107 | this.flightController.setRollPitchControlMode(RollPitchControlMode.VELOCITY);
108 | this.flightController.setYawControlMode(YawControlMode.ANGULAR_VELOCITY);
109 | this.flightController.sendVirtualStickFlightControlData(controlData, null);
110 | }
111 |
112 | this.restoreControlMode();
113 | this.finishRun(null);
114 | }
115 |
116 | protected void startListen() {
117 | this.innerEventBus = new InnerEventBus();
118 | }
119 |
120 | protected void stopListen() {
121 | this.innerEventBus.destroy();
122 | }
123 |
124 | private void backupControlMode() {
125 | this.oldRollPitchControlMode = this.getFlightController().getRollPitchControlMode();
126 | this.oldYawControlMode = this.getFlightController().getYawControlMode();
127 | this.oldVerticalControlMode = this.getFlightController().getVerticalControlMode();
128 | }
129 |
130 | private void restoreControlMode() {
131 | this.getFlightController().setRollPitchControlMode(this.oldRollPitchControlMode);
132 | this.getFlightController().setYawControlMode(this.oldYawControlMode);
133 | this.getFlightController().setVerticalControlMode(this.oldVerticalControlMode);
134 | }
135 |
136 | protected void onReceivedOSDData(DataOsdGetPushCommon osdData) {
137 | if(this.isRunning()) {
138 | if(osdData.groundOrSky() != 2) {
139 | this.finishRun(DJIMissionError.AIRCRAFT_NOT_IN_THE_AIR);
140 | return;
141 | }
142 |
143 | float currentYaw = (float)osdData.getYaw() * 0.1F;
144 | Log.i(TAG, "currentYaw= " + currentYaw);
145 |
146 | this.calculateTargetAngleIfNeeded(currentYaw);
147 |
148 | if (this.checkYawAngle(currentYaw)) {
149 | this.stopRotate();
150 | }
151 | else {
152 | this.rotate();
153 | }
154 | }
155 | }
156 |
157 | private void calculateTargetAngleIfNeeded(float currentYaw) {
158 | if (!this.targetAngleCalulationNeeded) {
159 | return;
160 | }
161 |
162 | this.targetAngleCalulationNeeded = false;
163 |
164 | this.lastRotateAngle = currentYaw;
165 | this.targetAngle = this.angle + this.lastRotateAngle;
166 |
167 | if (this.targetAngle > 180.0) {
168 | this.targetAngle -= 360.0;
169 | }
170 |
171 | if (this.targetAngle < -180.0) {
172 | this.targetAngle += 360.0;
173 | }
174 | }
175 |
176 | private boolean checkYawAngle(float currentAngle) {
177 | double distance = Math.abs((double)currentAngle - (double)this.targetAngle);
178 | Log.i(TAG, "checkYawAngle, distance=" + distance);
179 |
180 | if (distance <= AllowedErrorInDegrees || Math.abs(distance - 360.0) <= AllowedErrorInDegrees) {
181 | return true;
182 | }
183 |
184 | return false;
185 | }
186 |
187 | public void run() {
188 | this.startExecution();
189 | }
190 |
191 | public boolean isPausable() {
192 | return false;
193 | }
194 |
195 | public void stop() {
196 | this.finishRun(null);
197 | }
198 |
199 | public DJIError checkValidity() {
200 | if(this.angle > 180.0F || this.angle < -180.0F) {
201 | return AircraftYawActionError.INVALID_ANGLE_VALUE;
202 | }
203 |
204 | return null;
205 | }
206 |
207 | private class InnerEventBus {
208 | public InnerEventBus() {
209 | EventBus.getDefault().register(this);
210 | }
211 |
212 | public void destroy() {
213 | EventBus.getDefault().unregister(this);
214 | }
215 |
216 | public void onEventBackgroundThread(DataOsdGetPushCommon var1) {
217 | CustomAircraftYawAction action = CustomAircraftYawAction.this.target.get();
218 | if (action != null) {
219 | action.onReceivedOSDData(var1);
220 | }
221 | }
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_camera.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
21 |
22 |
25 |
26 |
27 |
33 |
34 |
38 |
39 |
42 |
43 |
46 |
47 |
50 |
51 |
54 |
55 |
58 |
59 |
62 |
63 |
66 |
67 |
71 |
72 |
73 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
169 |
170 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
199 |
200 |
206 |
207 |
214 |
215 |
224 |
225 |
226 |
--------------------------------------------------------------------------------
/app/src/main/java/unmannedairlines/dronepan/logic/SettingsFactory.java:
--------------------------------------------------------------------------------
1 | package unmannedairlines.dronepan.logic;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 | import android.widget.Toast;
6 |
7 | import org.json.JSONException;
8 | import org.json.JSONObject;
9 |
10 | import java.io.File;
11 | import java.io.FileInputStream;
12 | import java.io.FileNotFoundException;
13 | import java.io.FileOutputStream;
14 | import java.io.InputStream;
15 | import java.util.ArrayList;
16 |
17 | import dji.common.product.Model;
18 | import unmannedairlines.dronepan.DronePanApplication;
19 | import unmannedairlines.dronepan.R;
20 |
21 | public class SettingsFactory {
22 | private static final String TAG = SettingsFactory.class.getName();
23 |
24 | private static JSONObject defaults;
25 | private static JSONObject defaultSettings;
26 |
27 | public static void loadDefaults() {
28 | if (defaults != null) {
29 | return;
30 | }
31 |
32 | try {
33 | Context context = DronePanApplication.getContext();
34 | InputStream stream = context.getResources().openRawResource(R.raw.models);
35 | defaults = StringReader.ReadJson(stream);
36 | defaultSettings = defaults.getJSONObject("default");
37 | }
38 | catch (JSONException e) {
39 | Log.e(TAG, "Can't load default settings.", e);
40 | }
41 | }
42 |
43 | Settings settings;
44 |
45 | JSONObject modelSettings;
46 | JSONObject userSettings;
47 |
48 | private Model model;
49 | private String modelName;
50 | private boolean useUserSettings;
51 |
52 | private abstract class SetterGetter {
53 | public String setting;
54 | public boolean saveAsUserSetting;
55 |
56 | public SetterGetter(String setting) {
57 | this.setting = setting;
58 | this.saveAsUserSetting = true;
59 | }
60 |
61 | public SetterGetter(String setting, boolean saveAsUserSetting) {
62 | this.saveAsUserSetting = true;
63 | }
64 |
65 | abstract void set(Object value);
66 | abstract Object get();
67 | }
68 |
69 | ArrayList setterGetters = new ArrayList();
70 |
71 | public SettingsFactory(Model model)
72 | {
73 | this.settings = new Settings(model);
74 | this.initialize();
75 | }
76 |
77 | public SettingsFactory(Settings settings) {
78 | this.settings = settings;
79 | this.initialize();
80 | }
81 |
82 | private void initialize() {
83 | loadDefaults();
84 | this.createSetterGetters();
85 |
86 | this.model = this.settings.getModel();
87 | this.modelName = this.model.name();
88 |
89 | try {
90 | this.modelSettings = defaults.getJSONObject(modelName);
91 | }
92 | catch (JSONException e) {
93 | Log.e(TAG, "Can't load model specific settings.", e);
94 | }
95 | }
96 |
97 | private void createSetterGetters() {
98 | setterGetters.add(new SetterGetter(SettingsConstants.PhotosPerRow) {
99 | @Override
100 | void set(Object value) { settings.setPhotosPerRow((int)value); }
101 |
102 | @Override
103 | Object get() { return settings.getPhotosPerRow(); }
104 | });
105 | setterGetters.add(new SetterGetter(SettingsConstants.NumberOfRows) {
106 | @Override
107 | void set(Object value) { settings.setNumberOfRows((int)value); }
108 |
109 | @Override
110 | Object get() { return settings.getNumberOfRows(); }
111 | });
112 | setterGetters.add(new SetterGetter(SettingsConstants.NumberOfNadirShots) {
113 | @Override
114 | void set(Object value) { settings.setNumberOfNadirShots((int)value); }
115 |
116 | @Override
117 | Object get() { return settings.getNumberOfNadirShots(); }
118 | });
119 | setterGetters.add(new SetterGetter(SettingsConstants.DelayBeforeEachShot) {
120 | @Override
121 | void set(Object value) { settings.setDelayBeforeEachShotInMs((int)value); }
122 |
123 | @Override
124 | Object get() { return settings.getDelayBeforeEachShotInMs(); }
125 | });
126 | setterGetters.add(new SetterGetter(SettingsConstants.AllowsAboveHorizon) {
127 | @Override
128 | void set(Object value) { settings.setAllowsAboveHorizon((boolean)value); }
129 |
130 | @Override
131 | Object get() { return settings.getAllowsAboveHorizon(); }
132 | });
133 | setterGetters.add(new SetterGetter(SettingsConstants.UseImperial) {
134 | @Override
135 | void set(Object value) { settings.setUseImperial((boolean)value); }
136 |
137 | @Override
138 | Object get() { return settings.getUseImperial(); }
139 | });
140 | setterGetters.add(new SetterGetter(SettingsConstants.AebPhotoMode) {
141 | @Override
142 | void set(Object value) { settings.setAebPhotoMode((boolean)value); }
143 |
144 | @Override
145 | Object get() { return settings.getAebPhotoMode(); }
146 | });
147 | setterGetters.add(new SetterGetter(SettingsConstants.ShootRowByRow) {
148 | @Override
149 | void set(Object value) { settings.setShootRowByRow((boolean)value); }
150 |
151 | @Override
152 | Object get() { return settings.getShootRowByRow(); }
153 | });
154 | setterGetters.add(new SetterGetter(SettingsConstants.CanGimbalYaw) {
155 | @Override
156 | void set(Object value) { settings.setCanGimbalYaw((boolean)value); }
157 |
158 | @Override
159 | Object get() { return settings.getCanGimbalYaw(); }
160 | });
161 | setterGetters.add(new SetterGetter(SettingsConstants.UseGimbalToYaw) {
162 | @Override
163 | void set(Object value) { settings.setUseGimbalToYaw((boolean)value); }
164 |
165 | @Override
166 | Object get() { return settings.getUseGimbalToYaw(); }
167 | });
168 | }
169 |
170 | public Settings create() {
171 | this.userSettings = this.readUserSettings();
172 | this.useUserSettings = true;
173 |
174 | for (SetterGetter setterGetter : setterGetters) {
175 | this.loadAndSetValue(setterGetter);
176 | }
177 |
178 | return settings;
179 | }
180 |
181 | private void loadAndSetValue(SetterGetter setterGetter) {
182 | Object value = setterGetter.get();
183 |
184 | try {
185 | if (this.defaultSettings.has(setterGetter.setting)) {
186 | value = this.defaultSettings.get(setterGetter.setting);
187 | }
188 | }
189 | catch (JSONException e) {}
190 |
191 | try {
192 | if (this.modelSettings != null) {
193 | if (this.modelSettings.has(setterGetter.setting)) {
194 | value = this.modelSettings.get(setterGetter.setting);
195 | }
196 | }
197 | }
198 | catch (JSONException e) {}
199 |
200 | if (userSettings != null && this.useUserSettings) {
201 | try {
202 | if (this.userSettings.has(setterGetter.setting)) {
203 | value = this.userSettings.get(setterGetter.setting);
204 | }
205 | } catch (JSONException e) { }
206 | }
207 |
208 | try {
209 | setterGetter.set(value);
210 | }
211 | catch (ClassCastException e) {
212 | Log.e(TAG, "Can't set " + setterGetter.setting, e);
213 | }
214 | }
215 |
216 | public void saveUserSettings() {
217 | this.useUserSettings = false;
218 |
219 | try {
220 | JSONObject json = new JSONObject();
221 |
222 | for (SetterGetter setterGetter : this.setterGetters) {
223 | if (!setterGetter.saveAsUserSetting) {
224 | continue;
225 | }
226 |
227 | json.put(setterGetter.setting, setterGetter.get());
228 | }
229 |
230 | this.saveToUserSettingsFile(json.toString());
231 | }
232 | catch (JSONException e) {
233 | Log.e(TAG, "Could not create JSON.", e);
234 | }
235 | }
236 |
237 | public void revertToDefaultSettings() {
238 | this.useUserSettings = false;
239 |
240 | for (SetterGetter setterGetter : setterGetters) {
241 | this.loadAndSetValue(setterGetter);
242 | }
243 | }
244 |
245 | private File getUserSettingsFile() {
246 | String filename = this.model.name() + ".settings";
247 | File file = new File(DronePanApplication.getContext().getFilesDir(), filename);
248 | return file;
249 | }
250 |
251 | private JSONObject readUserSettings() {
252 | File file = getUserSettingsFile();
253 | if (file.exists()) {
254 | try {
255 | FileInputStream inputStream = DronePanApplication.getContext().openFileInput(file.getName());
256 | return StringReader.ReadJson(inputStream);
257 | }
258 | catch (FileNotFoundException e) {
259 | Log.e(TAG, "Could not open file.", e);
260 | }
261 | catch (JSONException e) {
262 | Log.e(TAG, "Could not parse JSON.", e);
263 | }
264 | }
265 |
266 | return null;
267 | }
268 |
269 | private void saveToUserSettingsFile(String json) {
270 | try {
271 | File file = getUserSettingsFile();
272 | FileOutputStream outputStream = DronePanApplication.getContext().openFileOutput(file.getName(), Context.MODE_PRIVATE);
273 | outputStream.write(json.getBytes());
274 | outputStream.close();
275 |
276 | Toast.makeText(DronePanApplication.getContext(), "Settings saved successfully.", Toast.LENGTH_LONG).show();
277 | }
278 | catch (Exception e) {
279 | Toast.makeText(DronePanApplication.getContext(), "Could not save settings.", Toast.LENGTH_LONG).show();
280 | Log.e(TAG, "Saving to JSON failed.", e);
281 | }
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | generateDebugSources
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
19 |
20 |
28 |
29 |
36 |
37 |
43 |
44 |
50 |
51 |
56 |
57 |
61 |
62 |
68 |
69 |
73 |
74 |
80 |
81 |
85 |
86 |
87 |
88 |
89 |
96 |
97 |
105 |
106 |
115 |
116 |
117 |
118 |
124 |
125 |
130 |
131 |
132 |
133 |
137 |
138 |
143 |
144 |
148 |
149 |
150 |
151 |
160 |
161 |
162 |
163 |
167 |
168 |
173 |
174 |
178 |
179 |
180 |
181 |
190 |
191 |
192 |
193 |
197 |
198 |
203 |
204 |
208 |
209 |
210 |
211 |
220 |
221 |
222 |
223 |
227 |
228 |
233 |
234 |
238 |
239 |
240 |
241 |
250 |
251 |
255 |
256 |
262 |
263 |
268 |
269 |
270 |
275 |
276 |
281 |
282 |
283 |
284 |
291 |
292 |
293 |
294 |
298 |
299 |
305 |
306 |
311 |
312 |
317 |
318 |
323 |
324 |
325 |
326 |
333 |
334 |
335 |
339 |
340 |
346 |
347 |
352 |
353 |
358 |
359 |
364 |
365 |
366 |
367 |
374 |
375 |
376 |
381 |
382 |
388 |
389 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
409 |
410 |
411 |
412 |
--------------------------------------------------------------------------------