• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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