| /* |
| * Copyright (C) 2019 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.net; |
| |
| import static android.annotation.SystemApi.Client.PRIVILEGED_APPS; |
| |
| import android.annotation.IntDef; |
| import android.annotation.IntRange; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.SystemApi; |
| import android.os.Binder; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| |
| import java.io.IOException; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.concurrent.Executor; |
| |
| /** |
| * Allows applications to request that the system periodically send specific packets on their |
| * behalf, using hardware offload to save battery power. |
| * |
| * To request that the system send keepalives, call one of the methods that return a |
| * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive}, |
| * passing in a non-null callback. If the {@link SocketKeepalive} is successfully |
| * started, the callback's {@code onStarted} method will be called. If an error occurs, |
| * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this |
| * class. |
| * |
| * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call |
| * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or |
| * {@link SocketKeepalive.Callback#onError} if an error occurred. |
| * |
| * For cellular, the device MUST support at least 1 keepalive slot. |
| * |
| * For WiFi, the device SHOULD support keepalive offload. If it does not, it MUST reply with |
| * {@link SocketKeepalive.Callback#onError} with {@code ERROR_UNSUPPORTED} to any keepalive offload |
| * request. If it does, it MUST support at least 3 concurrent keepalive slots. |
| */ |
| public abstract class SocketKeepalive implements AutoCloseable { |
| /** @hide */ |
| protected static final String TAG = "SocketKeepalive"; |
| |
| /** |
| * Success. It indicates there is no error. |
| * @hide |
| */ |
| @SystemApi |
| public static final int SUCCESS = 0; |
| |
| /** |
| * Success when trying to suspend. |
| * @hide |
| */ |
| public static final int SUCCESS_PAUSED = 1; |
| |
| /** |
| * No keepalive. This should only be internally as it indicates There is no keepalive. |
| * It should not propagate to applications. |
| * @hide |
| */ |
| public static final int NO_KEEPALIVE = -1; |
| |
| /** |
| * Data received. |
| * @hide |
| */ |
| public static final int DATA_RECEIVED = -2; |
| |
| /** |
| * The binder died. |
| * @hide |
| */ |
| public static final int BINDER_DIED = -10; |
| |
| /** |
| * The invalid network. It indicates the specified {@code Network} is not connected. |
| */ |
| public static final int ERROR_INVALID_NETWORK = -20; |
| |
| /** |
| * The invalid IP addresses. Indicates the specified IP addresses are invalid. |
| * For example, the specified source IP address is not configured on the |
| * specified {@code Network}. |
| */ |
| public static final int ERROR_INVALID_IP_ADDRESS = -21; |
| |
| /** |
| * The port is invalid. |
| */ |
| public static final int ERROR_INVALID_PORT = -22; |
| |
| /** |
| * The length is invalid (e.g. too long). |
| */ |
| public static final int ERROR_INVALID_LENGTH = -23; |
| |
| /** |
| * The interval is invalid (e.g. too short). |
| */ |
| public static final int ERROR_INVALID_INTERVAL = -24; |
| |
| /** |
| * The socket is invalid. |
| */ |
| public static final int ERROR_INVALID_SOCKET = -25; |
| |
| /** |
| * The socket is not idle. |
| */ |
| public static final int ERROR_SOCKET_NOT_IDLE = -26; |
| |
| /** |
| * The stop reason is uninitialized. This should only be internally used as initial state |
| * of stop reason, instead of propagating to application. |
| * @hide |
| */ |
| public static final int ERROR_STOP_REASON_UNINITIALIZED = -27; |
| |
| /** |
| * The request is unsupported. |
| */ |
| public static final int ERROR_UNSUPPORTED = -30; |
| |
| /** |
| * There was a hardware error. |
| */ |
| public static final int ERROR_HARDWARE_ERROR = -31; |
| |
| /** |
| * Resources are insufficient (e.g. all hardware slots are in use). |
| */ |
| public static final int ERROR_INSUFFICIENT_RESOURCES = -32; |
| |
| /** |
| * There was no such slot, or no keepalive running on this slot. |
| * @hide |
| */ |
| @SystemApi |
| public static final int ERROR_NO_SUCH_SLOT = -33; |
| |
| /** @hide */ |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef(prefix = { "ERROR_" }, value = { |
| ERROR_INVALID_NETWORK, |
| ERROR_INVALID_IP_ADDRESS, |
| ERROR_INVALID_PORT, |
| ERROR_INVALID_LENGTH, |
| ERROR_INVALID_INTERVAL, |
| ERROR_INVALID_SOCKET, |
| ERROR_SOCKET_NOT_IDLE, |
| ERROR_NO_SUCH_SLOT |
| }) |
| public @interface ErrorCode {} |
| |
| /** @hide */ |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef(value = { |
| SUCCESS, |
| ERROR_INVALID_LENGTH, |
| ERROR_UNSUPPORTED, |
| ERROR_INSUFFICIENT_RESOURCES, |
| }) |
| public @interface KeepaliveEvent {} |
| |
| /** |
| * Whether the system automatically toggles keepalive when no TCP connection is open on the VPN. |
| * |
| * If this flag is present, the system will monitor the VPN(s) running on top of the specified |
| * network for open TCP connections. When no such connections are open, it will turn off the |
| * keepalives to conserve battery power. When there is at least one such connection it will |
| * turn on the keepalives to make sure functionality is preserved. |
| * |
| * This only works with {@link NattSocketKeepalive}. |
| * @hide |
| */ |
| @SystemApi |
| public static final int FLAG_AUTOMATIC_ON_OFF = 1 << 0; |
| |
| /** @hide */ |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef(prefix = { "FLAG_"}, flag = true, value = { |
| FLAG_AUTOMATIC_ON_OFF |
| }) |
| public @interface StartFlags {} |
| |
| /** |
| * The minimum interval in seconds between keepalive packet transmissions. |
| * |
| * @hide |
| **/ |
| public static final int MIN_INTERVAL_SEC = 10; |
| |
| /** |
| * The maximum interval in seconds between keepalive packet transmissions. |
| * |
| * @hide |
| **/ |
| public static final int MAX_INTERVAL_SEC = 3600; |
| |
| /** |
| * An exception that embarks an error code. |
| * @hide |
| */ |
| public static class ErrorCodeException extends Exception { |
| public final int error; |
| public ErrorCodeException(final int error, final Throwable e) { |
| super(e); |
| this.error = error; |
| } |
| public ErrorCodeException(final int error) { |
| this.error = error; |
| } |
| } |
| |
| /** |
| * This socket is invalid. |
| * See the error code for details, and the optional cause. |
| * @hide |
| */ |
| public static class InvalidSocketException extends ErrorCodeException { |
| public InvalidSocketException(final int error, final Throwable e) { |
| super(error, e); |
| } |
| public InvalidSocketException(final int error) { |
| super(error); |
| } |
| } |
| |
| /** @hide */ |
| @NonNull protected final IConnectivityManager mService; |
| /** @hide */ |
| @NonNull protected final Network mNetwork; |
| /** @hide */ |
| @NonNull protected final ParcelFileDescriptor mPfd; |
| /** @hide */ |
| @NonNull protected final Executor mExecutor; |
| /** @hide */ |
| @NonNull protected final ISocketKeepaliveCallback mCallback; |
| |
| /** @hide */ |
| public SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network, |
| @NonNull ParcelFileDescriptor pfd, |
| @NonNull Executor executor, @NonNull Callback callback) { |
| mService = service; |
| mNetwork = network; |
| mPfd = pfd; |
| mExecutor = executor; |
| mCallback = new ISocketKeepaliveCallback.Stub() { |
| @Override |
| public void onStarted() { |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| mExecutor.execute(() -> { |
| callback.onStarted(); |
| }); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| @Override |
| public void onResumed() { |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| mExecutor.execute(() -> { |
| callback.onResumed(); |
| }); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| @Override |
| public void onStopped() { |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| executor.execute(() -> { |
| callback.onStopped(); |
| }); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| @Override |
| public void onPaused() { |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| executor.execute(() -> { |
| callback.onPaused(); |
| }); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| @Override |
| public void onError(int error) { |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| executor.execute(() -> { |
| callback.onError(error); |
| }); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| @Override |
| public void onDataReceived() { |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| executor.execute(() -> { |
| callback.onDataReceived(); |
| }); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Request that keepalive be started with the given {@code intervalSec}. |
| * |
| * See {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an |
| * exception when invoking start or stop of the {@link SocketKeepalive}, a |
| * {@link RuntimeException} caused by a {@link RemoteException} will be thrown into the |
| * {@link Executor}. This is typically not important to catch because the remote party is |
| * the system, so if it is not in shape to communicate through binder the system is going |
| * down anyway. If the caller still cares, it can use a custom {@link Executor} to catch the |
| * {@link RuntimeException}. |
| * |
| * @param intervalSec The target interval in seconds between keepalive packet transmissions. |
| * The interval should be between 10 seconds and 3600 seconds, otherwise |
| * {@link #ERROR_INVALID_INTERVAL} will be returned. |
| */ |
| public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC) |
| int intervalSec) { |
| startImpl(intervalSec, 0 /* flags */, null /* underpinnedNetwork */); |
| } |
| |
| /** |
| * Request that keepalive be started with the given {@code intervalSec}. |
| * |
| * See {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an |
| * exception when invoking start or stop of the {@link SocketKeepalive}, a |
| * {@link RuntimeException} caused by a {@link RemoteException} will be thrown into the |
| * {@link Executor}. This is typically not important to catch because the remote party is |
| * the system, so if it is not in shape to communicate through binder the system is going |
| * down anyway. If the caller still cares, it can use a custom {@link Executor} to catch the |
| * {@link RuntimeException}. |
| * |
| * @param intervalSec The target interval in seconds between keepalive packet transmissions. |
| * The interval should be between 10 seconds and 3600 seconds. Otherwise, |
| * the supplied {@link Callback} will see a call to |
| * {@link Callback#onError(int)} with {@link #ERROR_INVALID_INTERVAL}. |
| * @param flags Flags to enable/disable available options on this keepalive. |
| * @param underpinnedNetwork an optional network running over mNetwork that this |
| * keepalive is intended to keep up, e.g. an IPSec |
| * tunnel running over mNetwork. |
| * @hide |
| */ |
| @SystemApi(client = PRIVILEGED_APPS) |
| public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC) |
| int intervalSec, @StartFlags int flags, @Nullable Network underpinnedNetwork) { |
| startImpl(intervalSec, flags, underpinnedNetwork); |
| } |
| |
| /** @hide */ |
| protected abstract void startImpl(int intervalSec, @StartFlags int flags, |
| Network underpinnedNetwork); |
| |
| /** |
| * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped} |
| * before using the object. See {@link SocketKeepalive}. |
| */ |
| public final void stop() { |
| stopImpl(); |
| } |
| |
| /** @hide */ |
| protected abstract void stopImpl(); |
| |
| /** |
| * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be |
| * usable again if {@code close()} is called. |
| */ |
| @Override |
| public final void close() { |
| stop(); |
| try { |
| mPfd.close(); |
| } catch (IOException e) { |
| // Nothing much can be done. |
| } |
| } |
| |
| /** |
| * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See |
| * {@link SocketKeepalive}. |
| */ |
| public static class Callback { |
| /** The requested keepalive was successfully started. */ |
| public void onStarted() {} |
| /** |
| * The keepalive was resumed by the system after being suspended. |
| * @hide |
| **/ |
| public void onResumed() {} |
| /** The keepalive was successfully stopped. */ |
| public void onStopped() {} |
| /** |
| * The keepalive was paused by the system because it's not necessary right now. |
| * @hide |
| **/ |
| public void onPaused() {} |
| /** An error occurred. */ |
| public void onError(@ErrorCode int error) {} |
| /** The keepalive on a TCP socket was stopped because the socket received data. This is |
| * never called for UDP sockets. */ |
| public void onDataReceived() {} |
| } |
| } |