/* * Copyright (C) 2008 The Android Open Source Project, Copyright (C) 2012 Louis Fazen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alphabetbloc.accessadmin.services; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.TimeZone; import android.app.AlarmManager; import android.app.IntentService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.SystemClock; import android.preference.PreferenceManager; import android.util.Log; import com.alphabetbloc.accessadmin.R; import com.alphabetbloc.accessadmin.activities.NtpToastActivity; import com.alphabetbloc.accessadmin.activities.SetUserPassword; import com.alphabetbloc.accessadmin.data.Constants; import com.alphabetbloc.accessadmin.receivers.UpdateClockReceiver; /** * {@hide} * * Simple SNTP client class for retrieving network time. * * Sample usage: * * <pre> * SntpClient client = new SntpClient(); * if (client.requestTime("pool.ntp.com")) { * long now = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference(); * } * </pre> */ public class UpdateClockService extends IntentService { private NotificationManager mNM; private int NOTIFICATION = R.string.clock_service_start; private static final String TAG = UpdateClockService.class.getSimpleName(); // private static final int REFERENCE_TIME_OFFSET = 16; private static final int ORIGINATE_TIME_OFFSET = 24; private static final int RECEIVE_TIME_OFFSET = 32; private static final int TRANSMIT_TIME_OFFSET = 40; private static final int NTP_PACKET_SIZE = 48; private static final int NTP_PORT = 123; private static final int NTP_MODE_CLIENT = 3; private static final int NTP_VERSION = 3; // Number of seconds between Jan 1, 1900 and Jan 1, 1970 // 70 years plus 17 leap days private static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L; // system time computed from NTP server response private long mNtpTime = -1; // value of SystemClock.elapsedRealtime() corresponding to mNtpTime private long mNtpTimeReference = -1; // ADDED BY ME:; private static final long MINIMUM_CLOCK_TIME = 1370713699558L; private Context mContext; private int timeout = 10000; private String host = "pool.ntp.org"; /** * A constructor is required, and must call the super IntentService(String) * constructor with a name for the worker thread. */ public UpdateClockService() { super("UpdateClockService"); mContext = this; } @Override protected void onHandleIntent(Intent intent) { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext); boolean phoneLocked = settings.getBoolean(Constants.SIM_ERROR_PHONE_LOCKED, false); if (phoneLocked) { Log.e(TAG, "Skipping System Time Check while device is locked."); return; } mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); showNotification(); try { synchronized (this) { Thread requestNtp = new Thread(mRequestNtpTime, "RequestNtpTimeThread"); requestNtp.start(); try { requestNtp.join(); } catch (Exception exception) { if (Constants.DEBUG) Log.e(TAG, "Failed to join to requestNtp"); } } } catch (Exception e) { if (Constants.DEBUG) Log.e(TAG, "HandleIntent try failed: " + e); } // Check to see if it was able to obtain Ntp time boolean cancelAlarm = false; boolean updateTimeZone = false; if (timezoneNeedsUpdate()) updateTimeZone = true; boolean updateTime = false; if (System.currentTimeMillis() < MINIMUM_CLOCK_TIME) updateTime = true; if (mNtpTime > 0 && mNtpTimeReference > 0) { if (clockNeedsUpdate()) updateTime = true; else cancelAlarm = true; } if (updateTimeZone || updateTime) { requestUserUpdate(updateTimeZone, updateTime); } else if (!updateTimeZone && cancelAlarm) { cancelUpdateClockAlarms(); } // if (mNtpTime > 0 && mNtpTimeReference > 0) { // if (clockNeedsUpdate()) // requestUserUpdate(); // else // cancelUpdateClockAlarms(); // // } else if (timezoneNeedsUpdate()) { // requestUserUpdate(); // // } else if (System.currentTimeMillis() < MINIMUM_CLOCK_TIME) { // // requestUserUpdate(); // if (Constants.DEBUG) // Log.e(TAG, // "Time is very far off... prompting user to reset the time"); // // } else { // Log.e(TAG, // "Could not obtain the NTP Time. Alarm will continue to run every hour."); // } mNM.cancel(NOTIFICATION); } private void showNotification() { CharSequence tickerText = getText(R.string.clock_service_ticker); CharSequence dropdownText = getText(R.string.clock_service_text); Notification notification = new Notification(R.drawable.clock_update, tickerText, System.currentTimeMillis()); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, SetUserPassword.class), 0); notification.setLatestEventInfo(this, getText(R.string.clock_service_label), dropdownText, contentIntent); mNM.notify(NOTIFICATION, notification); } private Runnable mRequestNtpTime = new Runnable() { public void run() { DatagramSocket socket = null; try { socket = new DatagramSocket(); socket.setSoTimeout(timeout); InetAddress address = InetAddress.getByName(host); byte[] buffer = new byte[NTP_PACKET_SIZE]; DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT); // set mode = 3 (client) and version = 3 // mode is in low 3 bits of first byte // version is in bits 3-5 of first byte buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3); // get current time and write it to the request packet long requestTime = System.currentTimeMillis(); long requestTicks = SystemClock.elapsedRealtime(); writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime); socket.send(request); // read the response DatagramPacket response = new DatagramPacket(buffer, buffer.length); socket.receive(response); long responseTicks = SystemClock.elapsedRealtime(); long responseTime = requestTime + (responseTicks - requestTicks); socket.close(); // extract the results long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET); long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET); long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET); long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2; mNtpTime = responseTime + clockOffset; mNtpTimeReference = responseTicks; } catch (Exception e) { if (Constants.DEBUG) Log.d(TAG, "RequestNtpTime failed... caught Exception: " + e); } finally { if (socket != null) { socket.close(); } } } }; private boolean timezoneNeedsUpdate() { // check timezone SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext); TimeZone tz = TimeZone.getDefault(); String id = tz.getID(); String localId = settings.getString(Constants.DEFAULT_TIME_ZONE, "Africa/Nairobi"); if (!id.equalsIgnoreCase(localId)) { if (Constants.DEBUG) Log.e(TAG, "clockNeedsUpdate is true because the timezone is wrong. Current Timezone=" + id); return true; } else { return false; } } /** * Compares the NTPTime with the SystemTime. Returns true if the difference * is greater than 200 seconds. */ private boolean clockNeedsUpdate() { // check time long realTime = mNtpTime + (SystemClock.elapsedRealtime() - mNtpTimeReference); long systemTime = System.currentTimeMillis(); long delta = Math.abs(realTime - systemTime); if (delta > (1000 * 60 * 10)) { if (Constants.DEBUG) Log.e(TAG, "clockNeedsUpdate is true b/c delta is: " + delta); return true; } else { if (Constants.DEBUG) Log.e(TAG, "Delta is less than 120000 (2 min) already... no need to reset time and is: " + delta); return false; } } /** * Prompt the user to set the system clock to correct date and time (rather * than rely on root permissions). */ private void requestUserUpdate(boolean updateTimeZone, boolean updateTime) { if (Constants.DEBUG) Log.e(TAG, "Requesting User to update the time"); // show date and time preferences first try { Intent timeIntent = new Intent(android.provider.Settings.ACTION_DATE_SETTINGS); timeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); this.startActivity(timeIntent); } catch (Exception e) { Log.e(TAG, "Could not launch Date and Time Settings on this device."); } // Show alert dialog on top Intent i = new Intent(mContext, NtpToastActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); i.putExtra(NtpToastActivity.CHANGE_TIMEZONE, updateTimeZone); i.putExtra(NtpToastActivity.CHANGE_TIME, updateTime); if (mNtpTime > 0 && mNtpTimeReference > 0) { i.putExtra(NtpToastActivity.NTP_TIME, mNtpTime); i.putExtra(NtpToastActivity.NTP_TIME_REFERENCE, mNtpTimeReference); } mContext.startActivity(i); } /** * Cancel any updateclock alarms if the time is now within ten minutes of * being accurate. Otherwise, continue to prompt the user to change the * alarm. */ private void cancelUpdateClockAlarms() { Intent i = new Intent(mContext, UpdateClockReceiver.class); PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0); AlarmManager aM = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); aM.cancel(pi); if (Constants.DEBUG) Log.e(TAG, "Cancelled UpdateClock Alarms"); } @Override public void onDestroy() { if (Constants.DEBUG) Log.e(TAG, "onDestroy has been called!"); super.onDestroy(); } // ///////////// CHECK TIME FUNCTIONS ///////////////////////// /** * Reads an unsigned 32 bit big endian number from the given offset in the * buffer. */ private long read32(byte[] buffer, int offset) { byte b0 = buffer[offset]; byte b1 = buffer[offset + 1]; byte b2 = buffer[offset + 2]; byte b3 = buffer[offset + 3]; // convert signed bytes to unsigned values int i0 = ((b0 & 0x80) == 0x80 ? (b0 & 0x7F) + 0x80 : b0); int i1 = ((b1 & 0x80) == 0x80 ? (b1 & 0x7F) + 0x80 : b1); int i2 = ((b2 & 0x80) == 0x80 ? (b2 & 0x7F) + 0x80 : b2); int i3 = ((b3 & 0x80) == 0x80 ? (b3 & 0x7F) + 0x80 : b3); return ((long) i0 << 24) + ((long) i1 << 16) + ((long) i2 << 8) + (long) i3; } /** * Reads the NTP time stamp at the given offset in the buffer and returns it * as a system time (milliseconds since January 1, 1970). */ private long readTimeStamp(byte[] buffer, int offset) { long seconds = read32(buffer, offset); long fraction = read32(buffer, offset + 4); return ((seconds - OFFSET_1900_TO_1970) * 1000) + ((fraction * 1000L) / 0x100000000L); } /** * Writes system time (milliseconds since January 1, 1970) as an NTP time * stamp at the given offset in the buffer. */ private void writeTimeStamp(byte[] buffer, int offset, long time) { long seconds = time / 1000L; long milliseconds = time - seconds * 1000L; seconds += OFFSET_1900_TO_1970; // write seconds in big endian format buffer[offset++] = (byte) (seconds >> 24); buffer[offset++] = (byte) (seconds >> 16); buffer[offset++] = (byte) (seconds >> 8); buffer[offset++] = (byte) (seconds >> 0); long fraction = milliseconds * 0x100000000L / 1000L; // write fraction in big endian format buffer[offset++] = (byte) (fraction >> 24); buffer[offset++] = (byte) (fraction >> 16); buffer[offset++] = (byte) (fraction >> 8); // low order bits should be random data buffer[offset++] = (byte) (Math.random() * 255.0); } // ///////////// LOGGING ///////////////////////// /** * Convert a millisecond duration to a string format (for the sake of * logging purposes only) * * @param millis * A duration to convert to a string form * @return A string of the form "X Days Y Hours Z Minutes A Seconds". */ public static String getDuration(long millis) { if (millis < 0) { // throw new // IllegalArgumentException("Duration must be greater than zero!"); return "requested time is negative"; } int years = (int) (millis / (1000 * 60 * 60 * 24 * 365.25)); millis -= (years * (1000 * 60 * 60 * 24 * 365.25)); int days = (int) ((millis / (1000 * 60 * 60 * 24)) % 365.25); millis -= (days * (1000 * 60 * 60 * 24)); int hours = (int) ((millis / (1000 * 60 * 60)) % 24); millis -= (hours * (1000 * 60 * 60)); int minutes = (int) ((millis / (1000 * 60)) % 60); millis -= (minutes * (1000 * 60)); int seconds = (int) (millis / 1000) % 60; StringBuilder sb = new StringBuilder(64); sb.append(years); sb.append(" Years "); sb.append(days); sb.append(" Days "); sb.append(hours); sb.append(" Hours "); sb.append(minutes); sb.append(" Minutes "); sb.append(seconds); sb.append(" Seconds"); return (sb.toString()); } }