Add support for satellite pointing APIs

Test: atest SatelliteManagerTest
Bug: 266249740
Change-Id: Ie4b10dcf4762d86ea1560ed61647785559dcd43d
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index b7dfb77..d754d4c 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -143,6 +143,9 @@
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.stub.ImsConfigImplBase;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.satellite.ISatellitePositionUpdateCallback;
+import android.telephony.satellite.PointingInfo;
+import android.telephony.satellite.SatelliteManager;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -185,6 +188,7 @@
 import com.android.internal.telephony.ProxyController;
 import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.RILUtils;
 import com.android.internal.telephony.RadioInterfaceCapabilityController;
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.SmsApplication;
@@ -251,6 +255,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
@@ -376,6 +381,10 @@
     private static final int EVENT_IS_VONR_ENABLED_DONE = 116;
     private static final int CMD_PURCHASE_PREMIUM_CAPABILITY = 117;
     private static final int EVENT_PURCHASE_PREMIUM_CAPABILITY_DONE = 118;
+    private static final int CMD_START_SATELLITE_POSITION_UPDATES = 119;
+    private static final int EVENT_START_SATELLITE_POSITION_UPDATES_DONE = 120;
+    private static final int CMD_STOP_SATELLITE_POSITION_UPDATES = 121;
+    private static final int EVENT_STOP_SATELLITE_POSITION_UPDATES_DONE = 122;
     // Parameters of select command.
     private static final int SELECT_COMMAND = 0xA4;
     private static final int SELECT_P1 = 0x04;
@@ -406,6 +415,8 @@
     private static final int USER_ACTIVITY_NOTIFICATION_DELAY = 200;
 
     private Set<Integer> mCarrierPrivilegeTestOverrideSubIds = new ArraySet<>();
+    private Map<Integer, SatellitePositionUpdateHandler> mSatellitePositionUpdateHandlers =
+            new ConcurrentHashMap<>();
 
     private static final String PREF_CARRIERS_ALPHATAG_PREFIX = "carrier_alphtag_";
     private static final String PREF_CARRIERS_NUMBER_PREFIX = "carrier_number_";
@@ -501,6 +512,46 @@
         }
     }
 
