io.jawg.osmcontributor.service.OfflineRegionDownloadService.java Source code

Java tutorial

Introduction

Here is the source code for io.jawg.osmcontributor.service.OfflineRegionDownloadService.java

Source

/**
 * Copyright (C) 2016 eBusiness Information
 *
 * This file is part of OSM Contributor.
 *
 * OSM Contributor is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * OSM Contributor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OSM Contributor.  If not, see <http://www.gnu.org/licenses/>.
 */
package io.jawg.osmcontributor.service;

import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.offline.OfflineRegion;
import com.mapbox.mapboxsdk.offline.OfflineRegionError;
import com.mapbox.mapboxsdk.offline.OfflineRegionStatus;
import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import io.jawg.osmcontributor.BuildConfig;
import io.jawg.osmcontributor.OsmTemplateApplication;
import io.jawg.osmcontributor.R;
import io.jawg.osmcontributor.offline.OfflineRegionManager;
import io.jawg.osmcontributor.offline.events.CancelOfflineRegionDownloadEvent;
import io.jawg.osmcontributor.offline.events.OfflineRegionCreatedEvent;

/**
 * @author Tommy Buonomo on 03/08/16.
 */
public class OfflineRegionDownloadService extends IntentService {
    private static final String TAG = "MapOfflineManager";
    public static final String LIST_PARAM = "LIST";
    public static final String SIZE_PARAM = "SIZE_PARAM";
    public static final String REGION_NAME_PARAM = "REGION_NAME";
    private static final String CANCEL_DOWNLOAD = "CANCEL_DOWNLOAD";

    public static final int MIN_ZOOM = 13;
    private static final int MAX_ZOOM = 20;

    private List<OfflineRegion> waitingOfflineRegions;

    private OfflineRegion currentDownloadRegion;

    private boolean deliverStatusUpdate;
    private Intent intent;
    private Map<String, NotificationCompat.Builder> notifications;

    @Inject
    OfflineRegionManager offlineRegionManager;

    @Inject
    EventBus eventBus;

    @Inject
    NotificationManager notificationManager;

    public OfflineRegionDownloadService() {
        super(OfflineRegionDownloadService.class.getSimpleName());
    }

    @Override
    public void onCreate() {
        super.onCreate();
        ((OsmTemplateApplication) getApplication()).getOsmTemplateComponent().inject(this);
        eventBus.register(this);
        waitingOfflineRegions = new ArrayList<>();
        notifications = new HashMap<>();
    }

    @Override
    protected void onHandleIntent(final Intent intent) {
        this.intent = intent;
        offlineRegionManager.listOfflineRegions(new OfflineRegionManager.OnOfflineRegionsListedListener() {
            @Override
            public void onOfflineRegionsListed(List<OfflineRegion> offlineRegions) {
                startDownloadIfNeeded(intent, offlineRegions);
            }
        });
    }

    private void startDownloadIfNeeded(Intent intent, final List<OfflineRegion> presentOfflineRegions) {
        if (intent == null) {
            return;
        }
        final int size = intent.getIntExtra(SIZE_PARAM, -1);
        if (size != -1) {
            int c = 0;
            // There is some regions to download
            for (int i = 0; i < size; i++) {
                ArrayList<String> areasString = intent.getStringArrayListExtra(LIST_PARAM + i);
                LatLngBounds bounds = convertToLatLngBounds(areasString);
                OfflineRegion presentOfflineRegion = containsInOfflineRegion(presentOfflineRegions, bounds);
                if (presentOfflineRegion == null) {
                    // The region has never been downloaded
                    String regionName = intent.getStringExtra(REGION_NAME_PARAM);
                    regionName = regionName == null ? "Region " + (presentOfflineRegions.size() + c) : regionName;
                    c++;
                    downloadOfflineRegion(bounds, regionName);
                } else {
                    //The region is already downloaded, we check if it was completed
                    checkIfRegionDownloadIsCompleted(presentOfflineRegion);
                }
            }
        }
    }

    private void checkIfRegionDownloadIsCompleted(final OfflineRegion offlineRegion) {
        offlineRegion.getStatus(new OfflineRegion.OfflineRegionStatusCallback() {
            @Override
            public void onStatus(OfflineRegionStatus status) {
                if (!status.isComplete() && status.getDownloadState() != OfflineRegion.STATE_ACTIVE) {
                    resumeDownloadOfflineRegion(offlineRegion);
                }
            }

            @Override
            public void onError(String error) {
                Log.e(TAG, error);
            }
        });
    }

    public void downloadOfflineRegion(LatLngBounds latLngBounds, final String regionName) {
        final OfflineTilePyramidRegionDefinition definition = new OfflineTilePyramidRegionDefinition(
                BuildConfig.MAP_STYLE_URL, latLngBounds, MIN_ZOOM, MAX_ZOOM,
                this.getResources().getDisplayMetrics().density);

        // Build the notification
        buildNotification(regionName);
        offlineRegionManager.createOfflineRegion(definition, regionName,
                new OfflineRegionManager.OnOfflineRegionCreatedListener() {
                    @Override
                    public void onOfflineRegionCreated(OfflineRegion offlineRegion, String regionName) {
                        // Monitor the download progress using setObserver
                        offlineRegion.setObserver(getOfflineRegionObserver(regionName));
                        startDownloadOfflineRegion(offlineRegion);
                        eventBus.post(new OfflineRegionCreatedEvent());
                    }
                });
    }

