summaryrefslogtreecommitdiff
path: root/framework/src/android/net/NetworkAgent.java
blob: 2c50c73331bd504048cfa9560114bcfc7e616542 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
/*
 * Copyright (C) 2014 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.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * A utility class for handling for communicating between bearer-specific
 * code and ConnectivityService.
 *
 * An agent manages the life cycle of a network. A network starts its
 * life cycle when {@link register} is called on NetworkAgent. The network
 * is then connecting. When full L3 connectivity has been established,
 * the agent should call {@link markConnected} to inform the system that
 * this network is ready to use. When the network disconnects its life
 * ends and the agent should call {@link unregister}, at which point the
 * system will clean up and free resources.
 * Any reconnection becomes a new logical network, so after a network
 * is disconnected the agent cannot be used any more. Network providers
 * should create a new NetworkAgent instance to handle new connections.
 *
 * A bearer may have more than one NetworkAgent if it can simultaneously
 * support separate networks (IMS / Internet / MMS Apns on cellular, or
 * perhaps connections with different SSID or P2P for Wi-Fi).
 *
 * This class supports methods to start and stop sending keepalive packets.
 * Keepalive packets are typically sent at periodic intervals over a network
 * with NAT when there is no other traffic to avoid the network forcefully
 * closing the connection. NetworkAgents that manage technologies that
 * have hardware support for keepalive should implement the related
 * methods to save battery life. NetworkAgent that cannot get support
 * without waking up the CPU should not, as this would be prohibitive in
 * terms of battery - these agents should simply not override the related
 * methods, which results in the implementation returning
 * {@link SocketKeepalive.ERROR_UNSUPPORTED} as appropriate.
 *
 * Keepalive packets need to be sent at relatively frequent intervals
 * (a few seconds to a few minutes). As the contents of keepalive packets
 * depend on the current network status, hardware needs to be configured
 * to send them and has a limited amount of memory to do so. The HAL
 * formalizes this as slots that an implementation can configure to send
 * the correct packets. Devices typically have a small number of slots
 * per radio technology, and the specific number of slots for each
 * technology is specified in configuration files.
 * {@see SocketKeepalive} for details.
 *
 * @hide
 */
@SystemApi
public abstract class NetworkAgent {
    /**
     * The {@link Network} corresponding to this object.
     */
    @Nullable
    private volatile Network mNetwork;

    @Nullable
    private volatile INetworkAgentRegistry mRegistry;

    private interface RegistryAction {
        void execute(@NonNull INetworkAgentRegistry registry) throws RemoteException;
    }

    private final Handler mHandler;
    private final String LOG_TAG;
    private static final boolean DBG = true;
    private static final boolean VDBG = false;
    /** @hide */
    @TestApi
    public static final int MIN_LINGER_TIMER_MS = 2000;
    private final ArrayList<RegistryAction> mPreConnectedQueue = new ArrayList<>();
    private volatile long mLastBwRefreshTime = 0;
    private static final long BW_REFRESH_MIN_WIN_MS = 500;
    private boolean mBandwidthUpdateScheduled = false;
    private AtomicBoolean mBandwidthUpdatePending = new AtomicBoolean(false);
    @NonNull
    private NetworkInfo mNetworkInfo;
    @NonNull
    private final Object mRegisterLock = new Object();

    /**
     * The ID of the {@link NetworkProvider} that created this object, or
     * {@link NetworkProvider#ID_NONE} if unknown.
     * @hide
     */
    public final int providerId;

    // ConnectivityService parses message constants from itself and NetworkAgent with MessageUtils
    // for debugging purposes, and crashes if some messages have the same values.
    // TODO: have ConnectivityService store message names in different maps and remove this base
    private static final int BASE = 200;

    /**
     * Sent by ConnectivityService to the NetworkAgent to inform it of
     * suspected connectivity problems on its network.  The NetworkAgent
     * should take steps to verify and correct connectivity.
     * @hide
     */
    public static final int CMD_SUSPECT_BAD = BASE;

    /**
     * Sent by the NetworkAgent (note the EVENT vs CMD prefix) to
     * ConnectivityService to pass the current NetworkInfo (connection state).
     * Sent when the NetworkInfo changes, mainly due to change of state.
     * obj = NetworkInfo
     * @hide
     */
    public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1;

    /**
     * Sent by the NetworkAgent to ConnectivityService to pass the current
     * NetworkCapabilties.
     * obj = NetworkCapabilities
     * @hide
     */
    public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2;

    /**
     * Sent by the NetworkAgent to ConnectivityService to pass the current
     * NetworkProperties.
     * obj = NetworkProperties
     * @hide
     */
    public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;

    /**
     * Centralize the place where base network score, and network score scaling, will be
     * stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE
     * @hide
     */
    public static final int WIFI_BASE_SCORE = 60;

    /**
     * Sent by the NetworkAgent to ConnectivityService to pass the current
     * network score.
     * arg1 = network score int
     * @hide
     */
    public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;

    /**
     * Sent by the NetworkAgent to ConnectivityService to pass the current
     * list of underlying networks.
     * obj = array of Network objects
     * @hide
     */
    public static final int EVENT_UNDERLYING_NETWORKS_CHANGED = BASE + 5;

    /**
     * Sent by the NetworkAgent to ConnectivityService to pass the current value of the teardown
     * delay.
     * arg1 = teardown delay in milliseconds
     * @hide
     */
    public static final int EVENT_TEARDOWN_DELAY_CHANGED = BASE + 6;

    /**
     * The maximum value for the teardown delay, in milliseconds.
     * @hide
     */
    public static final int MAX_TEARDOWN_DELAY_MS = 5000;

    /**
     * Sent by ConnectivityService to the NetworkAgent to inform the agent of the
     * networks status - whether we could use the network or could not, due to
     * either a bad network configuration (no internet link) or captive portal.
     *
     * arg1 = either {@code VALID_NETWORK} or {@code INVALID_NETWORK}
     * obj = Bundle containing map from {@code REDIRECT_URL_KEY} to {@code String}
     *       representing URL that Internet probe was redirect to, if it was redirected,
     *       or mapping to {@code null} otherwise.
     * @hide
     */
    public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7;

    /**
     * Network validation suceeded.
     * Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}.
     */
    public static final int VALIDATION_STATUS_VALID = 1;

