android settings 打开auto timezone执行流程

/ android / 没有评论 / 2397浏览

用到的代码文件

frameworks/opt/telephony/src/java/:
com/android/internal/telephony/ServiceStateTracker.java

frameworks/base/core/java/:
android/app/AlarmManager.java

frameworks/base/services/core/java/:
com/android/server/AlarmManagerService.java

vendor/mediatek/proprietary/packages/apps/MtkSettings/src/:
com/android/settings/datetime/TimeChangeListenerMixin.java
com/android/settings/DateTimeSettings.java
com/android/settings/dashboard/DashboardFragment.java
com/android/settings/datetime/TimeZonePreferenceController.java

代码执行流程图

android timezone 查看原图

代码执行流程分析

下面的代码基于android O(api 27)。

本文基于MTK(MediaTek)平台基线。

本文主要记录在android Settings中点击auto timezone选项后代码的执行流程。
选项的改变实质上是更新Settings.Global.AUTO_TIME_ZONE的值。

在ServiceStateTracker.java类的构造方法中注册字段的监听。

public ServiceStateTracker(GsmCdmaPhone phone, 
        CommandsInterface ci) {
    ......
    mCr.registerContentObserver(
            Settings.Global.getUriFor(
                Settings.Global.AUTO_TIME_ZONE), true,
            mAutoTimeZoneObserver);
    ......
}

下面来主要看下mAutoTimeZoneObserver这个监听。
当Settings.Global.AUTO_TIME_ZONE这个字段的值改变时就会进入这里。

private ContentObserver mAutoTimeZoneObserver = 
        new ContentObserver(new Handler()) {
    @Override
    public void onChange(boolean selfChange) {
        revertToNitzTimeZone();
    }
};

下面这两个方法都定义在ServiceStateTracker.java类中。

protected void revertToNitzTimeZone() {
    if (Settings.Global.getInt(mCr, 
            Settings.Global.AUTO_TIME_ZONE, 0) == 0) {
        return;
    }
    String tmpLog = "Reverting to NITZ TimeZone: tz=" 
        + mSavedTimeZone;
    Log.w(LOG_TAG, tmpLog);
    if (DBG) log(tmpLog);
    mTimeZoneLog.log(tmpLog);
    if (mSavedTimeZone != null) {
        setAndBroadcastNetworkSetTimeZone(mSavedTimeZone);
    }
}
/**
 * Set the timezone and send out a sticky broadcast so the 
 * system can determine if the timezone was set by the carrier.
 *
 * @param zoneId timezone set by carrier
 */
protected void setAndBroadcastNetworkSetTimeZone(String zoneId) {
    if (DBG) log("setAndBroadcastNetworkSetTimeZone: setTimeZone=" 
            + zoneId);
    AlarmManager alarm =(AlarmManager)mPhone.getContext()
            .getSystemService(Context.ALARM_SERVICE);
    /*
     * 进入这里
     */
    alarm.setTimeZone(zoneId);
    Intent intent = new Intent(TelephonyIntents
            .ACTION_NETWORK_SET_TIMEZONE);
    intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
    intent.putExtra("time-zone", zoneId);
    mPhone.getContext().sendStickyBroadcastAsUser(intent, 
            UserHandle.ALL);
    if (DBG) {
        log("setAndBroadcastNetworkSetTimeZone: call "
            + "alarm.setTimeZone and broadcast zoneId=" 
            + zoneId);
    }
}

下面进入AlarmManager.java类的setTimeZone()方法。

private final IAlarmManager mService;

