Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b7342ace authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Tests for ThrottleService, NTP into TrustedTime.

Wrote initial suite of tests for ThrottleService, checking a variety
of edge cases.  Checks going over limits, updating policies, and reset
after cycle elapses.

Moved NTP code in ThrottleService into new TrustedTime interface,
which makes it easier to understand, and allows tests to provide custom
clocks.

Change-Id: I0d62b8b3a169516a2ab2d33025f6fe30dc792be8
parent cb95894b
Loading
Loading
Loading
Loading
+96 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.util;

import android.net.SntpClient;
import android.os.SystemClock;

/**
 * {@link TrustedTime} that connects with a remote NTP server as its remote
 * trusted time source.
 *
 * @hide
 */
public class NtpTrustedTime implements TrustedTime {
    private String mNtpServer;
    private long mNtpTimeout;

    private boolean mHasCache;
    private long mCachedNtpTime;
    private long mCachedNtpElapsedRealtime;
    private long mCachedNtpCertainty;

    public NtpTrustedTime() {
    }

    public void setNtpServer(String server, long timeout) {
        mNtpServer = server;
        mNtpTimeout = timeout;
    }

    /** {@inheritDoc} */
    public boolean forceRefresh() {
        if (mNtpServer == null) {
            throw new IllegalStateException("Missing NTP server");
        }

        final SntpClient client = new SntpClient();
        if (client.requestTime(mNtpServer, (int) mNtpTimeout)) {
            mHasCache = true;
            mCachedNtpTime = client.getNtpTime();
            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
            mCachedNtpCertainty = client.getRoundTripTime() / 2;
            return true;
        } else {
            return false;
        }
    }

    /** {@inheritDoc} */
    public boolean hasCache() {
        return mHasCache;
    }

    /** {@inheritDoc} */
    public long getCacheAge() {
        if (mHasCache) {
            return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;
        } else {
            return Long.MAX_VALUE;
        }
    }

    /** {@inheritDoc} */
    public long getCacheCertainty() {
        if (mHasCache) {
            return mCachedNtpCertainty;
        } else {
            return Long.MAX_VALUE;
        }
    }

    /** {@inheritDoc} */
    public long currentTimeMillis() {
        if (!mHasCache) {
            throw new IllegalStateException("Missing authoritative time source");
        }

        // current time is age after the last ntp cache; callers who
        // want fresh values will hit makeAuthoritative() first.
        return mCachedNtpTime + getCacheAge();
    }
}
+55 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.util;

/**
 * Interface that provides trusted time information, possibly coming from an NTP
 * server. Implementations may cache answers until {@link #forceRefresh()}.
 *
 * @hide
 */
public interface TrustedTime {
    /**
     * Force update with an external trusted time source, returning {@code true}
     * when successful.
     */
    public boolean forceRefresh();

    /**
     * Check if this instance has cached a response from a trusted time source.
     */
    public boolean hasCache();

    /**
     * Return time since last trusted time source contact, or
     * {@link Long#MAX_VALUE} if never contacted.
     */
    public long getCacheAge();

    /**
     * Return certainty of cached trusted time in milliseconds, or
     * {@link Long#MAX_VALUE} if never contacted. Smaller values are more
     * precise.
     */
    public long getCacheCertainty();

    /**
     * Return current time similar to {@link System#currentTimeMillis()},
     * possibly using a cached authoritative time source.
     */
    public long currentTimeMillis();
}
+104 −79
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.server;

import com.android.internal.R;
import com.android.internal.telephony.TelephonyProperties;

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -30,7 +33,6 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.INetworkManagementEventObserver;
import android.net.IThrottleManager;
import android.net.SntpClient;
import android.net.ThrottleManager;
import android.os.Binder;
import android.os.Environment;
@@ -47,10 +49,9 @@ import android.os.SystemProperties;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.NtpTrustedTime;
import android.util.Slog;

import com.android.internal.R;
import com.android.internal.telephony.TelephonyProperties;
import android.util.TrustedTime;

import java.io.BufferedWriter;
import java.io.File;
@@ -60,11 +61,11 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.GregorianCalendar;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

