diff options
4 files changed, 204 insertions, 42 deletions
| diff --git a/location/java/android/location/Country.java b/location/java/android/location/Country.java index 939bd4af109c..7c1485d88a3e 100755 --- a/location/java/android/location/Country.java +++ b/location/java/android/location/Country.java @@ -18,6 +18,7 @@ package android.location;  import android.os.Parcel;  import android.os.Parcelable; +import android.os.SystemClock;  import java.util.Locale; @@ -58,8 +59,14 @@ public class Country implements Parcelable {      private final int mSource;      private int mHashCode; + +    /** +     * Time that this object was created (which we assume to be the time that the source was +     * consulted). This time is in milliseconds since boot up. +     */ +    private final long mTimestamp; +      /** -     *       * @param countryIso the ISO 3166-1 two letters country code.       * @param source where the countryIso came from, could be one of below       *        values @@ -78,11 +85,23 @@ public class Country implements Parcelable {          }          mCountryIso = countryIso.toUpperCase(Locale.US);          mSource = source; +        mTimestamp = SystemClock.elapsedRealtime(); +    } + +    private Country(final String countryIso, final int source, long timestamp) { +        if (countryIso == null || source < COUNTRY_SOURCE_NETWORK +                || source > COUNTRY_SOURCE_LOCALE) { +            throw new IllegalArgumentException(); +        } +        mCountryIso = countryIso.toUpperCase(Locale.US); +        mSource = source; +        mTimestamp = timestamp;      }      public Country(Country country) {          mCountryIso = country.mCountryIso;          mSource = country.mSource; +        mTimestamp = country.mTimestamp;      }      /** @@ -106,9 +125,17 @@ public class Country implements Parcelable {          return mSource;      } +    /** +     * Returns the time that this object was created (which we assume to be the time that the source +     * was consulted). +     */ +    public final long getTimestamp() { +        return mTimestamp; +    } +      public static final Parcelable.Creator<Country> CREATOR = new Parcelable.Creator<Country>() {          public Country createFromParcel(Parcel in) { -            return new Country(in.readString(), in.readInt()); +            return new Country(in.readString(), in.readInt(), in.readLong());          }          public Country[] newArray(int size) { @@ -123,8 +150,14 @@ public class Country implements Parcelable {      public void writeToParcel(Parcel parcel, int flags) {          parcel.writeString(mCountryIso);          parcel.writeInt(mSource); +        parcel.writeLong(mTimestamp);      } +    /** +     * Returns true if this {@link Country} is equivalent to the given object. This ignores +     * the timestamp value and just checks for equivalence of countryIso and source values. +     * Returns false otherwise. +     */      @Override      public boolean equals(Object object) {          if (object == this) { @@ -132,6 +165,7 @@ public class Country implements Parcelable {          }          if (object instanceof Country) {              Country c = (Country) object; +            // No need to check the equivalence of the timestamp              return mCountryIso.equals(c.getCountryIso()) && mSource == c.getSource();          }          return false; @@ -150,8 +184,8 @@ public class Country implements Parcelable {      }      /** -     * Compare the specified country to this country object ignoring the mSource -     * field, return true if the countryIso fields are equal +     * Compare the specified country to this country object ignoring the source +     * and timestamp fields, return true if the countryIso fields are equal       *       * @param country the country to compare       * @return true if the specified country's countryIso field is equal to this @@ -160,4 +194,9 @@ public class Country implements Parcelable {      public boolean equalsIgnoreSource(Country country) {          return country != null && mCountryIso.equals(country.getCountryIso());      } + +    @Override +    public String toString() { +        return "Country {ISO=" + mCountryIso + ", source=" + mSource + ", time=" + mTimestamp + "}"; +    }  } diff --git a/services/java/com/android/server/CountryDetectorService.java b/services/java/com/android/server/CountryDetectorService.java index 3081ebefd05a..ab61a3c003b8 100644 --- a/services/java/com/android/server/CountryDetectorService.java +++ b/services/java/com/android/server/CountryDetectorService.java @@ -16,6 +16,8 @@  package com.android.server; +import java.io.FileDescriptor; +import java.io.PrintWriter;  import java.util.HashMap;  import com.android.server.location.ComprehensiveCountryDetector; @@ -30,6 +32,8 @@ import android.os.IBinder;  import android.os.Looper;  import android.os.Process;  import android.os.RemoteException; +import android.util.PrintWriterPrinter; +import android.util.Printer;  import android.util.Slog;  /** @@ -75,7 +79,7 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run          }      } -    private final static String TAG = "CountryDetectorService"; +    private final static String TAG = "CountryDetector";      private final HashMap<IBinder, Receiver> mReceivers;      private final Context mContext; @@ -201,4 +205,20 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run      boolean isSystemReady() {          return mSystemReady;      } + +    @Override +    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { +        try { +            final Printer p = new PrintWriterPrinter(fout); +            p.println("CountryDetectorService state:"); +            p.println("  Number of listeners=" + mReceivers.keySet().size()); +            if (mCountryDetector == null) { +                p.println("  ComprehensiveCountryDetector not initialized"); +            } else { +                p.println("  " + mCountryDetector.toString()); +            } +        } catch (Exception e) { +            Slog.e(TAG, "Failed to dump CountryDetectorService: ", e); +        } +    }  } diff --git a/services/java/com/android/server/location/ComprehensiveCountryDetector.java b/services/java/com/android/server/location/ComprehensiveCountryDetector.java index bb9e60f6e072..2d6a148012de 100755 --- a/services/java/com/android/server/location/ComprehensiveCountryDetector.java +++ b/services/java/com/android/server/location/ComprehensiveCountryDetector.java @@ -20,16 +20,19 @@ import android.content.Context;  import android.location.Country;  import android.location.CountryListener;  import android.location.Geocoder; +import android.os.SystemClock;  import android.provider.Settings;  import android.telephony.PhoneStateListener;  import android.telephony.ServiceState;  import android.telephony.TelephonyManager;  import android.text.TextUtils; +import android.util.Log;  import android.util.Slog;  import java.util.Locale;  import java.util.Timer;  import java.util.TimerTask; +import java.util.concurrent.ConcurrentLinkedQueue;  /**   * This class is used to detect the country where the user is. The sources of @@ -55,10 +58,15 @@ import java.util.TimerTask;   */  public class ComprehensiveCountryDetector extends CountryDetectorBase { -    private final static String TAG = "ComprehensiveCountryDetector"; +    private final static String TAG = "CountryDetector";      /* package */ static final boolean DEBUG = false;      /** +     * Max length of logs to maintain for debugging. +     */ +    private static final int MAX_LENGTH_DEBUG_LOGS = 20; + +    /**       * The refresh interval when the location based country was used       */      private final static long LOCATION_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 1 day @@ -66,26 +74,58 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase {      protected CountryDetectorBase mLocationBasedCountryDetector;      protected Timer mLocationRefreshTimer; -    private final int mPhoneType;      private Country mCountry; -    private TelephonyManager mTelephonyManager; +    private final TelephonyManager mTelephonyManager;      private Country mCountryFromLocation;      private boolean mStopped = false; -    private ServiceState mLastState; -    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { -        @Override -        public void onServiceStateChanged(ServiceState serviceState) { -            // TODO: Find out how often we will be notified, if this method is called too -            // many times, let's consider querying the network. -            Slog.d(TAG, "onServiceStateChanged"); -            // We only care the state change -            if (mLastState == null || mLastState.getState() != serviceState.getState()) { -                detectCountry(true, true); -                mLastState = new ServiceState(serviceState); -            } -        } -    }; +    private PhoneStateListener mPhoneStateListener; + +    /** +     * List of the most recent country state changes for debugging. This should have +     * a max length of MAX_LENGTH_LOGS. +     */ +    private final ConcurrentLinkedQueue<Country> mDebugLogs = new ConcurrentLinkedQueue<Country>(); + +    /** +     * 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 Country mLastCountryAddedToLogs; + +    /** +     * Object used to synchronize access to {@link #mLastCountryAddedToLogs}. Be careful if +     * using it to synchronize anything else in this file. +     */ +    private final Object mObject = new Object(); + +    /** +     * Start time of the current session for which the detector has been active. +     */ +    private long mStartTime; + +    /** +     * Stop time of the most recent session for which the detector was active. +     */ +    private long mStopTime; + +    /** +     * The sum of all the time intervals in which the detector was active. +     */ +    private long mTotalTime; + +    /** +     * Number of {@link PhoneStateListener#onServiceStateChanged(ServiceState state)} events that +     * have occurred for the current session for which the detector has been active. +     */ +    private int mCountServiceStateChanges; + +    /** +     * Total number of {@link PhoneStateListener#onServiceStateChanged(ServiceState state)} events +     * that have occurred for all time intervals in which the detector has been active. +     */ +    private int mTotalCountServiceStateChanges;      /**       * The listener for receiving the notification from LocationBasedCountryDetector. @@ -104,7 +144,6 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase {      public ComprehensiveCountryDetector(Context context) {          super(context);          mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); -        mPhoneType = mTelephonyManager.getPhoneType();      }      @Override @@ -115,6 +154,7 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase {      @Override      public void stop() { +        // Note: this method in this subclass called only by tests.          Slog.i(TAG, "Stop the detector.");          cancelLocationRefresh();          removePhoneStateListener(); @@ -138,17 +178,50 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase {          if (result == null) {              result = getLocaleCountry();          } +        addToLogs(result);          return result;      }      /** +     * Attempt to add this {@link Country} to the debug logs. +     */ +    private void addToLogs(Country country) { +        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 (Log.isLoggable(TAG, Log.DEBUG)) { +            Slog.d(TAG, country.toString()); +        } +        mDebugLogs.add(country); +    } + +    private boolean isNetworkCountryCodeAvailable() { +        // 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; +    } + +    /**       * @return the country from the mobile network.       */      protected Country getNetworkBasedCountry() {          String countryIso = null; -        // TODO: The document says the result may be unreliable on CDMA networks. Shall we use -        // it on CDMA phone? We may test the Android primarily used countries. -        if (mPhoneType == TelephonyManager.PHONE_TYPE_GSM) { +        if (isNetworkCountryCodeAvailable()) {              countryIso = mTelephonyManager.getNetworkCountryIso();              if (!TextUtils.isEmpty(countryIso)) {                  return new Country(countryIso, Country.COUNTRY_SOURCE_NETWORK); @@ -226,9 +299,14 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase {              removePhoneStateListener();              stopLocationBasedDetector();              cancelLocationRefresh(); +            mStopTime = SystemClock.elapsedRealtime(); +            mTotalTime += mStopTime;          } else if (prevListener == null) {              addPhoneStateListener();              detectCountry(false, true); +            mStartTime = SystemClock.elapsedRealtime(); +            mStopTime = 0; +            mCountServiceStateChanges = 0;          }      } @@ -316,9 +394,9 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase {      private void notifyIfCountryChanged(final Country country, final Country detectedCountry) {          if (detectedCountry != null && mListener != null                  && (country == null || !country.equals(detectedCountry))) { -            Slog.d(TAG, -                    "The country was changed from " + country != null ? country.getCountryIso() : -                        country + " to " + detectedCountry.getCountryIso()); +            if (Log.isLoggable(TAG, Log.DEBUG)) { +                Slog.d(TAG, "" + country + " --> " + detectedCountry); +            }              notifyListener(detectedCountry);          }      } @@ -356,20 +434,19 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase {      }      protected synchronized void addPhoneStateListener() { -        if (mPhoneStateListener == null && mPhoneType == TelephonyManager.PHONE_TYPE_GSM) { -            mLastState = null; +        if (mPhoneStateListener == null) {              mPhoneStateListener = new PhoneStateListener() {                  @Override                  public void onServiceStateChanged(ServiceState serviceState) { -                    // TODO: Find out how often we will be notified, if this -                    // method is called too -                    // many times, let's consider querying the network. -                    Slog.d(TAG, "onServiceStateChanged"); -                    // We only care the state change -                    if (mLastState == null || mLastState.getState() != serviceState.getState()) { -                        detectCountry(true, true); -                        mLastState = new 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); @@ -386,4 +463,30 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase {      protected boolean isGeoCoderImplemented() {          return Geocoder.isPresent();      } + +    @Override +    public String toString() { +        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(); +    }  } diff --git a/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java index 60677dff0d8d..5f5d6684f383 100755 --- a/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java +++ b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java @@ -213,7 +213,7 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase {          // QueryThread should be set to NULL          assertNull(detector.getQueryThread());          assertTrue(countryListener.notified()); -        assertEquals(countryListener.getCountry(), country); +        assertEquals("us", countryListener.getCountry().toLowerCase());      }      public void testFindingCountryCancelled() { @@ -238,7 +238,7 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase {          // QueryThread should be set to NULL          assertNull(detector.getQueryThread());          assertTrue(countryListener.notified()); -        assertEquals(countryListener.getCountry(), country); +        assertEquals("us", countryListener.getCountry().toLowerCase());      }      public void testFindingLocationCancelled() { @@ -339,7 +339,7 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase {          assertNull(detector.getQueryThread());          // CountryListener should be notified          assertTrue(countryListener.notified()); -        assertEquals(countryListener.getCountry(), country); +        assertEquals("us", countryListener.getCountry().toLowerCase());      }      private void waitForTimerReset(TestCountryDetector detector) { |