Android10实现根据定位自动更新时区

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);
                    ...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值