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