Insta360Develop / CameraSDK-Android

Android SDK to control Insta360 cameras
81 stars 9 forks source link

Demo app is not able to open camera on Pixel 3 device #10

Open gshcherb opened 3 years ago

gshcherb commented 3 years ago

SDK v 1.3.10. Android 11.

After launch demo app is not able to open the camera, even though device is already connected to camera wifi. Works fine on Samsung devices.

Logs:

E/OneCameraSocketIO: SocketException Connect timeout (connectWithTO): Operation now in progress I/onedriver: open wifi -1 E/com.arashivision.camera.command.OpenWifiCmd: failed init wifi: -1 W/Logger: BaseCamera: onCameraError, error: -1 D/Logger: BaseCamera: changeCameraStatus, newStatus: [ERROR], oldStatus: [OPENING], connectType: [WIFI], errorCode: [-14041] D/Logger: BaseCamera: destroy D/Logger: BaseCamera: changeCameraStatus, newStatus: [CLOSING], oldStatus: [ERROR], connectType: [WIFI], errorCode: [0]

EternalSoySauce commented 3 years ago

Check whether the network proxy is enabled. If the proxy is enabled, it is impossible to establish a socket with the camera

gshcherb commented 3 years ago

Thanks for suggestion. Network proxy is definitely not configured on the camera wifi connection. I can connect to camera from official Insta360 app just fine, bud demo app fails to connect. I see the same fail when I try to replicate camera opening using the code from the demo app.

Do you have any other ideas what to check, or maybe you can share how the connection is done in the official insta app?

EternalSoySauce commented 3 years ago

Have you confirmed that the insta app has been completely closed before connecting with the demo? You can first go to the application details page and click the forced close button. The insta app and sdk demo use the same dependency library to establish a connection with the camera. There should not be one success and the other failure.

gshcherb commented 3 years ago

Please take a look at the following video https://drive.google.com/file/d/1224sWB4cC_0RjIifVGQOF0N8e12DqB52/view?usp=drivesdk

Official app seemingly does something with Bluetooth before connecting WiFi and succeeds, while demo app is directly connecting to WiFi. I thought this may be a difference, but exactly the same scenario works fine on Samsung S20 and fails on Pixel 3 XL.

EternalSoySauce commented 3 years ago

The official app first connects to Bluetooth because there are some business logic for network requests in it, and it will also use Bluetooth to obtain the camera's wifi and password to automatically switch the system wifi, but in the SDK demo you have manually connected to the system wifi, so no need for Bluetooth connection

gshcherb commented 3 years ago

Do you have any ideas why connection fails as on the video?

EternalSoySauce commented 3 years ago

Oh I see. The official app has an extra network binding operation before connecting to ensure that the wifi connection is successful. Try to turn on your phone in airplane mode, and then connect in the sdk demo. If it could be successful, you can also add the following code to your own app, this belongs to business logic, so it is not integrated into the SDK.

/**
 * Permission check:CHANGE_WIFI_STATE and WRITE_SETTINGS
 * 
 * Main process:Bind the camera WIFI before openCamera() -> bindNetwork(),
 * Unbind WIFI before closeCamera() or when the camera is disconnected -> unbindNetwork()
 *
 */
public class CameraBindNetworkManager {

    private static String ACTION_BIND_NETWORK_NOTIFY = "com.xxxxx.ACTION_BIND_NETWORK_NOTIFY";
    private static String EXTRA_KEY_IS_BIND = "extra_key_is_bind";

    private static CameraBindNetworkManager sInstance;

    private boolean mHasBindNetwork = false;
    private boolean mIsBindingNetwork = false;

    private ConnectivityManager.NetworkCallback networkCallback = null;

    private CameraBindNetworkManager() {
    }

    public static CameraBindNetworkManager getInstance() {
        if (sInstance == null) {
            sInstance = new CameraBindNetworkManager();
        }
        return sInstance;
    }

    // Bind the camera WIFI before connecting the camera, and then call openCamera()
    public void bindNetwork(Context context, IBindNetWorkCallback bindNetWorkCallback) {
        if (!checkPermission()) {
            return;
        }
        if (mIsBindingNetwork) {
            // Control not to bind repeatedly, do not call openCamera() if this code is called back
            if (bindNetWorkCallback != null) {
                bindNetWorkCallback.onResult(YourCustomErrorCode.CAMERA_ALREADY_BINDING_WIFI_NETWORK);
            }
        } else if (mHasBindNetwork) {
            // If you have already bound WIFI to directly call back, you can call openCamera() at this timess
            if (bindNetWorkCallback != null) {
                bindNetWorkCallback.onResult(YourCustomErrorCode.OK);
            }
        } else {
            // Start to bind WIFI, you can call openCamera() after callback
            bindWifiNet(context, bindNetWorkCallback);
        }
    }

    // Need to unbind the WIFI before disconnecting the camera, and then call closeCamera()
    public void unbindNetwork(Context context) {
        if (mHasBindNetwork) {
            unbindWifiNet(context);
        }
        if (mIsBindingNetwork) {
            mIsBindingNetwork = false;
        }
        unregisterNetworkCallback(context);
    }