// TODO - add comments - reference the ThrottleManager for public API
public class ThrottleService extends IThrottleManager.Stub {
@@ -84,6 +85,11 @@ public class ThrottleService extends IThrottleManager.Stub {
    private static final int TESTING_RESET_PERIOD_SEC = 60 * 10;
    private static final long TESTING_THRESHOLD = 1 * 1024 * 1024;

    private static final long MAX_NTP_CACHE_AGE = 24 * 60 * 60 * 1000;
    private static final long MAX_NTP_FETCH_WAIT = 20 * 1000;

    private long mMaxNtpCacheAge = MAX_NTP_CACHE_AGE;

    private int mPolicyPollPeriodSec;
    private AtomicLong mPolicyThreshold;
    private AtomicInteger mPolicyThrottleValue;
@@ -121,10 +127,24 @@ public class ThrottleService extends IThrottleManager.Stub {
    private static final int THROTTLE_INDEX_UNTHROTTLED   =  0;

    private static final String PROPERTIES_FILE = "/etc/gps.conf";
    private String mNtpServer;
    private boolean mNtpActive;

    private Intent mPollStickyBroadcast;

    private TrustedTime mTime;

    private static INetworkManagementService getNetworkManagementService() {
        final IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
        return INetworkManagementService.Stub.asInterface(b);
    }

    public ThrottleService(Context context) {
        // TODO: move to using cached NtpTrustedTime
        this(context, getNetworkManagementService(), new NtpTrustedTime(),
                context.getResources().getString(R.string.config_datause_iface));
    }

    public ThrottleService(Context context, INetworkManagementService nmService, TrustedTime time,
            String iface) {
        if (VDBG) Slog.v(TAG, "Starting ThrottleService");
        mContext = context;

@@ -132,17 +152,15 @@ public class ThrottleService extends IThrottleManager.Stub {
        mPolicyThrottleValue = new AtomicInteger();
        mThrottleIndex = new AtomicInteger();

        mNtpActive = false;

        mIface = mContext.getResources().getString(R.string.config_datause_iface);
        mIface = iface;
        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
        Intent pollIntent = new Intent(ACTION_POLL, null);
        mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
        Intent resetIntent = new Intent(ACTION_RESET, null);
        mPendingResetIntent = PendingIntent.getBroadcast(mContext, RESET_REQUEST, resetIntent, 0);

        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
        mNMService = INetworkManagementService.Stub.asInterface(b);
        mNMService = nmService;
        mTime = time;

        mNotificationManager = (NotificationManager)mContext.getSystemService(
                Context.NOTIFICATION_SERVICE);
@@ -189,7 +207,7 @@ public class ThrottleService extends IThrottleManager.Stub {
            mMsg = msg;
        }

        void observe(Context context) {
        void register(Context context) {
            ContentResolver resolver = context.getContentResolver();
            resolver.registerContentObserver(Settings.Secure.getUriFor(
                    Settings.Secure.THROTTLE_POLLING_SEC), false, this);
@@ -207,6 +225,11 @@ public class ThrottleService extends IThrottleManager.Stub {
                    Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC), false, this);
        }

        void unregister(Context context) {
            final ContentResolver resolver = context.getContentResolver();
            resolver.unregisterContentObserver(this);
        }

        @Override
        public void onChange(boolean selfChange) {
            mHandler.obtainMessage(mMsg).sendToTarget();
@@ -220,7 +243,9 @@ public class ThrottleService extends IThrottleManager.Stub {
    }

    private long ntpToWallTime(long ntpTime) {
        long bestNow = getBestTime(true); // do it quickly
        // get time quickly without worrying about trusted state
        long bestNow = mTime.hasCache() ? mTime.currentTimeMillis()
                : System.currentTimeMillis();
        long localNow = System.currentTimeMillis();
        return localNow + (ntpTime - bestNow);
    }
@@ -300,7 +325,7 @@ public class ThrottleService extends IThrottleManager.Stub {
            new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget();
                    dispatchPoll();
                }
            }, new IntentFilter(ACTION_POLL));

@@ -308,7 +333,7 @@ public class ThrottleService extends IThrottleManager.Stub {
            new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget();
                    dispatchReset();
                }
            }, new IntentFilter(ACTION_RESET));

@@ -318,7 +343,10 @@ public class ThrottleService extends IThrottleManager.Stub {
            File file = new File(PROPERTIES_FILE);
            stream = new FileInputStream(file);
            properties.load(stream);
            mNtpServer = properties.getProperty("NTP_SERVER", null);
            final String ntpServer = properties.getProperty("NTP_SERVER", null);
            if (mTime instanceof NtpTrustedTime) {
                ((NtpTrustedTime) mTime).setNtpServer(ntpServer, MAX_NTP_FETCH_WAIT);
            }
        } catch (IOException e) {
            Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
        } finally {
@@ -343,9 +371,33 @@ public class ThrottleService extends IThrottleManager.Stub {
        }

        mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED);
        mSettingsObserver.observe(mContext);
        mSettingsObserver.register(mContext);
    }

