Fixes #2888: Use Dexter for requesting location permissions (#2925)

* Use dexter to ask for location permissions

* Addressed code review comments
This commit is contained in:
Vivek Maskara 2019-06-08 11:15:30 +05:30 committed by neslihanturan
parent 6f9d69e63c
commit 8971743479
6 changed files with 95 additions and 375 deletions

View file

@ -5,22 +5,10 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.database.Cursor; import android.database.Cursor;
import android.database.DataSetObserver; import android.database.DataSetObserver;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import androidx.cursoradapter.widget.CursorAdapter;
import androidx.appcompat.app.AlertDialog;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -28,6 +16,16 @@ import android.widget.Adapter;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.cursoradapter.widget.CursorAdapter;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import java.util.ArrayList; import java.util.ArrayList;
import javax.inject.Inject; import javax.inject.Inject;
@ -57,6 +55,7 @@ import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadService; import fr.free.nrw.commons.upload.UploadService;
import fr.free.nrw.commons.utils.ConfigUtils; import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.DialogUtil; import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
@ -66,7 +65,7 @@ import timber.log.Timber;
import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS; import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI; import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
import static fr.free.nrw.commons.location.LocationServiceManager.LOCATION_REQUEST; import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING; import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween; import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
@ -351,35 +350,6 @@ public class ContributionsFragment
} }
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult");
switch (requestCode) {
case LOCATION_REQUEST: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Timber.d("Location permission granted, refreshing view");
// No need to display permission request button anymore
locationManager.registerLocationManager();
} else {
if (store.getBoolean("displayLocationPermissionForCardView", true)) {
// Still ask for permission
DialogUtil.showAlertDialog(getActivity(),
getString(R.string.nearby_card_permission_title),
getString(R.string.nearby_card_permission_explanation),
this::displayYouWontSeeNearbyMessage,
this::enableLocationPermission,
checkBoxView,
false);
}
}
}
break;
default:
// This is needed to allow the request codes from the Fragments to be routed appropriately
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/** /**
* Replace whatever is in the current contributionsFragmentContainer view with * Replace whatever is in the current contributionsFragmentContainer view with
* mediaDetailPagerFragment, and preserve previous state in back stack. * mediaDetailPagerFragment, and preserve previous state in back stack.
@ -496,7 +466,7 @@ public class ContributionsFragment
if (store.getBoolean("displayNearbyCardView", true)) { if (store.getBoolean("displayNearbyCardView", true)) {
checkGPS(); checkPermissionsAndShowNearbyCardView();
if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) { if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
nearbyNotificationCardView.setVisibility(View.VISIBLE); nearbyNotificationCardView.setVisibility(View.VISIBLE);
} }
@ -509,77 +479,39 @@ public class ContributionsFragment
fetchCampaigns(); fetchCampaigns();
} }
/** private void checkPermissionsAndShowNearbyCardView() {
* Check GPS to decide displaying request permission button or not. if (PermissionUtils.hasPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)) {
*/ onLocationPermissionGranted();
private void checkGPS() { } else if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)
if (!locationManager.isProviderEnabled()) { && store.getBoolean("displayLocationPermissionForCardView", true)
Timber.d("GPS is not enabled"); && (((MainActivity) getActivity()).viewPager.getCurrentItem() == CONTRIBUTIONS_TAB_POSITION)) {
nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.ENABLE_GPS; nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.ENABLE_LOCATION_PERMISSION;
if (store.getBoolean("displayLocationPermissionForCardView", true)) { showNearbyCardPermissionRationale();
DialogUtil.showAlertDialog(getActivity(),
getString(R.string.nearby_card_permission_title),
getString(R.string.nearby_card_permission_explanation),
this::displayYouWontSeeNearbyMessage,
this::enableGPS,
checkBoxView,
false);
}
} else {
Timber.d("GPS is enabled");
checkLocationPermission();
} }
} }
private void checkLocationPermission() { private void requestLocationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PermissionUtils.checkPermissionsAndPerformAction(getActivity(),
if (locationManager.isLocationPermissionGranted(requireContext())) { Manifest.permission.ACCESS_FINE_LOCATION,
nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.NO_PERMISSION_NEEDED; this::onLocationPermissionGranted,
locationManager.registerLocationManager(); this::displayYouWontSeeNearbyMessage,
} else { -1,
nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.ENABLE_LOCATION_PERMISSION; -1);
// If user didn't selected Don't ask again
if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)
&& store.getBoolean("displayLocationPermissionForCardView", true)) {
DialogUtil.showAlertDialog(getActivity(),
getString(R.string.nearby_card_permission_title),
getString(R.string.nearby_card_permission_explanation),
this::displayYouWontSeeNearbyMessage,
this::enableLocationPermission,
checkBoxView,
false);
}
}
} else {
// If device is under Marshmallow, we already checked for GPS
nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.NO_PERMISSION_NEEDED;
locationManager.registerLocationManager();
}
} }
private void enableLocationPermission() { private void onLocationPermissionGranted() {
if (!getActivity().isFinishing()) { nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.NO_PERMISSION_NEEDED;
((MainActivity) getActivity()).locationManager.requestPermissions(getActivity()); locationManager.registerLocationManager();
}
} }
private void enableGPS() { private void showNearbyCardPermissionRationale() {
new AlertDialog.Builder(getActivity()) DialogUtil.showAlertDialog(getActivity(),
.setMessage(R.string.gps_disabled) getString(R.string.nearby_card_permission_title),
.setCancelable(false) getString(R.string.nearby_card_permission_explanation),
.setPositiveButton(R.string.enable_gps, this::displayYouWontSeeNearbyMessage,
(dialog, id) -> { this::requestLocationPermission,
Intent callGPSSettingIntent = new Intent( checkBoxView,
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS); false);
Timber.d("Loaded settings page");
((MainActivity) getActivity()).startActivityForResult(callGPSSettingIntent, 1);
})
.setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> {
dialog.cancel();
displayYouWontSeeNearbyMessage();
})
.create()
.show();
} }
private void displayYouWontSeeNearbyMessage() { private void displayYouWontSeeNearbyMessage() {
@ -589,7 +521,6 @@ public class ContributionsFragment
private void updateClosestNearbyCardViewInfo() { private void updateClosestNearbyCardViewInfo() {
curLatLng = locationManager.getLastLocation(); curLatLng = locationManager.getLastLocation();
compositeDisposable.add(Observable.fromCallable(() -> nearbyController compositeDisposable.add(Observable.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curLatLng, curLatLng, true, false)) // thanks to boolean, it will only return closest result .loadAttractionsFromLocation(curLatLng, curLatLng, true, false)) // thanks to boolean, it will only return closest result
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())

View file

@ -45,7 +45,6 @@ import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
import static android.content.ContentResolver.requestSync; import static android.content.ContentResolver.requestSync;
import static fr.free.nrw.commons.location.LocationServiceManager.LOCATION_REQUEST;
public class MainActivity extends AuthenticatedActivity implements FragmentManager.OnBackStackChangedListener { public class MainActivity extends AuthenticatedActivity implements FragmentManager.OnBackStackChangedListener {
@ -68,8 +67,8 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
public boolean isAuthCookieAcquired = false; public boolean isAuthCookieAcquired = false;
public ContributionsActivityPagerAdapter contributionsActivityPagerAdapter; public ContributionsActivityPagerAdapter contributionsActivityPagerAdapter;
public final int CONTRIBUTIONS_TAB_POSITION = 0; public static final int CONTRIBUTIONS_TAB_POSITION = 0;
public final int NEARBY_TAB_POSITION = 1; public static final int NEARBY_TAB_POSITION = 1;
public boolean isContributionsFragmentVisible = true; // False means nearby fragment is visible public boolean isContributionsFragmentVisible = true; // False means nearby fragment is visible
private Menu menu; private Menu menu;
@ -361,12 +360,6 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
} }
} }
private boolean deviceHasCamera() {
PackageManager pm = getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
}
public class ContributionsActivityPagerAdapter extends FragmentPagerAdapter { public class ContributionsActivityPagerAdapter extends FragmentPagerAdapter {
FragmentManager fragmentManager; FragmentManager fragmentManager;
private boolean isContributionsListFragment = true; // to know what to put in first tab, Contributions of Media Details private boolean isContributionsListFragment = true; // to know what to put in first tab, Contributions of Media Details
@ -450,15 +443,6 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
return "android:switcher:" + viewId + ":" + index; return "android:switcher:" + viewId + ":" + index;
} }
/**
* In first tab we can have ContributionsFragment or Media details fragment. This method
* is responsible to update related boolean
* @param isContributionsListFragment true when contribution fragment should be visible, false
* means user clicked to MediaDetails
*/
private void updateContributionFragmentTabContent(boolean isContributionsListFragment) {
this.isContributionsListFragment = isContributionsListFragment;
}
} }
@Override @Override
@ -468,27 +452,6 @@ public class MainActivity extends AuthenticatedActivity implements FragmentManag
controller.handleActivityResult(this, requestCode, resultCode, data); controller.handleActivityResult(this, requestCode, resultCode, data);
} }
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
if (requestCode == LOCATION_REQUEST) {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Timber.d("Location permission given");
((ContributionsFragment)contributionsActivityPagerAdapter
.getItem(0)).locationManager.registerLocationManager();
} else {
// If nearby fragment is visible and location permission is not given, send user back to contrib fragment
if (!isContributionsFragmentVisible) {
viewPager.setCurrentItem(CONTRIBUTIONS_TAB_POSITION);
// TODO: If contrib fragment is visible and location permission is not given, display permission request button
}
}
}
}
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();

