Merge "Add satellite DemoSimulator." into 24D1-dev
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramController.java b/src/java/com/android/internal/telephony/satellite/DatagramController.java
index dbb586d..2f18796 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramController.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramController.java
@@ -29,6 +29,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.content.res.Resources;
 import android.os.Build;
 import android.os.Looper;
 import android.os.SystemProperties;
@@ -69,6 +70,8 @@
     public static final int TIMEOUT_TYPE_WAIT_FOR_DATAGRAM_SENDING_RESPONSE = 3;
     /** This type is used by CTS to override the time to datagram delay in demo mode */
     public static final int TIMEOUT_TYPE_DATAGRAM_DELAY_IN_DEMO_MODE = 4;
+    /** This type is used by CTS to override wait for device alignment in demo datagram boolean */
+    public static final int BOOLEAN_TYPE_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_DATAGRAM = 1;
     private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
     private static final boolean DEBUG = !"user".equals(Build.TYPE);
 
@@ -101,6 +104,7 @@
     private long mAlignTimeoutDuration = SATELLITE_ALIGN_TIMEOUT;
     private long mDatagramWaitTimeForConnectedState;
     private long mModemImageSwitchingDuration;
+    private boolean mWaitForDeviceAlignmentInDemoDatagram;
     @GuardedBy("mLock")
     @SatelliteManager.SatelliteModemState
     private int mSatelltieModemState = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
@@ -155,6 +159,8 @@
 
         mDatagramWaitTimeForConnectedState = getDatagramWaitForConnectedStateTimeoutMillis();
         mModemImageSwitchingDuration = getSatelliteModemImageSwitchingDurationMillis();
+        mWaitForDeviceAlignmentInDemoDatagram =
+                getWaitForDeviceAlignmentInDemoDatagramFromResources();
         mDemoModeDatagramList = new ArrayList<>();
     }
 
@@ -492,6 +498,36 @@
         return true;
     }
 
+    /**
+     * This API can be used by only CTS to override the boolean configs used by the
+     * DatagramController module.
+     *
+     * @param enable Whether to enable or disable boolean config.
+     * @return {@code true} if the boolean config is set successfully, {@code false} otherwise.
+     */
+    boolean setDatagramControllerBooleanConfig(
+            boolean reset, int booleanType, boolean enable) {
+        if (!isMockModemAllowed()) {
+            loge("Updating boolean config is not allowed");
+            return false;
+        }
+
+        logd("setDatagramControllerTimeoutDuration: booleanType=" + booleanType
+                + ", reset=" + reset + ", enable=" + enable);
+        if (booleanType == BOOLEAN_TYPE_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_DATAGRAM) {
+            if (reset) {
+                mWaitForDeviceAlignmentInDemoDatagram =
+                        getWaitForDeviceAlignmentInDemoDatagramFromResources();
+            } else {
+                mWaitForDeviceAlignmentInDemoDatagram = enable;
+            }
+        } else {
+            loge("Invalid boolean type " + booleanType);
+            return false;
+        }
+        return true;
+    }
+
     private boolean isMockModemAllowed() {
         return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
     }
@@ -546,6 +582,38 @@
         }
     }
 
