blob: 83194d9e6fa9b10992d260073a9015cd4cf7301f [file] [log] [blame]
Hugo Benichi5e055182016-06-01 08:50:38 +09001/*
2 * Copyright (C) 2016, 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
17package com.android.server.connectivity;
18
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090019import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
20import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
21import static org.junit.Assert.assertEquals;
Hugo Benichif562ac32017-09-04 13:24:43 +090022import static org.junit.Assert.assertTrue;
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090023import static org.junit.Assert.fail;
Hugo Benichi5e055182016-06-01 08:50:38 +090024import static org.mockito.Mockito.any;
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090025import static org.mockito.Mockito.mock;
Hugo Benichi5e055182016-06-01 08:50:38 +090026import static org.mockito.Mockito.anyInt;
27import static org.mockito.Mockito.eq;
28import static org.mockito.Mockito.timeout;
29import static org.mockito.Mockito.times;
30import static org.mockito.Mockito.verify;
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090031import static org.mockito.Mockito.when;
Hugo Benichi5e055182016-06-01 08:50:38 +090032
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090033import android.content.Context;
34import android.net.ConnectivityManager;
35import android.net.Network;
36import android.net.NetworkCapabilities;
37import android.support.test.runner.AndroidJUnit4;
38import android.system.OsConstants;
39import android.test.suitebuilder.annotation.SmallTest;
40import android.util.Base64;
Hugo Benichi60c9f632017-09-05 13:34:48 +090041
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090042import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.DNSLookupBatch;
43import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
44import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
Hugo Benichi60c9f632017-09-05 13:34:48 +090045
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090046import java.io.FileOutputStream;
47import java.io.PrintWriter;
48import java.io.StringWriter;
49import java.util.ArrayList;
50import java.util.Arrays;
51import java.util.Comparator;
52import java.util.List;
Hugo Benichi60c9f632017-09-05 13:34:48 +090053
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090054import org.junit.Before;
55import org.junit.Test;
56import org.junit.runner.RunWith;
Hugo Benichi5e055182016-06-01 08:50:38 +090057
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090058@RunWith(AndroidJUnit4.class)
59@SmallTest
60public class NetdEventListenerServiceTest {
Hugo Benichi0d4a3982016-11-25 11:24:22 +090061 private static final String EXAMPLE_IPV4 = "192.0.2.1";
62 private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
63
Michal Karpinski4f261032016-09-26 09:20:25 +010064 NetdEventListenerService mNetdEventListenerService;
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090065 ConnectivityManager mCm;
Hugo Benichi5e055182016-06-01 08:50:38 +090066
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090067 @Before
Hugo Benichi5e055182016-06-01 08:50:38 +090068 public void setUp() {
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090069 NetworkCapabilities ncWifi = new NetworkCapabilities();
70 NetworkCapabilities ncCell = new NetworkCapabilities();
71 ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
72 ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
Hugo Benichi5e055182016-06-01 08:50:38 +090073
Hugo Benichi5eb90532017-03-23 18:38:22 +090074 mCm = mock(ConnectivityManager.class);
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090075 when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
76 when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
Hugo Benichi5e055182016-06-01 08:50:38 +090077
Hugo Benichi5eb90532017-03-23 18:38:22 +090078 mNetdEventListenerService = new NetdEventListenerService(mCm);
79 }
80
81 @Test
Hugo Benichif562ac32017-09-04 13:24:43 +090082 public void testWakeupEventLogging() throws Exception {
83 final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH;
84
Hugo Benichi380a0632017-10-20 09:25:29 +090085 // Baseline without any event
86 String[] baseline = listNetdEvent();
Hugo Benichif562ac32017-09-04 13:24:43 +090087
88 long now = System.currentTimeMillis();
89 String prefix = "iface:wlan0";
90 int[] uids = { 10001, 10002, 10004, 1000, 10052, 10023, 10002, 10123, 10004 };
91 for (int uid : uids) {
92 mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now);
93 }
94
Hugo Benichi380a0632017-10-20 09:25:29 +090095 String[] events2 = remove(listNetdEvent(), baseline);
Hugo Benichi60c9f632017-09-05 13:34:48 +090096 int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line
97 assertEquals(expectedLength2, events2.length);
98 assertContains(events2[0], "WakeupStats");
99 assertContains(events2[0], "wlan0");
Hugo Benichif562ac32017-09-04 13:24:43 +0900100 for (int i = 0; i < uids.length; i++) {
Hugo Benichi60c9f632017-09-05 13:34:48 +0900101 String got = events2[i+1];
102 assertContains(got, "WakeupEvent");
Hugo Benichif562ac32017-09-04 13:24:43 +0900103 assertContains(got, "wlan0");
104 assertContains(got, "uid: " + uids[i]);
105 }
106
107 int uid = 20000;
108 for (int i = 0; i < BUFFER_LENGTH * 2; i++) {
109 long ts = now + 10;
110 mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, ts);
111 }
112
Hugo Benichi380a0632017-10-20 09:25:29 +0900113 String[] events3 = remove(listNetdEvent(), baseline);
Hugo Benichi60c9f632017-09-05 13:34:48 +0900114 int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line
115 assertEquals(expectedLength3, events3.length);
116 assertContains(events2[0], "WakeupStats");
117 assertContains(events2[0], "wlan0");
118 for (int i = 1; i < expectedLength3; i++) {
119 String got = events3[i];
120 assertContains(got, "WakeupEvent");
Hugo Benichif562ac32017-09-04 13:24:43 +0900121 assertContains(got, "wlan0");
122 assertContains(got, "uid: " + uid);
123 }
124
125 uid = 45678;
126 mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now);
127
Hugo Benichi380a0632017-10-20 09:25:29 +0900128 String[] events4 = remove(listNetdEvent(), baseline);
Hugo Benichif562ac32017-09-04 13:24:43 +0900129 String lastEvent = events4[events4.length - 1];
Hugo Benichi60c9f632017-09-05 13:34:48 +0900130 assertContains(lastEvent, "WakeupEvent");
Hugo Benichif562ac32017-09-04 13:24:43 +0900131 assertContains(lastEvent, "wlan0");
132 assertContains(lastEvent, "uid: " + uid);
133 }
134
135 @Test
Hugo Benichi60c9f632017-09-05 13:34:48 +0900136 public void testWakeupStatsLogging() throws Exception {
137 wakeupEvent("wlan0", 1000);
138 wakeupEvent("rmnet0", 10123);
139 wakeupEvent("wlan0", 1000);
140 wakeupEvent("rmnet0", 10008);
141 wakeupEvent("wlan0", -1);
142 wakeupEvent("wlan0", 10008);
143 wakeupEvent("rmnet0", 1000);
144 wakeupEvent("wlan0", 10004);
145 wakeupEvent("wlan0", 1000);
146 wakeupEvent("wlan0", 0);
147 wakeupEvent("wlan0", -1);
148 wakeupEvent("rmnet0", 10052);
149 wakeupEvent("wlan0", 0);
150 wakeupEvent("rmnet0", 1000);
151 wakeupEvent("wlan0", 1010);
152
153 String got = flushStatistics();
154 String want = String.join("\n",
155 "dropped_events: 0",
156 "events <",
157 " if_name: \"\"",
158 " link_layer: 2",
159 " network_id: 0",
160 " time_ms: 0",
161 " transports: 0",
162 " wakeup_stats <",
163 " application_wakeups: 3",
164 " duration_sec: 0",
Hugo Benichi175b5742017-09-19 13:15:26 +0900165 " no_uid_wakeups: 0",
Hugo Benichi60c9f632017-09-05 13:34:48 +0900166 " non_application_wakeups: 0",
167 " root_wakeups: 0",
168 " system_wakeups: 2",
169 " total_wakeups: 5",
Hugo Benichi60c9f632017-09-05 13:34:48 +0900170 " >",
171 ">",
172 "events <",
173 " if_name: \"\"",
174 " link_layer: 4",
175 " network_id: 0",
176 " time_ms: 0",
177 " transports: 0",
178 " wakeup_stats <",
179 " application_wakeups: 2",
180 " duration_sec: 0",
Hugo Benichi175b5742017-09-19 13:15:26 +0900181 " no_uid_wakeups: 2",
Hugo Benichi60c9f632017-09-05 13:34:48 +0900182 " non_application_wakeups: 1",
183 " root_wakeups: 2",
184 " system_wakeups: 3",
185 " total_wakeups: 10",
Hugo Benichi60c9f632017-09-05 13:34:48 +0900186 " >",
187 ">",
188 "version: 2\n");
189 assertEquals(want, got);
190 }
191
192 @Test
Hugo Benichi5eb90532017-03-23 18:38:22 +0900193 public void testDnsLogging() throws Exception {
194 asyncDump(100);
195
Hugo Benichi2a5cfb92017-03-22 22:21:44 +0900196 dnsEvent(100, EVENT_GETADDRINFO, 0, 3456);
197 dnsEvent(100, EVENT_GETADDRINFO, 0, 267);
198 dnsEvent(100, EVENT_GETHOSTBYNAME, 22, 1230);
199 dnsEvent(100, EVENT_GETADDRINFO, 3, 45);
200 dnsEvent(100, EVENT_GETADDRINFO, 1, 2111);
201 dnsEvent(100, EVENT_GETADDRINFO, 0, 450);
202 dnsEvent(100, EVENT_GETHOSTBYNAME, 200, 638);
203 dnsEvent(100, EVENT_GETHOSTBYNAME, 178, 1300);
204 dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
205 dnsEvent(101, EVENT_GETADDRINFO, 0, 78);
206 dnsEvent(101, EVENT_GETADDRINFO, 0, 14);
207 dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 56);
208 dnsEvent(101, EVENT_GETADDRINFO, 0, 78);
209 dnsEvent(101, EVENT_GETADDRINFO, 0, 14);
Hugo Benichi5e055182016-06-01 08:50:38 +0900210
Hugo Benichi2a5cfb92017-03-22 22:21:44 +0900211 String got = flushStatistics();
212 String want = String.join("\n",
213 "dropped_events: 0",
214 "events <",
215 " if_name: \"\"",
Hugo Benichi2a5cfb92017-03-22 22:21:44 +0900216 " link_layer: 4",
217 " network_id: 100",
218 " time_ms: 0",
219 " transports: 2",
220 " dns_lookup_batch <",
221 " event_types: 1",
222 " event_types: 1",
223 " event_types: 2",
224 " event_types: 1",
225 " event_types: 1",
226 " event_types: 1",
227 " event_types: 2",
228 " event_types: 2",
Hugo Benichi4eccf782017-07-18 14:28:27 +0900229 " getaddrinfo_error_count: 0",
230 " getaddrinfo_query_count: 0",
231 " gethostbyname_error_count: 0",
232 " gethostbyname_query_count: 0",
Hugo Benichi2a5cfb92017-03-22 22:21:44 +0900233 " latencies_ms: 3456",
234 " latencies_ms: 267",
235 " latencies_ms: 1230",
236 " latencies_ms: 45",
237 " latencies_ms: 2111",
238 " latencies_ms: 450",
239 " latencies_ms: 638",
240 " latencies_ms: 1300",
241 " return_codes: 0",
242 " return_codes: 0",
243 " return_codes: 22",
244 " return_codes: 3",
245 " return_codes: 1",
246 " return_codes: 0",
247 " return_codes: 200",
248 " return_codes: 178",
249 " >",
250 ">",
251 "events <",
252 " if_name: \"\"",
253 " link_layer: 2",
254 " network_id: 101",
255 " time_ms: 0",
256 " transports: 1",
257 " dns_lookup_batch <",
258 " event_types: 1",
259 " event_types: 1",
260 " event_types: 1",
261 " event_types: 2",
262 " event_types: 1",
263 " event_types: 1",
Hugo Benichi4eccf782017-07-18 14:28:27 +0900264 " getaddrinfo_error_count: 0",
265 " getaddrinfo_query_count: 0",
266 " gethostbyname_error_count: 0",
267 " gethostbyname_query_count: 0",
Hugo Benichi2a5cfb92017-03-22 22:21:44 +0900268 " latencies_ms: 56",
269 " latencies_ms: 78",
270 " latencies_ms: 14",
271 " latencies_ms: 56",
272 " latencies_ms: 78",
273 " latencies_ms: 14",
274 " return_codes: 0",
275 " return_codes: 0",
276 " return_codes: 0",
277 " return_codes: 0",
278 " return_codes: 0",
279 " return_codes: 0",
280 " >",
281 ">",
282 "version: 2\n");
283 assertEquals(want, got);
Hugo Benichi5e055182016-06-01 08:50:38 +0900284 }
285
Hugo Benichi2a5cfb92017-03-22 22:21:44 +0900286 @Test
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900287 public void testConnectLogging() throws Exception {
Hugo Benichi5eb90532017-03-23 18:38:22 +0900288 asyncDump(100);
289
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900290 final int OK = 0;
291 Thread[] logActions = {
292 // ignored
Hugo Benichi5eb90532017-03-23 18:38:22 +0900293 connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4),
294 connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV6),
295 connectEventAction(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4),
296 connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
297 connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900298 // valid latencies
Hugo Benichi5eb90532017-03-23 18:38:22 +0900299 connectEventAction(100, OK, 110, EXAMPLE_IPV4),
300 connectEventAction(100, OK, 23, EXAMPLE_IPV4),
301 connectEventAction(100, OK, 45, EXAMPLE_IPV4),
302 connectEventAction(101, OK, 56, EXAMPLE_IPV4),
303 connectEventAction(101, OK, 523, EXAMPLE_IPV6),
304 connectEventAction(101, OK, 214, EXAMPLE_IPV6),
305 connectEventAction(101, OK, 67, EXAMPLE_IPV6),
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900306 // errors
Hugo Benichi5eb90532017-03-23 18:38:22 +0900307 connectEventAction(100, OsConstants.EPERM, 0, EXAMPLE_IPV4),
308 connectEventAction(101, OsConstants.EPERM, 0, EXAMPLE_IPV4),
309 connectEventAction(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4),
310 connectEventAction(100, OsConstants.EACCES, 0, EXAMPLE_IPV4),
311 connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV4),
312 connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV6),
313 connectEventAction(100, OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4),
314 connectEventAction(101, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4),
315 connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
316 connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
317 connectEventAction(101, OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4),
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900318 };
319
320 for (Thread t : logActions) {
321 t.start();
322 }
323 for (Thread t : logActions) {
324 t.join();
325 }
326
Hugo Benichi5eb90532017-03-23 18:38:22 +0900327 String got = flushStatistics();
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900328 String want = String.join("\n",
Hugo Benichi5eb90532017-03-23 18:38:22 +0900329 "dropped_events: 0",
330 "events <",
331 " if_name: \"\"",
332 " link_layer: 4",
333 " network_id: 100",
334 " time_ms: 0",
335 " transports: 2",
336 " connect_statistics <",
337 " connect_blocking_count: 3",
338 " connect_count: 6",
339 " errnos_counters <",
340 " key: 1",
341 " value: 1",
342 " >",
343 " errnos_counters <",
344 " key: 11",
345 " value: 1",
346 " >",
347 " errnos_counters <",
348 " key: 13",
349 " value: 1",
350 " >",
351 " errnos_counters <",
352 " key: 98",
353 " value: 1",
354 " >",
355 " errnos_counters <",
356 " key: 110",
357 " value: 2",
358 " >",
359 " ipv6_addr_count: 1",
360 " latencies_ms: 23",
361 " latencies_ms: 45",
362 " latencies_ms: 110",
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900363 " >",
Hugo Benichi5eb90532017-03-23 18:38:22 +0900364 ">",
365 "events <",
366 " if_name: \"\"",
367 " link_layer: 2",
368 " network_id: 101",
369 " time_ms: 0",
370 " transports: 1",
371 " connect_statistics <",
372 " connect_blocking_count: 4",
373 " connect_count: 6",
374 " errnos_counters <",
375 " key: 1",
376 " value: 1",
377 " >",
378 " errnos_counters <",
379 " key: 13",
380 " value: 2",
381 " >",
382 " errnos_counters <",
383 " key: 110",
384 " value: 1",
385 " >",
386 " errnos_counters <",
387 " key: 111",
388 " value: 1",
389 " >",
390 " ipv6_addr_count: 5",
391 " latencies_ms: 56",
392 " latencies_ms: 67",
393 " latencies_ms: 214",
394 " latencies_ms: 523",
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900395 " >",
Hugo Benichi5eb90532017-03-23 18:38:22 +0900396 ">",
397 "version: 2\n");
398 assertEquals(want, got);
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900399 }
400
Hugo Benichi5eb90532017-03-23 18:38:22 +0900401 Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) {
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900402 return new Thread(() -> {
403 try {
Hugo Benichi5eb90532017-03-23 18:38:22 +0900404 mNetdEventListenerService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900405 } catch (Exception e) {
406 fail(e.toString());
407 }
408 });
409 }
410
Hugo Benichi2a5cfb92017-03-22 22:21:44 +0900411 void dnsEvent(int netId, int type, int result, int latency) throws Exception {
412 mNetdEventListenerService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
413 }
414
Hugo Benichi60c9f632017-09-05 13:34:48 +0900415 void wakeupEvent(String iface, int uid) throws Exception {
416 String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
417 mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, 0);
418 }
419
Hugo Benichi5eb90532017-03-23 18:38:22 +0900420 void asyncDump(long durationMs) throws Exception {
Hugo Benichi2a5cfb92017-03-22 22:21:44 +0900421 final long stop = System.currentTimeMillis() + durationMs;
422 final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
Hugo Benichi5eb90532017-03-23 18:38:22 +0900423 new Thread(() -> {
Hugo Benichi2a5cfb92017-03-22 22:21:44 +0900424 while (System.currentTimeMillis() < stop) {
Hugo Benichi380a0632017-10-20 09:25:29 +0900425 mNetdEventListenerService.list(pw);
Michal Karpinskidd9bb4f2016-10-12 14:59:26 +0100426 }
Hugo Benichi5eb90532017-03-23 18:38:22 +0900427 }).start();
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900428 }
Hugo Benichi2a5cfb92017-03-22 22:21:44 +0900429
430 // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
431 String flushStatistics() throws Exception {
432 IpConnectivityMetrics metricsService =
433 new IpConnectivityMetrics(mock(Context.class), (ctx) -> 2000);
434 metricsService.mNetdListener = mNetdEventListenerService;
435
436 StringWriter buffer = new StringWriter();
437 PrintWriter writer = new PrintWriter(buffer);
438 metricsService.impl.dump(null, writer, new String[]{"flush"});
439 byte[] bytes = Base64.decode(buffer.toString(), Base64.DEFAULT);
Hugo Benichi5eb90532017-03-23 18:38:22 +0900440 IpConnectivityLog log = IpConnectivityLog.parseFrom(bytes);
441 for (IpConnectivityEvent ev : log.events) {
442 if (ev.getConnectStatistics() == null) {
443 continue;
444 }
445 // Sort repeated fields of connect() events arriving in non-deterministic order.
446 Arrays.sort(ev.getConnectStatistics().latenciesMs);
447 Arrays.sort(ev.getConnectStatistics().errnosCounters,
448 Comparator.comparingInt((p) -> p.key));
449 }
450 return log.toString();
Hugo Benichi2a5cfb92017-03-22 22:21:44 +0900451 }
Hugo Benichif562ac32017-09-04 13:24:43 +0900452
453 String[] listNetdEvent() throws Exception {
454 StringWriter buffer = new StringWriter();
455 PrintWriter writer = new PrintWriter(buffer);
456 mNetdEventListenerService.list(writer);
457 return buffer.toString().split("\\n");
458 }
459
460 static void assertContains(String got, String want) {
461 assertTrue(got + " did not contain \"" + want + "\"", got.contains(want));
462 }
Hugo Benichi380a0632017-10-20 09:25:29 +0900463
464 static <T> T[] remove(T[] array, T[] filtered) {
465 List<T> c = Arrays.asList(filtered);
466 int next = 0;
467 for (int i = 0; i < array.length; i++) {
468 if (c.contains(array[i])) {
469 continue;
470 }
471 array[next++] = array[i];
472 }
473 return Arrays.copyOf(array, next);
474 }
Hugo Benichi5e055182016-06-01 08:50:38 +0900475}