first commit

This commit is contained in:
gunther82 2023-03-16 16:01:08 +01:00
commit ccafb0a309
237 changed files with 6079 additions and 0 deletions

0
README.md Normal file
View File

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

172
app/build.gradle Normal file
View File

@ -0,0 +1,172 @@
/*
* Copyright (c) 2018-2020 DJI
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
lintOptions {
abortOnError = false
}
compileSdkVersion 31
buildToolsVersion "30.0.3"
useLibrary 'org.apache.http.legacy'
defaultConfig {
applicationId "com.dji.ux.beta.sample"
minSdkVersion 23
targetSdkVersion 31
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
ndk {
// On x86 devices that run Android API 23 or above, if the application is targeted with API 23 or
// above, FFmpeg lib might lead to runtime crashes or warnings.
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
/*
dexOptions {
javaMaxHeapSize "3g"
}
*/
lintOptions{
abortOnError false
}
packagingOptions{
doNotStrip "*/*/libdjivideo.so"
doNotStrip "*/*/libSDKRelativeJNI.so"
doNotStrip "*/*/libFlyForbid.so"
doNotStrip "*/*/libduml_vision_bokeh.so"
doNotStrip "*/*/libyuv2.so"
doNotStrip "*/*/libGroudStation.so"
doNotStrip "*/*/libFRCorkscrew.so"
doNotStrip "*/*/libUpgradeVerify.so"
doNotStrip "*/*/libFR.so"
doNotStrip "*/*/libDJIFlySafeCore.so"
doNotStrip "*/*/libdjifs_jni.so"
doNotStrip "*/*/libsfjni.so"
doNotStrip "*/*/libDJICommonJNI.so"
doNotStrip "*/*/libDJICSDKCommon.so"
doNotStrip "*/*/libDJIUpgradeCore.so"
doNotStrip "*/*/libDJIUpgradeJNI.so"
doNotStrip "*/*/libDJIWaypointV2Core.so"
doNotStrip "*/*/libDJIMOP.so"
doNotStrip "*/*/libDJISDKLOGJNI.so"
pickFirst 'lib/*/libstlport_shared.so'
pickFirst 'lib/*/libRoadLineRebuildAPI.so'
pickFirst 'lib/*/libGNaviUtils.so'
pickFirst 'lib/*/libGNaviMapex.so'
pickFirst 'lib/*/libGNaviData.so'
pickFirst 'lib/*/libGNaviMap.so'
pickFirst 'lib/*/libGNaviSearch.so'
exclude 'META-INF/proguard/okhttp3.pro'
exclude 'META-INF/rxjava.properties'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation ('com.dji:dji-sdk:4.16.1', {
/**
* Comment the "library-anti-distortion" if your app needs Anti Distortion for Mavic 2 Pro
* com.dji:dji-sdk:4.16
* and Mavic 2 Zoom.
* Comment the "fly-safe-database" if you don't need a smaller apk for release. When
* uncommented, we will download it when DJISDKManager.getInstance().registerApp
* is called, and it won't register success without fly-safe-database.
* Both will greatly reduce the size of the APK.
*/
exclude module: 'library-anti-distortion'
exclude module: 'fly-safe-database'
/**
* Uncomment the following line to exclude Amap from the app.
* Note that Google Play Store does not allow APKs that include this library.
*/
// exclude group: 'com.amap.api'
})
implementation 'androidx.test.ext:junit:1.1.3'
compileOnly ('com.dji:dji-sdk-provided:4.16.1')
//UXSDK MOBILE HERE
implementation 'com.github.dji-sdk:Mobile-UXSDK-Beta-Android:v0.5.1'
// UXSDK dependencies
implementation 'androidx.annotation:annotation:1.3.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.core:core:1.7.0'
implementation "androidx.core:core-ktx:1.7.0"
// UXSDK map dependencies: Only include dependencies for map providers that are used in your app
// Amap: Do not include if publishing to Google Play Store
//implementation 'com.amap.api:3dmap:6.9.2'
//implementation 'com.amap.api:search:6.9.2'
//implementation 'com.amap.api:location:4.7.0'
// HERE maps
//implementation files('libs/HERE-sdk-3.15.0.aar')
// Mapbox
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:9.2.0'
// Google maps
implementation 'com.google.android.gms:play-services-base:18.0.1'
implementation 'com.google.android.gms:play-services-location:19.0.1'
implementation 'com.google.android.gms:play-services-maps:18.0.2'
implementation 'com.google.android.gms:play-services-places:17.0.0'
implementation 'com.google.maps.android:android-maps-utils:1.2.1'
// SDK dependencies
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// required to avoid crash on Android 12 API 31
implementation 'androidx.work:work-runtime-ktx:2.7.1'
// Sample app dependencies
implementation 'com.jakewharton:butterknife:10.0.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0'
implementation "com.ncorti:slidetoact:0.9.0"
implementation "com.github.emrekose26:RecordButton:1.2.4"
implementation 'com.google.android.material:material:1.5.0'
api 'io.reactivex.rxjava3:rxandroid:3.0.0'
api 'io.reactivex.rxjava3:rxjava:3.0.0'
}
repositories {
mavenLocal()
}

21
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,26 @@
package com.dji.ux.beta.sample;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.dji.ux.beta.sample", appContext.getPackageName());
}
}

View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.dji.ux.beta.sample">
<!-- DJI SDK needs these permissions -->
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Needed only if your app looks for Bluetooth devices.
If your app doesn't use Bluetooth scan results to derive physical
location information, you can strongly assert that your app
doesn't derive physical location. -->
<!-- <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
Needed only if your app makes the device discoverable to Bluetooth
devices.
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
Needed only if your app communicates with already-paired Bluetooth
devices.
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
-->
<!--bibo01 : hardware option-->
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.accessory"
android:required="true" />
<application
android:name="com.dji.ux.beta.sample.SampleApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
tools:replace="android:label"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:extractNativeLibs="true"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<!-- DJI SDK -->
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
<uses-library android:name="com.android.future.usb.accessory" />
<meta-data
android:name="com.dji.sdk.API_KEY"
android:value="9c5530eda0ad04a7e4693dcc" />
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/maps_api_key"/>
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".DJIConnectionControlActivity"
android:theme="@android:style/Theme.Translucent"
android:exported="false">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/accessory_filter" />
</activity>
<activity
android:name=".cameraview.CameraActivity"
android:screenOrientation="sensorLandscape"
android:windowSoftInputMode="adjustNothing"
android:exported="true"
android:theme="@style/AppTheme"/>
<service
android:name=".connection.TcpClientService"
android:enabled="true"
android:exported="false" />
</application>
</manifest>

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2018-2020 DJI
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.dji.ux.beta.sample;
import android.app.Activity;
import android.content.Intent;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.view.View;
/**
* Controls the connection to the USB accessory. This activity listens for the USB attached action
* and sends a broadcast with an internal code which is listened to by the
* {@link OnDJIUSBAttachedReceiver}.
*/
public class DJIConnectionControlActivity extends Activity {
public static final String ACCESSORY_ATTACHED = "dji.ux.beta.sample.ACCESSORY_ATTACHED";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new View(this));
Intent usbIntent = getIntent();
if (usbIntent != null) {
String action = usbIntent.getAction();
if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(action)) {
Intent attachedIntent = new Intent();
attachedIntent.setAction(ACCESSORY_ATTACHED);
sendBroadcast(attachedIntent);
}
}
finish();
}
}

View File

