gt:                 if (alarmTime != newAlarmTime) {
context: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * 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.android.calendar.alerts;
  
  import com.android.calendar.GeneralPreferences;
  import com.android.calendar.LogUtil;
  import com.android.calendar.R;
  import com.android.calendar.Utils;
  
  import android.app.AlarmManager;
  import android.app.Notification;
  import android.app.NotificationManager;
  import android.app.Service;
  import android.content.ContentResolver;
  import android.content.ContentUris;
  import android.content.ContentValues;
  import android.content.Context;
  import android.content.Intent;
  import android.content.SharedPreferences;
  import android.database.Cursor;
  import android.media.AudioManager;
  import android.net.Uri;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.HandlerThread;
  import android.os.IBinder;
  import android.os.Looper;
  import android.os.Message;
  import android.os.Process;
  import android.provider.CalendarContract;
  import android.provider.CalendarContract.Attendees;
  import android.provider.CalendarContract.CalendarAlerts;
  import android.text.TextUtils;
  import android.text.format.DateUtils;
  import android.util.Log;
  
  import java.util.HashMap;
  
  /**
   * This service is used to handle calendar event reminders.
   */
  public class AlertService extends Service {
      static final boolean DEBUG = true;
      private static final String TAG = "AlertService";
  
      private volatile Looper mServiceLooper;
      private volatile ServiceHandler mServiceHandler;
  
      private static final String[] ALERT_PROJECTION = new String[] {
          CalendarAlerts._ID,                     // 0
          CalendarAlerts.EVENT_ID,                // 1
          CalendarAlerts.STATE,                   // 2
          CalendarAlerts.TITLE,                   // 3
          CalendarAlerts.EVENT_LOCATION,          // 4
          CalendarAlerts.SELF_ATTENDEE_STATUS,    // 5
          CalendarAlerts.ALL_DAY,                 // 6
          CalendarAlerts.ALARM_TIME,              // 7
          CalendarAlerts.MINUTES,                 // 8
          CalendarAlerts.BEGIN,                   // 9
          CalendarAlerts.END,                     // 10
      };
  
      private static final int ALERT_INDEX_ID = 0;
      private static final int ALERT_INDEX_EVENT_ID = 1;
      private static final int ALERT_INDEX_STATE = 2;
      private static final int ALERT_INDEX_TITLE = 3;
      private static final int ALERT_INDEX_EVENT_LOCATION = 4;
      private static final int ALERT_INDEX_SELF_ATTENDEE_STATUS = 5;
      private static final int ALERT_INDEX_ALL_DAY = 6;
      private static final int ALERT_INDEX_ALARM_TIME = 7;
      private static final int ALERT_INDEX_MINUTES = 8;
      private static final int ALERT_INDEX_BEGIN = 9;
      private static final int ALERT_INDEX_END = 10;
  
      private static final String ACTIVE_ALERTS_SELECTION = "(" + CalendarAlerts.STATE + "=? OR "
              + CalendarAlerts.STATE + "=?) AND " + CalendarAlerts.ALARM_TIME + "<=";
  
      private static final String[] ACTIVE_ALERTS_SELECTION_ARGS = new String[] {
              Integer.toString(CalendarAlerts.STATE_FIRED),
              Integer.toString(CalendarAlerts.STATE_SCHEDULED)
      };
  
      private static final String ACTIVE_ALERTS_SORT = "begin DESC, end DESC";
  
      private static final String DISMISS_OLD_SELECTION = CalendarAlerts.END + "<? AND "
              + CalendarAlerts.STATE + "=?";
  
      void processMessage(Message msg) {
          Bundle bundle = (Bundle) msg.obj;
  
          // On reboot, update the notification bar with the contents of the
          // CalendarAlerts table.
          String action = bundle.getString("action");
          if (DEBUG) {
              Log.d(TAG, bundle.getLong(android.provider.CalendarContract.CalendarAlerts.ALARM_TIME)
                      + " Action = " + action);
          }
  
          if (action.equals(Intent.ACTION_BOOT_COMPLETED)
                  || action.equals(Intent.ACTION_TIME_CHANGED)) {
              doTimeChanged();
              return;
          }
  
          if (!action.equals(android.provider.CalendarContract.ACTION_EVENT_REMINDER)
                  && !action.equals(Intent.ACTION_LOCALE_CHANGED)
                  && !action.equals(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS)
                  && !action.equals(Utils.UPDATE_EVENT_REMINDER_ACTION)) {
              Log.w(TAG, "Invalid action: " + action);
              return;
          }
          if (action.equals(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS)) {
              dismissOldAlerts(this);
          }
          updateAlertNotification(this);
      }
  
      static void dismissOldAlerts(Context context) {
          ContentResolver cr = context.getContentResolver();
          final long currentTime = System.currentTimeMillis();
          ContentValues vals = new ContentValues();
          vals.put(CalendarAlerts.STATE, CalendarAlerts.STATE_DISMISSED);
          cr.update(CalendarAlerts.CONTENT_URI, vals, DISMISS_OLD_SELECTION, new String[] {
                  Long.toString(currentTime), Integer.toString(CalendarAlerts.STATE_SCHEDULED)
          });
      }
  
      public static boolean updateAlertNotification(Context context) {
          ContentResolver cr = context.getContentResolver();
          final long currentTime = System.currentTimeMillis();
  
          SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
  
          boolean doAlert = prefs.getBoolean(GeneralPreferences.KEY_ALERTS, true);
          boolean doPopup = prefs.getBoolean(GeneralPreferences.KEY_ALERTS_POPUP, false);
  
          if (!doAlert) {
              if (DEBUG) {
                  Log.d(TAG, "alert preference is OFF");
              }
  
              // If we shouldn't be showing notifications cancel any existing ones
              // and return.
              NotificationManager nm = (NotificationManager) context
                      .getSystemService(Context.NOTIFICATION_SERVICE);
              nm.cancelAll();
              return true;
          }
  
          Cursor alertCursor = cr.query(CalendarAlerts.CONTENT_URI, ALERT_PROJECTION,
                  (ACTIVE_ALERTS_SELECTION + currentTime), ACTIVE_ALERTS_SELECTION_ARGS,
                  ACTIVE_ALERTS_SORT);
  
          if (alertCursor == null || alertCursor.getCount() == 0) {
              if (alertCursor != null) {
                  alertCursor.close();
              }
  
              if (DEBUG) Log.d(TAG, "No fired or scheduled alerts");
              NotificationManager nm =
                  (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
              nm.cancel(0);
              return false;
          }
  
          if (DEBUG) {
              Log.d(TAG, "alert count:" + alertCursor.getCount());
          }
  
          String notificationEventName = null;
          String notificationEventLocation = null;
          long notificationEventBegin = 0;
          int notificationEventStatus = 0;
          boolean notificationEventAllDay = true;
          HashMap<Long, Long> eventIds = new HashMap<Long, Long>();
          int numReminders = 0;
          int numFired = 0;
          try {
              while (alertCursor.moveToNext()) {
                  final long alertId = alertCursor.getLong(ALERT_INDEX_ID);
                  final long eventId = alertCursor.getLong(ALERT_INDEX_EVENT_ID);
                  final int minutes = alertCursor.getInt(ALERT_INDEX_MINUTES);
                  final String eventName = alertCursor.getString(ALERT_INDEX_TITLE);
                  final String location = alertCursor.getString(ALERT_INDEX_EVENT_LOCATION);
                  final int status = alertCursor.getInt(ALERT_INDEX_SELF_ATTENDEE_STATUS);
                  final boolean declined = status == Attendees.ATTENDEE_STATUS_DECLINED;
                  final long beginTime = alertCursor.getLong(ALERT_INDEX_BEGIN);
                  final long endTime = alertCursor.getLong(ALERT_INDEX_END);
                  final Uri alertUri = ContentUris
                          .withAppendedId(CalendarAlerts.CONTENT_URI, alertId);
                  final long alarmTime = alertCursor.getLong(ALERT_INDEX_ALARM_TIME);
                  int state = alertCursor.getInt(ALERT_INDEX_STATE);
                  final boolean allDay = alertCursor.getInt(ALERT_INDEX_ALL_DAY) != 0;
  
                  if (DEBUG) {
                      Log.d(TAG, "alarmTime:" + alarmTime + " alertId:" + alertId
                              + " eventId:" + eventId + " state: " + state + " minutes:" + minutes
                              + " declined:" + declined + " beginTime:" + beginTime
                              + " endTime:" + endTime);
                  }
  
                  ContentValues values = new ContentValues();
                  int newState = -1;
  
                  // Uncomment for the behavior of clearing out alerts after the
                  // events ended. b/1880369
                  //
                  // if (endTime < currentTime) {
                  //     newState = CalendarAlerts.DISMISSED;
                  // } else
  
                  // Remove declined events
                  if (!declined) {
                      // Don't count duplicate alerts for the same event
                      if (eventIds.put(eventId, beginTime) == null) {
                          numReminders++;
                      }
  
                      if (state == CalendarAlerts.STATE_SCHEDULED) {
                          newState = CalendarAlerts.STATE_FIRED;
                          numFired++;
  
                          // Record the received time in the CalendarAlerts table.
                          // This is useful for finding bugs that cause alarms to be
                          // missed or delayed.
                          values.put(CalendarAlerts.RECEIVED_TIME, currentTime);
                      }
                  } else {
                      newState = CalendarAlerts.STATE_DISMISSED;
                  }
  
                  // Update row if state changed
                  if (newState != -1) {
                      values.put(CalendarAlerts.STATE, newState);
                      state = newState;
                  }
  
                  if (state == CalendarAlerts.STATE_FIRED) {
                      // Record the time posting to notification manager.
                      // This is used for debugging missed alarms.
                      values.put(CalendarAlerts.NOTIFY_TIME, currentTime);
                  }
  
                  // Write row to if anything changed
                  if (values.size() > 0) cr.update(alertUri, values, null, null);
  
                  if (state != CalendarAlerts.STATE_FIRED) {
                      continue;
                  }
  
                  // Pick an Event title for the notification panel by the latest
                  // alertTime and give prefer accepted events in case of ties.
                  int newStatus;
                  switch (status) {
                      case Attendees.ATTENDEE_STATUS_ACCEPTED:
                          newStatus = 2;
                          break;
                      case Attendees.ATTENDEE_STATUS_TENTATIVE:
                          newStatus = 1;
                          break;
                      default:
                          newStatus = 0;
                  }
  
                  // TODO Prioritize by "primary" calendar
                  // Assumes alerts are sorted by begin time in reverse
                  ///M:Here modify notificationEventBegin <= beginTime && notificationEventStatus < newStatus@{
                  ///to notificationEventBegin >= beginTime && notificationEventStatus <= newStatus
                  ///So can selected the event that current create to show on the status bar. 
                  ///@}
                  if (notificationEventName == null
                          || (notificationEventBegin >= beginTime &&
                                  notificationEventStatus <= newStatus)) {
                      notificationEventName = eventName;
                      notificationEventLocation = location;
                      notificationEventBegin = beginTime;
                      notificationEventStatus = newStatus;
                      notificationEventAllDay = allDay;
                  }
              }
          } finally {
              if (alertCursor != null) {
                  alertCursor.close();
              }
          }
  
          boolean quietUpdate = numFired == 0;
          boolean highPriority = numFired > 0 && doPopup;
          postNotification(context, prefs, notificationEventName, notificationEventLocation,
                  numReminders, quietUpdate, highPriority, notificationEventBegin,
                  notificationEventAllDay);
  
          return true;
      }
  
      private static void postNotification(Context context, SharedPreferences prefs,
              String eventName, String location, int numReminders,
              boolean quietUpdate, boolean highPriority, long startMillis, boolean allDay) {
          if (DEBUG) {
              Log.d(TAG, "###### creating new alarm notification, numReminders: " + numReminders
                      + (quietUpdate ? " QUIET" : " loud")
                      + (highPriority ? " high-priority" : ""));
          }
  
          NotificationManager nm =
                  (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
  
          if (numReminders == 0) {
              nm.cancel(0);
              return;
          }
  
          Notification notification = AlertReceiver.makeNewAlertNotification(context, eventName,
                  location, numReminders, highPriority, startMillis, allDay);
          notification.defaults |= Notification.DEFAULT_LIGHTS;
  
          Notification nullNotification = new Notification();
          nullNotification.defaults |= Notification.DEFAULT_LIGHTS; 
          // Quietly update notification bar. Nothing new. Maybe something just got deleted.
          if (!quietUpdate) {
              // Flash ticker in status bar
              notification.tickerText = eventName;
              if (!TextUtils.isEmpty(location)) {
                  notification.tickerText = eventName + " - " + location;
              }
  
              // Generate either a pop-up dialog, status bar notification, or
              // neither. Pop-up dialog and status bar notification may include a
              // sound, an alert, or both. A status bar notification also includes
              // a toast.
  
              // Find out the circumstances under which to vibrate.
              // Migrate from pre-Froyo boolean setting if necessary.
              String vibrateWhen; // "always" or "silent" or "never"
              if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN))
              {
                  // Look up Froyo setting
                  vibrateWhen =
                      prefs.getString(GeneralPreferences.KEY_ALERTS_VIBRATE_WHEN, null);
              } else if(prefs.contains(GeneralPreferences.KEY_ALERTS_VIBRATE)) {
                  // No Froyo setting. Migrate pre-Froyo setting to new Froyo-defined value.
                  boolean vibrate =
                      prefs.getBoolean(GeneralPreferences.KEY_ALERTS_VIBRATE, false);
                  vibrateWhen = vibrate ?
                      context.getString(R.string.prefDefault_alerts_vibrate_true) :
                      context.getString(R.string.prefDefault_alerts_vibrate_false);
              } else {
                  // No setting. Use Froyo-defined default.
                  vibrateWhen = context.getString(R.string.prefDefault_alerts_vibrateWhen);
              }
              boolean vibrateAlways = vibrateWhen.equals("always");
              boolean vibrateSilent = vibrateWhen.equals("silent");
              AudioManager audioManager =
                  (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
              boolean nowSilent =
                  audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE;
  
              // Possibly generate a vibration
              if (vibrateAlways || (vibrateSilent && nowSilent)) {
                  notification.defaults |= Notification.DEFAULT_VIBRATE;
              }
  
              // Possibly generate a sound. If 'Silent' is chosen, the ringtone
              // string will be empty.
              String reminderRingtone = prefs.getString(
                      GeneralPreferences.KEY_ALERTS_RINGTONE, null);
              notification.sound = TextUtils.isEmpty(reminderRingtone) ? null : Uri
                      .parse(reminderRingtone);
              
              /*
               * Before notify the event info normally, here we need to  notify a null notification here to clear the notification info!!. 
               * the nullNotification would not be pop up in status bar.
               * The purpose: show the reminder info in status bar when it already in the status bar.
               * Why: 1.If don't do it, it can not pop up the reminder info when it has been notified before.
               * 2.The google's default behavior is:don't show the reminder's info again when its content is the same as the one that has already stored in the status bar.
               * 
               * UI: it can pop up the reminder's info in the status bar when AlertReceiver receive a EVENT_REMINDER intents.
               */
              nm.notify(0, nullNotification);
              //send the normal notification with the same identifier to pop up what we want.
          }
  
          LogUtil.i(TAG, "start true notification: " + notification); 
          nm.notify(0, notification);
      }
  
      private void doTimeChanged() {
          ContentResolver cr = getContentResolver();
          Object service = getSystemService(Context.ALARM_SERVICE);
          AlarmManager manager = (AlarmManager) service;
          // TODO Move this into Provider
          rescheduleMissedAlarms(cr, this, manager);
          updateAlertNotification(this);
      }
  
      private static final String SORT_ORDER_ALARMTIME_ASC =
              CalendarContract.CalendarAlerts.ALARM_TIME + " ASC";
  
      private static final String WHERE_RESCHEDULE_MISSED_ALARMS =
              CalendarContract.CalendarAlerts.STATE
              + "="
              + CalendarContract.CalendarAlerts.STATE_SCHEDULED
              + " AND "
              + CalendarContract.CalendarAlerts.ALARM_TIME
              + "<?"
              + " AND "
              + CalendarContract.CalendarAlerts.ALARM_TIME
              + ">?"
              + " AND "
              + CalendarContract.CalendarAlerts.END + ">=?";
  
      /**
       * Searches the CalendarAlerts table for alarms that should have fired but
       * have not and then reschedules them. This method can be called at boot
       * time to restore alarms that may have been lost due to a phone reboot.
       *
       * @param cr the ContentResolver
       * @param context the Context
       * @param manager the AlarmManager
       */
      public static final void rescheduleMissedAlarms(ContentResolver cr, Context context,
              AlarmManager manager) {
          // Get all the alerts that have been scheduled but have not fired
          // and should have fired by now and are not too old.
          long now = System.currentTimeMillis();
          long ancient = now - DateUtils.DAY_IN_MILLIS;
          String[] projection = new String[] {
              CalendarContract.CalendarAlerts.ALARM_TIME,
          };
  
          // TODO: construct an explicit SQL query so that we can add
          // "GROUPBY" instead of doing a sort and de-dup
          Cursor cursor = cr.query(CalendarAlerts.CONTENT_URI, projection,
                  WHERE_RESCHEDULE_MISSED_ALARMS, (new String[] {
                          Long.toString(now), Long.toString(ancient), Long.toString(now)
                  }), SORT_ORDER_ALARMTIME_ASC);
          if (cursor == null) {
              return;
          }
  
          if (DEBUG) {
              Log.d(TAG, "missed alarms found: " + cursor.getCount());
          }
  
          try {
              long alarmTime = -1;
  
              while (cursor.moveToNext()) {
                  long newAlarmTime = cursor.getLong(0);
  