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

Commit 19b56ffb authored by sandeepbandaru's avatar sandeepbandaru
Browse files

Add logic to unbind based on callback completions

This CL converts the erstwhile ServiceConnector#run methods to now use
postAsync(@NonNull Job<I, CompletableFuture<R>> job) this allows
wrapping the callback invocation as a future completion whenever the
terminal callback methods are invoked.

We rely on a common idle_timeout setting value, provided by remote
implementation such that all methods should reach their terminal state
(invoke success or failure callback) within this duration. With the
exception of download callback, which might take much longer than other
methods subject to networks speeds etc. Incase of downloads, we enforce
this idle timeout at the progress-callback level, such that if there is
no progress within timeout interval, we will consider the download to
have reached a terminal state.

Bug: 340551198
Change-Id: I7ab213ca6b2360e908b29b788543948f544db635
parent 5f5d459a
Loading
Loading
Loading
Loading
+15 −3
Original line number Diff line number Diff line
@@ -41,7 +41,10 @@ import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;

import com.android.internal.infra.AndroidFuture;

import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;

/**
 * Util methods for ensuring the Bundle passed in various methods are read-only and restricted to
@@ -183,7 +186,8 @@ public class BundleUtil {

    public static IStreamingResponseCallback wrapWithValidation(
            IStreamingResponseCallback streamingResponseCallback,
            Executor resourceClosingExecutor) {
            Executor resourceClosingExecutor,
            AndroidFuture future) {
        return new IStreamingResponseCallback.Stub() {
            @Override
            public void onNewContent(Bundle processedResult) throws RemoteException {
@@ -203,6 +207,7 @@ public class BundleUtil {
                    streamingResponseCallback.onSuccess(resultBundle);
                } finally {
                    resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
                    future.complete(null);
                }
            }

@@ -210,6 +215,7 @@ public class BundleUtil {
            public void onFailure(int errorCode, String errorMessage,
                    PersistableBundle errorParams) throws RemoteException {
                streamingResponseCallback.onFailure(errorCode, errorMessage, errorParams);
                future.completeExceptionally(new TimeoutException());
            }

            @Override
@@ -237,7 +243,8 @@ public class BundleUtil {
    }

    public static IResponseCallback wrapWithValidation(IResponseCallback responseCallback,
            Executor resourceClosingExecutor) {
            Executor resourceClosingExecutor,
            AndroidFuture future) {
        return new IResponseCallback.Stub() {
            @Override
            public void onSuccess(Bundle resultBundle)
@@ -247,6 +254,7 @@ public class BundleUtil {
                    responseCallback.onSuccess(resultBundle);
                } finally {
                    resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle));
                    future.complete(null);
                }
            }

@@ -254,6 +262,7 @@ public class BundleUtil {
            public void onFailure(int errorCode, String errorMessage,
                    PersistableBundle errorParams) throws RemoteException {
                responseCallback.onFailure(errorCode, errorMessage, errorParams);
                future.completeExceptionally(new TimeoutException());
            }

            @Override
@@ -280,17 +289,20 @@ public class BundleUtil {
    }


    public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback) {
    public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback,
            AndroidFuture future) {
        return new ITokenInfoCallback.Stub() {
            @Override
            public void onSuccess(TokenInfo tokenInfo) throws RemoteException {
                responseCallback.onSuccess(tokenInfo);
                future.complete(null);
            }

            @Override
            public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams)
                    throws RemoteException {
                responseCallback.onFailure(errorCode, errorMessage, errorParams);
                future.completeExceptionally(new TimeoutException());
            }
        };
    }
+136 −34
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.annotation.RequiresPermission;
import android.app.AppGlobals;
import android.app.ondeviceintelligence.DownloadCallback;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.FeatureDetails;
import android.app.ondeviceintelligence.IDownloadCallback;
import android.app.ondeviceintelligence.IFeatureCallback;
import android.app.ondeviceintelligence.IFeatureDetailsCallback;
@@ -64,6 +65,7 @@ import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
@@ -82,13 +84,17 @@ import com.android.internal.infra.ServiceConnector;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.ondeviceintelligence.callbacks.ListenableDownloadCallback;

import java.io.FileDescriptor;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * This is the system service for handling calls on the
@@ -135,7 +141,6 @@ public class OnDeviceIntelligenceManagerService extends SystemService {

    @GuardedBy("mLock")
    private String[] mTemporaryServiceNames;

    @GuardedBy("mLock")
    private String[] mTemporaryBroadcastKeys;
    @GuardedBy("mLock")
@@ -145,6 +150,8 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
     * Handler used to reset the temporary service names.
     */
    private Handler mTemporaryHandler;
    private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper());


    public OnDeviceIntelligenceManagerService(Context context) {
        super(context);
@@ -204,8 +211,16 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
                    return;
                }
                ensureRemoteIntelligenceServiceInitialized();
                mRemoteOnDeviceIntelligenceService.run(
                        service -> service.getVersion(remoteCallback));
                mRemoteOnDeviceIntelligenceService.postAsync(
                        service -> {
                            AndroidFuture future = new AndroidFuture();
                            service.getVersion(new RemoteCallback(
                                    result -> {
                                        remoteCallback.sendResult(result);
                                        future.complete(null);
                                    }));
                            return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
                        });
            }

            @Override
