1 /* 2 * Copyright 2022 Google LLC 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 package com.google.android.libraries.mobiledatadownload.monitor; 17 18 import static com.google.common.truth.Truth.assertThat; 19 import static com.google.common.util.concurrent.MoreExecutors.directExecutor; 20 import static java.util.concurrent.TimeUnit.MILLISECONDS; 21 import static java.util.concurrent.TimeUnit.SECONDS; 22 23 import android.content.Context; 24 import android.net.ConnectivityManager; 25 import android.net.NetworkInfo.DetailedState; 26 import android.net.Uri; 27 import android.os.Build; 28 import androidx.test.core.app.ApplicationProvider; 29 import com.google.mobiledatadownload.internal.MetadataProto.FileGroupLoggingState; 30 import com.google.mobiledatadownload.internal.MetadataProto.GroupKey; 31 import com.google.android.libraries.mobiledatadownload.file.common.testing.TemporaryUri; 32 import com.google.android.libraries.mobiledatadownload.file.spi.Monitor; 33 import com.google.android.libraries.mobiledatadownload.internal.logging.LoggingStateStore; 34 import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource; 35 import com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies; 36 import com.google.common.base.Optional; 37 import java.util.List; 38 import java.util.Random; 39 import java.util.concurrent.Executor; 40 import org.junit.Before; 41 import org.junit.Rule; 42 import org.junit.Test; 43 import org.junit.runner.RunWith; 44 import org.robolectric.RobolectricTestRunner; 45 import org.robolectric.Shadows; 46 import org.robolectric.shadows.ShadowNetworkInfo; 47 48 @RunWith(RobolectricTestRunner.class) 49 public class NetworkUsageMonitorTest { 50 51 private static final Executor executor = directExecutor(); 52 private static final String GROUP_NAME_1 = "group-name-1"; 53 private static final String OWNER_PACKAGE_1 = "owner-package-1"; 54 private static final String VARIANT_ID_1 = "variant-id-1"; 55 private static final int VERSION_NUMBER_1 = 1; 56 private static final int BUILD_ID_1 = 123; 57 58 private static final String GROUP_NAME_2 = "group-name-2"; 59 private static final String OWNER_PACKAGE_2 = "owner-package-2"; 60 private static final String VARIANT_ID_2 = "variant-id-2"; 61 62 private static final int VERSION_NUMBER_2 = 2; 63 private static final int BUILD_ID_2 = 456; 64 65 private static final String FILE_URI_1 = 66 "android://com.google.android.gms/files/datadownload/shared/public/file_1"; 67 68 // Note: We can't make those android uris static variable since the Uri.parse will fail 69 // with initialization. 70 private final Uri uri1 = Uri.parse(FILE_URI_1); 71 72 private static final String FILE_URI_2 = 73 "android://com.google.android.gms/files/datadownload/shared/public/file_2"; 74 private final Uri uri2 = Uri.parse(FILE_URI_2); 75 76 private static final String FILE_URI_3 = 77 "android://com.google.android.gms/files/datadownload/shared/public/file_3"; 78 private final Uri uri3 = Uri.parse(FILE_URI_3); 79 80 private NetworkUsageMonitor networkUsageMonitor; 81 private LoggingStateStore loggingStateStore; 82 private Context context; 83 private final FakeTimeSource clock = new FakeTimeSource(); 84 85 ConnectivityManager connectivityManager; 86 87 @Rule public final TemporaryUri tmpUri = new TemporaryUri(); 88 89 @Before setUp()90 public void setUp() throws Exception { 91 context = ApplicationProvider.getApplicationContext(); 92 93 loggingStateStore = 94 MddTestDependencies.LoggingStateStoreImpl.SHARED_PREFERENCES.loggingStateStore( 95 context, Optional.absent(), new FakeTimeSource(), executor, new Random()); 96 97 // TODO(b/177015303): use builder when available 98 networkUsageMonitor = new NetworkUsageMonitor(context, clock); 99 100 this.connectivityManager = 101 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 102 } 103 setNetworkConnectivityType(int networkConnectivityType)104 private void setNetworkConnectivityType(int networkConnectivityType) { 105 Shadows.shadowOf(connectivityManager) 106 .setActiveNetworkInfo( 107 ShadowNetworkInfo.newInstance( 108 DetailedState.CONNECTED, 109 networkConnectivityType, 110 0 /* subtype */, 111 true /* isAvailable */, 112 true /* isConnected */)); 113 } 114 115 @Test testBytesWritten()116 public void testBytesWritten() throws Exception { 117 // Setup 2 FileGroups: 118 // FileGroup1: file1 and file2. 119 // FileGroup2: file3. 120 121 GroupKey groupKey1 = 122 GroupKey.newBuilder() 123 .setOwnerPackage(OWNER_PACKAGE_1) 124 .setGroupName(GROUP_NAME_1) 125 .setVariantId(VARIANT_ID_1) 126 .build(); 127 networkUsageMonitor.monitorUri( 128 uri1, groupKey1, BUILD_ID_1, VARIANT_ID_1, VERSION_NUMBER_1, loggingStateStore); 129 networkUsageMonitor.monitorUri( 130 uri2, groupKey1, BUILD_ID_1, VARIANT_ID_1, VERSION_NUMBER_1, loggingStateStore); 131 132 GroupKey groupKey2 = 133 GroupKey.newBuilder() 134 .setOwnerPackage(OWNER_PACKAGE_2) 135 .setGroupName(GROUP_NAME_2) 136 .setVariantId(VARIANT_ID_2) 137 .build(); 138 139 networkUsageMonitor.monitorUri( 140 uri3, groupKey2, BUILD_ID_2, VARIANT_ID_2, VERSION_NUMBER_2, loggingStateStore); 141 142 Monitor.OutputMonitor outputMonitor1 = networkUsageMonitor.monitorWrite(uri1); 143 Monitor.OutputMonitor outputMonitor2 = networkUsageMonitor.monitorWrite(uri2); 144 Monitor.OutputMonitor outputMonitor3 = networkUsageMonitor.monitorWrite(uri3); 145 146 // outputMonitor1 is same as outputMonitor2 since they both monitor for FileGroup1. 147 assertThat(outputMonitor1).isSameInstanceAs(outputMonitor2); 148 assertThat(outputMonitor1).isNotSameInstanceAs(outputMonitor3); 149 150 // First we have WIFI connection. 151 // Downloaded 1 bytes on WIFI for uri1 152 setNetworkConnectivityType(ConnectivityManager.TYPE_WIFI); 153 outputMonitor1.bytesWritten(new byte[1], 0, 1); 154 155 // Downloaded 2 bytes on WIFI for uri1 156 outputMonitor1.bytesWritten(new byte[2], 0, 2); 157 158 // Downloaded 4 bytes on WIFI for uri2 159 outputMonitor2.bytesWritten(new byte[4], 0, 4); 160 161 // Downloaded 8 bytes on WIFI for uri3 162 outputMonitor3.bytesWritten(new byte[8], 0, 8); 163 164 // Then we have CELLULAR connection. 165 // Downloaded 16 bytes on CELLULAR for uri1 166 setNetworkConnectivityType(ConnectivityManager.TYPE_MOBILE); 167 outputMonitor1.bytesWritten(new byte[16], 0, 16); 168 169 // Downloaded 32 bytes on CELLULAR for uri2 170 outputMonitor2.bytesWritten(new byte[32], 0, 32); 171 172 // Downloaded 64 bytes on CELLULAR for uri3 173 outputMonitor3.bytesWritten(new byte[64], 0, 64); 174 175 // close() will trigger saving counters to LoggingStateStore. 176 outputMonitor1.close(); 177 outputMonitor2.close(); 178 outputMonitor3.close(); 179 180 // await executors idle here if we switch from directExecutor... 181 182 List<FileGroupLoggingState> allLoggingState = loggingStateStore.getAndResetAllDataUsage().get(); 183 184 assertThat(allLoggingState) 185 .containsExactly( 186 FileGroupLoggingState.newBuilder() 187 .setGroupKey(groupKey1) 188 .setBuildId(BUILD_ID_1) 189 .setVariantId(VARIANT_ID_1) 190 .setFileGroupVersionNumber(VERSION_NUMBER_1) 191 .setCellularUsage(16 + 32) 192 .setWifiUsage(1 + 2 + 4) 193 .build(), 194 FileGroupLoggingState.newBuilder() 195 .setGroupKey(groupKey2) 196 .setBuildId(BUILD_ID_2) 197 .setVariantId(VARIANT_ID_2) 198 .setFileGroupVersionNumber(VERSION_NUMBER_2) 199 .setCellularUsage(64) 200 .setWifiUsage(8) 201 .build()); 202 } 203 204 @Test testBytesWritten_multipleVersions()205 public void testBytesWritten_multipleVersions() throws Exception { 206 // Setup 2 versions of a FileGroup: 207 // FileGroup v1: file1 and file2. 208 // FileGroup v2: file2 and file3. 209 GroupKey groupKey1 = 210 GroupKey.newBuilder() 211 .setOwnerPackage(OWNER_PACKAGE_1) 212 .setGroupName(GROUP_NAME_1) 213 .setVariantId(VARIANT_ID_1) 214 .build(); 215 networkUsageMonitor.monitorUri( 216 uri1, groupKey1, BUILD_ID_1, VARIANT_ID_1, VERSION_NUMBER_1, loggingStateStore); 217 networkUsageMonitor.monitorUri( 218 uri2, groupKey1, BUILD_ID_1, VARIANT_ID_1, VERSION_NUMBER_1, loggingStateStore); 219 220 GroupKey groupKey2 = 221 GroupKey.newBuilder() 222 .setOwnerPackage(OWNER_PACKAGE_2) 223 .setGroupName(GROUP_NAME_2) 224 .setVariantId(VARIANT_ID_2) 225 .build(); 226 227 // This would update uri2 to belong to FileGroup v2. 228 networkUsageMonitor.monitorUri( 229 uri2, groupKey2, BUILD_ID_2, VARIANT_ID_2, VERSION_NUMBER_2, loggingStateStore); 230 networkUsageMonitor.monitorUri( 231 uri3, groupKey2, BUILD_ID_2, VARIANT_ID_2, VERSION_NUMBER_2, loggingStateStore); 232 233 Monitor.OutputMonitor outputMonitor1 = networkUsageMonitor.monitorWrite(uri1); 234 Monitor.OutputMonitor outputMonitor2 = networkUsageMonitor.monitorWrite(uri2); 235 Monitor.OutputMonitor outputMonitor3 = networkUsageMonitor.monitorWrite(uri3); 236 237 // outputMonitor2 is same as outputMonitor3 since they both monitor for the same version of the 238 // same FileGroup. 239 assertThat(outputMonitor1).isNotSameInstanceAs(outputMonitor2); 240 assertThat(outputMonitor2).isSameInstanceAs(outputMonitor3); 241 242 // First we have WIFI connection. 243 // Downloaded 1 bytes on WIFI for uri1 244 setNetworkConnectivityType(ConnectivityManager.TYPE_WIFI); 245 outputMonitor1.bytesWritten(new byte[1], 0, 1); 246 247 // Downloaded 2 bytes on WIFI for uri1 248 outputMonitor1.bytesWritten(new byte[2], 0, 2); 249 250 // Downloaded 4 bytes on WIFI for uri2 251 outputMonitor2.bytesWritten(new byte[4], 0, 4); 252 253 // Downloaded 8 bytes on WIFI for uri3 254 outputMonitor3.bytesWritten(new byte[8], 0, 8); 255 256 // Then we have CELLULAR connection. 257 // Downloaded 16 bytes on CELLULAR for uri1 258 setNetworkConnectivityType(ConnectivityManager.TYPE_MOBILE); 259 outputMonitor1.bytesWritten(new byte[16], 0, 16); 260 261 // Downloaded 32 bytes on CELLULAR for uri2 262 outputMonitor2.bytesWritten(new byte[32], 0, 32); 263 264 // Downloaded 64 bytes on CELLULAR for uri3 265 outputMonitor3.bytesWritten(new byte[64], 0, 64); 266 267 // close() will trigger saving counters to SharedPreference. 268 outputMonitor1.close(); 269 outputMonitor2.close(); 270 outputMonitor3.close(); 271 272 List<FileGroupLoggingState> allLoggingState = loggingStateStore.getAndResetAllDataUsage().get(); 273 274 assertThat(allLoggingState) 275 .containsExactly( 276 FileGroupLoggingState.newBuilder() 277 .setGroupKey(groupKey1) 278 .setBuildId(BUILD_ID_1) 279 .setVariantId(VARIANT_ID_1) 280 .setFileGroupVersionNumber(VERSION_NUMBER_1) 281 .setCellularUsage(16) 282 .setWifiUsage(1 + 2) 283 .build(), 284 FileGroupLoggingState.newBuilder() 285 .setGroupKey(groupKey2) 286 .setBuildId(BUILD_ID_2) 287 .setVariantId(VARIANT_ID_2) 288 .setFileGroupVersionNumber(VERSION_NUMBER_2) 289 .setCellularUsage(32 + 64) 290 .setWifiUsage(4 + 8) 291 .build()); 292 } 293 294 @Test testBytesWritten_flush_interval()295 public void testBytesWritten_flush_interval() throws Exception { 296 // Setup 1 FileGroups: 297 // FileGroup1: file1 298 299 GroupKey groupKey1 = 300 GroupKey.newBuilder() 301 .setOwnerPackage(OWNER_PACKAGE_1) 302 .setGroupName(GROUP_NAME_1) 303 .setVariantId(VARIANT_ID_1) 304 .build(); 305 networkUsageMonitor.monitorUri( 306 uri1, groupKey1, BUILD_ID_1, VARIANT_ID_1, VERSION_NUMBER_1, loggingStateStore); 307 308 Monitor.OutputMonitor outputMonitor1 = networkUsageMonitor.monitorWrite(uri1); 309 310 // Advance time so counters are flushed 311 clock.advance(NetworkUsageMonitor.LOG_FREQUENCY_SECONDS + 1, SECONDS); 312 313 // Downloaded 1 bytes on WIFI for uri1 314 setNetworkConnectivityType(ConnectivityManager.TYPE_WIFI); 315 outputMonitor1.bytesWritten(new byte[1], 0, 1); 316 assertThat(loggingStateStore.getAndResetAllDataUsage().get()) 317 .containsExactly( 318 FileGroupLoggingState.newBuilder() 319 .setGroupKey(groupKey1) 320 .setBuildId(BUILD_ID_1) 321 .setVariantId(VARIANT_ID_1) 322 .setFileGroupVersionNumber(VERSION_NUMBER_1) 323 .setCellularUsage(0) 324 .setWifiUsage(1) 325 .build()); 326 327 // Advance the clock by < LOG_FREQUENCY_SECONDS 328 clock.advance(1, MILLISECONDS); 329 outputMonitor1.bytesWritten(new byte[2], 0, 2); 330 331 clock.advance(1, MILLISECONDS); 332 outputMonitor1.bytesWritten(new byte[16], 0, 4); 333 334 // Only the 1st and 2nd chunks were saved. 335 assertThat(loggingStateStore.getAndResetAllDataUsage().get()).isEmpty(); 336 337 // Advance the clock by > LOG_FREQUENCY_SECONDS 338 clock.advance(NetworkUsageMonitor.LOG_FREQUENCY_SECONDS + 1, SECONDS); 339 outputMonitor1.bytesWritten(new byte[16], 0, 8); 340 341 // All chunks were saved. 342 assertThat(loggingStateStore.getAndResetAllDataUsage().get()) 343 .containsExactly( 344 FileGroupLoggingState.newBuilder() 345 .setGroupKey(groupKey1) 346 .setBuildId(BUILD_ID_1) 347 .setVariantId(VARIANT_ID_1) 348 .setFileGroupVersionNumber(VERSION_NUMBER_1) 349 .setCellularUsage(0) 350 .setWifiUsage(2 + 4 + 8) 351 .build()); 352 } 353 354 @Test testBytesWritten_mix_write_append()355 public void testBytesWritten_mix_write_append() throws Exception { 356 // Setup 2 FileGroups: 357 // FileGroup1: file1 and file2. 358 // FileGroup2: file3. 359 360 GroupKey groupKey1 = 361 GroupKey.newBuilder() 362 .setOwnerPackage(OWNER_PACKAGE_1) 363 .setGroupName(GROUP_NAME_1) 364 .setVariantId(VARIANT_ID_1) 365 .build(); 366 networkUsageMonitor.monitorUri( 367 uri1, groupKey1, BUILD_ID_1, VARIANT_ID_1, VERSION_NUMBER_1, loggingStateStore); 368 networkUsageMonitor.monitorUri( 369 uri2, groupKey1, BUILD_ID_1, VARIANT_ID_1, VERSION_NUMBER_1, loggingStateStore); 370 371 GroupKey groupKey2 = 372 GroupKey.newBuilder() 373 .setOwnerPackage(OWNER_PACKAGE_2) 374 .setGroupName(GROUP_NAME_2) 375 .setVariantId(VARIANT_ID_2) 376 .build(); 377 378 networkUsageMonitor.monitorUri( 379 uri3, groupKey2, BUILD_ID_2, VARIANT_ID_2, VERSION_NUMBER_2, loggingStateStore); 380 381 Monitor.OutputMonitor outputMonitor1 = networkUsageMonitor.monitorWrite(uri1); 382 Monitor.OutputMonitor outputMonitor2 = networkUsageMonitor.monitorAppend(uri2); 383 Monitor.OutputMonitor outputMonitor3 = networkUsageMonitor.monitorAppend(uri3); 384 385 // outputMonitor1 is same as outputMonitor2 since they both monitor for FileGroup1. 386 assertThat(outputMonitor1).isSameInstanceAs(outputMonitor2); 387 assertThat(outputMonitor1).isNotSameInstanceAs(outputMonitor3); 388 389 // First we have WIFI connection. 390 // Downloaded 1 bytes on WIFI for uri1 391 setNetworkConnectivityType(ConnectivityManager.TYPE_WIFI); 392 outputMonitor1.bytesWritten(new byte[1], 0, 1); 393 394 // Downloaded 2 bytes on WIFI for uri1 395 outputMonitor1.bytesWritten(new byte[2], 0, 2); 396 397 // Downloaded 4 bytes on WIFI for uri2 398 outputMonitor2.bytesWritten(new byte[4], 0, 4); 399 400 // Downloaded 8 bytes on WIFI for uri3 401 outputMonitor3.bytesWritten(new byte[8], 0, 8); 402 403 // Then we have CELLULAR connection. 404 // Downloaded 16 bytes on CELLULAR for uri1 405 setNetworkConnectivityType(ConnectivityManager.TYPE_MOBILE); 406 outputMonitor1.bytesWritten(new byte[16], 0, 16); 407 408 // Downloaded 32 bytes on CELLULAR for uri2 409 outputMonitor2.bytesWritten(new byte[32], 0, 32); 410 411 // Downloaded 64 bytes on CELLULAR for uri3 412 outputMonitor3.bytesWritten(new byte[64], 0, 64); 413 414 // close() will trigger saving counters to SharedPreference. 415 outputMonitor1.close(); 416 outputMonitor2.close(); 417 outputMonitor3.close(); 418 419 // await executors idle here if we switch from directExecutor... 420 421 List<FileGroupLoggingState> allLoggingState = loggingStateStore.getAndResetAllDataUsage().get(); 422 423 assertThat(allLoggingState) 424 .containsExactly( 425 FileGroupLoggingState.newBuilder() 426 .setGroupKey(groupKey1) 427 .setBuildId(BUILD_ID_1) 428 .setVariantId(VARIANT_ID_1) 429 .setFileGroupVersionNumber(VERSION_NUMBER_1) 430 .setCellularUsage(16 + 32) 431 .setWifiUsage(1 + 2 + 4) 432 .build(), 433 FileGroupLoggingState.newBuilder() 434 .setGroupKey(groupKey2) 435 .setBuildId(BUILD_ID_2) 436 .setVariantId(VARIANT_ID_2) 437 .setFileGroupVersionNumber(VERSION_NUMBER_2) 438 .setCellularUsage(64) 439 .setWifiUsage(8) 440 .build()); 441 } 442 443 @Test getNetworkConnectivityType()444 public void getNetworkConnectivityType() { 445 setNetworkConnectivityType(ConnectivityManager.TYPE_WIFI); 446 assertThat(NetworkUsageMonitor.isCellular(context)).isFalse(); 447 448 setNetworkConnectivityType(ConnectivityManager.TYPE_ETHERNET); 449 assertThat(NetworkUsageMonitor.isCellular(context)).isFalse(); 450 451 setNetworkConnectivityType(ConnectivityManager.TYPE_VPN); 452 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 453 assertThat(NetworkUsageMonitor.isCellular(context)).isFalse(); 454 455 } else { 456 assertThat(NetworkUsageMonitor.isCellular(context)).isTrue(); 457 } 458 459 setNetworkConnectivityType(ConnectivityManager.TYPE_MOBILE); 460 assertThat(NetworkUsageMonitor.isCellular(context)).isTrue(); 461 462 // Fail to get NetworkInfo(return null) will return TYPE_WIFI. 463 Shadows.shadowOf(connectivityManager).setActiveNetworkInfo(null); 464 assertThat(NetworkUsageMonitor.isCellular(context)).isFalse(); 465 } 466 467 @Test testNotRegisterUri()468 public void testNotRegisterUri() { 469 // Creating the outputMonitor before registering the uri through monitorUri will return 470 // null. 471 Monitor.OutputMonitor outputMonitor = networkUsageMonitor.monitorWrite(uri1); 472 assertThat(outputMonitor).isNull(); 473 474 outputMonitor = networkUsageMonitor.monitorAppend(uri1); 475 assertThat(outputMonitor).isNull(); 476 } 477 } 478