@ -0,0 +1,358 @@
/*
* Copyright (c) 2018-2020 DJI
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.dji.ux.beta.sample;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.dji.ux.beta.sample.cameraview.CameraActivity;
import com.dji.ux.beta.sample.connection.TcpClientService;
import com.dji.ux.beta.sample.utils.ToastUtils;
import com.ncorti.slidetoact.SlideToActView;
import org.jetbrains.annotations.NotNull;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import butterknife.BindView;
import butterknife.ButterKnife;
import dji.common.error.DJIError;
import dji.common.error.DJISDKError;
import dji.sdk.base.BaseComponent;
import dji.sdk.base.BaseProduct;
import dji.sdk.products.Aircraft;
import dji.sdk.sdkmanager.DJISDKInitEvent;
import dji.sdk.sdkmanager.DJISDKManager;
/**
* Handles the connection to the product and provides links to the different test activities. Also
* shows the current connection state and displays logs for the different steps of the SDK
* registration process.
*/
//@RequiresApi(api = Build.VERSION_CODES.S)
public class MainActivity extends AppCompatActivity {
// SharedPreferences sharedPreferences;
//region Constants
private static final int REQUEST_PERMISSION_CODE = 12345;
private static final String[] REQUIRED_PERMISSION_LIST = new String[]{
Manifest.permission.VIBRATE, // Gimbal rotation
Manifest.permission.INTERNET, // API requests
Manifest.permission.ACCESS_WIFI_STATE, // WIFI connected products
Manifest.permission.ACCESS_COARSE_LOCATION, // Maps
Manifest.permission.ACCESS_NETWORK_STATE, // WIFI connected products
Manifest.permission.ACCESS_FINE_LOCATION, // Maps
Manifest.permission.CHANGE_WIFI_STATE, // Changing between WIFI and USB connection
Manifest.permission.WRITE_EXTERNAL_STORAGE, // Log files
Manifest.permission.BLUETOOTH, // Bluetooth connected products
Manifest.permission.BLUETOOTH_ADMIN, // Bluetooth connected products
Manifest.permission.READ_EXTERNAL_STORAGE, // Log files
Manifest.permission.READ_PHONE_STATE, // Device UUID accessed upon registration
Manifest.permission.RECORD_AUDIO, // Speaker accessory
//Manifest.permission.BLUETOOTH_SCAN,
//Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.ACCESS_FINE_LOCATION
};
private static final String TIME_FORMAT = "MMM dd, yyyy 'at' h:mm:ss a";
private static final String TAG = "MainActivity";
private static final String ACTION = "FROM_MAIN";
//TODO: eliminare broadcast receiver e prendere indirizzo ip server dallo shared preference
//TODO: inviare al server stato iniziale connessione del drone
//TODO: applicazione crasha se Bluetooth acceso (Android 12 bug)
//TODO: eliminare il toast del log
//endregion
private static boolean isAppStarted = false;
@BindView(R.id.text_view_version)
protected TextView versionTextView;
@BindView(R.id.text_view_registered)
protected TextView registeredTextView;
@BindView(R.id.text_view_product_name)
protected TextView productNameTextView;
@BindView(R.id.text_view_server_ip)
protected TextView ipServerTextView;
@BindView(R.id.camera_button)
protected SlideToActView cameraButton;
//region Fields
private AtomicBoolean isRegistrationInProgress = new AtomicBoolean(false);
private int lastProgress = -1;
private DJISDKManager.SDKManagerCallback registrationCallback = new DJISDKManager.SDKManagerCallback() {
@Override
public void onRegister(DJIError error) {
isRegistrationInProgress.set(false);
if (error == DJISDKError.REGISTRATION_SUCCESS) {
DJISDKManager.getInstance().startConnectionToProduct();
runOnUiThread(() -> {
registeredTextView.setText(R.string.registered);
});
Log.i(TAG, "Registration success");
} else {
showToast( "Register sdk fails, check network is available");
Log.i(TAG, "Registration failed");
}
}
@Override
public void onProductDisconnect() {
runOnUiThread(() -> {
//addLog("Disconnected from product");
productNameTextView.setText(R.string.no_product);
fromActivityToService(ACTION, "drone_connection", getString(R.string.no_product));
});
Log.i(TAG, "Drone disconnected");
}
@Override
public void onProductConnect(BaseProduct product) {
if (product != null) {
runOnUiThread(() -> {
//addLog("Connected to product");
if (product.getModel() != null) {
productNameTextView.setText(getString(R.string.product_name, product.getModel().getDisplayName()));
fromActivityToService(ACTION, "drone_connection", getString(R.string.product_name, product.getModel().getDisplayName()));
} else if (product instanceof Aircraft) {
Aircraft aircraft = (Aircraft) product;
if (aircraft.getRemoteController() != null) {
productNameTextView.setText(getString(R.string.remote_controller));
fromActivityToService(ACTION, "drone_connection", getString((R.string.remote_controller)));
}
}
});
Log.i(TAG, String.format("onProductConnect newProduct:%s", product));
}
}
@Override
public void onProductChanged(BaseProduct product) {
if (product != null) {
runOnUiThread(() -> {
//addLog("Product changed");
if (product.getModel() != null) {
productNameTextView.setText(getString(R.string.product_name, product.getModel().getDisplayName()));
} else if (product instanceof Aircraft) {
Aircraft aircraft = (Aircraft) product;
if (aircraft.getRemoteController() != null) {
productNameTextView.setText(getString(R.string.remote_controller));
}
}
});
}
}
@Override
public void onComponentChange(BaseProduct.ComponentKey key,
BaseComponent oldComponent,
BaseComponent newComponent) {
Log.i(TAG, key.toString() + " changed");
}
@Override
public void onInitProcess(DJISDKInitEvent djisdkInitEvent, int totalProcess) {
Log.i(TAG, djisdkInitEvent.getInitializationState().toString());
}
@Override
public void onDatabaseDownloadProgress(long current, long total) {
runOnUiThread(() -> {
int progress = (int) (100 * current / total);
if (progress == lastProgress) {
return;
}
lastProgress = progress;
//addLog("Fly safe database download progress: " + progress);
});
}
};
private List<String> missingPermission = new ArrayList<>();
//endregion
/**
* Whether the app has started.
*
* @return `true` if the app has been started.
*/
public static boolean isStarted() {
return isAppStarted;
}
//region Lifecycle
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
isAppStarted = true;
checkAndRequestPermissions();
versionTextView.setText(getResources().getString(R.string.sdk_version,
DJISDKManager.getInstance().getSDKVersion()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(new Intent(this, TcpClientService.class));
} else {
startService(new Intent(this, TcpClientService.class));
}
cameraButton.setOnSlideCompleteListener(new SlideToActView.OnSlideCompleteListener() {
@Override
public void onSlideComplete(@NotNull SlideToActView slideToActView) {
Intent intent = new Intent(MainActivity.this, CameraActivity.class);
startActivity(intent);
cameraButton.resetSlider();
}
});
}
//endregion
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
/*
if("FROM_SERVER".equals(intent.getAction())){
String message = intent.getStringExtra("speed_wp");
addLog(message);
}*/
if ("IP".equals(intent.getAction())){
String ip = intent.getStringExtra("server_ip");
ipServerTextView.setText("Status: " + ip);
}
}
};
private void fromActivityToService(String action, String key, String msg){
Intent intent = new Intent(action);
intent.putExtra(key, msg);
LocalBroadcastManager.getInstance(MainActivity.this).sendBroadcast(intent);
}
@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter("IP");
filter.addAction("FROM_SERVER");
LocalBroadcastManager.getInstance(MainActivity.this).registerReceiver(broadcastReceiver, filter);
}
@Override
protected void onDestroy() {
// Prevent memory leak by releasing DJISDKManager's references to this activity
if (DJISDKManager.getInstance() != null) {
DJISDKManager.getInstance().destroy();
}
isAppStarted = false;
super.onDestroy();
stopService(new Intent(this, TcpClientService.class));
LocalBroadcastManager.getInstance(MainActivity.this).unregisterReceiver(broadcastReceiver);
}
/**
* Checks if there is any missing permissions, and
* requests runtime permission if needed.
*/
private void checkAndRequestPermissions() {
// Check for permissions
for (String eachPermission : REQUIRED_PERMISSION_LIST) {
if (ContextCompat.checkSelfPermission(this, eachPermission) != PackageManager.PERMISSION_GRANTED) {
missingPermission.add(eachPermission);
}
}
// Request for missing permissions
if (missingPermission.isEmpty()) {
startSDKRegistration();
} else {
ActivityCompat.requestPermissions(this,
missingPermission.toArray(new String[missingPermission.size()]),
REQUEST_PERMISSION_CODE);
}
}
/**
* Result of runtime permission request
*/
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// Check for granted permission and remove from missing list
if (requestCode == REQUEST_PERMISSION_CODE) {
for (int i = grantResults.length - 1; i >= 0; i--) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
missingPermission.remove(permissions[i]);
}
}
}
// If there is enough permission, we will start the registration
if (missingPermission.isEmpty()) {
startSDKRegistration();
} else {
ToastUtils.setResultToToast("Missing permissions! Will not register SDK to connect to aircraft.");
}
}
/**
* Start the SDK registration
*/
private void startSDKRegistration() {
if (isRegistrationInProgress.compareAndSet(false, true)) {
Log.i(TAG, "registering product");
//addLog("Registering product");
AsyncTask.execute(() -> DJISDKManager.getInstance().registerApp(getApplicationContext(), registrationCallback));
}
}
private void showToast(final String toastMsg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), toastMsg, Toast.LENGTH_LONG).show();
}
});
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2018-2020 DJI
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.dji.ux.beta.sample;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import dji.sdk.sdkmanager.DJISDKManager;
/**
* This receiver will detect the USB attached event.
* It will check if the app has been previously started.
* If the app is already running, it will prevent a new activity from being started and bring the
* existing activity to the top of the stack.
*/
public class OnDJIUSBAttachedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!MainActivity.isStarted()) {
Intent startIntent = context.getPackageManager()
.getLaunchIntentForPackage(context.getPackageName());
startIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
| Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
startIntent.addCategory(Intent.CATEGORY_LAUNCHER);
context.startActivity(startIntent);
} else {
Intent attachedIntent = new Intent();
attachedIntent.setAction(DJISDKManager.USB_ACCESSORY_ATTACHED);
context.sendBroadcast(attachedIntent);
}
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2018-2020 DJI
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.dji.ux.beta.sample;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.multidex.MultiDex;
import com.dji.frame.util.V_JsonUtil;
import com.secneo.sdk.Helper;
import dji.sdk.base.BaseProduct;
import dji.sdk.sdkmanager.DJISDKManager;
import dji.ux.beta.core.communication.DefaultGlobalPreferences;
import dji.ux.beta.core.communication.GlobalPreferencesManager;
import static com.dji.ux.beta.sample.DJIConnectionControlActivity.ACCESSORY_ATTACHED;
/**
* An application that loads the SDK classes.
*/
public class SampleApplication extends Application {
private static Application app = null;
private static BaseProduct product;
private static final String TAG = SampleApplication.class.getName();
public static synchronized BaseProduct getProductInstance(){
product = DJISDKManager.getInstance().getProduct();
return product;
}
@Override
public void onCreate() {
super.onCreate();
//For the global preferences to take effect, this must be done before the widgets are initialized
//If this is not done, no global preferences will take effect or persist across app restarts
//TODO: prima era commentato e non c'era njson
GlobalPreferencesManager.initialize(new DefaultGlobalPreferences(this));
BroadcastReceiver br = new OnDJIUSBAttachedReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(ACCESSORY_ATTACHED);
registerReceiver(br, filter);
V_JsonUtil.DjiLog();
SharedPreferences sharedPreferences = getSharedPreferences(getString(R.string.my_pref), Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(getString(R.string.disableConsole), true);
editor.apply();
}
@Override
protected void attachBaseContext(Context paramContext) {
super.attachBaseContext(paramContext);
Helper.install(SampleApplication.this);
MultiDex.install(this);
app = this;
}
public static Application getInstance() {
return SampleApplication.app;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,498 @@
package com.dji.ux.beta.sample.connection;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.content.res.ResourcesCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.dji.ux.beta.sample.R;
import com.dji.ux.beta.sample.cameraview.CameraActivity;
import com.dji.ux.beta.sample.mission.GPSPlancia;
import com.dji.ux.beta.sample.utils.DroneState;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
public class TcpClientService extends Service {
private final AtomicBoolean working = new AtomicBoolean(true);
private final AtomicBoolean workingJetson = new AtomicBoolean(true);
private Socket socket;
private Thread connectThread;
// private Handler handler = new Handler();
BufferedReader bufferedReader;//Declare the input stream object
OutputStream outputStream;//Declare the output stream object
public final String _ip = "192.168.1.104"; //console TP-link
// public final String _ip = "192.168.2.5"; //console RUBICON
// public final String _ip = "192.168.200.185"; //console livorno
// public final String _ip = "213.82.97.234"; //console livorno remoto
//public final String _ip = "192.168.1.105"; //localhost plancia
private final String port = "11000";
// private final String port = "8089"; //porta console livorno
// private final String TAG = TcpClientService.class.getSimpleName();
private final String TAG = "TcpClientService";
Boolean isconnectBoolean = false;
Boolean isconnectBooleanJetson = false;
private Socket socketJetson;
private Thread connectThreadJetson;
BufferedReader bufferedReaderJetson; //Declare the input stream object
OutputStream outputStreamJetson; //Declare the output stream object
public final String _ipJetson = "192.168.1.100"; //python jetson TP-link
// public final String _ipJetson = "192.168.2.8"; //python jetson RUBICON
// public final String _ipJetson = "192.168.200.22"; //python jetson livorno
// public final String _ipJetson = "146.48.39.44"; //python jetson remoto
private final String portJetson = "65432"; //port python
private String[] request;
private String[] requestJetson;
private String message = "Hello Server";
private String messageJetson = "Hello Jetson";
private static final String ACTION = "FROM_SERVER";
private static final String KEY = "mission";
SharedPreferences sharedPreferences;
private final Runnable runnable = new Runnable() {
//TODO: connettere tramite pulsante
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void run() {
try {
sharedPreferences = getSharedPreferences(getString(R.string.my_pref), Context.MODE_PRIVATE);
socket = new Socket(_ip, Integer.parseInt(port));
bufferedReader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
outputStream=socket.getOutputStream();
//char[] buffer=new char[256];//Define the array to receive the input stream data
StringBuilder bufferString= new StringBuilder();//Define a character to receive array data
//int tag=0;//First know where to write to the array
while (working.get()){
Log.i(TAG, "Connected to " + _ip);
fromServiceToActivity("IP", "server_ip", "Connected to " + _ip);
// outputStream.write((message +"\n").getBytes(StandardCharsets.UTF_8));
// //The output stream is sent to the server
// outputStream.flush();
//while (bufferedReader.read(buffer) >0){
String line;
while ((line = bufferedReader.readLine()) != null){
bufferString.append(line);
handleRequest(bufferString.toString());
//Log.i(TAG, message);
if(bufferString.toString().equals("status")){
boolean disableConsole = sharedPreferences.getBoolean(getString(R.string.disableConsole), true);
StringBuilder bufferStatus = DroneState.getDroneStatePlancia(disableConsole);
//StringBuilder bufferStatus = DroneState.getDroneState();
//bufferStatus.append("\n").append(DroneState.getStreamInfo());
//TODO status string
// Log.i(TAG, "State: " + bufferStatus.toString());
outputStream.write((bufferStatus.toString()).getBytes(StandardCharsets.UTF_8));
outputStream.flush();
}
if(bufferString.toString().equals("closing connection")){
//Log.i(TAG, "Status connection --> " + bufferStatus.toString());
fromServiceToActivity("IP", "server_ip", getString(R.string.server_disc));
break;
}
//fromServiceToActivity("FROM_SERVER", "server_msg", bufferString.toString());
bufferString.setLength(0);
}
break;
}
socket.close();
bufferedReader.close();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
};
private final Runnable runnableJetson = new Runnable() {
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void run() {
try {
socketJetson = new Socket(_ipJetson, Integer.parseInt(portJetson));
bufferedReaderJetson=new BufferedReader(new InputStreamReader(socketJetson.getInputStream()));
outputStreamJetson=socketJetson.getOutputStream();
StringBuilder bufferStringJetson= new StringBuilder();//Define a character to receive array data
while (workingJetson.get()){
Log.i(TAG, "Connected to " + _ipJetson);
fromServiceToActivity("IP", "jetson_ip", "Jetson connected");
// outputStreamJetson.write((messageJetson +"\n").getBytes(StandardCharsets.UTF_8));
// outputStreamJetson.flush();
String line;
while ((line=bufferedReaderJetson.readLine()) != null){
bufferStringJetson.append(line);
handleRequest(bufferStringJetson.toString());
if(bufferStringJetson.toString().equals("gps")){
StringBuilder bufferStatus = DroneState.getGPSJetson();
// Log.i(TAG, "Jetson gps: " + bufferStatus.toString());
//outputStreamJetson.write((bufferStatus.toString()).getBytes(StandardCharsets.UTF_8));
//outputStreamJetson.flush();
sendMessageJetson(bufferStatus.toString());
}
bufferStringJetson.setLength(0);
}
break;
}
socketJetson.close();
bufferedReaderJetson.close();
outputStreamJetson.close();
} catch (IOException e) {
e.printStackTrace();
}
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
IntentFilter filter = new IntentFilter("FROM CAMERA");
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, filter);
if(!isconnectBoolean){
connectThread = new Thread(runnable);
connectThread.start();
Log.d(TAG, "Connected to the server successfully");
isconnectBoolean=true;
}
if(!isconnectBooleanJetson){
connectThreadJetson = new Thread(runnableJetson);
connectThreadJetson.start();
Log.d(TAG, "Connected to the jetson successfully");
isconnectBooleanJetson=true;
}
startMeForeground();
}
@Override
public void onDestroy() {
if (isconnectBoolean){
working.set(false);
//connectThread.interrupt();
if(socket!=null && !socket.isClosed()){
try{
if(outputStream!=null){
outputStream.close();
}
if(bufferedReader!=null){
bufferedReader.close();
}
socket.close();
socket=null;
} catch (Exception e) {
e.printStackTrace();
Logger.getLogger(TcpClientService.class.getName()).log(Level.SEVERE, "Can't close a System.in based BufferedReader", e);
}
}
connectThread.interrupt();
Log.i(TAG, "Closing connection!");
isconnectBoolean=false;
}
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
}
private void startMeForeground() {
if (Build.VERSION.SDK_INT >= 26) {
String NOTIFICATION_CHANNEL_ID = this.getPackageName();
String channelName = "Tcp Client Background Service";
NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_DEFAULT);
notificationChannel.setLightColor(ResourcesCompat.getColor(getResources(), R.color.dark_gray, null));
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(notificationChannel);
Notification builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setOngoing(true)
.setSmallIcon(R.drawable.icon_slider) //TODO:cambiare icona notifica (mettere mini-drone)
.setContentTitle("Tcp Client is running in background")
.setPriority(NotificationManager.IMPORTANCE_MAX)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
startForeground(2, builder);
} else {startForeground(1, new Notification());}
}
private void fromServiceToActivity(String action, String key, String msg){
Intent intent = new Intent(action);
intent.putExtra(key, msg);
LocalBroadcastManager.getInstance(TcpClientService.this).sendBroadcast(intent);
}
private void handleRequest(String req){
sharedPreferences = this.getSharedPreferences(getString(R.string.my_pref), Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
boolean disableConsole = sharedPreferences.getBoolean(getString(R.string.disableConsole), true);
if(!req.equals("gps") && !req.equals("") && !req.equals("status"))
Log.i(TAG, "Received " + req);
request = req.split("-");
if(disableConsole && !request[0].startsWith("hotpoint_jetson") && !request[0].startsWith("warning") && !req.equals("gps") && !req.equals("") && !req.equals("status")) {
Log.i(TAG, "Ignoring command because the console is disabled");
return;
}
switch (request[0]){
case "waypoint_speed":
setSpeed(editor);
break;
case "interdiction_area":
setInterdictionRadius(editor);
break;
case "warning":
setWarning(editor);
break;
case "waypoint_coordinates":
//setCoordinate(editor);
request[1] = request[1].replace(",", ".");
request[2] = request[2].replace(",", ".");
GPSPlancia.populateGPSPlancia(request[1], request[2], request[3]);
//fromServiceToActivity(ACTION, KEY, "coordinates_wp");
break;
case "follow_coordinates":
request[1] = request[1].replace(",", ".");
request[2] = request[2].replace(",", ".");
setFMCoordinate(editor);
fromServiceToActivity(ACTION, KEY, "start_follow");
break;
case "go_to_ship":
request[1] = request[1].replace(",", ".");
request[2] = request[2].replace(",", ".");
setShipCoordinate(editor);
fromServiceToActivity(ACTION, KEY, "go_ship");
break;
case "update_coordinates":
request[1] = request[1].replace(",", ".");
request[2] = request[2].replace(",", ".");
// Log.i(TAG, "Converted update coordinates: " + request[1] + ", " + request[2]);
updateFMCoordinate(editor);
//fromServiceToActivity(ACTION, KEY, "start_follow"); //TODO: cambiare MSG, serve un msg?
break;
case "hotpoint_coordinates": //from command NADSearchAtPos
request[1] = request[1].replace(",", ".");
request[2] = request[2].replace(",", ".");
setHotCoordinate(editor);
fromServiceToActivity(ACTION, KEY, "start_hotpoint");
break;
case "hotpoint_jetson":
setHotCoordinate(editor);
//TODO: check if it is needed fromServiceToActivity() here too
break;
default:
fromServiceToActivity(ACTION, KEY, request[0]);
}
}
private void setSpeed(SharedPreferences.Editor editor){
Log.i(TAG, "Setting speed to: " + request[1]);
float speed = Float.parseFloat(request[1]);
if(checkSpeed(speed, request[1])){
editor.putFloat(getString(R.string.speed_waypoint), speed);
editor.apply();
fromServiceToActivity(ACTION, KEY, "speed_wp");
}
}
private void setInterdictionRadius(SharedPreferences.Editor editor){
Log.i(TAG, "New interdiction radius: " + request[1]);
request[1] = request[1].replace(",", ".");
if(isNumeric(request[1])){
// CameraActivity.setInterdictionRadius(Float.parseFloat(request[1]));
float interdictionArea = Float.parseFloat(request[1]);
editor.putFloat(getString(R.string.interdiction_area), interdictionArea);
editor.apply();
fromServiceToActivity(ACTION, KEY, "interdiction_radius");
}
else {
try {
outputStream.write(("Incorrect or null interdiction_area value! Please send a numeric value\n").getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void setWarning(SharedPreferences.Editor editor){
Log.i(TAG, "warning " + request[1]);
editor.putString(getString(R.string.warning), request[1]);
editor.apply();
fromServiceToActivity(ACTION, KEY, "warning");
}
private void setCoordinate(SharedPreferences.Editor editor){
editor.putString(getString(R.string.latitude), request[1]);
editor.putString(getString(R.string.longitude), request[2]);
editor.putString(getString(R.string.altitude), request[3]);
editor.apply();
}
private void setHotCoordinate(SharedPreferences.Editor editor){
editor.putString(getString(R.string.latitude_pos), request[1]);
editor.putString(getString(R.string.longitude_pos), request[2]);
editor.putString(getString(R.string.altitude_pos), request[3]);
editor.putString(getString(R.string.radius_pos), request[4]);
editor.apply();
}
private void setFMCoordinate(SharedPreferences.Editor editor){
editor.putString(getString(R.string.latitude_fm), request[1]);
editor.putString(getString(R.string.longitude_fm), request[2]);
editor.putString(getString(R.string.altitude_fm), request[3]);
editor.apply();
}
private void setShipCoordinate(SharedPreferences.Editor editor){
editor.putString(getString(R.string.latitude_ship), request[1]);
editor.putString(getString(R.string.longitude_ship), request[2]);
editor.putString(getString(R.string.altitude_ship), request[3]);
editor.apply();
}
private void updateFMCoordinate(SharedPreferences.Editor editor){
// //TODO check if the replace is needed or not
// request[1] = request[1].replace(",", ".");
// request[2] = request[2].replace(",", ".");
editor.putString(getString(R.string.latitude_fm), request[1]);
editor.putString(getString(R.string.longitude_fm), request[2]);
editor.apply();
}
private boolean checkSpeed(float speed, String msg){
if (speed <= 0.0f || speed > 15.0f || !isNumeric(msg)){
try {
outputStream.write(("Incorrect or null speed value! Please send a value in the range [0.0f-15.0f] " + "\n").getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
return true;
}
private static boolean isNumeric(String strNum) {
if (strNum == null) {
return false;
}
try {
Float.parseFloat(strNum);
} catch (NumberFormatException nfe) {
return false;
}
return true;
}
private static boolean checkGpsCoordinates(double latitude, double longitude) {
return (latitude > -90 && latitude < 90 && longitude > -180 && longitude < 180) && (latitude != 0f && longitude != 0f);
}
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Broadcaster: " + intent.getStringExtra("state"));
String msg= intent.getStringExtra("state");
if(msg.equals("isStreaming")){
sendMessageJetson(msg);
}
if(msg.equals("mob_mission")){
sendMessagePlancia(msg);
}
}
};
private void sendMessageJetson(String msg) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
if(outputStreamJetson!=null){
// Log.i(TAG, "sending to jetson" + msg);
outputStreamJetson.write((msg + "\n").getBytes(StandardCharsets.UTF_8));
outputStreamJetson.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();
}
private void sendMessagePlancia(String msg) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
if(outputStream!=null){
Log.i(TAG, "sending to ship" + msg);
outputStream.write((msg).getBytes(StandardCharsets.UTF_8));
outputStream.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();
}
//TODO: cambiare thread (ogni nuovo messaggio apre un thread-no buono)
}

View File

@ -0,0 +1,216 @@
package com.dji.ux.beta.sample.mission;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationManager;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.dji.mapkit.core.models.DJIBitmapDescriptor;
import com.dji.mapkit.core.models.DJIBitmapDescriptorFactory;
import com.dji.mapkit.core.models.DJILatLng;
import com.dji.mapkit.core.models.annotations.DJIMarkerOptions;
import com.dji.ux.beta.sample.R;
import com.dji.ux.beta.sample.SampleApplication;
import com.dji.ux.beta.sample.utils.DroneState;
import com.dji.ux.beta.sample.utils.ToastUtils;
import org.jetbrains.annotations.NotNull;
import dji.common.error.DJIError;
import dji.common.flightcontroller.FlightControllerState;
import dji.common.mission.followme.FollowMeHeading;
import dji.common.mission.followme.FollowMeMission;
import dji.common.mission.followme.FollowMeMissionEvent;
import dji.common.mission.followme.FollowMeMissionState;
import dji.common.model.LocationCoordinate2D;
import dji.common.util.CommonCallbacks;
import dji.sdk.base.BaseProduct;
import dji.sdk.flightcontroller.FlightController;
import dji.sdk.mission.followme.FollowMeMissionOperator;
import dji.sdk.mission.followme.FollowMeMissionOperatorListener;
import dji.sdk.products.Aircraft;
import dji.sdk.sdkmanager.DJISDKManager;
import dji.ux.beta.map.widget.map.MapWidget;
public class FollowMission {
private static final String TAG = FollowMission.class.getSimpleName();
private final Context mContext;
private FollowMeMissionOperator instance;
private double latitude;
private double longitude;
Thread locationUpdateThread;
private boolean missionRunning = false;
//TODO: disegnare icona nave che si muove quando cambia posizione
public FollowMission(Context mContext){
this.mContext = mContext;
}
private void setResultToToast(String s) {
ToastUtils.setResultToToast(s);
}
public FollowMeMissionOperator getFollowM(){
if(instance == null){
if(DJISDKManager.getInstance().getMissionControl()!=null){
instance=DJISDKManager.getInstance().getMissionControl().getFollowMeMissionOperator();
}
}
return instance;
}
public void addListenerFollow() {
if (getFollowM() != null){
getFollowM().addListener(followEventNotificationListener);
}
}
public void removeListenerFollow(){
if (getFollowM() != null){
getFollowM().removeListener(followEventNotificationListener);
}
}
private final FollowMeMissionOperatorListener followEventNotificationListener = new FollowMeMissionOperatorListener() {
@Override
public void onExecutionUpdate(@NonNull FollowMeMissionEvent followMeMissionEvent) {
Log.i(TAG, "FollowingMission: onExecutionUpdate state is: " + followMeMissionEvent.getCurrentState() + " distance to target is: " + followMeMissionEvent.getDistanceToTarget());
}
@Override
public void onExecutionStart() {
Log.i(TAG, "FollowMission started.");
setResultToToast("FollowMission started");
}
@Override
public void onExecutionFinish(@Nullable DJIError djiError) {
// locationUpdateThread.interrupt();
missionRunning = false;
Log.i(TAG, "FollowMission Execution finished: " + (djiError == null ? "Success!" : djiError.getDescription()));
setResultToToast("FollowMission Execution finished: " + (djiError == null ? "Success!" : djiError.getDescription()));
}
};
public void startFollowShip(SharedPreferences preferences){
Log.i(TAG, "Starting FollowMission, current state: " + getFollowState());
latitude = Double.parseDouble(preferences.getString(mContext.getString(R.string.latitude_fm), ""));
longitude = Double.parseDouble(preferences.getString(mContext.getString(R.string.longitude_fm), ""));
float altitude = Float.parseFloat(preferences.getString(mContext.getString(R.string.altitude_fm), ""));
if (getFollowM().getCurrentState().toString().equals(FollowMeMissionState.READY_TO_EXECUTE.toString())) {
setResultToToast("Ready to follow");
FollowMeMission followMeMission = new FollowMeMission(FollowMeHeading.TOWARD_FOLLOW_POSITION, latitude, longitude, altitude);
//followMeEvent = new FollowMeMissionEvent.Builder().distanceToTarget(34);
Log.i(TAG, "Altitude: " + altitude + "\nLatitude: " + latitude + "\nLongitude: " + longitude);
Log.i(TAG, "FollowMeInfo: " + followMeMission.getLatitude() + " " + followMeMission.getLongitude());
//starts the new mission just created
getFollowM().startMission(followMeMission, new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
if(djiError==null){
missionRunning = true;
// setResultToToast("Follow mission created successfully, starting follow thread...");
Log.i(TAG, "Follow mission created successfully with location destination: " + getFollowM().getFollowingTarget() + ", starting follow thread...");
locationUpdateThread = new Thread(new Runnable() {
@Override
public void run() {
// while (!Thread.currentThread().isInterrupted()) {
while (missionRunning) {
// if(getFollowState().equals(FollowMeMissionState.EXECUTING.toString()))
// {
latitude = Double.parseDouble(preferences.getString(mContext.getString(R.string.latitude_fm), ""));
longitude = Double.parseDouble(preferences.getString(mContext.getString(R.string.longitude_fm), ""));
Log.i(TAG, "FollowMission: " + getFollowState() + ", following target: " + getFollowM().getFollowingTarget());
// Log.i(TAG, "FollowMeInfo: " + followMeMission.getLatitude() + " " + followMeMission.getLongitude());
getFollowM().updateFollowingTarget(new LocationCoordinate2D(latitude, longitude), new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
if (djiError != null) {
// Log.i(TAG, "FollowMission: Failed to update target GPS: " + djiError.getDescription());
// setResultToToast("Failed to update target GPS: " + djiError.getDescription());
} else {
Log.i(TAG, "Location updating " + latitude + " " + longitude);
setResultToToast("Location updating " + latitude + " " + longitude);
}
}
});
try {
//TODO: cambiare millisecondi in sleep (consigliati 10Hz)
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
// }
Log.i(TAG, "Update thread ended.");
}
});
locationUpdateThread.start();
} else {
setResultToToast("FollowMission: error: " + djiError.getDescription());
Log.i(TAG, "FollowMission: error: " + djiError.getDescription());
}
}
});
} else {
setResultToToast("Not ready to execute FollowMission");
}
}
public void stopFollowShip(){
Log.i(TAG, "Stopping FollowMission, current state: " + getFollowState());
getFollowM().stopMission(new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
Log.i(TAG, "FollowMission Stopped: " + (djiError == null ? "Successfully" : djiError.getDescription()));
setResultToToast("FollowMission Stop: " + (djiError == null ? "Successfully" : djiError.getDescription()));
}
});
}
public void provaFollow(SharedPreferences preferences){
Thread locationUpdate = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
latitude = Double.parseDouble(preferences.getString(mContext.getString(R.string.latitude_fm), ""));
longitude = Double.parseDouble(preferences.getString(mContext.getString(R.string.longitude_fm), ""));
Log.i(TAG, "Location Value: " + "\n" + "Latitude: " + latitude
+ "\n" + "Longitude: " + longitude + "\n");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
});
locationUpdate.start();
}
public String getFollowState() {
return getFollowM().getCurrentState().toString();
}
}

View File

@ -0,0 +1,100 @@
package com.dji.ux.beta.sample.mission;
import android.location.Location;
import android.util.Log;
import android.widget.Toast;
import com.dji.ux.beta.sample.R;
import com.dji.ux.beta.sample.utils.ToastUtils;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.SphericalUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class GPSPlancia {
private static final String TAG = GPSPlancia.class.getSimpleName();
private double latitude;
private double longitude;
private float altitude;
private static CopyOnWriteArrayList<GPSPlancia> gpsPlanciaList = new CopyOnWriteArrayList<>();
private static HashSet<GPSPlancia> gpsPlanciaHashSet = new HashSet<>();
public GPSPlancia(double latitude, double longitude, float altitude){
this.latitude = latitude;
this.longitude = longitude;
this.altitude = altitude;
}
public double getLatitude() {
return latitude;
}
public float getAltitude() {
return altitude;
}
public double getLongitude() {
return longitude;
}
public static CopyOnWriteArrayList<GPSPlancia> getGPSPlanciaList() {
return gpsPlanciaList;
}
public static void populateGPSPlancia(String latitudeString, String longitudeString, String altitudeString) {
double latitude = Double.parseDouble(latitudeString);
double longitude = Double.parseDouble(longitudeString);
float altitude = Float.parseFloat(altitudeString);
GPSPlancia gpsPlancia = new GPSPlancia(latitude, longitude, altitude);
if(!gpsPlanciaList.contains(gpsPlancia)){
gpsPlanciaList.add(new GPSPlancia(latitude, longitude, altitude));
}
}
private static boolean checkPosDistance(LatLng oldPos, LatLng currentPos, double distanceMeters){
double distance = SphericalUtil.computeDistanceBetween(oldPos, currentPos);
return distance >= distanceMeters;
}
//remove waypoints that are closer than interdictionRadius between them
public static List<GPSPlancia> getCleanedGPSList(double interdictionRadius){
Log.i(TAG, "Initial GPS list length: " + gpsPlanciaList.size());
List<GPSPlancia> cleanedList = new ArrayList<>();
if(!gpsPlanciaList.isEmpty()){
cleanedList.add(gpsPlanciaList.get(0));
LatLng oldPos = new LatLng((gpsPlanciaList.get(0).getLatitude()), cleanedList.get(0).getLongitude());
for (GPSPlancia gps: gpsPlanciaList) {
LatLng currentPos = new LatLng(gps.getLatitude(), gps.getLongitude());
if(checkPosDistance(oldPos, currentPos, interdictionRadius)){
cleanedList.add(gps);
oldPos = currentPos;
}
if(cleanedList.size()==99){
break;
}
}
} else {
ToastUtils.setResultToToast("GPS list empty!");
// Toast.makeText(getApplicationContext(), "GPS list empty!", Toast.LENGTH_SHORT).show();
}
Log.i(TAG, "Cleaned GPS list length: " + cleanedList.size());
return cleanedList;
}
//remove first waypointCount element from gpsPlanciaList
public static void updateGPSList(int waypointCount) {
// int wpToRemove = waypointCount/2;
//TODO capire perche' serve il /2
int wpToRemove = waypointCount + 1; //the parameter is the number of waypoints to be deleted
Log.i(TAG, "Initial GPS list length: " + gpsPlanciaList.size() + ", waypointCount: " + waypointCount + ", therefore removing " + wpToRemove + " waypoints");
for (int i = 0; i < wpToRemove; i++) {
gpsPlanciaList.remove(0);
}
Log.i(TAG, "Updated GPS list length: " + gpsPlanciaList.size());
}
}

View File

@ -0,0 +1,352 @@
package com.dji.ux.beta.sample.mission;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.dji.ux.beta.sample.R;
import com.dji.ux.beta.sample.cameraview.CameraActivity;
import com.dji.ux.beta.sample.connection.TcpClientService;
import com.dji.ux.beta.sample.utils.ToastUtils;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import dji.common.error.DJIError;
import dji.common.mission.waypoint.Waypoint;
import dji.common.mission.waypoint.WaypointAction;
import dji.common.mission.waypoint.WaypointActionType;
import dji.common.mission.waypoint.WaypointMission;
import dji.common.mission.waypoint.WaypointMissionDownloadEvent;
import dji.common.mission.waypoint.WaypointMissionExecuteState;
import dji.common.mission.waypoint.WaypointMissionExecutionEvent;
import dji.common.mission.waypoint.WaypointMissionFinishedAction;
import dji.common.mission.waypoint.WaypointMissionFlightPathMode;
import dji.common.mission.waypoint.WaypointMissionHeadingMode;
import dji.common.mission.waypoint.WaypointMissionState;
import dji.common.mission.waypoint.WaypointMissionUploadEvent;
import dji.common.util.CommonCallbacks;
import dji.sdk.mission.waypoint.WaypointMissionOperator;
import dji.sdk.mission.waypoint.WaypointMissionOperatorListener;
import dji.sdk.sdkmanager.DJISDKManager;
public class PointMission {
private static final String TAG = PointMission.class.getSimpleName();
private WaypointMissionOperator instance;
public static WaypointMission.Builder waypointMissionBuilder;
private final WaypointMissionFinishedAction mFinishedAction = WaypointMissionFinishedAction.NO_ACTION;
//private final WaypointMissionFinishedAction mFinishedAction = WaypointMissionFinishedAction.GO_HOME; this action if you want the drone to come back to first WP when execution finished
private final WaypointMissionHeadingMode mHeadingMode = WaypointMissionHeadingMode.AUTO;
private int wayPointCount = 0;
private int nextWaypointIndex = -1;
//private float waypointSpeed = 10.0f; //range [2,15] m/s.
private final Context mContext;
//TODO: decidere se rimuovere i controlli di stato prima di start(pause/resume/stop (sono presenti in start di follow e pos)
public PointMission(Context mContext){
this.mContext = mContext;
}
// private void setResultToToast(Context mContext, String s) {
// Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show();
// }
private void setResultToToast(String s) {
ToastUtils.setResultToToast(s);
}
public WaypointMissionOperator getWaypointM (){
if (instance == null) {
if(DJISDKManager.getInstance().getMissionControl()!= null) {
instance = DJISDKManager.getInstance().getMissionControl().getWaypointMissionOperator();
}
}
return instance;
}
public void addListenerWaypoint() {
if (getWaypointM() != null){
getWaypointM().addListener(waypointEventNotificationListener);
}
}
public void removeListenerWaypoint(){
if (getWaypointM() != null){
getWaypointM().removeListener(waypointEventNotificationListener);
}
}
private final WaypointMissionOperatorListener waypointEventNotificationListener = new WaypointMissionOperatorListener() {
@Override
public void onDownloadUpdate(@NonNull WaypointMissionDownloadEvent waypointMissionDownloadEvent) {
}
@Override
public void onUploadUpdate(@NonNull WaypointMissionUploadEvent waypointMissionUploadEvent) {
if (waypointMissionUploadEvent.getProgress() != null
&& waypointMissionUploadEvent.getProgress().isSummaryUploaded
&& waypointMissionUploadEvent.getProgress().uploadedWaypointIndex == (waypointMissionUploadEvent.getProgress().totalWaypointCount) - 1) {
Log.i(TAG, "onUploadUpdate: Mission uploaded successfully, current state: " + getWaypointMissionState());
}
}
@Override
public void onExecutionUpdate(@NonNull WaypointMissionExecutionEvent waypointMissionExecutionEvent) {
if(waypointMissionExecutionEvent.getProgress().isWaypointReached){
nextWaypointIndex = waypointMissionExecutionEvent.getProgress().targetWaypointIndex;
Log.i(TAG, "Waypoint reached " + nextWaypointIndex);
}
}
@Override
public void onExecutionStart() {
Log.i(TAG, "Execution started, number of waypoints: " + waypointMissionBuilder.getWaypointCount());
}
@Override
public void onExecutionFinish(@Nullable @org.jetbrains.annotations.Nullable DJIError djiError) {
Log.i(TAG, "Execution ended with nextWaypointIndex: " + nextWaypointIndex + " and waypointMissionBuilder.getWaypointCount(): " + waypointMissionBuilder.getWaypointCount());
if(nextWaypointIndex >= 0 && nextWaypointIndex == waypointMissionBuilder.getWaypointCount()-1) {
Log.i(TAG, "All waypoints reached");
setResultToToast("Execution finished: " + (djiError == null ? "Success!" : djiError.getDescription()));
deleteWaypoint();
CameraActivity activity = (CameraActivity) mContext;
activity.deleteMarkers();
activity.setPersonNotification(false);
}
// waypointMissionBuilder = null;
nextWaypointIndex = -1;
}
};
public void createWaypointFromList(List<GPSPlancia> cleanedList){
if(waypointMissionBuilder == null){
waypointMissionBuilder = new WaypointMission.Builder();
}
if (cleanedList.isEmpty()){
setResultToToast("List of positions is empty!");
} else {
for (GPSPlancia gps: cleanedList) {
Waypoint waypoint = new Waypoint(gps.getLatitude(), gps.getLongitude(), gps.getAltitude());
//waypoint.addAction(new WaypointAction(WaypointActionType.STAY, 0));
waypoint.gimbalPitch = -90.0f; //from = -135.0, to = 45.0)
waypointMissionBuilder.addWaypoint(waypoint);
}
}
Log.i(TAG, "WP MissBuildList " + waypointMissionBuilder.getWaypointList().toString());
Log.i(TAG, "WP MissBuildList size: " + waypointMissionBuilder.getWaypointList().size());
// Log.i(TAG, "WP WaypointCount(): " + waypointMissionBuilder.getWaypointCount());
if(waypointMissionBuilder!= null && !waypointMissionBuilder.getWaypointList().isEmpty()){
setResultToToast("Positions added to the map!");
}
}
public void configWaypointMission(float waypointSpeed){
if (waypointMissionBuilder == null){
waypointMissionBuilder = new WaypointMission.Builder()
.finishedAction(mFinishedAction)
.headingMode(mHeadingMode)
.autoFlightSpeed(waypointSpeed)
.maxFlightSpeed(waypointSpeed)
.flightPathMode(WaypointMissionFlightPathMode.NORMAL)
.setGimbalPitchRotationEnabled(true); //per controllare gimbal pitch
} else{
waypointMissionBuilder
.finishedAction(mFinishedAction)
.headingMode(mHeadingMode)
.autoFlightSpeed(waypointSpeed)
.maxFlightSpeed(waypointSpeed)
.flightPathMode(WaypointMissionFlightPathMode.NORMAL)
.setGimbalPitchRotationEnabled(true); //per controllare gimbal pitch
}
Log.i(TAG, "is gimbal rotation enabled? " + waypointMissionBuilder.isGimbalPitchRotationEnabled());
Log.i(TAG, "Auto Flight Speed " + waypointMissionBuilder.getAutoFlightSpeed());
DJIError error = getWaypointM().loadMission(waypointMissionBuilder.build());
if(error == null){
// setResultToToast("loadMission success");
} else {
setResultToToast("loadMission failed " + error.getDescription());
Log.i(TAG, "loadMission failed " + error.getDescription());
}
}
public String getWaypointMissionState(){
return getWaypointM().getCurrentState().toString();
}
public void uploadAndStartWayPointMission(){
Log.i(TAG, "Uploading WP mission, current state is: " + getWaypointMissionState());
getWaypointM().uploadMission(new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
if(djiError == null){
Log.i(TAG, "Mission uploaded successfully, current state: " + getWaypointMissionState());
setResultToToast("Mission upload successfully!");
startWaypointMission();
} else {
Log.i(TAG, "Mission upload failed, error: " + djiError.getDescription() + " retrying...");
setResultToToast("Mission upload failed, error: " + djiError.getDescription() + " retrying...");
getWaypointM().retryUploadMission(null);
}
}
});
}
public void setWaypointMissionSpeed(float waypointSpeed){ //(@FloatRange(from = -15.0f, to = 15.0f)
getWaypointM().setAutoFlightSpeed(waypointSpeed, new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
if(djiError == null){
setResultToToast("Speed set successfully!");
} else {
setResultToToast("Unable to set the desired speed: " + djiError.getDescription());
}
}
});
}
public void startWaypointMission(){
Log.i(TAG, "Starting WP mission, current state: " + getWaypointMissionState());
getWaypointM().startMission(new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
if(djiError == null){
Log.i(TAG, "WaypointMission started successfully!");
setResultToToast("Mission start successfully!");
}
else {
Log.i(TAG, "Error while starting WaypointMission: " + djiError.getDescription());
setResultToToast("Error while starting WaypointMission: " + djiError.getDescription());
}
}
});
}
public int pauseWaypointMission(){
if (getWaypointMissionState().equals(WaypointMissionState.EXECUTING.toString())){
getWaypointM().pauseMission(new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
Log.i(TAG, "WaypointMission Pause: " + (djiError == null ? "Successfully" : djiError.getDescription()));
setResultToToast("WaypointMission Pause: " + (djiError == null ? "Successfully" : djiError.getDescription()));
}
});
} else {
setResultToToast("Couldn't pause WaypointMission because state is not executing, current state: " + getWaypointMissionState());
}
return nextWaypointIndex;
}
public void resumeWaypointMission(){
if (getWaypointMissionState().equals(WaypointMissionState.EXECUTION_PAUSED.toString())){
getWaypointM().resumeMission(new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
Log.i(TAG, "WaypointMission Resume: " + (djiError == null ? "Successfully" : djiError.getDescription()));
setResultToToast("WaypointMission Resume: " + (djiError == null ? "Successfully" : djiError.getDescription()));
}
});
} else {
setResultToToast("Couldn't resume WaypointMission because state is not paused, current state: " + getWaypointMissionState());
}
}
public int stopWaypointMission(){
if (getWaypointMissionState().equals(WaypointMissionState.EXECUTING.toString()) || getWaypointMissionState().equals(WaypointMissionState.EXECUTION_PAUSED.toString())){
getWaypointM().stopMission(new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
Log.i(TAG, "WaypointMission Stop: " + (djiError == null ? "Successfully" : djiError.getDescription()));
setResultToToast("WaypointMission Stop: " + (djiError == null ? "Successfully" : djiError.getDescription()));
}
});
}
else {
setResultToToast("Couldn't stop WaypointMission because state is not executing or paused, current state: " + getWaypointMissionState());
}
return nextWaypointIndex;
}
private boolean deleteWaypointsFromMissionBuilder() {
if(waypointMissionBuilder!=null) {
Log.i(TAG, "Deleting: " + waypointMissionBuilder.getWaypointCount() + " elements in waypointMissionBuilder.getWaypointList()");
int totalWp = waypointMissionBuilder.getWaypointCount();
for (int i = 0; i < totalWp; i++) {
waypointMissionBuilder.removeWaypoint(0);
}
Log.i(TAG, "WP MissBuildList after delete" + waypointMissionBuilder.getWaypointList().toString());
Log.i(TAG, "WP MissBuildCount after delete: " + waypointMissionBuilder.getWaypointCount());
return true;
}
else {
Log.i(TAG, "waypointMissionBuilder is null, nothing to delete");
return false;
}
}
//remove all waypoints from waypointMissionBuilder (and set it tu null) and from GPSPlancia
public void deleteWaypoint(){
boolean deleted = deleteWaypointsFromMissionBuilder();
if (deleted) {
GPSPlancia.getGPSPlanciaList().clear();
nextWaypointIndex = -1;
setResultToToast("All positions deleted");
} else {
setResultToToast("No position to delete");
//return "No position to delete";
}
// wayPointCount = 0;
}
public int getWayPointCount(){
return this.wayPointCount;
}
public int getNextWaypointIndex() {
return this.nextWaypointIndex;
}
// remove visited waypoints from waypointMissionBuilder and from GPSPlancia
public void deleteWPCount(){
// if(wayPointCount>0 && !waypointMissionBuilder.getWaypointList().isEmpty()){
// waypointMissionBuilder.getWaypointList().subList(0, wayPointCount/2).clear();
// //TODO capire perche' serve il /2
// }
boolean deleted = deleteWaypointsFromMissionBuilder();
// if (deleted) {
// setResultToToast("Visited positions deleted");
// }
if(nextWaypointIndex>=0 && !GPSPlancia.getGPSPlanciaList().isEmpty()) {
GPSPlancia.updateGPSList(nextWaypointIndex);
}
}
}

