com.berniesanders.fieldthebern.controllers.LocationController.java Source code

Java tutorial

Introduction

Here is the source code for com.berniesanders.fieldthebern.controllers.LocationController.java

Source

/*
 * Copyright (c) 2016 - Bernie 2016, 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.berniesanders.fieldthebern.controllers;

/**
 *
 */

import android.annotation.TargetApi;
import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import com.berniesanders.fieldthebern.exceptions.AddressUnavailableException;
import com.berniesanders.fieldthebern.exceptions.LocationUnavailableException;
import com.berniesanders.fieldthebern.location.StateConverter;
import com.google.android.gms.maps.model.LatLng;
import dagger.Module;
import dagger.Provides;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import javax.inject.Singleton;
import mortar.Presenter;
import mortar.bundler.BundleService;
import org.apache.commons.lang3.StringUtils;
import rx.Observable;
import rx.Subscriber;
import timber.log.Timber;

import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static mortar.bundler.BundleService.getBundleService;

/**
 * Provides a service to access location
 */
public class LocationController extends Presenter<LocationController.Activity> {

    private final Context context;
    private final LocationManager locationManager;

    public interface Activity {
        AppCompatActivity getActivity();
    }

    // package private
    // only instantiated via the dagger module inner class 'LocationModule' below
    LocationController(Context context, LocationManager locationManager) {
        this.context = context;
        this.locationManager = locationManager;
    }

    ////////////////////////////////// Mortar boilerplate ///////////////////////////////////////
    @Override
    public void onLoad(Bundle savedInstanceState) {
        Timber.v("onLoad()");
        //you can safely call getView() to get the activity here
    }

    @Override
    public void dropView(Activity view) {
        super.dropView(view);
        //after this it is no longer safe to call getView()
    }

    //required by mortar
    @Override
    protected BundleService extractBundleService(Activity activity) {
        return getBundleService(activity.getActivity());
    }

    //used to inject this singleton present onto the Activity
    @Module
    public static class LocationModule {

        @Provides
        @Singleton
        LocationController provideTemplateController(Context context, LocationManager locationManager) {
            return new LocationController(context, locationManager);
        }
    }

    /////////////////////////////////////// Rx ///////////////////////////////////////////

    public Observable<Location> get() {
        return Observable.create(new Observable.OnSubscribe<Location>() {
            @Override
            public void call(Subscriber<? super Location> subscriber) {
                try {
                    subscriber.onNext(getLocation());
                    subscriber.onCompleted();
                } catch (LocationUnavailableException e) {
                    subscriber.onError(e);
                }
            }
        });
    }

    public Observable<String> getAddress() {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                try {
                    String stateCode = getStateForLocation(getLocation());
                    subscriber.onNext(stateCode);
                    subscriber.onCompleted();
                } catch (LocationUnavailableException e) {
                    subscriber.onError(e);
                }
            }
        });
    }

    public Observable<Address> reverseGeocode(final LatLng latLng) {

        return Observable.create(new Observable.OnSubscribe<Address>() {
            @Override
            public void call(Subscriber<? super Address> subscriber) {
                try {
                    Address address = getAddressForLocation(latLng.latitude, latLng.longitude);
                    subscriber.onNext(address);
                    subscriber.onCompleted();
                } catch (LocationUnavailableException e) {
                    subscriber.onError(e);
                }
            }
        });
    }

    //////////////////////////////////// Location ////////////////////////////////////////

    @TargetApi(23)
    private Location getLocation() throws LocationUnavailableException {
        if (Build.VERSION.SDK_INT >= 23
                && ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) != PERMISSION_GRANTED
                && ContextCompat.checkSelfPermission(context, ACCESS_COARSE_LOCATION) != PERMISSION_GRANTED) {
            // TODO: Consider calling: ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            // onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
            Timber.e("location permission error");
            throw new LocationUnavailableException("location permission error");
        }

        Location location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);

        if (location == null) {
            throw new LocationUnavailableException("location was null");
        }

        return location;
    }

    private String getStateForLocation(Location location) throws AddressUnavailableException {
        return StateConverter.getStateCode(getAddressForLocation(location).getAdminArea());
    }

    private Address getAddressForLocation(Location location) throws AddressUnavailableException {
        return getAddressForLocation(location.getLatitude(), location.getLongitude());
    }

    private Address getAddressForLocation(double lat, double lng) throws AddressUnavailableException {
        // Errors could still arise from using the Geocoder (for example, if there is no
        // connectivity, or if the Geocoder is given illegal location data). Or, the Geocoder may
        // simply not have an address for a location. In all these cases, we communicate with the
        // receiver using a resultCode indicating failure. If an address is found, we use a
        // resultCode indicating success.

        // The Geocoder used in this sample. The Geocoder's responses are localized for the given
        // Locale, which represents a specific geographical or linguistic region. Locales are used
        // to alter the presentation of information such as numbers or dates to suit the conventions
        // in the region they describe.
        Geocoder geocoder = new Geocoder(context, Locale.getDefault());

        List<Address> addresses = null;
        Address address = null;
        try {
            // Using getFromLocation() returns an array of Addresses for the area immediately
            // surrounding the given latitude and longitude. The results are a best guess and are
            // not guaranteed to be accurate.
            // In this sample, we get just a single address.
            addresses = geocoder.getFromLocation(lat, lng, 1);
        } catch (IOException | IllegalArgumentException ioException) {
            throw new AddressUnavailableException();
        }

        // Handle case where no address was found.
        if (addresses == null || addresses.size() == 0) {
            throw new AddressUnavailableException();
        } else {
            address = addresses.get(0);

            if (address == null || StringUtils.isBlank(address.getAdminArea())) {
                throw new AddressUnavailableException("address or state code was null/blank");
            }

            return address;
        }
    }

    public boolean isLocationEnabled() {
        int locationMode = 0;
        String locationProviders;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            try {
                locationMode = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE);
            } catch (Settings.SettingNotFoundException e) {
                Timber.w(e, "isLocationEnabled SettingNotFoundException");
            }

            //We want network location
            //LOCATION_MODE_SENSORS_ONLY doesnt really work, thanks google
            return locationMode != Settings.Secure.LOCATION_MODE_OFF
                    && locationMode != Settings.Secure.LOCATION_MODE_SENSORS_ONLY;
        } else {
            locationProviders = Settings.Secure.getString(context.getContentResolver(),
                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
            return !TextUtils.isEmpty(locationProviders);
        }
    }
}