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