1/* 2 * Copyright (C) 2009 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 17package com.android.providers.calendar; 18 19import android.app.AlarmManager; 20import android.app.PendingIntent; 21import android.appwidget.AppWidgetManager; 22import android.appwidget.AppWidgetProvider; 23import android.content.ComponentName; 24import android.content.Context; 25import android.content.Intent; 26import android.content.pm.PackageManager; 27import android.os.PowerManager; 28import android.os.PowerManager.WakeLock; 29import android.text.format.DateUtils; 30import android.util.Log; 31 32/** 33 * Simple widget to show next upcoming calendar event. 34 */ 35public class CalendarAppWidgetProvider extends AppWidgetProvider { 36 static final String TAG = "CalendarAppWidgetProvider"; 37 static final boolean LOGD = false; 38 39 static final String ACTION_CALENDAR_APPWIDGET_UPDATE = 40 "com.android.providers.calendar.APPWIDGET_UPDATE"; 41 42 /** 43 * Threshold to check against when building widget updates. If system clock 44 * has changed less than this amount, we consider ignoring the request. 45 */ 46 static final long UPDATE_THRESHOLD = DateUtils.MINUTE_IN_MILLIS; 47 48 /** 49 * Maximum time to hold {@link WakeLock} when performing widget updates. 50 */ 51 static final long WAKE_LOCK_TIMEOUT = DateUtils.MINUTE_IN_MILLIS; 52 53 static final String PACKAGE_THIS_APPWIDGET = 54 "com.android.providers.calendar"; 55 static final String CLASS_THIS_APPWIDGET = 56 "com.android.providers.calendar.CalendarAppWidgetProvider"; 57 58 private static CalendarAppWidgetProvider sInstance; 59 60 static synchronized CalendarAppWidgetProvider getInstance() { 61 if (sInstance == null) { 62 sInstance = new CalendarAppWidgetProvider(); 63 } 64 return sInstance; 65 } 66 67 /** 68 * {@inheritDoc} 69 */ 70 @Override 71 public void onReceive(Context context, Intent intent) { 72 // Handle calendar-specific updates ourselves because they might be 73 // coming in without extras, which AppWidgetProvider then blocks. 74 final String action = intent.getAction(); 75 if (ACTION_CALENDAR_APPWIDGET_UPDATE.equals(action)) { 76 performUpdate(context, null /* all widgets */, 77 null /* no eventIds */, false /* don't ignore */); 78 } else { 79 super.onReceive(context, intent); 80 } 81 } 82 83 /** 84 * {@inheritDoc} 85 */ 86 @Override 87 public void onEnabled(Context context) { 88 // Enable updates for timezone and date changes 89 PackageManager pm = context.getPackageManager(); 90 pm.setComponentEnabledSetting( 91 new ComponentName(context, TimeChangeReceiver.class), 92 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 93 PackageManager.DONT_KILL_APP); 94 } 95 96 /** 97 * {@inheritDoc} 98 */ 99 @Override 100 public void onDisabled(Context context) { 101 // Unsubscribe from all AlarmManager updates 102 AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 103 PendingIntent pendingUpdate = getUpdateIntent(context); 104 am.cancel(pendingUpdate); 105 106 // Disable updates for timezone and date changes 107 PackageManager pm = context.getPackageManager(); 108 pm.setComponentEnabledSetting( 109 new ComponentName(context, TimeChangeReceiver.class), 110 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 111 PackageManager.DONT_KILL_APP); 112 } 113 114 /** 115 * {@inheritDoc} 116 */ 117 @Override 118 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 119 performUpdate(context, appWidgetIds, null /* no eventIds */, false /* force */); 120 } 121 122 /** 123 * Check against {@link AppWidgetManager} if there are any instances of this widget. 124 */ 125 private boolean hasInstances(Context context) { 126 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 127 ComponentName thisAppWidget = getComponentName(context); 128 int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget); 129 return (appWidgetIds.length > 0); 130 } 131 132 /** 133 * Build {@link ComponentName} describing this specific 134 * {@link AppWidgetProvider} 135 */ 136 static ComponentName getComponentName(Context context) { 137 return new ComponentName(PACKAGE_THIS_APPWIDGET, CLASS_THIS_APPWIDGET); 138 } 139 140 /** 141 * The {@link CalendarProvider} has been updated, which means we should push 142 * updates to any widgets, if they exist. 143 * 144 * @param context Context to use when creating widget. 145 * @param changedEventId Specific event known to be changed, otherwise -1. 146 * If present, we use it to decide if an update is necessary. 147 */ 148 void providerUpdated(Context context, long changedEventId) { 149 if (hasInstances(context)) { 150 // Only pass along changedEventId if not -1 151 long[] changedEventIds = null; 152 if (changedEventId != -1) { 153 changedEventIds = new long[] { changedEventId }; 154 } 155 156 performUpdate(context, null /* all widgets */, changedEventIds, false /* force */); 157 } 158 } 159 160 /** 161 * {@link TimeChangeReceiver} has triggered that the time changed. 162 * 163 * @param context Context to use when creating widget. 164 * @param considerIgnore If true, compare 165 * {@link AppWidgetShared#sLastRequest} against 166 * {@link #UPDATE_THRESHOLD} to consider ignoring this update 167 * request. 168 */ 169 void timeUpdated(Context context, boolean considerIgnore) { 170 if (hasInstances(context)) { 171 performUpdate(context, null /* all widgets */, null /* no events */, considerIgnore); 172 } 173 } 174 175 /** 176 * Process and push out an update for the given appWidgetIds. This call 177 * actually fires an intent to start {@link CalendarAppWidgetService} as a 178 * background service which handles the actual update, to prevent ANR'ing 179 * during database queries. 180 * <p> 181 * This call will acquire a single {@link WakeLock} and set a flag that an 182 * update has been requested. 183 * 184 * @param context Context to use when acquiring {@link WakeLock} and 185 * starting {@link CalendarAppWidgetService}. 186 * @param appWidgetIds List of specific appWidgetIds to update, or null for 187 * all. 188 * @param changedEventIds Specific events known to be changed. If present, 189 * we use it to decide if an update is necessary. 190 * @param considerIgnore If true, compare 191 * {@link AppWidgetShared#sLastRequest} against 192 * {@link #UPDATE_THRESHOLD} to consider ignoring this update 193 * request. 194 */ 195 private void performUpdate(Context context, int[] appWidgetIds, 196 long[] changedEventIds, boolean considerIgnore) { 197 synchronized (AppWidgetShared.sLock) { 198 // Consider ignoring this update request if inside threshold. This 199 // check is inside the lock because we depend on this "now" time. 200 long now = System.currentTimeMillis(); 201 if (considerIgnore && AppWidgetShared.sLastRequest != -1) { 202 long delta = Math.abs(now - AppWidgetShared.sLastRequest); 203 if (delta < UPDATE_THRESHOLD) { 204 if (LOGD) Log.d(TAG, "Ignoring update request because delta=" + delta); 205 return; 206 } 207 } 208 209 // We need to update, so make sure we have a valid, held wakelock 210 if (AppWidgetShared.sWakeLock == null || 211 !AppWidgetShared.sWakeLock.isHeld()) { 212 if (LOGD) Log.d(TAG, "no held wakelock found, so acquiring new one"); 213 PowerManager powerManager = (PowerManager) 214 context.getSystemService(Context.POWER_SERVICE); 215 AppWidgetShared.sWakeLock = 216 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 217 AppWidgetShared.sWakeLock.setReferenceCounted(false); 218 AppWidgetShared.sWakeLock.acquire(WAKE_LOCK_TIMEOUT); 219 } 220 221 if (LOGD) Log.d(TAG, "setting request now=" + now); 222 AppWidgetShared.sLastRequest = now; 223 AppWidgetShared.sUpdateRequested = true; 224 225 // Apply filters that would limit the scope of this update, or clear 226 // any pending filters if all requested. 227 AppWidgetShared.mergeAppWidgetIdsLocked(appWidgetIds); 228 AppWidgetShared.mergeChangedEventIdsLocked(changedEventIds); 229 230 // Launch over to service so it can perform update 231 final Intent updateIntent = new Intent(context, CalendarAppWidgetService.class); 232 context.startService(updateIntent); 233 } 234 } 235 236 /** 237 * Build the {@link PendingIntent} used to trigger an update of all calendar 238 * widgets. Uses {@link #ACTION_CALENDAR_APPWIDGET_UPDATE} to directly target 239 * all widgets instead of using {@link AppWidgetManager#EXTRA_APPWIDGET_IDS}. 240 * 241 * @param context Context to use when building broadcast. 242 */ 243 static PendingIntent getUpdateIntent(Context context) { 244 Intent updateIntent = new Intent(ACTION_CALENDAR_APPWIDGET_UPDATE); 245 updateIntent.setComponent(new ComponentName(context, CalendarAppWidgetProvider.class)); 246 return PendingIntent.getBroadcast(context, 0 /* no requestCode */, 247 updateIntent, 0 /* no flags */); 248 } 249} 250