/*
* BaseMapActivity.java
* Copyright (C) 2016 Nicholas Killewald
*
* This file is distributed under the terms of the BSD license.
* The source package should have a LICENSE file at the toplevel.
*/
package net.exclaimindustries.geohashdroid.activities;
import android.Manifest;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.backup.BackupManager;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.MapStyleOptions;
import net.exclaimindustries.geohashdroid.R;
import net.exclaimindustries.geohashdroid.fragments.MapTypeDialogFragment;
import net.exclaimindustries.geohashdroid.util.GHDConstants;
/**
* This is just a base Activity that holds the permission-checking stuff shared
* between CentralMap and KnownLocationsPicker. Hooray for not cutting and
* pasting code!
*/
public abstract class BaseMapActivity
extends BaseGHDThemeActivity
implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
MapTypeDialogFragment.MapTypeCallback {
private static final String DEBUG_TAG = "BaseMapActivity";
protected GoogleApiClient mGoogleClient;
// Bool to track whether the app is already resolving an error.
protected boolean mResolvingError = false;
// Bool to track whether or not the user's refused permissions.
protected boolean mPermissionsDenied = false;
protected GoogleMap mMap;
/**
* This is a fragment used to display an error dialog, used by both map
* activities when they do their crazy permissions requesting stuff.
*/
public static class ErrorDialogFragment
extends DialogFragment {
/** Request code to use when launching the resolution activity. */
public static final int REQUEST_RESOLVE_ERROR = 1001;
/** A unique tag for the error dialog fragment. */
public static final String DIALOG_API_ERROR = "ApiErrorDialog";
public ErrorDialogFragment() {
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Get the error code and retrieve the appropriate dialog
int errorCode = this.getArguments().getInt(DIALOG_API_ERROR);
return GoogleApiAvailability.getInstance().getErrorDialog(
this.getActivity(), errorCode, REQUEST_RESOLVE_ERROR);
}
@Override
public void onDismiss(DialogInterface dialog) {
// Gee, I really hope this only gets called by BaseMapActivity!
((BaseMapActivity) getActivity()).onDialogDismissed();
}
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult result) {
// Oh, so THAT'S how the connection can fail: If we're using Marshmallow
// and the user refused to give permissions to the API or the user
// doesn't have the Google Play Services installed. Okay, that's fair.
// Let's deal with it, then.
if(!mResolvingError) {
if(result.hasResolution()) {
try {
mResolvingError = true;
result.startResolutionForResult(this, ErrorDialogFragment.REQUEST_RESOLVE_ERROR);
} catch(IntentSender.SendIntentException e) {
// We get this if something went wrong sending the intent. So,
// let's just try to connect again.
mGoogleClient.connect();
}
} else {
// If we can't actually resolve this, give up and throw an error.
// doReadyChecks() won't ever be called.
showErrorDialog(result.getErrorCode());
mResolvingError = true;
}
}
}
private void showErrorDialog(int errorCode) {
BaseMapActivity.ErrorDialogFragment dialogFragment = new BaseMapActivity.ErrorDialogFragment();
// Pass the error that should be displayed
Bundle args = new Bundle();
args.putInt(BaseMapActivity.ErrorDialogFragment.DIALOG_API_ERROR, errorCode);
dialogFragment.setArguments(args);
dialogFragment.show(getFragmentManager(), "errordialog");
}
public void onDialogDismissed() {
mResolvingError = false;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == BaseMapActivity.ErrorDialogFragment.REQUEST_RESOLVE_ERROR) {
mResolvingError = false;
if (resultCode == RESULT_OK) {
// Make sure the app is not already connected or attempting to connect
if (!mGoogleClient.isConnecting() &&
!mGoogleClient.isConnected()) {
mGoogleClient.connect();
}
}
}
}
/**
* <p>
* Checks for permissions on {@link Manifest.permission#ACCESS_FINE_LOCATION},
* automatically firing off the permission request if it hasn't been
* granted yet. This method DOES return, mind; if it returns true, continue
* as normal, and if it returns false, don't do anything. In the false
* case, it will (usually) ask for permissions, with CentralMap handling the
* callback.
* </p>
*
* <p>
* If skipRequest is set, permissions won't be asked for in the event that
* they're not already granted, and no explanation popup will show up,
* either. Use that for cases like shutdowns where all the listeners are
* being unregistered.
* </p>
*
* @param requestCode the type of check this is, so that whatever it was can be tried again on permissions being granted
* @param skipRequest if true, don't bother requesting permission, just drop it and go on
* @return true if permissions are good, false if not (in the false case, a request might be in progress)
*/
public synchronized boolean checkLocationPermissions(int requestCode, boolean skipRequest) {
// First, the easy case: Permissions granted.
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
// Yay!
return true;
} else {
// Boo! Now we need to fire off a permissions request! If we were
// already denied permissions once, though, don't bother trying
// again.
if(!skipRequest && !mPermissionsDenied)
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
requestCode);
return false;
}
}
/**
* Convenience method that calls {@link #checkLocationPermissions(int, boolean)}
* with skipRequest set to false.
*
* @param requestCode the type of check this is, so that whatever it was can be tried again on permissions being granted
* @return true if permissions are good, false if not (in the false case, a request might be in progress)
*/
public synchronized boolean checkLocationPermissions(int requestCode) {
return checkLocationPermissions(requestCode, false);
}
@Override
public void mapTypeSelected(int type) {
// 1 is night, -1 is day.
short becomesNight = 0;
// Map type!
if(mMap != null) {
switch(type) {
case GoogleMap.MAP_TYPE_NORMAL:
mMap.setMapStyle(null);
becomesNight = -1;
// Let's abuse a fallthrough!
case GoogleMap.MAP_TYPE_HYBRID:
case GoogleMap.MAP_TYPE_TERRAIN:
mMap.setMapType(type);
break;
case MapTypeDialogFragment.MAP_STYLE_NIGHT:
// Whoops, this one isn't a type. It's a style. First, the
// type has to be normal for this to work.
mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
// Then, load up the night style.
if(!mMap.setMapStyle(MapStyleOptions.loadRawResourceStyle(this, R.raw.map_night)))
Log.e(DEBUG_TAG, "Couldn't parse the map style JSON!");
becomesNight = 1;
break;
}
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor edit = prefs.edit();
edit.putInt(GHDConstants.PREF_LAST_MAP_TYPE, type);
edit.apply();
BackupManager bm = new BackupManager(this);
bm.dataChanged();
// Set the night only if it's changed at all.
if(becomesNight == 1) setNightMode(true);
else if(becomesNight == -1) setNightMode(false);
}
}