    // Need to check CHANGE_WIFI_STATE permission and WRITE_SETTINGS permission
    public boolean checkPermission(Activity activity) {
        if (PermissionUtils.hasSelfPermissions(activity, Manifest.permission.CHANGE_WIFI_STATE)) {
            return true;
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (activity != null) {
                if (Settings.System.canWrite(activity)) {
                    return true;
                } else {
                    DialogUtils.showRequestWriteSettingPermissionDialog(activity);
                    return false;
                }
            }
        }
        return false;
    }

    private void bindWifiNet(Context context, IBindNetWorkCallback bindNetWorkCallback) {
        if (mIsBindingNetwork) {
            return;
        }
        mIsBindingNetwork = true;
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkRequest.Builder req = new NetworkRequest.Builder();
        // Set the specified network transmission type wifi
        req.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
        // Request the network
        networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
                if (!mIsBindingNetwork) {
                    return;
                }
                try {
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                        if (ConnectivityManager.setProcessDefaultNetwork(network)) {
                            // Bind successfully
                            mHasBindNetwork = true;
                            mIsBindingNetwork = false;
                            if (bindNetWorkCallback != null) {
                                bindNetWorkCallback.onResult(YourCustomErrorCode.OK);
                            }
                        } else {
                            // Binding failed
                            mIsBindingNetwork = false;
                            if (bindNetWorkCallback != null) {
                                bindNetWorkCallback.onResult(YourCustomErrorCode.CAMERA_BIND_NETWORK_FAIL);
                            }
                        }
                    } else {
                        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
                        if (connectivityManager.bindProcessToNetwork(network)) {
                            // Bind successfully
                            mHasBindNetwork = true;
                            mIsBindingNetwork = false;
                            if (bindNetWorkCallback != null) {
                                bindNetWorkCallback.onResult(YourCustomErrorCode.OK);
                            }
                        } else {
                            // Binding failed
                            mIsBindingNetwork = false;
                            if (bindNetWorkCallback != null) {
                                bindNetWorkCallback.onResult(YourCustomErrorCode.CAMERA_BIND_NETWORK_FAIL);
                            }
                        }
                    }
                } catch (IllegalStateException e) {
                    mIsBindingNetwork = false;
                    if (bindNetWorkCallback != null) {
                        bindNetWorkCallback.onResult(YourCustomErrorCode.CAMERA_BIND_NETWORK_FAIL);
                    }
                }
            }

            @Override
            public void onUnavailable() {
                // The requested network failed, binding failed
                mIsBindingNetwork = false;
                if (bindNetWorkCallback != null) {
                    bindNetWorkCallback.onResult(YourCustomErrorCode.CAMERA_BIND_NETWORK_FAIL);
                }
            }
        };
        cm.requestNetwork(req.build(), networkCallback);
    }

    private void unbindWifiNet(Context context) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            if (ConnectivityManager.setProcessDefaultNetwork(null)) {
                mHasBindNetwork = false;
            }
        } else {
            ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            if (connectivityManager.bindProcessToNetwork(null)) {
                mHasBindNetwork = false;
            }
        }
    }

    private void unregisterNetworkCallback(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (cm != null && networkCallback != null) {
            cm.unregisterNetworkCallback(networkCallback);
            networkCallback = null;
        }
    }

    /************************* interface *************************/
    public interface IBindNetWorkCallback {
        void onResult(int errorCode);
    }

}
gshcherb commented 3 years ago

Thanks for posting this snipped. I understand what this is doing and we had solved this issue previously for OSC communication. One drawback is - this approach binds all of the network communication to the WiFi interface and, since we are connected to camera, no internet will be available via cellular connection. Business requirement and currently implemented logic we have is use WiFi to communicate to camera and cellular for communicating to the server API at the same time.

I am not sure what SDK is using internally, but if you use OkHttp or something similar this can very easily be achieved by

    fun wifiBoundClient(client: OkHttpClient, network: Network): OkHttpClient {
        return client.newBuilder()
            .socketFactory(network.socketFactory)
            .build()
    }

https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/socket-factory/

This way even when connected to camera user will still have cellular internet connection.

Is it possible to change how SDK is sending requests or supply SDK with custom HttpClient that is pre-configured on app side like shown above?

EternalSoySauce commented 3 years ago

First of all, my code above can be bound to wifi, of course, it can also be bound to cellular network. The official app can also use the cellular network while connecting to the camera, isn't it? Secondly, the connection between the SDK and the camera is established through a socket (not http), and use our private protocol. If you use osc, you can use it independently of sdk. Osc is a pure http request and has nothing to do with sdk.

gshcherb commented 3 years ago

I am not sure how internet communication is implemented in official app, but binding forces all the new sockets to be bound to this network as per documentation. So it does not matter if it is http or socket communication.

Binds the current process to network. All Sockets created in the future (and not explicitly bound via a bound SocketFactory from Network.getSocketFactory()) will be bound to network. All host name resolutions will be limited to network as well. 

Internet may be working either if http client is explicitly bound to cellular or it uses sockets that were created before opening camera.

Anyways if you able to bind your camera connectivity sockets to wifi only as described above, this would make life easier, if not we we will have to bind process before opening camera/starting preview and unbind it immediately after that.

EternalSoySauce commented 3 years ago

"we will have to bind process before opening camera/starting preview and unbind it immediately after that." Offical app just do like this