FileDocCategorySizeDatePackage
ComprehensiveCountryDetector.javaAPI DocAndroid 5.1 API17949Thu Mar 12 22:22:42 GMT 2015com.android.server.location

ComprehensiveCountryDetector

public class ComprehensiveCountryDetector extends CountryDetectorBase
This class is used to detect the country where the user is. The sources of country are queried in order of reliability, like
  • Mobile network
  • Location
  • SIM's country
  • Phone's locale

Call the {@link #detectCountry()} to get the available country immediately.

To be notified of the future country change, using the {@link #setCountryListener(CountryListener)}

Using the {@link #stop()} to stop listening to the country change.

The country information will be refreshed every {@link #LOCATION_REFRESH_INTERVAL} once the location based country is used.

hide

Fields Summary
private static final String
TAG
static final boolean
DEBUG
private static final int
MAX_LENGTH_DEBUG_LOGS
Max length of logs to maintain for debugging.
private static final long
LOCATION_REFRESH_INTERVAL
The refresh interval when the location based country was used
protected CountryDetectorBase
mLocationBasedCountryDetector
protected Timer
mLocationRefreshTimer
private android.location.Country
mCountry
private final android.telephony.TelephonyManager
mTelephonyManager
private android.location.Country
mCountryFromLocation
private boolean
mStopped
private android.telephony.PhoneStateListener
mPhoneStateListener
private final ConcurrentLinkedQueue
mDebugLogs
List of the most recent country state changes for debugging. This should have a max length of MAX_LENGTH_LOGS.
private android.location.Country
mLastCountryAddedToLogs
Most recent {@link Country} result that was added to the debug logs {@link #mDebugLogs}. We keep track of this value to help prevent adding many of the same {@link Country} objects to the logs.
private final Object
mObject
Object used to synchronize access to {@link #mLastCountryAddedToLogs}. Be careful if using it to synchronize anything else in this file.
private long
mStartTime
Start time of the current session for which the detector has been active.
private long
mStopTime
Stop time of the most recent session for which the detector was active.
private long
mTotalTime
The sum of all the time intervals in which the detector was active.
private int
mCountServiceStateChanges
Number of {@link PhoneStateListener#onServiceStateChanged(ServiceState state)} events that have occurred for the current session for which the detector has been active.
private int
mTotalCountServiceStateChanges
Total number of {@link PhoneStateListener#onServiceStateChanged(ServiceState state)} events that have occurred for all time intervals in which the detector has been active.
private android.location.CountryListener
mLocationBasedCountryDetectionListener
The listener for receiving the notification from LocationBasedCountryDetector.
Constructors Summary
public ComprehensiveCountryDetector(android.content.Context context)


       
        super(context);
        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    
Methods Summary
protected synchronized voidaddPhoneStateListener()

        if (mPhoneStateListener == null) {
            mPhoneStateListener = new PhoneStateListener() {
                @Override
                public void onServiceStateChanged(ServiceState serviceState) {
                    mCountServiceStateChanges++;
                    mTotalCountServiceStateChanges++;

                    if (!isNetworkCountryCodeAvailable()) {
                        return;
                    }
                    if (DEBUG) Slog.d(TAG, "onServiceStateChanged: " + serviceState.getState());

                    detectCountry(true, true);
                }
            };
            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
        }
    
private voidaddToLogs(android.location.Country country)
Attempt to add this {@link Country} to the debug logs.

        if (country == null) {
            return;
        }
        // If the country (ISO and source) are the same as before, then there is no
        // need to add this country as another entry in the logs. Synchronize access to this
        // variable since multiple threads could be calling this method.
        synchronized (mObject) {
            if (mLastCountryAddedToLogs != null && mLastCountryAddedToLogs.equals(country)) {
                return;
            }
            mLastCountryAddedToLogs = country;
        }
        // Manually maintain a max limit for the list of logs
        if (mDebugLogs.size() >= MAX_LENGTH_DEBUG_LOGS) {
            mDebugLogs.poll();
        }
        if (DEBUG) {
            Slog.d(TAG, country.toString());
        }
        mDebugLogs.add(country);
    
private synchronized voidcancelLocationRefresh()
Cancel the scheduled refresh task if it exists

        if (mLocationRefreshTimer != null) {
            mLocationRefreshTimer.cancel();
            mLocationRefreshTimer = null;
        }
    
protected CountryDetectorBasecreateLocationBasedCountryDetector()

        return new LocationBasedCountryDetector(mContext);
    
private android.location.CountrydetectCountry(boolean notifyChange, boolean startLocationBasedDetection)

param
notifyChange indicates whether the listener should be notified the change of the country
param
startLocationBasedDetection indicates whether the LocationBasedCountryDetector could be started if the current country source is less reliable than the location.
return
the current available UserCountry

        Country country = getCountry();
        runAfterDetectionAsync(mCountry != null ? new Country(mCountry) : mCountry, country,
                notifyChange, startLocationBasedDetection);
        mCountry = country;
        return mCountry;
    
public android.location.CountrydetectCountry()

        // Don't start the LocationBasedCountryDetector if we have been stopped.
        return detectCountry(false, !mStopped);
    
private android.location.CountrygetCountry()
Get the country from different sources in order of the reliability.

        Country result = null;
        result = getNetworkBasedCountry();
        if (result == null) {
            result = getLastKnownLocationBasedCountry();
        }
        if (result == null) {
            result = getSimBasedCountry();
        }
        if (result == null) {
            result = getLocaleCountry();
        }
        addToLogs(result);
        return result;
    
protected android.location.CountrygetLastKnownLocationBasedCountry()

return
the cached location based country.

        return mCountryFromLocation;
    
protected android.location.CountrygetLocaleCountry()

return
the country from the system's locale.

        Locale defaultLocale = Locale.getDefault();
        if (defaultLocale != null) {
            return new Country(defaultLocale.getCountry(), Country.COUNTRY_SOURCE_LOCALE);
        } else {
            return null;
        }
    
protected android.location.CountrygetNetworkBasedCountry()

return
the country from the mobile network.

        String countryIso = null;
        if (isNetworkCountryCodeAvailable()) {
            countryIso = mTelephonyManager.getNetworkCountryIso();
            if (!TextUtils.isEmpty(countryIso)) {
                return new Country(countryIso, Country.COUNTRY_SOURCE_NETWORK);
            }
        }
        return null;
    
protected android.location.CountrygetSimBasedCountry()

return
the country from SIM card

        String countryIso = null;
        countryIso = mTelephonyManager.getSimCountryIso();
        if (!TextUtils.isEmpty(countryIso)) {
            return new Country(countryIso, Country.COUNTRY_SOURCE_SIM);
        }
        return null;
    
protected booleanisAirplaneModeOff()

        return Settings.Global.getInt(
                mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) == 0;
    
protected booleanisGeoCoderImplemented()

        return Geocoder.isPresent();
    
private booleanisNetworkCountryCodeAvailable()

        // On CDMA TelephonyManager.getNetworkCountryIso() just returns SIM country.  We don't want
        // to prioritize it over location based country, so ignore it.
        final int phoneType = mTelephonyManager.getPhoneType();
        if (DEBUG) Slog.v(TAG, "    phonetype=" + phoneType);
        return phoneType == TelephonyManager.PHONE_TYPE_GSM;
    
private voidnotifyIfCountryChanged(android.location.Country country, android.location.Country detectedCountry)
Notify the country change.

        if (detectedCountry != null && mListener != null
                && (country == null || !country.equals(detectedCountry))) {
            if (DEBUG) {
                Slog.d(TAG, "" + country + " --> " + detectedCountry);
            }
            notifyListener(detectedCountry);
        }
    
protected synchronized voidremovePhoneStateListener()

        if (mPhoneStateListener != null) {
            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
            mPhoneStateListener = null;
        }
    
voidrunAfterDetection(android.location.Country country, android.location.Country detectedCountry, boolean notifyChange, boolean startLocationBasedDetection)

        if (notifyChange) {
            notifyIfCountryChanged(country, detectedCountry);
        }
        if (DEBUG) {
            Slog.d(TAG, "startLocationBasedDetection=" + startLocationBasedDetection
                    + " detectCountry=" + (detectedCountry == null ? null :
                        "(source: " + detectedCountry.getSource()
                        + ", countryISO: " + detectedCountry.getCountryIso() + ")")
                    + " isAirplaneModeOff()=" + isAirplaneModeOff()
                    + " mListener=" + mListener
                    + " isGeoCoderImplemnted()=" + isGeoCoderImplemented());
        }

        if (startLocationBasedDetection && (detectedCountry == null
                || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION)
                && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) {
            if (DEBUG) Slog.d(TAG, "run startLocationBasedDetector()");
            // Start finding location when the source is less reliable than the
            // location and the airplane mode is off (as geocoder will not
            // work).
            // TODO : Shall we give up starting the detector within a
            // period of time?
            startLocationBasedDetector(mLocationBasedCountryDetectionListener);
        }
        if (detectedCountry == null
                || detectedCountry.getSource() >= Country.COUNTRY_SOURCE_LOCATION) {
            // Schedule the location refresh if the country source is
            // not more reliable than the location or no country is
            // found.
            // TODO: Listen to the preference change of GPS, Wifi etc,
            // and start detecting the country.
            scheduleLocationRefresh();
        } else {
            // Cancel the location refresh once the current source is
            // more reliable than the location.
            cancelLocationRefresh();
            stopLocationBasedDetector();
        }
    
protected voidrunAfterDetectionAsync(android.location.Country country, android.location.Country detectedCountry, boolean notifyChange, boolean startLocationBasedDetection)
Run the tasks in the service's thread.

        mHandler.post(new Runnable() {
            @Override
            public void run() {
                runAfterDetection(
                        country, detectedCountry, notifyChange, startLocationBasedDetection);
            }
        });
    
private synchronized voidscheduleLocationRefresh()
Schedule the next location refresh. We will do nothing if the scheduled task exists.

        if (mLocationRefreshTimer != null) return;
        if (DEBUG) {
            Slog.d(TAG, "start periodic location refresh timer. Interval: "
                    + LOCATION_REFRESH_INTERVAL);
        }
        mLocationRefreshTimer = new Timer();
        mLocationRefreshTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                if (DEBUG) {
                    Slog.d(TAG, "periodic location refresh event. Starts detecting Country code");
                }
                mLocationRefreshTimer = null;
                detectCountry(false, true);
            }
        }, LOCATION_REFRESH_INTERVAL);
    
public voidsetCountryListener(android.location.CountryListener listener)

        CountryListener prevListener = mListener;
        mListener = listener;
        if (mListener == null) {
            // Stop listening all services
            removePhoneStateListener();
            stopLocationBasedDetector();
            cancelLocationRefresh();
            mStopTime = SystemClock.elapsedRealtime();
            mTotalTime += mStopTime;
        } else if (prevListener == null) {
            addPhoneStateListener();
            detectCountry(false, true);
            mStartTime = SystemClock.elapsedRealtime();
            mStopTime = 0;
            mCountServiceStateChanges = 0;
        }
    
private synchronized voidstartLocationBasedDetector(android.location.CountryListener listener)
Find the country from LocationProvider.

        if (mLocationBasedCountryDetector != null) {
            return;
        }
        if (DEBUG) {
            Slog.d(TAG, "starts LocationBasedDetector to detect Country code via Location info "
                    + "(e.g. GPS)");
        }
        mLocationBasedCountryDetector = createLocationBasedCountryDetector();
        mLocationBasedCountryDetector.setCountryListener(listener);
        mLocationBasedCountryDetector.detectCountry();
    
public voidstop()

        // Note: this method in this subclass called only by tests.
        Slog.i(TAG, "Stop the detector.");
        cancelLocationRefresh();
        removePhoneStateListener();
        stopLocationBasedDetector();
        mListener = null;
        mStopped = true;
    
private synchronized voidstopLocationBasedDetector()

        if (DEBUG) {
            Slog.d(TAG, "tries to stop LocationBasedDetector "
                    + "(current detector: " + mLocationBasedCountryDetector + ")");
        }
        if (mLocationBasedCountryDetector != null) {
            mLocationBasedCountryDetector.stop();
            mLocationBasedCountryDetector = null;
        }
    
public java.lang.StringtoString()

        long currentTime = SystemClock.elapsedRealtime();
        long currentSessionLength = 0;
        StringBuilder sb = new StringBuilder();
        sb.append("ComprehensiveCountryDetector{");
        // The detector hasn't stopped yet --> still running
        if (mStopTime == 0) {
            currentSessionLength = currentTime - mStartTime;
            sb.append("timeRunning=" + currentSessionLength + ", ");
        } else {
            // Otherwise, it has already stopped, so take the last session
            sb.append("lastRunTimeLength=" + (mStopTime - mStartTime) + ", ");
        }
        sb.append("totalCountServiceStateChanges=" + mTotalCountServiceStateChanges + ", ");
        sb.append("currentCountServiceStateChanges=" + mCountServiceStateChanges + ", ");
        sb.append("totalTime=" + (mTotalTime + currentSessionLength) + ", ");
        sb.append("currentTime=" + currentTime + ", ");
        sb.append("countries=");
        for (Country country : mDebugLogs) {
            sb.append("\n   " + country.toString());
        }
        sb.append("}");
        return sb.toString();