+    /**
+     * Get whether to wait for device alignment with satellite before sending datagrams.
+     *
+     * @param isAligned if the device is aligned with satellite or not
+     * @return {@code true} if device is not aligned to satellite,
+     * and it is required to wait for alignment else {@code false}
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean waitForAligningToSatellite(boolean isAligned) {
+        if (isAligned) {
+            return false;
+        }
+
+        return getWaitForDeviceAlignmentInDemoDatagram();
+    }
+
+    private boolean getWaitForDeviceAlignmentInDemoDatagram() {
+        return mWaitForDeviceAlignmentInDemoDatagram;
+    }
+
+    private boolean getWaitForDeviceAlignmentInDemoDatagramFromResources() {
+        boolean waitForDeviceAlignmentInDemoDatagram = false;
+        try {
+            waitForDeviceAlignmentInDemoDatagram = mContext.getResources().getBoolean(
+                    R.bool.config_wait_for_device_alignment_in_demo_datagram);
+        } catch (Resources.NotFoundException ex) {
+            loge("getWaitForDeviceAlignmentInDemoDatagram: ex=" + ex);
+        }
+
+        return waitForDeviceAlignmentInDemoDatagram;
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
index 4fc8d55..f764b2b 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
@@ -239,7 +239,7 @@
                     if (mIsDemoMode && (error == SatelliteManager.SATELLITE_RESULT_SUCCESS)) {
                         if (argument.skipCheckingSatelliteAligned) {
                             logd("Satellite was already aligned. No need to check alignment again");
-                        } else if (!mIsAligned) {
+                        } else if (mDatagramController.waitForAligningToSatellite(mIsAligned)) {
                             logd("Satellite is not aligned in demo mode, wait for the alignment.");
                             startSatelliteAlignedTimer(request);
                             break;
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
index 145b017..f1f0fde 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
@@ -620,7 +620,7 @@
             DatagramReceiverHandlerRequest request = new DatagramReceiverHandlerRequest(
                     callback, phone, subId);
             synchronized (mLock) {
-                if (mIsAligned) {
+                if (!mDatagramController.waitForAligningToSatellite(mIsAligned)) {
                     Message msg = obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE,
                             request);
                     AsyncResult.forMessage(msg, null, null);
diff --git a/src/java/com/android/internal/telephony/satellite/DemoSimulator.java b/src/java/com/android/internal/telephony/satellite/DemoSimulator.java
new file mode 100644
index 0000000..3c31ae8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/DemoSimulator.java
@@ -0,0 +1,329 @@
+/*
+ * 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.internal.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telephony.IIntegerConsumer;
+import android.telephony.satellite.stub.ISatelliteListener;
+import android.telephony.satellite.stub.NtnSignalStrength;
+import android.telephony.satellite.stub.SatelliteModemState;
+import android.telephony.satellite.stub.SatelliteResult;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+public class DemoSimulator extends StateMachine {
+    private static final String TAG = "DemoSimulator";
+    private static final boolean DBG = true;
+
+    private static final int EVENT_SATELLITE_MODE_ON = 1;
+    private static final int EVENT_SATELLITE_MODE_OFF = 2;
+    private static final int EVENT_DEVICE_ALIGNED_WITH_SATELLITE = 3;
+    protected static final int EVENT_DEVICE_ALIGNED = 4;
+    protected static final int EVENT_DEVICE_NOT_ALIGNED = 5;
+
+    @NonNull private static DemoSimulator sInstance;
+
+    @NonNull private final Context mContext;
+    @NonNull private final SatelliteController mSatelliteController;
+    @NonNull private final PowerOffState mPowerOffState = new PowerOffState();
+    @NonNull private final NotConnectedState mNotConnectedState = new NotConnectedState();
+    @NonNull private final ConnectedState mConnectedState = new ConnectedState();
+    @NonNull private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private boolean mIsAligned = false;
+    private ISatelliteListener mISatelliteListener;
+
+    /**
+     * @return The singleton instance of DemoSimulator.
+     */
+    public static DemoSimulator getInstance() {
+        if (sInstance == null) {
+            Log.e(TAG, "DemoSimulator was not yet initialized.");
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create the DemoSimulator singleton instance.
+     *
+     * @param context The Context for the DemoSimulator.
+     * @return The singleton instance of DemoSimulator.
+     */
+    public static DemoSimulator make(@NonNull Context context,
+            @NonNull SatelliteController satelliteController) {
+        if (sInstance == null) {
+            sInstance = new DemoSimulator(context, Looper.getMainLooper(), satelliteController);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create a DemoSimulator.
+     *
+     * @param context The Context for the DemoSimulator.
+     * @param looper The looper associated with the handler of this class.
+     */
+    protected DemoSimulator(@NonNull Context context, @NonNull Looper looper,
+            @NonNull SatelliteController satelliteController) {
+        super(TAG, looper);
+
+        mContext = context;
+        mSatelliteController = satelliteController;
+        addState(mPowerOffState);
+        addState(mNotConnectedState);
+        addState(mConnectedState);
+        setInitialState(mPowerOffState);
+        start();
+    }
+
+    private class PowerOffState extends State {
+        @Override
+        public void enter() {
+            logd("Entering PowerOffState");
+        }
+
+        @Override
+        public void exit() {
+            logd("Exiting PowerOffState");
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("PowerOffState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_SATELLITE_MODE_ON:
+                    transitionTo(mNotConnectedState);
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+    }
+
+    private class NotConnectedState extends State {
+        @Override
+        public void enter() {
+            logd("Entering NotConnectedState");
+
+            try {
+                NtnSignalStrength ntnSignalStrength = new NtnSignalStrength();
+                ntnSignalStrength.signalStrengthLevel = 0;
+                mISatelliteListener.onSatelliteModemStateChanged(
+                        SatelliteModemState.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+                mISatelliteListener.onNtnSignalStrengthChanged(ntnSignalStrength);
+
+                synchronized (mLock) {
+                    if (mIsAligned) {
+                        handleEventDeviceAlignedWithSatellite(true);
+                    }
+                }
+            } catch (RemoteException e) {
+                loge("NotConnectedState: RemoteException " + e);
+            }
+        }
+
+        @Override
+        public void exit() {
+            logd("Exiting NotConnectedState");
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("NotConnectedState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_SATELLITE_MODE_OFF:
+                    transitionTo(mPowerOffState);
+                    break;
+                case EVENT_DEVICE_ALIGNED_WITH_SATELLITE:
+                    handleEventDeviceAlignedWithSatellite((boolean) msg.obj);
+                    break;
+                case EVENT_DEVICE_ALIGNED:
+                    transitionTo(mConnectedState);
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+
+        private void handleEventDeviceAlignedWithSatellite(boolean isAligned) {
+            if (isAligned && !hasMessages(EVENT_DEVICE_ALIGNED)) {
+                long durationMillis = mSatelliteController.getDemoPointingAlignedDurationMillis();
+                logd("NotConnectedState: handleEventAlignedWithSatellite isAligned=true."
+                        + " Send delayed EVENT_DEVICE_ALIGNED message in"
+                        + " durationMillis=" + durationMillis);
+                sendMessageDelayed(EVENT_DEVICE_ALIGNED, durationMillis);
+            } else if (!isAligned && hasMessages(EVENT_DEVICE_ALIGNED)) {
+                logd("NotConnectedState: handleEventAlignedWithSatellite isAligned=false."
+                        + " Remove EVENT_DEVICE_ALIGNED message.");
+                removeMessages(EVENT_DEVICE_ALIGNED);
+            }
+        }
+    }
+
+    private class ConnectedState extends State {
+        @Override
+        public void enter() {
+            logd("Entering ConnectedState");
+
+            try {
+                NtnSignalStrength ntnSignalStrength = new NtnSignalStrength();
+                ntnSignalStrength.signalStrengthLevel = 2;
+                mISatelliteListener.onSatelliteModemStateChanged(
+                        SatelliteModemState.SATELLITE_MODEM_STATE_CONNECTED);
+                mISatelliteListener.onNtnSignalStrengthChanged(ntnSignalStrength);
+
+                synchronized (mLock) {
+                    if (!mIsAligned) {
+                        handleEventDeviceAlignedWithSatellite(false);
+                    }
+                }
+            } catch (RemoteException e) {
+                loge("ConnectedState: RemoteException " + e);
+            }
+        }
+
+        @Override
+        public void exit() {
+            logd("Exiting ConnectedState");
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("ConnectedState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_SATELLITE_MODE_OFF:
+                    transitionTo(mPowerOffState);
+                    break;
+                case EVENT_DEVICE_ALIGNED_WITH_SATELLITE:
+                    handleEventDeviceAlignedWithSatellite((boolean) msg.obj);
+                    break;
+                case EVENT_DEVICE_NOT_ALIGNED:
+                    transitionTo(mNotConnectedState);
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+
+        private void handleEventDeviceAlignedWithSatellite(boolean isAligned) {
+            if (!isAligned && !hasMessages(EVENT_DEVICE_NOT_ALIGNED)) {
+                long durationMillis =
+                        mSatelliteController.getDemoPointingNotAlignedDurationMillis();
+                logd("ConnectedState: handleEventAlignedWithSatellite isAligned=false."
+                        + " Send delayed EVENT_DEVICE_NOT_ALIGNED message"
+                        + " in durationMillis=" + durationMillis);
+                sendMessageDelayed(EVENT_DEVICE_NOT_ALIGNED, durationMillis);
+            } else if (isAligned && hasMessages(EVENT_DEVICE_NOT_ALIGNED)) {
+                logd("ConnectedState: handleEventAlignedWithSatellite isAligned=true."
+                        + " Remove EVENT_DEVICE_NOT_ALIGNED message.");
+                removeMessages(EVENT_DEVICE_NOT_ALIGNED);
+            }
+        }
+    }
+
+    /**
+     * @return the string for msg.what
+     */
+    @Override
+    protected String getWhatToString(int what) {
+        String whatString;
+        switch (what) {
+            case EVENT_SATELLITE_MODE_ON:
+                whatString = "EVENT_SATELLITE_MODE_ON";
+                break;
+            case EVENT_SATELLITE_MODE_OFF:
+                whatString = "EVENT_SATELLITE_MODE_OFF";
+                break;
+            case EVENT_DEVICE_ALIGNED_WITH_SATELLITE:
+                whatString = "EVENT_DEVICE_ALIGNED_WITH_SATELLITE";
+                break;
+            case EVENT_DEVICE_ALIGNED:
+                whatString = "EVENT_DEVICE_ALIGNED";
+                break;
+            case EVENT_DEVICE_NOT_ALIGNED:
+                whatString = "EVENT_DEVICE_NOT_ALIGNED";
+                break;
+            default:
+                whatString = "UNKNOWN EVENT " + what;
+        }
+        return whatString;
+    }
+
+    /**
+     * Register the callback interface with satellite service.
+     *
+     * @param listener The callback interface to handle satellite service indications.
+     */
+    public void setSatelliteListener(@NonNull ISatelliteListener listener) {
+        mISatelliteListener = listener;
+    }
+
+    /**
+     * Allow cellular modem scanning while satellite mode is on.
+     *
+     * @param enabled  {@code true} to enable cellular modem while satellite mode is on
+     *                             and {@code false} to disable
+     * @param errorCallback The callback to receive the error code result of the operation.
+     */
+    public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
+            @NonNull IIntegerConsumer errorCallback) {
+        try {
+            errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS);
+        } catch (RemoteException e) {
+            loge("enableCellularModemWhileSatelliteModeIsOn: RemoteException " + e);
+        }
+    }
+
+    /**
+     * This function is used by {@link SatelliteSessionController} to notify {@link DemoSimulator}
+     * that satellite mode is ON.
+     */
+    public void onSatelliteModeOn() {
+        if (mSatelliteController.isDemoModeEnabled()) {
+            sendMessage(EVENT_SATELLITE_MODE_ON);
+        }
+    }
+
+    /**
+     * This function is used by {@link SatelliteSessionController} to notify {@link DemoSimulator}
+     * that satellite mode is OFF.
+     */
+    public void onSatelliteModeOff() {
+        sendMessage(EVENT_SATELLITE_MODE_OFF);
+    }
+
+    /**
+     * Set whether the device is aligned with the satellite.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void setDeviceAlignedWithSatellite(boolean isAligned) {
+        synchronized (mLock) {
+            if (mSatelliteController.isDemoModeEnabled()) {
+                mIsAligned = isAligned;
+                sendMessage(EVENT_DEVICE_ALIGNED_WITH_SATELLITE, isAligned);
+            }
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 1cc2977..c0917fe 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -162,6 +162,10 @@
      * to enable satellite.
      */
     public static final int TIMEOUT_TYPE_WAIT_FOR_SATELLITE_ENABLING_RESPONSE = 1;
+    /** This is used by CTS to override demo pointing aligned duration. */
+    public static final int TIMEOUT_TYPE_DEMO_POINTING_ALIGNED_DURATION_MILLIS = 2;
+    /** This is used by CTS to override demo pointing not aligned duration. */
+    public static final int TIMEOUT_TYPE_DEMO_POINTING_NOT_ALIGNED_DURATION_MILLIS = 3;
 
     /** Key used to read/write OEM-enabled satellite provision status in shared preferences. */
     private static final String OEM_ENABLED_SATELLITE_PROVISION_STATUS_KEY =
@@ -401,6 +405,8 @@
     private final SparseArray<List<String>> mMergedPlmnListPerCarrier = new SparseArray<>();
     private static AtomicLong sNextSatelliteEnableRequestId = new AtomicLong(0);
     private long mWaitTimeForSatelliteEnablingResponse;
+    private long mDemoPointingAlignedDurationMillis;
+    private long mDemoPointingNotAlignedDurationMillis;
 
     /** Key used to read/write satellite system notification done in shared preferences. */
     private static final String SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY =
@@ -528,6 +534,9 @@
                 null);
         loadSatelliteSharedPreferences();
         mWaitTimeForSatelliteEnablingResponse = getWaitForSatelliteEnablingResponseTimeoutMillis();
+        mDemoPointingAlignedDurationMillis = getDemoPointingAlignedDurationMillisFromResources();
+        mDemoPointingNotAlignedDurationMillis =
+                getDemoPointingNotAlignedDurationMillisFromResources();
     }
 
     /**
@@ -2046,6 +2055,8 @@
             logd("setDeviceAlignedWithSatellite: oemEnabledSatelliteFlag is disabled");
             return;
         }
+
+        DemoSimulator.getInstance().setDeviceAlignedWithSatellite(isAligned);
         mDatagramController.setDeviceAlignedWithSatellite(isAligned);
     }
 
@@ -2376,6 +2387,25 @@
     }
 
     /**
+     * This API can be used by only CTS to override the boolean configs used by the
+     * DatagramController module.
+     *
+     * @param enable Whether to enable or disable boolean config.
+     * @return {@code true} if the boolean config is set successfully, {@code false} otherwise.
+     */
+    public boolean setDatagramControllerBooleanConfig(
+            boolean reset, int booleanType, boolean enable) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("setDatagramControllerBooleanConfig: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
+        logd("setDatagramControllerBooleanConfig: reset=" + reset + ", booleanType="
+                + booleanType + ", enable=" + enable);
+        return mDatagramController.setDatagramControllerBooleanConfig(
+                reset, booleanType, enable);
+    }
+
+    /**
      * This API can be used by only CTS to override timeout durations used by SatelliteController
      * module.
      *
@@ -2402,6 +2432,20 @@
                 mWaitTimeForSatelliteEnablingResponse = timeoutMillis;
             }
             logd("mWaitTimeForSatelliteEnablingResponse=" + mWaitTimeForSatelliteEnablingResponse);
+        } else if (timeoutType == TIMEOUT_TYPE_DEMO_POINTING_ALIGNED_DURATION_MILLIS) {
+            if (reset) {
+                mDemoPointingAlignedDurationMillis =
+                        getDemoPointingAlignedDurationMillisFromResources();
+            } else {
+                mDemoPointingAlignedDurationMillis = timeoutMillis;
+            }
+        } else if (timeoutType == TIMEOUT_TYPE_DEMO_POINTING_NOT_ALIGNED_DURATION_MILLIS) {
+            if (reset) {
+                mDemoPointingNotAlignedDurationMillis =
+                        getDemoPointingNotAlignedDurationMillisFromResources();
+            } else {
+                mDemoPointingNotAlignedDurationMillis = timeoutMillis;
+            }
         } else {
             logw("Invalid timeoutType=" + timeoutType);
             return false;
@@ -4568,6 +4612,40 @@
         }
     }
 
+    private long getDemoPointingAlignedDurationMillisFromResources() {
+        long durationMillis = 15000L;
+        try {
+            durationMillis = mContext.getResources().getInteger(
+                    R.integer.config_demo_pointing_aligned_duration_millis);
+        } catch (Resources.NotFoundException ex) {
+            loge("getPointingAlignedDurationMillis: ex=" + ex);
+        }
+
+        return durationMillis;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public long getDemoPointingAlignedDurationMillis() {
+        return mDemoPointingAlignedDurationMillis;
+    }
+
+    private long getDemoPointingNotAlignedDurationMillisFromResources() {
+        long durationMillis = 30000L;
+        try {
+            durationMillis = mContext.getResources().getInteger(
+                    R.integer.config_demo_pointing_not_aligned_duration_millis);
+        } catch (Resources.NotFoundException ex) {
+            loge("getPointingNotAlignedDurationMillis: ex=" + ex);
+        }
+
+        return durationMillis;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public long getDemoPointingNotAlignedDurationMillis() {
+        return mDemoPointingNotAlignedDurationMillis;
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
index 2e99ae6..58260d1 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
@@ -42,6 +42,7 @@
 import android.telephony.satellite.stub.ISatellite;
 import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
 import android.telephony.satellite.stub.ISatelliteListener;
+import android.telephony.satellite.stub.SatelliteModemState;
 import android.telephony.satellite.stub.SatelliteService;
 import android.text.TextUtils;
 import android.util.Pair;
@@ -64,6 +65,7 @@
 
     @NonNull private static SatelliteModemInterface sInstance;
     @NonNull private final Context mContext;
+    @NonNull private final DemoSimulator mDemoSimulator;
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @NonNull protected final ExponentialBackoff mExponentialBackoff;
     @NonNull private final Object mLock = new Object();
@@ -96,7 +98,14 @@
     @NonNull private final RegistrantList mSatelliteSupportedStateChangedRegistrants =
             new RegistrantList();
 
-    @NonNull private final ISatelliteListener mListener = new ISatelliteListener.Stub() {
+    private class SatelliteListener extends ISatelliteListener.Stub {
+
+        private final boolean mIsDemoListener;
+
+        SatelliteListener(boolean isDemoListener) {
+            mIsDemoListener = isDemoListener;
+        }
+
         @Override
         public void onSatelliteProvisionStateChanged(boolean provisioned) {
             mSatelliteProvisionStateChangedRegistrants.notifyResult(provisioned);
@@ -105,15 +114,19 @@
         @Override
         public void onSatelliteDatagramReceived(
                 android.telephony.satellite.stub.SatelliteDatagram datagram, int pendingCount) {
-            logd("onSatelliteDatagramReceived: pendingCount=" + pendingCount);
-            mSatelliteDatagramsReceivedRegistrants.notifyResult(new Pair<>(
-                    SatelliteServiceUtils.fromSatelliteDatagram(datagram), pendingCount));
+            if (notifyResultIfExpectedListener()) {
+                logd("onSatelliteDatagramReceived: pendingCount=" + pendingCount);
+                mSatelliteDatagramsReceivedRegistrants.notifyResult(new Pair<>(
+                        SatelliteServiceUtils.fromSatelliteDatagram(datagram), pendingCount));
+            }
         }
 
         @Override
         public void onPendingDatagrams() {
-            logd("onPendingDatagrams");
-            mPendingDatagramsRegistrants.notifyResult(null);
+            if (notifyResultIfExpectedListener()) {
+                logd("onPendingDatagrams");
+                mPendingDatagramsRegistrants.notifyResult(null);
+            }
         }
 
         @Override
@@ -125,33 +138,39 @@
 
         @Override
         public void onSatelliteModemStateChanged(int state) {
-            mSatelliteModemStateChangedRegistrants.notifyResult(
-                    SatelliteServiceUtils.fromSatelliteModemState(state));
-            int datagramTransferState = SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN;
-            switch (state) {
-                case SatelliteManager.SATELLITE_MODEM_STATE_IDLE:
-                    datagramTransferState = SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
-                    break;
-                case SatelliteManager.SATELLITE_MODEM_STATE_LISTENING:
-                    datagramTransferState =
-                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING;
-                    break;
-                case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING:
-                    datagramTransferState =
-                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
-                    break;
-                case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING:
-                    // keep previous state as this could be retrying sending or receiving
-                    break;
+            if (notifyModemStateChanged(state)) {
+                mSatelliteModemStateChangedRegistrants.notifyResult(
+                        SatelliteServiceUtils.fromSatelliteModemState(state));
+                int datagramTransferState =
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN;
+                switch (state) {
+                    case SatelliteManager.SATELLITE_MODEM_STATE_IDLE:
+                        datagramTransferState =
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+                        break;
+                    case SatelliteManager.SATELLITE_MODEM_STATE_LISTENING:
+                        datagramTransferState =
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING;
+                        break;
+                    case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING:
+                        datagramTransferState =
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
+                        break;
+                    case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING:
+                        // keep previous state as this could be retrying sending or receiving
+                        break;
+                }
+                mDatagramTransferStateChangedRegistrants.notifyResult(datagramTransferState);
             }
-            mDatagramTransferStateChangedRegistrants.notifyResult(datagramTransferState);
         }
 
         @Override
         public void onNtnSignalStrengthChanged(
                 android.telephony.satellite.stub.NtnSignalStrength ntnSignalStrength) {
-            mNtnSignalStrengthChangedRegistrants.notifyResult(
-                    SatelliteServiceUtils.fromNtnSignalStrength(ntnSignalStrength));
+            if (notifyResultIfExpectedListener()) {
+                mNtnSignalStrengthChangedRegistrants.notifyResult(
+                        SatelliteServiceUtils.fromNtnSignalStrength(ntnSignalStrength));
+            }
         }
 
         @Override
@@ -165,7 +184,22 @@
         public void onSatelliteSupportedStateChanged(boolean supported) {
             mSatelliteSupportedStateChangedRegistrants.notifyResult(supported);
         }
-    };
+
+        private boolean notifyResultIfExpectedListener() {
+            // Demo listener should notify results only during demo mode
+            // Vendor listener should notify result only during real mode
+            return mIsDemoListener == mSatelliteController.isDemoModeEnabled();
+        }
+
+        private boolean notifyModemStateChanged(int state) {
+            if (notifyResultIfExpectedListener()) {
+                return true;
+            }
+
+            return state == SatelliteModemState.SATELLITE_MODEM_STATE_OFF
+                    || state == SatelliteModemState.SATELLITE_MODEM_STATE_UNAVAILABLE;
+        }
+    }
 
     /**
      * @return The singleton instance of SatelliteModemInterface.
@@ -202,6 +236,7 @@
     protected SatelliteModemInterface(@NonNull Context context,
             SatelliteController satelliteController, @NonNull Looper looper) {
         mContext = context;
+        mDemoSimulator = DemoSimulator.make(context, satelliteController);
         mIsSatelliteServiceSupported = getSatelliteServiceSupport();
         mSatelliteController = satelliteController;
         mExponentialBackoff = new ExponentialBackoff(REBIND_INITIAL_DELAY, REBIND_MAXIMUM_DELAY,
@@ -314,7 +349,11 @@
             mSatelliteService = ISatellite.Stub.asInterface(service);
             mExponentialBackoff.stop();
             try {
-                mSatelliteService.setSatelliteListener(mListener);
+                SatelliteListener vendorListener = new SatelliteListener(false);
+                mSatelliteService.setSatelliteListener(vendorListener);
+
+                SatelliteListener demoListener = new SatelliteListener(true);
+                mDemoSimulator.setSatelliteListener(demoListener);
             } catch (RemoteException e) {
                 // TODO: Retry setSatelliteListener
                 logd("setSatelliteListener: RemoteException " + e);
@@ -584,19 +623,26 @@
             @Nullable Message message) {
         if (mSatelliteService != null) {
             try {
-                mSatelliteService.enableCellularModemWhileSatelliteModeIsOn(enabled,
-                        new IIntegerConsumer.Stub() {
-                            @Override
-                            public void accept(int result) {
-                                int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("enableCellularModemWhileSatelliteModeIsOn: " + error);
-                                Binder.withCleanCallingIdentity(() -> {
-                                        if (message != null) {
-                                            sendMessageWithResult(message, null, error);
-                                        }
-                                });
+                IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("enableCellularModemWhileSatelliteModeIsOn: " + error);
+                        Binder.withCleanCallingIdentity(() -> {
+                            if (message != null) {
+                                sendMessageWithResult(message, null, error);
                             }
                         });
+                    }
+                };
+
+                if (mSatelliteController.isDemoModeEnabled()) {
+                    mDemoSimulator.enableCellularModemWhileSatelliteModeIsOn(
+                            enabled, errorCallback);
+                } else {
+                    mSatelliteService.enableCellularModemWhileSatelliteModeIsOn(
+                            enabled, errorCallback);
+                }
             } catch (RemoteException e) {
                 loge("enableCellularModemWhileSatelliteModeIsOn: RemoteException " + e);
                 if (message != null) {
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
index cd3c05b..dc0cf47 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
@@ -450,6 +450,7 @@
             }
             unbindService();
             stopNbIotInactivityTimer();
+            DemoSimulator.getInstance().onSatelliteModeOff();
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_OFF);
         }
 
@@ -520,6 +521,7 @@
                 } else {
                     transitionTo(mIdleState);
                 }
+                DemoSimulator.getInstance().onSatelliteModeOn();
             } else {
                 /*
                  * During the state transition from ENABLING to NOT_CONNECTED, modem might be
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
index ecec4cd..4896671 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
@@ -487,6 +487,7 @@
         mDatagramDispatcherUT.setDemoMode(true);
         mDatagramDispatcherUT.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN);
         mDatagramDispatcherUT.setDeviceAlignedWithSatellite(false);
+        when(mMockDatagramController.waitForAligningToSatellite(false)).thenReturn(true);
 
         int[] sosDatagramTypes = {DATAGRAM_TYPE1, DATAGRAM_TYPE4, DATAGRAM_TYPE5};
         for (int datagramType : sosDatagramTypes) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
index 361c638..79d3657 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
@@ -372,6 +372,7 @@
         mTestDemoModeDatagramReceiver.setDemoMode(true);
         mTestDemoModeDatagramReceiver.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN);
         mTestDemoModeDatagramReceiver.setDeviceAlignedWithSatellite(false);
+        when(mMockDatagramController.waitForAligningToSatellite(false)).thenReturn(true);
         when(mMockDatagramController.popDemoModeDatagram()).thenReturn(mDatagram);
         mTestDemoModeDatagramReceiver.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
         processAllMessages();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DemoSimulatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DemoSimulatorTest.java
new file mode 100644
index 0000000..319e39f
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DemoSimulatorTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.internal.telephony.satellite;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
+import android.telephony.satellite.stub.ISatelliteListener;
+import android.telephony.satellite.stub.NtnSignalStrength;
+import android.telephony.satellite.stub.SatelliteModemState;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for DemoSimulator
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DemoSimulatorTest extends TelephonyTest {
+    private static final String TAG = "DemoSimulatorTest";
+    private static final long TEST_DEVICE_POINTING_ALIGNED_DURATION_MILLIS = 200L;
+    private static final long TEST_DEVICE_POINTING_NOT_ALIGNED_DURATION_MILLIS = 300L;
+    private static final String STATE_POWER_OFF = "PowerOffState";
+    private static final String STATE_NOT_CONNECTED = "NotConnectedState";
+    private static final String STATE_CONNECTED = "ConnectedState";
+
+    private TestDemoSimulator mTestDemoSimulator;
+    @Mock private ISatelliteListener mISatelliteListener;
+
+    @Mock private SatelliteController mMockSatelliteController;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+
+        when(mMockSatelliteController.isDemoModeEnabled()).thenReturn(true);
+        when(mMockSatelliteController.getDemoPointingAlignedDurationMillis()).thenReturn(
+                TEST_DEVICE_POINTING_ALIGNED_DURATION_MILLIS);
+        when(mMockSatelliteController.getDemoPointingNotAlignedDurationMillis()).thenReturn(
+                TEST_DEVICE_POINTING_NOT_ALIGNED_DURATION_MILLIS);
+
+        mTestDemoSimulator = new TestDemoSimulator(mContext, Looper.myLooper(),
+                mMockSatelliteController);
+        mTestDemoSimulator.setSatelliteListener(mISatelliteListener);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testInitialState() {
+        assertNotNull(mTestDemoSimulator);
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, mTestDemoSimulator.getCurrentStateName());
+    }
+
+    @Test
+    public void testStateTransition() {
+        // State transitions: POWER_OFF -> NOT_CONNECTED -> CONNECTED
+        moveToConnectedState();
+
+        // Device is not aligned with satellite. EVENT_DEVICE_NOT_ALIGNED timer should start
+        mTestDemoSimulator.setDeviceAlignedWithSatellite(false);
+        processAllMessages();
+        assertTrue(mTestDemoSimulator.isDeviceNotAlignedTimerStarted());
+
+        // After timeout, DemoSimulator should move to NOT_CONNECTED state.
+        moveTimeForward(TEST_DEVICE_POINTING_NOT_ALIGNED_DURATION_MILLIS);
+        processAllMessages();
+        assertEquals(STATE_NOT_CONNECTED, mTestDemoSimulator.getCurrentStateName());
+
+        // Satellite mode is OFF. DemoSimulator should move to POWER_OFF state.
+        mTestDemoSimulator.onSatelliteModeOff();
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, mTestDemoSimulator.getCurrentStateName());
+    }
+
+    @Test
+    public void testNotConnectedState_enter() throws Exception {
+        clearInvocations(mISatelliteListener);
+
+        // State transitions: POWER_OFF -> NOT_CONNECTED
+        moveToNotConnectedState();
+
+        verify(mISatelliteListener).onSatelliteModemStateChanged(
+                SatelliteModemState.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        ArgumentCaptor<NtnSignalStrength> ntnSignalStrength = ArgumentCaptor.forClass(
+                NtnSignalStrength.class);
+        verify(mISatelliteListener).onNtnSignalStrengthChanged(ntnSignalStrength.capture());
+        assertEquals(0, ntnSignalStrength.getValue().signalStrengthLevel);
+    }
+
+    @Test
+    public void testNotConnectedState() {
+        // State transitions: POWER_OFF -> NOT_CONNECTED
+        moveToNotConnectedState();
+
+        // Device is aligned with satellite. EVENT_DEVICE_ALIGNED timer should start.
+        mTestDemoSimulator.setDeviceAlignedWithSatellite(true);
+        processAllMessages();
+        assertTrue(mTestDemoSimulator.isDeviceAlignedTimerStarted());
+
+        // Device is not aligned with satellite. EVENT_DEVICE_ALIGNED messages should be removed.
+        mTestDemoSimulator.setDeviceAlignedWithSatellite(false);
+        processAllMessages();
+        assertFalse(mTestDemoSimulator.isDeviceAlignedTimerStarted());
+        assertEquals(STATE_NOT_CONNECTED, mTestDemoSimulator.getCurrentStateName());
+
+        // Satellite mode is OFF. DemoSimulator should move to POWER_OFF state.
+        mTestDemoSimulator.onSatelliteModeOff();
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, mTestDemoSimulator.getCurrentStateName());
+    }
+
+    @Test
+    public void testConnectedState_enter() throws Exception {
+        clearInvocations(mISatelliteListener);
+
+        // State transitions: POWER_OFF -> NOT_CONNECTED -> CONNECTED
+        moveToConnectedState();
+
+        verify(mISatelliteListener).onSatelliteModemStateChanged(
+                SatelliteModemState.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        verify(mISatelliteListener).onSatelliteModemStateChanged(
+                SatelliteModemState.SATELLITE_MODEM_STATE_CONNECTED);
+        ArgumentCaptor<NtnSignalStrength> ntnSignalStrength = ArgumentCaptor.forClass(
+                NtnSignalStrength.class);
+        verify(mISatelliteListener, times(2))
+                .onNtnSignalStrengthChanged(ntnSignalStrength.capture());
+        NtnSignalStrength ntnSignalStrengthOnConnected = ntnSignalStrength.getAllValues().get(1);
+        assertEquals(2, ntnSignalStrengthOnConnected.signalStrengthLevel);
+    }
+
+    @Test
+    public void testConnectedState() {
+        // State transitions: POWER_OFF -> NOT_CONNECTED -> CONNECTED
+        moveToConnectedState();
+
+        // Device is not aligned with satellite. EVENT_DEVICE_NOT_ALIGNED timer should start
+        mTestDemoSimulator.setDeviceAlignedWithSatellite(false);
+        processAllMessages();
+        assertTrue(mTestDemoSimulator.isDeviceNotAlignedTimerStarted());
+
+        // Device is aligned with satellite before timeout.
+        // EVENT_DEVICE_NOT_ALIGNED messages should be removed.
+        mTestDemoSimulator.setDeviceAlignedWithSatellite(true);
+        processAllMessages();
+        assertFalse(mTestDemoSimulator.isDeviceNotAlignedTimerStarted());
+        assertEquals(STATE_CONNECTED, mTestDemoSimulator.getCurrentStateName());
+
+        // Satellite mode is off. DemoSimulator should move to POWER_OFF state
+        mTestDemoSimulator.onSatelliteModeOff();
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, mTestDemoSimulator.getCurrentStateName());
+    }
+
+    private void moveToNotConnectedState() {
+        // DemoSimulator will initially be in POWER_OFF state.
+        assertNotNull(mTestDemoSimulator);
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, mTestDemoSimulator.getCurrentStateName());
+
+        // Satellite mode is ON. DemoSimulator should move to NOT_CONNECTED state.
+        mTestDemoSimulator.onSatelliteModeOn();
+        processAllMessages();
+        assertEquals(STATE_NOT_CONNECTED, mTestDemoSimulator.getCurrentStateName());
+    }
+
+    private void moveToConnectedState() {
+        // DemoSimulator will initially be in POWER_OFF state.
+        assertNotNull(mTestDemoSimulator);
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, mTestDemoSimulator.getCurrentStateName());
+
+        // Satellite mode is ON. DemoSimulator should move to NOT_CONNECTED state.
+        mTestDemoSimulator.onSatelliteModeOn();
+        processAllMessages();
+        assertEquals(STATE_NOT_CONNECTED, mTestDemoSimulator.getCurrentStateName());
+
+        // Device is aligned with satellite. EVENT_DEVICE_ALIGNED timer should start.
+        mTestDemoSimulator.setDeviceAlignedWithSatellite(true);
+        processAllMessages();
+        assertTrue(mTestDemoSimulator.isDeviceAlignedTimerStarted());
+
+        // After timeout, DemoSimulator should move to CONNECTED state.
+        moveTimeForward(TEST_DEVICE_POINTING_ALIGNED_DURATION_MILLIS);
+        processAllMessages();
+        assertEquals(STATE_CONNECTED, mTestDemoSimulator.getCurrentStateName());
+    }
+
+    private static class TestDemoSimulator extends DemoSimulator {
+
+        TestDemoSimulator(@NonNull Context context, @NonNull Looper looper,
+                @NonNull SatelliteController satelliteController) {
+            super(context, looper, satelliteController);
+        }
+
+        String getCurrentStateName() {
+            return getCurrentState().getName();
+        }
+
+        boolean isDeviceAlignedTimerStarted() {
+            return hasMessages(EVENT_DEVICE_ALIGNED);
+        }
+
+        boolean isDeviceNotAlignedTimerStarted() {
+            return hasMessages(EVENT_DEVICE_NOT_ALIGNED);
+        }
+    }
+}