    /**
     * Network validation was attempted and failed. This may be received more than once as
     * subsequent validation attempts are made.
     */
    public static final int VALIDATION_STATUS_NOT_VALID = 2;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = { "VALIDATION_STATUS_" }, value = {
            VALIDATION_STATUS_VALID,
            VALIDATION_STATUS_NOT_VALID
    })
    public @interface ValidationStatus {}

    // TODO: remove.
    /** @hide */
    public static final int VALID_NETWORK = 1;
    /** @hide */
    public static final int INVALID_NETWORK = 2;

    /**
     * The key for the redirect URL in the Bundle argument of {@code CMD_REPORT_NETWORK_STATUS}.
     * @hide
     */
    public static final String REDIRECT_URL_KEY = "redirect URL";

    /**
     * Sent by the NetworkAgent to ConnectivityService to indicate this network was
     * explicitly selected.  This should be sent before the NetworkInfo is marked
     * CONNECTED so it can be given special treatment at that time.
     *
     * obj = boolean indicating whether to use this network even if unvalidated
     * @hide
     */
    public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8;

    /**
     * Sent by ConnectivityService to the NetworkAgent to inform the agent of
     * whether the network should in the future be used even if not validated.
     * This decision is made by the user, but it is the network transport's
     * responsibility to remember it.
     *
     * arg1 = 1 if true, 0 if false
     * @hide
     */
    public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;

    /**
     * Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
     * the underlying network connection for updated bandwidth information.
     * @hide
     */
    public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;

    /**
     * Sent by ConnectivityService to the NetworkAgent to request that the specified packet be sent
     * periodically on the given interval.
     *
     *   arg1 = the hardware slot number of the keepalive to start
     *   arg2 = interval in seconds
     *   obj = KeepalivePacketData object describing the data to be sent
     *
     * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
     * @hide
     */
    public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11;

    /**
     * Requests that the specified keepalive packet be stopped.
     *
     * arg1 = hardware slot number of the keepalive to stop.
     *
     * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
     * @hide
     */
    public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12;

    /**
     * Sent by the NetworkAgent to ConnectivityService to provide status on a socket keepalive
     * request. This may either be the reply to a CMD_START_SOCKET_KEEPALIVE, or an asynchronous
     * error notification.
     *
     * This is also sent by KeepaliveTracker to the app's {@link SocketKeepalive},
     * so that the app's {@link SocketKeepalive.Callback} methods can be called.
     *
     * arg1 = hardware slot number of the keepalive
     * arg2 = error code
     * @hide
     */
    public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13;

    /**
     * Sent by ConnectivityService to inform this network transport of signal strength thresholds
     * that when crossed should trigger a system wakeup and a NetworkCapabilities update.
     *
     *   obj = int[] describing signal strength thresholds.
     * @hide
     */
    public static final int CMD_SET_SIGNAL_STRENGTH_THRESHOLDS = BASE + 14;

    /**
     * Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid
     * automatically reconnecting to this network (e.g. via autojoin).  Happens
     * when user selects "No" option on the "Stay connected?" dialog box.
     * @hide
     */
    public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15;

    /**
     * Sent by the KeepaliveTracker to NetworkAgent to add a packet filter.
     *
     * For TCP keepalive offloads, keepalive packets are sent by the firmware. However, because the
     * remote site will send ACK packets in response to the keepalive packets, the firmware also
     * needs to be configured to properly filter the ACKs to prevent the system from waking up.
     * This does not happen with UDP, so this message is TCP-specific.
     * arg1 = hardware slot number of the keepalive to filter for.
     * obj = the keepalive packet to send repeatedly.
     * @hide
     */
    public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16;

    /**
     * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See
     * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}.
     * arg1 = hardware slot number of the keepalive packet filter to remove.
     * @hide
     */
    public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17;

    /**
     * Sent by ConnectivityService to the NetworkAgent to complete the bidirectional connection.
     * obj = INetworkAgentRegistry
     */
    private static final int EVENT_AGENT_CONNECTED = BASE + 18;

    /**
     * Sent by ConnectivityService to the NetworkAgent to inform the agent that it was disconnected.
     */
    private static final int EVENT_AGENT_DISCONNECTED = BASE + 19;

    /**
     * Sent by QosCallbackTracker to {@link NetworkAgent} to register a new filter with
     * callback.
     *
     * arg1 = QoS agent callback ID
     * obj = {@link QosFilter}
     * @hide
     */
    public static final int CMD_REGISTER_QOS_CALLBACK = BASE + 20;

    /**
     * Sent by QosCallbackTracker to {@link NetworkAgent} to unregister a callback.
     *
     * arg1 = QoS agent callback ID
     * @hide
     */
    public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21;

    /**
     * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent that its native
     * network was created and the Network object is now valid.
     *
     * @hide
     */
    public static final int CMD_NETWORK_CREATED = BASE + 22;

    /**
     * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent that its native
     * network was destroyed.
     *
     * @hide
     */
    public static final int CMD_NETWORK_DESTROYED = BASE + 23;

    /**
     * Sent by the NetworkAgent to ConnectivityService to set the linger duration for this network
     * agent.
     * arg1 = the linger duration, represents by {@link Duration}.
     *
     * @hide
     */
    public static final int EVENT_LINGER_DURATION_CHANGED = BASE + 24;

    /**
     * Sent by the NetworkAgent to ConnectivityService to set add a DSCP policy.
     *
     * @hide
     */
    public static final int EVENT_ADD_DSCP_POLICY = BASE + 25;

    /**
     * Sent by the NetworkAgent to ConnectivityService to set remove a DSCP policy.
     *
     * @hide
     */
    public static final int EVENT_REMOVE_DSCP_POLICY = BASE + 26;

    /**
     * Sent by the NetworkAgent to ConnectivityService to remove all DSCP policies.
     *
     * @hide
     */
    public static final int EVENT_REMOVE_ALL_DSCP_POLICIES = BASE + 27;

    /**
     * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent of an updated
     * status for a DSCP policy.
     *
     * @hide
     */
    public static final int CMD_DSCP_POLICY_STATUS = BASE + 28;

    /**
     * DSCP policy was successfully added.
     */
    public static final int DSCP_POLICY_STATUS_SUCCESS = 0;

    /**
     * DSCP policy was rejected for any reason besides invalid classifier or insufficient resources.
     */
    public static final int DSCP_POLICY_STATUS_REQUEST_DECLINED = 1;

    /**
     * Requested DSCP policy contained a classifier which is not supported.
     */
    public static final int DSCP_POLICY_STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED = 2;

    /**
     * Requested DSCP policy was not added due to insufficient processing resources.
     */
    // TODO: should this error case be supported?
    public static final int DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES = 3;

    /**
     * DSCP policy was deleted.
     */
    public static final int DSCP_POLICY_STATUS_DELETED = 4;

    /**
     * DSCP policy was not found during deletion.
     */
    public static final int DSCP_POLICY_STATUS_POLICY_NOT_FOUND = 5;

    /** @hide */
    @IntDef(prefix = "DSCP_POLICY_STATUS_", value = {
        DSCP_POLICY_STATUS_SUCCESS,
        DSCP_POLICY_STATUS_REQUEST_DECLINED,
        DSCP_POLICY_STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED,
        DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES,
        DSCP_POLICY_STATUS_DELETED
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface DscpPolicyStatus {}

    /**
     * Sent by the NetworkAgent to ConnectivityService to notify that this network is expected to be
     * replaced within the specified time by a similar network.
     * arg1 = timeout in milliseconds
     * @hide
     */
    public static final int EVENT_UNREGISTER_AFTER_REPLACEMENT = BASE + 29;

    private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
        final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
                config.legacyTypeName, config.legacySubTypeName);
        ni.setIsAvailable(true);
        ni.setDetailedState(NetworkInfo.DetailedState.CONNECTING, null /* reason */,
                config.getLegacyExtraInfo());
        return ni;
    }

    // Temporary backward compatibility constructor
    public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
            @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score,
            @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) {
        this(context, looper, logTag, nc, lp,
                new NetworkScore.Builder().setLegacyInt(score).build(), config, provider);
    }

    /**
     * Create a new network agent.
     * @param context a {@link Context} to get system services from.
     * @param looper the {@link Looper} on which to invoke the callbacks.
     * @param logTag the tag for logs
     * @param nc the initial {@link NetworkCapabilities} of this network. Update with
     *           sendNetworkCapabilities.
     * @param lp the initial {@link LinkProperties} of this network. Update with sendLinkProperties.
     * @param score the initial score of this network. Update with sendNetworkScore.
     * @param config an immutable {@link NetworkAgentConfig} for this agent.
     * @param provider the {@link NetworkProvider} managing this agent.
     */
    public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag,
            @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
            @NonNull NetworkScore score, @NonNull NetworkAgentConfig config,
            @Nullable NetworkProvider provider) {
        this(looper, context, logTag, nc, lp, score, config,
                provider == null ? NetworkProvider.ID_NONE : provider.getProviderId(),
                getLegacyNetworkInfo(config));
    }

    private static class InitialConfiguration {
        public final Context context;
        public final NetworkCapabilities capabilities;
        public final LinkProperties properties;
        public final NetworkScore score;
        public final NetworkAgentConfig config;
        public final NetworkInfo info;
        InitialConfiguration(@NonNull Context context, @NonNull NetworkCapabilities capabilities,
                @NonNull LinkProperties properties, @NonNull NetworkScore score,
                @NonNull NetworkAgentConfig config, @NonNull NetworkInfo info) {
            this.context = context;
            this.capabilities = capabilities;
            this.properties = properties;
            this.score = score;
            this.config = config;
            this.info = info;
        }
    }
    private volatile InitialConfiguration mInitialConfiguration;

    private NetworkAgent(@NonNull Looper looper, @NonNull Context context, @NonNull String logTag,
            @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp,
            @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId,
            @NonNull NetworkInfo ni) {
        mHandler = new NetworkAgentHandler(looper);
        LOG_TAG = logTag;
        mNetworkInfo = new NetworkInfo(ni);
        this.providerId = providerId;
        if (ni == null || nc == null || lp == null) {
            throw new IllegalArgumentException();
        }

        mInitialConfiguration = new InitialConfiguration(context,
                new NetworkCapabilities(nc, NetworkCapabilities.REDACT_NONE),
                new LinkProperties(lp), score, config, ni);
    }

    private class NetworkAgentHandler extends Handler {
        NetworkAgentHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case EVENT_AGENT_CONNECTED: {
                    if (mRegistry != null) {
                        log("Received new connection while already connected!");
                    } else {
                        if (VDBG) log("NetworkAgent fully connected");
                        synchronized (mPreConnectedQueue) {
                            final INetworkAgentRegistry registry = (INetworkAgentRegistry) msg.obj;
                            mRegistry = registry;
                            for (RegistryAction a : mPreConnectedQueue) {
                                try {
                                    a.execute(registry);
                                } catch (RemoteException e) {
                                    Log.wtf(LOG_TAG, "Communication error with registry", e);
                                    // Fall through
                                }
                            }
                            mPreConnectedQueue.clear();
                        }
                    }
                    break;
                }
                case EVENT_AGENT_DISCONNECTED: {
                    if (DBG) log("NetworkAgent channel lost");
                    // let the client know CS is done with us.
                    onNetworkUnwanted();
                    synchronized (mPreConnectedQueue) {
                        mRegistry = null;
                    }
                    break;
                }
                case CMD_SUSPECT_BAD: {
                    log("Unhandled Message " + msg);
                    break;
                }
                case CMD_REQUEST_BANDWIDTH_UPDATE: {
                    long currentTimeMs = System.currentTimeMillis();
                    if (VDBG) {
                        log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
                    }
                    if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
                        mBandwidthUpdateScheduled = false;
                        if (!mBandwidthUpdatePending.getAndSet(true)) {
                            onBandwidthUpdateRequested();
                        }
                    } else {
                        // deliver the request at a later time rather than discard it completely.
                        if (!mBandwidthUpdateScheduled) {
                            long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS
                                    - currentTimeMs + 1;
                            mBandwidthUpdateScheduled = sendEmptyMessageDelayed(
                                    CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
                        }
                    }
                    break;
                }
                case CMD_REPORT_NETWORK_STATUS: {
                    String redirectUrl = ((Bundle) msg.obj).getString(REDIRECT_URL_KEY);
                    if (VDBG) {
                        log("CMD_REPORT_NETWORK_STATUS("
                                + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ")
                                + redirectUrl);
                    }
                    Uri uri = null;
                    try {
                        if (null != redirectUrl) {
                            uri = Uri.parse(redirectUrl);
                        }
                    } catch (Exception e) {
                        Log.wtf(LOG_TAG, "Surprising URI : " + redirectUrl, e);
                    }
                    onValidationStatus(msg.arg1 /* status */, uri);
                    break;
                }
                case CMD_SAVE_ACCEPT_UNVALIDATED: {
                    onSaveAcceptUnvalidated(msg.arg1 != 0);
                    break;
                }
                case CMD_START_SOCKET_KEEPALIVE: {
                    onStartSocketKeepalive(msg.arg1 /* slot */,
                            Duration.ofSeconds(msg.arg2) /* interval */,
                            (KeepalivePacketData) msg.obj /* packet */);
                    break;
                }
                case CMD_STOP_SOCKET_KEEPALIVE: {
                    onStopSocketKeepalive(msg.arg1 /* slot */);
                    break;
                }

                case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
                    onSignalStrengthThresholdsUpdated((int[]) msg.obj);
                    break;
                }
                case CMD_PREVENT_AUTOMATIC_RECONNECT: {
                    onAutomaticReconnectDisabled();
                    break;
                }
                case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
                    onAddKeepalivePacketFilter(msg.arg1 /* slot */,
                            (KeepalivePacketData) msg.obj /* packet */);
                    break;
                }
                case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
                    onRemoveKeepalivePacketFilter(msg.arg1 /* slot */);
                    break;
                }
                case CMD_REGISTER_QOS_CALLBACK: {
                    onQosCallbackRegistered(
                            msg.arg1 /* QoS callback id */,
                            (QosFilter) msg.obj /* QoS filter */);
                    break;
                }
                case CMD_UNREGISTER_QOS_CALLBACK: {
                    onQosCallbackUnregistered(
                            msg.arg1 /* QoS callback id */);
                    break;
                }
                case CMD_NETWORK_CREATED: {
                    onNetworkCreated();
                    break;
                }
                case CMD_NETWORK_DESTROYED: {
                    onNetworkDestroyed();
                    break;
                }
                case CMD_DSCP_POLICY_STATUS: {
                    onDscpPolicyStatusUpdated(
                            msg.arg1 /* Policy ID */,
                            msg.arg2 /* DSCP Policy Status */);
                    break;
                }
            }
        }
    }

    /**
     * Register this network agent with ConnectivityService.
     *
     * This method can only be called once per network agent.
     *
     * @return the Network associated with this network agent (which can also be obtained later
     *         by calling getNetwork() on this agent).
     * @throws IllegalStateException thrown by the system server if this network agent is
     *         already registered.
     */
    @NonNull
    public Network register() {
        if (VDBG) log("Registering NetworkAgent");
        synchronized (mRegisterLock) {
            if (mNetwork != null) {
                throw new IllegalStateException("Agent already registered");
            }
            final ConnectivityManager cm = (ConnectivityManager) mInitialConfiguration.context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler),
                    new NetworkInfo(mInitialConfiguration.info),
                    mInitialConfiguration.properties, mInitialConfiguration.capabilities,
                    mInitialConfiguration.score, mInitialConfiguration.config, providerId);
            mInitialConfiguration = null; // All this memory can now be GC'd
        }
        return mNetwork;
    }

    private static class NetworkAgentBinder extends INetworkAgent.Stub {
        private static final String LOG_TAG = NetworkAgentBinder.class.getSimpleName();

        private final Handler mHandler;

        private NetworkAgentBinder(Handler handler) {
            mHandler = handler;
        }

        @Override
        public void onRegistered(@NonNull INetworkAgentRegistry registry) {
            mHandler.sendMessage(mHandler.obtainMessage(EVENT_AGENT_CONNECTED, registry));
        }

        @Override
        public void onDisconnected() {
            mHandler.sendMessage(mHandler.obtainMessage(EVENT_AGENT_DISCONNECTED));
        }

        @Override
        public void onBandwidthUpdateRequested() {
            mHandler.sendMessage(mHandler.obtainMessage(CMD_REQUEST_BANDWIDTH_UPDATE));
        }

        @Override
        public void onValidationStatusChanged(
                int validationStatus, @Nullable String captivePortalUrl) {
            // TODO: consider using a parcelable as argument when the interface is structured
            Bundle redirectUrlBundle = new Bundle();
            redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, captivePortalUrl);
            mHandler.sendMessage(mHandler.obtainMessage(CMD_REPORT_NETWORK_STATUS,
                    validationStatus, 0, redirectUrlBundle));
        }

        @Override
        public void onSaveAcceptUnvalidated(boolean acceptUnvalidated) {
            mHandler.sendMessage(mHandler.obtainMessage(CMD_SAVE_ACCEPT_UNVALIDATED,
                    acceptUnvalidated ? 1 : 0, 0));
        }

        @Override
        public void onStartNattSocketKeepalive(int slot, int intervalDurationMs,
                @NonNull NattKeepalivePacketData packetData) {
            mHandler.sendMessage(mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE,
                    slot, intervalDurationMs, packetData));
        }

        @Override
        public void onStartTcpSocketKeepalive(int slot, int intervalDurationMs,
                @NonNull TcpKeepalivePacketData packetData) {
            mHandler.sendMessage(mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE,
                    slot, intervalDurationMs, packetData));
        }

        @Override
        public void onStopSocketKeepalive(int slot) {
            mHandler.sendMessage(mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0));
        }

        @Override
        public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) {
            mHandler.sendMessage(mHandler.obtainMessage(
                    CMD_SET_SIGNAL_STRENGTH_THRESHOLDS, thresholds));
        }

        @Override
        public void onPreventAutomaticReconnect() {
            mHandler.sendMessage(mHandler.obtainMessage(CMD_PREVENT_AUTOMATIC_RECONNECT));
        }

        @Override
        public void onAddNattKeepalivePacketFilter(int slot,
                @NonNull NattKeepalivePacketData packetData) {
            mHandler.sendMessage(mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER,
                    slot, 0, packetData));
        }

        @Override
        public void onAddTcpKeepalivePacketFilter(int slot,
                @NonNull TcpKeepalivePacketData packetData) {
            mHandler.sendMessage(mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER,
                    slot, 0, packetData));
        }

        @Override
        public void onRemoveKeepalivePacketFilter(int slot) {
            mHandler.sendMessage(mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER,
                    slot, 0));
        }

        @Override
        public void onQosFilterCallbackRegistered(final int qosCallbackId,
                final QosFilterParcelable qosFilterParcelable) {
            if (qosFilterParcelable.getQosFilter() != null) {
                mHandler.sendMessage(
                        mHandler.obtainMessage(CMD_REGISTER_QOS_CALLBACK, qosCallbackId, 0,
                                qosFilterParcelable.getQosFilter()));
                return;
            }

            Log.wtf(LOG_TAG, "onQosFilterCallbackRegistered: qos filter is null.");
        }

        @Override
        public void onQosCallbackUnregistered(final int qosCallbackId) {
            mHandler.sendMessage(mHandler.obtainMessage(
                    CMD_UNREGISTER_QOS_CALLBACK, qosCallbackId, 0, null));
        }

        @Override
        public void onNetworkCreated() {
            mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_CREATED));
        }

        @Override
        public void onNetworkDestroyed() {
            mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DESTROYED));
        }

        @Override
        public void onDscpPolicyStatusUpdated(final int policyId,
                @DscpPolicyStatus final int status) {
            mHandler.sendMessage(mHandler.obtainMessage(
                    CMD_DSCP_POLICY_STATUS, policyId, status));
        }
    }

    /**
     * Register this network agent with a testing harness.
     *
     * The returned Messenger sends messages to the Handler. This allows a test to send
     * this object {@code CMD_*} messages as if they came from ConnectivityService, which
     * is useful for testing the behavior.
     *
     * @hide
     */
    public INetworkAgent registerForTest(final Network network) {
        log("Registering NetworkAgent for test");
        synchronized (mRegisterLock) {
            mNetwork = network;
            mInitialConfiguration = null;
        }
        return new NetworkAgentBinder(mHandler);
    }

    /**
     * Waits for the handler to be idle.
     * This is useful for testing, and has smaller scope than an accessor to mHandler.
     * TODO : move the implementation in common library with the tests
     * @hide
     */
    @VisibleForTesting
    public boolean waitForIdle(final long timeoutMs) {
        final ConditionVariable cv = new ConditionVariable(false);
        mHandler.post(cv::open);
        return cv.block(timeoutMs);
    }

    /**
     * @return The Network associated with this agent, or null if it's not registered yet.
     */
    @Nullable
    public Network getNetwork() {
        return mNetwork;
    }

    private void queueOrSendMessage(@NonNull RegistryAction action) {
        synchronized (mPreConnectedQueue) {
            if (mRegistry != null) {
                try {
                    action.execute(mRegistry);
                } catch (RemoteException e) {
                    Log.wtf(LOG_TAG, "Error executing registry action", e);
                    // Fall through: the channel is asynchronous and does not report errors back
                }
            } else {
                mPreConnectedQueue.add(action);
            }
        }
    }

    /**
     * Must be called by the agent when the network's {@link LinkProperties} change.
     * @param linkProperties the new LinkProperties.
     */
    public final void sendLinkProperties(@NonNull LinkProperties linkProperties) {
        Objects.requireNonNull(linkProperties);
        final LinkProperties lp = new LinkProperties(linkProperties);
        queueOrSendMessage(reg -> reg.sendLinkProperties(lp));
    }

    /**
     * Must be called by the agent when the network's underlying networks change.
     *
     * <p>{@code networks} is one of the following:
     * <ul>
     * <li><strong>a non-empty array</strong>: an array of one or more {@link Network}s, in
     * decreasing preference order. For example, if this VPN uses both wifi and mobile (cellular)
     * networks to carry app traffic, but prefers or uses wifi more than mobile, wifi should appear
     * first in the array.</li>
     * <li><strong>an empty array</strong>: a zero-element array, meaning that the VPN has no
     * underlying network connection, and thus, app traffic will not be sent or received.</li>
     * <li><strong>null</strong>: (default) signifies that the VPN uses whatever is the system's
     * default network. I.e., it doesn't use the {@code bindSocket} or {@code bindDatagramSocket}
     * APIs mentioned above to send traffic over specific channels.</li>
     * </ul>
     *
     * @param underlyingNetworks the new list of underlying networks.
     * @see {@link VpnService.Builder#setUnderlyingNetworks(Network[])}
     */
    public final void setUnderlyingNetworks(
            @SuppressLint("NullableCollection") @Nullable List<Network> underlyingNetworks) {
        final ArrayList<Network> underlyingArray = (underlyingNetworks != null)
                ? new ArrayList<>(underlyingNetworks) : null;
        queueOrSendMessage(reg -> reg.sendUnderlyingNetworks(underlyingArray));
    }

    /**
     * Inform ConnectivityService that this agent has now connected.
     * Call {@link #unregister} to disconnect.
     */
    public void markConnected() {
        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null /* reason */,
                mNetworkInfo.getExtraInfo());
        queueOrSendNetworkInfo(mNetworkInfo);
    }

    /**
     * Unregister this network agent.
     *
     * This signals the network has disconnected and ends its lifecycle. After this is called,
     * the network is torn down and this agent can no longer be used.
     */
    public void unregister() {
        // When unregistering an agent nobody should use the extrainfo (or reason) any more.
        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null /* reason */,
                null /* extraInfo */);
        queueOrSendNetworkInfo(mNetworkInfo);
    }

    /**
     * Sets the value of the teardown delay.
     *
     * The teardown delay is the time between when the network disconnects and when the native
     * network corresponding to this {@code NetworkAgent} is destroyed. By default, the native
     * network is destroyed immediately. If {@code teardownDelayMs} is non-zero, then when this
     * network disconnects, the system will instead immediately mark the network as restricted
     * and unavailable to unprivileged apps, but will defer destroying the native network until the
     * teardown delay timer expires.
     *
     * The interfaces in use by this network will remain in use until the native network is
     * destroyed and cannot be reused until {@link #onNetworkDestroyed()} is called.
     *
     * This method may be called at any time while the network is connected. It has no effect if
     * the network is already disconnected and the teardown delay timer is running.
     *
     * @param teardownDelayMillis the teardown delay to set, or 0 to disable teardown delay.
     */
    public void setTeardownDelayMillis(
            @IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int teardownDelayMillis) {
        queueOrSendMessage(reg -> reg.sendTeardownDelayMs(teardownDelayMillis));
    }

    /**
     * Indicates that this agent will likely soon be replaced by another agent for a very similar
     * network (e.g., same Wi-Fi SSID).
     *
     * If the network is not currently satisfying any {@link NetworkRequest}s, it will be torn down.
     * If it is satisfying requests, then the native network corresponding to the agent will be
     * destroyed immediately, but the agent will remain registered and will continue to satisfy
     * requests until {@link #unregister} is called, the network is replaced by an equivalent or
     * better network, or the specified timeout expires. During this time:
     *
     * <ul>
     * <li>The agent may not send any further updates, for example by calling methods
     *    such as {@link #sendNetworkCapabilities}, {@link #sendLinkProperties},
     *    {@link #sendNetworkScore(NetworkScore)} and so on. Any such updates will be ignored.
     * <li>The network will remain connected and continue to satisfy any requests that it would
     *    otherwise satisfy (including, possibly, the default request).
     * <li>The validation state of the network will not change, and calls to
     *    {@link ConnectivityManager#reportNetworkConnectivity(Network, boolean)} will be ignored.
     * </ul>
     *
     * Once this method is called, it is not possible to restore the agent to a functioning state.
     * If a replacement network becomes available, then a new agent must be registered. When that
     * replacement network is fully capable of replacing this network (including, possibly, being
     * validated), this agent will no longer be needed and will be torn down. Otherwise, this agent
     * can be disconnected by calling {@link #unregister}. If {@link #unregister} is not called,
     * this agent will automatically be unregistered when the specified timeout expires. Any
     * teardown delay previously set using{@link #setTeardownDelayMillis} is ignored.
     *
     * <p>This method has no effect if {@link #markConnected} has not yet been called.
     * <p>This method may only be called once.
     *
     * @param timeoutMillis the timeout after which this network will be unregistered even if
     *                      {@link #unregister} was not called.
     */
    public void unregisterAfterReplacement(
            @IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int timeoutMillis) {
        queueOrSendMessage(reg -> reg.sendUnregisterAfterReplacement(timeoutMillis));
    }

    /**
     * Change the legacy subtype of this network agent.
     *
     * This is only for backward compatibility and should not be used by non-legacy network agents,
     * or agents that did not use to set a subtype. As such, only TYPE_MOBILE type agents can use
     * this and others will be thrown an exception if they try.
     *
     * @deprecated this is for backward compatibility only.
     * @param legacySubtype the legacy subtype.
     * @hide
     */
    @Deprecated
    @SystemApi
    public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) {
        mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName);
        queueOrSendNetworkInfo(mNetworkInfo);
    }

    /**
     * Set the ExtraInfo of this network agent.
     *
     * This sets the ExtraInfo field inside the NetworkInfo returned by legacy public API and the
     * broadcasts about the corresponding Network.
     * This is only for backward compatibility and should not be used by non-legacy network agents,
     * who will be thrown an exception if they try. The extra info should only be :
     * <ul>
     *   <li>For cellular agents, the APN name.</li>
     *   <li>For ethernet agents, the interface name.</li>
     * </ul>
     *
     * @deprecated this is for backward compatibility only.
     * @param extraInfo the ExtraInfo.
     * @hide
     */
    @Deprecated
    public void setLegacyExtraInfo(@Nullable final String extraInfo) {
        mNetworkInfo.setExtraInfo(extraInfo);
        queueOrSendNetworkInfo(mNetworkInfo);
    }

    /**
     * Must be called by the agent when it has a new NetworkInfo object.
     * @hide TODO: expose something better.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    public final void sendNetworkInfo(NetworkInfo networkInfo) {
        queueOrSendNetworkInfo(networkInfo);
    }

    private void queueOrSendNetworkInfo(NetworkInfo networkInfo) {
        final NetworkInfo ni = new NetworkInfo(networkInfo);
        queueOrSendMessage(reg -> reg.sendNetworkInfo(ni));
    }

    /**
     * Must be called by the agent when the network's {@link NetworkCapabilities} change.
     * @param networkCapabilities the new NetworkCapabilities.
     */
    public final void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
        Objects.requireNonNull(networkCapabilities);
        mBandwidthUpdatePending.set(false);
        mLastBwRefreshTime = System.currentTimeMillis();
        final NetworkCapabilities nc =
                new NetworkCapabilities(networkCapabilities, NetworkCapabilities.REDACT_NONE);
        queueOrSendMessage(reg -> reg.sendNetworkCapabilities(nc));
    }

    /**
     * Must be called by the agent to update the score of this network.
     *
     * @param score the new score.
     */
    public final void sendNetworkScore(@NonNull NetworkScore score) {
        Objects.requireNonNull(score);
        queueOrSendMessage(reg -> reg.sendScore(score));
    }

    /**
     * Must be called by the agent to update the score of this network.
     *
     * @param score the new score, between 0 and 99.
     * deprecated use sendNetworkScore(NetworkScore) TODO : remove in S.
     */
    public final void sendNetworkScore(@IntRange(from = 0, to = 99) int score) {
        sendNetworkScore(new NetworkScore.Builder().setLegacyInt(score).build());
    }

    /**
     * Must be called by the agent to indicate this network was manually selected by the user.
     * This should be called before the NetworkInfo is marked CONNECTED so that this
     * Network can be given special treatment at that time. If {@code acceptUnvalidated} is
     * {@code true}, then the system will switch to this network. If it is {@code false} and the
     * network cannot be validated, the system will ask the user whether to switch to this network.
     * If the user confirms and selects "don't ask again", then the system will call
     * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever
     * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement
     * {@link #saveAcceptUnvalidated} to respect the user's choice.
     * @hide should move to NetworkAgentConfig.
     */
    public void explicitlySelected(boolean acceptUnvalidated) {
        explicitlySelected(true /* explicitlySelected */, acceptUnvalidated);
    }

    /**
     * Must be called by the agent to indicate whether the network was manually selected by the
     * user. This should be called before the network becomes connected, so it can be given
     * special treatment when it does.
     *
     * If {@code explicitlySelected} is {@code true}, and {@code acceptUnvalidated} is {@code true},
     * then the system will switch to this network. If {@code explicitlySelected} is {@code true}
     * and {@code acceptUnvalidated} is {@code false}, and the  network cannot be validated, the
     * system will ask the user whether to switch to this network.  If the user confirms and selects
     * "don't ask again", then the system will call {@link #saveAcceptUnvalidated} to persist the
     * user's choice. Thus, if the transport ever calls this method with {@code explicitlySelected}
     * set to {@code true} and {@code acceptUnvalidated} set to {@code false}, it must also
     * implement {@link #saveAcceptUnvalidated} to respect the user's choice.
     *
     * If  {@code explicitlySelected} is {@code false} and {@code acceptUnvalidated} is
     * {@code true}, the system will interpret this as the user having accepted partial connectivity
     * on this network. Thus, the system will switch to the network and consider it validated even
     * if it only provides partial connectivity, but the network is not otherwise treated specially.
     * @hide should move to NetworkAgentConfig.
     */
    public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) {
        queueOrSendMessage(reg -> reg.sendExplicitlySelected(
                explicitlySelected, acceptUnvalidated));
    }

    /**
     * Called when ConnectivityService has indicated they no longer want this network.
     * The parent factory should (previously) have received indication of the change
     * as well, either canceling NetworkRequests or altering their score such that this
     * network won't be immediately requested again.
     */
    public void onNetworkUnwanted() {
        unwanted();
    }
    /** @hide TODO delete once subclasses have moved to onNetworkUnwanted. */
    protected void unwanted() {
    }

    /**
     * Called when ConnectivityService request a bandwidth update. The parent factory
     * shall try to overwrite this method and produce a bandwidth update if capable.
     * @hide
     */
    @SystemApi
    public void onBandwidthUpdateRequested() {
        pollLceData();
    }
    /** @hide TODO delete once subclasses have moved to onBandwidthUpdateRequested. */
    protected void pollLceData() {
    }

    /**
     * Called when the system determines the usefulness of this network.
     *
     * The system attempts to validate Internet connectivity on networks that provide the
     * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} capability.
     *
     * Currently there are two possible values:
     * {@code VALIDATION_STATUS_VALID} if Internet connectivity was validated,
     * {@code VALIDATION_STATUS_NOT_VALID} if Internet connectivity was not validated.
     *
     * This is guaranteed to be called again when the network status changes, but the system
     * may also call this multiple times even if the status does not change.
     *
     * @param status one of {@code VALIDATION_STATUS_VALID} or {@code VALIDATION_STATUS_NOT_VALID}.
     * @param redirectUri If Internet connectivity is being redirected (e.g., on a captive portal),
     *        this is the destination the probes are being redirected to, otherwise {@code null}.
     */
    public void onValidationStatus(@ValidationStatus int status, @Nullable Uri redirectUri) {
        networkStatus(status, null == redirectUri ? "" : redirectUri.toString());
    }
    /** @hide TODO delete once subclasses have moved to onValidationStatus */
    protected void networkStatus(int status, String redirectUrl) {
    }


    /**
     * Called when the user asks to remember the choice to use this network even if unvalidated.
     * The transport is responsible for remembering the choice, and the next time the user connects
     * to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}.
     * This method will only be called if {@link #explicitlySelected} was called with
     * {@code acceptUnvalidated} set to {@code false}.
     * @param accept whether the user wants to use the network even if unvalidated.
     */
    public void onSaveAcceptUnvalidated(boolean accept) {
        saveAcceptUnvalidated(accept);
    }
    /** @hide TODO delete once subclasses have moved to onSaveAcceptUnvalidated */
    protected void saveAcceptUnvalidated(boolean accept) {
    }

    /**
     * Called when ConnectivityService has successfully created this NetworkAgent's native network.
     */
    public void onNetworkCreated() {}


    /**
     * Called when ConnectivityService has successfully destroy this NetworkAgent's native network.
     */
    public void onNetworkDestroyed() {}

    /**
     * Called when when the DSCP Policy status has changed.
     */
    public void onDscpPolicyStatusUpdated(int policyId, @DscpPolicyStatus int status) {}

    /**
     * Requests that the network hardware send the specified packet at the specified interval.
     *
     * @param slot the hardware slot on which to start the keepalive.
     * @param interval the interval between packets, between 10 and 3600. Note that this API
     *                 does not support sub-second precision and will round off the request.
     * @param packet the packet to send.
     */
    // seconds is from SocketKeepalive.MIN_INTERVAL_SEC to MAX_INTERVAL_SEC, but these should
    // not be exposed as constants because they may change in the future (API guideline 4.8)
    // and should have getters if exposed at all. Getters can't be used in the annotation,
    // so the values unfortunately need to be copied.
    public void onStartSocketKeepalive(int slot, @NonNull Duration interval,
            @NonNull KeepalivePacketData packet) {
        final long intervalSeconds = interval.getSeconds();
        if (intervalSeconds < SocketKeepalive.MIN_INTERVAL_SEC
                || intervalSeconds > SocketKeepalive.MAX_INTERVAL_SEC) {
            throw new IllegalArgumentException("Interval needs to be comprised between "
                    + SocketKeepalive.MIN_INTERVAL_SEC + " and " + SocketKeepalive.MAX_INTERVAL_SEC
                    + " but was " + intervalSeconds);
        }
        final Message msg = mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, slot,
                (int) intervalSeconds, packet);
        startSocketKeepalive(msg);
        msg.recycle();
    }
    /** @hide TODO delete once subclasses have moved to onStartSocketKeepalive */
    protected void startSocketKeepalive(Message msg) {
        onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
    }

    /**
     * Requests that the network hardware stop a previously-started keepalive.
     *
     * @param slot the hardware slot on which to stop the keepalive.
     */
    public void onStopSocketKeepalive(int slot) {
        Message msg = mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0, null);
        stopSocketKeepalive(msg);
        msg.recycle();
    }
    /** @hide TODO delete once subclasses have moved to onStopSocketKeepalive */
    protected void stopSocketKeepalive(Message msg) {
        onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
    }

    /**
     * Must be called by the agent when a socket keepalive event occurs.
     *
     * @param slot the hardware slot on which the event occurred.
     * @param event the event that occurred, as one of the SocketKeepalive.ERROR_*
     *              or SocketKeepalive.SUCCESS constants.
     */
    public final void sendSocketKeepaliveEvent(int slot,
            @SocketKeepalive.KeepaliveEvent int event) {
        queueOrSendMessage(reg -> reg.sendSocketKeepaliveEvent(slot, event));
    }
    /** @hide TODO delete once callers have moved to sendSocketKeepaliveEvent */
    public void onSocketKeepaliveEvent(int slot, int reason) {
        sendSocketKeepaliveEvent(slot, reason);
    }

    /**
     * Called by ConnectivityService to add specific packet filter to network hardware to block
     * replies (e.g., TCP ACKs) matching the sent keepalive packets. Implementations that support
     * this feature must override this method.
     *
     * @param slot the hardware slot on which the keepalive should be sent.
     * @param packet the packet that is being sent.
     */
    public void onAddKeepalivePacketFilter(int slot, @NonNull KeepalivePacketData packet) {
        Message msg = mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0, packet);
        addKeepalivePacketFilter(msg);
        msg.recycle();
    }
    /** @hide TODO delete once subclasses have moved to onAddKeepalivePacketFilter */
    protected void addKeepalivePacketFilter(Message msg) {
    }

    /**
     * Called by ConnectivityService to remove a packet filter installed with
     * {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature
     * must override this method.
     *
     * @param slot the hardware slot on which the keepalive is being sent.
     */
    public void onRemoveKeepalivePacketFilter(int slot) {
        Message msg = mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, slot, 0, null);
        removeKeepalivePacketFilter(msg);
        msg.recycle();
    }
    /** @hide TODO delete once subclasses have moved to onRemoveKeepalivePacketFilter */
    protected void removeKeepalivePacketFilter(Message msg) {
    }

    /**
     * Called by ConnectivityService to inform this network agent of signal strength thresholds
     * that when crossed should trigger a system wakeup and a NetworkCapabilities update.
     *
     * When the system updates the list of thresholds that should wake up the CPU for a
     * given agent it will call this method on the agent. The agent that implement this
     * should implement it in hardware so as to ensure the CPU will be woken up on breach.
     * Agents are expected to react to a breach by sending an updated NetworkCapabilities
     * object with the appropriate signal strength to sendNetworkCapabilities.
     *
     * The specific units are bearer-dependent. See details on the units and requests in
     * {@link NetworkCapabilities.Builder#setSignalStrength}.
     *
     * @param thresholds the array of thresholds that should trigger wakeups.
     */
    public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) {
        setSignalStrengthThresholds(thresholds);
    }
    /** @hide TODO delete once subclasses have moved to onSetSignalStrengthThresholds */
    protected void setSignalStrengthThresholds(int[] thresholds) {
    }

    /**
     * Called when the user asks to not stay connected to this network because it was found to not
     * provide Internet access.  Usually followed by call to {@code unwanted}.  The transport is
     * responsible for making sure the device does not automatically reconnect to the same network
     * after the {@code unwanted} call.
     */
    public void onAutomaticReconnectDisabled() {
        preventAutomaticReconnect();
    }
    /** @hide TODO delete once subclasses have moved to onAutomaticReconnectDisabled */
    protected void preventAutomaticReconnect() {
    }

    /**
     * Called when a qos callback is registered with a filter.
     * @param qosCallbackId the id for the callback registered
     * @param filter the filter being registered
     */
    public void onQosCallbackRegistered(final int qosCallbackId, final @NonNull QosFilter filter) {
    }

    /**
     * Called when a qos callback is registered with a filter.
     * <p/>
     * Any QoS events that are sent with the same callback id after this method is called
     * are a no-op.
     *
     * @param qosCallbackId the id for the callback being unregistered
     */
    public void onQosCallbackUnregistered(final int qosCallbackId) {
    }


    /**
     * Sends the attributes of Qos Session back to the Application
     *
     * @param qosCallbackId the callback id that the session belongs to
     * @param sessionId the unique session id across all Qos Sessions
     * @param attributes the attributes of the Qos Session
     */
    public final void sendQosSessionAvailable(final int qosCallbackId, final int sessionId,
            @NonNull final QosSessionAttributes attributes) {
        Objects.requireNonNull(attributes, "The attributes must be non-null");
        if (attributes instanceof EpsBearerQosSessionAttributes) {
            queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId,
                    new QosSession(sessionId, QosSession.TYPE_EPS_BEARER),
                    (EpsBearerQosSessionAttributes)attributes));
        } else if (attributes instanceof NrQosSessionAttributes) {
            queueOrSendMessage(ra -> ra.sendNrQosSessionAvailable(qosCallbackId,
                    new QosSession(sessionId, QosSession.TYPE_NR_BEARER),
                    (NrQosSessionAttributes)attributes));
        }
    }

    /**
     * Sends event that the Qos Session was lost.
     *
     * @param qosCallbackId the callback id that the session belongs to
     * @param sessionId the unique session id across all Qos Sessions
     * @param qosSessionType the session type {@code QosSesson#QosSessionType}
     */
    public final void sendQosSessionLost(final int qosCallbackId,
            final int sessionId, final int qosSessionType) {
        queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId,
                new QosSession(sessionId, qosSessionType)));
    }

    /**
     * Sends the exception type back to the application.
     *
     * The NetworkAgent should not send anymore messages with this id.
     *
     * @param qosCallbackId the callback id this exception belongs to
     * @param exceptionType the type of exception
     */
    public final void sendQosCallbackError(final int qosCallbackId,
            @QosCallbackException.ExceptionType final int exceptionType) {
        queueOrSendMessage(ra -> ra.sendQosCallbackError(qosCallbackId, exceptionType));
    }

    /**
     * Set the linger duration for this network agent.
     * @param duration the delay between the moment the network becomes unneeded and the
     *                 moment the network is disconnected or moved into the background.
     *                 Note that If this duration has greater than millisecond precision, then
     *                 the internal implementation will drop any excess precision.
     */
    public void setLingerDuration(@NonNull final Duration duration) {
        Objects.requireNonNull(duration);
        final long durationMs = duration.toMillis();
        if (durationMs < MIN_LINGER_TIMER_MS || durationMs > Integer.MAX_VALUE) {
            throw new IllegalArgumentException("Duration must be within ["
                    + MIN_LINGER_TIMER_MS + "," + Integer.MAX_VALUE + "]ms");
        }
        queueOrSendMessage(ra -> ra.sendLingerDuration((int) durationMs));
    }

    /**
     * Add a DSCP Policy.
     * @param policy the DSCP policy to be added.
     */
    public void sendAddDscpPolicy(@NonNull final DscpPolicy policy) {
        Objects.requireNonNull(policy);
        queueOrSendMessage(ra -> ra.sendAddDscpPolicy(policy));
    }

    /**
     * Remove the specified DSCP policy.
     * @param policyId the ID corresponding to a specific DSCP Policy.
     */
    public void sendRemoveDscpPolicy(final int policyId) {
        queueOrSendMessage(ra -> ra.sendRemoveDscpPolicy(policyId));
    }

    /**
     * Remove all the DSCP policies on this network.
     */
    public void sendRemoveAllDscpPolicies() {
        queueOrSendMessage(ra -> ra.sendRemoveAllDscpPolicies());
    }

    /** @hide */
    protected void log(final String s) {
        Log.d(LOG_TAG, "NetworkAgent: " + s);
    }
}