1 /* 2 * Copyright (C) 2023 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.annotation.Nullable; 21 import android.content.ActivityNotFoundException; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.os.AsyncResult; 26 import android.os.Build; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.Looper; 30 import android.os.Message; 31 import android.os.RemoteException; 32 import android.os.SystemProperties; 33 import android.telephony.Rlog; 34 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback; 35 import android.telephony.satellite.PointingInfo; 36 import android.telephony.satellite.SatelliteManager; 37 import android.text.TextUtils; 38 39 import com.android.internal.R; 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.telephony.Phone; 42 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.concurrent.ConcurrentHashMap; 46 import java.util.function.Consumer; 47 48 /** 49 * PointingApp controller to manage interactions with PointingUI app. 50 */ 51 public class PointingAppController { 52 private static final String TAG = "PointingAppController"; 53 private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem"; 54 private static final boolean DEBUG = !"user".equals(Build.TYPE); 55 56 @NonNull 57 private static PointingAppController sInstance; 58 @NonNull private final Context mContext; 59 private boolean mStartedSatelliteTransmissionUpdates; 60 @NonNull private String mPointingUiPackageName = ""; 61 @NonNull private String mPointingUiClassName = ""; 62 63 /** 64 * Map key: subId, value: SatelliteTransmissionUpdateHandler to notify registrants. 65 */ 66 private final ConcurrentHashMap<Integer, SatelliteTransmissionUpdateHandler> 67 mSatelliteTransmissionUpdateHandlers = new ConcurrentHashMap<>(); 68 69 /** 70 * @return The singleton instance of PointingAppController. 71 */ getInstance()72 public static PointingAppController getInstance() { 73 if (sInstance == null) { 74 loge("PointingAppController was not yet initialized."); 75 } 76 return sInstance; 77 } 78 79 /** 80 * Create the PointingAppController singleton instance. 81 * @param context The Context to use to create the PointingAppController. 82 * @return The singleton instance of PointingAppController. 83 */ make(@onNull Context context)84 public static PointingAppController make(@NonNull Context context) { 85 if (sInstance == null) { 86 sInstance = new PointingAppController(context); 87 } 88 return sInstance; 89 } 90 91 /** 92 * Create a PointingAppController to manage interactions with PointingUI app. 93 * 94 * @param context The Context for the PointingUIController. 95 */ 96 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) PointingAppController(@onNull Context context)97 public PointingAppController(@NonNull Context context) { 98 mContext = context; 99 mStartedSatelliteTransmissionUpdates = false; 100 } 101 102 /** 103 * Set the flag mStartedSatelliteTransmissionUpdates to true or false based on the state of 104 * transmission updates 105 * @param startedSatelliteTransmissionUpdates boolean to set the flag 106 */ 107 @VisibleForTesting setStartedSatelliteTransmissionUpdates( boolean startedSatelliteTransmissionUpdates)108 public void setStartedSatelliteTransmissionUpdates( 109 boolean startedSatelliteTransmissionUpdates) { 110 mStartedSatelliteTransmissionUpdates = startedSatelliteTransmissionUpdates; 111 } 112 113 /** 114 * Get the flag mStartedSatelliteTransmissionUpdates 115 * @return returns mStartedSatelliteTransmissionUpdates 116 */ 117 @VisibleForTesting getStartedSatelliteTransmissionUpdates()118 public boolean getStartedSatelliteTransmissionUpdates() { 119 return mStartedSatelliteTransmissionUpdates; 120 } 121 122 private static final class DatagramTransferStateHandlerRequest { 123 public int datagramTransferState; 124 public int pendingCount; 125 public int errorCode; 126 DatagramTransferStateHandlerRequest(int datagramTransferState, int pendingCount, int errorCode)127 DatagramTransferStateHandlerRequest(int datagramTransferState, int pendingCount, 128 int errorCode) { 129 this.datagramTransferState = datagramTransferState; 130 this.pendingCount = pendingCount; 131 this.errorCode = errorCode; 132 } 133 } 134 135 136 private static final class SatelliteTransmissionUpdateHandler extends Handler { 137 public static final int EVENT_POSITION_INFO_CHANGED = 1; 138 public static final int EVENT_SEND_DATAGRAM_STATE_CHANGED = 2; 139 public static final int EVENT_RECEIVE_DATAGRAM_STATE_CHANGED = 3; 140 public static final int EVENT_DATAGRAM_TRANSFER_STATE_CHANGED = 4; 141 142 private final ConcurrentHashMap<IBinder, ISatelliteTransmissionUpdateCallback> mListeners; 143 SatelliteTransmissionUpdateHandler(Looper looper)144 SatelliteTransmissionUpdateHandler(Looper looper) { 145 super(looper); 146 mListeners = new ConcurrentHashMap<>(); 147 } 148 addListener(ISatelliteTransmissionUpdateCallback listener)149 public void addListener(ISatelliteTransmissionUpdateCallback listener) { 150 mListeners.put(listener.asBinder(), listener); 151 } 152 removeListener(ISatelliteTransmissionUpdateCallback listener)153 public void removeListener(ISatelliteTransmissionUpdateCallback listener) { 154 mListeners.remove(listener.asBinder()); 155 } 156 hasListeners()157 public boolean hasListeners() { 158 return !mListeners.isEmpty(); 159 } 160 161 @Override handleMessage(@onNull Message msg)162 public void handleMessage(@NonNull Message msg) { 163 switch (msg.what) { 164 case EVENT_POSITION_INFO_CHANGED: { 165 AsyncResult ar = (AsyncResult) msg.obj; 166 PointingInfo pointingInfo = (PointingInfo) ar.result; 167 List<IBinder> toBeRemoved = new ArrayList<>(); 168 mListeners.values().forEach(listener -> { 169 try { 170 listener.onSatellitePositionChanged(pointingInfo); 171 } catch (RemoteException e) { 172 logd("EVENT_POSITION_INFO_CHANGED RemoteException: " + e); 173 toBeRemoved.add(listener.asBinder()); 174 } 175 }); 176 toBeRemoved.forEach(listener -> { 177 mListeners.remove(listener); 178 }); 179 break; 180 } 181 182 case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED: { 183 AsyncResult ar = (AsyncResult) msg.obj; 184 logd("Receive EVENT_DATAGRAM_TRANSFER_STATE_CHANGED state=" + (int) ar.result); 185 break; 186 } 187 188 case EVENT_SEND_DATAGRAM_STATE_CHANGED: { 189 logd("Received EVENT_SEND_DATAGRAM_STATE_CHANGED"); 190 DatagramTransferStateHandlerRequest request = 191 (DatagramTransferStateHandlerRequest) msg.obj; 192 List<IBinder> toBeRemoved = new ArrayList<>(); 193 mListeners.values().forEach(listener -> { 194 try { 195 listener.onSendDatagramStateChanged(request.datagramTransferState, 196 request.pendingCount, request.errorCode); 197 } catch (RemoteException e) { 198 logd("EVENT_SEND_DATAGRAM_STATE_CHANGED RemoteException: " + e); 199 toBeRemoved.add(listener.asBinder()); 200 } 201 }); 202 toBeRemoved.forEach(listener -> { 203 mListeners.remove(listener); 204 }); 205 break; 206 } 207 208 case EVENT_RECEIVE_DATAGRAM_STATE_CHANGED: { 209 logd("Received EVENT_RECEIVE_DATAGRAM_STATE_CHANGED"); 210 DatagramTransferStateHandlerRequest request = 211 (DatagramTransferStateHandlerRequest) msg.obj; 212 List<IBinder> toBeRemoved = new ArrayList<>(); 213 mListeners.values().forEach(listener -> { 214 try { 215 listener.onReceiveDatagramStateChanged(request.datagramTransferState, 216 request.pendingCount, request.errorCode); 217 } catch (RemoteException e) { 218 logd("EVENT_RECEIVE_DATAGRAM_STATE_CHANGED RemoteException: " + e); 219 toBeRemoved.add(listener.asBinder()); 220 } 221 }); 222 toBeRemoved.forEach(listener -> { 223 mListeners.remove(listener); 224 }); 225 break; 226 } 227 228 default: 229 loge("SatelliteTransmissionUpdateHandler unknown event: " + msg.what); 230 } 231 } 232 } 233 234 /** 235 * Register to start receiving updates for satellite position and datagram transfer state 236 * @param subId The subId of the subscription to register for receiving the updates. 237 * @param callback The callback to notify of satellite transmission updates. 238 * @param phone The Phone object to unregister for receiving the updates. 239 */ registerForSatelliteTransmissionUpdates(int subId, ISatelliteTransmissionUpdateCallback callback, Phone phone)240 public void registerForSatelliteTransmissionUpdates(int subId, 241 ISatelliteTransmissionUpdateCallback callback, Phone phone) { 242 SatelliteTransmissionUpdateHandler handler = 243 mSatelliteTransmissionUpdateHandlers.get(subId); 244 if (handler != null) { 245 handler.addListener(callback); 246 return; 247 } else { 248 handler = new SatelliteTransmissionUpdateHandler(Looper.getMainLooper()); 249 handler.addListener(callback); 250 mSatelliteTransmissionUpdateHandlers.put(subId, handler); 251 if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { 252 SatelliteModemInterface.getInstance().registerForSatellitePositionInfoChanged( 253 handler, SatelliteTransmissionUpdateHandler.EVENT_POSITION_INFO_CHANGED, 254 null); 255 SatelliteModemInterface.getInstance().registerForDatagramTransferStateChanged( 256 handler, 257 SatelliteTransmissionUpdateHandler.EVENT_DATAGRAM_TRANSFER_STATE_CHANGED, 258 null); 259 } else { 260 phone.registerForSatellitePositionInfoChanged(handler, 261 SatelliteTransmissionUpdateHandler.EVENT_POSITION_INFO_CHANGED, null); 262 } 263 } 264 } 265 266 /** 267 * Unregister to stop receiving updates on satellite position and datagram transfer state 268 * If the callback was not registered before, it is ignored 269 * @param subId The subId of the subscription to unregister for receiving the updates. 270 * @param result The callback to get the error code in case of failure 271 * @param callback The callback that was passed to {@link 272 * #registerForSatelliteTransmissionUpdates(int, ISatelliteTransmissionUpdateCallback, Phone)}. 273 * @param phone The Phone object to unregister for receiving the updates 274 */ unregisterForSatelliteTransmissionUpdates(int subId, Consumer<Integer> result, ISatelliteTransmissionUpdateCallback callback, Phone phone)275 public void unregisterForSatelliteTransmissionUpdates(int subId, Consumer<Integer> result, 276 ISatelliteTransmissionUpdateCallback callback, Phone phone) { 277 SatelliteTransmissionUpdateHandler handler = 278 mSatelliteTransmissionUpdateHandlers.get(subId); 279 if (handler != null) { 280 handler.removeListener(callback); 281 if (handler.hasListeners()) { 282 result.accept(SatelliteManager.SATELLITE_ERROR_NONE); 283 return; 284 } 285 286 mSatelliteTransmissionUpdateHandlers.remove(subId); 287 if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { 288 SatelliteModemInterface.getInstance().unregisterForSatellitePositionInfoChanged( 289 handler); 290 SatelliteModemInterface.getInstance().unregisterForDatagramTransferStateChanged( 291 handler); 292 } else { 293 if (phone == null) { 294 result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); 295 return; 296 } 297 phone.unregisterForSatellitePositionInfoChanged(handler); 298 } 299 } 300 } 301 302 /** 303 * Start receiving satellite trasmission updates. 304 * This can be called by the pointing UI when the user starts pointing to the satellite. 305 * Modem should continue to report the pointing input as the device or satellite moves. 306 * The transmission updates will be received via 307 * {@link android.telephony.satellite.SatelliteTransmissionUpdateCallback 308 * #onSatellitePositionChanged(pointingInfo)}. 309 */ startSatelliteTransmissionUpdates(@onNull Message message, @Nullable Phone phone)310 public void startSatelliteTransmissionUpdates(@NonNull Message message, @Nullable Phone phone) { 311 if (mStartedSatelliteTransmissionUpdates) { 312 logd("startSatelliteTransmissionUpdates: already started"); 313 AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException( 314 SatelliteManager.SATELLITE_ERROR_NONE)); 315 message.sendToTarget(); 316 return; 317 } 318 if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { 319 SatelliteModemInterface.getInstance().startSendingSatellitePointingInfo(message); 320 mStartedSatelliteTransmissionUpdates = true; 321 return; 322 } 323 if (phone != null) { 324 phone.startSatellitePositionUpdates(message); 325 mStartedSatelliteTransmissionUpdates = true; 326 } else { 327 loge("startSatelliteTransmissionUpdates: No phone object"); 328 AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException( 329 SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)); 330 message.sendToTarget(); 331 } 332 } 333 334 /** 335 * Stop receiving satellite transmission updates. 336 * Reset the flag mStartedSatelliteTransmissionUpdates 337 * This can be called by the pointing UI when the user stops pointing to the satellite. 338 */ stopSatelliteTransmissionUpdates(@onNull Message message, @Nullable Phone phone)339 public void stopSatelliteTransmissionUpdates(@NonNull Message message, @Nullable Phone phone) { 340 setStartedSatelliteTransmissionUpdates(false); 341 if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { 342 SatelliteModemInterface.getInstance().stopSendingSatellitePointingInfo(message); 343 return; 344 } 345 if (phone != null) { 346 phone.stopSatellitePositionUpdates(message); 347 } else { 348 loge("startSatelliteTransmissionUpdates: No phone object"); 349 AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException( 350 SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)); 351 message.sendToTarget(); 352 } 353 } 354 355 /** 356 * Check if Pointing is needed and Launch Pointing UI 357 * @param needFullScreenPointingUI if pointing UI has to be launchd with Full screen 358 */ startPointingUI(boolean needFullScreenPointingUI)359 public void startPointingUI(boolean needFullScreenPointingUI) { 360 String packageName = getPointingUiPackageName(); 361 if (TextUtils.isEmpty(packageName)) { 362 logd("startPointingUI: config_pointing_ui_package is not set. Ignore the request"); 363 return; 364 } 365 366 Intent launchIntent; 367 String className = getPointingUiClassName(); 368 if (!TextUtils.isEmpty(className)) { 369 launchIntent = new Intent() 370 .setComponent(new ComponentName(packageName, className)) 371 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 372 } else { 373 launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName); 374 } 375 if (launchIntent == null) { 376 loge("startPointingUI: launchIntent is null"); 377 return; 378 } 379 launchIntent.putExtra("needFullScreen", needFullScreenPointingUI); 380 381 try { 382 mContext.startActivity(launchIntent); 383 } catch (ActivityNotFoundException ex) { 384 loge("startPointingUI: Pointing UI app activity is not found, ex=" + ex); 385 } 386 } 387 updateSendDatagramTransferState(int subId, @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState, int sendPendingCount, int errorCode)388 public void updateSendDatagramTransferState(int subId, 389 @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState, 390 int sendPendingCount, int errorCode) { 391 DatagramTransferStateHandlerRequest request = new DatagramTransferStateHandlerRequest( 392 datagramTransferState, sendPendingCount, errorCode); 393 SatelliteTransmissionUpdateHandler handler = 394 mSatelliteTransmissionUpdateHandlers.get(subId); 395 396 if (handler != null) { 397 Message msg = handler.obtainMessage( 398 SatelliteTransmissionUpdateHandler.EVENT_SEND_DATAGRAM_STATE_CHANGED, 399 request); 400 msg.sendToTarget(); 401 } else { 402 loge("SatelliteTransmissionUpdateHandler not found for subId: " + subId); 403 } 404 } 405 updateReceiveDatagramTransferState(int subId, @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState, int receivePendingCount, int errorCode)406 public void updateReceiveDatagramTransferState(int subId, 407 @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState, 408 int receivePendingCount, int errorCode) { 409 DatagramTransferStateHandlerRequest request = new DatagramTransferStateHandlerRequest( 410 datagramTransferState, receivePendingCount, errorCode); 411 SatelliteTransmissionUpdateHandler handler = 412 mSatelliteTransmissionUpdateHandlers.get(subId); 413 414 if (handler != null) { 415 Message msg = handler.obtainMessage( 416 SatelliteTransmissionUpdateHandler.EVENT_RECEIVE_DATAGRAM_STATE_CHANGED, 417 request); 418 msg.sendToTarget(); 419 } else { 420 loge(" SatelliteTransmissionUpdateHandler not found for subId: " + subId); 421 } 422 } 423 424 /** 425 * This API can be used by only CTS to update satellite pointing UI app package and class names. 426 * 427 * @param packageName The package name of the satellite pointing UI app. 428 * @param className The class name of the satellite pointing UI app. 429 * @return {@code true} if the satellite pointing UI app package and class is set successfully, 430 * {@code false} otherwise. 431 */ setSatellitePointingUiClassName( @ullable String packageName, @Nullable String className)432 boolean setSatellitePointingUiClassName( 433 @Nullable String packageName, @Nullable String className) { 434 if (!isMockModemAllowed()) { 435 loge("setSatellitePointingUiClassName: modifying satellite pointing UI package and " 436 + "class name is not allowed"); 437 return false; 438 } 439 440 logd("setSatellitePointingUiClassName: config_pointing_ui_package is updated, new " 441 + "packageName=" + packageName 442 + ", config_pointing_ui_class new className=" + className); 443 444 if (packageName == null || packageName.equals("null")) { 445 mPointingUiPackageName = ""; 446 mPointingUiClassName = ""; 447 } else { 448 mPointingUiPackageName = packageName; 449 if (className == null || className.equals("null")) { 450 mPointingUiClassName = ""; 451 } else { 452 mPointingUiClassName = className; 453 } 454 } 455 456 return true; 457 } 458 getPointingUiPackageName()459 @NonNull private String getPointingUiPackageName() { 460 if (!TextUtils.isEmpty(mPointingUiPackageName)) { 461 return mPointingUiPackageName; 462 } 463 return TextUtils.emptyIfNull(mContext.getResources().getString( 464 R.string.config_pointing_ui_package)); 465 } 466 getPointingUiClassName()467 @NonNull private String getPointingUiClassName() { 468 if (!TextUtils.isEmpty(mPointingUiClassName)) { 469 return mPointingUiClassName; 470 } 471 return TextUtils.emptyIfNull(mContext.getResources().getString( 472 R.string.config_pointing_ui_class)); 473 } 474 isMockModemAllowed()475 private boolean isMockModemAllowed() { 476 return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false)); 477 } 478 logd(@onNull String log)479 private static void logd(@NonNull String log) { 480 Rlog.d(TAG, log); 481 } 482 loge(@onNull String log)483 private static void loge(@NonNull String log) { 484 Rlog.e(TAG, log); 485 } 486 /** 487 * TODO: The following needs to be added in this class: 488 * - check if pointingUI crashes - then restart it 489 */ 490 } 491