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

Commit b143fe49 authored by Mehdi Alizadeh's avatar Mehdi Alizadeh Committed by Android (Google) Code Review
Browse files

Merge "Restores state of AppPredictionService after remote service crash" into qt-dev

parents 20a2e7ae 056ac838
Loading
Loading
Loading
Loading
+134 −3
Original line number Diff line number Diff line
@@ -28,13 +28,17 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.ServiceInfo;
import android.os.IBinder;
import android.os.RemoteException;
import android.service.appprediction.AppPredictionService;
import android.util.ArrayMap;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.server.infra.AbstractPerUserSystemService;

import java.util.ArrayList;

/**
 * Per-user instance of {@link AppPredictionManagerService}.
 */
@@ -48,6 +52,17 @@ public class AppPredictionPerUserService extends
    @GuardedBy("mLock")
    private RemoteAppPredictionService mRemoteService;

    /**
     * When {@code true}, remote service died but service state is kept so it's restored after
     * the system re-binds to it.
     */
    @GuardedBy("mLock")
    private boolean mZombie;

    @GuardedBy("mLock")
    private final ArrayMap<AppPredictionSessionId, AppPredictionSessionInfo> mSessionInfos =
            new ArrayMap<>();

    protected AppPredictionPerUserService(AppPredictionManagerService master,
            Object lock, int userId) {
        super(master, lock, userId);
@@ -92,6 +107,16 @@ public class AppPredictionPerUserService extends
        final RemoteAppPredictionService service = getRemoteServiceLocked();
        if (service != null) {
            service.onCreatePredictionSession(context, sessionId);

            mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context, () -> {
                synchronized (mLock) {
                    AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
                    if (sessionInfo != null) {
                        sessionInfo.removeAllCallbacksLocked();
                        mSessionInfos.remove(sessionId);
                    }
                }
            }));
        }
    }

@@ -140,6 +165,11 @@ public class AppPredictionPerUserService extends
        final RemoteAppPredictionService service = getRemoteServiceLocked();
        if (service != null) {
            service.registerPredictionUpdates(sessionId, callback);

            AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
            if (sessionInfo != null) {
                sessionInfo.addCallbackLocked(callback);
            }
        }
    }

@@ -152,6 +182,11 @@ public class AppPredictionPerUserService extends
        final RemoteAppPredictionService service = getRemoteServiceLocked();
        if (service != null) {
            service.unregisterPredictionUpdates(sessionId, callback);

            AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
            if (sessionInfo != null) {
                sessionInfo.removeCallbackLocked(callback);
            }
        }
    }

@@ -174,6 +209,12 @@ public class AppPredictionPerUserService extends
        final RemoteAppPredictionService service = getRemoteServiceLocked();
        if (service != null) {
            service.onDestroyPredictionSession(sessionId);

            AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
            if (sessionInfo != null) {
                sessionInfo.removeAllCallbacksLocked();
                mSessionInfos.remove(sessionId);
            }
        }
    }

@@ -182,17 +223,54 @@ public class AppPredictionPerUserService extends
        if (isDebug()) {
            Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
        }

        // Do nothing, we are just proxying to the prediction service
    }

    @Override
    public void onConnectedStateChanged(boolean connected) {
        if (isDebug()) {
            Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected);
        }
        if (connected) {
            synchronized (mLock) {
                if (mZombie) {
                    // Sanity check - shouldn't happen
                    if (mRemoteService == null) {
                        Slog.w(TAG, "Cannot resurrect sessions because remote service is null");
                        return;
                    }
                    mZombie = false;
                    resurrectSessionsLocked();
                }
            }
        }
    }

    @Override
    public void onServiceDied(RemoteAppPredictionService service) {
        if (isDebug()) {
            Slog.d(TAG, "onServiceDied():");
            Slog.w(TAG, "onServiceDied(): service=" + service);
        }
        synchronized (mLock) {
            mZombie = true;
        }
        // Do nothing, eventually the system will bind to the remote service again...
    }

        // Do nothing, we are just proxying to the prediction service
    /**
     * Called after the remote service connected, it's used to restore state from a 'zombie'
     * service (i.e., after it died).
     */
    private void resurrectSessionsLocked() {
        final int numSessions = mSessionInfos.size();
        if (isDebug()) {
            Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on "
                    + numSessions + " sessions.");
        }

        for (AppPredictionSessionInfo sessionInfo : mSessionInfos.values()) {
            sessionInfo.resurrectSessionLocked(this);
        }
    }

    @GuardedBy("mLock")
@@ -215,4 +293,57 @@ public class AppPredictionPerUserService extends

        return mRemoteService;
    }

    private static final class AppPredictionSessionInfo {
        private final AppPredictionSessionId mSessionId;
        private final AppPredictionContext mContext;
        private final ArrayList<IPredictionCallback> mCallbacks = new ArrayList<>();
        private final IBinder.DeathRecipient mBinderDeathHandler;

        AppPredictionSessionInfo(AppPredictionSessionId id, AppPredictionContext context,
                IBinder.DeathRecipient binderDeathHandler) {
            mSessionId = id;
            mContext = context;
            mBinderDeathHandler = binderDeathHandler;
        }

        void addCallbackLocked(IPredictionCallback callback) {
            if (mBinderDeathHandler != null) {
                try {
                    callback.asBinder().linkToDeath(mBinderDeathHandler, 0);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Failed to link to death: " + e);
                }
            }
            mCallbacks.add(callback);
        }

        void removeCallbackLocked(IPredictionCallback callback) {
            if (mBinderDeathHandler != null) {
                callback.asBinder().unlinkToDeath(mBinderDeathHandler, 0);
            }
            mCallbacks.remove(callback);
        }

        void removeAllCallbacksLocked() {
            if (mBinderDeathHandler != null) {
                for (IPredictionCallback callback : mCallbacks) {
                    callback.asBinder().unlinkToDeath(mBinderDeathHandler, 0);
                }
            }
            mCallbacks.clear();
        }

        void resurrectSessionLocked(AppPredictionPerUserService service) {
            if (service.isDebug()) {
                Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
                        + ") for session Id=" + mSessionId + " and "
                        + mCallbacks.size() + " callbacks.");
            }
            service.onCreatePredictionSessionLocked(mContext, mSessionId);
            for (IPredictionCallback callback : mCallbacks) {
                service.registerPredictionUpdatesLocked(mSessionId, callback);
            }
        }
    }
}
+15 −0
Original line number Diff line number Diff line
@@ -42,6 +42,8 @@ public class RemoteAppPredictionService extends

    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;

    private final RemoteAppPredictionServiceCallbacks mCallback;

    public RemoteAppPredictionService(Context context, String serviceInterface,
            ComponentName componentName, int userId,
            RemoteAppPredictionServiceCallbacks callback, boolean bindInstantServiceAllowed,
@@ -50,6 +52,7 @@ public class RemoteAppPredictionService extends
                context.getMainThreadHandler(),
                bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
                verbose, /* initialCapacity= */ 1);
        mCallback = callback;
    }

    @Override
@@ -141,5 +144,17 @@ public class RemoteAppPredictionService extends
         * Notifies a the failure or timeout of a remote call.
         */
        void onFailureOrTimeout(boolean timedOut);

        /**
         * Notifies change in connected state of the remote service.
         */
        void onConnectedStateChanged(boolean connected);
    }

    @Override // from AbstractRemoteService
    protected void handleOnConnectedStateChanged(boolean connected) {
        if (mCallback != null) {
            mCallback.onConnectedStateChanged(connected);
        }
    }
}