Merge "Location Query for OTT Emergency Calls (frameworks)"

This commit is contained in:
TreeHugger Robot
2023-01-18 01:55:23 +00:00
committed by Android (Google) Code Review
9 changed files with 299 additions and 0 deletions

View File

@@ -41609,6 +41609,7 @@ package android.telecom {
method public void onUsingAlternativeUi(boolean);
method public static String propertiesToString(int);
method public final void putExtras(@NonNull android.os.Bundle);
method public final void queryLocationForEmergency(@IntRange(from=100, to=5000) long, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.location.Location,android.telecom.QueryLocationException>);
method public final void removeExtras(java.util.List<java.lang.String>);
method public final void removeExtras(java.lang.String...);
method @Deprecated public void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
@@ -42012,6 +42013,22 @@ package android.telecom {
field public static final int REASON_USER_SET = 3; // 0x3
}
public final class QueryLocationException extends java.lang.RuntimeException implements android.os.Parcelable {
ctor public QueryLocationException(@Nullable String);
ctor public QueryLocationException(@Nullable String, int);
ctor public QueryLocationException(@Nullable String, int, @Nullable Throwable);
method public int describeContents();
method public int getCode();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telecom.QueryLocationException> CREATOR;
field public static final int ERROR_NOT_ALLOWED_FOR_NON_EMERGENCY_CONNECTIONS = 4; // 0x4
field public static final int ERROR_NOT_PERMITTED = 3; // 0x3
field public static final int ERROR_PREVIOUS_REQUEST_EXISTS = 2; // 0x2
field public static final int ERROR_REQUEST_TIME_OUT = 1; // 0x1
field public static final int ERROR_SERVICE_UNAVAILABLE = 5; // 0x5
field public static final int ERROR_UNSPECIFIED = 6; // 0x6
}
public final class RemoteConference {
method public void disconnect();
method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();

View File

@@ -33,6 +33,7 @@ import android.compat.annotation.UnsupportedAppUsage;
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 abstract class Connection extends Conferenceable {
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 abstract class Connection extends Conferenceable {
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) {}
}
/**
@@ -3232,6 +3243,36 @@ public abstract class Connection extends Conferenceable {
mListeners.forEach((l) -> l.onRemoteRttRequest(Connection.this));
}
/**
* 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.
*

View File

@@ -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.annotation.TestApi;
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 @@ public abstract class ConnectionService extends Service {
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} */

View File

@@ -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 @@ final class ConnectionServiceAdapter implements DeathRecipient {
}
}
}
/**
* 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)));
}
}
}
}

View File

@@ -78,6 +78,7 @@ final class ConnectionServiceAdapterServant {
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 @@ final class ConnectionServiceAdapterServant {
} 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 @@ final class ConnectionServiceAdapterServant {
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) {

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -517,6 +517,12 @@ final class RemoteConnectionService {
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 =

View File

@@ -139,4 +139,7 @@ oneway interface IConnectionServiceAdapter {
void setConferenceState(String callId, boolean isConference, in Session.Info sessionInfo);
void setCallDirection(String callId, int direction, in Session.Info sessionInfo);
void queryLocation(String callId, long timeoutMillis, String provider,
in ResultReceiver callback, in Session.Info sessionInfo);
}