View File

@ -0,0 +1,163 @@
package com.dji.ux.beta.sample.mission;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.dji.ux.beta.sample.R;
import com.dji.ux.beta.sample.utils.ToastUtils;
import dji.common.error.DJIError;
import dji.common.mission.hotpoint.HotpointHeading;
import dji.common.mission.hotpoint.HotpointMission;
import dji.common.mission.hotpoint.HotpointMissionEvent;
import dji.common.mission.hotpoint.HotpointMissionState;
import dji.common.mission.hotpoint.HotpointStartPoint;
import dji.common.mission.waypoint.WaypointMissionState;
import dji.common.model.LocationCoordinate2D;
import dji.common.util.CommonCallbacks;
import dji.sdk.mission.hotpoint.HotpointMissionOperator;
import dji.sdk.mission.hotpoint.HotpointMissionOperatorListener;
import dji.sdk.sdkmanager.DJISDKManager;
//TODO: qui ho utilizzato il toast di ToastUtils
//TODO: mandare gli stati delle missioni alla plancia quando viene avviata/stoppata ecc..
public class PosMission {
private static final String TAG = PosMission.class.getSimpleName();
private HotpointMissionOperator instance;
private final HotpointStartPoint hotpointStartPoint = HotpointStartPoint.NEAREST;
private final HotpointHeading hotpointHeading = HotpointHeading.TOWARDS_HOT_POINT;
private final Context mContext;
public PosMission(Context mContext) {
this.mContext = mContext;
}
private void setResultToToast(String s) {
ToastUtils.setResultToToast(s);
}
public HotpointMissionOperator getHotpointM(){
if (instance==null){
if(DJISDKManager.getInstance().getMissionControl()!=null){
instance = DJISDKManager.getInstance().getMissionControl().getHotpointMissionOperator();
}
}
return instance;
}
public String getHPState(){
return getHotpointM().getCurrentState().toString();
}
public void addListenerHotpoint() {
if (getHotpointM() != null){
getHotpointM().addListener(hotpointEventNotificationListener);
}
}
public void removeListenerHotpoint(){
if (getHotpointM() != null){
getHotpointM().removeListener(hotpointEventNotificationListener);
}
}
private final HotpointMissionOperatorListener hotpointEventNotificationListener = new HotpointMissionOperatorListener() {
@Override
public void onExecutionUpdate(@NonNull HotpointMissionEvent hotpointMissionEvent) {
}
@Override
public void onExecutionStart() {
}
@Override
public void onExecutionFinish(@Nullable DJIError djiError) {
setResultToToast("HotpointMission finished: " + (djiError == null ? "Success!" : djiError.getDescription()));
}
};
public void startHotPoint(SharedPreferences preferences){
Log.i(TAG, "Starting HotpointMission, current state: " + getHPState());
if (getHPState().equals(HotpointMissionState.READY_TO_EXECUTE.toString())){
double latitude = Double.parseDouble(preferences.getString(mContext.getString(R.string.latitude_pos), ""));
double longitude = Double.parseDouble(preferences.getString(mContext.getString(R.string.longitude_pos), ""));
double altitude = Double.parseDouble(preferences.getString(mContext.getString(R.string.altitude_pos), ""));
double radius = Double.parseDouble(preferences.getString(mContext.getString(R.string.radius_pos), "5.0"));
float angularVel = 20.0f;
HotpointMission mHotpointMission = new HotpointMission(new LocationCoordinate2D(latitude, longitude), altitude, radius,
angularVel, true, hotpointStartPoint, hotpointHeading);
Log.i(TAG, "Altitude: " + altitude + "\nLatitude: " + latitude + "\nLongitude: " + longitude + "\nradius: " + radius);
Log.i(TAG, "Hotpoint " + mHotpointMission.getHotpoint().toString());
getHotpointM().startMission(mHotpointMission, new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
if (djiError != null) {
Log.i(TAG, "Error while starting HotpointMission, current state: " + getHPState() + ", ERROR: " + djiError.getDescription());
setResultToToast("Could not start hotpoint mission: " + djiError.getDescription());
} else {
Log.i(TAG, "HotpointMission started Successfully! Current state: " + getHPState());
setResultToToast("HotpointMission started Successfully!");
//TODO: inviare alla plancia
}
}
});
}
}
public void pauseHotPoint(){
if (getHPState().equals(WaypointMissionState.EXECUTING.toString())){
getHotpointM().pause(new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
Log.i(TAG, "HotpointMission Paused: " + (djiError == null ? "Successfully" : djiError.getDescription()));
setResultToToast("HotpointMission Paused: " + (djiError == null ? "Successfully" : djiError.getDescription()));
}
});
} else {
setResultToToast("Couldn't pause HotpointMission because state is not executing, current state: " + getHPState());
}
}
public void resumeHotPoint(){
if (getHPState().equals(WaypointMissionState.EXECUTION_PAUSED.toString())){
getHotpointM().resume(new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
Log.i(TAG, "HotpointMission Resumed: " + (djiError == null ? "Successfully" : djiError.getDescription()));
setResultToToast("HotpointMission Resumed: " + (djiError == null ? "Successfully" : djiError.getDescription()));
}
});
} else {
setResultToToast("Couldn't resume HotpointMission because state is not paused, current state: " + getHPState());
}
}
public void stopHotPoint(){
if (getHPState().equals(WaypointMissionState.INITIAL_PHASE.toString()) || getHPState().equals(WaypointMissionState.EXECUTING.toString()) || getHPState().equals(WaypointMissionState.EXECUTION_PAUSED.toString())){
getHotpointM().stop(new CommonCallbacks.CompletionCallback() {
@Override
public void onResult(DJIError djiError) {
Log.i(TAG, "HotpointMission Stopped: " + (djiError == null ? "Successfully" : djiError.getDescription()));
setResultToToast("HotpointMission Stopped: " + (djiError == null ? "Successfully" : djiError.getDescription()));
}
});
}
else {
setResultToToast("Couldn't stop HotpointMission because state is not executing or paused, current state: " + getHPState());
}
}
}

