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