package de.westnordost.streetcomplete.location; import android.Manifest; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.Bundle; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import de.westnordost.streetcomplete.R; import de.westnordost.streetcomplete.view.dialogs.AlertDialogBuilder; import static android.location.LocationManager.PROVIDERS_CHANGED_ACTION; import static de.westnordost.streetcomplete.location.LocationUtil.MODE_CHANGED; /** Manages the process to ensure that the app can access the user's location. Two steps: * <ol> * <li>ask for permission</li> * <li>ask for location to be turned on</li> * </ol> * * This fragment reports back to the Activity it is attached to via LocationRequestListener. * The process is started via {@link #startRequest()} */ public class LocationRequestFragment extends Fragment { private static final int LOCATION_PERMISSION_REQUEST = 1; private static final int LOCATION_TURN_ON_REQUEST = 2; public interface LocationRequestListener { void onLocationRequestFinished(LocationState withLocationState); } private LocationRequestListener callbackListener; private LocationState state; private boolean inProgress; private BroadcastReceiver locationProviderChangedReceiver; public LocationRequestFragment() { super(); state = null; } /** Start location request process. When already started, will not be started again. */ public void startRequest() { if(!inProgress) { inProgress = true; state = null; nextStep(); } } private void nextStep() { if(state == null || state == LocationState.DENIED) { requestLocationPermissions(); } else if(state == LocationState.ALLOWED) { requestLocationSettingsToBeOn(); } else if(state == LocationState.ENABLED) { finish(); } } private void finish() { inProgress = false; callbackListener.onLocationRequestFinished(state); } @Override public void onAttach(Context context) { super.onAttach(context); callbackListener = (LocationRequestListener) context; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { // must be for someone else... if(requestCode != LOCATION_PERMISSION_REQUEST) return; if(permissions.length == 0 || !permissions[0].equals(Manifest.permission.ACCESS_FINE_LOCATION)) return; if(grantResults[0] == PackageManager.PERMISSION_GRANTED) { requestLocationPermissions(); // retry then... } else { new AlertDialogBuilder(getContext()) .setMessage(R.string.no_location_permission_warning) .setPositiveButton(R.string.retry, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { requestLocationPermissions(); } }) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { state = LocationState.DENIED; finish(); } }) .setOnCancelListener( new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { state = LocationState.DENIED; finish(); } } ) .show(); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { // must be for someone else... if(requestCode != LOCATION_TURN_ON_REQUEST) return; // we ignore the resultCode, because we always get Activity.RESULT_CANCELED. Instead, we // check if the conditions are fulfilled now requestLocationSettingsToBeOn(); } private void requestLocationPermissions() { if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { state = LocationState.ALLOWED; nextStep(); } else { requestPermissions(new String[]{ Manifest.permission.ACCESS_FINE_LOCATION }, LOCATION_PERMISSION_REQUEST); } } private void requestLocationSettingsToBeOn() { if(LocationUtil.isLocationSettingsOn(getContext())) { state = LocationState.ENABLED; nextStep(); } else { final AlertDialog dlg = new AlertDialogBuilder(getContext()) .setMessage(R.string.turn_on_location_request) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); Intent viewIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); startActivityForResult(viewIntent, LOCATION_TURN_ON_REQUEST); } }) .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { cancelTurnLocationOnDialog(); } }) .setOnCancelListener( new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { cancelTurnLocationOnDialog(); } } ).create(); // the user may turn on location in the pull-down-overlay, without actually going into // settings dialog registerForLocationProviderChanges(dlg); dlg.show(); } } private void cancelTurnLocationOnDialog() { unregisterForLocationProviderChanges(); finish(); } private void registerForLocationProviderChanges(final AlertDialog dlg) { locationProviderChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { dlg.dismiss(); unregisterForLocationProviderChanges(); requestLocationSettingsToBeOn(); } }; String name = LocationUtil.isNewLocationApi() ? MODE_CHANGED : PROVIDERS_CHANGED_ACTION; getActivity().registerReceiver(locationProviderChangedReceiver, new IntentFilter(name)); } private void unregisterForLocationProviderChanges() { if(locationProviderChangedReceiver != null) { getActivity().unregisterReceiver(locationProviderChangedReceiver); locationProviderChangedReceiver = null; } } @Override public void onStop() { super.onStop(); unregisterForLocationProviderChanges(); } public LocationState getState() { return state != null ? state : LocationState.DENIED; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if(state != null) outState.putString("locationState", state.name()); outState.putBoolean("inProgress", inProgress); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if(savedInstanceState != null) { String stateName = savedInstanceState.getString("locationState"); if(stateName != null) state = LocationState.valueOf(stateName); inProgress = savedInstanceState.getBoolean("inProgress"); } } }