1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony.satellite;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.os.Looper;
22 import android.os.Message;
23 import android.os.RemoteException;
24 import android.telephony.IIntegerConsumer;
25 import android.telephony.satellite.stub.ISatelliteListener;
26 import android.telephony.satellite.stub.NtnSignalStrength;
27 import android.telephony.satellite.stub.SatelliteModemState;
28 import android.telephony.satellite.stub.SatelliteResult;
29 import android.util.Log;
30 
31 import com.android.internal.annotations.GuardedBy;
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.util.State;
34 import com.android.internal.util.StateMachine;
35 
36 public class DemoSimulator extends StateMachine {
37     private static final String TAG = "DemoSimulator";
38     private static final boolean DBG = true;
39 
40     private static final int EVENT_SATELLITE_MODE_ON = 1;
41     private static final int EVENT_SATELLITE_MODE_OFF = 2;
42     private static final int EVENT_DEVICE_ALIGNED_WITH_SATELLITE = 3;
43     protected static final int EVENT_DEVICE_ALIGNED = 4;
44     protected static final int EVENT_DEVICE_NOT_ALIGNED = 5;
45 
46     @NonNull private static DemoSimulator sInstance;
47 
48     @NonNull private final Context mContext;
49     @NonNull private final SatelliteController mSatelliteController;
50     @NonNull private final PowerOffState mPowerOffState = new PowerOffState();
51     @NonNull private final NotConnectedState mNotConnectedState = new NotConnectedState();
52     @NonNull private final ConnectedState mConnectedState = new ConnectedState();
53     @NonNull private final Object mLock = new Object();
54     @GuardedBy("mLock")
55     private boolean mIsAligned = false;
56     private ISatelliteListener mISatelliteListener;
57 
58     /**
59      * @return The singleton instance of DemoSimulator.
60      */
getInstance()61     public static DemoSimulator getInstance() {
62         if (sInstance == null) {
63             Log.e(TAG, "DemoSimulator was not yet initialized.");
64         }
65         return sInstance;
66     }
67 
68     /**
69      * Create the DemoSimulator singleton instance.
70      *
71      * @param context The Context for the DemoSimulator.
72      * @return The singleton instance of DemoSimulator.
73      */
make(@onNull Context context, @NonNull SatelliteController satelliteController)74     public static DemoSimulator make(@NonNull Context context,
75             @NonNull SatelliteController satelliteController) {
76         if (sInstance == null) {
77             sInstance = new DemoSimulator(context, Looper.getMainLooper(), satelliteController);
78         }
79         return sInstance;
80     }
81 
82     /**
83      * Create a DemoSimulator.
84      *
85      * @param context The Context for the DemoSimulator.
86      * @param looper The looper associated with the handler of this class.
87      */
DemoSimulator(@onNull Context context, @NonNull Looper looper, @NonNull SatelliteController satelliteController)88     protected DemoSimulator(@NonNull Context context, @NonNull Looper looper,
89             @NonNull SatelliteController satelliteController) {
90         super(TAG, looper);
91 
92         mContext = context;
93         mSatelliteController = satelliteController;
94         addState(mPowerOffState);
95         addState(mNotConnectedState);
96         addState(mConnectedState);
97         setInitialState(mPowerOffState);
98         start();
99     }
100 
101     private class PowerOffState extends State {
102         @Override
enter()103         public void enter() {
104             logd("Entering PowerOffState");
105         }
106 
107         @Override
exit()108         public void exit() {
109             logd("Exiting PowerOffState");
110         }
111 
112         @Override
processMessage(Message msg)113         public boolean processMessage(Message msg) {
114             if (DBG) log("PowerOffState: processing " + getWhatToString(msg.what));
115             switch (msg.what) {
116                 case EVENT_SATELLITE_MODE_ON:
117                     transitionTo(mNotConnectedState);
118                     break;
119             }
120             // Ignore all unexpected events.
121             return HANDLED;
122         }
123     }
124 
125     private class NotConnectedState extends State {
126         @Override
enter()127         public void enter() {
128             logd("Entering NotConnectedState");
129 
130             try {
131                 NtnSignalStrength ntnSignalStrength = new NtnSignalStrength();
132                 ntnSignalStrength.signalStrengthLevel = 0;
133                 mISatelliteListener.onSatelliteModemStateChanged(
134                         SatelliteModemState.SATELLITE_MODEM_STATE_NOT_CONNECTED);
135                 mISatelliteListener.onNtnSignalStrengthChanged(ntnSignalStrength);
136 
137                 synchronized (mLock) {
138                     if (mIsAligned) {
139                         handleEventDeviceAlignedWithSatellite(true);
140                     }
141                 }
142             } catch (RemoteException e) {
143                 loge("NotConnectedState: RemoteException " + e);
144             }
145         }
146 
147         @Override
exit()148         public void exit() {
149             logd("Exiting NotConnectedState");
150         }
151 
152         @Override
processMessage(Message msg)153         public boolean processMessage(Message msg) {
154             if (DBG) log("NotConnectedState: processing " + getWhatToString(msg.what));
155             switch (msg.what) {
156                 case EVENT_SATELLITE_MODE_OFF:
157                     transitionTo(mPowerOffState);
158                     break;
159                 case EVENT_DEVICE_ALIGNED_WITH_SATELLITE:
160                     handleEventDeviceAlignedWithSatellite((boolean) msg.obj);
161                     break;
162                 case EVENT_DEVICE_ALIGNED:
163                     transitionTo(mConnectedState);
164                     break;
165             }
166             // Ignore all unexpected events.
167             return HANDLED;
168         }
169 
handleEventDeviceAlignedWithSatellite(boolean isAligned)170         private void handleEventDeviceAlignedWithSatellite(boolean isAligned) {
171             if (isAligned && !hasMessages(EVENT_DEVICE_ALIGNED)) {
172                 long durationMillis = mSatelliteController.getDemoPointingAlignedDurationMillis();
173                 logd("NotConnectedState: handleEventAlignedWithSatellite isAligned=true."
174                         + " Send delayed EVENT_DEVICE_ALIGNED message in"
175                         + " durationMillis=" + durationMillis);
176                 sendMessageDelayed(EVENT_DEVICE_ALIGNED, durationMillis);
177             } else if (!isAligned && hasMessages(EVENT_DEVICE_ALIGNED)) {
178                 logd("NotConnectedState: handleEventAlignedWithSatellite isAligned=false."
179                         + " Remove EVENT_DEVICE_ALIGNED message.");
180                 removeMessages(EVENT_DEVICE_ALIGNED);
181             }
182         }
183     }
184 
185     private class ConnectedState extends State {
186         @Override
enter()187         public void enter() {
188             logd("Entering ConnectedState");
189 
190             try {
191                 NtnSignalStrength ntnSignalStrength = new NtnSignalStrength();
192                 ntnSignalStrength.signalStrengthLevel = 2;
193                 mISatelliteListener.onSatelliteModemStateChanged(
194                         SatelliteModemState.SATELLITE_MODEM_STATE_CONNECTED);
195                 mISatelliteListener.onNtnSignalStrengthChanged(ntnSignalStrength);
196 
197                 synchronized (mLock) {
198                     if (!mIsAligned) {
199                         handleEventDeviceAlignedWithSatellite(false);
200                     }
201                 }
202             } catch (RemoteException e) {
203                 loge("ConnectedState: RemoteException " + e);
204             }
205         }
206 
207         @Override
exit()208         public void exit() {
209             logd("Exiting ConnectedState");
210         }
211 
212         @Override
processMessage(Message msg)213         public boolean processMessage(Message msg) {
214             if (DBG) log("ConnectedState: processing " + getWhatToString(msg.what));
215             switch (msg.what) {
216                 case EVENT_SATELLITE_MODE_OFF:
217                     transitionTo(mPowerOffState);
218                     break;
219                 case EVENT_DEVICE_ALIGNED_WITH_SATELLITE:
220                     handleEventDeviceAlignedWithSatellite((boolean) msg.obj);
221                     break;
222                 case EVENT_DEVICE_NOT_ALIGNED:
223                     transitionTo(mNotConnectedState);
224                     break;
225             }
226             // Ignore all unexpected events.
227             return HANDLED;
228         }
229 
handleEventDeviceAlignedWithSatellite(boolean isAligned)230         private void handleEventDeviceAlignedWithSatellite(boolean isAligned) {
231             if (!isAligned && !hasMessages(EVENT_DEVICE_NOT_ALIGNED)) {
232                 long durationMillis =
233                         mSatelliteController.getDemoPointingNotAlignedDurationMillis();
234                 logd("ConnectedState: handleEventAlignedWithSatellite isAligned=false."
235                         + " Send delayed EVENT_DEVICE_NOT_ALIGNED message"
236                         + " in durationMillis=" + durationMillis);
237                 sendMessageDelayed(EVENT_DEVICE_NOT_ALIGNED, durationMillis);
238             } else if (isAligned && hasMessages(EVENT_DEVICE_NOT_ALIGNED)) {
239                 logd("ConnectedState: handleEventAlignedWithSatellite isAligned=true."
240                         + " Remove EVENT_DEVICE_NOT_ALIGNED message.");
241                 removeMessages(EVENT_DEVICE_NOT_ALIGNED);
242             }
243         }
244     }
245 
246     /**
247      * @return the string for msg.what
248      */
249     @Override
getWhatToString(int what)250     protected String getWhatToString(int what) {
251         String whatString;
252         switch (what) {
253             case EVENT_SATELLITE_MODE_ON:
254                 whatString = "EVENT_SATELLITE_MODE_ON";
255                 break;
256             case EVENT_SATELLITE_MODE_OFF:
257                 whatString = "EVENT_SATELLITE_MODE_OFF";
258                 break;
259             case EVENT_DEVICE_ALIGNED_WITH_SATELLITE:
260                 whatString = "EVENT_DEVICE_ALIGNED_WITH_SATELLITE";
261                 break;
262             case EVENT_DEVICE_ALIGNED:
263                 whatString = "EVENT_DEVICE_ALIGNED";
264                 break;
265             case EVENT_DEVICE_NOT_ALIGNED:
266                 whatString = "EVENT_DEVICE_NOT_ALIGNED";
267                 break;
268             default:
269                 whatString = "UNKNOWN EVENT " + what;
270         }
271         return whatString;
272     }
273 
274     /**
275      * Register the callback interface with satellite service.
276      *
277      * @param listener The callback interface to handle satellite service indications.
278      */
setSatelliteListener(@onNull ISatelliteListener listener)279     public void setSatelliteListener(@NonNull ISatelliteListener listener) {
280         mISatelliteListener = listener;
281     }
282 
283     /**
284      * Allow cellular modem scanning while satellite mode is on.
285      *
286      * @param enabled  {@code true} to enable cellular modem while satellite mode is on
287      *                             and {@code false} to disable
288      * @param errorCallback The callback to receive the error code result of the operation.
289      */
enableCellularModemWhileSatelliteModeIsOn(boolean enabled, @NonNull IIntegerConsumer errorCallback)290     public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
291             @NonNull IIntegerConsumer errorCallback) {
292         try {
293             errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS);
294         } catch (RemoteException e) {
295             loge("enableCellularModemWhileSatelliteModeIsOn: RemoteException " + e);
296         }
297     }
298 
299     /**
300      * This function is used by {@link SatelliteSessionController} to notify {@link DemoSimulator}
301      * that satellite mode is ON.
302      */
onSatelliteModeOn()303     public void onSatelliteModeOn() {
304         if (mSatelliteController.isDemoModeEnabled()) {
305             sendMessage(EVENT_SATELLITE_MODE_ON);
306         }
307     }
308 
309     /**
310      * This function is used by {@link SatelliteSessionController} to notify {@link DemoSimulator}
311      * that satellite mode is OFF.
312      */
onSatelliteModeOff()313     public void onSatelliteModeOff() {
314         sendMessage(EVENT_SATELLITE_MODE_OFF);
315     }
316 
317     /**
318      * Set whether the device is aligned with the satellite.
319      */
320     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setDeviceAlignedWithSatellite(boolean isAligned)321     public void setDeviceAlignedWithSatellite(boolean isAligned) {
322         synchronized (mLock) {
323             if (mSatelliteController.isDemoModeEnabled()) {
324                 mIsAligned = isAligned;
325                 sendMessage(EVENT_DEVICE_ALIGNED_WITH_SATELLITE, isAligned);
326             }
327         }
328     }
329 }
330