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 com.android.internal.telephony.satellite.DatagramController.ROUNDING_UNIT; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.content.ContentResolver; 24 import android.content.ContentValues; 25 import android.content.Context; 26 import android.content.SharedPreferences; 27 import android.database.Cursor; 28 import android.database.SQLException; 29 import android.net.Uri; 30 import android.os.AsyncResult; 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.provider.Telephony; 37 import android.telephony.Rlog; 38 import android.telephony.SubscriptionManager; 39 import android.telephony.satellite.ISatelliteDatagramCallback; 40 import android.telephony.satellite.SatelliteDatagram; 41 import android.telephony.satellite.SatelliteManager; 42 import android.util.Pair; 43 44 import com.android.internal.R; 45 import com.android.internal.annotations.GuardedBy; 46 import com.android.internal.annotations.VisibleForTesting; 47 import com.android.internal.telephony.IIntegerConsumer; 48 import com.android.internal.telephony.IVoidConsumer; 49 import com.android.internal.telephony.Phone; 50 import com.android.internal.telephony.metrics.SatelliteStats; 51 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats; 52 import com.android.internal.util.FunctionalUtils; 53 54 import java.util.concurrent.ConcurrentHashMap; 55 import java.util.concurrent.atomic.AtomicLong; 56 import java.util.function.Consumer; 57 58 /** 59 * Datagram receiver used to receive satellite datagrams and then, 60 * deliver received datagrams to messaging apps. 61 */ 62 public class DatagramReceiver extends Handler { 63 private static final String TAG = "DatagramReceiver"; 64 65 private static final int CMD_POLL_PENDING_SATELLITE_DATAGRAMS = 1; 66 private static final int EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE = 2; 67 private static final int EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT = 3; 68 69 /** Key used to read/write satellite datagramId in shared preferences. */ 70 private static final String SATELLITE_DATAGRAM_ID_KEY = "satellite_datagram_id_key"; 71 private static AtomicLong mNextDatagramId = new AtomicLong(0); 72 73 @NonNull private static DatagramReceiver sInstance; 74 @NonNull private final Context mContext; 75 @NonNull private final ContentResolver mContentResolver; 76 @NonNull private SharedPreferences mSharedPreferences = null; 77 @NonNull private final DatagramController mDatagramController; 78 @NonNull private final ControllerMetricsStats mControllerMetricsStats; 79 @NonNull private final Looper mLooper; 80 81 private long mDatagramTransferStartTime = 0; 82 private boolean mIsDemoMode = false; 83 @GuardedBy("mLock") 84 private boolean mIsAligned = false; 85 private DatagramReceiverHandlerRequest mPollPendingSatelliteDatagramsRequest = null; 86 private final Object mLock = new Object(); 87 88 /** 89 * Map key: subId, value: SatelliteDatagramListenerHandler to notify registrants. 90 */ 91 private final ConcurrentHashMap<Integer, SatelliteDatagramListenerHandler> 92 mSatelliteDatagramListenerHandlers = new ConcurrentHashMap<>(); 93 94 /** 95 * Map key: DatagramId, value: pendingAckCount 96 * This map is used to track number of listeners that are yet to send ack for a particular 97 * datagram. 98 */ 99 private final ConcurrentHashMap<Long, Integer> 100 mPendingAckCountHashMap = new ConcurrentHashMap<>(); 101 102 /** 103 * Create the DatagramReceiver singleton instance. 104 * @param context The Context to use to create the DatagramReceiver. 105 * @param looper The looper for the handler. 106 * @param datagramController DatagramController which is used to update datagram transfer state. 107 * @return The singleton instance of DatagramReceiver. 108 */ make(@onNull Context context, @NonNull Looper looper, @NonNull DatagramController datagramController)109 public static DatagramReceiver make(@NonNull Context context, @NonNull Looper looper, 110 @NonNull DatagramController datagramController) { 111 if (sInstance == null) { 112 sInstance = new DatagramReceiver(context, looper, datagramController); 113 } 114 return sInstance; 115 } 116 117 /** 118 * Create a DatagramReceiver to received satellite datagrams. 119 * The received datagrams will be delivered to respective messaging apps. 120 * 121 * @param context The Context for the DatagramReceiver. 122 * @param looper The looper for the handler. 123 * @param datagramController DatagramController which is used to update datagram transfer state. 124 */ 125 @VisibleForTesting DatagramReceiver(@onNull Context context, @NonNull Looper looper, @NonNull DatagramController datagramController)126 protected DatagramReceiver(@NonNull Context context, @NonNull Looper looper, 127 @NonNull DatagramController datagramController) { 128 super(looper); 129 mContext = context; 130 mLooper = looper; 131 mContentResolver = context.getContentResolver(); 132 mDatagramController = datagramController; 133 mControllerMetricsStats = ControllerMetricsStats.getInstance(); 134 135 try { 136 mSharedPreferences = 137 mContext.getSharedPreferences(SatelliteController.SATELLITE_SHARED_PREF, 138 Context.MODE_PRIVATE); 139 } catch (Exception e) { 140 loge("Cannot get default shared preferences: " + e); 141 } 142 143 if ((mSharedPreferences != null) && 144 (!mSharedPreferences.contains(SATELLITE_DATAGRAM_ID_KEY))) { 145 mSharedPreferences.edit().putLong(SATELLITE_DATAGRAM_ID_KEY, mNextDatagramId.get()) 146 .commit(); 147 } 148 } 149 150 private static final class DatagramReceiverHandlerRequest { 151 /** The argument to use for the request */ 152 public @NonNull Object argument; 153 /** The caller needs to specify the phone to be used for the request */ 154 public @NonNull Phone phone; 155 /** The subId of the subscription used for the request. */ 156 public int subId; 157 /** The result of the request that is run on the main thread */ 158 public @Nullable Object result; 159 DatagramReceiverHandlerRequest(Object argument, Phone phone, int subId)160 DatagramReceiverHandlerRequest(Object argument, Phone phone, int subId) { 161 this.argument = argument; 162 this.phone = phone; 163 this.subId = subId; 164 } 165 } 166 167 /** 168 * Listeners are updated about incoming datagrams using a backgroundThread. 169 */ 170 @VisibleForTesting 171 public static final class SatelliteDatagramListenerHandler extends Handler { 172 public static final int EVENT_SATELLITE_DATAGRAM_RECEIVED = 1; 173 public static final int EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM = 2; 174 public static final int EVENT_RECEIVED_ACK = 3; 175 176 @NonNull private final ConcurrentHashMap<IBinder, ISatelliteDatagramCallback> mListeners; 177 private final int mSubId; 178 179 private static final class DatagramRetryArgument { 180 public long datagramId; 181 @NonNull public SatelliteDatagram datagram; 182 public int pendingCount; 183 @NonNull public ISatelliteDatagramCallback listener; 184 DatagramRetryArgument(long datagramId, @NonNull SatelliteDatagram datagram, int pendingCount, @NonNull ISatelliteDatagramCallback listener)185 DatagramRetryArgument(long datagramId, @NonNull SatelliteDatagram datagram, 186 int pendingCount, @NonNull ISatelliteDatagramCallback listener) { 187 this.datagramId = datagramId; 188 this.datagram = datagram; 189 this.pendingCount = pendingCount; 190 this.listener = listener; 191 } 192 193 @Override equals(Object other)194 public boolean equals(Object other) { 195 if (this == other) return true; 196 if (other == null || getClass() != other.getClass()) return false; 197 DatagramRetryArgument that = (DatagramRetryArgument) other; 198 return datagramId == that.datagramId 199 && datagram.equals(that.datagram) 200 && pendingCount == that.pendingCount 201 && listener.equals(that.listener); 202 } 203 } 204 205 @VisibleForTesting SatelliteDatagramListenerHandler(@onNull Looper looper, int subId)206 public SatelliteDatagramListenerHandler(@NonNull Looper looper, int subId) { 207 super(looper); 208 mSubId = subId; 209 mListeners = new ConcurrentHashMap<>(); 210 } 211 addListener(@onNull ISatelliteDatagramCallback listener)212 public void addListener(@NonNull ISatelliteDatagramCallback listener) { 213 mListeners.put(listener.asBinder(), listener); 214 } 215 removeListener(@onNull ISatelliteDatagramCallback listener)216 public void removeListener(@NonNull ISatelliteDatagramCallback listener) { 217 mListeners.remove(listener.asBinder()); 218 } 219 hasListeners()220 public boolean hasListeners() { 221 return !mListeners.isEmpty(); 222 } 223 getNumOfListeners()224 public int getNumOfListeners() { 225 return mListeners.size(); 226 } 227 getTimeoutToReceiveAck()228 private int getTimeoutToReceiveAck() { 229 return sInstance.mContext.getResources().getInteger( 230 R.integer.config_timeout_to_receive_delivered_ack_millis); 231 } 232 getDatagramId()233 private long getDatagramId() { 234 long datagramId = 0; 235 if (sInstance.mSharedPreferences == null) { 236 try { 237 // Try to recreate if it is null 238 sInstance.mSharedPreferences = sInstance.mContext 239 .getSharedPreferences(SatelliteController.SATELLITE_SHARED_PREF, 240 Context.MODE_PRIVATE); 241 } catch (Exception e) { 242 loge("Cannot get default shared preferences: " + e); 243 } 244 } 245 246 if (sInstance.mSharedPreferences != null) { 247 long prevDatagramId = sInstance.mSharedPreferences 248 .getLong(SATELLITE_DATAGRAM_ID_KEY, mNextDatagramId.get()); 249 datagramId = (prevDatagramId + 1) % DatagramController.MAX_DATAGRAM_ID; 250 mNextDatagramId.set(datagramId); 251 sInstance.mSharedPreferences.edit().putLong(SATELLITE_DATAGRAM_ID_KEY, datagramId) 252 .commit(); 253 } else { 254 loge("Shared preferences is null - returning default datagramId"); 255 datagramId = mNextDatagramId.getAndUpdate( 256 n -> ((n + 1) % DatagramController.MAX_DATAGRAM_ID)); 257 } 258 259 return datagramId; 260 } 261 insertDatagram(long datagramId, @NonNull SatelliteDatagram datagram)262 private void insertDatagram(long datagramId, @NonNull SatelliteDatagram datagram) { 263 ContentValues contentValues = new ContentValues(); 264 contentValues.put( 265 Telephony.SatelliteDatagrams.COLUMN_UNIQUE_KEY_DATAGRAM_ID, datagramId); 266 contentValues.put( 267 Telephony.SatelliteDatagrams.COLUMN_DATAGRAM, datagram.getSatelliteDatagram()); 268 Uri uri = sInstance.mContentResolver.insert(Telephony.SatelliteDatagrams.CONTENT_URI, 269 contentValues); 270 if (uri == null) { 271 loge("Cannot insert datagram with datagramId: " + datagramId); 272 } else { 273 logd("Inserted datagram with datagramId: " + datagramId); 274 } 275 } 276 deleteDatagram(long datagramId)277 private void deleteDatagram(long datagramId) { 278 String whereClause = (Telephony.SatelliteDatagrams.COLUMN_UNIQUE_KEY_DATAGRAM_ID 279 + "=" + datagramId); 280 try (Cursor cursor = sInstance.mContentResolver.query( 281 Telephony.SatelliteDatagrams.CONTENT_URI, 282 null, whereClause, null, null)) { 283 if ((cursor != null) && (cursor.getCount() == 1)) { 284 int numRowsDeleted = sInstance.mContentResolver.delete( 285 Telephony.SatelliteDatagrams.CONTENT_URI, whereClause, null); 286 if (numRowsDeleted == 0) { 287 loge("Cannot delete datagram with datagramId: " + datagramId); 288 } else { 289 logd("Deleted datagram with datagramId: " + datagramId); 290 } 291 } else { 292 loge("Datagram with datagramId: " + datagramId + " is not present in DB."); 293 } 294 } catch(SQLException e) { 295 loge("deleteDatagram SQLException e:" + e); 296 } 297 } 298 onSatelliteDatagramReceived(@onNull DatagramRetryArgument argument)299 private void onSatelliteDatagramReceived(@NonNull DatagramRetryArgument argument) { 300 try { 301 IVoidConsumer internalAck = new IVoidConsumer.Stub() { 302 /** 303 * This callback will be used by datagram receiver app 304 * to send ack back to Telephony. If the callback is not 305 * received within five minutes, then Telephony will 306 * resend the datagram again. 307 */ 308 @Override 309 public void accept() { 310 logd("acknowledgeSatelliteDatagramReceived: " 311 + "datagramId=" + argument.datagramId); 312 sendMessage(obtainMessage(EVENT_RECEIVED_ACK, argument)); 313 } 314 }; 315 316 argument.listener.onSatelliteDatagramReceived(argument.datagramId, 317 argument.datagram, argument.pendingCount, internalAck); 318 } catch (RemoteException e) { 319 logd("EVENT_SATELLITE_DATAGRAM_RECEIVED RemoteException: " + e); 320 } 321 } 322 323 @Override handleMessage(@onNull Message msg)324 public void handleMessage(@NonNull Message msg) { 325 switch (msg.what) { 326 case EVENT_SATELLITE_DATAGRAM_RECEIVED: { 327 AsyncResult ar = (AsyncResult) msg.obj; 328 Pair<SatelliteDatagram, Integer> result = 329 (Pair<SatelliteDatagram, Integer>) ar.result; 330 SatelliteDatagram satelliteDatagram = result.first; 331 int pendingCount = result.second; 332 logd("Received EVENT_SATELLITE_DATAGRAM_RECEIVED for subId=" + mSubId 333 + " pendingCount:" + pendingCount); 334 335 if (pendingCount <= 0 && satelliteDatagram == null) { 336 sInstance.mDatagramController.updateReceiveStatus(mSubId, 337 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE, 338 pendingCount, SatelliteManager.SATELLITE_ERROR_NONE); 339 } else if (satelliteDatagram != null) { 340 sInstance.mDatagramController.updateReceiveStatus(mSubId, 341 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS, 342 pendingCount, SatelliteManager.SATELLITE_ERROR_NONE); 343 344 long datagramId = getDatagramId(); 345 sInstance.mPendingAckCountHashMap.put(datagramId, getNumOfListeners()); 346 insertDatagram(datagramId, satelliteDatagram); 347 348 mListeners.values().forEach(listener -> { 349 DatagramRetryArgument argument = new DatagramRetryArgument(datagramId, 350 satelliteDatagram, pendingCount, listener); 351 onSatelliteDatagramReceived(argument); 352 // wait for ack and retry after the timeout specified. 353 sendMessageDelayed( 354 obtainMessage(EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM, 355 argument), getTimeoutToReceiveAck()); 356 }); 357 358 sInstance.mControllerMetricsStats.reportIncomingDatagramCount( 359 SatelliteManager.SATELLITE_ERROR_NONE); 360 } 361 362 if (pendingCount <= 0) { 363 sInstance.mDatagramController.updateReceiveStatus(mSubId, 364 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 365 pendingCount, SatelliteManager.SATELLITE_ERROR_NONE); 366 } else { 367 // Poll for pending datagrams 368 IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() { 369 @Override 370 public void accept(int result) { 371 logd("pollPendingSatelliteDatagram result: " + result); 372 } 373 }; 374 Consumer<Integer> callback = FunctionalUtils.ignoreRemoteException( 375 internalCallback::accept); 376 sInstance.pollPendingSatelliteDatagramsInternal(mSubId, callback); 377 } 378 379 // Send the captured data about incoming datagram to metric 380 sInstance.reportMetrics( 381 satelliteDatagram, SatelliteManager.SATELLITE_ERROR_NONE); 382 break; 383 } 384 385 case EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM: { 386 DatagramRetryArgument argument = (DatagramRetryArgument) msg.obj; 387 logd("Received EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM datagramId:" 388 + argument.datagramId); 389 onSatelliteDatagramReceived(argument); 390 break; 391 } 392 393 case EVENT_RECEIVED_ACK: { 394 DatagramRetryArgument argument = (DatagramRetryArgument) msg.obj; 395 int pendingAckCount = sInstance.mPendingAckCountHashMap 396 .get(argument.datagramId); 397 pendingAckCount -= 1; 398 sInstance.mPendingAckCountHashMap.put(argument.datagramId, pendingAckCount); 399 logd("Received EVENT_RECEIVED_ACK datagramId:" + argument.datagramId); 400 removeMessages(EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM, argument); 401 402 if (pendingAckCount <= 0) { 403 // Delete datagram from DB after receiving ack from all listeners 404 deleteDatagram(argument.datagramId); 405 sInstance.mPendingAckCountHashMap.remove(argument.datagramId); 406 } 407 break; 408 } 409 410 default: 411 loge("SatelliteDatagramListenerHandler unknown event: " + msg.what); 412 } 413 } 414 } 415 416 @Override handleMessage(Message msg)417 public void handleMessage(Message msg) { 418 DatagramReceiverHandlerRequest request; 419 Message onCompleted; 420 AsyncResult ar; 421 422 switch (msg.what) { 423 case CMD_POLL_PENDING_SATELLITE_DATAGRAMS: { 424 request = (DatagramReceiverHandlerRequest) msg.obj; 425 onCompleted = 426 obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE, request); 427 428 if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { 429 SatelliteModemInterface.getInstance() 430 .pollPendingSatelliteDatagrams(onCompleted); 431 break; 432 } 433 434 Phone phone = request.phone; 435 if (phone != null) { 436 phone.pollPendingSatelliteDatagrams(onCompleted); 437 } else { 438 loge("pollPendingSatelliteDatagrams: No phone object"); 439 mDatagramController.updateReceiveStatus(request.subId, 440 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED, 441 mDatagramController.getReceivePendingCount(), 442 SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); 443 444 mDatagramController.updateReceiveStatus(request.subId, 445 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 446 mDatagramController.getReceivePendingCount(), 447 SatelliteManager.SATELLITE_ERROR_NONE); 448 449 reportMetrics(null, SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); 450 mControllerMetricsStats.reportIncomingDatagramCount( 451 SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); 452 // Send response for current request 453 ((Consumer<Integer>) request.argument) 454 .accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); 455 } 456 break; 457 } 458 459 case EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE: { 460 ar = (AsyncResult) msg.obj; 461 request = (DatagramReceiverHandlerRequest) ar.userObj; 462 int error = SatelliteServiceUtils.getSatelliteError(ar, 463 "pollPendingSatelliteDatagrams"); 464 465 if (mIsDemoMode && error == SatelliteManager.SATELLITE_ERROR_NONE) { 466 SatelliteDatagram datagram = mDatagramController.getDemoModeDatagram(); 467 final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId( 468 request.subId, mContext); 469 SatelliteDatagramListenerHandler listenerHandler = 470 mSatelliteDatagramListenerHandlers.get(validSubId); 471 if (listenerHandler != null) { 472 Pair<SatelliteDatagram, Integer> pair = new Pair<>(datagram, 0); 473 ar = new AsyncResult(null, pair, null); 474 Message message = listenerHandler.obtainMessage( 475 SatelliteDatagramListenerHandler.EVENT_SATELLITE_DATAGRAM_RECEIVED, 476 ar); 477 listenerHandler.sendMessage(message); 478 } else { 479 error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE; 480 } 481 } 482 483 logd("EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE error: " + error); 484 if (error != SatelliteManager.SATELLITE_ERROR_NONE) { 485 mDatagramController.updateReceiveStatus(request.subId, 486 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED, 487 mDatagramController.getReceivePendingCount(), error); 488 489 mDatagramController.updateReceiveStatus(request.subId, 490 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 491 mDatagramController.getReceivePendingCount(), 492 SatelliteManager.SATELLITE_ERROR_NONE); 493 494 reportMetrics(null, error); 495 mControllerMetricsStats.reportIncomingDatagramCount(error); 496 } 497 // Send response for current request 498 ((Consumer<Integer>) request.argument).accept(error); 499 break; 500 } 501 502 case EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT: { 503 handleEventSatelliteAlignedTimeout((DatagramReceiverHandlerRequest) msg.obj); 504 break; 505 } 506 } 507 } 508 509 /** 510 * Register to receive incoming datagrams over satellite. 511 * 512 * @param subId The subId of the subscription to register for incoming satellite datagrams. 513 * @param callback The callback to handle incoming datagrams over satellite. 514 * 515 * @return The {@link SatelliteManager.SatelliteError} result of the operation. 516 */ registerForSatelliteDatagram(int subId, @NonNull ISatelliteDatagramCallback callback)517 @SatelliteManager.SatelliteError public int registerForSatelliteDatagram(int subId, 518 @NonNull ISatelliteDatagramCallback callback) { 519 if (!SatelliteController.getInstance().isSatelliteSupported()) { 520 return SatelliteManager.SATELLITE_NOT_SUPPORTED; 521 } 522 523 final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext); 524 SatelliteDatagramListenerHandler satelliteDatagramListenerHandler = 525 mSatelliteDatagramListenerHandlers.get(validSubId); 526 if (satelliteDatagramListenerHandler == null) { 527 satelliteDatagramListenerHandler = new SatelliteDatagramListenerHandler( 528 mLooper, validSubId); 529 if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { 530 SatelliteModemInterface.getInstance().registerForSatelliteDatagramsReceived( 531 satelliteDatagramListenerHandler, 532 SatelliteDatagramListenerHandler.EVENT_SATELLITE_DATAGRAM_RECEIVED, null); 533 } else { 534 Phone phone = SatelliteServiceUtils.getPhone(); 535 phone.registerForSatelliteDatagramsReceived(satelliteDatagramListenerHandler, 536 SatelliteDatagramListenerHandler.EVENT_SATELLITE_DATAGRAM_RECEIVED, null); 537 } 538 } 539 540 satelliteDatagramListenerHandler.addListener(callback); 541 mSatelliteDatagramListenerHandlers.put(validSubId, satelliteDatagramListenerHandler); 542 return SatelliteManager.SATELLITE_ERROR_NONE; 543 } 544 545 /** 546 * Unregister to stop receiving incoming datagrams over satellite. 547 * If callback was not registered before, the request will be ignored. 548 * 549 * @param subId The subId of the subscription to unregister for incoming satellite datagrams. 550 * @param callback The callback that was passed to 551 * {@link #registerForSatelliteDatagram(int, ISatelliteDatagramCallback)}. 552 */ unregisterForSatelliteDatagram(int subId, @NonNull ISatelliteDatagramCallback callback)553 public void unregisterForSatelliteDatagram(int subId, 554 @NonNull ISatelliteDatagramCallback callback) { 555 final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext); 556 SatelliteDatagramListenerHandler handler = 557 mSatelliteDatagramListenerHandlers.get(validSubId); 558 if (handler != null) { 559 handler.removeListener(callback); 560 561 if (!handler.hasListeners()) { 562 mSatelliteDatagramListenerHandlers.remove(validSubId); 563 if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { 564 SatelliteModemInterface.getInstance() 565 .unregisterForSatelliteDatagramsReceived(handler); 566 } else { 567 Phone phone = SatelliteServiceUtils.getPhone(); 568 if (phone != null) { 569 phone.unregisterForSatelliteDatagramsReceived(handler); 570 } 571 } 572 } 573 } 574 } 575 576 /** 577 * Poll pending satellite datagrams over satellite. 578 * 579 * This method requests modem to check if there are any pending datagrams to be received over 580 * satellite. If there are any incoming datagrams, they will be received via 581 * {@link android.telephony.satellite.SatelliteDatagramCallback 582 * #onSatelliteDatagramReceived(long, SatelliteDatagram, int, Consumer)} 583 * 584 * @param subId The subId of the subscription used for receiving datagrams. 585 * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request. 586 */ pollPendingSatelliteDatagrams(int subId, @NonNull Consumer<Integer> callback)587 public void pollPendingSatelliteDatagrams(int subId, @NonNull Consumer<Integer> callback) { 588 if (!mDatagramController.isPollingInIdleState()) { 589 // Poll request should be sent to satellite modem only when it is free. 590 logd("pollPendingSatelliteDatagrams: satellite modem is busy receiving datagrams."); 591 callback.accept(SatelliteManager.SATELLITE_MODEM_BUSY); 592 return; 593 } 594 595 pollPendingSatelliteDatagramsInternal(subId, callback); 596 } 597 pollPendingSatelliteDatagramsInternal(int subId, @NonNull Consumer<Integer> callback)598 private void pollPendingSatelliteDatagramsInternal(int subId, 599 @NonNull Consumer<Integer> callback) { 600 if (!mDatagramController.isSendingInIdleState()) { 601 // Poll request should be sent to satellite modem only when it is free. 602 logd("pollPendingSatelliteDatagrams: satellite modem is busy sending datagrams."); 603 callback.accept(SatelliteManager.SATELLITE_MODEM_BUSY); 604 return; 605 } 606 607 mDatagramController.updateReceiveStatus(subId, 608 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING, 609 mDatagramController.getReceivePendingCount(), 610 SatelliteManager.SATELLITE_ERROR_NONE); 611 mDatagramTransferStartTime = System.currentTimeMillis(); 612 Phone phone = SatelliteServiceUtils.getPhone(); 613 614 if (mIsDemoMode) { 615 DatagramReceiverHandlerRequest request = new DatagramReceiverHandlerRequest( 616 callback, phone, subId); 617 synchronized (mLock) { 618 if (mIsAligned) { 619 Message msg = obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE, 620 request); 621 AsyncResult.forMessage(msg, null, null); 622 msg.sendToTarget(); 623 } else { 624 startSatelliteAlignedTimer(request); 625 } 626 } 627 } else { 628 sendRequestAsync(CMD_POLL_PENDING_SATELLITE_DATAGRAMS, callback, phone, subId); 629 } 630 } 631 632 /** 633 * This function is used by {@link DatagramController} to notify {@link DatagramReceiver} 634 * that satellite modem state has changed. 635 * 636 * @param state Current satellite modem state. 637 */ onSatelliteModemStateChanged(@atelliteManager.SatelliteModemState int state)638 public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) { 639 synchronized (mLock) { 640 if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF 641 || state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) { 642 logd("onSatelliteModemStateChanged: cleaning up resources"); 643 cleanUpResources(); 644 } 645 } 646 } 647 648 @GuardedBy("mLock") cleanupDemoModeResources()649 private void cleanupDemoModeResources() { 650 if (isSatelliteAlignedTimerStarted()) { 651 stopSatelliteAlignedTimer(); 652 if (mPollPendingSatelliteDatagramsRequest == null) { 653 loge("Satellite aligned timer was started " 654 + "but mPollPendingSatelliteDatagramsRequest is null"); 655 } else { 656 Consumer<Integer> callback = 657 (Consumer<Integer>) mPollPendingSatelliteDatagramsRequest.argument; 658 callback.accept(SatelliteManager.SATELLITE_REQUEST_ABORTED); 659 } 660 } 661 mIsDemoMode = false; 662 mPollPendingSatelliteDatagramsRequest = null; 663 mIsAligned = false; 664 } 665 666 @GuardedBy("mLock") cleanUpResources()667 private void cleanUpResources() { 668 if (mDatagramController.isReceivingDatagrams()) { 669 mDatagramController.updateReceiveStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, 670 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED, 671 mDatagramController.getReceivePendingCount(), 672 SatelliteManager.SATELLITE_REQUEST_ABORTED); 673 } 674 mDatagramController.updateReceiveStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, 675 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 0, 676 SatelliteManager.SATELLITE_ERROR_NONE); 677 cleanupDemoModeResources(); 678 } 679 680 /** 681 * Posts the specified command to be executed on the main thread and returns immediately. 682 * 683 * @param command command to be executed on the main thread 684 * @param argument additional parameters required to perform of the operation 685 * @param phone phone object used to perform the operation 686 * @param subId The subId of the subscription used for the request. 687 */ sendRequestAsync(int command, @NonNull Object argument, @Nullable Phone phone, int subId)688 private void sendRequestAsync(int command, @NonNull Object argument, @Nullable Phone phone, 689 int subId) { 690 DatagramReceiverHandlerRequest request = new DatagramReceiverHandlerRequest( 691 argument, phone, subId); 692 Message msg = this.obtainMessage(command, request); 693 msg.sendToTarget(); 694 } 695 696 /** Report incoming datagram related metrics */ reportMetrics(@ullable SatelliteDatagram satelliteDatagram, @NonNull @SatelliteManager.SatelliteError int resultCode)697 private void reportMetrics(@Nullable SatelliteDatagram satelliteDatagram, 698 @NonNull @SatelliteManager.SatelliteError int resultCode) { 699 int datagramSizeRoundedBytes = -1; 700 int datagramTransferTime = 0; 701 702 if (satelliteDatagram != null) { 703 if (satelliteDatagram.getSatelliteDatagram() != null) { 704 int sizeBytes = satelliteDatagram.getSatelliteDatagram().length; 705 // rounded by 10 bytes 706 datagramSizeRoundedBytes = 707 (int) (Math.round((double) sizeBytes / ROUNDING_UNIT) * ROUNDING_UNIT); 708 } 709 datagramTransferTime = (int) (System.currentTimeMillis() - mDatagramTransferStartTime); 710 mDatagramTransferStartTime = 0; 711 } 712 713 SatelliteStats.getInstance().onSatelliteIncomingDatagramMetrics( 714 new SatelliteStats.SatelliteIncomingDatagramParams.Builder() 715 .setResultCode(resultCode) 716 .setDatagramSizeBytes(datagramSizeRoundedBytes) 717 .setDatagramTransferTimeMillis(datagramTransferTime) 718 .build()); 719 } 720 721 /** Set demo mode 722 * 723 * @param isDemoMode {@code true} means demo mode is on, {@code false} otherwise. 724 */ 725 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) setDemoMode(boolean isDemoMode)726 protected void setDemoMode(boolean isDemoMode) { 727 mIsDemoMode = isDemoMode; 728 } 729 730 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) onDeviceAlignedWithSatellite(boolean isAligned)731 protected void onDeviceAlignedWithSatellite(boolean isAligned) { 732 if (mIsDemoMode) { 733 synchronized (mLock) { 734 mIsAligned = isAligned; 735 if (isAligned) handleEventSatelliteAligned(); 736 } 737 } 738 } 739 startSatelliteAlignedTimer(DatagramReceiverHandlerRequest request)740 private void startSatelliteAlignedTimer(DatagramReceiverHandlerRequest request) { 741 if (isSatelliteAlignedTimerStarted()) { 742 logd("Satellite aligned timer was already started"); 743 return; 744 } 745 mPollPendingSatelliteDatagramsRequest = request; 746 sendMessageDelayed( 747 obtainMessage(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT, request), 748 getSatelliteAlignedTimeoutDuration()); 749 } 750 751 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) getSatelliteAlignedTimeoutDuration()752 protected long getSatelliteAlignedTimeoutDuration() { 753 return mDatagramController.getSatelliteAlignedTimeoutDuration(); 754 } 755 handleEventSatelliteAligned()756 private void handleEventSatelliteAligned() { 757 if (isSatelliteAlignedTimerStarted()) { 758 stopSatelliteAlignedTimer(); 759 760 if (mPollPendingSatelliteDatagramsRequest == null) { 761 loge("handleSatelliteAlignedTimer: mPollPendingSatelliteDatagramsRequest is null"); 762 } else { 763 Message message = obtainMessage( 764 EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE, 765 mPollPendingSatelliteDatagramsRequest); 766 mPollPendingSatelliteDatagramsRequest = null; 767 AsyncResult.forMessage(message, null, null); 768 message.sendToTarget(); 769 } 770 } 771 } 772 handleEventSatelliteAlignedTimeout(DatagramReceiverHandlerRequest request)773 private void handleEventSatelliteAlignedTimeout(DatagramReceiverHandlerRequest request) { 774 SatelliteManager.SatelliteException exception = 775 new SatelliteManager.SatelliteException( 776 SatelliteManager.SATELLITE_NOT_REACHABLE); 777 Message message = obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE, request); 778 AsyncResult.forMessage(message, null, exception); 779 message.sendToTarget(); 780 } 781 isSatelliteAlignedTimerStarted()782 private boolean isSatelliteAlignedTimerStarted() { 783 return hasMessages(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT); 784 } 785 stopSatelliteAlignedTimer()786 private void stopSatelliteAlignedTimer() { 787 removeMessages(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT); 788 } 789 790 /** 791 * Destroys this DatagramDispatcher. Used for tearing down static resources during testing. 792 */ 793 @VisibleForTesting destroy()794 public void destroy() { 795 sInstance = null; 796 } 797 logd(@onNull String log)798 private static void logd(@NonNull String log) { 799 Rlog.d(TAG, log); 800 } 801 loge(@onNull String log)802 private static void loge(@NonNull String log) { 803 Rlog.e(TAG, log); 804 } 805 } 806