@@ -225,8 +240,25 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
                }
                ensureRemoteIntelligenceServiceInitialized();
                int callerUid = Binder.getCallingUid();
                mRemoteOnDeviceIntelligenceService.run(
                        service -> service.getFeature(callerUid, id, featureCallback));
                mRemoteOnDeviceIntelligenceService.postAsync(
                        service -> {
                            AndroidFuture future = new AndroidFuture();
                            service.getFeature(callerUid, id, new IFeatureCallback.Stub() {
                                @Override
                                public void onSuccess(Feature result) throws RemoteException {
                                    featureCallback.onSuccess(result);
                                    future.complete(null);
                                }

                                @Override
                                public void onFailure(int errorCode, String errorMessage,
                                        PersistableBundle errorParams) throws RemoteException {
                                    featureCallback.onFailure(errorCode, errorMessage, errorParams);
                                    future.completeExceptionally(new TimeoutException());
                                }
                            });
                            return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
                        });
            }

            @Override
@@ -246,9 +278,29 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
                }
                ensureRemoteIntelligenceServiceInitialized();
                int callerUid = Binder.getCallingUid();
                mRemoteOnDeviceIntelligenceService.run(
                        service -> service.listFeatures(callerUid,
                                listFeaturesCallback));
                mRemoteOnDeviceIntelligenceService.postAsync(
                        service -> {
                            AndroidFuture future = new AndroidFuture();
                            service.listFeatures(callerUid,
                                    new IListFeaturesCallback.Stub() {
                                        @Override
                                        public void onSuccess(List<Feature> result)
                                                throws RemoteException {
                                            listFeaturesCallback.onSuccess(result);
                                            future.complete(null);
                                        }

                                        @Override
                                        public void onFailure(int errorCode, String errorMessage,
                                                PersistableBundle errorParams)
                                                throws RemoteException {
                                            listFeaturesCallback.onFailure(errorCode, errorMessage,
                                                    errorParams);
                                            future.completeExceptionally(new TimeoutException());
                                        }
                                    });
                            return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
                        });
            }

            @Override
@@ -270,9 +322,29 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
                }
                ensureRemoteIntelligenceServiceInitialized();
                int callerUid = Binder.getCallingUid();
                mRemoteOnDeviceIntelligenceService.run(
                        service -> service.getFeatureDetails(callerUid, feature,
                                featureDetailsCallback));
                mRemoteOnDeviceIntelligenceService.postAsync(
                        service -> {
                            AndroidFuture future = new AndroidFuture();
                            service.getFeatureDetails(callerUid, feature,
                                    new IFeatureDetailsCallback.Stub() {
                                        @Override
                                        public void onSuccess(FeatureDetails result)
                                                throws RemoteException {
                                            future.complete(null);
                                            featureDetailsCallback.onSuccess(result);
                                        }

                                        @Override
                                        public void onFailure(int errorCode, String errorMessage,
                                                PersistableBundle errorParams)
                                                throws RemoteException {
                                            future.completeExceptionally(null);
                                            featureDetailsCallback.onFailure(errorCode,
                                                    errorMessage, errorParams);
                                        }
                                    });
                            return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
                        });
            }

            @Override