View File

@ -0,0 +1,295 @@
package com.dji.ux.beta.sample.utils;
/*
Util class that provides info onm drone status such as battery charge, altitude, speed, etc..
*/
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.dji.mapkit.core.models.DJIBitmapDescriptor;
import com.dji.mapkit.core.models.DJIBitmapDescriptorFactory;
import com.dji.ux.beta.sample.R;
import com.dji.ux.beta.sample.SampleApplication;
import com.dji.ux.beta.sample.mission.GPSPlancia;
import com.google.android.gms.maps.model.LatLng;
import org.jetbrains.annotations.NotNull;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import dji.common.battery.BatteryState;
import dji.common.error.DJIError;
import dji.common.flightcontroller.FlightControllerState;
import dji.common.model.LocationCoordinate2D;
import dji.common.util.CommonCallbacks;
import dji.sdk.base.BaseProduct;
import dji.sdk.flightcontroller.FlightController;
import dji.sdk.flighthub.FlightHubManager;
import dji.sdk.flighthub.model.RealTimeFlightData;
import dji.sdk.products.Aircraft;
import dji.sdk.sdkmanager.DJISDKManager;
public class DroneState {
private static final String TAG = DroneState.class.getSimpleName();
protected static StringBuilder stringState = new StringBuilder();
protected static StringBuilder stringStatePlancia = new StringBuilder();
protected static StringBuilder stringStream = new StringBuilder();
protected static StringBuilder stringJetson;
protected static String altitude;
protected static String latitude;
protected static String longitude;
protected static String whichLatitude;
protected static int batteryCharge;
private static FlightController flightController;
private static String tracking;
private static String searching;
private static String following;
private static String serialNumber;
private static String speed;
private static LatLng homeLocation;
public static LatLng getHomeLocation(){
BaseProduct product = SampleApplication.getProductInstance();
if (product instanceof Aircraft) { flightController = ((Aircraft) product).getFlightController(); }
if(flightController != null){
flightController.getHomeLocation(new CommonCallbacks.CompletionCallbackWith<LocationCoordinate2D>() {
@Override
public void onSuccess(LocationCoordinate2D locationCoordinate2D) {
homeLocation = new LatLng(locationCoordinate2D.getLatitude(), locationCoordinate2D.getLongitude());
}
@Override
public void onFailure(DJIError djiError) {
}
});
} else{
homeLocation = new LatLng(43.719259015524976, 10.421048893480233);
}
return homeLocation;
}
private static void getDroneLocation(BaseProduct product){
if (product instanceof Aircraft) { flightController = ((Aircraft) product).getFlightController(); }
if (flightController !=null){
flightController.setStateCallback(new FlightControllerState.Callback() {
@Override
public void onUpdate(@NonNull @NotNull FlightControllerState flightControllerState) {
if (flightControllerState.getAircraftLocation() != null) {
if(!Double.isNaN(flightControllerState.getAircraftLocation().getLatitude()) && !Double.isNaN(flightControllerState.getAircraftLocation().getLongitude())) {
latitude = String.valueOf(flightControllerState.getAircraftLocation().getLatitude());
longitude = String.valueOf(flightControllerState.getAircraftLocation().getLongitude());
altitude = String.valueOf(flightControllerState.getAircraftLocation().getAltitude());
} else {
latitude = "0.0";
longitude = "0.0";
latitude = "0.0";
}
}
}
});
}
}
public static void getDroneBattery(){
try {
SampleApplication.getProductInstance().getBattery().setStateCallback(new BatteryState.Callback() {
@Override
public void onUpdate(BatteryState batteryState) {
batteryCharge = batteryState.getChargeRemainingInPercent();
}
});
} catch (Exception ignored){
}
}
public static void setTracking(){
whichLatitude = "Drone Latitude: ";
//tracking = DJISDKManager.getInstance().getMissionControl().getWaypointMissionOperator().getCurrentState().toString();
tracking = DJISDKManager.getInstance().getMissionControl().getHotpointMissionOperator().getCurrentState().toString();
searching = DJISDKManager.getInstance().getMissionControl().getWaypointMissionOperator().getCurrentState().toString();
if(searching.equals("EXECUTING")){
whichLatitude = "Searching Drone Latitude: ";
}
following = DJISDKManager.getInstance().getMissionControl().getFollowMeMissionOperator().getCurrentState().toString();
}
public static StringBuilder getDroneState(){
BaseProduct product = SampleApplication.getProductInstance();
if (product != null && product.isConnected()) {
getDroneLocation(product);
getDroneBattery();
setTracking();
stringState.delete(0, stringState.length());
stringState.append("\n");
stringState.append("Product Connected!").append("\n");
stringState.append("Product Name: ").append(product.getModel().getDisplayName()).append("\n");
stringState.append("Battery charge: ").append(batteryCharge).append("%\n");
stringState.append(whichLatitude).append(altitude).append("m\n");
stringState.append("Drone latitude: ").append(latitude).append("\n");
stringState.append("Drone longitude: ").append(longitude).append("\n");
stringState.append("Flight State: ").append(tracking).append("\n");
stringState.append("Following State: ").append(following).append("\n");
} else {
stringState.delete(0, stringState.length());
stringState.append("No product connected.").append("\n");
}
return stringState;
}
public static StringBuilder getStreamInfo(){
BaseProduct product = SampleApplication.getProductInstance();
if (product != null && product.isConnected()) {
if(DJISDKManager.getInstance().getLiveStreamManager().isStreaming()) {
stringStream.append("Streaming live on: ").append(DJISDKManager.getInstance().getLiveStreamManager().getLiveUrl()).append("\n");
long startTime = DJISDKManager.getInstance().getLiveStreamManager().getStartTime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
String sd = sdf.format(new Date(Long.parseLong(String.valueOf(startTime))));
stringStream.append("Start Time: ").append(sd).append("\n");;
stringStream.append("Video Resolution: ").append(DJISDKManager.getInstance().getLiveStreamManager().getLiveVideoResolution()).append("\n");;
stringStream.append("Video BitRate:").append(DJISDKManager.getInstance().getLiveStreamManager().getLiveVideoBitRate()).append(" kpbs\n");
stringStream.append("Audio BitRate:").append(DJISDKManager.getInstance().getLiveStreamManager().getLiveAudioBitRate()).append(" kpbs\n");
stringStream.append("Video FPS:").append(DJISDKManager.getInstance().getLiveStreamManager().getLiveVideoFps()).append("\n");
stringStream.append("Video Cache size:").append(DJISDKManager.getInstance().getLiveStreamManager().getLiveVideoCacheSize()).append(" frame");
} else {
stringStream.append("No streaming available.").append("\n");
}
} else {
stringStream.append("No product connected.").append("\n");
}
return stringStream;
}
public static StringBuilder getDroneStatePlancia(boolean disableConsole){
BaseProduct product = SampleApplication.getProductInstance();
if (product != null && product.isConnected()) {
getDroneLocation(product);
getDroneBattery();
setTracking();
// getRealTimeData();
getSpeed();
stringStatePlancia.delete(0, stringStatePlancia.length());
// stringStatePlancia.append("\n");
stringStatePlancia.append("true").append("#");
stringStatePlancia.append(disableConsole).append("#");
stringStatePlancia.append(batteryCharge).append("#");
stringStatePlancia.append(speed).append("#");
// stringStatePlancia.append("0").append("#");
stringStatePlancia.append(altitude).append("#");
stringStatePlancia.append(latitude).append("#");
stringStatePlancia.append(longitude).append("#");
if(tracking != null && tracking.equals("EXECUTING"))
stringStatePlancia.append("true").append("#");
else
stringStatePlancia.append("false").append("#");
if(following != null && following.equals("EXECUTING"))
stringStatePlancia.append("true").append("#");
else
stringStatePlancia.append("false").append("#");
if(searching != null && searching.equals("EXECUTING"))
stringStatePlancia.append("true").append("#");
else
stringStatePlancia.append("false").append("#");
if(DJISDKManager.getInstance().getLiveStreamManager().isStreaming()) {
stringStatePlancia.append(DJISDKManager.getInstance().getLiveStreamManager().getLiveUrl()).append("#");
} else {
stringStatePlancia.append("NoStream").append("#");
}
} else {
stringStatePlancia.delete(0, stringStatePlancia.length());
stringStatePlancia.append("False").append("#");
}
return stringStatePlancia;
}
public static StringBuilder getGPSJetson(){
stringJetson = new StringBuilder();
BaseProduct product = SampleApplication.getProductInstance();
if (product != null && product.isConnected()) {
getDroneLocation(product);
stringJetson.delete(0, stringJetson.length());
//stringJetson.append("hotpoint_coordinates").append("-"); if we want to automatic search
stringJetson.append("hotpoint_jetson").append("-");
stringJetson.append(latitude).append("-");
stringJetson.append(longitude).append("-");
stringJetson.append(altitude).append("-");
stringJetson.append("5").append("\n\r");
} else {
stringJetson.append("no_connected").append("\n\r");
}
return stringJetson;
}
private static void getSpeed() {
String velX = Float.toString(flightController.getState().getVelocityX());
String velY = Float.toString(flightController.getState().getVelocityY());
String velZ = Float.toString(flightController.getState().getVelocityZ());
speed = velX + ":" + velY + ":" + velZ;
// Log.i(TAG, "speed " + speed);
}
// gets real time flight data of the aircraft with the given serial number
private static void getRealTimeData() {
//TODO:se non va togliere commento
BaseProduct product = SampleApplication.getProductInstance();
if (product instanceof Aircraft) { flightController = ((Aircraft) product).getFlightController(); }
flightController.getSerialNumber(new CommonCallbacks.CompletionCallbackWith<String>() {
@Override
public void onSuccess(String s) {
serialNumber = s;
}
@Override
public void onFailure(DJIError djiError) {
}
});
final ArrayList<String> serialNumbers = new ArrayList<>(1);
serialNumbers.add(serialNumber);
try {
DJISDKManager.getInstance().getFlightHubManager().getAircraftRealTimeFlightData(serialNumbers, new CommonCallbacks.CompletionCallbackWith<List<RealTimeFlightData>>() {
@Override
public void onSuccess(List<RealTimeFlightData> realTimeFlightData) {
for (RealTimeFlightData s : realTimeFlightData) {
speed = String.valueOf(s.getSpeed());
Log.i(TAG, "speed " + s.getSpeed());
}
}
@Override
public void onFailure(DJIError error) {
Log.i(TAG, "getAircraftRealTimeFlightData failed: " + error.getDescription());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,64 @@
package com.dji.ux.beta.sample.utils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Pair;
import android.widget.TextView;
import android.widget.Toast;
import com.dji.ux.beta.sample.SampleApplication;
public class ToastUtils {
private static final int MESSAGE_UPDATE = 1;
private static final int MESSAGE_TOAST = 2;
private static Handler mUIHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
//Get the message string
switch ((msg.what)) {
case MESSAGE_UPDATE:
showMessage((Pair<TextView, String>) msg.obj);
break;
case MESSAGE_TOAST:
showToast((String) msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
private static void showMessage(Pair<TextView, String> msg) {
if (msg != null) {
if (msg.first == null) {
Toast.makeText(SampleApplication.getInstance(), "tv is null", Toast.LENGTH_SHORT).show();
} else {
msg.first.setText(msg.second);
}
}
}
public static void showToast(String msg) {
Toast.makeText(SampleApplication.getInstance(), msg, Toast.LENGTH_SHORT).show();
}
public static void setResultToToast(final String string) {
Message msg = new Message();
msg.what = MESSAGE_TOAST;
msg.obj = string;
mUIHandler.sendMessage(msg);
}
public static void setResultToText(final TextView tv, final String s) {
Message msg = new Message();
msg.what = MESSAGE_UPDATE;
msg.obj = new Pair<>(tv, s);
mUIHandler.sendMessage(msg);
}
// public static void seToToast(Context mContext, String s) {
// Toast.makeText(mContext, s, Toast.LENGTH_SHORT).show();
// }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2018-2020 DJI
~
~ Permission is hereby granted, free of charge, to any person obtaining a copy
~ of this software and associated documentation files (the "Software"), to deal
~ in the Software without restriction, including without limitation the rights
~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
~ copies of the Software, and to permit persons to whom the Software is
~ furnished to do so, subject to the following conditions:
~
~ The above copyright notice and this permission notice shall be included in all
~ copies or substantial portions of the Software.
~
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
~ SOFTWARE.
~
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="90"
android:endColor="@color/transparent"
android:startColor="@color/uxsdk_black_40_percent" />
</shape>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2018-2020 DJI
~
~ Permission is hereby granted, free of charge, to any person obtaining a copy
~ of this software and associated documentation files (the "Software"), to deal
~ in the Software without restriction, including without limitation the rights
~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
~ copies of the Software, and to permit persons to whom the Software is
~ furnished to do so, subject to the following conditions:
~
~ The above copyright notice and this permission notice shall be included in all
~ copies or substantial portions of the Software.
~
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
~ SOFTWARE.
~
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:endColor="@color/transparent"
android:startColor="@color/uxsdk_black_40_percent" />
</shape>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2018-2020 DJI
~
~ Permission is hereby granted, free of charge, to any person obtaining a copy
~ of this software and associated documentation files (the "Software"), to deal
~ in the Software without restriction, including without limitation the rights
~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
~ copies of the Software, and to permit persons to whom the Software is
~ furnished to do so, subject to the following conditions:
~
~ The above copyright notice and this permission notice shall be included in all
~ copies or substantial portions of the Software.
~
~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
~ SOFTWARE.
~
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:endColor="@color/uxsdk_black_40_percent"
android:startColor="@color/transparent" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Some files were not shown because too many files have changed in this diff Show More