org.onebusaway.android.directions.realtime.RealtimeService.java Source code

Java tutorial

Introduction

Here is the source code for org.onebusaway.android.directions.realtime.RealtimeService.java

Source

/**
 * Copyright (C) 2016 Cambridge Systematics, 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 org.onebusaway.android.directions.realtime;

import android.app.Activity;
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.Bundle;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import org.onebusaway.android.R;
import org.onebusaway.android.app.Application;
import org.onebusaway.android.directions.model.ItineraryDescription;
import org.onebusaway.android.directions.tasks.TripRequest;
import org.onebusaway.android.directions.util.OTPConstants;
import org.onebusaway.android.directions.util.TripRequestBuilder;
import org.opentripplanner.api.model.Itinerary;
import org.opentripplanner.api.model.Leg;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class RealtimeService extends IntentService {

    private static final String TAG = "RealtimeService";

    private static final String ITINERARY_DESC = ".ItineraryDesc";
    private static final String ITINERARY_END_DATE = ".ItineraryEndDate";

    public RealtimeService() {
        super("RealtimeService");
    }

    /**
     * Start realtime updates.
     *
     * @param source Activity from which updates are started
     * @param bundle Bundle with selected itinerary/parameters
     */
    public static void start(Activity source, Bundle bundle) {

        SharedPreferences prefs = Application.getPrefs();
        if (!prefs.getBoolean(OTPConstants.PREFERENCE_KEY_LIVE_UPDATES, true)) {
            return;
        }

        bundle.putSerializable(OTPConstants.NOTIFICATION_TARGET, source.getClass());
        Intent intent = new Intent(OTPConstants.INTENT_START_CHECKS);
        intent.putExtras(bundle);
        source.sendBroadcast(intent);
    }

    @Override
    public void onHandleIntent(Intent intent) {
        Bundle bundle = intent.getExtras();

        if (intent.getAction().equals(OTPConstants.INTENT_START_CHECKS)) {
            disableListenForTripUpdates();
            if (!possibleReschedule(bundle)) {
                Itinerary itinerary = getItinerary(bundle);
                startRealtimeUpdates(bundle, itinerary);
            }
        } else if (intent.getAction().equals(OTPConstants.INTENT_CHECK_TRIP_TIME)) {
            checkForItineraryChange(bundle);
        }

        RealtimeWakefulReceiver.completeWakefulIntent(intent);
    }

    // Depending on preferences / whether there is realtime info, start updates.
    private void startRealtimeUpdates(Bundle params, Itinerary itinerary) {

        Log.d(TAG, "Checking whether to start realtime updates.");

        boolean realtimeLegsOnItineraries = false;

        for (Leg leg : itinerary.legs) {
            if (leg.realTime) {
                realtimeLegsOnItineraries = true;
            }
        }

        if (realtimeLegsOnItineraries) {
            Log.d(TAG, "Starting realtime updates for itinerary");

            // init alarm mgr
            getAlarmManager().setInexactRepeating(AlarmManager.RTC, new Date().getTime(),
                    OTPConstants.DEFAULT_UPDATE_INTERVAL_TRIP_TIME, getAlarmIntent(params));
        } else {
            Log.d(TAG, "No realtime legs on itinerary");
        }

    }

    // Reschedule the start of checks, if necessary
    // Return true if rescheduled
    private boolean possibleReschedule(Bundle bundle) {

        // Delay if this trip doesn't start for at least an hour
        Date start = new TripRequestBuilder(bundle).getDateTime();
        Date queryStart = new Date(start.getTime() - OTPConstants.REALTIME_SERVICE_QUERY_WINDOW);
        boolean reschedule = new Date().before(queryStart);

        if (reschedule) {
            Log.d(TAG, "Start service at " + queryStart);
            Intent future = new Intent(OTPConstants.INTENT_START_CHECKS);
            future.putExtras(bundle);

            PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, future,
                    PendingIntent.FLAG_CANCEL_CURRENT);
            getAlarmManager().set(AlarmManager.RTC_WAKEUP, queryStart.getTime(), pendingIntent);
        }

        return reschedule;
    }

    private void checkForItineraryChange(final Bundle bundle) {
        TripRequestBuilder builder = TripRequestBuilder.initFromBundleSimple(bundle);
        ItineraryDescription desc = getItineraryDescription(bundle);
        Class target = getNotificationTarget(bundle);
        if (target == null) {
            disableListenForTripUpdates();
            return;
        }
        checkForItineraryChange(target, builder, desc);
    }

    private void checkForItineraryChange(final Class<? extends Activity> source, final TripRequestBuilder builder,
            final ItineraryDescription itineraryDescription) {

        Log.d(TAG, "Check for change");

        TripRequest.Callback callback = new TripRequest.Callback() {
            @Override
            public void onTripRequestComplete(List<Itinerary> itineraries, String url) {
                if (itineraries == null || itineraries.isEmpty()) {
                    onTripRequestFailure(-1, null);
                    return;
                }

                // Check each itinerary. Notify user if our *current* itinerary doesn't exist
                // or has a lower rank.
                for (int i = 0; i < itineraries.size(); i++) {
                    ItineraryDescription other = new ItineraryDescription(itineraries.get(i));

                    if (itineraryDescription.itineraryMatches(other)) {

                        long delay = itineraryDescription.getDelay(other);
                        Log.d(TAG, "Schedule deviation on itinerary: " + delay);

                        if (Math.abs(delay) > OTPConstants.REALTIME_SERVICE_DELAY_THRESHOLD) {
                            Log.d(TAG, "Notify due to large early/late schedule deviation.");
                            showNotification(itineraryDescription,
                                    (delay > 0) ? R.string.trip_plan_delay : R.string.trip_plan_early,
                                    R.string.trip_plan_notification_new_plan_text, source, builder.getBundle(),
                                    itineraries);
                            disableListenForTripUpdates();
                            return;
                        }

                        // Otherwise, we are still good.
                        Log.d(TAG, "Itinerary exists and no large schedule deviation.");
                        checkDisableDueToTimeout(itineraryDescription);

                        return;
                    }
                }
                Log.d(TAG,
                        "Did not find a matching itinerary in new call - notify user that something has changed.");
                showNotification(itineraryDescription, R.string.trip_plan_notification_new_plan_title,
                        R.string.trip_plan_notification_new_plan_text, source, builder.getBundle(), itineraries);
                disableListenForTripUpdates();
            }

            @Override
            public void onTripRequestFailure(int result, String url) {
                Log.e(TAG, "Failure checking itineraries. Result=" + result + ", url=" + url);
                disableListenForTripUpdates();
            }
        };

        builder.setListener(callback);

        try {
            builder.execute();
        } catch (Exception e) {
            e.printStackTrace();
            disableListenForTripUpdates();
        }
    }

    private void showNotification(ItineraryDescription description, int title, int message,
            Class<? extends Activity> notificationTarget, Bundle params, List<Itinerary> itineraries) {

        String titleText = getResources().getString(title);
        String messageText = getResources().getString(message);

        Intent openIntent = new Intent(getApplicationContext(), notificationTarget);
        openIntent.putExtras(params);
        openIntent.putExtra(OTPConstants.INTENT_SOURCE, OTPConstants.Source.NOTIFICATION);
        openIntent.putExtra(OTPConstants.ITINERARIES, (ArrayList<Itinerary>) itineraries);
        openIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent openPendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, openIntent,
                PendingIntent.FLAG_CANCEL_CURRENT);

        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getApplicationContext())
                .setSmallIcon(R.drawable.ic_stat_notification).setContentTitle(titleText)
                .setStyle(new NotificationCompat.BigTextStyle().bigText(messageText)).setContentText(messageText)
                .setPriority(NotificationCompat.PRIORITY_MAX).setContentIntent(openPendingIntent);

        NotificationManager notificationManager = (NotificationManager) getApplicationContext()
                .getSystemService(Context.NOTIFICATION_SERVICE);
        Notification notification = mBuilder.build();
        notification.defaults = Notification.DEFAULT_ALL;
        notification.flags |= Notification.FLAG_AUTO_CANCEL | Notification.FLAG_SHOW_LIGHTS;

        Integer notificationId = description.getId();
        notificationManager.notify(notificationId, notification);
    }

    // If the end time for this itinerary has passed, disable trip updates.
    private void checkDisableDueToTimeout(ItineraryDescription itineraryDescription) {
        if (itineraryDescription.isExpired()) {
            Log.d(TAG, "End of trip has passed.");
            disableListenForTripUpdates();
        }
    }

    public void disableListenForTripUpdates() {
        Log.d(TAG, "Disable trip updates.");
        getAlarmManager().cancel(getAlarmIntent(null));
    }

    private AlarmManager getAlarmManager() {
        return (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
    }

    private PendingIntent getAlarmIntent(Bundle bundle) {
        Intent intent = new Intent(OTPConstants.INTENT_CHECK_TRIP_TIME);
        if (bundle != null) {
            Bundle extras = getSimplifiedBundle(bundle);
            intent.putExtras(extras);
        }
        PendingIntent alarmIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        return alarmIntent;
    }

    private Itinerary getItinerary(Bundle bundle) {
        ArrayList<Itinerary> itineraries = (ArrayList<Itinerary>) bundle.getSerializable(OTPConstants.ITINERARIES);
        int i = bundle.getInt(OTPConstants.SELECTED_ITINERARY);
        return itineraries.get(i);
    }

    private ItineraryDescription getItineraryDescription(Bundle bundle) {
        String ids[] = bundle.getStringArray(ITINERARY_DESC);
        long date = bundle.getLong(ITINERARY_END_DATE);
        return new ItineraryDescription(Arrays.asList(ids), new Date(date));
    }

    private Class getNotificationTarget(Bundle bundle) {
        String name = bundle.getString(OTPConstants.NOTIFICATION_TARGET);
        try {
            return Class.forName(name);
        } catch (ClassNotFoundException e) {
            Log.e(TAG, "unable to find class for name " + name);
        }
        return null;
    }

    private Bundle getSimplifiedBundle(Bundle params) {
        Itinerary itinerary = getItinerary(params);
        ItineraryDescription desc = new ItineraryDescription(itinerary);

        Bundle extras = new Bundle();
        new TripRequestBuilder(params).copyIntoBundleSimple(extras);

        List<String> idList = desc.getTripIds();
        String[] ids = idList.toArray(new String[idList.size()]);
        extras.putStringArray(ITINERARY_DESC, ids);
        extras.putLong(ITINERARY_END_DATE, desc.getEndDate().getTime());

        Class<? extends Activity> source = (Class<? extends Activity>) params
                .getSerializable(OTPConstants.NOTIFICATION_TARGET);

        extras.putString(OTPConstants.NOTIFICATION_TARGET, source.getName());

        return extras;
    }

}