• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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