public void setTimeZone(String timeZone) {
    if (TextUtils.isEmpty(timeZone)) {
        return;
    }

    // Reject this timezone if it isn't an Olson zone we 
    // recognize.
   if (mTargetSdkVersion >= Build.VERSION_CODES.M) {
        boolean hasTimeZone = false;
        try {
            hasTimeZone = ZoneInfoDB.getInstance()
                    .hasTimeZone(timeZone);
        } catch (IOException ignored) {
        }

        if (!hasTimeZone) {
            throw new IllegalArgumentException("Timezone: " 
                    + timeZone 
                    + " is not an Olson ID");
        }
    }

    try {
        mService.setTimeZone(timeZone);
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
}

下面进入AlarmManagerService.java类的setTimeZone()方法。

@Override
public void setTimeZone(String tz) {
    getContext().enforceCallingOrSelfPermission(
            "android.permission.SET_TIME_ZONE",
            "setTimeZone");

    final long oldId = Binder.clearCallingIdentity();
    try {
        setTimeZoneImpl(tz);
    } finally {
        Binder.restoreCallingIdentity(oldId);
    }
}

下面进入AlarmManagerService.java类的setTimeZoneImpl()方法。

void setTimeZoneImpl(String tz) {
    if (TextUtils.isEmpty(tz)) {
        return;
    }

    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;
            SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());
        }

        // Update the kernel timezone information
        // Kernel tracks time offsets as 'minutes west of GMT'
        int gmtOffset = zone.getOffset(System.currentTimeMillis());
        setKernelTimezone(mNativeData, -(gmtOffset / 60000));
    }

    TimeZone.setDefault(null);

    /*
     * 如果设置成功就发送广播
     */
    if (timeZoneWasChanged) {
        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);
    }
}

这个广播ACTION_TIMEZONE_CHANGED在TimeChangeListenerMixin.java类中有注册。
TimeChangeListenerMixin继承自BroadcastReceiver类。

private final UpdateTimeAndDateCallback mCallback;

@Override
public void onReceive(Context context, Intent intent) {
    if (mCallback != null) {
        mCallback.updateTimeAndDateDisplay(mContext);
    }
}

TimeChangeListenerMixin对象的实例在DateTimeSettings.java中生成。

@Override
public void onAttach(Context context) {
    /// M: add for auto GPS time @{
    mLocationManager = (LocationManager)getSystemService(
            Context.LOCATION_SERVICE);
    isGPSSupport = 
        (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) 
        != null);
    /// @}
    super.onAttach(context);
    getLifecycle()
        .addObserver(new TimeChangeListenerMixin(context, this));
}

下面看下UpdateTimeAndDateCallback在DateTimeSettings中的具体实现。
这个调用的是其父类DashboardFragment的方法。

@Override
public void updateTimeAndDateDisplay(Context context) {
    Log.d(TAG,"method: updateTimeAndDateDisplay()");
    updatePreferenceStates();
}

可以看见是遍历每个PreferenceController。

protected void updatePreferenceStates() {
    Collection<AbstractPreferenceController> controllers = 
            mPreferenceControllers.values();
    final PreferenceScreen screen = getPreferenceScreen();
    for (AbstractPreferenceController controller : controllers) {
        if (!controller.isAvailable()) {
            continue;
        }
        final String key = controller.getPreferenceKey();

        final Preference preference =mProgressiveDisclosureMixin
                .findPreference(screen, key);
        if (preference == null) {
            Log.d(TAG, String.format("Cannot find preference"
                + " with key %s in Controller %s",
                key, controller.getClass().getSimpleName()));
            continue;
        }
        controller.updateState(preference);
    }
}

我们的TimeZonePreferenceController也在其中。
下面看下TimeZonePreferenceController的updateState()方法。
这个方法就更新当前界面上时区的显示了。

@Override
public void updateState(Preference preference) {
    final Calendar now = Calendar.getInstance();
    preference.setSummary(
            getTimeZoneText(now.getTimeZone(),true));
    preference.setEnabled(
            !mAutoTimeZonePreferenceController.isEnabled());
}

系统时区值的获取

在上面的流程中更新手机系统的时区的准确值是从哪里获取的呢?
答案是ServiceStateTracker.java类中的mSavedTimeZone字符串变量;
这个变量是由Message EVENT_NITZ_TIME进行赋值。而这个Message是由系统底层SIM卡触发发出。所以必须给手机插入一张正常的SIM卡并且能驻网那么才能从手机网络中获取到正确的时区。