FileDocCategorySizeDatePackage
CalendarAppWidgetProvider.javaAPI DocAndroid 1.5 API10223Wed May 06 22:42:48 BST 2009com.android.providers.calendar

CalendarAppWidgetProvider.java

/*
 * Copyright (C) 2009 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.providers.calendar;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.text.format.DateUtils;
import android.util.Log;

/**
 * Simple widget to show next upcoming calendar event.
 */
public class CalendarAppWidgetProvider extends AppWidgetProvider {
    static final String TAG = "CalendarAppWidgetProvider";
    static final boolean LOGD = false;

    static final String ACTION_CALENDAR_APPWIDGET_UPDATE =
            "com.android.providers.calendar.APPWIDGET_UPDATE";
    
    /**
     * Threshold to check against when building widget updates. If system clock
     * has changed less than this amount, we consider ignoring the request.
     */
    static final long UPDATE_THRESHOLD = DateUtils.MINUTE_IN_MILLIS;

    /**
     * Maximum time to hold {@link WakeLock} when performing widget updates.
     */
    static final long WAKE_LOCK_TIMEOUT = DateUtils.MINUTE_IN_MILLIS;
    
    static final String PACKAGE_THIS_APPWIDGET =
        "com.android.providers.calendar";
    static final String CLASS_THIS_APPWIDGET =
        "com.android.providers.calendar.CalendarAppWidgetProvider";

    private static CalendarAppWidgetProvider sInstance;
    
