Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5a7d933b authored by David Luhmer's avatar David Luhmer
Browse files

unbind and re-bind service when service connection is lost / add more logging



Signed-off-by: default avatarDavid Luhmer <david-dev@live.de>
parent 985e3320
Loading
Loading
Loading
Loading
+36 −33
Original line number Diff line number Diff line
@@ -11,6 +11,8 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;

import androidx.annotation.NonNull;

import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap;
import com.nextcloud.android.sso.Constants;
@@ -31,8 +33,6 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

import androidx.annotation.NonNull;

import static com.nextcloud.android.sso.exceptions.SSOException.parseNextcloudCustomException;

public class AidlNetworkRequest extends NetworkRequest {
@@ -51,7 +51,7 @@ public class AidlNetworkRequest extends NetworkRequest {
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            Log.v(TAG, "onServiceConnected [" + Thread.currentThread().getName() + "]");
            Log.d(TAG, "[onServiceConnected] called from Thread: [" + Thread.currentThread().getName() + "] with IBinder [" + className.toString() + "]: " + service);

            mService = IInputStreamService.Stub.asInterface(service);
            mBound.set(true);
@@ -62,20 +62,25 @@ public class AidlNetworkRequest extends NetworkRequest {
        }

        public void onServiceDisconnected(ComponentName className) {
            Log.e(TAG, "ServiceDisconnected [" +className.toString() + "]");
            Log.w(TAG, "[onServiceDisconnected] [" + className.toString() + "]");
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
            mBound.set(false);
            // unexpectedly disconnected -- that is, its process crashed or the service was
            // terminated due to an update (e.g. google play store)

            if (!mDestroyed) {
                Log.d(TAG, "Reconnecting lost service connection");
                connectApiWithBackoff();
                // In case we're currently not reconnecting
                Log.d(TAG, "[onServiceDisconnected] Reconnecting lost service connection to component: [" + className.toString() + "]");
                reconnect();
            } else {
                // API was destroyed on purpose
                mService = null;
                mBound.set(false);
            }
        }
    };

