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

Commit 0fcefc1d authored by Yifei Zhang's avatar Yifei Zhang
Browse files

contexthub: add HubEndpointSession + IHubEndpointLifecycleCallback

- Introduce HubEndpoint and HubEndpointSession class
- Introduce IHubEndpointLifecycleCallback interface

- Add IContextHubEndpoint & IContextHubEndpointCallback that connects
  the lifecycle events to the client API
- Create implementation stub for ContextHubService

Test: build
API-Coverage-Bug: 377554469
Bug: 375487784
Flag: android.chre.flags.offload_api
Change-Id: I7b279ae0857c766c3d801197bb5c90b6cf9a3afe
parent 144ace42
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
@@ -5173,6 +5173,19 @@ package android.hardware.contexthub {
    method @NonNull public android.hardware.contexthub.HubEndpointInfo getHubEndpointInfo();
  }
  @FlaggedApi("android.chre.flags.offload_api") public class HubEndpoint {
    method @Nullable public android.hardware.contexthub.IHubEndpointLifecycleCallback getLifecycleCallback();
    method @Nullable public String getTag();
  }
  public static final class HubEndpoint.Builder {
    ctor public HubEndpoint.Builder(@NonNull android.content.Context);
    method @NonNull public android.hardware.contexthub.HubEndpoint build();
    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
    method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setTag(@NonNull String);
  }
  @FlaggedApi("android.chre.flags.offload_api") public final class HubEndpointInfo implements android.os.Parcelable {
    method public int describeContents();
    method @NonNull public android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier getIdentifier();
@@ -5187,6 +5200,18 @@ package android.hardware.contexthub {
    method public long getHub();
  }
  @FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
    method public void close();
  }
  @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
    method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
    method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
    field public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; // 0x4
    field public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; // 0x3
    field public static final int REASON_UNSPECIFIED = 0; // 0x0
  }
}
package android.hardware.devicestate {
@@ -6188,13 +6213,16 @@ package android.hardware.location {
    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.NanoAppInstanceInfo getNanoAppInstanceInfo(int);
    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int loadNanoApp(int, @NonNull android.hardware.location.NanoApp);
    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.NanoAppBinary);
    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo);
    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(@NonNull android.hardware.location.ContextHubInfo);
    method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
    method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessage(int, int, @NonNull android.hardware.location.ContextHubMessage);
    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int unloadNanoApp(int);
    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
    method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
    method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
    field public static final int AUTHORIZATION_DENIED = 0; // 0x0
    field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1
    field public static final int AUTHORIZATION_GRANTED = 2; // 0x2
+365 −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 android.hardware.contexthub;

import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.chre.flags.Flags;
import android.content.Context;
import android.hardware.location.IContextHubService;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;

import androidx.annotation.GuardedBy;

import java.util.concurrent.Executor;

/**
 * An object representing an endpoint exposed to ContextHub and VendorHub. The object encapsulates
 * the lifecycle and message callbacks for an endpoint.
 *
 * @hide
 */