+    private static final class SatellitePositionUpdateHandler extends Handler {
+        public static final int EVENT_POSITION_UPDATE = 1;
+        public static final int EVENT_MESSAGE_TRANSFER_STATE_UPDATE = 2;
+
+        private final ISatellitePositionUpdateCallback mCallback;
+
+        SatellitePositionUpdateHandler(ISatellitePositionUpdateCallback callback, Looper looper) {
+            super(looper);
+            mCallback = callback;
+        }
+
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            switch (msg.what) {
+                case EVENT_POSITION_UPDATE: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    PointingInfo pointingInfo = (PointingInfo) ar.result;
+                    try {
+                        mCallback.onSatellitePositionUpdate(pointingInfo);
+                    } catch (RemoteException e) {
+                        loge("EVENT_POSITION_UPDATE RemoteException: " + e);
+                    }
+                    break;
+                }
+                case EVENT_MESSAGE_TRANSFER_STATE_UPDATE: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    int state = (int) ar.result;
+                    try {
+                        mCallback.onMessageTransferStateUpdate(state);
+                    } catch (RemoteException e) {
+                        loge("EVENT_MESSAGE_TRANSFER_STATE_UPDATE RemoteException: " + e);
+                    }
+                    break;
+                }
+                default:
+                    loge("SatellitePositionUpdateHandler unknown event: " + msg.what);
+            }
+        }
+    }
+
     /**
      * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
      * request after sending. The main thread will notify the request when it is complete.
@@ -2247,6 +2298,76 @@
                     notifyRequester(request);
                     break;
 
+                case CMD_START_SATELLITE_POSITION_UPDATES: {
+                    request = (MainThreadRequest) msg.obj;
+                    onCompleted =
+                            obtainMessage(EVENT_START_SATELLITE_POSITION_UPDATES_DONE, request);
+                    Phone phone = getPhoneFromRequest(request);
+                    if (phone != null) {
+                        phone.startSatellitePositionUpdates(onCompleted);
+                    } else {
+                        loge("startSatellitePositionUpdates: No phone object");
+                        request.result = SatelliteManager.SATELLITE_SERVICE_REQUEST_FAILED;
+                        notifyRequester(request);
+                    }
+                    break;
+                }
+
+                case EVENT_START_SATELLITE_POSITION_UPDATES_DONE: {
+                    ar = (AsyncResult) msg.obj;
+                    request = (MainThreadRequest) ar.userObj;
+                    if (ar.exception == null) {
+                        request.result = SatelliteManager.SATELLITE_SERVICE_SUCCESS;
+                    } else {
+                        request.result = SatelliteManager.SATELLITE_SERVICE_ERROR;
+                        if (ar.exception instanceof CommandException) {
+                            CommandException.Error error =
+                                    ((CommandException) (ar.exception)).getCommandError();
+                            request.result = RILUtils.convertToSatelliteError(error);
+                            loge("startSatellitePositionUpdates CommandException: " + ar.exception);
+                        } else {
+                            loge("startSatellitePositionUpdates unknown exception:" + ar.exception);
+                        }
+                    }
+                    notifyRequester(request);
+                    break;
+                }
+
+                case CMD_STOP_SATELLITE_POSITION_UPDATES: {
+                    request = (MainThreadRequest) msg.obj;
+                    onCompleted =
+                            obtainMessage(EVENT_STOP_SATELLITE_POSITION_UPDATES_DONE, request);
+                    Phone phone = getPhoneFromRequest(request);
+                    if (phone != null) {
+                        phone.stopSatellitePositionUpdates(onCompleted);
+                    } else {
+                        loge("stopSatellitePositionUpdates: No phone object");
+                        request.result = SatelliteManager.SATELLITE_SERVICE_REQUEST_FAILED;
+                        notifyRequester(request);
+                    }
+                    break;
+                }
+
+                case EVENT_STOP_SATELLITE_POSITION_UPDATES_DONE: {
+                    ar = (AsyncResult) msg.obj;
+                    request = (MainThreadRequest) ar.userObj;
+                    if (ar.exception == null) {
+                        request.result = SatelliteManager.SATELLITE_SERVICE_SUCCESS;
+                    } else {
+                        request.result = SatelliteManager.SATELLITE_SERVICE_ERROR;
+                        if (ar.exception instanceof CommandException) {
+                            CommandException.Error error =
+                                    ((CommandException) (ar.exception)).getCommandError();
+                            request.result = RILUtils.convertToSatelliteError(error);
+                            loge("stopSatellitePositionUpdates CommandException: " + ar.exception);
+                        } else {
+                            loge("stopSatellitePositionUpdates unknown exception:" + ar.exception);
+                        }
+                    }
+                    notifyRequester(request);
+                    break;
+                }
+
                 default:
                     Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what);
                     break;
@@ -12038,6 +12159,94 @@
     }
 
     /**
+     * Start receiving satellite position updates.
+     * This can be called by the pointing UI when the user starts pointing to the satellite.
+     * Modem should continue to report the pointing input as the device or satellite moves.
+     *
+     * @param subId The subId to start satellite position updates for.
+     * @param callbackId The callback ID associating the public SatellitePositionUpdateCallback to
+     *                   the internal ISatellitePositionUpdateCallback below.
+     * @param callback The callback to notify of changes in satellite position.
+     * @return The result of the operation.
+     */
+    @Override
+    @SatelliteManager.SatelliteServiceResult public int startSatellitePositionUpdates(int subId,
+            int callbackId, @NonNull ISatellitePositionUpdateCallback callback) {
+        // TODO: check for SATELLITE_COMMUNICATION permission
+        Phone phone = getPhone(subId);
+        if (phone == null) {
+            loge("startSatellitePositionUpdates called with invalid subId: " + subId
+                    + ". Retrying with default phone.");
+            phone = getDefaultPhone();
+            if (phone == null) {
+                loge("startSatellitePositionUpdates failed with no phone object.");
+                return SatelliteManager.SATELLITE_SERVICE_REQUEST_FAILED;
+            }
+        }
+
+        if (mSatellitePositionUpdateHandlers.containsKey(callbackId)) {
+            log("startSatellitePositionUpdates: callback already registered: " + callbackId);
+            return SatelliteManager.SATELLITE_SERVICE_SUCCESS;
+        }
+
+        SatellitePositionUpdateHandler handler =
+                new SatellitePositionUpdateHandler(callback, Looper.getMainLooper());
+        phone.registerForSatellitePointingInfoChanged(handler,
+                SatellitePositionUpdateHandler.EVENT_POSITION_UPDATE, null);
+        phone.registerForSatelliteMessagesTransferComplete(handler,
+                SatellitePositionUpdateHandler.EVENT_MESSAGE_TRANSFER_STATE_UPDATE, null);
+        mSatellitePositionUpdateHandlers.put(callbackId, handler);
+
+        int result = (int) sendRequest(CMD_START_SATELLITE_POSITION_UPDATES, null, subId);
+        if (DBG) log("startSatellitePositionUpdates result: " + result);
+        return result;
+    }
+
+    /**
+     * Stop receiving satellite position updates.
+     * This can be called by the pointing UI when the user stops pointing to the satellite.
+     *
+     * @param subId The subId to stop satellite position updates for.
+     * @param callbackId The ID of the callback that was passed in {@link
+     *                   #startSatellitePositionUpdates(int, int, ISatellitePositionUpdateCallback)}
+     * @return The result of the operation.
+     */
+    @Override
+    @SatelliteManager.SatelliteServiceResult public int stopSatellitePositionUpdates(int subId,
+            int callbackId) {
+        // TODO: check for SATELLITE_COMMUNICATION permission
+        Phone phone = getPhone(subId);
+        if (phone == null) {
+            loge("stopSatellitePositionUpdates called with invalid subId: " + subId
+                    + ". Retrying with default phone.");
+            phone = getDefaultPhone();
+            if (phone == null) {
+                loge("stopSatellitePositionUpdates failed with no phone object.");
+                return SatelliteManager.SATELLITE_SERVICE_REQUEST_FAILED;
+            }
+        }
+
+        SatellitePositionUpdateHandler handler =
+                mSatellitePositionUpdateHandlers.remove(callbackId);
+        if (handler == null) {
+            loge("stopSatellitePositionUpdates: No SatellitePositionArgument");
+            return SatelliteManager.SATELLITE_SERVICE_REQUEST_FAILED;
+        } else {
+            phone.unregisterForSatellitePointingInfoChanged(handler);
+            phone.unregisterForSatelliteMessagesTransferComplete(handler);
+        }
+
+        if (!mSatellitePositionUpdateHandlers.isEmpty()) {
+            log("stopSatellitePositionUpdates: other listeners still exist.");
+            return SatelliteManager.SATELLITE_SERVICE_SUCCESS;
+        }
+
+        int result = (int) sendRequest(CMD_STOP_SATELLITE_POSITION_UPDATES, null, subId);
+        if (DBG) log("stopSatellitePositionUpdates result: " + result);
+        return result;
+    }
+
+    /**
      * Check whether the caller (or self, if not processing an IPC) can read device identifiers.
      *
      * <p>This method behaves in one of the following ways: