1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net;
18 
19 import android.annotation.IntDef;
20 import android.annotation.IntRange;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SystemApi;
24 import android.os.Binder;
25 import android.os.ParcelFileDescriptor;
26 import android.os.RemoteException;
27 
28 import java.io.IOException;
29 import java.lang.annotation.Retention;
30 import java.lang.annotation.RetentionPolicy;
31 import java.util.concurrent.Executor;
32 
33 /**
34  * Allows applications to request that the system periodically send specific packets on their
35  * behalf, using hardware offload to save battery power.
36  *
37  * To request that the system send keepalives, call one of the methods that return a
38  * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive},
39  * passing in a non-null callback. If the {@link SocketKeepalive} is successfully
40  * started, the callback's {@code onStarted} method will be called. If an error occurs,
41  * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this
42  * class.
43  *
44  * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call
45  * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or
46  * {@link SocketKeepalive.Callback#onError} if an error occurred.
47  *
48  * For cellular, the device MUST support at least 1 keepalive slot.
49  *
50  * For WiFi, the device SHOULD support keepalive offload. If it does not, it MUST reply with
51  * {@link SocketKeepalive.Callback#onError} with {@code ERROR_UNSUPPORTED} to any keepalive offload
52  * request. If it does, it MUST support at least 3 concurrent keepalive slots.
53  */
54 public abstract class SocketKeepalive implements AutoCloseable {
55     static final String TAG = "SocketKeepalive";
56 
57     /**
58      * No errors.
59      * @hide
60      */
61     @SystemApi
62     public static final int SUCCESS = 0;
63 
64     /** @hide */
65     public static final int NO_KEEPALIVE = -1;
66 
67     /** @hide */
68     public static final int DATA_RECEIVED = -2;
69 
70     /** @hide */
71     public static final int BINDER_DIED = -10;
72 
73     /** The specified {@code Network} is not connected. */
74     public static final int ERROR_INVALID_NETWORK = -20;
75     /** The specified IP addresses are invalid. For example, the specified source IP address is
76      * not configured on the specified {@code Network}. */
77     public static final int ERROR_INVALID_IP_ADDRESS = -21;
78     /** The requested port is invalid. */
79     public static final int ERROR_INVALID_PORT = -22;
80     /** The packet length is invalid (e.g., too long). */
81     public static final int ERROR_INVALID_LENGTH = -23;
82     /** The packet transmission interval is invalid (e.g., too short). */
83     public static final int ERROR_INVALID_INTERVAL = -24;
84     /** The target socket is invalid. */
85     public static final int ERROR_INVALID_SOCKET = -25;
86     /** The target socket is not idle. */
87     public static final int ERROR_SOCKET_NOT_IDLE = -26;
88 
89     /** The device does not support this request. */
90     public static final int ERROR_UNSUPPORTED = -30;
91     /** @hide TODO: delete when telephony code has been updated. */
92     public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED;
93     /** The hardware returned an error. */
94     public static final int ERROR_HARDWARE_ERROR = -31;
95     /** The limitation of resource is reached. */
96     public static final int ERROR_INSUFFICIENT_RESOURCES = -32;
97 
98 
99     /** @hide */
100     @Retention(RetentionPolicy.SOURCE)
101     @IntDef(prefix = { "ERROR_" }, value = {
102             ERROR_INVALID_NETWORK,
103             ERROR_INVALID_IP_ADDRESS,
104             ERROR_INVALID_PORT,
105             ERROR_INVALID_LENGTH,
106             ERROR_INVALID_INTERVAL,
107             ERROR_INVALID_SOCKET,
108             ERROR_SOCKET_NOT_IDLE
109     })
110     public @interface ErrorCode {}
111 
112     /** @hide */
113     @Retention(RetentionPolicy.SOURCE)
114     @IntDef(value = {
115             SUCCESS,
116             ERROR_INVALID_LENGTH,
117             ERROR_UNSUPPORTED,
118             ERROR_INSUFFICIENT_RESOURCES,
119             ERROR_HARDWARE_UNSUPPORTED
120     })
121     public @interface KeepaliveEvent {}
122 
123     /**
124      * The minimum interval in seconds between keepalive packet transmissions.
125      *
126      * @hide
127      **/
128     public static final int MIN_INTERVAL_SEC = 10;
129 
130     /**
131      * The maximum interval in seconds between keepalive packet transmissions.
132      *
133      * @hide
134      **/
135     public static final int MAX_INTERVAL_SEC = 3600;
136 
137     /**
138      * An exception that embarks an error code.
139      * @hide
140      */
141     public static class ErrorCodeException extends Exception {
142         public final int error;
ErrorCodeException(final int error, final Throwable e)143         public ErrorCodeException(final int error, final Throwable e) {
144             super(e);
145             this.error = error;
146         }
ErrorCodeException(final int error)147         public ErrorCodeException(final int error) {
148             this.error = error;
149         }
150     }
151 
152     /**
153      * This socket is invalid.
154      * See the error code for details, and the optional cause.
155      * @hide
156      */
157     public static class InvalidSocketException extends ErrorCodeException {
InvalidSocketException(final int error, final Throwable e)158         public InvalidSocketException(final int error, final Throwable e) {
159             super(error, e);
160         }
InvalidSocketException(final int error)161         public InvalidSocketException(final int error) {
162             super(error);
163         }
164     }
165 
166     @NonNull final IConnectivityManager mService;
167     @NonNull final Network mNetwork;
168     @NonNull final ParcelFileDescriptor mPfd;
169     @NonNull final Executor mExecutor;
170     @NonNull final ISocketKeepaliveCallback mCallback;
171     // TODO: remove slot since mCallback could be used to identify which keepalive to stop.
172     @Nullable Integer mSlot;
173 
SocketKeepalive(@onNull IConnectivityManager service, @NonNull Network network, @NonNull ParcelFileDescriptor pfd, @NonNull Executor executor, @NonNull Callback callback)174     SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
175             @NonNull ParcelFileDescriptor pfd,
176             @NonNull Executor executor, @NonNull Callback callback) {
177         mService = service;
178         mNetwork = network;
179         mPfd = pfd;
180         mExecutor = executor;
181         mCallback = new ISocketKeepaliveCallback.Stub() {
182             @Override
183             public void onStarted(int slot) {
184                 Binder.withCleanCallingIdentity(() ->
185                         mExecutor.execute(() -> {
186                             mSlot = slot;
187                             callback.onStarted();
188                         }));
189             }
190 
191             @Override
192             public void onStopped() {
193                 Binder.withCleanCallingIdentity(() ->
194                         executor.execute(() -> {
195                             mSlot = null;
196                             callback.onStopped();
197                         }));
198             }
199 
200             @Override
201             public void onError(int error) {
202                 Binder.withCleanCallingIdentity(() ->
203                         executor.execute(() -> {
204                             mSlot = null;
205                             callback.onError(error);
206                         }));
207             }
208 
209             @Override
210             public void onDataReceived() {
211                 Binder.withCleanCallingIdentity(() ->
212                         executor.execute(() -> {
213                             mSlot = null;
214                             callback.onDataReceived();
215                         }));
216             }
217         };
218     }
219 
220     /**
221      * Request that keepalive be started with the given {@code intervalSec}. See
222      * {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an exception
223      * when invoking start or stop of the {@link SocketKeepalive}, a {@link RemoteException} will be
224      * thrown into the {@code executor}. This is typically not important to catch because the remote
225      * party is the system, so if it is not in shape to communicate through binder the system is
226      * probably going down anyway. If the caller cares regardless, it can use a custom
227      * {@link Executor} to catch the {@link RemoteException}.
228      *
229      * @param intervalSec The target interval in seconds between keepalive packet transmissions.
230      *                    The interval should be between 10 seconds and 3600 seconds, otherwise
231      *                    {@link #ERROR_INVALID_INTERVAL} will be returned.
232      */
start(@ntRangefrom = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC) int intervalSec)233     public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
234             int intervalSec) {
235         startImpl(intervalSec);
236     }
237 
startImpl(int intervalSec)238     abstract void startImpl(int intervalSec);
239 
240     /**
241      * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
242      * before using the object. See {@link SocketKeepalive}.
243      */
stop()244     public final void stop() {
245         stopImpl();
246     }
247 
stopImpl()248     abstract void stopImpl();
249 
250     /**
251      * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be
252      * usable again if {@code close()} is called.
253      */
254     @Override
close()255     public final void close() {
256         stop();
257         try {
258             mPfd.close();
259         } catch (IOException e) {
260             // Nothing much can be done.
261         }
262     }
263 
264     /**
265      * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See
266      * {@link SocketKeepalive}.
267      */
268     public static class Callback {
269         /** The requested keepalive was successfully started. */
onStarted()270         public void onStarted() {}
271         /** The keepalive was successfully stopped. */
onStopped()272         public void onStopped() {}
273         /** An error occurred. */
onError(@rrorCode int error)274         public void onError(@ErrorCode int error) {}
275         /** The keepalive on a TCP socket was stopped because the socket received data. This is
276          * never called for UDP sockets. */
onDataReceived()277         public void onDataReceived() {}
278     }
279 }
280