Cached NTP time for system services.
Uses NTP server and timeout from secure settings, or fallback to defaults in resources. Update various system services to use cached NTP time when fresh enough, or force updates as needed. Bug: 4517273 Change-Id: Ie1c4c4883836013d02ca0bbd850cf8949f93b34b
This commit is contained in:
@@ -3833,6 +3833,11 @@ public final class Settings {
|
||||
/** {@hide} */
|
||||
public static final String NETSTATS_TAG_MAX_HISTORY = "netstats_tag_max_history";
|
||||
|
||||
/** Preferred NTP server. {@hide} */
|
||||
public static final String NTP_SERVER = "ntp_server";
|
||||
/** Timeout in milliseconds to wait for NTP server. {@hide} */
|
||||
public static final String NTP_TIMEOUT = "ntp_timeout";
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
|
||||
@@ -16,41 +16,71 @@
|
||||
|
||||
package android.util;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.SntpClient;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.Settings;
|
||||
|
||||
/**
|
||||
* {@link TrustedTime} that connects with a remote NTP server as its remote
|
||||
* trusted time source.
|
||||
* {@link TrustedTime} that connects with a remote NTP server as its trusted
|
||||
* time source.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class NtpTrustedTime implements TrustedTime {
|
||||
private String mNtpServer;
|
||||
private long mNtpTimeout;
|
||||
private static final String TAG = "NtpTrustedTime";
|
||||
private static final boolean LOGD = false;
|
||||
|
||||
private static NtpTrustedTime sSingleton;
|
||||
|
||||
private final String mServer;
|
||||
private final long mTimeout;
|
||||
|
||||
private boolean mHasCache;
|
||||
private long mCachedNtpTime;
|
||||
private long mCachedNtpElapsedRealtime;
|
||||
private long mCachedNtpCertainty;
|
||||
|
||||
public NtpTrustedTime() {
|
||||
private NtpTrustedTime(String server, long timeout) {
|
||||
if (LOGD) Log.d(TAG, "creating NtpTrustedTime using " + server);
|
||||
mServer = server;
|
||||
mTimeout = timeout;
|
||||
}
|
||||
|
||||
public void setNtpServer(String server, long timeout) {
|
||||
mNtpServer = server;
|
||||
mNtpTimeout = timeout;
|
||||
public static synchronized NtpTrustedTime getInstance(Context context) {
|
||||
if (sSingleton == null) {
|
||||
final Resources res = context.getResources();
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
final String defaultServer = res.getString(
|
||||
com.android.internal.R.string.config_ntpServer);
|
||||
final long defaultTimeout = res.getInteger(
|
||||
com.android.internal.R.integer.config_ntpTimeout);
|
||||
|
||||
final String secureServer = Settings.Secure.getString(
|
||||
resolver, Settings.Secure.NTP_SERVER);
|
||||
final long timeout = Settings.Secure.getLong(
|
||||
resolver, Settings.Secure.NTP_TIMEOUT, defaultTimeout);
|
||||
|
||||
final String server = secureServer != null ? secureServer : defaultServer;
|
||||
sSingleton = new NtpTrustedTime(server, timeout);
|
||||
}
|
||||
|
||||
return sSingleton;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public boolean forceRefresh() {
|
||||
if (mNtpServer == null) {
|
||||
if (mServer == null) {
|
||||
// missing server, so no trusted time available
|
||||
return false;
|
||||
}
|
||||
|
||||
if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
|
||||
final SntpClient client = new SntpClient();
|
||||
if (client.requestTime(mNtpServer, (int) mNtpTimeout)) {
|
||||
if (client.requestTime(mServer, (int) mTimeout)) {
|
||||
mHasCache = true;
|
||||
mCachedNtpTime = client.getNtpTime();
|
||||
mCachedNtpElapsedRealtime = client.getNtpTimeReference();
|
||||
@@ -89,9 +119,19 @@ public class NtpTrustedTime implements TrustedTime {
|
||||
if (!mHasCache) {
|
||||
throw new IllegalStateException("Missing authoritative time source");
|
||||
}
|
||||
if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit");
|
||||
|
||||
// current time is age after the last ntp cache; callers who
|
||||
// want fresh values will hit makeAuthoritative() first.
|
||||
return mCachedNtpTime + getCacheAge();
|
||||
}
|
||||
|
||||
public long getCachedNtpTime() {
|
||||
if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
|
||||
return mCachedNtpTime;
|
||||
}
|
||||
|
||||
public long getCachedNtpTimeReference() {
|
||||
return mCachedNtpElapsedRealtime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,4 +660,9 @@
|
||||
extremely limited. -->
|
||||
<bool name="config_allowActionMenuItemTextWithIcon">false</bool>
|
||||
|
||||
<!-- Remote server that can provide NTP responses. -->
|
||||
<string translatable="false" name="config_ntpServer">pool.ntp.org</string>
|
||||
<!-- Timeout to wait for NTP server response. -->
|
||||
<integer name="config_ntpTimeout">20000</integer>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.server;
|
||||
|
||||
import com.android.internal.telephony.TelephonyIntents;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -28,7 +26,6 @@ import android.content.IntentFilter;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.SntpClient;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
@@ -36,12 +33,10 @@ import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.NtpTrustedTime;
|
||||
import android.util.TrustedTime;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
import com.android.internal.telephony.TelephonyIntents;
|
||||
|
||||
/**
|
||||
* Monitors the network time and updates the system time if it is out of sync
|
||||
@@ -68,14 +63,11 @@ public class NetworkTimeUpdateService {
|
||||
private static final long POLLING_INTERVAL_SHORTER_MS = 60 * 1000L; // 60 seconds
|
||||
/** Number of times to try again */
|
||||
private static final int TRY_AGAIN_TIMES_MAX = 3;
|
||||
/** How long to wait for the NTP server to respond. */
|
||||
private static final int MAX_NTP_FETCH_WAIT_MS = 20 * 1000;
|
||||
/** If the time difference is greater than this threshold, then update the time. */
|
||||
private static final int TIME_ERROR_THRESHOLD_MS = 5 * 1000;
|
||||
|
||||
private static final String ACTION_POLL =
|
||||
"com.android.server.NetworkTimeUpdateService.action.POLL";
|
||||
private static final String PROPERTIES_FILE = "/etc/gps.conf";
|
||||
private static int POLL_REQUEST = 0;
|
||||
|
||||
private static final long NOT_SET = -1;
|
||||
@@ -84,14 +76,14 @@ public class NetworkTimeUpdateService {
|
||||
private long mNitzZoneSetTime = NOT_SET;
|
||||
|
||||
private Context mContext;
|
||||
private TrustedTime mTime;
|
||||
|
||||
// NTP lookup is done on this thread and handler
|
||||
private Handler mHandler;
|
||||
private HandlerThread mThread;
|
||||
private AlarmManager mAlarmManager;
|
||||
private PendingIntent mPendingPollIntent;
|
||||
private SettingsObserver mSettingsObserver;
|
||||
// Address of the NTP server
|
||||
private String mNtpServer;
|
||||
// The last time that we successfully fetched the NTP time.
|
||||
private long mLastNtpFetchTime = NOT_SET;
|
||||
// Keeps track of how many quick attempts were made to fetch NTP time.
|
||||
@@ -101,6 +93,7 @@ public class NetworkTimeUpdateService {
|
||||
|
||||
public NetworkTimeUpdateService(Context context) {
|
||||
mContext = context;
|
||||
mTime = NtpTrustedTime.getInstance(context);
|
||||
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
|
||||
Intent pollIntent = new Intent(ACTION_POLL, null);
|
||||
mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
|
||||
@@ -108,12 +101,6 @@ public class NetworkTimeUpdateService {
|
||||
|
||||
/** Initialize the receivers and initiate the first NTP request */
|
||||
public void systemReady() {
|
||||
mNtpServer = getNtpServerAddress();
|
||||
if (mNtpServer == null) {
|
||||
Slog.e(TAG, "NTP server address not found, not syncing to NTP time");
|
||||
return;
|
||||
}
|
||||
|
||||
registerForTelephonyIntents();
|
||||
registerForAlarms();
|
||||
registerForConnectivityIntents();
|
||||
@@ -128,27 +115,6 @@ public class NetworkTimeUpdateService {
|
||||
mSettingsObserver.observe(mContext);
|
||||
}
|
||||
|
||||
private String getNtpServerAddress() {
|
||||
String serverAddress = null;
|
||||
FileInputStream stream = null;
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
File file = new File(PROPERTIES_FILE);
|
||||
stream = new FileInputStream(file);
|
||||
properties.load(stream);
|
||||
serverAddress = properties.getProperty("NTP_SERVER", null);
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
}
|
||||
return serverAddress;
|
||||
}
|
||||
|
||||
private void registerForTelephonyIntents() {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
|
||||
@@ -189,9 +155,15 @@ public class NetworkTimeUpdateService {
|
||||
if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + POLLING_INTERVAL_MS
|
||||
|| event == EVENT_AUTO_TIME_CHANGED) {
|
||||
if (DBG) Log.d(TAG, "Before Ntp fetch");
|
||||
long ntp = getNtpTime();
|
||||
if (DBG) Log.d(TAG, "Ntp = " + ntp);
|
||||
if (ntp > 0) {
|
||||
|
||||
// force refresh NTP cache when outdated
|
||||
if (mTime.getCacheAge() >= POLLING_INTERVAL_MS) {
|
||||
mTime.forceRefresh();
|
||||
}
|
||||
|
||||
// only update when NTP time is fresh
|
||||
if (mTime.getCacheAge() < POLLING_INTERVAL_MS) {
|
||||
final long ntp = mTime.currentTimeMillis();
|
||||
mTryAgainCounter = 0;
|
||||
mLastNtpFetchTime = SystemClock.elapsedRealtime();
|
||||
if (Math.abs(ntp - currentTime) > TIME_ERROR_THRESHOLD_MS) {
|
||||
@@ -232,15 +204,6 @@ public class NetworkTimeUpdateService {
|
||||
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
|
||||
}
|
||||
|
||||
private long getNtpTime() {
|
||||
SntpClient client = new SntpClient();
|
||||
if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT_MS)) {
|
||||
return client.getNtpTime();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user prefers to automatically set the time.
|
||||
*/
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
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;
|
||||
@@ -54,6 +51,9 @@ import android.util.NtpTrustedTime;
|
||||
import android.util.Slog;
|
||||
import android.util.TrustedTime;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.telephony.TelephonyProperties;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
@@ -63,7 +63,6 @@ import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@@ -87,7 +86,6 @@ public class ThrottleService extends IThrottleManager.Stub {
|
||||
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;
|
||||
|
||||
@@ -127,8 +125,6 @@ public class ThrottleService extends IThrottleManager.Stub {
|
||||
private static final int THROTTLE_INDEX_UNINITIALIZED = -1;
|
||||
private static final int THROTTLE_INDEX_UNTHROTTLED = 0;
|
||||
|
||||
private static final String PROPERTIES_FILE = "/etc/gps.conf";
|
||||
|
||||
private Intent mPollStickyBroadcast;
|
||||
|
||||
private TrustedTime mTime;
|
||||
@@ -139,8 +135,7 @@ public class ThrottleService extends IThrottleManager.Stub {
|
||||
}
|
||||
|
||||
public ThrottleService(Context context) {
|
||||
// TODO: move to using cached NtpTrustedTime
|
||||
this(context, getNetworkManagementService(), new NtpTrustedTime(),
|
||||
this(context, getNetworkManagementService(), NtpTrustedTime.getInstance(context),
|
||||
context.getResources().getString(R.string.config_datause_iface));
|
||||
}
|
||||
|
||||
@@ -341,26 +336,6 @@ public class ThrottleService extends IThrottleManager.Stub {
|
||||
}
|
||||
}, new IntentFilter(ACTION_RESET));
|
||||
|
||||
FileInputStream stream = null;
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
File file = new File(PROPERTIES_FILE);
|
||||
stream = new FileInputStream(file);
|
||||
properties.load(stream);
|
||||
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 {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
}
|
||||
|
||||
// use a new thread as we don't want to stall the system for file writes
|
||||
mThread = new HandlerThread(TAG);
|
||||
mThread.start();
|
||||
|
||||
@@ -32,7 +32,6 @@ import android.location.LocationManager;
|
||||
import android.location.LocationProvider;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.SntpClient;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@@ -51,6 +50,7 @@ import android.telephony.SmsMessage;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.telephony.gsm.GsmCellLocation;
|
||||
import android.util.Log;
|
||||
import android.util.NtpTrustedTime;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.android.internal.app.IBatteryStats;
|
||||
@@ -61,7 +61,7 @@ import com.android.internal.telephony.Phone;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringBufferInputStream;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Map.Entry;
|
||||
@@ -235,13 +235,13 @@ public class GpsLocationProvider implements LocationProviderInterface {
|
||||
|
||||
// properties loaded from PROPERTIES_FILE
|
||||
private Properties mProperties;
|
||||
private String mNtpServer;
|
||||
private String mSuplServerHost;
|
||||
private int mSuplServerPort;
|
||||
private String mC2KServerHost;
|
||||
private int mC2KServerPort;
|
||||
|
||||
private final Context mContext;
|
||||
private final NtpTrustedTime mNtpTime;
|
||||
private final ILocationManager mLocationManager;
|
||||
private Location mLocation = new Location(LocationManager.GPS_PROVIDER);
|
||||
private Bundle mLocationExtras = new Bundle();
|
||||
@@ -286,10 +286,6 @@ public class GpsLocationProvider implements LocationProviderInterface {
|
||||
// current setting - 5 minutes
|
||||
private static final long RETRY_INTERVAL = 5*60*1000;
|
||||
|
||||
// to avoid injecting bad NTP time, we reject any time fixes that differ from system time
|
||||
// by more than 5 minutes.
|
||||
private static final long MAX_NTP_SYSTEM_TIME_OFFSET = 5*60*1000;
|
||||
|
||||
private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() {
|
||||
public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException {
|
||||
if (listener == null) {
|
||||
@@ -378,6 +374,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
|
||||
|
||||
public GpsLocationProvider(Context context, ILocationManager locationManager) {
|
||||
mContext = context;
|
||||
mNtpTime = NtpTrustedTime.getInstance(context);
|
||||
mLocationManager = locationManager;
|
||||
mNIHandler = new GpsNetInitiatedHandler(context);
|
||||
|
||||
@@ -418,7 +415,6 @@ public class GpsLocationProvider implements LocationProviderInterface {
|
||||
FileInputStream stream = new FileInputStream(file);
|
||||
mProperties.load(stream);
|
||||
stream.close();
|
||||
mNtpServer = mProperties.getProperty("NTP_SERVER", null);
|
||||
|
||||
mSuplServerHost = mProperties.getProperty("SUPL_HOST");
|
||||
String portString = mProperties.getProperty("SUPL_PORT");
|
||||
@@ -530,13 +526,18 @@ public class GpsLocationProvider implements LocationProviderInterface {
|
||||
}
|
||||
mInjectNtpTimePending = false;
|
||||
|
||||
SntpClient client = new SntpClient();
|
||||
long delay;
|
||||
|
||||
if (client.requestTime(mNtpServer, 10000)) {
|
||||
long time = client.getNtpTime();
|
||||
long timeReference = client.getNtpTimeReference();
|
||||
int certainty = (int)(client.getRoundTripTime()/2);
|
||||
// force refresh NTP cache when outdated
|
||||
if (mNtpTime.getCacheAge() >= NTP_INTERVAL) {
|
||||
mNtpTime.forceRefresh();
|
||||
}
|
||||
|
||||
// only update when NTP time is fresh
|
||||
if (mNtpTime.getCacheAge() < NTP_INTERVAL) {
|
||||
long time = mNtpTime.getCachedNtpTime();
|
||||
long timeReference = mNtpTime.getCachedNtpTimeReference();
|
||||
long certainty = mNtpTime.getCacheCertainty();
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
Log.d(TAG, "NTP server returned: "
|
||||
@@ -545,7 +546,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
|
||||
+ " certainty: " + certainty
|
||||
+ " system time offset: " + (time - now));
|
||||
|
||||
native_inject_time(time, timeReference, certainty);
|
||||
native_inject_time(time, timeReference, (int) certainty);
|
||||
delay = NTP_INTERVAL;
|
||||
} else {
|
||||
if (DEBUG) Log.d(TAG, "requestTime failed");
|
||||
@@ -1395,7 +1396,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
|
||||
Properties extraProp = new Properties();
|
||||
|
||||
try {
|
||||
extraProp.load(new StringBufferInputStream(extras));
|
||||
extraProp.load(new StringReader(extras));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
||||
@@ -204,9 +204,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
|
||||
public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
|
||||
IPowerManager powerManager, INetworkStatsService networkStats,
|
||||
INetworkManagementService networkManagement) {
|
||||
// TODO: move to using cached NtpTrustedTime
|
||||
this(context, activityManager, powerManager, networkStats, networkManagement,
|
||||
new NtpTrustedTime(), getSystemDir());
|
||||
NtpTrustedTime.getInstance(context), getSystemDir());
|
||||
}
|
||||
|
||||
private static File getSystemDir() {
|
||||
|
||||
@@ -27,7 +27,6 @@ import static android.net.NetworkStats.IFACE_ALL;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.NetworkStats.UID_ALL;
|
||||
import static android.net.TrafficStats.UID_REMOVED;
|
||||
import static android.provider.Settings.Secure.NETSTATS_ENABLED;
|
||||
import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
|
||||
import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
|
||||
import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
|
||||
@@ -71,7 +70,6 @@ import android.util.Slog;
|
||||
import android.util.TrustedTime;
|
||||
|
||||
import com.android.internal.os.AtomicFile;
|
||||
import com.android.server.NativeDaemonConnectorException;
|
||||
import com.google.android.collect.Maps;
|
||||
import com.google.android.collect.Sets;
|
||||
|
||||
@@ -175,9 +173,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
||||
|
||||
public NetworkStatsService(
|
||||
Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
|
||||
// TODO: move to using cached NtpTrustedTime
|
||||
this(context, networkManager, alarmManager, new NtpTrustedTime(), getSystemDir(),
|
||||
new DefaultNetworkStatsSettings(context));
|
||||
this(context, networkManager, alarmManager, NtpTrustedTime.getInstance(context),
|
||||
getSystemDir(), new DefaultNetworkStatsSettings(context));
|
||||
}
|
||||
|
||||
private static File getSystemDir() {
|
||||
|
||||
Reference in New Issue
Block a user