1 /* 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 package com.google.android.exoplayer2.upstream; 17 18 import android.content.BroadcastReceiver; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.IntentFilter; 22 import android.net.ConnectivityManager; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.util.SparseArray; 26 import androidx.annotation.Nullable; 27 import com.google.android.exoplayer2.C; 28 import com.google.android.exoplayer2.util.Assertions; 29 import com.google.android.exoplayer2.util.Clock; 30 import com.google.android.exoplayer2.util.EventDispatcher; 31 import com.google.android.exoplayer2.util.SlidingPercentile; 32 import com.google.android.exoplayer2.util.Util; 33 import java.lang.ref.WeakReference; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.HashMap; 37 import java.util.Map; 38 import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 39 40 /** 41 * Estimates bandwidth by listening to data transfers. 42 * 43 * <p>The bandwidth estimate is calculated using a {@link SlidingPercentile} and is updated each 44 * time a transfer ends. The initial estimate is based on the current operator's network country 45 * code or the locale of the user, as well as the network connection type. This can be configured in 46 * the {@link Builder}. 47 */ 48 public final class DefaultBandwidthMeter implements BandwidthMeter, TransferListener { 49 50 /** 51 * Country groups used to determine the default initial bitrate estimate. The group assignment for 52 * each country is an array of group indices for [Wifi, 2G, 3G, 4G]. 53 */ 54 public static final Map<String, int[]> DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS = 55 createInitialBitrateCountryGroupAssignment(); 56 57 /** Default initial Wifi bitrate estimate in bits per second. */ 58 public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI = 59 new long[] {5_700_000, 3_500_000, 2_000_000, 1_100_000, 470_000}; 60 61 /** Default initial 2G bitrate estimates in bits per second. */ 62 public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_2G = 63 new long[] {200_000, 148_000, 132_000, 115_000, 95_000}; 64 65 /** Default initial 3G bitrate estimates in bits per second. */ 66 public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_3G = 67 new long[] {2_200_000, 1_300_000, 970_000, 810_000, 490_000}; 68 69 /** Default initial 4G bitrate estimates in bits per second. */ 70 public static final long[] DEFAULT_INITIAL_BITRATE_ESTIMATES_4G = 71 new long[] {5_300_000, 3_200_000, 2_000_000, 1_400_000, 690_000}; 72 73 /** 74 * Default initial bitrate estimate used when the device is offline or the network type cannot be 75 * determined, in bits per second. 76 */ 77 public static final long DEFAULT_INITIAL_BITRATE_ESTIMATE = 1_000_000; 78 79 /** Default maximum weight for the sliding window. */ 80 public static final int DEFAULT_SLIDING_WINDOW_MAX_WEIGHT = 2000; 81 82 @Nullable private static DefaultBandwidthMeter singletonInstance; 83 84 /** Builder for a bandwidth meter. */ 85 public static final class Builder { 86 87 @Nullable private final Context context; 88 89 private SparseArray<Long> initialBitrateEstimates; 90 private int slidingWindowMaxWeight; 91 private Clock clock; 92 private boolean resetOnNetworkTypeChange; 93 94 /** 95 * Creates a builder with default parameters and without listener. 96 * 97 * @param context A context. 98 */ Builder(Context context)99 public Builder(Context context) { 100 // Handling of null is for backward compatibility only. 101 this.context = context == null ? null : context.getApplicationContext(); 102 initialBitrateEstimates = getInitialBitrateEstimatesForCountry(Util.getCountryCode(context)); 103 slidingWindowMaxWeight = DEFAULT_SLIDING_WINDOW_MAX_WEIGHT; 104 clock = Clock.DEFAULT; 105 resetOnNetworkTypeChange = true; 106 } 107 108 /** 109 * Sets the maximum weight for the sliding window. 110 * 111 * @param slidingWindowMaxWeight The maximum weight for the sliding window. 112 * @return This builder. 113 */ setSlidingWindowMaxWeight(int slidingWindowMaxWeight)114 public Builder setSlidingWindowMaxWeight(int slidingWindowMaxWeight) { 115 this.slidingWindowMaxWeight = slidingWindowMaxWeight; 116 return this; 117 } 118 119 /** 120 * Sets the initial bitrate estimate in bits per second that should be assumed when a bandwidth 121 * estimate is unavailable. 122 * 123 * @param initialBitrateEstimate The initial bitrate estimate in bits per second. 124 * @return This builder. 125 */ setInitialBitrateEstimate(long initialBitrateEstimate)126 public Builder setInitialBitrateEstimate(long initialBitrateEstimate) { 127 for (int i = 0; i < initialBitrateEstimates.size(); i++) { 128 initialBitrateEstimates.setValueAt(i, initialBitrateEstimate); 129 } 130 return this; 131 } 132 133 /** 134 * Sets the initial bitrate estimate in bits per second that should be assumed when a bandwidth 135 * estimate is unavailable and the current network connection is of the specified type. 136 * 137 * @param networkType The {@link C.NetworkType} this initial estimate is for. 138 * @param initialBitrateEstimate The initial bitrate estimate in bits per second. 139 * @return This builder. 140 */ setInitialBitrateEstimate( @.NetworkType int networkType, long initialBitrateEstimate)141 public Builder setInitialBitrateEstimate( 142 @C.NetworkType int networkType, long initialBitrateEstimate) { 143 initialBitrateEstimates.put(networkType, initialBitrateEstimate); 144 return this; 145 } 146 147 /** 148 * Sets the initial bitrate estimates to the default values of the specified country. The 149 * initial estimates are used when a bandwidth estimate is unavailable. 150 * 151 * @param countryCode The ISO 3166-1 alpha-2 country code of the country whose default bitrate 152 * estimates should be used. 153 * @return This builder. 154 */ setInitialBitrateEstimate(String countryCode)155 public Builder setInitialBitrateEstimate(String countryCode) { 156 initialBitrateEstimates = 157 getInitialBitrateEstimatesForCountry(Util.toUpperInvariant(countryCode)); 158 return this; 159 } 160 161 /** 162 * Sets the clock used to estimate bandwidth from data transfers. Should only be set for testing 163 * purposes. 164 * 165 * @param clock The clock used to estimate bandwidth from data transfers. 166 * @return This builder. 167 */ setClock(Clock clock)168 public Builder setClock(Clock clock) { 169 this.clock = clock; 170 return this; 171 } 172 173 /** 174 * Sets whether to reset if the network type changes. The default value is {@code true}. 175 * 176 * @param resetOnNetworkTypeChange Whether to reset if the network type changes. 177 * @return This builder. 178 */ setResetOnNetworkTypeChange(boolean resetOnNetworkTypeChange)179 public Builder setResetOnNetworkTypeChange(boolean resetOnNetworkTypeChange) { 180 this.resetOnNetworkTypeChange = resetOnNetworkTypeChange; 181 return this; 182 } 183 184 /** 185 * Builds the bandwidth meter. 186 * 187 * @return A bandwidth meter with the configured properties. 188 */ build()189 public DefaultBandwidthMeter build() { 190 return new DefaultBandwidthMeter( 191 context, 192 initialBitrateEstimates, 193 slidingWindowMaxWeight, 194 clock, 195 resetOnNetworkTypeChange); 196 } 197 getInitialBitrateEstimatesForCountry(String countryCode)198 private static SparseArray<Long> getInitialBitrateEstimatesForCountry(String countryCode) { 199 int[] groupIndices = getCountryGroupIndices(countryCode); 200 SparseArray<Long> result = new SparseArray<>(/* initialCapacity= */ 6); 201 result.append(C.NETWORK_TYPE_UNKNOWN, DEFAULT_INITIAL_BITRATE_ESTIMATE); 202 result.append(C.NETWORK_TYPE_WIFI, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]); 203 result.append(C.NETWORK_TYPE_2G, DEFAULT_INITIAL_BITRATE_ESTIMATES_2G[groupIndices[1]]); 204 result.append(C.NETWORK_TYPE_3G, DEFAULT_INITIAL_BITRATE_ESTIMATES_3G[groupIndices[2]]); 205 result.append(C.NETWORK_TYPE_4G, DEFAULT_INITIAL_BITRATE_ESTIMATES_4G[groupIndices[3]]); 206 // Assume default Wifi bitrate for Ethernet and 5G to prevent using the slower fallback. 207 result.append( 208 C.NETWORK_TYPE_ETHERNET, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]); 209 result.append(C.NETWORK_TYPE_5G, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]); 210 return result; 211 } 212 getCountryGroupIndices(String countryCode)213 private static int[] getCountryGroupIndices(String countryCode) { 214 @Nullable int[] groupIndices = DEFAULT_INITIAL_BITRATE_COUNTRY_GROUPS.get(countryCode); 215 // Assume median group if not found. 216 return groupIndices == null ? new int[] {2, 2, 2, 2} : groupIndices; 217 } 218 } 219 220 /** 221 * Returns a singleton instance of a {@link DefaultBandwidthMeter} with default configuration. 222 * 223 * @param context A {@link Context}. 224 * @return The singleton instance. 225 */ getSingletonInstance(Context context)226 public static synchronized DefaultBandwidthMeter getSingletonInstance(Context context) { 227 if (singletonInstance == null) { 228 singletonInstance = new DefaultBandwidthMeter.Builder(context).build(); 229 } 230 return singletonInstance; 231 } 232 233 private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000; 234 private static final int BYTES_TRANSFERRED_FOR_ESTIMATE = 512 * 1024; 235 236 @Nullable private final Context context; 237 private final SparseArray<Long> initialBitrateEstimates; 238 private final EventDispatcher<EventListener> eventDispatcher; 239 private final SlidingPercentile slidingPercentile; 240 private final Clock clock; 241 242 private int streamCount; 243 private long sampleStartTimeMs; 244 private long sampleBytesTransferred; 245 246 @C.NetworkType private int networkType; 247 private long totalElapsedTimeMs; 248 private long totalBytesTransferred; 249 private long bitrateEstimate; 250 private long lastReportedBitrateEstimate; 251 252 private boolean networkTypeOverrideSet; 253 @C.NetworkType private int networkTypeOverride; 254 255 /** @deprecated Use {@link Builder} instead. */ 256 @Deprecated DefaultBandwidthMeter()257 public DefaultBandwidthMeter() { 258 this( 259 /* context= */ null, 260 /* initialBitrateEstimates= */ new SparseArray<>(), 261 DEFAULT_SLIDING_WINDOW_MAX_WEIGHT, 262 Clock.DEFAULT, 263 /* resetOnNetworkTypeChange= */ false); 264 } 265 DefaultBandwidthMeter( @ullable Context context, SparseArray<Long> initialBitrateEstimates, int maxWeight, Clock clock, boolean resetOnNetworkTypeChange)266 private DefaultBandwidthMeter( 267 @Nullable Context context, 268 SparseArray<Long> initialBitrateEstimates, 269 int maxWeight, 270 Clock clock, 271 boolean resetOnNetworkTypeChange) { 272 this.context = context == null ? null : context.getApplicationContext(); 273 this.initialBitrateEstimates = initialBitrateEstimates; 274 this.eventDispatcher = new EventDispatcher<>(); 275 this.slidingPercentile = new SlidingPercentile(maxWeight); 276 this.clock = clock; 277 // Set the initial network type and bitrate estimate 278 networkType = context == null ? C.NETWORK_TYPE_UNKNOWN : Util.getNetworkType(context); 279 bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType); 280 // Register to receive connectivity actions if possible. 281 if (context != null && resetOnNetworkTypeChange) { 282 ConnectivityActionReceiver connectivityActionReceiver = 283 ConnectivityActionReceiver.getInstance(context); 284 connectivityActionReceiver.register(/* bandwidthMeter= */ this); 285 } 286 } 287 288 /** 289 * Overrides the network type. Handled in the same way as if the meter had detected a change from 290 * the current network type to the specified network type internally. 291 * 292 * <p>Applications should not normally call this method. It is intended for testing purposes. 293 * 294 * @param networkType The overriding network type. 295 */ setNetworkTypeOverride(@.NetworkType int networkType)296 public synchronized void setNetworkTypeOverride(@C.NetworkType int networkType) { 297 networkTypeOverride = networkType; 298 networkTypeOverrideSet = true; 299 onConnectivityAction(); 300 } 301 302 @Override getBitrateEstimate()303 public synchronized long getBitrateEstimate() { 304 return bitrateEstimate; 305 } 306 307 @Override getTransferListener()308 public TransferListener getTransferListener() { 309 return this; 310 } 311 312 @Override addEventListener(Handler eventHandler, EventListener eventListener)313 public void addEventListener(Handler eventHandler, EventListener eventListener) { 314 eventDispatcher.addListener(eventHandler, eventListener); 315 } 316 317 @Override removeEventListener(EventListener eventListener)318 public void removeEventListener(EventListener eventListener) { 319 eventDispatcher.removeListener(eventListener); 320 } 321 322 @Override onTransferInitializing(DataSource source, DataSpec dataSpec, boolean isNetwork)323 public void onTransferInitializing(DataSource source, DataSpec dataSpec, boolean isNetwork) { 324 // Do nothing. 325 } 326 327 @Override onTransferStart( DataSource source, DataSpec dataSpec, boolean isNetwork)328 public synchronized void onTransferStart( 329 DataSource source, DataSpec dataSpec, boolean isNetwork) { 330 if (!isTransferAtFullNetworkSpeed(dataSpec, isNetwork)) { 331 return; 332 } 333 if (streamCount == 0) { 334 sampleStartTimeMs = clock.elapsedRealtime(); 335 } 336 streamCount++; 337 } 338 339 @Override onBytesTransferred( DataSource source, DataSpec dataSpec, boolean isNetwork, int bytes)340 public synchronized void onBytesTransferred( 341 DataSource source, DataSpec dataSpec, boolean isNetwork, int bytes) { 342 if (!isTransferAtFullNetworkSpeed(dataSpec, isNetwork)) { 343 return; 344 } 345 sampleBytesTransferred += bytes; 346 } 347 348 @Override onTransferEnd(DataSource source, DataSpec dataSpec, boolean isNetwork)349 public synchronized void onTransferEnd(DataSource source, DataSpec dataSpec, boolean isNetwork) { 350 if (!isTransferAtFullNetworkSpeed(dataSpec, isNetwork)) { 351 return; 352 } 353 Assertions.checkState(streamCount > 0); 354 long nowMs = clock.elapsedRealtime(); 355 int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs); 356 totalElapsedTimeMs += sampleElapsedTimeMs; 357 totalBytesTransferred += sampleBytesTransferred; 358 if (sampleElapsedTimeMs > 0) { 359 float bitsPerSecond = (sampleBytesTransferred * 8000f) / sampleElapsedTimeMs; 360 slidingPercentile.addSample((int) Math.sqrt(sampleBytesTransferred), bitsPerSecond); 361 if (totalElapsedTimeMs >= ELAPSED_MILLIS_FOR_ESTIMATE 362 || totalBytesTransferred >= BYTES_TRANSFERRED_FOR_ESTIMATE) { 363 bitrateEstimate = (long) slidingPercentile.getPercentile(0.5f); 364 } 365 maybeNotifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); 366 sampleStartTimeMs = nowMs; 367 sampleBytesTransferred = 0; 368 } // Else any sample bytes transferred will be carried forward into the next sample. 369 streamCount--; 370 } 371 onConnectivityAction()372 private synchronized void onConnectivityAction() { 373 int networkType = 374 networkTypeOverrideSet 375 ? networkTypeOverride 376 : (context == null ? C.NETWORK_TYPE_UNKNOWN : Util.getNetworkType(context)); 377 if (this.networkType == networkType) { 378 return; 379 } 380 381 this.networkType = networkType; 382 if (networkType == C.NETWORK_TYPE_OFFLINE 383 || networkType == C.NETWORK_TYPE_UNKNOWN 384 || networkType == C.NETWORK_TYPE_OTHER) { 385 // It's better not to reset the bandwidth meter for these network types. 386 return; 387 } 388 389 // Reset the bitrate estimate and report it, along with any bytes transferred. 390 this.bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType); 391 long nowMs = clock.elapsedRealtime(); 392 int sampleElapsedTimeMs = streamCount > 0 ? (int) (nowMs - sampleStartTimeMs) : 0; 393 maybeNotifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); 394 395 // Reset the remainder of the state. 396 sampleStartTimeMs = nowMs; 397 sampleBytesTransferred = 0; 398 totalBytesTransferred = 0; 399 totalElapsedTimeMs = 0; 400 slidingPercentile.reset(); 401 } 402 maybeNotifyBandwidthSample( int elapsedMs, long bytesTransferred, long bitrateEstimate)403 private void maybeNotifyBandwidthSample( 404 int elapsedMs, long bytesTransferred, long bitrateEstimate) { 405 if (elapsedMs == 0 && bytesTransferred == 0 && bitrateEstimate == lastReportedBitrateEstimate) { 406 return; 407 } 408 lastReportedBitrateEstimate = bitrateEstimate; 409 eventDispatcher.dispatch( 410 listener -> listener.onBandwidthSample(elapsedMs, bytesTransferred, bitrateEstimate)); 411 } 412 getInitialBitrateEstimateForNetworkType(@.NetworkType int networkType)413 private long getInitialBitrateEstimateForNetworkType(@C.NetworkType int networkType) { 414 Long initialBitrateEstimate = initialBitrateEstimates.get(networkType); 415 if (initialBitrateEstimate == null) { 416 initialBitrateEstimate = initialBitrateEstimates.get(C.NETWORK_TYPE_UNKNOWN); 417 } 418 if (initialBitrateEstimate == null) { 419 initialBitrateEstimate = DEFAULT_INITIAL_BITRATE_ESTIMATE; 420 } 421 return initialBitrateEstimate; 422 } 423 isTransferAtFullNetworkSpeed(DataSpec dataSpec, boolean isNetwork)424 private static boolean isTransferAtFullNetworkSpeed(DataSpec dataSpec, boolean isNetwork) { 425 return isNetwork && !dataSpec.isFlagSet(DataSpec.FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED); 426 } 427 428 /* 429 * Note: This class only holds a weak reference to DefaultBandwidthMeter instances. It should not 430 * be made non-static, since doing so adds a strong reference (i.e. DefaultBandwidthMeter.this). 431 */ 432 private static class ConnectivityActionReceiver extends BroadcastReceiver { 433 434 private static @MonotonicNonNull ConnectivityActionReceiver staticInstance; 435 436 private final Handler mainHandler; 437 private final ArrayList<WeakReference<DefaultBandwidthMeter>> bandwidthMeters; 438 getInstance(Context context)439 public static synchronized ConnectivityActionReceiver getInstance(Context context) { 440 if (staticInstance == null) { 441 staticInstance = new ConnectivityActionReceiver(); 442 IntentFilter filter = new IntentFilter(); 443 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 444 context.registerReceiver(staticInstance, filter); 445 } 446 return staticInstance; 447 } 448 ConnectivityActionReceiver()449 private ConnectivityActionReceiver() { 450 mainHandler = new Handler(Looper.getMainLooper()); 451 bandwidthMeters = new ArrayList<>(); 452 } 453 register(DefaultBandwidthMeter bandwidthMeter)454 public synchronized void register(DefaultBandwidthMeter bandwidthMeter) { 455 removeClearedReferences(); 456 bandwidthMeters.add(new WeakReference<>(bandwidthMeter)); 457 // Simulate an initial update on the main thread (like the sticky broadcast we'd receive if 458 // we were to register a separate broadcast receiver for each bandwidth meter). 459 mainHandler.post(() -> updateBandwidthMeter(bandwidthMeter)); 460 } 461 462 @Override onReceive(Context context, Intent intent)463 public synchronized void onReceive(Context context, Intent intent) { 464 if (isInitialStickyBroadcast()) { 465 return; 466 } 467 removeClearedReferences(); 468 for (int i = 0; i < bandwidthMeters.size(); i++) { 469 WeakReference<DefaultBandwidthMeter> bandwidthMeterReference = bandwidthMeters.get(i); 470 DefaultBandwidthMeter bandwidthMeter = bandwidthMeterReference.get(); 471 if (bandwidthMeter != null) { 472 updateBandwidthMeter(bandwidthMeter); 473 } 474 } 475 } 476 updateBandwidthMeter(DefaultBandwidthMeter bandwidthMeter)477 private void updateBandwidthMeter(DefaultBandwidthMeter bandwidthMeter) { 478 bandwidthMeter.onConnectivityAction(); 479 } 480 removeClearedReferences()481 private void removeClearedReferences() { 482 for (int i = bandwidthMeters.size() - 1; i >= 0; i--) { 483 WeakReference<DefaultBandwidthMeter> bandwidthMeterReference = bandwidthMeters.get(i); 484 DefaultBandwidthMeter bandwidthMeter = bandwidthMeterReference.get(); 485 if (bandwidthMeter == null) { 486 bandwidthMeters.remove(i); 487 } 488 } 489 } 490 } 491 createInitialBitrateCountryGroupAssignment()492 private static Map<String, int[]> createInitialBitrateCountryGroupAssignment() { 493 HashMap<String, int[]> countryGroupAssignment = new HashMap<>(); 494 countryGroupAssignment.put("AD", new int[] {1, 1, 0, 0}); 495 countryGroupAssignment.put("AE", new int[] {1, 4, 4, 4}); 496 countryGroupAssignment.put("AF", new int[] {4, 4, 3, 3}); 497 countryGroupAssignment.put("AG", new int[] {3, 1, 0, 1}); 498 countryGroupAssignment.put("AI", new int[] {1, 0, 0, 3}); 499 countryGroupAssignment.put("AL", new int[] {1, 2, 0, 1}); 500 countryGroupAssignment.put("AM", new int[] {2, 2, 2, 2}); 501 countryGroupAssignment.put("AO", new int[] {3, 4, 2, 0}); 502 countryGroupAssignment.put("AR", new int[] {2, 3, 2, 2}); 503 countryGroupAssignment.put("AS", new int[] {3, 0, 4, 2}); 504 countryGroupAssignment.put("AT", new int[] {0, 3, 0, 0}); 505 countryGroupAssignment.put("AU", new int[] {0, 3, 0, 1}); 506 countryGroupAssignment.put("AW", new int[] {1, 1, 0, 3}); 507 countryGroupAssignment.put("AX", new int[] {0, 3, 0, 2}); 508 countryGroupAssignment.put("AZ", new int[] {3, 3, 3, 3}); 509 countryGroupAssignment.put("BA", new int[] {1, 1, 0, 1}); 510 countryGroupAssignment.put("BB", new int[] {0, 2, 0, 0}); 511 countryGroupAssignment.put("BD", new int[] {2, 1, 3, 3}); 512 countryGroupAssignment.put("BE", new int[] {0, 0, 0, 1}); 513 countryGroupAssignment.put("BF", new int[] {4, 4, 4, 1}); 514 countryGroupAssignment.put("BG", new int[] {0, 1, 0, 0}); 515 countryGroupAssignment.put("BH", new int[] {2, 1, 3, 4}); 516 countryGroupAssignment.put("BI", new int[] {4, 4, 4, 4}); 517 countryGroupAssignment.put("BJ", new int[] {4, 4, 4, 4}); 518 countryGroupAssignment.put("BL", new int[] {1, 0, 2, 2}); 519 countryGroupAssignment.put("BM", new int[] {1, 2, 0, 0}); 520 countryGroupAssignment.put("BN", new int[] {4, 1, 3, 2}); 521 countryGroupAssignment.put("BO", new int[] {1, 2, 3, 2}); 522 countryGroupAssignment.put("BQ", new int[] {1, 1, 2, 4}); 523 countryGroupAssignment.put("BR", new int[] {2, 3, 3, 2}); 524 countryGroupAssignment.put("BS", new int[] {2, 1, 1, 4}); 525 countryGroupAssignment.put("BT", new int[] {3, 0, 3, 1}); 526 countryGroupAssignment.put("BW", new int[] {4, 4, 1, 2}); 527 countryGroupAssignment.put("BY", new int[] {0, 1, 1, 2}); 528 countryGroupAssignment.put("BZ", new int[] {2, 2, 2, 1}); 529 countryGroupAssignment.put("CA", new int[] {0, 3, 1, 3}); 530 countryGroupAssignment.put("CD", new int[] {4, 4, 2, 2}); 531 countryGroupAssignment.put("CF", new int[] {4, 4, 3, 0}); 532 countryGroupAssignment.put("CG", new int[] {3, 4, 2, 4}); 533 countryGroupAssignment.put("CH", new int[] {0, 0, 1, 0}); 534 countryGroupAssignment.put("CI", new int[] {3, 4, 3, 3}); 535 countryGroupAssignment.put("CK", new int[] {2, 4, 1, 0}); 536 countryGroupAssignment.put("CL", new int[] {1, 2, 2, 3}); 537 countryGroupAssignment.put("CM", new int[] {3, 4, 3, 1}); 538 countryGroupAssignment.put("CN", new int[] {2, 0, 2, 3}); 539 countryGroupAssignment.put("CO", new int[] {2, 3, 2, 2}); 540 countryGroupAssignment.put("CR", new int[] {2, 3, 4, 4}); 541 countryGroupAssignment.put("CU", new int[] {4, 4, 3, 1}); 542 countryGroupAssignment.put("CV", new int[] {2, 3, 1, 2}); 543 countryGroupAssignment.put("CW", new int[] {1, 1, 0, 0}); 544 countryGroupAssignment.put("CY", new int[] {1, 1, 0, 0}); 545 countryGroupAssignment.put("CZ", new int[] {0, 1, 0, 0}); 546 countryGroupAssignment.put("DE", new int[] {0, 1, 1, 3}); 547 countryGroupAssignment.put("DJ", new int[] {4, 3, 4, 1}); 548 countryGroupAssignment.put("DK", new int[] {0, 0, 1, 1}); 549 countryGroupAssignment.put("DM", new int[] {1, 0, 1, 3}); 550 countryGroupAssignment.put("DO", new int[] {3, 3, 4, 4}); 551 countryGroupAssignment.put("DZ", new int[] {3, 3, 4, 4}); 552 countryGroupAssignment.put("EC", new int[] {2, 3, 4, 3}); 553 countryGroupAssignment.put("EE", new int[] {0, 1, 0, 0}); 554 countryGroupAssignment.put("EG", new int[] {3, 4, 2, 2}); 555 countryGroupAssignment.put("EH", new int[] {2, 0, 3, 3}); 556 countryGroupAssignment.put("ER", new int[] {4, 2, 2, 0}); 557 countryGroupAssignment.put("ES", new int[] {0, 1, 1, 1}); 558 countryGroupAssignment.put("ET", new int[] {4, 4, 4, 0}); 559 countryGroupAssignment.put("FI", new int[] {0, 0, 1, 0}); 560 countryGroupAssignment.put("FJ", new int[] {3, 0, 3, 3}); 561 countryGroupAssignment.put("FK", new int[] {3, 4, 2, 2}); 562 countryGroupAssignment.put("FM", new int[] {4, 0, 4, 0}); 563 countryGroupAssignment.put("FO", new int[] {0, 0, 0, 0}); 564 countryGroupAssignment.put("FR", new int[] {1, 0, 3, 1}); 565 countryGroupAssignment.put("GA", new int[] {3, 3, 2, 2}); 566 countryGroupAssignment.put("GB", new int[] {0, 1, 3, 3}); 567 countryGroupAssignment.put("GD", new int[] {2, 0, 4, 4}); 568 countryGroupAssignment.put("GE", new int[] {1, 1, 1, 4}); 569 countryGroupAssignment.put("GF", new int[] {2, 3, 4, 4}); 570 countryGroupAssignment.put("GG", new int[] {0, 1, 0, 0}); 571 countryGroupAssignment.put("GH", new int[] {3, 3, 2, 2}); 572 countryGroupAssignment.put("GI", new int[] {0, 0, 0, 1}); 573 countryGroupAssignment.put("GL", new int[] {2, 2, 0, 2}); 574 countryGroupAssignment.put("GM", new int[] {4, 4, 3, 4}); 575 countryGroupAssignment.put("GN", new int[] {3, 4, 4, 2}); 576 countryGroupAssignment.put("GP", new int[] {2, 1, 1, 4}); 577 countryGroupAssignment.put("GQ", new int[] {4, 4, 3, 0}); 578 countryGroupAssignment.put("GR", new int[] {1, 1, 0, 2}); 579 countryGroupAssignment.put("GT", new int[] {3, 3, 3, 3}); 580 countryGroupAssignment.put("GU", new int[] {1, 2, 4, 4}); 581 countryGroupAssignment.put("GW", new int[] {4, 4, 4, 1}); 582 countryGroupAssignment.put("GY", new int[] {3, 2, 1, 1}); 583 countryGroupAssignment.put("HK", new int[] {0, 2, 3, 4}); 584 countryGroupAssignment.put("HN", new int[] {3, 2, 3, 2}); 585 countryGroupAssignment.put("HR", new int[] {1, 1, 0, 1}); 586 countryGroupAssignment.put("HT", new int[] {4, 4, 4, 4}); 587 countryGroupAssignment.put("HU", new int[] {0, 1, 0, 0}); 588 countryGroupAssignment.put("ID", new int[] {3, 2, 3, 4}); 589 countryGroupAssignment.put("IE", new int[] {1, 0, 1, 1}); 590 countryGroupAssignment.put("IL", new int[] {0, 0, 2, 3}); 591 countryGroupAssignment.put("IM", new int[] {0, 0, 0, 1}); 592 countryGroupAssignment.put("IN", new int[] {2, 2, 4, 4}); 593 countryGroupAssignment.put("IO", new int[] {4, 2, 2, 2}); 594 countryGroupAssignment.put("IQ", new int[] {3, 3, 4, 2}); 595 countryGroupAssignment.put("IR", new int[] {3, 0, 2, 2}); 596 countryGroupAssignment.put("IS", new int[] {0, 1, 0, 0}); 597 countryGroupAssignment.put("IT", new int[] {1, 0, 1, 2}); 598 countryGroupAssignment.put("JE", new int[] {1, 0, 0, 1}); 599 countryGroupAssignment.put("JM", new int[] {2, 3, 3, 1}); 600 countryGroupAssignment.put("JO", new int[] {1, 2, 1, 2}); 601 countryGroupAssignment.put("JP", new int[] {0, 2, 1, 1}); 602 countryGroupAssignment.put("KE", new int[] {3, 4, 4, 3}); 603 countryGroupAssignment.put("KG", new int[] {1, 1, 2, 2}); 604 countryGroupAssignment.put("KH", new int[] {1, 0, 4, 4}); 605 countryGroupAssignment.put("KI", new int[] {4, 4, 4, 4}); 606 countryGroupAssignment.put("KM", new int[] {4, 3, 2, 3}); 607 countryGroupAssignment.put("KN", new int[] {1, 0, 1, 3}); 608 countryGroupAssignment.put("KP", new int[] {4, 2, 4, 2}); 609 countryGroupAssignment.put("KR", new int[] {0, 1, 1, 1}); 610 countryGroupAssignment.put("KW", new int[] {2, 3, 1, 1}); 611 countryGroupAssignment.put("KY", new int[] {1, 1, 0, 1}); 612 countryGroupAssignment.put("KZ", new int[] {1, 2, 2, 3}); 613 countryGroupAssignment.put("LA", new int[] {2, 2, 1, 1}); 614 countryGroupAssignment.put("LB", new int[] {3, 2, 0, 0}); 615 countryGroupAssignment.put("LC", new int[] {1, 1, 0, 0}); 616 countryGroupAssignment.put("LI", new int[] {0, 0, 2, 4}); 617 countryGroupAssignment.put("LK", new int[] {2, 1, 2, 3}); 618 countryGroupAssignment.put("LR", new int[] {3, 4, 3, 1}); 619 countryGroupAssignment.put("LS", new int[] {3, 3, 2, 0}); 620 countryGroupAssignment.put("LT", new int[] {0, 0, 0, 0}); 621 countryGroupAssignment.put("LU", new int[] {0, 0, 0, 0}); 622 countryGroupAssignment.put("LV", new int[] {0, 0, 0, 0}); 623 countryGroupAssignment.put("LY", new int[] {4, 4, 4, 4}); 624 countryGroupAssignment.put("MA", new int[] {2, 1, 2, 1}); 625 countryGroupAssignment.put("MC", new int[] {0, 0, 0, 1}); 626 countryGroupAssignment.put("MD", new int[] {1, 1, 0, 0}); 627 countryGroupAssignment.put("ME", new int[] {1, 2, 1, 2}); 628 countryGroupAssignment.put("MF", new int[] {1, 1, 1, 1}); 629 countryGroupAssignment.put("MG", new int[] {3, 4, 2, 2}); 630 countryGroupAssignment.put("MH", new int[] {4, 0, 2, 4}); 631 countryGroupAssignment.put("MK", new int[] {1, 0, 0, 0}); 632 countryGroupAssignment.put("ML", new int[] {4, 4, 2, 0}); 633 countryGroupAssignment.put("MM", new int[] {3, 3, 1, 2}); 634 countryGroupAssignment.put("MN", new int[] {2, 3, 2, 3}); 635 countryGroupAssignment.put("MO", new int[] {0, 0, 4, 4}); 636 countryGroupAssignment.put("MP", new int[] {0, 2, 4, 4}); 637 countryGroupAssignment.put("MQ", new int[] {2, 1, 1, 4}); 638 countryGroupAssignment.put("MR", new int[] {4, 2, 4, 2}); 639 countryGroupAssignment.put("MS", new int[] {1, 2, 3, 3}); 640 countryGroupAssignment.put("MT", new int[] {0, 1, 0, 0}); 641 countryGroupAssignment.put("MU", new int[] {2, 2, 3, 4}); 642 countryGroupAssignment.put("MV", new int[] {4, 3, 0, 2}); 643 countryGroupAssignment.put("MW", new int[] {3, 2, 1, 0}); 644 countryGroupAssignment.put("MX", new int[] {2, 4, 4, 3}); 645 countryGroupAssignment.put("MY", new int[] {2, 2, 3, 3}); 646 countryGroupAssignment.put("MZ", new int[] {3, 3, 2, 1}); 647 countryGroupAssignment.put("NA", new int[] {3, 3, 2, 1}); 648 countryGroupAssignment.put("NC", new int[] {2, 0, 3, 3}); 649 countryGroupAssignment.put("NE", new int[] {4, 4, 4, 3}); 650 countryGroupAssignment.put("NF", new int[] {1, 2, 2, 2}); 651 countryGroupAssignment.put("NG", new int[] {3, 4, 3, 1}); 652 countryGroupAssignment.put("NI", new int[] {3, 3, 4, 4}); 653 countryGroupAssignment.put("NL", new int[] {0, 2, 3, 3}); 654 countryGroupAssignment.put("NO", new int[] {0, 1, 1, 0}); 655 countryGroupAssignment.put("NP", new int[] {2, 2, 2, 2}); 656 countryGroupAssignment.put("NR", new int[] {4, 0, 3, 1}); 657 countryGroupAssignment.put("NZ", new int[] {0, 0, 1, 2}); 658 countryGroupAssignment.put("OM", new int[] {3, 2, 1, 3}); 659 countryGroupAssignment.put("PA", new int[] {1, 3, 3, 4}); 660 countryGroupAssignment.put("PE", new int[] {2, 3, 4, 4}); 661 countryGroupAssignment.put("PF", new int[] {2, 2, 0, 1}); 662 countryGroupAssignment.put("PG", new int[] {4, 3, 3, 1}); 663 countryGroupAssignment.put("PH", new int[] {3, 0, 3, 4}); 664 countryGroupAssignment.put("PK", new int[] {3, 3, 3, 3}); 665 countryGroupAssignment.put("PL", new int[] {1, 0, 1, 3}); 666 countryGroupAssignment.put("PM", new int[] {0, 2, 2, 0}); 667 countryGroupAssignment.put("PR", new int[] {1, 2, 3, 3}); 668 countryGroupAssignment.put("PS", new int[] {3, 3, 2, 4}); 669 countryGroupAssignment.put("PT", new int[] {1, 1, 0, 0}); 670 countryGroupAssignment.put("PW", new int[] {2, 1, 2, 0}); 671 countryGroupAssignment.put("PY", new int[] {2, 0, 2, 3}); 672 countryGroupAssignment.put("QA", new int[] {2, 2, 1, 2}); 673 countryGroupAssignment.put("RE", new int[] {1, 0, 2, 2}); 674 countryGroupAssignment.put("RO", new int[] {0, 1, 1, 2}); 675 countryGroupAssignment.put("RS", new int[] {1, 2, 0, 0}); 676 countryGroupAssignment.put("RU", new int[] {0, 1, 1, 1}); 677 countryGroupAssignment.put("RW", new int[] {4, 4, 2, 4}); 678 countryGroupAssignment.put("SA", new int[] {2, 2, 2, 1}); 679 countryGroupAssignment.put("SB", new int[] {4, 4, 3, 0}); 680 countryGroupAssignment.put("SC", new int[] {4, 2, 0, 1}); 681 countryGroupAssignment.put("SD", new int[] {4, 4, 4, 3}); 682 countryGroupAssignment.put("SE", new int[] {0, 1, 0, 0}); 683 countryGroupAssignment.put("SG", new int[] {0, 2, 3, 3}); 684 countryGroupAssignment.put("SH", new int[] {4, 4, 2, 3}); 685 countryGroupAssignment.put("SI", new int[] {0, 0, 0, 0}); 686 countryGroupAssignment.put("SJ", new int[] {2, 0, 2, 4}); 687 countryGroupAssignment.put("SK", new int[] {0, 1, 0, 0}); 688 countryGroupAssignment.put("SL", new int[] {4, 3, 3, 3}); 689 countryGroupAssignment.put("SM", new int[] {0, 0, 2, 4}); 690 countryGroupAssignment.put("SN", new int[] {3, 4, 4, 2}); 691 countryGroupAssignment.put("SO", new int[] {3, 4, 4, 3}); 692 countryGroupAssignment.put("SR", new int[] {2, 2, 1, 0}); 693 countryGroupAssignment.put("SS", new int[] {4, 3, 4, 3}); 694 countryGroupAssignment.put("ST", new int[] {3, 4, 2, 2}); 695 countryGroupAssignment.put("SV", new int[] {2, 3, 3, 4}); 696 countryGroupAssignment.put("SX", new int[] {2, 4, 1, 0}); 697 countryGroupAssignment.put("SY", new int[] {4, 3, 2, 1}); 698 countryGroupAssignment.put("SZ", new int[] {4, 4, 3, 4}); 699 countryGroupAssignment.put("TC", new int[] {1, 2, 1, 1}); 700 countryGroupAssignment.put("TD", new int[] {4, 4, 4, 2}); 701 countryGroupAssignment.put("TG", new int[] {3, 3, 1, 0}); 702 countryGroupAssignment.put("TH", new int[] {1, 3, 4, 4}); 703 countryGroupAssignment.put("TJ", new int[] {4, 4, 4, 4}); 704 countryGroupAssignment.put("TL", new int[] {4, 2, 4, 4}); 705 countryGroupAssignment.put("TM", new int[] {4, 1, 2, 2}); 706 countryGroupAssignment.put("TN", new int[] {2, 2, 1, 2}); 707 countryGroupAssignment.put("TO", new int[] {3, 3, 3, 1}); 708 countryGroupAssignment.put("TR", new int[] {2, 2, 1, 2}); 709 countryGroupAssignment.put("TT", new int[] {1, 3, 1, 2}); 710 countryGroupAssignment.put("TV", new int[] {4, 2, 2, 4}); 711 countryGroupAssignment.put("TW", new int[] {0, 0, 0, 0}); 712 countryGroupAssignment.put("TZ", new int[] {3, 3, 4, 3}); 713 countryGroupAssignment.put("UA", new int[] {0, 2, 1, 2}); 714 countryGroupAssignment.put("UG", new int[] {4, 3, 3, 2}); 715 countryGroupAssignment.put("US", new int[] {1, 1, 3, 3}); 716 countryGroupAssignment.put("UY", new int[] {2, 2, 1, 1}); 717 countryGroupAssignment.put("UZ", new int[] {2, 2, 2, 2}); 718 countryGroupAssignment.put("VA", new int[] {1, 2, 4, 2}); 719 countryGroupAssignment.put("VC", new int[] {2, 0, 2, 4}); 720 countryGroupAssignment.put("VE", new int[] {4, 4, 4, 3}); 721 countryGroupAssignment.put("VG", new int[] {3, 0, 1, 3}); 722 countryGroupAssignment.put("VI", new int[] {1, 1, 4, 4}); 723 countryGroupAssignment.put("VN", new int[] {0, 2, 4, 4}); 724 countryGroupAssignment.put("VU", new int[] {4, 1, 3, 1}); 725 countryGroupAssignment.put("WS", new int[] {3, 3, 3, 2}); 726 countryGroupAssignment.put("XK", new int[] {1, 2, 1, 0}); 727 countryGroupAssignment.put("YE", new int[] {4, 4, 4, 3}); 728 countryGroupAssignment.put("YT", new int[] {2, 2, 2, 3}); 729 countryGroupAssignment.put("ZA", new int[] {2, 4, 2, 2}); 730 countryGroupAssignment.put("ZM", new int[] {3, 2, 2, 1}); 731 countryGroupAssignment.put("ZW", new int[] {3, 3, 2, 1}); 732 return Collections.unmodifiableMap(countryGroupAssignment); 733 } 734 } 735