    void shutdown() {
        // TODO: eventually connect with ShutdownThread to persist stats during
        // graceful shutdown.

        if (mThread != null) {
            mThread.quit();
        }

        if (mSettingsObserver != null) {
            mSettingsObserver.unregister(mContext);
        }

        if (mPollStickyBroadcast != null) {
            mContext.removeStickyBroadcast(mPollStickyBroadcast);
        }
    }

    void dispatchPoll() {
        mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget();
    }

    void dispatchReset() {
        mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget();
    }

    private static final int EVENT_REBOOT_RECOVERY = 0;
    private static final int EVENT_POLICY_CHANGED  = 1;
@@ -440,15 +492,17 @@ public class ThrottleService extends IThrottleManager.Stub {
            mPolicyNotificationsAllowedMask = Settings.Secure.getInt(mContext.getContentResolver(),
                    Settings.Secure.THROTTLE_NOTIFICATION_TYPE, defaultNotificationType);

            mMaxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(),
                    Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC, MAX_NTP_CACHE_AGE_SEC);
            final int maxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(),
                    Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC,
                    (int) (MAX_NTP_CACHE_AGE / 1000));
            mMaxNtpCacheAge = maxNtpCacheAgeSec * 1000;

            if (VDBG || (mPolicyThreshold.get() != 0)) {
                Slog.d(TAG, "onPolicyChanged testing=" + testing +", period=" +
                        mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold.get() +
                        ", value=" + mPolicyThrottleValue.get() + ", resetDay=" + mPolicyResetDay +
                        ", noteType=" + mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" +
                        mMaxNtpCacheAgeSec);
                        ", noteType=" + mPolicyNotificationsAllowedMask + ", mMaxNtpCacheAge=" +
                        mMaxNtpCacheAge);
            }

            // force updates
@@ -466,7 +520,13 @@ public class ThrottleService extends IThrottleManager.Stub {
            long now = SystemClock.elapsedRealtime();
            long next = now + mPolicyPollPeriodSec * 1000;

            checkForAuthoritativeTime();
            // when trusted cache outdated, try refreshing
            if (mTime.getCacheAge() > mMaxNtpCacheAge) {
                if (mTime.forceRefresh()) {
                    if (VDBG) Slog.d(TAG, "updated trusted time, reseting alarm");
                    dispatchReset();
                }
            }

            long incRead = 0;
            long incWrite = 0;
@@ -509,6 +569,7 @@ public class ThrottleService extends IThrottleManager.Stub {
            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_START, getPeriodStartTime(mIface));
            broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_END, getResetTime(mIface));
            mContext.sendStickyBroadcast(broadcast);
            mPollStickyBroadcast = broadcast;

            mAlarmManager.cancel(mPendingPollIntent);
            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
