0

I am using a AIDL system service which interacts with serial port of HW component. My goal is on 1 method to bind to AIDL service and then connect to the HW on same method (this is a constraint).

For test, from Activity, if I bind on onCreate and use serialOpen on button event, it works (I can connect to the HW).

2021-12-09 01:15:54.322 11904-11904/com.example.hwtestapp D/RFID: Binding to serial service.. 2021-12-09 01:15:54.559 11904-11904/com.example.hwtestapp D/RFID: Service connected.

If I remove the service binding from onCreate and use only serialOpen (which take care of binding), it fails! First click with binding error, the second time, I can connect (because it has been binded on first click). I do not explain the reason of that.

First click, it waits SERVICE_BINDING_TIMEOUT and raise an error. As soon it exits from the method, it connects to the service. I tried to increase the SERVICE_BINDING_TIMEOUT behaviour is the same, it just take more delay.

2021-12-09 01:11:38.513 10927-10927/com.example.hwtestapp D/example: Binding to serial service.. 2021-12-09 01:11:39.027 10927-10927/com.example.hwtestapp E/example: Error binding to serial service 2021-12-09 01:11:39.039 10927-10927/com.example.hwtestapp I/Choreographer: Skipped 32 frames! The application may be doing too much work on its main thread. 2021-12-09 01:11:39.063 10927-10927/com.example.hwtestapp D/example: Service connected.

I tried to bind on thread, asyncTask, no effect. Bind looks to wait the end of method. it looks like to complete the binding, operating system needs to have the hand. Any idea is welcome. THanks!

    public void serialOpen(Context context) throws IOException {
    if (context == null) throw new InvalidParameterException("Context cannot be Null");
    this.context = context;
    // bind if not already done
    if(!bound) bindToSerialService(context, true);
    // wait for binding completion
    long t = SystemClock.elapsedRealtime();
    while(!bound && (SystemClock.elapsedRealtime() - t < SERVICE_BINDING_TIMEOUT)){
        SystemClock.sleep(10);
    }
    if(!bound) throw new IOException("Error binding to serial service");

    String[] ports;
    try {
        // get port names when bind has completed
        ports = serial.getPortNames();
        if (ports.length == 0) throw new IOException("No serial port detected");

        for (String p : ports) if(DEBUG) Log.d(TAG, "Port list contains: " + p);

        port = serial.open(ports[0]);
        if(DEBUG) Log.d(TAG, "Open returned " + port);

        if (port == null) throw new IOException("Error opening serial port");

        boolean ok = port.configure(baudRate, DATA_BITS, PARITY, STOP_BITS, FLOW_CONTROL);
        if(DEBUG) Log.d(TAG, "Configuration returned " + ok);
       
        //...

        isConnected = true;


    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

    private void bindToSerialService(Context context, boolean state) {
    if (context == null) throw new InvalidParameterException("Context cannot be Null");
    this.context = context;
    if(state) {
        // bind to service
        if(DEBUG) Log.d(TAG, "Binding to serial service..");
        Intent intent = new Intent().setComponent(new ComponentName("com.example.serial", "com.example.serial.SerialService"));
        context.bindService(intent, serialConnection, Context.BIND_AUTO_CREATE);
    } else {
        context.unbindService(serialConnection);
        bound = false;
        if(DEBUG) Log.d(TAG, "Unbound to serial service.");
    }
}
JMC
  • 31
  • 6

1 Answers1

0

Refer to Android how do I wait until a service is actually connected?

Android 10 has introduced a new bindService method signature when binding to a service to provide an Executor (which can be created from the Executors). It helps to fix my issue

/**
 * Same as {@link #bindService(Intent, ServiceConnection, int)} with executor to control
 * ServiceConnection callbacks.
 * @param executor Callbacks on ServiceConnection will be called on executor. Must use same
 *      instance for the same instance of ServiceConnection.
*/
public boolean bindService(@RequiresPermission @NonNull Intent service,
        @BindServiceFlags int flags, @NonNull @CallbackExecutor Executor executor,
        @NonNull ServiceConnection conn) {
    throw new RuntimeException("Not implemented. Must override in a subclass.");
}

This allows to bind to the service in a thread and wait until it is connected.

private final AtomicBoolean connected = new AtomicBoolean()
private final Object lock = new Object();

... 


/**
 * Monitors the connection to the serial service.
 */
private final ServiceConnection serialConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        synchronized (lock) {
            serial = ISerial.Stub.asInterface(iBinder);
            if (DEBUG) Log.d(TAG, "Service connected.");
            bound.set(true);
            lock.notifyAll();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        serial = null;
        if(DEBUG) Log.d(TAG, "Service disconnected.");
        bound.set(false);
    }
};

/**
 * binds to serial system service
 * @param context context
 */
public void bindToSerialService(Context context) throws IOException {
    if (context == null) throw new InvalidParameterException("Context cannot be Null");
    this.context = context;
    // bind to service
    if (DEBUG) Log.d(TAG, "Binding to serial service..");
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    Intent intent = new Intent().setComponent(new ComponentName("com.example.serial", "com.example.serial.SerialService"));
    context.bindService(intent, Context.BIND_AUTO_CREATE, executorService, serialConnection);

    // wait the connection completion to the serial service
    synchronized (lock) {
        try {
            if (DEBUG) Log.d(TAG, "Waiting the binding..");
            lock.wait(SERVICE_BINDING_TIMEOUT);
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
        }
        if (!bound.get()) {
            context.unbindService(serialConnection);
            throw new IOException("Error binding to serial service");
        }
    }
}
/**
 * unbinds to serial system service
 * @param context context
 */
public void unbindFromSerialService() {
    if(bound.get()) {
        context.unbindService(serialConnection);
        bound.set(false);
        if(DEBUG) Log.d(TAG, "Unbound to serial service.");
    } else {
        if(DEBUG) Log.d(TAG, "Not bound or already unbound to serial service.");
    }
}


public void serialOpen(Context context) throws IOException {
    if (context == null) throw new InvalidParameterException("Context cannot be Null");
    this.context = context;
    // bind to serial service if not already done
    if(!bound.get()) bindToSerialService(context);

    try {
        // get port names when bind has completed
        String[] ports = serial.getPortNames();
        if (ports.length == 0) throw new IOException("No serial port detected");

        for (String p : ports) if(DEBUG) Log.d(TAG, "Port list contains: " + p);

        port = serial.open(ports[0]);
        if(DEBUG) Log.d(TAG, "Open returned " + port);

        if (port == null) throw new IOException("Error opening serial port");

        boolean ok = port.configure(baudRate, DATA_BITS, PARITY, STOP_BITS, FLOW_CONTROL);
        if(DEBUG) Log.d(TAG, "Configuration returned " + ok);
       
        //...

        isConnected = true;


    } catch (RemoteException e) {
        e.printStackTrace();
    }
}
JMC
  • 31
  • 6