1/*
2 * Copyright 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony;
18
19import android.app.AlarmManager;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.Intent;
23import android.database.ContentObserver;
24import android.os.Handler;
25import android.os.SystemClock;
26import android.os.SystemProperties;
27import android.os.UserHandle;
28import android.provider.Settings;
29
30/**
31 * An interface to various time / time zone detection behaviors that should be centralized into a
32 * new service.
33 */
34// Non-final to allow mocking.
35public class TimeServiceHelper {
36
37    /**
38     * Callback interface for automatic detection enable/disable changes.
39     */
40    public interface Listener {
41        /**
42         * Automatic time detection has been enabled or disabled.
43         */
44        void onTimeDetectionChange(boolean enabled);
45
46        /**
47         * Automatic time zone detection has been enabled or disabled.
48         */
49        void onTimeZoneDetectionChange(boolean enabled);
50    }
51
52    private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
53
54    private final Context mContext;
55    private final ContentResolver mCr;
56
57    private Listener mListener;
58
59    /** Creates a TimeServiceHelper */
60    public TimeServiceHelper(Context context) {
61        mContext = context;
62        mCr = context.getContentResolver();
63    }
64
65    /**
66     * Sets a listener that will be called when the automatic time / time zone detection setting
67     * changes.
68     */
69    public void setListener(Listener listener) {
70        if (listener == null) {
71            throw new NullPointerException("listener==null");
72        }
73        if (mListener != null) {
74            throw new IllegalStateException("listener already set");
75        }
76        this.mListener = listener;
77        mCr.registerContentObserver(
78                Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
79                new ContentObserver(new Handler()) {
80                    public void onChange(boolean selfChange) {
81                        listener.onTimeDetectionChange(isTimeDetectionEnabled());
82                    }
83                });
84        mCr.registerContentObserver(
85                Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
86                new ContentObserver(new Handler()) {
87                    public void onChange(boolean selfChange) {
88                        listener.onTimeZoneDetectionChange(isTimeZoneDetectionEnabled());
89                    }
90                });
91    }
92
93    /**
94     * Returns the same value as {@link System#currentTimeMillis()}.
95     */
96    public long currentTimeMillis() {
97        return System.currentTimeMillis();
98    }
99
100    /**
101     * Returns the same value as {@link SystemClock#elapsedRealtime()}.
102     */
103    public long elapsedRealtime() {
104        return SystemClock.elapsedRealtime();
105    }
106
107    /**
108     * Returns true if the device has an explicit time zone set.
109     */
110    public boolean isTimeZoneSettingInitialized() {
111        return isTimeZoneSettingInitializedStatic();
112
113    }
114
115    /**
116     * Returns true if automatic time detection is enabled in settings.
117     */
118    public boolean isTimeDetectionEnabled() {
119        try {
120            return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME) > 0;
121        } catch (Settings.SettingNotFoundException snfe) {
122            return true;
123        }
124    }
125
126    /**
127     * Returns true if automatic time zone detection is enabled in settings.
128     */
129    public boolean isTimeZoneDetectionEnabled() {
130        try {
131            return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE) > 0;
132        } catch (Settings.SettingNotFoundException snfe) {
133            return true;
134        }
135    }
136
137    /**
138     * Set the device time zone and send out a sticky broadcast so the system can
139     * determine if the timezone was set by the carrier.
140     *
141     * @param zoneId timezone set by carrier
142     */
143    public void setDeviceTimeZone(String zoneId) {
144        setDeviceTimeZoneStatic(mContext, zoneId);
145    }
146
147    /**
148     * Set the time and Send out a sticky broadcast so the system can determine
149     * if the time was set by the carrier.
150     *
151     * @param time time set by network
152     */
153    public void setDeviceTime(long time) {
154        SystemClock.setCurrentTimeMillis(time);
155        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
156        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
157        intent.putExtra("time", time);
158        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
159    }
160
161    /**
162     * Static implementation of isTimeZoneSettingInitialized() for use from {@link MccTable}. This
163     * is a hack to deflake TelephonyTests when running on a device with a real SIM: in that
164     * situation real service events may come in while a TelephonyTest is running, leading to flakes
165     * as the real / fake instance of TimeServiceHelper is swapped in and out from
166     * {@link TelephonyComponentFactory}.
167     */
168    static boolean isTimeZoneSettingInitializedStatic() {
169        // timezone.equals("GMT") will be true and only true if the timezone was
170        // set to a default value by the system server (when starting, system server
171        // sets the persist.sys.timezone to "GMT" if it's not set). "GMT" is not used by
172        // any code that sets it explicitly (in case where something sets GMT explicitly,
173        // "Etc/GMT" Olsen ID would be used).
174        // TODO(b/64056758): Remove "timezone.equals("GMT")" hack when there's a
175        // better way of telling if the value has been defaulted.
176
177        String timeZoneId = SystemProperties.get(TIMEZONE_PROPERTY);
178        return timeZoneId != null && timeZoneId.length() > 0 && !timeZoneId.equals("GMT");
179    }
180
181    /**
182     * Static method for use by MccTable. See {@link #isTimeZoneSettingInitializedStatic()} for
183     * explanation.
184     */
185    static void setDeviceTimeZoneStatic(Context context, String zoneId) {
186        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
187        alarmManager.setTimeZone(zoneId);
188        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
189        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
190        intent.putExtra("time-zone", zoneId);
191        context.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
192    }
193}
194