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

Commit 2f64737b authored by Yifei Zhang's avatar Yifei Zhang Committed by Android (Google) Code Review
Browse files

Merge changes I24065c91,I7b279ae0,I19c41329 into main

* changes:
  contexthub: add HubEndpointSessionResult to allow accept/reject open session request
  contexthub: add HubEndpointSession + IHubEndpointLifecycleCallback
  contexthub: implement findEndpoints in service
parents 08f37916 16985ae8
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
@@ -5220,6 +5220,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();
@@ -5234,6 +5247,26 @@ 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 class HubEndpointSessionResult {
    method @NonNull public static android.hardware.contexthub.HubEndpointSessionResult accept();
    method @Nullable public String getReason();
    method public boolean isAccepted();
    method @NonNull public static android.hardware.contexthub.HubEndpointSessionResult reject(@NonNull String);
  }
  @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
    method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
    method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo);
    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 {
@@ -6235,13 +6268,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
+406 −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;
                        }
                    }

                    if (mLifecycleCallback != null) {
                        mLifecycleCallbackExecutor.execute(
                                () ->
                                        processSessionOpenRequestResult(
                                                sessionId,
                                                initiator,
                                                mLifecycleCallback.onSessionOpenRequest(
                                                        initiator)));
                    }
                }

                private void processSessionOpenRequestResult(
                        int sessionId, HubEndpointInfo initiator, HubEndpointSessionResult result) {
                    if (result == null) {
                        throw new IllegalArgumentException(
                                "HubEndpointSessionResult shouldn't be null.");
                    }

                    if (result.isAccepted()) {
                        acceptSession(sessionId, initiator);
                    } else {
                        Log.i(
                                TAG,
                                "Session "
                                        + sessionId
                                        + " from "
                                        + initiator
                                        + " was rejected, reason="
                                        + result.getReason());
                        rejectSession(sessionId);
                    }
                }

                private void acceptSession(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));
                    }
                }

                private void rejectSession(int sessionId) {
                    if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
                        // No longer registered?
                        return;
                    }

                    try {
                        mServiceToken.closeSession(
                                sessionId,
                                IHubEndpointLifecycleCallback
                                        .REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
                    } catch (RemoteException e) {
                        e.rethrowFromSystemServer();
                    }
                }

                @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

File added.

Preview size limit exceeded, changes collapsed.

+79 −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.Nullable;
import android.annotation.SystemApi;
import android.chre.flags.Flags;

/**
 * Return type of {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines
 * whether a open session request from the remote is accepted or not.
 *
 * @hide
 */
@SystemApi
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public class HubEndpointSessionResult {
    private final boolean mAccepted;

    @Nullable private final String mReason;

    private HubEndpointSessionResult(boolean accepted, @Nullable String reason) {
        mAccepted = accepted;
        mReason = reason;
    }

    /**
     * Retrieve the decision of the session request.
     *
     * @return Whether a session request was accepted or not, previously set with {@link #accept()}
     *     or {@link #reject(String)}.
     */
    public boolean isAccepted() {
        return mAccepted;
    }

    /**
     * Retrieve the decision of the session request.
     *
     * @return The reason previously set in {@link #reject(String)}. If the result was {@link
     *     #accept()}, the reason will be null.
     */
    @Nullable
    public String getReason() {
        return mReason;
    }

    /** Accept the request. */
    @NonNull
    public static HubEndpointSessionResult accept() {
        return new HubEndpointSessionResult(true, null);
    }

    /**
     * Reject the request with a reason.
     *
     * @param reason Reason why the request was rejected, for diagnostic purposes.
     */
    @NonNull
    public static HubEndpointSessionResult reject(@NonNull String reason) {
        return new HubEndpointSessionResult(false, reason);
    }
}
Loading