diff options
author | Ihab Awad <ihab@google.com> | 2014-08-19 21:21:33 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-08-19 15:47:37 +0000 |
commit | 9e4329ea0b8b725ca5bcec74032b765d01deaa80 (patch) | |
tree | 46525f731d3d76728404bc98124964c0d5fed22e /telecomm/java/android | |
parent | dd7930354aaf2baf91810bc7a3e47543dbcc7f28 (diff) | |
parent | 6107bab041fb7d851fbf865b7310d294aae970c8 (diff) | |
download | frameworks_base-9e4329ea0b8b725ca5bcec74032b765d01deaa80.zip frameworks_base-9e4329ea0b8b725ca5bcec74032b765d01deaa80.tar.gz frameworks_base-9e4329ea0b8b725ca5bcec74032b765d01deaa80.tar.bz2 |
Merge "ConnectionService API has only one completed callback (1/3)" into lmp-dev
Diffstat (limited to 'telecomm/java/android')
7 files changed, 116 insertions, 252 deletions
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java index 27debde..c1d5715 100644 --- a/telecomm/java/android/telecomm/Connection.java +++ b/telecomm/java/android/telecomm/Connection.java @@ -36,6 +36,15 @@ import java.util.concurrent.ConcurrentHashMap; /** * Represents a connection to a remote endpoint that carries voice traffic. + * <p> + * Implementations create a custom subclass of {@code Connection} and return it to the framework + * as the return value of + * {@link ConnectionService#onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)} + * or + * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * Implementations are then responsible for updating the state of the {@code Connection}, and + * must call {@link #destroy()} to signal to the framework that the {@code Connection} is no + * longer used and associated resources may be recovered. */ public abstract class Connection { @@ -53,10 +62,6 @@ public abstract class Connection { public static final int STATE_DISCONNECTED = 6; - public static final int STATE_FAILED = 7; - - public static final int STATE_CANCELED = 8; - // Flag controlling whether PII is emitted into the logs private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG); @@ -644,10 +649,6 @@ public abstract class Connection { return "STATE_HOLDING"; case STATE_DISCONNECTED: return "DISCONNECTED"; - case STATE_FAILED: - return "STATE_FAILED"; - case STATE_CANCELED: - return "STATE_CANCELED"; default: Log.wtf(Connection.class, "Unknown state %d", state); return "UNKNOWN"; @@ -694,33 +695,6 @@ public abstract class Connection { } /** - * Cancel the {@link Connection}. Once this is called, the {@link Connection} will not be used, - * and no subsequent {@link Connection}s will be attempted. - */ - public final void setCanceled() { - Log.d(this, "setCanceled"); - setState(STATE_CANCELED); - } - - /** - * Move the {@link Connection} to the {@link #STATE_FAILED} state, with the given code - * ({@see DisconnectCause}) and message. This message is not shown to the user, but is useful - * for logging and debugging purposes. - * <p> - * After calling this, the {@link Connection} will not be used. - * - * @param code The {@link DisconnectCause} indicating why the connection - * failed. - * @param message A message explaining why the {@link Connection} failed. - */ - public final void setFailed(int code, String message) { - Log.d(this, "setFailed (%d: %s)", code, message); - mFailureCode = code; - mFailureMessage = message; - setState(STATE_FAILED); - } - - /** * Set the video state for the connection. * Valid values: {@link VideoProfile.VideoState#AUDIO_ONLY}, * {@link VideoProfile.VideoState#BIDIRECTIONAL}, @@ -1094,9 +1068,8 @@ public abstract class Connection { } private void setState(int state) { - if (mState == STATE_FAILED || mState == STATE_CANCELED) { - Log.d(this, "Connection already %s; cannot transition out of this state.", - stateToString(mState)); + if (mState == STATE_DISCONNECTED && mState != state) { + Log.d(this, "Connection already DISCONNECTED; cannot transition out of this state."); return; } if (mState != state) { @@ -1109,33 +1082,41 @@ public abstract class Connection { } } + static class FailureSignalingConnection extends Connection { + public FailureSignalingConnection(int code, String message) { + setDisconnected(code, message); + } + } + /** - * Return a {@link Connection} which represents a failed connection attempt. The returned - * {@link Connection} will have {@link #getFailureCode()}, {@link #getFailureMessage()}, and - * {@link #getState()} set appropriately, but the {@link Connection} itself should not be used - * for anything. + * Return a {@code Connection} which represents a failed connection attempt. The returned + * {@code Connection} will have a {@link #getFailureCode()} and {@link #getFailureMessage()} + * as specified, a {@link #getState()} of {@link #STATE_DISCONNECTED}. + * <p> + * The returned {@code Connection} can be assumed to {@link #destroy()} itself when appropriate, + * so users of this method need not maintain a reference to its return value to destroy it. * * @param code The failure code ({@see DisconnectCause}). * @param message A reason for why the connection failed (not intended to be shown to the user). - * @return A {@link Connection} which indicates failure. + * @return A {@code Connection} which indicates failure. */ public static Connection createFailedConnection(final int code, final String message) { - return new Connection() {{ - setFailed(code, message); - }}; + return new FailureSignalingConnection(code, message); } - private static final Connection CANCELED_CONNECTION = new Connection() {{ - setCanceled(); - }}; + private static final Connection CANCELED_CONNECTION = + new FailureSignalingConnection(DisconnectCause.OUTGOING_CANCELED, null); /** - * Return a {@link Connection} which represents a canceled a connection attempt. The returned - * {@link Connection} will have state {@link #STATE_CANCELED}, and cannot be moved out of that - * state. This connection should not be used for anything, and no other {@link Connection}s - * should be attempted. + * Return a {@code Connection} which represents a canceled connection attempt. The returned + * {@code Connection} will have state {@link #STATE_DISCONNECTED}, and cannot be moved out of + * that state. This connection should not be used for anything, and no other + * {@code Connection}s should be attempted. + * <p> + * The returned {@code Connection} can be assumed to {@link #destroy()} itself when appropriate, + * so users of this method need not maintain a reference to its return value to destroy it. * - * @return A {@link Connection} which indicates that the underlying call should be canceled. + * @return A {@code Connection} which indicates that the underlying call should be canceled. */ public static Connection createCanceledConnection() { return CANCELED_CONNECTION; diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java index 97a3102..03b6c7b 100644 --- a/telecomm/java/android/telecomm/ConnectionService.java +++ b/telecomm/java/android/telecomm/ConnectionService.java @@ -500,81 +500,26 @@ public abstract class ConnectionService extends Service { boolean isIncoming) { Log.d(this, "call %s", request); - final Connection createdConnection; - if (isIncoming) { - createdConnection = onCreateIncomingConnection(callManagerAccount, request); - } else { - createdConnection = onCreateOutgoingConnection(callManagerAccount, request); - } - - if (createdConnection != null) { - Log.d(this, "adapter handleCreateConnectionSuccessful %s", callId); - if (createdConnection.getState() == Connection.STATE_INITIALIZING) { - // Wait for the connection to become initialized. - createdConnection.addConnectionListener(new Connection.Listener() { - @Override - public void onStateChanged(Connection c, int state) { - switch (state) { - case Connection.STATE_FAILED: - Log.d(this, "Connection (%s) failed (%d: %s)", request, - c.getFailureCode(), c.getFailureMessage()); - Log.d(this, "adapter handleCreateConnectionFailed %s", - callId); - mAdapter.handleCreateConnectionFailed( - callId, - request, - c.getFailureCode(), - c.getFailureMessage()); - break; - case Connection.STATE_CANCELED: - Log.d(this, "adapter handleCreateConnectionCanceled %s", - callId); - mAdapter.handleCreateConnectionCancelled(callId, request); - break; - case Connection.STATE_INITIALIZING: - Log.d(this, "State changed to STATE_INITIALIZING; ignoring"); - return; // Don't want to stop listening on this state transition. - } - c.removeConnectionListener(this); - } + Connection createdConnection = isIncoming + ? onCreateIncomingConnection(callManagerAccount, request) + : onCreateOutgoingConnection(callManagerAccount, request); - @Override - public void onDestroyed(Connection c) { - // Listen to onDestroy in case the connection is destroyed before - // transitioning to another state. - c.removeConnectionListener(this); - } - }); - Log.d(this, "Connection created in state INITIALIZING"); - connectionCreated(callId, request, createdConnection); - } else if (createdConnection.getState() == Connection.STATE_CANCELED) { - // Tell telecomm not to attempt any more services. - mAdapter.handleCreateConnectionCancelled(callId, request); - } else if (createdConnection.getState() == Connection.STATE_FAILED) { - mAdapter.handleCreateConnectionFailed( - callId, - request, - createdConnection.getFailureCode(), - createdConnection.getFailureMessage()); - } else { - connectionCreated(callId, request, createdConnection); - } - } else { + if (createdConnection == null) { + Log.d(this, "adapter handleCreateConnectionComplete CANCELED %s", callId); // Tell telecomm to try a different service. - Log.d(this, "adapter handleCreateConnectionFailed %s", callId); - mAdapter.handleCreateConnectionFailed( - callId, - request, - DisconnectCause.ERROR_UNSPECIFIED, - null); + createdConnection = Connection.createCanceledConnection(); } + connectionCreated(callId, request, createdConnection); } private void connectionCreated( String callId, ConnectionRequest request, Connection connection) { - addConnection(callId, connection); + if (!(connection instanceof Connection.FailureSignalingConnection)) { + addConnection(callId, connection); + } + Uri handle = connection.getHandle(); String number = handle == null ? "null" : handle.getSchemeSpecificPart(); Log.v(this, "connectionCreated, parcelableconnection: %s, %d, %s", @@ -583,7 +528,7 @@ public abstract class ConnectionService extends Service { PhoneCapabilities.toString(connection.getCallCapabilities())); Log.d(this, "adapter handleCreateConnectionSuccessful %s", callId); - mAdapter.handleCreateConnectionSuccessful( + mAdapter.handleCreateConnectionComplete( callId, request, new ParcelableConnection( @@ -599,7 +544,9 @@ public abstract class ConnectionService extends Service { connection.getVideoState(), connection.isRequestingRingback(), connection.getAudioModeIsVoip(), - connection.getStatusHints())); + connection.getStatusHints(), + connection.getFailureCode(), + connection.getFailureMessage())); } private void abort(String callId) { @@ -699,7 +646,8 @@ public abstract class ConnectionService extends Service { final List<ComponentName> componentNames, final List<IBinder> services) { mHandler.post(new Runnable() { - @Override public void run() { + @Override + public void run() { for (int i = 0; i < componentNames.size() && i < services.size(); i++) { mRemoteConnectionManager.addConnectionService( componentNames.get(i), @@ -714,7 +662,8 @@ public abstract class ConnectionService extends Service { @Override public void onError() { mHandler.post(new Runnable() { - @Override public void run() { + @Override + public void run() { mAreAccountsInitialized = true; } }); diff --git a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java index 0188e62..41c6360 100644 --- a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java +++ b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java @@ -76,37 +76,13 @@ final class ConnectionServiceAdapter implements DeathRecipient { } } - void handleCreateConnectionSuccessful( + void handleCreateConnectionComplete( String id, ConnectionRequest request, ParcelableConnection connection) { for (IConnectionServiceAdapter adapter : mAdapters) { try { - adapter.handleCreateConnectionSuccessful(id, request, connection); - } catch (RemoteException e) { - } - } - } - - void handleCreateConnectionFailed( - String id, - ConnectionRequest request, - int errorCode, - String errorMsg) { - for (IConnectionServiceAdapter adapter : mAdapters) { - try { - adapter.handleCreateConnectionFailed(id, request, errorCode, errorMsg); - } catch (RemoteException e) { - } - } - } - - void handleCreateConnectionCancelled( - String id, - ConnectionRequest request) { - for (IConnectionServiceAdapter adapter : mAdapters) { - try { - adapter.handleCreateConnectionCancelled(id, request); + adapter.handleCreateConnectionComplete(id, request, connection); } catch (RemoteException e) { } } diff --git a/telecomm/java/android/telecomm/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecomm/ConnectionServiceAdapterServant.java index 2654ace..0e1c516 100644 --- a/telecomm/java/android/telecomm/ConnectionServiceAdapterServant.java +++ b/telecomm/java/android/telecomm/ConnectionServiceAdapterServant.java @@ -38,29 +38,27 @@ import java.util.List; * @hide */ final class ConnectionServiceAdapterServant { - private static final int MSG_HANDLE_CREATE_CONNECTION_SUCCESSFUL = 1; - private static final int MSG_HANDLE_CREATE_CONNECTION_FAILED = 2; - private static final int MSG_HANDLE_CREATE_CONNECTION_CANCELLED = 3; - private static final int MSG_SET_ACTIVE = 4; - private static final int MSG_SET_RINGING = 5; - private static final int MSG_SET_DIALING = 6; - private static final int MSG_SET_DISCONNECTED = 7; - private static final int MSG_SET_ON_HOLD = 8; - private static final int MSG_SET_REQUESTING_RINGBACK = 9; - private static final int MSG_SET_CALL_CAPABILITIES = 10; - private static final int MSG_SET_IS_CONFERENCED = 11; - private static final int MSG_ADD_CONFERENCE_CALL = 12; - private static final int MSG_REMOVE_CALL = 13; - private static final int MSG_ON_POST_DIAL_WAIT = 14; - private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 15; - private static final int MSG_SET_VIDEO_STATE = 16; - private static final int MSG_SET_VIDEO_CALL_PROVIDER = 17; - private static final int MSG_SET_AUDIO_MODE_IS_VOIP = 18; - private static final int MSG_SET_STATUS_HINTS = 19; - private static final int MSG_SET_HANDLE = 20; - private static final int MSG_SET_CALLER_DISPLAY_NAME = 21; - private static final int MSG_START_ACTIVITY_FROM_IN_CALL = 22; - private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 23; + private static final int MSG_HANDLE_CREATE_CONNECTION_COMPLETE = 1; + private static final int MSG_SET_ACTIVE = 2; + private static final int MSG_SET_RINGING = 3; + private static final int MSG_SET_DIALING = 4; + private static final int MSG_SET_DISCONNECTED = 5; + private static final int MSG_SET_ON_HOLD = 6; + private static final int MSG_SET_REQUESTING_RINGBACK = 7; + private static final int MSG_SET_CALL_CAPABILITIES = 8; + private static final int MSG_SET_IS_CONFERENCED = 9; + private static final int MSG_ADD_CONFERENCE_CALL = 10; + private static final int MSG_REMOVE_CALL = 11; + private static final int MSG_ON_POST_DIAL_WAIT = 12; + private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 13; + private static final int MSG_SET_VIDEO_STATE = 14; + private static final int MSG_SET_VIDEO_CALL_PROVIDER = 15; + private static final int MSG_SET_AUDIO_MODE_IS_VOIP = 16; + private static final int MSG_SET_STATUS_HINTS = 17; + private static final int MSG_SET_HANDLE = 18; + private static final int MSG_SET_CALLER_DISPLAY_NAME = 19; + private static final int MSG_START_ACTIVITY_FROM_IN_CALL = 20; + private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 21; private final IConnectionServiceAdapter mDelegate; @@ -76,10 +74,10 @@ final class ConnectionServiceAdapterServant { // Internal method defined to centralize handling of RemoteException private void internalHandleMessage(Message msg) throws RemoteException { switch (msg.what) { - case MSG_HANDLE_CREATE_CONNECTION_SUCCESSFUL: { + case MSG_HANDLE_CREATE_CONNECTION_COMPLETE: { SomeArgs args = (SomeArgs) msg.obj; try { - mDelegate.handleCreateConnectionSuccessful( + mDelegate.handleCreateConnectionComplete( (String) args.arg1, (ConnectionRequest) args.arg2, (ParcelableConnection) args.arg3); @@ -88,30 +86,6 @@ final class ConnectionServiceAdapterServant { } break; } - case MSG_HANDLE_CREATE_CONNECTION_FAILED: { - SomeArgs args = (SomeArgs) msg.obj; - try { - mDelegate.handleCreateConnectionFailed( - (String) args.arg1, - (ConnectionRequest) args.arg2, - args.argi1, - (String) args.arg3); - } finally { - args.recycle(); - } - break; - } - case MSG_HANDLE_CREATE_CONNECTION_CANCELLED: { - SomeArgs args = (SomeArgs) msg.obj; - try { - mDelegate.handleCreateConnectionCancelled( - (String) args.arg1, - (ConnectionRequest) args.arg2); - } finally { - args.recycle(); - } - break; - } case MSG_SET_ACTIVE: mDelegate.setActive((String) msg.obj); break; @@ -244,7 +218,7 @@ final class ConnectionServiceAdapterServant { private final IConnectionServiceAdapter mStub = new IConnectionServiceAdapter.Stub() { @Override - public void handleCreateConnectionSuccessful( + public void handleCreateConnectionComplete( String id, ConnectionRequest request, ParcelableConnection connection) { @@ -252,31 +226,7 @@ final class ConnectionServiceAdapterServant { args.arg1 = id; args.arg2 = request; args.arg3 = connection; - mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_SUCCESSFUL, args).sendToTarget(); - } - - @Override - public void handleCreateConnectionFailed( - String id, - ConnectionRequest request, - int errorCode, - String errorMessage) { - SomeArgs args = SomeArgs.obtain(); - args.arg1 = id; - args.arg2 = request; - args.argi1 = errorCode; - args.arg3 = errorMessage; - mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_FAILED, args).sendToTarget(); - } - - @Override - public void handleCreateConnectionCancelled( - String id, - ConnectionRequest request) { - SomeArgs args = SomeArgs.obtain(); - args.arg1 = id; - args.arg2 = request; - mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_CANCELLED, args).sendToTarget(); + mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_COMPLETE, args).sendToTarget(); } @Override diff --git a/telecomm/java/android/telecomm/ParcelableConnection.java b/telecomm/java/android/telecomm/ParcelableConnection.java index 7a87b87..30ff5be 100644 --- a/telecomm/java/android/telecomm/ParcelableConnection.java +++ b/telecomm/java/android/telecomm/ParcelableConnection.java @@ -41,6 +41,8 @@ public final class ParcelableConnection implements Parcelable { private boolean mRequestingRingback; private boolean mAudioModeIsVoip; private StatusHints mStatusHints; + private int mFailureCode; + private String mFailureMessage; /** @hide */ public ParcelableConnection( @@ -55,7 +57,9 @@ public final class ParcelableConnection implements Parcelable { int videoState, boolean requestingRingback, boolean audioModeIsVoip, - StatusHints statusHints) { + StatusHints statusHints, + int failureCode, + String failureMessage) { mPhoneAccount = phoneAccount; mState = state; mCapabilities = capabilities; @@ -68,6 +72,8 @@ public final class ParcelableConnection implements Parcelable { mRequestingRingback = requestingRingback; mAudioModeIsVoip = audioModeIsVoip; mStatusHints = statusHints; + mFailureCode = failureCode; + mFailureMessage = failureMessage; } public PhoneAccountHandle getPhoneAccount() { @@ -119,6 +125,14 @@ public final class ParcelableConnection implements Parcelable { return mStatusHints; } + public final int getFailureCode() { + return mFailureCode; + } + + public final String getFailureMessage() { + return mFailureMessage; + } + @Override public String toString() { return new StringBuilder() @@ -150,6 +164,8 @@ public final class ParcelableConnection implements Parcelable { boolean requestingRingback = source.readByte() == 1; boolean audioModeIsVoip = source.readByte() == 1; StatusHints statusHints = source.readParcelable(classLoader); + int disconnectCauseCode = source.readInt(); + String disconnectCauseMessage = source.readString(); return new ParcelableConnection( phoneAccount, @@ -163,7 +179,9 @@ public final class ParcelableConnection implements Parcelable { videoState, requestingRingback, audioModeIsVoip, - statusHints); + statusHints, + disconnectCauseCode, + disconnectCauseMessage); } @Override @@ -194,5 +212,7 @@ public final class ParcelableConnection implements Parcelable { destination.writeByte((byte) (mRequestingRingback ? 1 : 0)); destination.writeByte((byte) (mAudioModeIsVoip ? 1 : 0)); destination.writeParcelable(mStatusHints, 0); + destination.writeInt(mFailureCode); + destination.writeString(mFailureMessage); } } diff --git a/telecomm/java/android/telecomm/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java index f1cee10..30cfdde 100644 --- a/telecomm/java/android/telecomm/RemoteConnection.java +++ b/telecomm/java/android/telecomm/RemoteConnection.java @@ -233,9 +233,9 @@ public final class RemoteConnection { RemoteConnection(int failureCode, String failureMessage) { this("NULL", null, null); mConnected = false; - mState = Connection.STATE_FAILED; - mFailureCode = failureCode; - mFailureMessage = failureMessage; + mState = Connection.STATE_DISCONNECTED; + mFailureCode = DisconnectCause.OUTGOING_FAILURE; + mFailureMessage = failureMessage + " original code = " + failureCode; } /** @@ -675,8 +675,9 @@ public final class RemoteConnection { } /** - * Create a RemoteConnection which is in the {@link Connection#STATE_FAILED} state. Attempting - * to use it for anything will almost certainly result in bad things happening. Do not do this. + * Create a RemoteConnection represents a failure, and which will be in + * {@link Connection#STATE_DISCONNECTED}. Attempting to use it for anything will almost + * certainly result in bad things happening. Do not do this. * * @return a failed {@link RemoteConnection} * diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java index dedb10e..541df4e 100644 --- a/telecomm/java/android/telecomm/RemoteConnectionService.java +++ b/telecomm/java/android/telecomm/RemoteConnectionService.java @@ -48,7 +48,7 @@ final class RemoteConnectionService { private final IConnectionServiceAdapter mServantDelegate = new IConnectionServiceAdapter() { @Override - public void handleCreateConnectionSuccessful( + public void handleCreateConnectionComplete( String id, ConnectionRequest request, ParcelableConnection parcel) { @@ -56,6 +56,7 @@ final class RemoteConnectionService { findConnectionForAction(id, "handleCreateConnectionSuccessful"); if (connection != NULL_CONNECTION && mPendingConnections.contains(connection)) { mPendingConnections.remove(connection); + // Unconditionally initialize the connection ... connection.setState(parcel.getState()); connection.setCallCapabilities(parcel.getCapabilities()); connection.setHandle( @@ -64,29 +65,15 @@ final class RemoteConnectionService { parcel.getCallerDisplayName(), parcel.getCallerDisplayNamePresentation()); // TODO: Do we need to support video providers for remote connections? + if (connection.getState() == Connection.STATE_DISCONNECTED) { + // ... then, if it was created in a disconnected state, that indicates + // failure on the providing end, so immediately mark it destroyed + connection.setDestroyed(); + } } } @Override - public void handleCreateConnectionFailed( - String id, - ConnectionRequest request, - int errorCode, - String errorMessage) { - // TODO: How do we propagate the failure codes? - findConnectionForAction(id, "handleCreateConnectionFailed") - .setDestroyed(); - } - - @Override - public void handleCreateConnectionCancelled( - String id, - ConnectionRequest request) { - findConnectionForAction(id, "handleCreateConnectionCancelled") - .setDestroyed(); - } - - @Override public void setActive(String callId) { findConnectionForAction(callId, "setActive") .setState(Connection.STATE_ACTIVE); |