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 =