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 
17 package com.android.internal.telephony;
18 
19 import android.app.AlarmManager;
20 import android.content.ContentResolver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.database.ContentObserver;
24 import android.os.Handler;
25 import android.os.SystemClock;
26 import android.os.SystemProperties;
27 import android.os.UserHandle;
28 import 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.
35 public 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          */
onTimeDetectionChange(boolean enabled)44         void onTimeDetectionChange(boolean enabled);
45 
46         /**
47          * Automatic time zone detection has been enabled or disabled.
48          */
onTimeZoneDetectionChange(boolean enabled)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 */
TimeServiceHelper(Context context)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      */
setListener(Listener listener)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      */
currentTimeMillis()96     public long currentTimeMillis() {
97         return System.currentTimeMillis();
98     }
99 
100     /**
101      * Returns the same value as {@link SystemClock#elapsedRealtime()}.
102      */
elapsedRealtime()103     public long elapsedRealtime() {
104         return SystemClock.elapsedRealtime();
105     }
106 
107     /**
108      * Returns true if the device has an explicit time zone set.
109      */
isTimeZoneSettingInitialized()110     public boolean isTimeZoneSettingInitialized() {
111         return isTimeZoneSettingInitializedStatic();
112 
113     }
114 
115     /**
116      * Returns true if automatic time detection is enabled in settings.
117      */
isTimeDetectionEnabled()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      */
isTimeZoneDetectionEnabled()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      */
setDeviceTimeZone(String zoneId)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      */
setDeviceTime(long time)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      */
isTimeZoneSettingInitializedStatic()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      */
setDeviceTimeZoneStatic(Context context, String zoneId)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