com.google.android.apps.iosched.calendar.SessionAlarmService.java Source code

Java tutorial

Introduction

Here is the source code for com.google.android.apps.iosched.calendar.SessionAlarmService.java

Source

/*
 * Copyright 2012 Google Inc.
 *
 * 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.google.android.apps.iosched.calendar;

import com.google.android.apps.iosched.R;
import com.google.android.apps.iosched.provider.ScheduleContract;
import com.google.android.apps.iosched.util.UIUtils;

import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.support.v4.app.NotificationCompat;

import java.util.ArrayList;

import static com.google.android.apps.iosched.util.LogUtils.makeLogTag;

/**
 * Background service to handle scheduling of starred session notification via
 * {@link android.app.AlarmManager}.
 */
public class SessionAlarmService extends IntentService {
    private static final String TAG = makeLogTag(SessionAlarmService.class);

    public static final String ACTION_NOTIFY_SESSION = "com.google.android.apps.iosched.action.NOTIFY_SESSION";
    public static final String ACTION_SCHEDULE_STARRED_BLOCK = "com.google.android.apps.iosched.action.SCHEDULE_STARRED_BLOCK";
    public static final String ACTION_SCHEDULE_ALL_STARRED_BLOCKS = "com.google.android.apps.iosched.action.SCHEDULE_ALL_STARRED_BLOCKS";
    public static final String EXTRA_SESSION_START = "com.google.android.apps.iosched.extra.SESSION_START";
    public static final String EXTRA_SESSION_END = "com.google.android.apps.iosched.extra.SESSION_END";
    public static final String EXTRA_SESSION_ALARM_OFFSET = "com.google.android.apps.iosched.extra.SESSION_ALARM_OFFSET";

    private static final int NOTIFICATION_ID = 100;

    // pulsate every 1 second, indicating a relatively high degree of urgency
    private static final int NOTIFICATION_LED_ON_MS = 100;
    private static final int NOTIFICATION_LED_OFF_MS = 1000;
    private static final int NOTIFICATION_ARGB_COLOR = 0xff0088ff; // cyan

    private static final long ONE_MINUTE_MILLIS = 1 * 60 * 1000;
    private static final long TEN_MINUTES_MILLIS = 10 * ONE_MINUTE_MILLIS;

    private static final long UNDEFINED_ALARM_OFFSET = -1;

    public SessionAlarmService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        final String action = intent.getAction();
        if (ACTION_SCHEDULE_ALL_STARRED_BLOCKS.equals(action)) {
            scheduleAllStarredBlocks();
            return;
        }

        final long sessionStart = intent.getLongExtra(SessionAlarmService.EXTRA_SESSION_START, -1);
        if (sessionStart == -1) {
            return;
        }

        final long sessionEnd = intent.getLongExtra(SessionAlarmService.EXTRA_SESSION_END, -1);
        if (sessionEnd == -1) {
            return;
        }

        final long sessionAlarmOffset = intent.getLongExtra(SessionAlarmService.EXTRA_SESSION_ALARM_OFFSET,
                UNDEFINED_ALARM_OFFSET);