@@ -538,7 +599,8 @@ public class ThrottleService extends IThrottleManager.Stub {

            // have we spoken with an ntp server yet?
            // this is controversial, but we'd rather err towards not throttling
            if ((mNtpServer != null) && !mNtpActive) {
            if (!mTime.hasCache()) {
                Slog.w(TAG, "missing trusted time, skipping throttle check");
                return;
            }

@@ -697,9 +759,15 @@ public class ThrottleService extends IThrottleManager.Stub {
                        " bytes read and " + mRecorder.getPeriodTx(0) + " written");
            }

            long now = getBestTime(false);
            // when trusted cache outdated, try refreshing
            if (mTime.getCacheAge() > mMaxNtpCacheAge) {
                mTime.forceRefresh();
            }

            if (mNtpActive || (mNtpServer == null)) {
            // as long as we have a trusted time cache, we always reset alarms,
            // even if the refresh above failed.
            if (mTime.hasCache()) {
                final long now = mTime.currentTimeMillis();
                Calendar end = calculatePeriodEnd(now);
                Calendar start = calculatePeriodStart(end);

@@ -714,56 +782,11 @@ public class ThrottleService extends IThrottleManager.Stub {
                        SystemClock.elapsedRealtime() + offset,
                        mPendingResetIntent);
            } else {
                if (VDBG) Slog.d(TAG, "no authoritative time - not resetting period");
                if (VDBG) Slog.d(TAG, "no trusted time, not resetting period");
            }
        }
    }

    private void checkForAuthoritativeTime() {
        if (mNtpActive || (mNtpServer == null)) return;

        // will try to get the ntp time and switch to it if found.
        // will also cache the time so we don't fetch it repeatedly.
        getBestTime(false);
    }

    private static final int MAX_NTP_CACHE_AGE_SEC = 60 * 60 * 24; // 1 day
    private static final int MAX_NTP_FETCH_WAIT = 20 * 1000;
    private int mMaxNtpCacheAgeSec = MAX_NTP_CACHE_AGE_SEC;
    private long cachedNtp;
    private long cachedNtpTimestamp;

    // if the request is tied to UI and ANR's are a danger, request a fast result
    // the regular polling should have updated the cached time recently using the
    // slower method (!fast)
    private long getBestTime(boolean fast) {
        if (mNtpServer != null) {
            if (mNtpActive) {
                long ntpAge = SystemClock.elapsedRealtime() - cachedNtpTimestamp;
                if (ntpAge < mMaxNtpCacheAgeSec * 1000 || fast) {
                    if (VDBG) Slog.v(TAG, "using cached time");
                    return cachedNtp + ntpAge;
                }
            }
            SntpClient client = new SntpClient();
            if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT)) {
                cachedNtp = client.getNtpTime();
                cachedNtpTimestamp = SystemClock.elapsedRealtime();
                if (!mNtpActive) {
                    mNtpActive = true;
                    if (VDBG) Slog.d(TAG, "found Authoritative time - reseting alarm");
                    mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget();
                }
                if (VDBG) Slog.v(TAG, "using Authoritative time: " + cachedNtp);
                return cachedNtp;
            }
        }
        long time = System.currentTimeMillis();
        if (VDBG) Slog.v(TAG, "using User time: " + time);
        mNtpActive = false;
        return time;
    }

    // records bytecount data for a given time and accumulates it into larger time windows
    // for logging and other purposes
    //
@@ -929,7 +952,7 @@ public class ThrottleService extends IThrottleManager.Stub {
        private void checkAndDeleteLRUDataFile(File dir) {
            File[] files = dir.listFiles();

            if (files.length <= MAX_SIMS_SUPPORTED) return;
            if (files == null || files.length <= MAX_SIMS_SUPPORTED) return;
            if (DBG) Slog.d(TAG, "Too many data files");
            do {
                File oldest = null;
@@ -949,11 +972,13 @@ public class ThrottleService extends IThrottleManager.Stub {
            File newest = null;
            File[] files = dir.listFiles();

            if (files != null) {
                for (File f : files) {
                    if ((newest == null) || (newest.lastModified() < f.lastModified())) {
                        newest = f;
                    }
                }
            }
            if (newest == null) {
                newest = new File(dir, "temp");
            }
@@ -1126,7 +1151,7 @@ public class ThrottleService extends IThrottleManager.Stub {
                " seconds.");
        pw.println("Polling every " + mPolicyPollPeriodSec + " seconds");
        pw.println("Current Throttle Index is " + mThrottleIndex.get());
        pw.println("Max NTP Cache Age is " + mMaxNtpCacheAgeSec);
        pw.println("mMaxNtpCacheAge=" + mMaxNtpCacheAge);

        for (int i = 0; i < mRecorder.getPeriodCount(); i++) {
            pw.println(" Period[" + i + "] - read:" + mRecorder.getPeriodRx(i) + ", written:" +
+3 −1
Original line number Diff line number Diff line
@@ -7,7 +7,9 @@ LOCAL_MODULE_TAGS := tests
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)

LOCAL_STATIC_JAVA_LIBRARIES := easymocklib
LOCAL_STATIC_JAVA_LIBRARIES := \
    easymocklib \
    guava

LOCAL_JAVA_LIBRARIES := android.test.runner services

+4 −0
Original line number Diff line number Diff line
@@ -20,6 +20,10 @@
    <uses-permission android:name="android.permission.READ_LOGS" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
    
    <application>
        <uses-library android:name="android.test.runner" />
Loading