content: /*
   * 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 android.annotation.IntDef;
  import android.annotation.IntRange;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  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 {
      static final String TAG = "SocketKeepalive";
  
      /** @hide */
      public static final int SUCCESS = 0;
  
      /** @hide */
      public static final int NO_KEEPALIVE = -1;
  
      /** @hide */
      public static final int DATA_RECEIVED = -2;
  
      /** @hide */
      public static final int BINDER_DIED = -10;
  
      /** The specified {@code Network} is not connected. */
      public static final int ERROR_INVALID_NETWORK = -20;
      /** 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 requested port is invalid. */
      public static final int ERROR_INVALID_PORT = -22;
      /** The packet length is invalid (e.g., too long). */
      public static final int ERROR_INVALID_LENGTH = -23;
      /** The packet transmission interval is invalid (e.g., too short). */
      public static final int ERROR_INVALID_INTERVAL = -24;
      /** The target socket is invalid. */
      public static final int ERROR_INVALID_SOCKET = -25;
      /** The target socket is not idle. */
      public static final int ERROR_SOCKET_NOT_IDLE = -26;
  
      /** The device does not support this request. */
      public static final int ERROR_UNSUPPORTED = -30;
      /** @hide TODO: delete when telephony code has been updated. */
      public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED;
      /** The hardware returned an error. */
      public static final int ERROR_HARDWARE_ERROR = -31;
      /** The limitation of resource is reached. */
      public static final int ERROR_INSUFFICIENT_RESOURCES = -32;
  
  
      /** @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
      })
      public @interface ErrorCode {}
  
      /**
       * 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);
          }
      }
  
      /**
       * This packet is invalid.
       * See the error code for details.
       * @hide
       */
      public static class InvalidPacketException extends ErrorCodeException {
          public InvalidPacketException(final int error) {
              super(error);
          }
      }
  
      @NonNull final IConnectivityManager mService;
      @NonNull final Network mNetwork;
      @NonNull final ParcelFileDescriptor mPfd;
      @NonNull final Executor mExecutor;
      @NonNull final ISocketKeepaliveCallback mCallback;
      // TODO: remove slot since mCallback could be used to identify which keepalive to stop.
      @Nullable Integer mSlot;
  
      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(int slot) {
                  Binder.withCleanCallingIdentity(() ->
                          mExecutor.execute(() -> {
                              mSlot = slot;
                              callback.onStarted();
                          }));
              }
  
              @Override
              public void onStopped() {
                  Binder.withCleanCallingIdentity(() ->
                          executor.execute(() -> {
                              mSlot = null;
                              callback.onStopped();
                          }));
              }
  
              @Override
              public void onError(int error) {
                  Binder.withCleanCallingIdentity(() ->
                          executor.execute(() -> {
                              mSlot = null;
                              callback.onError(error);
                          }));
              }
  
              @Override
              public void onDataReceived() {
                  Binder.withCleanCallingIdentity(() ->
                          executor.execute(() -> {
                              mSlot = null;
                              callback.onDataReceived();
                          }));
              }
          };
      }
  
      /**
       * 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 RemoteException} will be
       * thrown into the {@code 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
       * probably going down anyway. If the caller cares regardless, it can use a custom
       * {@link Executor} to catch the {@link RemoteException}.
       *
       * @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);
      }
  
      abstract void startImpl(int intervalSec);
  
      /**
       * 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();
      }
  
      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 successfully stopped. */
          public void onStopped() {}
          /** 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() {}
      }
  }
  