├── .gitignore ├── FPVDemo ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── dji │ │ │ └── FPVDemo │ │ │ └── ApplicationTest.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── dji │ │ │ └── FPVDemo │ │ │ ├── ConnectionActivity.java │ │ │ ├── FPVDemoApplication.java │ │ │ ├── MApplication.java │ │ │ └── MainActivity.java │ │ └── res │ │ ├── drawable │ │ ├── back_button_disable.png │ │ ├── back_button_normal.png │ │ ├── back_button_press.png │ │ ├── round_btn.xml │ │ ├── round_btn_disable.xml │ │ ├── round_btn_normal.xml │ │ ├── round_btn_pressed.xml │ │ └── selector_back_button.xml │ │ ├── layout │ │ ├── activity_connection.xml │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── raw │ │ └── keep.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── import-summary.txt └── settings.gradle ├── LICENSE.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | obj/ 15 | DJI-SDK-LIB_3.1/build/ 16 | build/ 17 | 18 | # generated file 19 | lint.xml 20 | 21 | # Local configuration file (sdk/lib path, etc) 22 | local.properties 23 | project.properties 24 | 25 | # Eclipse project files 26 | .classpath 27 | .settings/ 28 | 29 | # Proguard folder generated by Eclipse 30 | proguard/ 31 | 32 | # Intellij project files 33 | *.iml 34 | *.ipr 35 | *.iws 36 | .idea/ 37 | 38 | # Mac files 39 | .DS_Store 40 | 41 | # Gradle generated files 42 | .gradle/ 43 | 44 | # Signing files 45 | .signing/ 46 | 47 | # User-specific configurations 48 | .idea/libraries/ 49 | .idea/workspace.xml 50 | .idea/tasks.xml 51 | .idea/.name 52 | .idea/compiler.xml 53 | .idea/copyright/profiles_settings.xml 54 | .idea/encodings.xml 55 | .idea/misc.xml 56 | .idea/modules.xml 57 | .idea/scopes/scope_settings.xml 58 | .idea/vcs.xml 59 | *.iml 60 | 61 | # OS-specific files 62 | .DS_Store 63 | .DS_Store? 64 | ._* 65 | .Spotlight-V100 66 | .Trashes 67 | ehthumbs.db 68 | Thumbs.db 69 | 70 | # Import summary file 71 | import-summary.txt 72 | -------------------------------------------------------------------------------- /FPVDemo/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /FPVDemo/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | repositories { 4 | mavenLocal() 5 | } 6 | 7 | android { 8 | compileSdkVersion 30 9 | buildToolsVersion '30.0.2' 10 | useLibrary 'org.apache.http.legacy' 11 | 12 | defaultConfig { 13 | minSdkVersion 23 14 | targetSdkVersion 30 15 | multiDexEnabled true 16 | ndk { 17 | // On x86 devices that run Android API 23 or above, if the application is targeted with API 23 or 18 | // above, FFmpeg lib might lead to runtime crashes or warnings. 19 | abiFilters 'armeabi-v7a', 'arm64-v8a' 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 27 | } 28 | debug { 29 | shrinkResources true 30 | minifyEnabled true 31 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 32 | } 33 | } 34 | 35 | dexOptions { 36 | javaMaxHeapSize "4g" 37 | } 38 | 39 | packagingOptions { 40 | doNotStrip "*/*/libdjivideo.so" 41 | doNotStrip "*/*/libSDKRelativeJNI.so" 42 | doNotStrip "*/*/libFlyForbid.so" 43 | doNotStrip "*/*/libduml_vision_bokeh.so" 44 | doNotStrip "*/*/libyuv2.so" 45 | doNotStrip "*/*/libGroudStation.so" 46 | doNotStrip "*/*/libFRCorkscrew.so" 47 | doNotStrip "*/*/libUpgradeVerify.so" 48 | doNotStrip "*/*/libFR.so" 49 | doNotStrip "*/*/libDJIFlySafeCore.so" 50 | doNotStrip "*/*/libdjifs_jni.so" 51 | doNotStrip "*/*/libsfjni.so" 52 | doNotStrip "*/*/libDJICommonJNI.so" 53 | doNotStrip "*/*/libDJICSDKCommon.so" 54 | doNotStrip "*/*/libDJIUpgradeCore.so" 55 | doNotStrip "*/*/libDJIUpgradeJNI.so" 56 | doNotStrip "*/*/libDJIWaypointV2Core.so" 57 | doNotStrip "*/*/libAMapSDK_MAP_v6_9_2.so" 58 | doNotStrip "*/*/libDJIMOP.so" 59 | doNotStrip "*/*/libDJISDKLOGJNI.so" 60 | exclude 'META-INF/rxjava.properties' 61 | exclude 'assets/location_map_gps_locked.png' 62 | exclude 'assets/location_map_gps_3d.png' 63 | } 64 | 65 | compileOptions { 66 | sourceCompatibility JavaVersion.VERSION_1_8 67 | targetCompatibility JavaVersion.VERSION_1_8 68 | } 69 | } 70 | 71 | 72 | dependencies { 73 | implementation 'androidx.multidex:multidex:2.0.1' 74 | implementation 'com.squareup:otto:1.3.8' 75 | implementation('com.dji:dji-sdk:4.15', { 76 | /** 77 | * Uncomment the "library-anti-distortion" if your app does not need Anti Distortion for Mavic 2 Pro and Mavic 2 Zoom. 78 | * Uncomment the "fly-safe-database" if you need database for release, or we will download it when DJISDKManager.getInstance().registerApp 79 | * is called. 80 | * Both will greatly reducing the size of the APK. 81 | */ 82 | exclude module: 'library-anti-distortion' 83 | //exclude module: 'fly-safe-database' 84 | }) 85 | compileOnly 'com.dji:dji-sdk-provided:4.15' 86 | 87 | implementation 'androidx.appcompat:appcompat:1.2.0' 88 | implementation 'androidx.core:core:1.3.2' 89 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 90 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 91 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' 92 | implementation 'androidx.annotation:annotation:1.2.0' 93 | } 94 | -------------------------------------------------------------------------------- /FPVDemo/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keepattributes Exceptions,InnerClasses,*Annotation*,Signature,EnclosingMethod 2 | 3 | -dontoptimize 4 | -dontpreverify 5 | -dontwarn okio.** 6 | -dontwarn org.bouncycastle.** 7 | -dontwarn dji.** 8 | -dontwarn com.dji.** 9 | -dontwarn sun.** 10 | -dontwarn java.** 11 | -dontwarn com.amap.api.** 12 | -dontwarn com.here.** 13 | -dontwarn com.mapbox.** 14 | -dontwarn okhttp3.** 15 | -dontwarn retrofit2.** 16 | 17 | -keepclassmembers enum * { 18 | public static ; 19 | } 20 | 21 | -keepnames class * implements java.io.Serializable 22 | -keepclassmembers class * implements java.io.Serializable { 23 | static final long serialVersionUID; 24 | private static final java.io.ObjectStreamField[] serialPersistentFields; 25 | !static !transient ; 26 | private void writeObject(java.io.ObjectOutputStream); 27 | private void readObject(java.io.ObjectInputStream); 28 | java.lang.Object writeReplace(); 29 | java.lang.Object readResolve(); 30 | } 31 | -keep class * extends android.os.Parcelable { 32 | public static final android.os.Parcelable$Creator *; 33 | } 34 | 35 | -keep,allowshrinking class * extends dji.publics.DJIUI.** { 36 | public ; 37 | } 38 | 39 | -keep class net.sqlcipher.** { *; } 40 | 41 | -keep class net.sqlcipher.database.* { *; } 42 | 43 | -keep class dji.** { *; } 44 | 45 | -keep class com.dji.** { *; } 46 | 47 | -keep class com.google.** { *; } 48 | 49 | -keep class org.bouncycastle.** { *; } 50 | 51 | -keep,allowshrinking class org.** { *; } 52 | 53 | -keep class com.squareup.wire.** { *; } 54 | 55 | -keep class sun.misc.Unsafe { *; } 56 | 57 | -keep class com.secneo.** { *; } 58 | 59 | -keep class org.greenrobot.eventbus.**{*;} 60 | 61 | -keep class it.sauronsoftware.ftp4j.**{*;} 62 | 63 | -keepclasseswithmembers,allowshrinking class * { 64 | native ; 65 | } 66 | 67 | -keep class * implements com.google.gson.TypeAdapterFactory 68 | -keep class * implements com.google.gson.JsonSerializer 69 | -keep class * implements com.google.gson.JsonDeserializer 70 | 71 | -keep class androidx.appcompat.widget.SearchView { *; } 72 | 73 | -keepclassmembers class * extends android.app.Service 74 | -keepclassmembers public class * extends android.view.View { 75 | void set*(***); 76 | *** get*(); 77 | } 78 | -keepclassmembers class * extends android.app.Activity { 79 | public void *(android.view.View); 80 | } 81 | -keep class androidx.** { *; } 82 | -keep class android.media.** { *; } 83 | -keep class okio.** { *; } 84 | -keep class com.lmax.disruptor.** { *; } 85 | -keep class com.qx.wz.dj.rtcm.* { *; } 86 | 87 | -dontwarn com.mapbox.services.android.location.LostLocationEngine 88 | -dontwarn com.mapbox.services.android.location.MockLocationEngine 89 | -keepclassmembers class * implements android.arch.lifecycle.LifecycleObserver { 90 | (...); 91 | } 92 | # ViewModel's empty constructor is considered to be unused by proguard 93 | -keepclassmembers class * extends android.arch.lifecycle.ViewModel { 94 | (...); 95 | } 96 | # keep Lifecycle State and Event enums values 97 | -keepclassmembers class android.arch.lifecycle.Lifecycle$State { *; } 98 | -keepclassmembers class android.arch.lifecycle.Lifecycle$Event { *; } 99 | # keep methods annotated with @OnLifecycleEvent even if they seem to be unused 100 | # (Mostly for LiveData.LifecycleBoundObserver.onStateChange(), but who knows) 101 | -keepclassmembers class * { 102 | @android.arch.lifecycle.OnLifecycleEvent *; 103 | } 104 | 105 | -keepclassmembers class * implements android.arch.lifecycle.LifecycleObserver { 106 | (...); 107 | } 108 | 109 | -keep class * implements android.arch.lifecycle.LifecycleObserver { 110 | (...); 111 | } 112 | -keepclassmembers class android.arch.** { *; } 113 | -keep class android.arch.** { *; } 114 | -dontwarn android.arch.** 115 | 116 | -keep class org.apache.commons.** {*;} 117 | 118 | 119 | #<------------ utmiss config start------------> 120 | -keep class dji.sdk.utmiss.** { *; } 121 | -keep class utmisslib.** { *; } 122 | #<------------ utmiss config end------------> -------------------------------------------------------------------------------- /FPVDemo/app/src/androidTest/java/com/dji/FPVDemo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.dji.FPVDemo; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /FPVDemo/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 30 | 31 | 32 | 33 | 41 | 42 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 70 | 71 | 72 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /FPVDemo/app/src/main/java/com/dji/FPVDemo/ConnectionActivity.java: -------------------------------------------------------------------------------- 1 | package com.dji.FPVDemo; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.content.pm.PackageManager; 10 | import android.os.AsyncTask; 11 | import android.os.Build; 12 | import android.os.Bundle; 13 | import android.util.Log; 14 | import android.view.View; 15 | import android.widget.Button; 16 | import android.widget.TextView; 17 | import android.widget.Toast; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.concurrent.atomic.AtomicBoolean; 22 | 23 | import androidx.annotation.NonNull; 24 | import androidx.core.app.ActivityCompat; 25 | import androidx.core.content.ContextCompat; 26 | import dji.common.error.DJIError; 27 | import dji.common.error.DJISDKError; 28 | import dji.log.DJILog; 29 | import dji.sdk.base.BaseComponent; 30 | import dji.sdk.base.BaseProduct; 31 | import dji.sdk.products.Aircraft; 32 | import dji.sdk.sdkmanager.DJISDKInitEvent; 33 | import dji.sdk.sdkmanager.DJISDKManager; 34 | 35 | public class ConnectionActivity extends Activity implements View.OnClickListener { 36 | 37 | private static final String TAG = ConnectionActivity.class.getName(); 38 | 39 | private TextView mTextConnectionStatus; 40 | private TextView mTextProduct; 41 | private TextView mVersionTv; 42 | private Button mBtnOpen; 43 | private static final String[] REQUIRED_PERMISSION_LIST = new String[]{ 44 | Manifest.permission.VIBRATE, 45 | Manifest.permission.INTERNET, 46 | Manifest.permission.ACCESS_WIFI_STATE, 47 | Manifest.permission.WAKE_LOCK, 48 | Manifest.permission.ACCESS_COARSE_LOCATION, 49 | Manifest.permission.ACCESS_NETWORK_STATE, 50 | Manifest.permission.ACCESS_FINE_LOCATION, 51 | Manifest.permission.CHANGE_WIFI_STATE, 52 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 53 | Manifest.permission.BLUETOOTH, 54 | Manifest.permission.BLUETOOTH_ADMIN, 55 | Manifest.permission.READ_EXTERNAL_STORAGE, 56 | Manifest.permission.READ_PHONE_STATE, 57 | }; 58 | private List missingPermission = new ArrayList<>(); 59 | private AtomicBoolean isRegistrationInProgress = new AtomicBoolean(false); 60 | private static final int REQUEST_PERMISSION_CODE = 12345; 61 | 62 | @Override 63 | protected void onCreate(Bundle savedInstanceState) { 64 | super.onCreate(savedInstanceState); 65 | checkAndRequestPermissions(); 66 | setContentView(R.layout.activity_connection); 67 | initUI(); 68 | // Register the broadcast receiver for receiving the device connection's changes. 69 | IntentFilter filter = new IntentFilter(); 70 | filter.addAction(FPVDemoApplication.FLAG_CONNECTION_CHANGE); 71 | registerReceiver(mReceiver, filter); 72 | } 73 | 74 | /** 75 | * Checks if there is any missing permissions, and 76 | * requests runtime permission if needed. 77 | */ 78 | private void checkAndRequestPermissions() { 79 | // Check for permissions 80 | for (String eachPermission : REQUIRED_PERMISSION_LIST) { 81 | if (ContextCompat.checkSelfPermission(this, eachPermission) != PackageManager.PERMISSION_GRANTED) { 82 | missingPermission.add(eachPermission); 83 | } 84 | } 85 | // Request for missing permissions 86 | if (!missingPermission.isEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 87 | ActivityCompat.requestPermissions(this, 88 | missingPermission.toArray(new String[missingPermission.size()]), 89 | REQUEST_PERMISSION_CODE); 90 | } 91 | } 92 | 93 | /** 94 | * Result of runtime permission request 95 | */ 96 | @Override 97 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 98 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 99 | // Check for granted permission and remove from missing list 100 | if (requestCode == REQUEST_PERMISSION_CODE) { 101 | for (int i = grantResults.length - 1; i >= 0; i--) { 102 | if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { 103 | missingPermission.remove(permissions[i]); 104 | } 105 | } 106 | } 107 | // If there is enough permission, we will start the registration 108 | if (missingPermission.isEmpty()) { 109 | startSDKRegistration(); 110 | } else { 111 | showToast("Missing permissions!!!"); 112 | } 113 | } 114 | 115 | private void startSDKRegistration() { 116 | if (isRegistrationInProgress.compareAndSet(false, true)) { 117 | AsyncTask.execute(new Runnable() { 118 | @Override 119 | public void run() { 120 | showToast( "registering, pls wait..."); 121 | DJISDKManager.getInstance().registerApp(getApplicationContext(), new DJISDKManager.SDKManagerCallback() { 122 | @Override 123 | public void onRegister(DJIError djiError) { 124 | if (djiError == DJISDKError.REGISTRATION_SUCCESS) { 125 | DJILog.e("App registration", DJISDKError.REGISTRATION_SUCCESS.getDescription()); 126 | DJISDKManager.getInstance().startConnectionToProduct(); 127 | showToast("Register Success"); 128 | } else { 129 | showToast( "Register sdk fails, check network is available"); 130 | } 131 | Log.v(TAG, djiError.getDescription()); 132 | } 133 | 134 | @Override 135 | public void onProductDisconnect() { 136 | Log.d(TAG, "onProductDisconnect"); 137 | showToast("Product Disconnected"); 138 | 139 | } 140 | @Override 141 | public void onProductConnect(BaseProduct baseProduct) { 142 | Log.d(TAG, String.format("onProductConnect newProduct:%s", baseProduct)); 143 | showToast("Product Connected"); 144 | 145 | } 146 | 147 | @Override 148 | public void onProductChanged(BaseProduct baseProduct) { 149 | 150 | } 151 | 152 | @Override 153 | public void onComponentChange(BaseProduct.ComponentKey componentKey, BaseComponent oldComponent, 154 | BaseComponent newComponent) { 155 | 156 | if (newComponent != null) { 157 | newComponent.setComponentListener(new BaseComponent.ComponentListener() { 158 | 159 | @Override 160 | public void onConnectivityChange(boolean isConnected) { 161 | Log.d(TAG, "onComponentConnectivityChanged: " + isConnected); 162 | } 163 | }); 164 | } 165 | Log.d(TAG, 166 | String.format("onComponentChange key:%s, oldComponent:%s, newComponent:%s", 167 | componentKey, 168 | oldComponent, 169 | newComponent)); 170 | 171 | } 172 | 173 | @Override 174 | public void onInitProcess(DJISDKInitEvent djisdkInitEvent, int i) { 175 | 176 | } 177 | 178 | @Override 179 | public void onDatabaseDownloadProgress(long l, long l1) { 180 | 181 | } 182 | }); 183 | } 184 | }); 185 | } 186 | } 187 | 188 | @Override 189 | public void onResume() { 190 | Log.e(TAG, "onResume"); 191 | super.onResume(); 192 | } 193 | 194 | @Override 195 | public void onPause() { 196 | Log.e(TAG, "onPause"); 197 | super.onPause(); 198 | } 199 | 200 | @Override 201 | public void onStop() { 202 | Log.e(TAG, "onStop"); 203 | super.onStop(); 204 | } 205 | 206 | @Override 207 | protected void onDestroy() { 208 | Log.e(TAG, "onDestroy"); 209 | unregisterReceiver(mReceiver); 210 | super.onDestroy(); 211 | } 212 | 213 | private void initUI() { 214 | 215 | mTextConnectionStatus = (TextView) findViewById(R.id.text_connection_status); 216 | mTextProduct = (TextView) findViewById(R.id.text_product_info); 217 | mBtnOpen = (Button) findViewById(R.id.btn_open); 218 | mBtnOpen.setOnClickListener(this); 219 | mBtnOpen.setEnabled(false); 220 | mVersionTv = (TextView) findViewById(R.id.textView2); 221 | mVersionTv.setText(getResources().getString(R.string.sdk_version, DJISDKManager.getInstance().getRegistrationSDKVersion())); 222 | } 223 | 224 | protected BroadcastReceiver mReceiver = new BroadcastReceiver() { 225 | 226 | @Override 227 | public void onReceive(Context context, Intent intent) { 228 | refreshSDKRelativeUI(); 229 | } 230 | }; 231 | 232 | private void refreshSDKRelativeUI() { 233 | BaseProduct mProduct = FPVDemoApplication.getProductInstance(); 234 | 235 | if (null != mProduct && mProduct.isConnected()) { 236 | 237 | Log.v(TAG, "refreshSDK: True"); 238 | mBtnOpen.setEnabled(true); 239 | 240 | String str = mProduct instanceof Aircraft ? "DJIAircraft" : "DJIHandHeld"; 241 | mTextConnectionStatus.setText("Status: " + str + " connected"); 242 | 243 | if (null != mProduct.getModel()) { 244 | mTextProduct.setText("" + mProduct.getModel().getDisplayName()); 245 | } else { 246 | mTextProduct.setText(R.string.product_information); 247 | } 248 | 249 | } else { 250 | 251 | Log.v(TAG, "refreshSDK: False"); 252 | mBtnOpen.setEnabled(false); 253 | 254 | mTextProduct.setText(R.string.product_information); 255 | mTextConnectionStatus.setText(R.string.connection_loose); 256 | } 257 | } 258 | 259 | @Override 260 | public void onClick(View v) { 261 | switch (v.getId()) { 262 | 263 | case R.id.btn_open: { 264 | Intent intent = new Intent(this, MainActivity.class); 265 | startActivity(intent); 266 | break; 267 | } 268 | default: 269 | break; 270 | } 271 | } 272 | 273 | private void showToast(final String toastMsg) { 274 | runOnUiThread(new Runnable() { 275 | @Override 276 | public void run() { 277 | Toast.makeText(getApplicationContext(), toastMsg, Toast.LENGTH_LONG).show(); 278 | 279 | } 280 | }); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /FPVDemo/app/src/main/java/com/dji/FPVDemo/FPVDemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.dji.FPVDemo; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Build; 7 | import android.os.Handler; 8 | import android.os.Looper; 9 | import android.util.Log; 10 | import android.widget.Toast; 11 | 12 | import androidx.core.content.ContextCompat; 13 | 14 | import dji.common.error.DJIError; 15 | import dji.common.error.DJISDKError; 16 | import dji.sdk.base.BaseComponent; 17 | import dji.sdk.base.BaseProduct; 18 | import dji.sdk.camera.Camera; 19 | import dji.sdk.products.Aircraft; 20 | import dji.sdk.products.HandHeld; 21 | import dji.sdk.sdkmanager.DJISDKInitEvent; 22 | import dji.sdk.sdkmanager.DJISDKManager; 23 | 24 | public class FPVDemoApplication extends Application{ 25 | 26 | public static final String FLAG_CONNECTION_CHANGE = "fpv_tutorial_connection_change"; 27 | 28 | private static BaseProduct mProduct; 29 | public Handler mHandler; 30 | 31 | private Application instance; 32 | 33 | public void setContext(Application application) { 34 | instance = application; 35 | } 36 | 37 | @Override 38 | public Context getApplicationContext() { 39 | return instance; 40 | } 41 | 42 | public FPVDemoApplication() { 43 | 44 | } 45 | 46 | /** 47 | * This function is used to get the instance of DJIBaseProduct. 48 | * If no product is connected, it returns null. 49 | */ 50 | public static synchronized BaseProduct getProductInstance() { 51 | if (null == mProduct) { 52 | mProduct = DJISDKManager.getInstance().getProduct(); 53 | } 54 | return mProduct; 55 | } 56 | 57 | public static synchronized Camera getCameraInstance() { 58 | if (getProductInstance() == null) return null; 59 | Camera camera = null; 60 | if (getProductInstance() instanceof Aircraft){ 61 | camera = ((Aircraft) getProductInstance()).getCamera(); 62 | } else if (getProductInstance() instanceof HandHeld) { 63 | camera = ((HandHeld) getProductInstance()).getCamera(); 64 | } 65 | return camera; 66 | } 67 | 68 | @Override 69 | public void onCreate() { 70 | super.onCreate(); 71 | mHandler = new Handler(Looper.getMainLooper()); 72 | 73 | /** 74 | * When starting SDK services, an instance of interface DJISDKManager.DJISDKManagerCallback will be used to listen to 75 | * the SDK Registration result and the product changing. 76 | */ 77 | //Listens to the SDK registration result 78 | DJISDKManager.SDKManagerCallback mDJISDKManagerCallback = new DJISDKManager.SDKManagerCallback() { 79 | 80 | //Listens to the SDK registration result 81 | @Override 82 | public void onRegister(DJIError djiError) { 83 | Handler handler = new Handler(Looper.getMainLooper()); 84 | if (djiError == DJISDKError.REGISTRATION_SUCCESS) { 85 | handler.post(new Runnable() { 86 | @Override 87 | public void run() { 88 | Toast.makeText(getApplicationContext(), "SDK register success", Toast.LENGTH_LONG).show(); 89 | } 90 | }); 91 | DJISDKManager.getInstance().startConnectionToProduct(); 92 | } else { 93 | handler.post(new Runnable() { 94 | @Override 95 | public void run() { 96 | Toast.makeText(getApplicationContext(), "SDK register fail", Toast.LENGTH_LONG).show(); 97 | } 98 | }); 99 | 100 | } 101 | } 102 | 103 | @Override 104 | public void onProductDisconnect() { 105 | notifyStatusChange(); 106 | } 107 | 108 | @Override 109 | public void onProductConnect(BaseProduct baseProduct) { 110 | notifyStatusChange(); 111 | } 112 | 113 | @Override 114 | public void onProductChanged(BaseProduct baseProduct) { 115 | notifyStatusChange(); 116 | } 117 | 118 | @Override 119 | public void onComponentChange(BaseProduct.ComponentKey componentKey, BaseComponent oldComponent, BaseComponent newComponent) { 120 | if (newComponent != null) { 121 | newComponent.setComponentListener(new BaseComponent.ComponentListener() { 122 | @Override 123 | public void onConnectivityChange(boolean isConnected) { 124 | notifyStatusChange(); 125 | } 126 | }); 127 | } 128 | } 129 | 130 | @Override 131 | public void onInitProcess(DJISDKInitEvent djisdkInitEvent, int i) { 132 | 133 | } 134 | 135 | @Override 136 | public void onDatabaseDownloadProgress(long l, long l1) { 137 | 138 | } 139 | 140 | }; 141 | 142 | //Check the permissions before registering the application for android system 6.0 above. 143 | int permissionCheck = ContextCompat.checkSelfPermission(getApplicationContext(), android.Manifest.permission.WRITE_EXTERNAL_STORAGE); 144 | int permissionCheck2 = ContextCompat.checkSelfPermission(getApplicationContext(), android.Manifest.permission.READ_PHONE_STATE); 145 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || (permissionCheck == 0 && permissionCheck2 == 0)) { 146 | //This is used to start SDK services and initiate SDK. 147 | DJISDKManager.getInstance().registerApp(getApplicationContext(), mDJISDKManagerCallback); 148 | Toast.makeText(getApplicationContext(), "registering, pls wait...", Toast.LENGTH_LONG).show(); 149 | 150 | } else { 151 | Toast.makeText(getApplicationContext(), "Please check if the permission is granted.", Toast.LENGTH_LONG).show(); 152 | } 153 | } 154 | 155 | private void notifyStatusChange() { 156 | mHandler.removeCallbacks(updateRunnable); 157 | mHandler.postDelayed(updateRunnable, 500); 158 | } 159 | 160 | private Runnable updateRunnable = new Runnable() { 161 | @Override 162 | public void run() { 163 | Intent intent = new Intent(FLAG_CONNECTION_CHANGE); 164 | getApplicationContext().sendBroadcast(intent); 165 | } 166 | }; 167 | } 168 | -------------------------------------------------------------------------------- /FPVDemo/app/src/main/java/com/dji/FPVDemo/MApplication.java: -------------------------------------------------------------------------------- 1 | package com.dji.FPVDemo; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.secneo.sdk.Helper; 7 | 8 | public class MApplication extends Application { 9 | 10 | private FPVDemoApplication fpvDemoApplication; 11 | @Override 12 | protected void attachBaseContext(Context paramContext) { 13 | super.attachBaseContext(paramContext); 14 | Helper.install(MApplication.this); 15 | if (fpvDemoApplication == null) { 16 | fpvDemoApplication = new FPVDemoApplication(); 17 | fpvDemoApplication.setContext(this); 18 | } 19 | } 20 | 21 | @Override 22 | public void onCreate() { 23 | super.onCreate(); 24 | fpvDemoApplication.onCreate(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FPVDemo/app/src/main/java/com/dji/FPVDemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.dji.FPVDemo; 2 | 3 | import android.app.Activity; 4 | import android.graphics.SurfaceTexture; 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.util.Log; 8 | import android.view.TextureView; 9 | import android.view.View; 10 | import android.view.View.OnClickListener; 11 | import android.view.TextureView.SurfaceTextureListener; 12 | import android.widget.Button; 13 | import android.widget.CompoundButton; 14 | import android.widget.TextView; 15 | import android.widget.Toast; 16 | import android.widget.ToggleButton; 17 | 18 | import dji.common.camera.SettingsDefinitions; 19 | import dji.common.camera.SystemState; 20 | import dji.common.error.DJIError; 21 | import dji.common.product.Model; 22 | import dji.common.useraccount.UserAccountState; 23 | import dji.common.util.CommonCallbacks; 24 | import dji.sdk.base.BaseProduct; 25 | import dji.sdk.camera.Camera; 26 | import dji.sdk.camera.VideoFeeder; 27 | import dji.sdk.codec.DJICodecManager; 28 | import dji.sdk.useraccount.UserAccountManager; 29 | 30 | public class MainActivity extends Activity implements SurfaceTextureListener,OnClickListener{ 31 | 32 | private static final String TAG = MainActivity.class.getName(); 33 | protected VideoFeeder.VideoDataListener mReceivedVideoDataListener = null; 34 | 35 | // Codec for video live view 36 | protected DJICodecManager mCodecManager = null; 37 | 38 | protected TextureView mVideoSurface = null; 39 | private Button mCaptureBtn, mShootPhotoModeBtn, mRecordVideoModeBtn; 40 | private ToggleButton mRecordBtn; 41 | private TextView recordingTime; 42 | 43 | private Handler handler; 44 | 45 | @Override 46 | protected void onCreate(Bundle savedInstanceState) { 47 | 48 | super.onCreate(savedInstanceState); 49 | setContentView(R.layout.activity_main); 50 | 51 | handler = new Handler(); 52 | 53 | initUI(); 54 | 55 | // The callback for receiving the raw H264 video data for camera live view 56 | mReceivedVideoDataListener = new VideoFeeder.VideoDataListener() { 57 | 58 | @Override 59 | public void onReceive(byte[] videoBuffer, int size) { 60 | if (mCodecManager != null) { 61 | mCodecManager.sendDataToDecoder(videoBuffer, size); 62 | } 63 | } 64 | }; 65 | 66 | Camera camera = FPVDemoApplication.getCameraInstance(); 67 | 68 | if (camera != null) { 69 | 70 | camera.setSystemStateCallback(new SystemState.Callback() { 71 | @Override 72 | public void onUpdate(SystemState cameraSystemState) { 73 | if (null != cameraSystemState) { 74 | 75 | int recordTime = cameraSystemState.getCurrentVideoRecordingTimeInSeconds(); 76 | int minutes = (recordTime % 3600) / 60; 77 | int seconds = recordTime % 60; 78 | 79 | final String timeString = String.format("%02d:%02d", minutes, seconds); 80 | final boolean isVideoRecording = cameraSystemState.isRecording(); 81 | 82 | MainActivity.this.runOnUiThread(new Runnable() { 83 | 84 | @Override 85 | public void run() { 86 | 87 | recordingTime.setText(timeString); 88 | 89 | /* 90 | * Update recordingTime TextView visibility and mRecordBtn's check state 91 | */ 92 | if (isVideoRecording){ 93 | recordingTime.setVisibility(View.VISIBLE); 94 | }else 95 | { 96 | recordingTime.setVisibility(View.INVISIBLE); 97 | } 98 | } 99 | }); 100 | } 101 | } 102 | }); 103 | 104 | } 105 | 106 | } 107 | 108 | protected void onProductChange() { 109 | initPreviewer(); 110 | loginAccount(); 111 | } 112 | 113 | private void loginAccount(){ 114 | 115 | UserAccountManager.getInstance().logIntoDJIUserAccount(this, 116 | new CommonCallbacks.CompletionCallbackWith() { 117 | @Override 118 | public void onSuccess(final UserAccountState userAccountState) { 119 | Log.e(TAG, "Login Success"); 120 | } 121 | @Override 122 | public void onFailure(DJIError error) { 123 | showToast("Login Error:" 124 | + error.getDescription()); 125 | } 126 | }); 127 | } 128 | 129 | @Override 130 | public void onResume() { 131 | Log.e(TAG, "onResume"); 132 | super.onResume(); 133 | initPreviewer(); 134 | onProductChange(); 135 | 136 | if(mVideoSurface == null) { 137 | Log.e(TAG, "mVideoSurface is null"); 138 | } 139 | } 140 | 141 | @Override 142 | public void onPause() { 143 | Log.e(TAG, "onPause"); 144 | uninitPreviewer(); 145 | super.onPause(); 146 | } 147 | 148 | @Override 149 | public void onStop() { 150 | Log.e(TAG, "onStop"); 151 | super.onStop(); 152 | } 153 | 154 | public void onReturn(View view){ 155 | Log.e(TAG, "onReturn"); 156 | this.finish(); 157 | } 158 | 159 | @Override 160 | protected void onDestroy() { 161 | Log.e(TAG, "onDestroy"); 162 | uninitPreviewer(); 163 | super.onDestroy(); 164 | } 165 | 166 | private void initUI() { 167 | // init mVideoSurface 168 | mVideoSurface = (TextureView)findViewById(R.id.video_previewer_surface); 169 | 170 | recordingTime = (TextView) findViewById(R.id.timer); 171 | mCaptureBtn = (Button) findViewById(R.id.btn_capture); 172 | mRecordBtn = (ToggleButton) findViewById(R.id.btn_record); 173 | mShootPhotoModeBtn = (Button) findViewById(R.id.btn_shoot_photo_mode); 174 | mRecordVideoModeBtn = (Button) findViewById(R.id.btn_record_video_mode); 175 | 176 | if (null != mVideoSurface) { 177 | mVideoSurface.setSurfaceTextureListener(this); 178 | } 179 | 180 | mCaptureBtn.setOnClickListener(this); 181 | mRecordBtn.setOnClickListener(this); 182 | mShootPhotoModeBtn.setOnClickListener(this); 183 | mRecordVideoModeBtn.setOnClickListener(this); 184 | 185 | recordingTime.setVisibility(View.INVISIBLE); 186 | 187 | mRecordBtn.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 188 | @Override 189 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 190 | if (isChecked) { 191 | startRecord(); 192 | } else { 193 | stopRecord(); 194 | } 195 | } 196 | }); 197 | } 198 | 199 | private void initPreviewer() { 200 | 201 | BaseProduct product = FPVDemoApplication.getProductInstance(); 202 | 203 | if (product == null || !product.isConnected()) { 204 | showToast(getString(R.string.disconnected)); 205 | } else { 206 | if (null != mVideoSurface) { 207 | mVideoSurface.setSurfaceTextureListener(this); 208 | } 209 | if (!product.getModel().equals(Model.UNKNOWN_AIRCRAFT)) { 210 | VideoFeeder.getInstance().getPrimaryVideoFeed().addVideoDataListener(mReceivedVideoDataListener); 211 | } 212 | } 213 | } 214 | 215 | private void uninitPreviewer() { 216 | Camera camera = FPVDemoApplication.getCameraInstance(); 217 | if (camera != null){ 218 | // Reset the callback 219 | VideoFeeder.getInstance().getPrimaryVideoFeed().addVideoDataListener(null); 220 | } 221 | } 222 | 223 | @Override 224 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 225 | Log.e(TAG, "onSurfaceTextureAvailable"); 226 | if (mCodecManager == null) { 227 | mCodecManager = new DJICodecManager(this, surface, width, height); 228 | } 229 | } 230 | 231 | @Override 232 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 233 | Log.e(TAG, "onSurfaceTextureSizeChanged"); 234 | } 235 | 236 | @Override 237 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 238 | Log.e(TAG,"onSurfaceTextureDestroyed"); 239 | if (mCodecManager != null) { 240 | mCodecManager.cleanSurface(); 241 | mCodecManager = null; 242 | } 243 | 244 | return false; 245 | } 246 | 247 | @Override 248 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 249 | } 250 | 251 | public void showToast(final String msg) { 252 | runOnUiThread(new Runnable() { 253 | public void run() { 254 | Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show(); 255 | } 256 | }); 257 | } 258 | 259 | @Override 260 | public void onClick(View v) { 261 | 262 | switch (v.getId()) { 263 | case R.id.btn_capture: 264 | captureAction(); 265 | break; 266 | case R.id.btn_shoot_photo_mode: 267 | if (isMavicAir2() || isM300()) { 268 | switchCameraFlatMode(SettingsDefinitions.FlatCameraMode.PHOTO_SINGLE); 269 | }else { 270 | switchCameraMode(SettingsDefinitions.CameraMode.SHOOT_PHOTO); 271 | } 272 | break; 273 | case R.id.btn_record_video_mode: 274 | if (isMavicAir2() || isM300()) { 275 | switchCameraFlatMode(SettingsDefinitions.FlatCameraMode.VIDEO_NORMAL); 276 | }else { 277 | switchCameraMode(SettingsDefinitions.CameraMode.RECORD_VIDEO); 278 | } 279 | break; 280 | default: 281 | break; 282 | } 283 | } 284 | 285 | private void switchCameraFlatMode(SettingsDefinitions.FlatCameraMode flatCameraMode){ 286 | Camera camera = FPVDemoApplication.getCameraInstance(); 287 | if (camera != null) { 288 | camera.setFlatMode(flatCameraMode, error -> { 289 | if (error == null) { 290 | showToast("Switch Camera Flat Mode Succeeded"); 291 | } else { 292 | showToast(error.getDescription()); 293 | } 294 | }); 295 | } 296 | } 297 | 298 | private void switchCameraMode(SettingsDefinitions.CameraMode cameraMode){ 299 | Camera camera = FPVDemoApplication.getCameraInstance(); 300 | if (camera != null) { 301 | camera.setMode(cameraMode, error -> { 302 | if (error == null) { 303 | showToast("Switch Camera Mode Succeeded"); 304 | } else { 305 | showToast(error.getDescription()); 306 | } 307 | }); 308 | } 309 | } 310 | 311 | // Method for taking photo 312 | private void captureAction(){ 313 | final Camera camera = FPVDemoApplication.getCameraInstance(); 314 | if (camera != null) { 315 | if (isMavicAir2() || isM300()) { 316 | camera.setFlatMode(SettingsDefinitions.FlatCameraMode.PHOTO_SINGLE, djiError -> { 317 | if (null == djiError) { 318 | takePhoto(); 319 | } 320 | }); 321 | }else { 322 | camera.setShootPhotoMode(SettingsDefinitions.ShootPhotoMode.SINGLE, djiError -> { 323 | if (null == djiError) { 324 | takePhoto(); 325 | } 326 | }); 327 | } 328 | } 329 | } 330 | 331 | private void takePhoto(){ 332 | final Camera camera = FPVDemoApplication.getCameraInstance(); 333 | if (camera == null){ 334 | return; 335 | } 336 | handler.postDelayed(new Runnable() { 337 | @Override 338 | public void run() { 339 | camera.startShootPhoto(djiError -> { 340 | if (djiError == null) { 341 | showToast("take photo: success"); 342 | } else { 343 | showToast(djiError.getDescription()); 344 | } 345 | }); 346 | } 347 | }, 2000); 348 | } 349 | 350 | // Method for starting recording 351 | private void startRecord(){ 352 | 353 | final Camera camera = FPVDemoApplication.getCameraInstance(); 354 | if (camera != null) { 355 | camera.startRecordVideo(djiError -> { 356 | if (djiError == null) { 357 | showToast("Record video: success"); 358 | }else { 359 | showToast(djiError.getDescription()); 360 | } 361 | }); // Execute the startRecordVideo API 362 | } 363 | } 364 | 365 | // Method for stopping recording 366 | private void stopRecord(){ 367 | 368 | Camera camera = FPVDemoApplication.getCameraInstance(); 369 | if (camera != null) { 370 | camera.stopRecordVideo(djiError -> { 371 | if(djiError == null) { 372 | showToast("Stop recording: success"); 373 | }else { 374 | showToast(djiError.getDescription()); 375 | } 376 | }); // Execute the stopRecordVideo API 377 | } 378 | } 379 | 380 | private boolean isMavicAir2(){ 381 | BaseProduct baseProduct = FPVDemoApplication.getProductInstance(); 382 | if (baseProduct != null) { 383 | return baseProduct.getModel() == Model.MAVIC_AIR_2; 384 | } 385 | return false; 386 | } 387 | 388 | private boolean isM300(){ 389 | BaseProduct baseProduct = FPVDemoApplication.getProductInstance(); 390 | if (baseProduct != null) { 391 | return baseProduct.getModel() == Model.MATRICE_300_RTK; 392 | } 393 | return false; 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /FPVDemo/app/src/main/res/drawable/back_button_disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DJI-Mobile-SDK-Tutorials/Android-FPVDemo/eb2165529de2dfe12f55983f1aeba7fd3231c099/FPVDemo/app/src/main/res/drawable/back_button_disable.png -------------------------------------------------------------------------------- /FPVDemo/app/src/main/res/drawable/back_button_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DJI-Mobile-SDK-Tutorials/Android-FPVDemo/eb2165529de2dfe12f55983f1aeba7fd3231c099/FPVDemo/app/src/main/res/drawable/back_button_normal.png -------------------------------------------------------------------------------- /FPVDemo/app/src/main/res/drawable/back_button_press.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DJI-Mobile-SDK-Tutorials/Android-FPVDemo/eb2165529de2dfe12f55983f1aeba7fd3231c099/FPVDemo/app/src/main/res/drawable/back_button_press.png -------------------------------------------------------------------------------- /FPVDemo/app/src/main/res/drawable/round_btn.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FPVDemo/app/src/main/res/drawable/round_btn_disable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /FPVDemo/app/src/main/res/drawable/round_btn_normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /FPVDemo/app/src/main/res/drawable/round_btn_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /FPVDemo/app/src/main/res/drawable/selector_back_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /FPVDemo/app/src/main/res/layout/activity_connection.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 33 | 34 |