@@ -293,10 +365,20 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
                }
                ensureRemoteIntelligenceServiceInitialized();
                int callerUid = Binder.getCallingUid();
                mRemoteOnDeviceIntelligenceService.run(
                        service -> service.requestFeatureDownload(callerUid, feature,
                mRemoteOnDeviceIntelligenceService.postAsync(
                        service -> {
                            AndroidFuture future = new AndroidFuture();
                            ListenableDownloadCallback listenableDownloadCallback =
                                    new ListenableDownloadCallback(
                                            downloadCallback,
                                            mMainHandler, future, getIdleTimeoutMs());
                            service.requestFeatureDownload(callerUid, feature,
                                    wrapCancellationFuture(cancellationSignalFuture),
                                downloadCallback));
                                    listenableDownloadCallback);
                            return future; // this future has no timeout because, actual download
                            // might take long, but we fail early if there is no progress callbacks.
                        }
                );
            }


@@ -323,11 +405,15 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
                    }
                    ensureRemoteInferenceServiceInitialized();
                    int callerUid = Binder.getCallingUid();
                    result = mRemoteInferenceService.post(
                            service -> service.requestTokenInfo(callerUid, feature,
                    result = mRemoteInferenceService.postAsync(
                            service -> {
                                AndroidFuture future = new AndroidFuture();
                                service.requestTokenInfo(callerUid, feature,
                                        request,
                                        wrapCancellationFuture(cancellationSignalFuture),
                                    wrapWithValidation(tokenInfoCallback)));
                                        wrapWithValidation(tokenInfoCallback, future));
                                return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
                            });
                    result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
                            resourceClosingExecutor);
                } finally {
@@ -362,13 +448,18 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
                    }
                    ensureRemoteInferenceServiceInitialized();
                    int callerUid = Binder.getCallingUid();
                    result = mRemoteInferenceService.post(
                            service -> service.processRequest(callerUid, feature,
                    result = mRemoteInferenceService.postAsync(
                            service -> {
                                AndroidFuture future = new AndroidFuture();
                                service.processRequest(callerUid, feature,
                                        request,
                                        requestType,
                                        wrapCancellationFuture(cancellationSignalFuture),
                                        wrapProcessingFuture(processingSignalFuture),
                                    wrapWithValidation(responseCallback, resourceClosingExecutor)));
                                        wrapWithValidation(responseCallback,
                                                resourceClosingExecutor, future));
                                return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
                            });
                    result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
                            resourceClosingExecutor);
                } finally {
@@ -402,13 +493,18 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
                    }
                    ensureRemoteInferenceServiceInitialized();
                    int callerUid = Binder.getCallingUid();
                    result = mRemoteInferenceService.post(
                            service -> service.processRequestStreaming(callerUid,
                    result = mRemoteInferenceService.postAsync(
                            service -> {
                                AndroidFuture future = new AndroidFuture();
                                service.processRequestStreaming(callerUid,
                                        feature,
                                        request, requestType,
                                        wrapCancellationFuture(cancellationSignalFuture),
                                        wrapProcessingFuture(processingSignalFuture),
                                    streamingCallback));
                                        wrapWithValidation(streamingCallback,
                                                resourceClosingExecutor, future));
                                return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS);
                            });
                    result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request),
                            resourceClosingExecutor);
                } finally {
@@ -859,4 +955,10 @@ public class OnDeviceIntelligenceManagerService extends SystemService {

        return mTemporaryHandler;
    }

    private long getIdleTimeoutMs() {
        return Settings.Secure.getLongForUser(mContext.getContentResolver(),
                Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, TimeUnit.HOURS.toMillis(1),
                mContext.getUserId());
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import java.util.concurrent.TimeUnit;
 */
public class RemoteOnDeviceIntelligenceService extends
        ServiceConnector.Impl<IOnDeviceIntelligenceService> {
    private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(4);
    private static final String TAG =
            RemoteOnDeviceIntelligenceService.class.getSimpleName();

@@ -50,6 +51,11 @@ public class RemoteOnDeviceIntelligenceService extends
        connect();
    }

    @Override
    protected long getRequestTimeoutMs() {
        return LONG_TIMEOUT;
    }

    @Override
    protected long getAutoDisconnectTimeoutMs() {
        return Settings.Secure.getLongForUser(mContext.getContentResolver(),
+8 −0
Original line number Diff line number Diff line
@@ -38,6 +38,8 @@ import java.util.concurrent.TimeUnit;
 */
public class RemoteOnDeviceSandboxedInferenceService extends
        ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> {
    private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(1);

    /**
     * Creates an instance of {@link ServiceConnector}
     *
@@ -58,6 +60,12 @@ public class RemoteOnDeviceSandboxedInferenceService extends
        connect();
    }

    @Override
    protected long getRequestTimeoutMs() {
        return LONG_TIMEOUT;
    }


    @Override
    protected long getAutoDisconnectTimeoutMs() {
        return Settings.Secure.getLongForUser(mContext.getContentResolver(),
+97 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.ondeviceintelligence.callbacks;

import android.app.ondeviceintelligence.IDownloadCallback;
import android.os.Handler;
import android.os.PersistableBundle;
import android.os.RemoteException;

import com.android.internal.infra.AndroidFuture;

import java.util.concurrent.TimeoutException;

/**
 * This class extends the {@link IDownloadCallback} and adds a timeout Runnable to the callback
 * such that, in the case where the callback methods are not invoked, we do not have to wait for
 * timeout based on {@link #onDownloadCompleted} which might take minutes or hours to complete in
 * some cases. Instead, in such cases we rely on the remote service sending progress updates and if
 * there are *no* progress callbacks in the duration of {@link #idleTimeoutMs}, we can assume the
 * download will not complete and enabling faster cleanup.
 */
public class ListenableDownloadCallback extends IDownloadCallback.Stub implements Runnable {
    private final IDownloadCallback callback;
    private final Handler handler;
    private final AndroidFuture future;
    private final long idleTimeoutMs;

    /**
     * Constructor to create a ListenableDownloadCallback.
     *
     * @param callback      callback to send download updates to caller.
     * @param handler       handler to schedule timeout runnable.
     * @param future        future to complete to signal the callback has reached a terminal state.
     * @param idleTimeoutMs timeout within which download updates should be received.
     */
    public ListenableDownloadCallback(IDownloadCallback callback, Handler handler,
            AndroidFuture future,
            long idleTimeoutMs) {
        this.callback = callback;
        this.handler = handler;
        this.future = future;
        this.idleTimeoutMs = idleTimeoutMs;
        handler.postDelayed(this,
                idleTimeoutMs); // init the timeout runnable in case no callback is ever invoked
    }

    @Override
    public void onDownloadStarted(long bytesToDownload) throws RemoteException {
        callback.onDownloadStarted(bytesToDownload);
        handler.removeCallbacks(this);
        handler.postDelayed(this, idleTimeoutMs);
    }

    @Override
    public void onDownloadProgress(long bytesDownloaded) throws RemoteException {
        callback.onDownloadProgress(bytesDownloaded);
        handler.removeCallbacks(this); // remove previously queued timeout tasks.
        handler.postDelayed(this, idleTimeoutMs); // queue fresh timeout task for next update.
    }

    @Override
    public void onDownloadFailed(int failureStatus,
            String errorMessage, PersistableBundle errorParams) throws RemoteException {
        callback.onDownloadFailed(failureStatus, errorMessage, errorParams);
        handler.removeCallbacks(this);
        future.completeExceptionally(new TimeoutException());
    }

    @Override
    public void onDownloadCompleted(
            android.os.PersistableBundle downloadParams) throws RemoteException {
        callback.onDownloadCompleted(downloadParams);
        handler.removeCallbacks(this);
        future.complete(null);
    }

    @Override
    public void run() {
        future.completeExceptionally(
                new TimeoutException()); // complete the future as we haven't received updates
        // for download progress.
    }
}
 No newline at end of file