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

Commit e200be5c authored by Matthew Sedam's avatar Matthew Sedam Committed by Android (Google) Code Review
Browse files

Merge changes from topic "relm-service-retry" into main

* changes:
  Add reliable message retry support to the Context Hub Service
  Make ContextHubTestModeManager a dedicated class
  Add reliable message retry fields to ContextHubServiceTransaction
parents 6df90fc4 18687245
Loading
Loading
Loading
Loading
+9 −58
Original line number Diff line number Diff line
@@ -81,7 +81,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.PriorityQueue;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -162,8 +161,8 @@ public class ContextHubService extends IContextHubService.Stub {
            new PriorityQueue<>(
                    Comparator.comparingLong(ReliableMessageRecord::getTimestamp));

    // The test mode manager that manages behaviors during test mode.
    private final TestModeManager mTestModeManager = new TestModeManager();
    // The test mode manager that manages behaviors during test mode
    private final ContextHubTestModeManager mTestModeManager = new ContextHubTestModeManager();

    // The period of the recurring time
    private static final int PERIOD_METRIC_QUERY_DAYS = 1;
@@ -229,9 +228,11 @@ public class ContextHubService extends IContextHubService.Stub {
            if (Flags.reliableMessageImplementation()
                    && Flags.reliableMessageTestModeBehavior()
                    && mIsTestModeEnabled.get()
                    && mTestModeManager.handleNanoappMessage(mContextHubId, hostEndpointId,
                            message, nanoappPermissions, messagePermissions)) {
                // The TestModeManager handled the nanoapp message, so return here.
                    && mTestModeManager.handleNanoappMessage(() -> {
                        handleClientMessageCallback(mContextHubId, hostEndpointId, message,
                                nanoappPermissions, messagePermissions);
                    }, message)) {
                // The ContextHubTestModeManager handled the nanoapp message, so return here.
                return;
            }

@@ -261,8 +262,6 @@ public class ContextHubService extends IContextHubService.Stub {
     * Records a reliable message from a nanoapp for duplicate detection.
     */
    private static class ReliableMessageRecord {
        public static final int TIMEOUT_NS = 1000000000;

        public int mContextHubId;
        public long mTimestamp;
        public int mMessageSequenceNumber;
@@ -297,56 +296,8 @@ public class ContextHubService extends IContextHubService.Stub {
        }

        public boolean isExpired() {
            return mTimestamp + TIMEOUT_NS < SystemClock.elapsedRealtimeNanos();
        }
    }

    /**
     * A class to manage behaviors during test mode. This is used for testing.
     */
    private class TestModeManager {
        /**
         * Probability (in percent) of duplicating a message.
         */
        private static final int MESSAGE_DUPLICATION_PROBABILITY_PERCENT = 50;

        /**
         * The number of total messages to send when the duplicate event happens.
         */
        private static final int NUM_MESSAGES_TO_DUPLICATE = 3;

        /**
         * A probability percent for a certain event.
         */
        private static final int MAX_PROBABILITY_PERCENT = 100;

        private final Random mRandom = new Random();

        /**
         * @return whether the message was handled
         * @see ContextHubServiceCallback#handleNanoappMessage
         */
        public boolean handleNanoappMessage(int contextHubId,
                short hostEndpointId, NanoAppMessage message,
                List<String> nanoappPermissions, List<String> messagePermissions) {
            if (!message.isReliable()) {
                return false;
            }

            if (Flags.reliableMessageDuplicateDetectionService()
                    && mRandom.nextInt(MAX_PROBABILITY_PERCENT)
                    < MESSAGE_DUPLICATION_PROBABILITY_PERCENT) {
                Log.i(TAG, "[TEST MODE] Duplicating message ("
                        + NUM_MESSAGES_TO_DUPLICATE
                        + " sends) with message sequence number: "
                        + message.getMessageSequenceNumber());
                for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) {
                    handleClientMessageCallback(contextHubId, hostEndpointId,
                            message, nanoappPermissions, messagePermissions);
                }
                return true;
            }
            return false;
            return mTimestamp + ContextHubTransactionManager.RELIABLE_MESSAGE_TIMEOUT.toNanos()
                    < SystemClock.elapsedRealtimeNanos();
        }
    }

+75 −36
Original line number Diff line number Diff line
@@ -27,53 +27,65 @@ import java.util.concurrent.TimeUnit;
 *
 * @hide
 */
/* package */ abstract class ContextHubServiceTransaction {
abstract class ContextHubServiceTransaction {
    private final int mTransactionId;

    @ContextHubTransaction.Type
    private final int mTransactionType;

    /** The ID of the nanoapp this transaction is targeted for, null if not applicable. */
    private final Long mNanoAppId;

    /**
     * The host package associated with this transaction.
     */
    private final String mPackage;

    /**
     * The message sequence number associated with this transaction, null if not applicable.
     */
    private final Integer mMessageSequenceNumber;

    /**
     * true if the transaction has already completed, false otherwise
     */
    private long mNextRetryTime;

    private long mTimeoutTime;

    /** The number of times the transaction has been started (start function called). */
    private int mNumCompletedStartCalls;

    private final short mHostEndpointId;

    private boolean mIsComplete = false;

    /* package */ ContextHubServiceTransaction(int id, int type, String packageName) {
    ContextHubServiceTransaction(int id, int type, String packageName) {
        mTransactionId = id;
        mTransactionType = type;
        mNanoAppId = null;
        mPackage = packageName;
        mMessageSequenceNumber = null;
        mNextRetryTime = Long.MAX_VALUE;
        mTimeoutTime = Long.MAX_VALUE;
        mNumCompletedStartCalls = 0;
        mHostEndpointId = Short.MAX_VALUE;
    }

    /* package */ ContextHubServiceTransaction(int id, int type, long nanoAppId,
    ContextHubServiceTransaction(int id, int type, long nanoAppId,
            String packageName) {
        mTransactionId = id;
        mTransactionType = type;
        mNanoAppId = nanoAppId;
        mPackage = packageName;
        mMessageSequenceNumber = null;
        mNextRetryTime = Long.MAX_VALUE;
        mTimeoutTime = Long.MAX_VALUE;
        mNumCompletedStartCalls = 0;
        mHostEndpointId = Short.MAX_VALUE;
    }

    /* package */ ContextHubServiceTransaction(int id, int type, String packageName,
            int messageSequenceNumber) {
    ContextHubServiceTransaction(int id, int type, String packageName,
            int messageSequenceNumber, short hostEndpointId) {
        mTransactionId = id;
        mTransactionType = type;
        mNanoAppId = null;
        mPackage = packageName;
        mMessageSequenceNumber = messageSequenceNumber;
        mNextRetryTime = Long.MAX_VALUE;
        mTimeoutTime = Long.MAX_VALUE;
        mNumCompletedStartCalls = 0;
        mHostEndpointId = hostEndpointId;
    }

    /**
@@ -95,7 +107,7 @@ import java.util.concurrent.TimeUnit;
     *
     * @param result the result of the transaction
     */
    /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
    void onTransactionComplete(@ContextHubTransaction.Result int result) {
    }

    /**
@@ -106,44 +118,51 @@ import java.util.concurrent.TimeUnit;
     * @param result           the result of the query
     * @param nanoAppStateList the list of nanoapps given by the query response
     */
    /* package */ void onQueryResponse(
    void onQueryResponse(
            @ContextHubTransaction.Result int result, List<NanoAppState> nanoAppStateList) {
    }

    /**
     * @return the ID of this transaction
     */
    /* package */ int getTransactionId() {
    int getTransactionId() {
        return mTransactionId;
    }

    /**
     * @return the type of this transaction
     * @see ContextHubTransaction.Type
     */
    @ContextHubTransaction.Type
    /* package */ int getTransactionType() {
    int getTransactionType() {
        return mTransactionType;
    }

    /**
     * @return the message sequence number of this transaction
     */
    Integer getMessageSequenceNumber() {
        return mMessageSequenceNumber;
    }

    long getNextRetryTime() {
        return mNextRetryTime;
    }

    long getTimeoutTime() {
        return mTimeoutTime;
    }

    int getNumCompletedStartCalls() {
        return mNumCompletedStartCalls;
    }

    short getHostEndpointId() {
        return mHostEndpointId;
    }

    /**
     * Gets the timeout period as defined in IContexthub.hal
     *
     * @return the timeout of this transaction in the specified time unit
     */
    /* package */ long getTimeout(TimeUnit unit) {
    long getTimeout(TimeUnit unit) {
        switch (mTransactionType) {
            case ContextHubTransaction.TYPE_LOAD_NANOAPP:
                return unit.convert(30L, TimeUnit.SECONDS);
            case ContextHubTransaction.TYPE_RELIABLE_MESSAGE:
                return unit.convert(1000L, TimeUnit.MILLISECONDS);
                return unit.convert(ContextHubTransactionManager.RELIABLE_MESSAGE_TIMEOUT.toNanos(),
                        TimeUnit.NANOSECONDS);
            case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
            case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
            case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
@@ -159,14 +178,23 @@ import java.util.concurrent.TimeUnit;
     *
     * Should only be called as a result of a response from a Context Hub callback
     */
    /* package */ void setComplete() {
    void setComplete() {
        mIsComplete = true;
    }

    /**
     * @return true if the transaction has already completed, false otherwise
     */
    /* package */ boolean isComplete() {
    void setNextRetryTime(long nextRetryTime) {
        mNextRetryTime = nextRetryTime;
    }

    void setTimeoutTime(long timeoutTime) {
        mTimeoutTime = timeoutTime;
    }

    void setNumCompletedStartCalls(int numCompletedStartCalls) {
        mNumCompletedStartCalls = numCompletedStartCalls;
    }

    boolean isComplete() {
        return mIsComplete;
    }

@@ -187,7 +215,18 @@ import java.util.concurrent.TimeUnit;
            out.append(", messageSequenceNumber = ");
            out.append(mMessageSequenceNumber);
        }
        if (mTransactionType == ContextHubTransaction.TYPE_RELIABLE_MESSAGE) {
            out.append(", nextRetryTime = ");
            out.append(mNextRetryTime);
            out.append(", timeoutTime = ");
            out.append(mTimeoutTime);
            out.append(", numCompletedStartCalls = ");
            out.append(mNumCompletedStartCalls);
            out.append(", hostEndpointId = ");
            out.append(mHostEndpointId);
        }
        out.append(")");

        return out.toString();
    }
}
+82 −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.location.contexthub;

import android.chre.flags.Flags;
import android.hardware.location.NanoAppMessage;
import android.util.Log;

import java.util.Random;

/**
 * A class to manage behaviors during test mode. This is used for testing.
 * @hide
 */
public class ContextHubTestModeManager {
    private static final String TAG = "ContextHubTestModeManager";

    /** Probability (in percent) of duplicating a message. */
    private static final int MESSAGE_DROP_PROBABILITY_PERCENT = 20;

    /** Probability (in percent) of duplicating a message. */
    private static final int MESSAGE_DUPLICATION_PROBABILITY_PERCENT = 20;

    /** The number of total messages to send when the duplicate event happens. */
    private static final int NUM_MESSAGES_TO_DUPLICATE = 3;

    /** A probability percent for a certain event. */
    private static final int MAX_PROBABILITY_PERCENT = 100;

    private final Random mRandom = new Random();

    /**
     * @return whether the message was handled
     * @see ContextHubServiceCallback#handleNanoappMessage
     */
    public boolean handleNanoappMessage(Runnable handleMessage, NanoAppMessage message) {
        if (Flags.reliableMessageDuplicateDetectionService()
                && message.isReliable()
                && mRandom.nextInt(MAX_PROBABILITY_PERCENT)
                        < MESSAGE_DUPLICATION_PROBABILITY_PERCENT) {
            Log.i(TAG, "[TEST MODE] Duplicating message ("
                    + NUM_MESSAGES_TO_DUPLICATE
                    + " sends) with message sequence number: "
                    + message.getMessageSequenceNumber());
            for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) {
                handleMessage.run();
            }
            return true;
        }
        return false;
    }

    /**
     * @return whether the message was handled
     * @see IContextHubWrapper#sendMessageToContextHub
     */
    public boolean sendMessageToContextHub(NanoAppMessage message) {
        if (Flags.reliableMessageRetrySupportService()
                && message.isReliable()
                && mRandom.nextInt(MAX_PROBABILITY_PERCENT)
                        < MESSAGE_DROP_PROBABILITY_PERCENT) {
            Log.i(TAG, "[TEST MODE] Dropping message with message sequence number: "
                    + message.getMessageSequenceNumber());
            return true;
        }
        return false;
    }
}
+234 −52

File changed.

Preview size limit exceeded, changes collapsed.

+22 −2
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @hide
@@ -432,10 +433,16 @@ public abstract class IContextHubWrapper {

        // Use this thread in case where the execution requires to be on a service thread.
        // For instance, AppOpsManager.noteOp requires the UPDATE_APP_OPS_STATS permission.
        private HandlerThread mHandlerThread =
        private final HandlerThread mHandlerThread =
                new HandlerThread("Context Hub AIDL callback", Process.THREAD_PRIORITY_BACKGROUND);
        private Handler mHandler;

        // True if test mode is enabled for the Context Hub
        private final AtomicBoolean mIsTestModeEnabled = new AtomicBoolean(false);

        // The test mode manager that manages behaviors during test mode
        private final ContextHubTestModeManager mTestModeManager = new ContextHubTestModeManager();

        private class ContextHubAidlCallback extends
                android.hardware.contexthub.IContextHubCallback.Stub {
            private final int mContextHubId;
@@ -549,6 +556,8 @@ public abstract class IContextHubWrapper {
            } else {
                Log.e(TAG, "mHandleServiceRestartCallback is not set");
            }

            mIsTestModeEnabled.set(false);
        }

        public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
@@ -659,7 +668,17 @@ public abstract class IContextHubWrapper {
            try {
                var msg = ContextHubServiceUtil.createAidlContextHubMessage(
                        hostEndpointId, message);

                // Only process the message normally if not using test mode manager or if
                // the test mode manager call returned false as this indicates it did not
                // process the message.
                boolean useTestModeManager = Flags.reliableMessageImplementation()
                        && Flags.reliableMessageTestModeBehavior()
                        && mIsTestModeEnabled.get();
                if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(message)) {
                    hub.sendMessageToHub(contextHubId, msg);
                }

                return ContextHubTransaction.RESULT_SUCCESS;
            } catch (RemoteException | ServiceSpecificException e) {
                return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -828,6 +847,7 @@ public abstract class IContextHubWrapper {
                return false;
            }

            mIsTestModeEnabled.set(enable);
            try {
                hub.setTestMode(enable);
                return true;