    private void resumeDownloadOfflineRegion(OfflineRegion offlineRegion) {
        Log.d(TAG, "resumeDownloadOfflineRegion: " + offlineRegion);
        String regionName = OfflineRegionManager.decodeRegionName(offlineRegion.getMetadata());
        buildNotification(regionName);
        offlineRegion.setObserver(getOfflineRegionObserver(regionName));
        startDownloadOfflineRegion(offlineRegion);
    }

    private void startDownloadOfflineRegion(OfflineRegion offlineRegion) {
        // An area is already downloading, we put the area in waiting
        if (currentDownloadRegion != null) {
            waitingOfflineRegions.add(offlineRegion);
        } else {
            currentDownloadRegion = offlineRegion;
            offlineRegion.setDownloadState(OfflineRegion.STATE_ACTIVE);
            deliverStatusUpdate = true;
        }
    }

    private synchronized void checkNextDownload() {
        if (!waitingOfflineRegions.isEmpty()) {
            startDownloadOfflineRegion(waitingOfflineRegions.get(0));
            waitingOfflineRegions.remove(0);
        }
    }

    private void cancelDownloadOfflineRegion() {
        if (currentDownloadRegion != null) {
            currentDownloadRegion.setDownloadState(OfflineRegion.STATE_INACTIVE);
            deliverStatusUpdate = false;
            currentDownloadRegion = null;
        }
    }

    private OfflineRegion.OfflineRegionObserver getOfflineRegionObserver(final String regionName) {
        final NotificationCompat.Builder builder = notifications.get(regionName);
        return new OfflineRegion.OfflineRegionObserver() {
            int percentage;

            @Override
            public void onStatusChanged(OfflineRegionStatus status) {
                if (deliverStatusUpdate) {
                    // Calculate the download percentage and update the progress bar
                    int newPercent = (int) Math.round(status.getRequiredResourceCount() >= 0
                            ? (100.0 * status.getCompletedResourceCount() / status.getRequiredResourceCount())
                            : 0.0);
                    if (newPercent != percentage) {
                        percentage = newPercent;
                        builder.setContentText(percentage + "%");
                        builder.setProgress(100, percentage, false);
                        notificationManager.notify(regionName.hashCode(), builder.build());
                    }
                }
                if (status.isComplete()) {
                    // Download complete
                    // When the loop is finished, updates the notification
                    builder.setContentText("Region downloaded successfully").setProgress(0, 0, false).mActions
                            .clear();
                    notificationManager.notify(regionName.hashCode(), builder.build());
                    currentDownloadRegion = null;
                    checkNextDownload();
                }
            }

            @Override
            public void onError(OfflineRegionError error) {
                // If an error occurs, print to logcat
                Log.e(TAG, "onError reason: " + error.getReason());
                Log.e(TAG, "onError message: " + error.getMessage());
            }

            @Override
            public void mapboxTileCountLimitExceeded(long limit) {
                // Notify if offline region exceeds maximum tile count
                Log.e(TAG, "Mapbox tile count limit exceeded: " + limit);
            }
        };
    }

    /**
     * Build the download notification to display
     *
     * @param regionName
     * @return
     */
    public void buildNotification(String regionName) {
        Intent cancelButtonIntent = new Intent(getApplicationContext(), CancelButtonReceiver.class);
        cancelButtonIntent.putExtra(CancelButtonReceiver.MAP_TAG_PARAM, regionName);
        cancelButtonIntent.setAction(CANCEL_DOWNLOAD + regionName.hashCode());

        PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, cancelButtonIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_file_download_white)
                .setContentTitle(this.getString(R.string.notification_download_title) + " " + regionName)
                .addAction(new NotificationCompat.Action(R.drawable.ic_clear_white, getString(R.string.cancel),
                        pendingIntent))
                .setDeleteIntent(pendingIntent);
        notifications.put(regionName, builder);
    }

    /**
     * Check if a region is present in the list with the bounds parameter.
     * @param regions
     * @param bounds
     * @return the OfflineRegion if it's present or null.
     */
    private OfflineRegion containsInOfflineRegion(List<OfflineRegion> regions, LatLngBounds bounds) {
        for (OfflineRegion offlineRegion : regions) {
            if (((OfflineTilePyramidRegionDefinition) offlineRegion.getDefinition()).getBounds().equals(bounds)) {
                return offlineRegion;
            }
        }
        return null;
    }

    private LatLngBounds convertToLatLngBounds(List<String> latLngBoundsStrings) {
        if (latLngBoundsStrings.size() == 4) {
            return new LatLngBounds.Builder()
                    .include(new LatLng(Double.parseDouble(latLngBoundsStrings.get(0)),
                            Double.parseDouble(latLngBoundsStrings.get(1))))
                    .include(new LatLng(Double.parseDouble(latLngBoundsStrings.get(2)),
                            Double.parseDouble(latLngBoundsStrings.get(3))))
                    .build();
        }
        return null;
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    @SuppressWarnings("unused")
    public void onCancelOfflineRegionEvent(final CancelOfflineRegionDownloadEvent event) {
        if (currentDownloadRegion != null && OfflineRegionManager
                .decodeRegionName(currentDownloadRegion.getMetadata()).equals(event.getRegionName())) {
            cancelDownloadOfflineRegion();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}