@SystemApi
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public class HubEndpoint {
    private static final String TAG = "HubEndpoint";

    private final Object mLock = new Object();
    private final HubEndpointInfo mPendingHubEndpointInfo;
    @Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback;
    @NonNull private final Executor mLifecycleCallbackExecutor;

    @GuardedBy("mLock")
    private final SparseArray<HubEndpointSession> mActiveSessions = new SparseArray<>();

    private final IContextHubEndpointCallback mServiceCallback =
            new IContextHubEndpointCallback.Stub() {
                @Override
                public void onSessionOpenRequest(int sessionId, HubEndpointInfo initiator)
                        throws RemoteException {
                    HubEndpointSession activeSession;
                    synchronized (mLock) {
                        activeSession = mActiveSessions.get(sessionId);
                        // TODO(b/378974199): Consider refactor these assertions
                        if (activeSession != null) {
                            Log.i(
                                    TAG,
                                    "onSessionOpenComplete: session already exists, id="
                                            + sessionId);
                            return;
                        }
                    }

                    mLifecycleCallbackExecutor.execute(
                            () -> {
                                // TODO(b/375487784): create API and connect for this
                                onSessionOpenRequestResult(sessionId, initiator);
                            });
                }

                // TODO(b/375487784): Process the result, always accept for now
                private void onSessionOpenRequestResult(int sessionId, HubEndpointInfo initiator) {
                    if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
                        // No longer registered?
                        return;
                    }

                    // Retrieve the active session
                    HubEndpointSession activeSession;
                    synchronized (mLock) {
                        activeSession = mActiveSessions.get(sessionId);
                        // TODO(b/378974199): Consider refactor these assertions
                        if (activeSession != null) {
                            Log.e(
                                    TAG,
                                    "onSessionOpenRequestResult: session already exists, id="
                                            + sessionId);
                            return;
                        }

                        activeSession =
                                new HubEndpointSession(
                                        sessionId,
                                        HubEndpoint.this,
                                        mAssignedHubEndpointInfo,
                                        initiator);
                        try {
                            // oneway call to notify system service that the request is completed
                            mServiceToken.openSessionRequestComplete(sessionId);
                        } catch (RemoteException e) {
                            Log.e(TAG, "onSessionOpenRequestResult: ", e);
                            return;
                        }

                        mActiveSessions.put(sessionId, activeSession);
                    }

                    // Execute the callback
                    activeSession.setOpened();
                    if (mLifecycleCallback != null) {
                        final HubEndpointSession finalActiveSession = activeSession;
                        mLifecycleCallbackExecutor.execute(
                                () -> mLifecycleCallback.onSessionOpened(finalActiveSession));
                    }
                }

                @Override
                public void onSessionOpenComplete(int sessionId) throws RemoteException {
                    final HubEndpointSession activeSession;

                    // Retrieve the active session
                    synchronized (mLock) {
                        activeSession = mActiveSessions.get(sessionId);
                    }
                    // TODO(b/378974199): Consider refactor these assertions
                    if (activeSession == null) {
                        Log.i(
                                TAG,
                                "onSessionOpenComplete: no pending session open request? id="
                                        + sessionId);
                        return;
                    }

                    // Execute the callback
                    activeSession.setOpened();
                    if (mLifecycleCallback != null) {
                        mLifecycleCallbackExecutor.execute(
                                () -> mLifecycleCallback.onSessionOpened(activeSession));
                    }
                }

                @Override
                public void onSessionClosed(int sessionId, int reason) throws RemoteException {
                    final HubEndpointSession activeSession;

                    // Retrieve the active session
                    synchronized (mLock) {
                        activeSession = mActiveSessions.get(sessionId);
                    }
                    // TODO(b/378974199): Consider refactor these assertions
                    if (activeSession == null) {
                        Log.i(TAG, "onSessionClosed: session not active, id=" + sessionId);
                        return;
                    }

                    // Execute the callback
                    if (mLifecycleCallback != null) {
                        mLifecycleCallbackExecutor.execute(
                                () -> {
                                    mLifecycleCallback.onSessionClosed(activeSession, reason);

                                    // Remove the session object first to call
                                    activeSession.setClosed();
                                    synchronized (mLock) {
                                        mActiveSessions.remove(sessionId);
                                    }
                                });
                    }
                }
            };

    /** Binder returned from system service, non-null while registered. */
    @Nullable private IContextHubEndpoint mServiceToken;

    /** HubEndpointInfo with the assigned endpoint id from system service. */
    @Nullable private HubEndpointInfo mAssignedHubEndpointInfo;

    private HubEndpoint(
            @NonNull HubEndpointInfo pendingEndpointInfo,
            @Nullable IHubEndpointLifecycleCallback endpointLifecycleCallback,
            @NonNull Executor lifecycleCallbackExecutor) {
        mPendingHubEndpointInfo = pendingEndpointInfo;
        mLifecycleCallback = endpointLifecycleCallback;
        mLifecycleCallbackExecutor = lifecycleCallbackExecutor;
    }

    /** @hide */
    public void register(IContextHubService service) {
        // TODO(b/378974199): Consider refactor these assertions
        if (mServiceToken != null) {
            // Already registered
            return;
        }
        try {
            IContextHubEndpoint serviceToken =
                    service.registerEndpoint(mPendingHubEndpointInfo, mServiceCallback);
            mAssignedHubEndpointInfo = serviceToken.getAssignedHubEndpointInfo();
            mServiceToken = serviceToken;
        } catch (RemoteException e) {
            Log.e(TAG, "registerEndpoint: failed to register endpoint", e);
            e.rethrowFromSystemServer();
        }
    }

    /** @hide */
    public void unregister() {
        IContextHubEndpoint serviceToken = mServiceToken;
        // TODO(b/378974199): Consider refactor these assertions
        if (serviceToken == null) {
            // Not yet registered
            return;
        }

        try {
            synchronized (mLock) {
                // Don't call HubEndpointSession.close() here.
                for (int i = 0; i < mActiveSessions.size(); i++) {
                    mActiveSessions.get(mActiveSessions.keyAt(i)).setClosed();
                }
                mActiveSessions.clear();
            }
            mServiceToken.unregister();
        } catch (RemoteException e) {
            Log.e(TAG, "unregisterEndpoint: failed to unregister endpoint", e);
            e.rethrowFromSystemServer();
        } finally {
            mServiceToken = null;
            mAssignedHubEndpointInfo = null;
        }
    }

    /** @hide */
    public void openSession(HubEndpointInfo destinationInfo) {
        // TODO(b/378974199): Consider refactor these assertions
        if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
            // No longer registered?
            return;
        }

        HubEndpointSession newSession;
        try {
            // Request system service to assign session id.
            int sessionId = mServiceToken.openSession(destinationInfo);

            // Save the newly created session
            synchronized (mLock) {
                newSession =
                        new HubEndpointSession(
                                sessionId,
                                HubEndpoint.this,
                                destinationInfo,
                                mAssignedHubEndpointInfo);
                mActiveSessions.put(sessionId, newSession);
            }
        } catch (RemoteException e) {
            // Move this to toString
            Log.e(TAG, "openSession: failed to open session to " + destinationInfo, e);
            e.rethrowFromSystemServer();
        }
    }

    /** @hide */
    public void closeSession(HubEndpointSession session) {
        IContextHubEndpoint serviceToken = mServiceToken;
        // TODO(b/378974199): Consider refactor these assertions
        if (serviceToken == null || mAssignedHubEndpointInfo == null) {
            // Not registered
            return;
        }

        synchronized (mLock) {
            if (!mActiveSessions.contains(session.getId())) {
                // Already closed?
                return;
            }
            session.setClosed();
            mActiveSessions.remove(session.getId());
        }

        try {
            // Oneway notification to system service
            serviceToken.closeSession(
                    session.getId(),
                    IHubEndpointLifecycleCallback.REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
        } catch (RemoteException e) {
            Log.e(TAG, "closeSession: failed to close session " + session, e);
            e.rethrowFromSystemServer();
        }
    }

    @Nullable
    public String getTag() {
        return mPendingHubEndpointInfo.getTag();
    }

    @Nullable
    public IHubEndpointLifecycleCallback getLifecycleCallback() {
        return mLifecycleCallback;
    }

    /** Builder for a {@link HubEndpoint} object. */
    public static final class Builder {
        private final String mPackageName;

        @Nullable private IHubEndpointLifecycleCallback mLifecycleCallback;

        @NonNull private Executor mLifecycleCallbackExecutor;

        @Nullable private String mTag;

        /** Create a builder for {@link HubEndpoint} */
        public Builder(@NonNull Context context) {
            mPackageName = context.getPackageName();
            mLifecycleCallbackExecutor = context.getMainExecutor();
        }

        /**
         * Set a tag string. The tag can be used to further identify the creator of the endpoint.
         * Endpoints created by the same package share the same name but should have different tags.
         */
        @NonNull
        public Builder setTag(@NonNull String tag) {
            mTag = tag;
            return this;
        }

        /** Attach a callback interface for lifecycle events for this Endpoint */
        @NonNull
        public Builder setLifecycleCallback(
                @NonNull IHubEndpointLifecycleCallback lifecycleCallback) {
            mLifecycleCallback = lifecycleCallback;
            return this;
        }

        /**
         * Attach a callback interface for lifecycle events for this Endpoint with a specified
         * executor
         */
        @NonNull
        public Builder setLifecycleCallback(
                @NonNull @CallbackExecutor Executor executor,
                @NonNull IHubEndpointLifecycleCallback lifecycleCallback) {
            mLifecycleCallbackExecutor = executor;
            mLifecycleCallback = lifecycleCallback;
            return this;
        }

        /** Build the {@link HubEndpoint} object. */
        @NonNull
        public HubEndpoint build() {
            return new HubEndpoint(
                    new HubEndpointInfo(mPackageName, mTag),
                    mLifecycleCallback,
                    mLifecycleCallbackExecutor);
        }
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -115,6 +115,13 @@ public final class HubEndpointInfo implements Parcelable {
        mTag = endpointInfo.tag;
    }

    /** @hide */
    public HubEndpointInfo(String name, @Nullable String tag) {
        mId = HubEndpointIdentifier.invalid();
        mName = name;
        mTag = tag;
    }

    private HubEndpointInfo(Parcel in) {
        long hubId = in.readLong();
        long endpointId = in.readLong();
+115 −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 android.hardware.contexthub;

import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.chre.flags.Flags;
import android.util.CloseGuard;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * An object representing a communication session between two different hub endpoints.
 *
 * <p>A published enpoint can receive
 *
 * @hide
 */
@SystemApi
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public class HubEndpointSession implements AutoCloseable {
    private final CloseGuard mCloseGuard = new CloseGuard();

    private final int mId;

    // TODO(b/377717509): Implement Message sending API & interface
    @NonNull private final HubEndpoint mHubEndpoint;
    @NonNull private final HubEndpointInfo mInitiator;
    @NonNull private final HubEndpointInfo mDestination;

    private final AtomicBoolean mIsClosed = new AtomicBoolean(true);

    /** @hide */
    HubEndpointSession(
            int id,
            @NonNull HubEndpoint hubEndpoint,
            @NonNull HubEndpointInfo destination,
            @NonNull HubEndpointInfo initiator) {
        mId = id;
        mHubEndpoint = hubEndpoint;
        mDestination = destination;
        mInitiator = initiator;
    }

    /** @hide */
    public int getId() {
        return mId;
    }

    /** @hide */
    public void setOpened() {
        mIsClosed.set(false);
        mCloseGuard.open("close");
    }

    /** @hide */
    public void setClosed() {
        mIsClosed.set(true);
        mCloseGuard.close();
    }

    /**
     * Closes the connection for this session between an endpoint and the Context Hub Service.
     *
     * <p>When this function is invoked, the messaging associated with this session is invalidated.
     * All futures messages targeted for this client are dropped.
     */
    public void close() {
        if (!mIsClosed.getAndSet(true)) {
            mCloseGuard.close();
            mHubEndpoint.closeSession(this);
        }
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("Session [");
        stringBuilder.append(mId);
        stringBuilder.append("]: [");
        stringBuilder.append(mInitiator);
        stringBuilder.append("]->[");
        stringBuilder.append(mDestination);
        stringBuilder.append("]");
        return stringBuilder.toString();
    }

    /** @hide */
    protected void finalize() throws Throwable {
        try {
            // Note that guard could be null if the constructor threw.
            if (mCloseGuard != null) {
                mCloseGuard.warnIfOpen();
            }
            close();
        } finally {
            super.finalize();
        }
    }
}
+65 −0
Original line number Diff line number Diff line
/*
 * Copyright 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 android.hardware.contexthub;

import android.hardware.contexthub.HubEndpointInfo;

/**
 * @hide
 */
interface IContextHubEndpoint {
    /**
     * Retrieve the up-to-date EndpointInfo, with assigned endpoint id.
     */
    HubEndpointInfo getAssignedHubEndpointInfo();

    /**
     * Request system service to open a session with a specific destination.
     *
     * @param destination A valid HubEndpointInfo representing the destination.
     *
     * @throws IllegalArgumentException If the HubEndpointInfo is not valid.
     * @throws IllegalStateException If there are too many opened sessions.
     */
    int openSession(in HubEndpointInfo destination);

    /**
     * Request system service to close a specific session
     *
     * @param sessionId An integer identifying the session, assigned by system service
     * @param reason An integer identifying the reason
     *
     * @throws IllegalStateException If the session wasn't opened.
     */
    void closeSession(int sessionId, int reason);

    /**
     * Callback when a session is opened. This callback is the status callback for a previous
     * IContextHubEndpointCallback.onSessionOpenRequest().
     *
     * @param sessionId The integer representing the communication session, previously set in
     *         onSessionOpenRequest().
     *
     * @throws IllegalStateException If the session wasn't opened.
     */
    void openSessionRequestComplete(int sessionId);

    /**
     * Unregister this endpoint from the HAL, invalidate the EndpointInfo previously assigned.
     */
    void unregister();
}
Loading