    public void connect(String type) {
        Log.d(TAG, "[connect] Binding to AccountManagerService for type [" + type + "]");
        super.connect(type);

        String componentName = Constants.PACKAGE_NAME_PROD;
@@ -83,42 +88,50 @@ public class AidlNetworkRequest extends NetworkRequest {
            componentName = Constants.PACKAGE_NAME_DEV;
        }

        Log.d(TAG, "[connect] Component name is: [" + componentName+ "]");

        try {
            Intent intentService = new Intent();
            intentService.setComponent(new ComponentName(componentName,
                                                         "com.owncloud.android.services.AccountManagerService"));
            if (!mContext.bindService(intentService, mConnection, Context.BIND_AUTO_CREATE)) {
                Log.d(TAG, "Binding to AccountManagerService returned false");
            // https://developer.android.com/reference/android/content/Context#BIND_ABOVE_CLIENT
            if (!mContext.bindService(intentService, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_ABOVE_CLIENT)) {
                Log.d(TAG, "[connect] Binding to AccountManagerService returned false");
                throw new IllegalStateException("Binding to AccountManagerService returned false");
            } else {
                Log.d(TAG, "[connect] Bound to AccountManagerService successfully");
            }
        } catch (SecurityException e) {
            Log.e(TAG, "can't bind to AccountManagerService, check permission in Manifest");
            Log.e(TAG, "[connect] can't bind to AccountManagerService, check permission in Manifest");
            mCallback.onError(e);
        }
    }

    public void reconnect() {
        Log.d(TAG, "[reconnect] called");
        unbindService();
        connectApiWithBackoff();
    }

    public void stop() {
        super.stop();

        unbindService();
        mContext = null;
    }

    private void unbindService() {
        // Unbind from the service
        if (mBound.get()) {
            if (mContext != null) {
                Log.d(TAG, "[unbindService] Unbinding AccountManagerService");
                mContext.unbindService(mConnection);
            } else {
                Log.e(TAG, "Context was null, cannot unbind nextcloud single sign-on service connection!");
                Log.e(TAG, "[unbindService] Context was null, cannot unbind nextcloud single sign-on service connection!");
            }
            mBound.set(false);
            mContext = null;
        }
    }

    public void reconnect() {
        if (mContext != null) {
            mContext.unbindService(mConnection);
        } else {
            Log.e(TAG, "Context was null, cannot unbind nextcloud single sign-on service connection!");
            mService = null;
        }
        // the callback "onServiceDisconnected" will trigger a reconnect automatically. No need to do anything here..
    }

    private void waitForApi() throws NextcloudApiNotRespondingException {
@@ -131,16 +144,6 @@ public class AidlNetworkRequest extends NetworkRequest {

                    // If api is still not bound after 10 seconds.. try reconnecting
                    if(!mBound.get()) {
                        /*
                        // try one reconnect
                        reconnect();

                        mBound.wait(10000); // wait up to 10 seconds
                        // If api is still not bound after 10 seconds.. throw an exception
                        if(!mBound.get()) {
                            throw new NextcloudApiNotRespondingException();
                        }
                        */
                        throw new NextcloudApiNotRespondingException();
                    }
                } catch (InterruptedException ex) {
+5 −4
Original line number Diff line number Diff line
@@ -4,15 +4,14 @@ import android.content.Context;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.NonNull;

import com.nextcloud.android.sso.aidl.NextcloudRequest;
import com.nextcloud.android.sso.exceptions.NextcloudApiNotRespondingException;
import com.nextcloud.android.sso.helper.ExponentialBackoff;
import com.nextcloud.android.sso.model.SingleSignOnAccount;

import java.io.InputStream;

import androidx.annotation.NonNull;

public abstract class NetworkRequest {

    private static final String TAG = NetworkRequest.class.getCanonicalName();
@@ -31,7 +30,7 @@ public abstract class NetworkRequest {


    protected void connect(String type) {
        Log.v(TAG, "Nextcloud Single sign-on connect() called [" + Thread.currentThread().getName() + "] Account-Type: [" + type + "]");
        Log.d(TAG, "[connect] connect() called [" + Thread.currentThread().getName() + "] Account-Type: [" + type + "]");
        if (mDestroyed) {
            throw new IllegalStateException("API already destroyed! You cannot reuse a stopped API instance");
        }
@@ -42,7 +41,9 @@ public abstract class NetworkRequest {
    protected abstract Response performNetworkRequestV2(NextcloudRequest request, InputStream requestBodyInputStream) throws Exception;

    protected void connectApiWithBackoff() {
        Log.d(TAG, "[connectApiWithBackoff] connectApiWithBackoff() called from Thread: [" + Thread.currentThread().getName() + "]");
        new ExponentialBackoff(1000, 5000, 2, 5, Looper.getMainLooper(), () -> {
            Log.d(TAG, "[connectApiWithBackoff] trying to connect..");
            connect(mAccount.type);
        }, () -> {
            Log.e(TAG, "Unable to recover API");
+9 −2
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.nextcloud.android.sso.helper;

import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

@@ -27,6 +29,8 @@ import java.security.InvalidParameterException;
/** The implementation of exponential backoff with jitter applied. */
public class ExponentialBackoff {

    private static final String TAG = ExponentialBackoff.class.getCanonicalName();

    private int mRetryCounter;
    private long mStartDelayMs;
    private long mMaximumDelayMs;
@@ -107,8 +111,10 @@ public class ExponentialBackoff {
    }

    /** Should call when the retry action has failed and we want to retry after a longer delay. */
    private void notifyFailed() {
    private void notifyFailed(Exception ex) {
        Log.d(TAG, "[notifyFailed] Error: [" + ex.getMessage() + "]");
        if(mRetryCounter > mMaxRetries) {
            Log.d(TAG, "[notifyFailed] Retries exceeded, ending now");
            stop();
            mFailedCallback.run();
        } else {
@@ -116,6 +122,7 @@ public class ExponentialBackoff {
            long temp = Math.min(
                    mMaximumDelayMs, (long) (mStartDelayMs * Math.pow(mMultiplier, mRetryCounter)));
            mCurrentDelayMs = (long) (((1 + Math.random()) / 2) * temp);
            Log.d(TAG, "[notifyFailed] retrying in: [" + mCurrentDelayMs + "ms]");
            mHandlerAdapter.removeCallbacks(mRunnable);
            mHandlerAdapter.postDelayed(mRunnable, mCurrentDelayMs);
        }
@@ -134,7 +141,7 @@ public class ExponentialBackoff {
            try {
                runnable.run();
            } catch (Exception ex) {
                notifyFailed();
                notifyFailed(ex);
            }
        }
    }