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