View file

@ -1,19 +1,12 @@
package fr.free.nrw.commons.location; package fr.free.nrw.commons.location;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location; import android.location.Location;
import android.location.LocationListener; import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -44,78 +37,6 @@ public class LocationServiceManager implements LocationListener {
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
} }
/**
* Returns the current status of the location provider.
*
* @return true if the location provider is enabled
*/
public boolean isProviderEnabled() {
return (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER));
}
/**
* Returns whether the location permission is granted.
* @return true if the location permission is granted
*/
public boolean isLocationPermissionGranted(@NonNull Context context) {
return ContextCompat.checkSelfPermission(context,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
/**
* Requests the location permission to be granted.
*
* @param activity the activity
*/
public void requestPermissions(Activity activity) {
if (activity.isFinishing()) {
return;
}
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
LOCATION_REQUEST);
}
/**
* The permission explanation dialog box is now displayed just once for a particular activity. We are subscribing
* to updates from multiple providers so its important to show the dialog just once. Otherwise it will be displayed
* once for every provider, which in our case currently is 2.
* @param activity
* @return
*/
public boolean isPermissionExplanationRequired(Activity activity) {
if (activity.isFinishing()) {
return false;
}
boolean showRequestPermissionRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_FINE_LOCATION);
if (showRequestPermissionRationale && !locationExplanationDisplayed.contains(activity)) {
locationExplanationDisplayed.add(activity);
return true;
}
return false;
}
/**
* Gets the last known location in cases where there wasn't time to register a listener
* (e.g. when Location permission just granted)
* @return last known LatLng
*/
@SuppressLint("MissingPermission")
public LatLng getLKL(@NonNull Context context) {
if (isLocationPermissionGranted(context)) {
Location lastKL = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (lastKL == null) {
lastKL = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
if (lastKL == null) {
return null;
}
return LatLng.from(lastKL);
} else {
return null;
}
}
public LatLng getLastLocation() { public LatLng getLastLocation() {
if (lastLocation == null) { if (lastLocation == null) {
return null; return null;

View file

@ -1,20 +1,11 @@
package fr.free.nrw.commons.nearby; package fr.free.nrw.commons.nearby;
import android.Manifest;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.appcompat.app.AlertDialog;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -22,12 +13,19 @@ import android.widget.FrameLayout;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.snackbar.Snackbar;
import com.google.gson.Gson; import com.google.gson.Gson;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
@ -38,6 +36,7 @@ import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationUpdateListener; import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.utils.FragmentUtils; import fr.free.nrw.commons.utils.FragmentUtils;
import fr.free.nrw.commons.utils.NetworkUtils; import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.WikidataEditListener; import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Observable; import io.reactivex.Observable;
@ -45,6 +44,8 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import timber.log.Timber; import timber.log.Timber;
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
import static fr.free.nrw.commons.contributions.MainActivity.NEARBY_TAB_POSITION;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED; import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED; import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
@ -193,8 +194,8 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override @Override
public void onStateChanged(View bottomSheet, int newState) { public void onStateChanged(View bottomSheet, int unusedNewState) {
prepareViewsForSheetPosition(newState); prepareViewsForSheetPosition();
} }
@Override @Override
@ -210,9 +211,8 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
/** /**
* Sets camera position, zoom level according to sheet positions * Sets camera position, zoom level according to sheet positions
* @param bottomSheetState expanded, collapsed or hidden
*/ */
public void prepareViewsForSheetPosition(int bottomSheetState) { private void prepareViewsForSheetPosition() {
// TODO // TODO
} }
@ -246,7 +246,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
* *
* @param locationChangeType defines if location changed significantly or slightly * @param locationChangeType defines if location changed significantly or slightly
*/ */
public void refreshView(LocationServiceManager.LocationChangeType locationChangeType) { private void refreshView(LocationServiceManager.LocationChangeType locationChangeType) {
Timber.d("Refreshing nearby places"); Timber.d("Refreshing nearby places");
if (lockNearbyView) { if (lockNearbyView) {
return; return;
@ -324,7 +324,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
* button. It populates places for custom location. * button. It populates places for custom location.
* @param customLatLng Custom area which we will search around * @param customLatLng Custom area which we will search around
*/ */
public void refreshViewForCustomLocation(LatLng customLatLng, boolean refreshForCurrentLocation) { void refreshViewForCustomLocation(LatLng customLatLng, boolean refreshForCurrentLocation) {
if (customLatLng == null) { if (customLatLng == null) {
// If null, return // If null, return
return; return;
@ -568,130 +568,7 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
* This method first checks if the location permissions has been granted and then register the location manager for updates. * This method first checks if the location permissions has been granted and then register the location manager for updates.
*/ */
private void registerLocationUpdates() { private void registerLocationUpdates() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { locationManager.registerLocationManager();
if (locationManager.isLocationPermissionGranted(requireContext())) {
locationManager.registerLocationManager();
} else {
// Should we show an explanation?
if (locationManager.isPermissionExplanationRequired(getActivity())) {
new AlertDialog.Builder(getActivity())
.setMessage(getString(R.string.location_permission_rationale_nearby))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
requestLocationPermissions();
dialog.dismiss();
})
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
showLocationPermissionDeniedErrorDialog();
dialog.cancel();
})
.create()
.show();
} else {
// No explanation needed, we can request the permission.
requestLocationPermissions();
}
}
} else {
locationManager.registerLocationManager();
}
}
/**
* Requests location permission if activity is not null
*/
private void requestLocationPermissions() {
if (!getActivity().isFinishing()) {
locationManager.requestPermissions(getActivity());
}
}
/**
* Will warn user if location is denied
*/
private void showLocationPermissionDeniedErrorDialog() {
new AlertDialog.Builder(getActivity())
.setMessage(R.string.nearby_needs_permissions)
.setCancelable(false)
.setPositiveButton(R.string.give_permission, (dialog, which) -> {
//will ask for the location permission again
checkGps();
})
.setNegativeButton(R.string.cancel, (dialog, which) -> {
//dismiss dialog and send user to contributions tab instead
dialog.cancel();
((MainActivity)getActivity()).viewPager.setCurrentItem(((MainActivity)getActivity()).CONTRIBUTIONS_TAB_POSITION);
})
.create()
.show();
}
/**
* Checks device GPS permission first for all API levels
*/
private void checkGps() {
Timber.d("checking GPS");
if (!locationManager.isProviderEnabled()) {
Timber.d("GPS is not enabled");
new AlertDialog.Builder(getActivity())
.setMessage(R.string.gps_disabled)
.setCancelable(false)
.setPositiveButton(R.string.enable_gps,
(dialog, id) -> {
Intent callGPSSettingIntent = new Intent(
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
Timber.d("Loaded settings page");
startActivityForResult(callGPSSettingIntent, 1);
})
.setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> {
showLocationPermissionDeniedErrorDialog();
dialog.cancel();
})
.create()
.show();
} else {
Timber.d("GPS is enabled");
checkLocationPermission();
}
}
/**
* This method ideally should be called from inside of CheckGPS method. If device GPS is enabled
* then we need to control app specific permissions for >=M devices. For other devices, enabled
* GPS is enough for nearby, so directly call refresh view.
*/
private void checkLocationPermission() {
Timber.d("Checking location permission");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (locationManager.isLocationPermissionGranted(requireContext())) {
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
} else {
// Should we show an explanation?
if (locationManager.isPermissionExplanationRequired(getActivity())) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
new AlertDialog.Builder(getActivity())
.setMessage(getString(R.string.location_permission_rationale_nearby))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
requestLocationPermissions();
dialog.dismiss();
})
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
showLocationPermissionDeniedErrorDialog();
dialog.cancel();
})
.create()
.show();
} else {
// No explanation needed, we can request the permission.
requestLocationPermissions();
}
}
} else {
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
}
} }
private void showErrorMessage(String message) { private void showErrorMessage(String message) {
@ -748,8 +625,12 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
// Resume the fragment if exist // Resume the fragment if exist
resumeFragment(); if (((MainActivity) getActivity()).viewPager.getCurrentItem() == NEARBY_TAB_POSITION) {
checkPermissionsAndPerformAction(this::resumeFragment);
} else {
resumeFragment();
} }
}
/** /**
* Perform nearby operations on nearby tab selected * Perform nearby operations on nearby tab selected
@ -758,8 +639,16 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
public void onTabSelected(boolean onOrientationChanged) { public void onTabSelected(boolean onOrientationChanged) {
Timber.d("On nearby tab selected"); Timber.d("On nearby tab selected");
this.onOrientationChanged = onOrientationChanged; this.onOrientationChanged = onOrientationChanged;
performNearbyOperations(); checkPermissionsAndPerformAction(this::performNearbyOperations);
}
private void checkPermissionsAndPerformAction(Runnable runnable) {
PermissionUtils.checkPermissionsAndPerformAction(getActivity(),
Manifest.permission.ACCESS_FINE_LOCATION,
runnable,
() -> ((MainActivity) getActivity()).viewPager.setCurrentItem(CONTRIBUTIONS_TAB_POSITION),
R.string.location_permission_title,
R.string.location_permission_rationale_nearby);
} }
/** /**
@ -769,8 +658,8 @@ public class NearbyFragment extends CommonsDaggerSupportFragment
locationManager.addLocationListener(this); locationManager.addLocationListener(this);
registerLocationUpdates(); registerLocationUpdates();
lockNearbyView = false; lockNearbyView = false;
checkGps();
addNetworkBroadcastReceiver(); addNetworkBroadcastReceiver();
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
} }
@Override @Override

View file

@ -5,17 +5,17 @@ import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.provider.Settings; import android.provider.Settings;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.karumi.dexter.Dexter; import com.karumi.dexter.Dexter;
import com.karumi.dexter.PermissionToken; import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionDeniedResponse; import com.karumi.dexter.listener.PermissionDeniedResponse;
import com.karumi.dexter.listener.PermissionGrantedResponse; import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.PermissionRequest; import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.single.BasePermissionListener; import com.karumi.dexter.listener.single.BasePermissionListener;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
@ -27,7 +27,7 @@ public class PermissionUtils {
It open the app settings from where the user can manually give us the required permission. It open the app settings from where the user can manually give us the required permission.
* @param activity * @param activity
*/ */
public static void askUserToManuallyEnablePermissionFromSettings(Activity activity) { private static void askUserToManuallyEnablePermissionFromSettings(Activity activity) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", activity.getPackageName(), null); Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri); intent.setData(uri);
@ -50,6 +50,9 @@ public class PermissionUtils {
* Checks for a particular permission and runs the runnable to perform an action when the permission is granted * Checks for a particular permission and runs the runnable to perform an action when the permission is granted
* Also, it shows a rationale if needed * Also, it shows a rationale if needed
* *
* rationaleTitle and rationaleMessage can be invalid @StringRes. If the value is -1 then no permission rationale
* will be displayed and permission would be requested
*
* Sample usage: * Sample usage:
* *
* PermissionUtils.checkPermissionsAndPerformAction(activity, * PermissionUtils.checkPermissionsAndPerformAction(activity,
@ -58,12 +61,20 @@ public class PermissionUtils {
* R.string.storage_permission_title, * R.string.storage_permission_title,
* R.string.write_storage_permission_rationale); * R.string.write_storage_permission_rationale);
* *
* If you don't want the permission rationale to be shown then use:
*
* PermissionUtils.checkPermissionsAndPerformAction(activity,
* Manifest.permission.WRITE_EXTERNAL_STORAGE,
* () -> initiateCameraUpload(activity),
* - 1, -1);
*
*
* *
* @param activity activity requesting permissions * @param activity activity requesting permissions
* @param permission the permission being requests * @param permission the permission being requests
* @param onPermissionGranted the runnable to be executed when the permission is granted * @param onPermissionGranted the runnable to be executed when the permission is granted
* @param rationaleTitle rationale title to be displayed when permission was denied * @param rationaleTitle rationale title to be displayed when permission was denied. It can be an invalid @StringRes
* @param rationaleMessage rationale message to be displayed when permission was denied * @param rationaleMessage rationale message to be displayed when permission was denied. It can be an invalid @StringRes
*/ */
public static void checkPermissionsAndPerformAction(Activity activity, String permission, public static void checkPermissionsAndPerformAction(Activity activity, String permission,
Runnable onPermissionGranted, @StringRes int rationaleTitle, Runnable onPermissionGranted, @StringRes int rationaleTitle,
@ -93,7 +104,6 @@ public class PermissionUtils {
* @param rationaleTitle rationale title to be displayed when permission was denied * @param rationaleTitle rationale title to be displayed when permission was denied
* @param rationaleMessage rationale message to be displayed when permission was denied * @param rationaleMessage rationale message to be displayed when permission was denied
*/ */
public static void checkPermissionsAndPerformAction(Activity activity, String permission, public static void checkPermissionsAndPerformAction(Activity activity, String permission,
Runnable onPermissionGranted, Runnable onPermissionDenied, @StringRes int rationaleTitle, Runnable onPermissionGranted, Runnable onPermissionDenied, @StringRes int rationaleTitle,
@StringRes int rationaleMessage) { @StringRes int rationaleMessage) {
@ -120,6 +130,10 @@ public class PermissionUtils {
@Override @Override
public void onPermissionRationaleShouldBeShown(PermissionRequest permission, public void onPermissionRationaleShouldBeShown(PermissionRequest permission,
PermissionToken token) { PermissionToken token) {
if (rationaleTitle == -1 && rationaleMessage == -1) {
token.continuePermissionRequest();
return;
}
DialogUtil.showAlertDialog(activity, activity.getString(rationaleTitle), DialogUtil.showAlertDialog(activity, activity.getString(rationaleTitle),
activity.getString(rationaleMessage), activity.getString(rationaleMessage),
activity.getString(android.R.string.ok), activity.getString(android.R.string.ok),
@ -129,4 +143,5 @@ public class PermissionUtils {
}) })
.check(); .check();
} }
} }

View file

@ -166,6 +166,7 @@
<string name="storage_permission_title">Requesting Storage Permission</string> <string name="storage_permission_title">Requesting Storage Permission</string>
<string name="read_storage_permission_rationale">Required permission: Read external storage. App cannot access your gallery without this.</string> <string name="read_storage_permission_rationale">Required permission: Read external storage. App cannot access your gallery without this.</string>
<string name="write_storage_permission_rationale">Required permission: Write external storage. App cannot access your camera/gallery without this.</string> <string name="write_storage_permission_rationale">Required permission: Write external storage. App cannot access your camera/gallery without this.</string>
<string name="location_permission_title">Requesting Location Permission</string>
<string name="location_permission_rationale">Optional permission: Get current location for category suggestions</string> <string name="location_permission_rationale">Optional permission: Get current location for category suggestions</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="title_activity_nearby">Nearby Places</string> <string name="title_activity_nearby">Nearby Places</string>