Location Query for OTT Emergency Calls (frameworks)

This commit implements Location Query for OTT Emergency Calls
in frameworks.

Bug: 236748912
Test: cts test
Change-Id: Id1fbd33e7027146bca9d3a723977d1f63b077696
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 4656226..6138f67 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -33,6 +33,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.hardware.camera2.CameraManager;
+import android.location.Location;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -1050,6 +1051,13 @@
     public static final String EXTRA_CALL_QUALITY_REPORT =
             "android.telecom.extra.CALL_QUALITY_REPORT";
 
+    /**
+     * Key to obtain location as a result of ({@code queryLocationForEmergency} from Bundle
+     * @hide
+     */
+    public static final String EXTRA_KEY_QUERY_LOCATION =
+            "android.telecom.extra.KEY_QUERY_LOCATION";
+
     // Flag controlling whether PII is emitted into the logs
     private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
 
@@ -1285,6 +1293,9 @@
         public void onConnectionTimeReset(Connection c) {}
         public void onEndpointChanged(Connection c, CallEndpoint endpoint, Executor executor,
                 OutcomeReceiver<Void, CallEndpointException> callback) {}
+        public void onQueryLocation(Connection c, long timeoutMillis, @NonNull String provider,
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull OutcomeReceiver<Location, QueryLocationException> callback) {}
     }
 
     /**
@@ -3233,6 +3244,36 @@
     }
 
     /**
+     * Query the device's location in order to place an Emergency Call.
+     * Only SIM call managers can call this method for Connections representing Emergency calls.
+     * If a previous location query request is not completed, the new location query request will
+     * be rejected and return a QueryLocationException with
+     * {@code QueryLocationException#ERROR_PREVIOUS_REQUEST_EXISTS}
+     *
+     * @param timeoutMillis long: Timeout in millis waiting for query response (MAX:5000, MIN:100).
+     * @param provider String: the location provider name, This value cannot be null.
+     *                 It is the caller's responsibility to select an enabled provider. The caller
+     *                 can use {@link android.location.LocationManager#getProviders(boolean)}
+     *                 to choose one of the enabled providers and pass it in.
+     * @param executor The executor of where the callback will execute.
+     * @param callback The callback to notify the result of queryLocation.
+     */
+    public final void queryLocationForEmergency(
+            @IntRange(from = 100, to = 5000) long timeoutMillis,
+            @NonNull String provider,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Location, QueryLocationException> callback) {
+        if (provider == null || executor == null || callback == null) {
+            throw new IllegalArgumentException("There are arguments that must not be null");
+        }
+        if (timeoutMillis < 100 || timeoutMillis > 5000) {
+            throw new IllegalArgumentException("The timeoutMillis should be min 100, max 5000");
+        }
+        mListeners.forEach((l) ->
+                l.onQueryLocation(this, timeoutMillis, provider, executor, callback));
+    }
+
+    /**
      * Notifies this Connection that the {@link #getAudioState()} property has a new value.
      *
      * @param state The new connection audio state.
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 4d6caf8..773ed70 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -25,6 +26,7 @@
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.location.Location;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -2020,6 +2022,16 @@
                 mAdapter.requestCallEndpointChange(id, endpoint, executor, callback);
             }
         }
+
+        @Override
+        public void onQueryLocation(Connection c, long timeoutMillis, @NonNull String provider,
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull OutcomeReceiver<Location, QueryLocationException> callback) {
+            String id = mIdByConnection.get(c);
+            if (id != null) {
+                mAdapter.queryLocation(id, timeoutMillis, provider, executor, callback);
+            }
+        }
     };
 
     /** {@inheritDoc} */
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 39928ef..a7105d3 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -16,6 +16,9 @@
 
 package android.telecom;
 
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.location.Location;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -748,4 +751,45 @@
             }
         }
     }
+
+    /**
+     * Query location information.
+     * Only SIM call managers can call this method for Connections representing Emergency calls.
+     * If the previous request is not completed, the new request will be rejected.
+     *
+     * @param timeoutMillis long: Timeout in millis waiting for query response.
+     * @param provider String: the location provider name, This value cannot be null.
+     * @param executor The executor of where the callback will execute.
+     * @param callback The callback to notify the result of queryLocation.
+     */
+    void queryLocation(String callId, long timeoutMillis, @NonNull String provider,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Location, QueryLocationException> callback) {
+        Log.v(this, "queryLocation: %s %d", callId, timeoutMillis);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.queryLocation(callId, timeoutMillis, provider,
+                        new ResultReceiver(null) {
+                            @Override
+                            protected void onReceiveResult(int resultCode, Bundle result) {
+                                super.onReceiveResult(resultCode, result);
+
+                                if (resultCode == 1 /* success */) {
+                                    executor.execute(() -> callback.onResult(result.getParcelable(
+                                            Connection.EXTRA_KEY_QUERY_LOCATION, Location.class)));
+                                } else {
+                                    executor.execute(() -> callback.onError(result.getParcelable(
+                                            QueryLocationException.QUERY_LOCATION_ERROR,
+                                            QueryLocationException.class)));
+                                }
+                            }
+                        },
+                        Log.getExternalSession());
+            } catch (RemoteException e) {
+                Log.d(this, "queryLocation: Exception e : " + e);
+                executor.execute(() -> callback.onError(new QueryLocationException(
+                        e.getMessage(), QueryLocationException.ERROR_SERVICE_UNAVAILABLE)));
+            }
+        }
+    }
 }
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index c95e14f..8a59020 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -78,6 +78,7 @@
     private static final int MSG_SET_CONFERENCE_STATE = 36;
     private static final int MSG_HANDLE_CREATE_CONFERENCE_COMPLETE = 37;
     private static final int MSG_SET_CALL_DIRECTION = 38;
