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.app.timedetector.TimeDetector; 21 import android.app.timedetector.TimeSignal; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.database.ContentObserver; 26 import android.os.Handler; 27 import android.os.SystemClock; 28 import android.os.SystemProperties; 29 import android.os.UserHandle; 30 import android.provider.Settings; 31 import android.util.TimestampedValue; 32 33 /** 34 * An interface to various time / time zone detection behaviors that should be centralized into a 35 * new service. 36 */ 37 // Non-final to allow mocking. 38 public class NewTimeServiceHelper { 39 40 /** 41 * Callback interface for automatic detection enable/disable changes. 42 */ 43 public interface Listener { 44 /** 45 * Automatic time zone detection has been enabled or disabled. 46 */ onTimeZoneDetectionChange(boolean enabled)47 void onTimeZoneDetectionChange(boolean enabled); 48 } 49 50 private static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; 51 52 private final Context mContext; 53 private final ContentResolver mCr; 54 private final TimeDetector mTimeDetector; 55 56 private Listener mListener; 57 58 /** Creates a TimeServiceHelper */ NewTimeServiceHelper(Context context)59 public NewTimeServiceHelper(Context context) { 60 mContext = context; 61 mCr = context.getContentResolver(); 62 mTimeDetector = context.getSystemService(TimeDetector.class); 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_ZONE), true, 79 new ContentObserver(new Handler()) { 80 public void onChange(boolean selfChange) { 81 listener.onTimeZoneDetectionChange(isTimeZoneDetectionEnabled()); 82 } 83 }); 84 } 85 86 /** 87 * Returns the same value as {@link System#currentTimeMillis()}. 88 */ currentTimeMillis()89 public long currentTimeMillis() { 90 return System.currentTimeMillis(); 91 } 92 93 /** 94 * Returns the same value as {@link SystemClock#elapsedRealtime()}. 95 */ elapsedRealtime()96 public long elapsedRealtime() { 97 return SystemClock.elapsedRealtime(); 98 } 99 100 /** 101 * Returns true if the device has an explicit time zone set. 102 */ isTimeZoneSettingInitialized()103 public boolean isTimeZoneSettingInitialized() { 104 return isTimeZoneSettingInitializedStatic(); 105 106 } 107 108 /** 109 * Returns true if automatic time zone detection is enabled in settings. 110 */ isTimeZoneDetectionEnabled()111 public boolean isTimeZoneDetectionEnabled() { 112 try { 113 return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE) > 0; 114 } catch (Settings.SettingNotFoundException snfe) { 115 return true; 116 } 117 } 118 119 /** 120 * Set the device time zone and send out a sticky broadcast so the system can 121 * determine if the timezone was set by the carrier. 122 * 123 * @param zoneId timezone set by carrier 124 */ setDeviceTimeZone(String zoneId)125 public void setDeviceTimeZone(String zoneId) { 126 setDeviceTimeZoneStatic(mContext, zoneId); 127 } 128 129 /** 130 * Suggest the time to the {@link TimeDetector}. 131 * 132 * @param signalTimeMillis the signal time as received from the network 133 */ suggestDeviceTime(TimestampedValue<Long> signalTimeMillis)134 public void suggestDeviceTime(TimestampedValue<Long> signalTimeMillis) { 135 TimeSignal timeSignal = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, signalTimeMillis); 136 mTimeDetector.suggestTime(timeSignal); 137 } 138 139 /** 140 * Static implementation of isTimeZoneSettingInitialized() for use from {@link MccTable}. This 141 * is a hack to deflake TelephonyTests when running on a device with a real SIM: in that 142 * situation real service events may come in while a TelephonyTest is running, leading to flakes 143 * as the real / fake instance of TimeServiceHelper is swapped in and out from 144 * {@link TelephonyComponentFactory}. 145 */ isTimeZoneSettingInitializedStatic()146 static boolean isTimeZoneSettingInitializedStatic() { 147 // timezone.equals("GMT") will be true and only true if the timezone was 148 // set to a default value by the system server (when starting, system server 149 // sets the persist.sys.timezone to "GMT" if it's not set). "GMT" is not used by 150 // any code that sets it explicitly (in case where something sets GMT explicitly, 151 // "Etc/GMT" Olsen ID would be used). 152 // TODO(b/64056758): Remove "timezone.equals("GMT")" hack when there's a 153 // better way of telling if the value has been defaulted. 154 155 String timeZoneId = SystemProperties.get(TIMEZONE_PROPERTY); 156 return timeZoneId != null && timeZoneId.length() > 0 && !timeZoneId.equals("GMT"); 157 } 158 159 /** 160 * Static method for use by MccTable. See {@link #isTimeZoneSettingInitializedStatic()} for 161 * explanation. 162 */ setDeviceTimeZoneStatic(Context context, String zoneId)163 static void setDeviceTimeZoneStatic(Context context, String zoneId) { 164 AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 165 alarmManager.setTimeZone(zoneId); 166 Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE); 167 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 168 intent.putExtra("time-zone", zoneId); 169 context.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 170 } 171 } 172