        if (ACTION_NOTIFY_SESSION.equals(action)) {
            notifySession(sessionStart, sessionEnd, sessionAlarmOffset);
        } else if (ACTION_SCHEDULE_STARRED_BLOCK.equals(action)) {
            scheduleAlarm(sessionStart, sessionEnd, sessionAlarmOffset);
        }
    }

    private void scheduleAlarm(final long sessionStart, final long sessionEnd, final long alarmOffset) {

        NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        nm.cancel(NOTIFICATION_ID);
        final long currentTime = System.currentTimeMillis();

        // If the session is already started, do not schedule system notification.
        if (currentTime > sessionStart) {
            return;
        }

        // By default, sets alarm to go off at 10 minutes before session start time.  If alarm
        // offset is provided, alarm is set to go off by that much time from now.
        long alarmTime;
        if (alarmOffset == UNDEFINED_ALARM_OFFSET) {
            alarmTime = sessionStart - TEN_MINUTES_MILLIS;
        } else {
            alarmTime = currentTime + alarmOffset;
        }

        final Intent alarmIntent = new Intent(ACTION_NOTIFY_SESSION, null, this, SessionAlarmService.class);

        // Setting data to ensure intent's uniqueness for different session start times.
        alarmIntent.setData(new Uri.Builder().authority(ScheduleContract.CONTENT_AUTHORITY)
                .path(String.valueOf(sessionStart)).build());
        alarmIntent.putExtra(SessionAlarmService.EXTRA_SESSION_START, sessionStart);
        alarmIntent.putExtra(SessionAlarmService.EXTRA_SESSION_END, sessionEnd);
        alarmIntent.putExtra(SessionAlarmService.EXTRA_SESSION_ALARM_OFFSET, alarmOffset);
        final AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

        // Schedule an alarm to be fired to notify user of added sessions are about to begin.
        am.set(AlarmManager.RTC_WAKEUP, alarmTime,
                PendingIntent.getService(this, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT));
    }

    /**
     * Constructs and triggers system notification for when starred sessions are about to begin.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void notifySession(final long sessionStart, final long sessionEnd, final long alarmOffset) {
        long currentTime = System.currentTimeMillis();
        if (sessionStart < currentTime) {
            return;
        }

        // Avoid repeated notifications.
        if (alarmOffset == UNDEFINED_ALARM_OFFSET && UIUtils.isNotificationFiredForBlock(this,
                ScheduleContract.Blocks.generateBlockId(sessionStart, sessionEnd))) {
            return;
        }

        final ContentResolver resolver = getContentResolver();
        final Uri starredBlockUri = ScheduleContract.Blocks
                .buildStarredSessionsUri(ScheduleContract.Blocks.generateBlockId(sessionStart, sessionEnd));
        Cursor cursor = resolver.query(starredBlockUri, new String[] { ScheduleContract.Blocks.NUM_STARRED_SESSIONS,
                ScheduleContract.Sessions.SESSION_TITLE }, null, null, null);
        int starredCount = 0;
        ArrayList<String> starredSessionTitles = new ArrayList<String>();
        while (cursor.moveToNext()) {
            starredSessionTitles.add(cursor.getString(1));
            starredCount = cursor.getInt(0);
        }

        if (starredCount < 1) {
            return;
        }

        // Generates the pending intent which gets fired when the user touches on the notification.
        Intent sessionIntent = new Intent(Intent.ACTION_VIEW, starredBlockUri);
        sessionIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pi = PendingIntent.getActivity(this, 0, sessionIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        final Resources res = getResources();
        String contentText;
        int minutesLeft = (int) (sessionStart - currentTime + 59000) / 60000;
        if (minutesLeft < 1) {
            minutesLeft = 1;
        }

        if (starredCount == 1) {
            contentText = res.getString(R.string.session_notification_text_1, minutesLeft);
        } else {
            contentText = res.getQuantityString(R.plurals.session_notification_text, starredCount - 1, minutesLeft,
                    starredCount - 1);
        }

        // Construct a notification. Use Jelly Bean (API 16) rich notifications if possible.
        Notification notification;
        if (UIUtils.hasJellyBean()) {
            // Rich notifications
            Notification.Builder builder = new Notification.Builder(this)
                    .setContentTitle(starredSessionTitles.get(0)).setContentText(contentText)
                    .setTicker(res
                            .getQuantityString(R.plurals.session_notification_ticker, starredCount, starredCount))
                    .setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE)
                    .setLights(SessionAlarmService.NOTIFICATION_ARGB_COLOR,
                            SessionAlarmService.NOTIFICATION_LED_ON_MS, SessionAlarmService.NOTIFICATION_LED_OFF_MS)
                    .setSmallIcon(R.drawable.ic_stat_notification).setContentIntent(pi)
                    .setPriority(Notification.PRIORITY_MAX).setAutoCancel(true);
            if (minutesLeft > 5) {
                builder.addAction(R.drawable.ic_alarm_holo_dark,
                        String.format(res.getString(R.string.snooze_x_min), 5),
                        createSnoozeIntent(sessionStart, sessionEnd, 5));
            }
            Notification.InboxStyle richNotification = new Notification.InboxStyle(builder)
                    .setBigContentTitle(res.getQuantityString(R.plurals.session_notification_title, starredCount,
                            minutesLeft, starredCount));

            // Adds starred sessions starting at this time block to the notification.
            for (int i = 0; i < starredCount; i++) {
                richNotification.addLine(starredSessionTitles.get(i));
            }
            notification = richNotification.build();

        } else {
            // Pre-Jelly Bean non-rich notifications
            notification = new NotificationCompat.Builder(this).setContentTitle(starredSessionTitles.get(0))
                    .setContentText(contentText)
                    .setTicker(res
                            .getQuantityString(R.plurals.session_notification_ticker, starredCount, starredCount))
                    .setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE)
                    .setLights(SessionAlarmService.NOTIFICATION_ARGB_COLOR,
                            SessionAlarmService.NOTIFICATION_LED_ON_MS, SessionAlarmService.NOTIFICATION_LED_OFF_MS)
                    .setSmallIcon(R.drawable.ic_stat_notification).setContentIntent(pi).getNotification();
        }
        NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        nm.notify(NOTIFICATION_ID, notification);
    }

    private PendingIntent createSnoozeIntent(final long sessionStart, final long sessionEnd,
            final int snoozeMinutes) {
        Intent scheduleIntent = new Intent(SessionAlarmService.ACTION_SCHEDULE_STARRED_BLOCK, null, this,
                SessionAlarmService.class);
        scheduleIntent.putExtra(SessionAlarmService.EXTRA_SESSION_START, sessionStart);
        scheduleIntent.putExtra(SessionAlarmService.EXTRA_SESSION_END, sessionEnd);
        scheduleIntent.putExtra(SessionAlarmService.EXTRA_SESSION_ALARM_OFFSET, snoozeMinutes * ONE_MINUTE_MILLIS);
        return PendingIntent.getService(this, 0, scheduleIntent, PendingIntent.FLAG_CANCEL_CURRENT);
    }

    private void scheduleAllStarredBlocks() {
        final Cursor cursor = getContentResolver().query(
                ScheduleContract.Sessions.CONTENT_STARRED_URI, new String[] {
                        "distinct " + ScheduleContract.Sessions.BLOCK_START, ScheduleContract.Sessions.BLOCK_END },
                null, null, null);
        if (cursor == null) {
            return;
        }

        while (cursor.moveToNext()) {
            final long sessionStart = cursor.getLong(0);
            final long sessionEnd = cursor.getLong(1);
            scheduleAlarm(sessionStart, sessionEnd, UNDEFINED_ALARM_OFFSET);
        }
    }
}