+    private static final int MSG_QUERY_LOCATION = 39;
 
     private final IConnectionServiceAdapter mDelegate;
 
@@ -373,6 +374,18 @@
                     } finally {
                         args.recycle();
                     }
+                    break;
+                }
+                case MSG_QUERY_LOCATION: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.queryLocation((String) args.arg1, (long) args.arg2,
+                                (String) args.arg3, (ResultReceiver) args.arg4,
+                                (Session.Info) args.arg5);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
                 }
             }
         }
@@ -699,6 +712,18 @@
                 ResultReceiver callback, Session.Info sessionInfo) {
             // Do nothing
         }
+
+        @Override
+        public void queryLocation(String callId, long timeoutMillis, String provider,
+                ResultReceiver callback, Session.Info sessionInfo) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = timeoutMillis;
+            args.arg3 = provider;
+            args.arg4 = callback;
+            args.arg5 = sessionInfo;
+            mHandler.obtainMessage(MSG_QUERY_LOCATION, args).sendToTarget();
+        }
     };
 
     public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
diff --git a/telecomm/java/android/telecom/QueryLocationException.aidl b/telecomm/java/android/telecom/QueryLocationException.aidl
new file mode 100644
index 0000000..56ac412
--- /dev/null
+++ b/telecomm/java/android/telecom/QueryLocationException.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable QueryLocationException;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/QueryLocationException.java b/telecomm/java/android/telecom/QueryLocationException.java
new file mode 100644
index 0000000..fd90d1e
--- /dev/null
+++ b/telecomm/java/android/telecom/QueryLocationException.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telecom;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.OutcomeReceiver;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * This class represents a set of exceptions that can occur when requesting a
+ * {@link Connection#queryLocationForEmergency(long, String, Executor, OutcomeReceiver)}
+ */
+public final class QueryLocationException extends RuntimeException implements Parcelable {
+    /** @hide */
+    public static final String QUERY_LOCATION_ERROR = "QueryLocationErrorKey";
+
+    /**
+     * The operation was not completed on time.
+     */
+    public static final int ERROR_REQUEST_TIME_OUT = 1;
+    /**
+     * The operation was rejected due to an existing request.
+     */
+    public static final int ERROR_PREVIOUS_REQUEST_EXISTS = 2;
+    /**
+     * The operation has failed because it is not permitted.
+     */
+    public static final int ERROR_NOT_PERMITTED = 3;
+    /**
+     * The operation has failed due to a location query being requested for a non-emergency
+     * connection.
+     */
+    public static final int ERROR_NOT_ALLOWED_FOR_NON_EMERGENCY_CONNECTIONS = 4;
+    /**
+     * The operation has failed due to the service is not available.
+     */
+    public static final int ERROR_SERVICE_UNAVAILABLE = 5;
+    /**
+     * The operation has failed due to an unknown or unspecified error.
+     */
+    public static final int ERROR_UNSPECIFIED = 6;
+
+    private int mCode = ERROR_UNSPECIFIED;
+    private final String mMessage;
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mMessage);
+        dest.writeInt(mCode);
+    }
+    /**
+     * Responsible for creating QueryLocationException objects for deserialized Parcels.
+     */
+    public static final
+            @android.annotation.NonNull Parcelable.Creator<QueryLocationException> CREATOR =
+            new Parcelable.Creator<>() {
+                @Override
+                public QueryLocationException createFromParcel(Parcel source) {
+                    return new QueryLocationException(source.readString8(), source.readInt());
+                }
+                @Override
+                public QueryLocationException[] newArray(int size) {
+                    return new QueryLocationException[size];
+                }
+            };
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ERROR_REQUEST_TIME_OUT,
+            ERROR_PREVIOUS_REQUEST_EXISTS,
+            ERROR_NOT_PERMITTED,
+            ERROR_NOT_ALLOWED_FOR_NON_EMERGENCY_CONNECTIONS,
+            ERROR_SERVICE_UNAVAILABLE,
+            ERROR_UNSPECIFIED})
+    public @interface QueryLocationErrorCode {}
+    public QueryLocationException(@Nullable String message) {
+        super(getMessage(message, ERROR_UNSPECIFIED));
+        mMessage = message;
+    }
+    public QueryLocationException(@Nullable String message, @QueryLocationErrorCode int code) {
+        super(getMessage(message, code));
+        mCode = code;
+        mMessage = message;
+    }
+    public QueryLocationException(
+            @Nullable String message, @QueryLocationErrorCode int code, @Nullable Throwable cause) {
+        super(getMessage(message, code), cause);
+        mCode = code;
+        mMessage = message;
+    }
+    public @QueryLocationErrorCode int getCode() {
+        return mCode;
+    }
+    private static String getMessage(String message, int code) {
+        StringBuilder builder;
+        if (!TextUtils.isEmpty(message)) {
+            builder = new StringBuilder(message);
+            builder.append(" (code: ");
+            builder.append(code);
+            builder.append(")");
+            return builder.toString();
+        } else {
+            return "code: " + code;
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 6561732..2fc6a22 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -517,6 +517,12 @@
                 ResultReceiver callback, Session.Info sessionInfo) {
             // Do nothing
         }
+
+        @Override
+        public void queryLocation(String callId, long timeoutMillis, String provider,
+                ResultReceiver callback, Session.Info sessionInfo) {
+            // Do nothing
+        }
     };
 
     private final ConnectionServiceAdapterServant mServant =