    static synchronized CalendarAppWidgetProvider getInstance() {
        if (sInstance == null) {
            sInstance = new CalendarAppWidgetProvider();
        }
        return sInstance;
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        // Handle calendar-specific updates ourselves because they might be
        // coming in without extras, which AppWidgetProvider then blocks.
        final String action = intent.getAction();
        if (ACTION_CALENDAR_APPWIDGET_UPDATE.equals(action)) {
            performUpdate(context, null /* all widgets */,
                    null /* no eventIds */, false /* don't ignore */);
        } else {
            super.onReceive(context, intent);
        }
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public void onEnabled(Context context) {
        // Enable updates for timezone and date changes
        PackageManager pm = context.getPackageManager();
        pm.setComponentEnabledSetting(
                new ComponentName(context, TimeChangeReceiver.class),
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onDisabled(Context context) {
        // Unsubscribe from all AlarmManager updates
        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        PendingIntent pendingUpdate = getUpdateIntent(context);
        am.cancel(pendingUpdate);

        // Disable updates for timezone and date changes
        PackageManager pm = context.getPackageManager();
        pm.setComponentEnabledSetting(
                new ComponentName(context, TimeChangeReceiver.class),
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        performUpdate(context, appWidgetIds, null /* no eventIds */, false /* force */);
    }
    
    /**
     * Check against {@link AppWidgetManager} if there are any instances of this widget.
     */
    private boolean hasInstances(Context context) {
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        ComponentName thisAppWidget = getComponentName(context);
        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget);
        return (appWidgetIds.length > 0);
    }
    
    /**
     * Build {@link ComponentName} describing this specific
     * {@link AppWidgetProvider}
     */
    static ComponentName getComponentName(Context context) {
        return new ComponentName(PACKAGE_THIS_APPWIDGET, CLASS_THIS_APPWIDGET);
    }

    /**
     * The {@link CalendarProvider} has been updated, which means we should push
     * updates to any widgets, if they exist.
     * 
     * @param context Context to use when creating widget.
     * @param changedEventId Specific event known to be changed, otherwise -1.
     *            If present, we use it to decide if an update is necessary.
     */
    void providerUpdated(Context context, long changedEventId) {
        if (hasInstances(context)) {
            // Only pass along changedEventId if not -1
            long[] changedEventIds = null;
            if (changedEventId != -1) {
                changedEventIds = new long[] { changedEventId };
            }
            
            performUpdate(context, null /* all widgets */, changedEventIds, false /* force */);
        }
    }

    /**
     * {@link TimeChangeReceiver} has triggered that the time changed.
     * 
     * @param context Context to use when creating widget.
     * @param considerIgnore If true, compare
     *            {@link AppWidgetShared#sLastRequest} against
     *            {@link #UPDATE_THRESHOLD} to consider ignoring this update
     *            request.
     */
    void timeUpdated(Context context, boolean considerIgnore) {
        if (hasInstances(context)) {
            performUpdate(context, null /* all widgets */, null /* no events */, considerIgnore);
        }
    }
    
    /**
     * Process and push out an update for the given appWidgetIds. This call
     * actually fires an intent to start {@link CalendarAppWidgetService} as a
     * background service which handles the actual update, to prevent ANR'ing
     * during database queries.
     * <p>
     * This call will acquire a single {@link WakeLock} and set a flag that an
     * update has been requested.
     * 
     * @param context Context to use when acquiring {@link WakeLock} and
     *            starting {@link CalendarAppWidgetService}.
     * @param appWidgetIds List of specific appWidgetIds to update, or null for
     *            all.
     * @param changedEventIds Specific events known to be changed. If present,
     *            we use it to decide if an update is necessary.
     * @param considerIgnore If true, compare
     *            {@link AppWidgetShared#sLastRequest} against
     *            {@link #UPDATE_THRESHOLD} to consider ignoring this update
     *            request.
     */
    private void performUpdate(Context context, int[] appWidgetIds,
            long[] changedEventIds, boolean considerIgnore) {
        synchronized (AppWidgetShared.sLock) {
            // Consider ignoring this update request if inside threshold. This
            // check is inside the lock because we depend on this "now" time.
            long now = System.currentTimeMillis();
            if (considerIgnore && AppWidgetShared.sLastRequest != -1) {
                long delta = Math.abs(now - AppWidgetShared.sLastRequest);
                if (delta < UPDATE_THRESHOLD) {
                    if (LOGD) Log.d(TAG, "Ignoring update request because delta=" + delta);
                    return;
                }
            }
            
            // We need to update, so make sure we have a valid, held wakelock
            if (AppWidgetShared.sWakeLock == null ||
                    !AppWidgetShared.sWakeLock.isHeld()) {
                if (LOGD) Log.d(TAG, "no held wakelock found, so acquiring new one");
                PowerManager powerManager = (PowerManager)
                        context.getSystemService(Context.POWER_SERVICE);
                AppWidgetShared.sWakeLock =
                        powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
                AppWidgetShared.sWakeLock.setReferenceCounted(false);
                AppWidgetShared.sWakeLock.acquire(WAKE_LOCK_TIMEOUT);
            }
            
            if (LOGD) Log.d(TAG, "setting request now=" + now);
            AppWidgetShared.sLastRequest = now;
            AppWidgetShared.sUpdateRequested = true;
            
            // Apply filters that would limit the scope of this update, or clear
            // any pending filters if all requested.
            AppWidgetShared.mergeAppWidgetIdsLocked(appWidgetIds);
            AppWidgetShared.mergeChangedEventIdsLocked(changedEventIds);
            
            // Launch over to service so it can perform update
            final Intent updateIntent = new Intent(context, CalendarAppWidgetService.class);
            context.startService(updateIntent);
        }
    }
    
    /**
     * Build the {@link PendingIntent} used to trigger an update of all calendar
     * widgets. Uses {@link #ACTION_CALENDAR_APPWIDGET_UPDATE} to directly target
     * all widgets instead of using {@link AppWidgetManager#EXTRA_APPWIDGET_IDS}.
     * 
     * @param context Context to use when building broadcast.
     */
    static PendingIntent getUpdateIntent(Context context) {
        Intent updateIntent = new Intent(ACTION_CALENDAR_APPWIDGET_UPDATE);
        updateIntent.setComponent(new ComponentName(context, CalendarAppWidgetProvider.class));
        return PendingIntent.getBroadcast(context, 0 /* no requestCode */,
                updateIntent, 0 /* no flags */);
    }
}