Android系统时间⌚相关文章
Android 10&15 Framework 允许设置系统时间早于编译时间
Android10设置未来时间后自动更新时间失败
Android10实现根据定位自动更新时区
一、环境配置
软件系统: Android 10
二、前言
Android系统自动更新时区是通过NITZ(Network Identity and Time Zone)机制,从移动网络运营商获取和更新系统时区信息,因此设备需要有移动通信模块。
笔者项目设备上同时有5G模块和GPS模块,需求期望在未使用5G模块时,通过GPS也能自动设置时区。
三、自动更新时区代码流程分析
3.1 原生自动时区设置
首先来看看原生Android 10自动更新时区是如何实现的。

packages/apps/Settings/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean autoZoneEnabled = (Boolean) newValue;
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
autoZoneEnabled ? 1 : 0);
mCallback.updateTimeAndDateDisplay(mContext);
return true;
}
当在系统设置中打开自动时区更新按钮时,代码会改变AUTO_TIME_ZONE配置值,而此配置值在开机时最终被NitzStateMachineImpl注册监听
frameworks/opt/telephony/src/java/com/android/internal/telephony/TimeServiceHelperImpl.java
@Override
public void setListener(Listener listener) {
...
this.mListener = listener;
mCr.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
new ContentObserver(new Handler()) {
public void onChange(boolean selfChange) {
listener.onTimeZoneDetectionChange(isTimeZoneDetectionEnabled());
}
});
}
frameworks/opt/telephony/src/java/com/android/internal/telephony/NitzStateMachineImpl.java
@VisibleForTesting
public NitzStateMachineImpl(GsmCdmaPhone phone, TimeServiceHelper timeServiceHelper,
DeviceState deviceState, TimeZoneLookupHelper timeZoneLookupHelper) {
...
mTimeServiceHelper.setListener(new TimeServiceHelper.Listener() {
@Override
public void onTimeZoneDetectionChange(boolean enabled) {
if (enabled) {
handleAutoTimeZoneEnabled();
}
}
});
}
private void handleAutoTimeZoneEnabled() {
...
//从persist.radio.mcc.zone属性中获取mcc时区
String mccTimeZoneId = android.os.SystemProperties.get("persist.radio.mcc.zone");
//若设置过时区,则重新设置并覆盖,保证一致性
if (mSavedTimeZoneId != null) {
setAndBroadcastNetworkSetTimeZone(mSavedTimeZoneId);
}else if(mccTimeZoneId!=null && (!mccTimeZoneId.equals(""))){
Rlog.d(LOG_TAG, "no find zone form Operators,but we get from mcc table: mccTimeZoneId=" + mccTimeZoneId);
setAndBroadcastNetworkSetTimeZone(mccTimeZoneId);
}
}
private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
...
mTimeServiceHelper.setDeviceTimeZone(zoneId);
}
mSavedTimeZoneId用来记录上一次成功解析到的NITZ时区,mccTimeZoneId则表示所在地的MCC(Mobile Country Code)在本地静态表查到的时区。
frameworks/opt/telephony/src/java/com/android/internal/telephony/TimeServiceHelperImpl.java
@Override
public void setDeviceTimeZone(String zoneId) {
setDeviceTimeZoneStatic(mContext, zoneId);
}
static void setDeviceTimeZoneStatic(Context context, String zoneId) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.setTimeZone(zoneId);
Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("time-zone", zoneId);
context.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
frameworks/base/core/java/android/app/AlarmManager.java
@RequiresPermission(android.Manifest.permission.SET_TIME_ZONE)
public void setTimeZone(String timeZone) {
...
try {
mService.setTimeZone(timeZone);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
@Override
public void setTimeZone(String tz) {
...
try {
setTimeZoneImpl(tz);
} finally {
Binder.restoreCallingIdentity(oldId);
}
}
void setTimeZoneImpl(String tz) {
...
TimeZone zone = TimeZone.getTimeZone(tz);
// Prevent reentrant calls from stepping on each other when writing
// the time zone property
boolean timeZoneWasChanged = false;
synchronized (this) {
String current = SystemProperties.get(TIMEZONE_PROPERTY);
if (current == null || !current.equals(zone.getID())) {
if (localLOGV) {
Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID());
}
timeZoneWasChanged = true;
//将时区设置到系统属性persist.sys.timezone
SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());
}
// Update the kernel timezone information
// Kernel tracks time offsets as 'minutes west of GMT'
int gmtOffset = zone.getOffset(mInjector.getCurrentTimeMillis());
mInjector.setKernelTimezone(-(gmtOffset / 60000));
}
TimeZone.setDefault(null);
if (timeZoneWasChanged) {
// Don't wait for broadcasts to update our midnight alarm
mClockReceiver.scheduleDateChangedEvent();
// And now let everyone else know
Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
intent.putExtra("time-zone", zone.getID());
getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
}
}
至此默认自动时区设置完成
3.2 经纬度自动更新时区
根据经纬度设置自动时区功能设计:
- 未插入SIM卡,但有GPS经纬度信息,打开自动更新时手动触发根据经纬度更新时区
- 已插入SIM卡获取时区,优先使用NITZ时区
- 自动更新时区打开、GPS打开时,每1h重新根据经纬度重新自动更新时区
具体修改如下:
framework/opt/telephony/src/java/com/android/internal/telephony/NitzStateMachineImpl.java
+ import android.location.Location;
+ import android.location.LocationListener;
+ import android.location.LocationManager;
+ import android.os.Bundle;
+ import android.os.SystemClock;
+ import android.telephony.TelephonyManager;
+ import com.android.internal.telephony.TimezoneMapper; //第三方开源库时区表
...
+ private final LocationManager mLocationManager;
+ private final TelephonyManager mTelephonyManager;
+ private Context mContext;
+ //记录从GPS获取的经纬度
+ private double mLatitude = 0;
+ private double mLongitude = 0;
+ //record the time when updating timezone via location
+ private long lastLocationUpdateTimezoneTime = 0;
+ private boolean mAutoSetTimeZoneEnable = false;
+ private static final long LOCATION_UPDATE_TIMEZONE_INTERVAL = 60 * 60 * 1000L;
...
@VisibleForTesting
public NitzStateMachineImpl(GsmCdmaPhone phone, TimeServiceHelper timeServiceHelper,
DeviceState deviceState, TimeZoneLookupHelper timeZoneLookupHelper) {
mPhone = phone;
- Context context = phone.getContext();
+ //Patch Start to auto timezone by loaction
+ mContext = phone.getContext();
PowerManager powerManager =
- (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
mDeviceState = deviceState;
mTimeZoneLookupHelper = timeZoneLookupHelper;
mTimeServiceHelper = timeServiceHelper;
mTimeServiceHelper.setListener(new TimeServiceHelper.Listener() {
@Override
public void onTimeZoneDetectionChange(boolean enabled) {
+ mAutoSetTimeZoneEnable = enabled;
if (enabled) {
handleAutoTimeZoneEnabled();
}
}
});
+ mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ LocationListener locationListener = new LocationListener() {
+ @Override
+ public void onLocationChanged(Location location) {
+ mLatitude = location.getLatitude();
+ mLongitude = location.getLongitude();
+ if (DBG) {
+ Rlog.d(LOG_TAG, "onLocationChanged mLatitude:" + mLatitude + ",mLongitude:" + mLongitude);
+ }
+ long now = SystemClock.elapsedRealtime();
+ if (mAutoSetTimeZoneEnable && (now - lastLocationUpdateTimezoneTime >= LOCATION_UPDATE_TIMEZONE_INTERVAL) && !hasSimCard()) {
+ lastLocationUpdateTimezoneTime = now;
+ handleAutoTimeZoneEnabled();
+ }
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {}
+
+ @Override
+ public void onProviderEnabled(String provider) {}
+
+ @Override
+ public void onProviderDisabled(String provider) {}
+ };
+ mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);
+ //Patch End to auto timezone by loaction
+ }
...
+ //Patch Start to auto timezone by loaction
+ /**
+ * check whether has simcard
+ */
+ private boolean hasSimCard() {
+ if (mTelephonyManager == null) {
+ Rlog.e(LOG_TAG, "hasSimCard mTelephonyManager is null!");
+ return false;
+ }
+ for (int slot = 0; slot < mTelephonyManager.getPhoneCount(); slot++) {
+ int state = mTelephonyManager.getSimState(slot);
+ if (state == TelephonyManager.SIM_STATE_READY) {
+ return true;
+ }
+ }
+ return false;
+ }
private void handleAutoTimeZoneEnabled() {
String tmpLog = "handleAutoTimeZoneEnabled: Reverting to NITZ TimeZone:"
+ " mSavedTimeZoneId=" + mSavedTimeZoneId;
if (DBG) {
Rlog.d(LOG_TAG, tmpLog);
}
mTimeZoneLog.log(tmpLog);
String mccTimeZoneId = android.os.SystemProperties.get("persist.radio.mcc.zone");
if (mSavedTimeZoneId != null) {
setAndBroadcastNetworkSetTimeZone(mSavedTimeZoneId);
- }else if(mccTimeZoneId!=null && (!mccTimeZoneId.equals(""))){
+ }else if(mccTimeZoneId!=null && (!mccTimeZoneId.equals("")) && hasSimCard()){
Rlog.d(LOG_TAG, "no find zone form Operators,but we get from mcc table: mccTimeZoneId=" + mccTimeZoneId);
setAndBroadcastNetworkSetTimeZone(mccTimeZoneId);
}
+ // auto set timezone via location info
+ else if (!hasSimCard()) {
+ if (mLocationManager == null) {
+ Rlog.e(LOG_TAG, "hasSimCard mTelephonyManager is null!");
+ return;
+ }
+ if (mLocationManager.isLocationEnabled() && (mLatitude != 0 && mLongitude != 0)) {
+ try {
+ String tzId = TimezoneMapper.latLngToTimezoneString(mLatitude, mLongitude);
+ Rlog.d(LOG_TAG, "location tzId:" + tzId);
+ if (tzId != null && !"unknown".equals(tzId)) {
+ if (DBG) {
+ Rlog.d(LOG_TAG, "handleAutoTimeZoneEnabled: NITZ unavailable, falling back to Location. " +
+ "Lat=" + mLatitude + " Lng=" + mLongitude + " Zone=" + tzId);
+ }
+ setAndBroadcastNetworkSetTimeZone(tzId);
+ } else {
+ if (DBG) {
+ Rlog.d(LOG_TAG, "handleAutoTimeZoneEnabled: TimezoneMapper returned unknown for location.");
+ }
+ }
+ } catch (Exception e) {
+ Rlog.e(LOG_TAG, "handleAutoTimeZoneEnabled: Error using TimezoneMapper", e);
+ }
+ } else {
+ if (DBG) {
+ Rlog.d(LOG_TAG, "handleAutoTimeZoneEnabled: No valid location available for fallback.");
+ }
+ }
+ }
}
+ //Patch End to auto timezone by loaction
当打开自动更新时间开关时,获取经纬度信息,如果未接入sim卡,且距离上次经纬度更新时区超过1h,则准备进入handleAutoTimeZoneEnabled() 使用经纬度更新时区;
在handleAutoTimeZoneEnabled() 中自动更新时区优先级低于NITZ,若未插入sim卡,且GPS经纬度有效,则通过 TimezoneMapper.latLngToTimezoneString()传入经纬度拿到时区,再通过setAndBroadcastNetworkSetTimeZone()更新时区。
核心实现是根据经纬度计算出时区,使用开源第三方库:LatLongToTimezone
将其java导入framework/opt/telephony/src/java/com/android/internal/telephony/TimezoneMapper.java
3.3 验证
打开NitzStateMachineImpl.java DBG开关
framework/opt/telephony/src/java/com/android/internal/telephony/NitzStateMachineImpl.java
private static final boolean DBG = true;//ServiceStateTracker.DBG;
对于国内时区,打开自动更新时区,查看log检查获取的经纬度、计算得到的时区是否正确:
...
public void onLocationChanged(Location location) {
mLatitude = location.getLatitude();
mLongitude = location.getLongitude();
if (DBG) {
Rlog.d(LOG_TAG, "onLocationChanged mLatitude:" + mLatitude + ",mLongitude:" + mLongitude);
}
l...
}
...
// auto set timezone via location info
else if (!hasSimCard()) {
...
if (mLocationManager.isLocationEnabled() && (mLatitude != 0 && mLongitude != 0)) {
try {
String tzId = TimezoneMapper.latLngToTimezoneString(mLatitude, mLongitude);
Rlog.d(LOG_TAG, "location tzId:" + tzId);
...
国外时区,通过地图获取城市位置经纬度,设置固定mLatitude 和 mLongitude ,观察log得到是时区是否实际相符。
笔者大致测过几个外国时区,完全正确。
...
public void onLocationChanged(Location location) {
mLatitude = xxx;//location.getLatitude();
mLongitude = yyy;//location.getLongitude();
if (DBG) {
Rlog.d(LOG_TAG, "onLocationChanged mLatitude:" + mLatitude + ",mLongitude:" + mLongitude);
}
l...
}
...
// auto set timezone via location info
else if (!hasSimCard()) {
...
if (mLocationManager.isLocationEnabled() && (mLatitude != 0 && mLongitude != 0)) {
try {
String tzId = TimezoneMapper.latLngToTimezoneString(mLatitude, mLongitude);
Rlog.d(LOG_TAG, "location tzId:" + tzId);
...

379

被折叠的 条评论
为什么被折叠?



