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