weakPermissionListener;
25 | private final String[] requiredPermissions;
26 | private DialogProvider rationalDialogProvider;
27 |
28 | /**
29 | * This class is responsible to get required permissions, and notify {@linkplain LocationManager}.
30 | *
31 | * @param requiredPermissions are required, setting this field empty will {@throws IllegalStateException}
32 | * @param rationaleDialogProvider will be used to display rationale dialog when it is necessary. If this field is set
33 | * to null, then rationale dialog will not be displayed to user at all.
34 | */
35 | public PermissionProvider(String[] requiredPermissions, @Nullable DialogProvider rationaleDialogProvider) {
36 | if (requiredPermissions == null || requiredPermissions.length == 0) {
37 | throw new IllegalStateException("You cannot create PermissionProvider without any permission required.");
38 | }
39 |
40 | this.requiredPermissions = requiredPermissions;
41 | this.rationalDialogProvider = rationaleDialogProvider;
42 | }
43 |
44 | /**
45 | * Return true if it is possible to ask permission, false otherwise
46 | */
47 | public abstract boolean requestPermissions();
48 |
49 | /**
50 | * This method needs to be called when permission results are received
51 | */
52 | public abstract void onRequestPermissionsResult(int requestCode,
53 | @Nullable String[] permissions, @NonNull int[] grantResults);
54 |
55 | public String[] getRequiredPermissions() {
56 | return requiredPermissions;
57 | }
58 |
59 | @Nullable public DialogProvider getDialogProvider() {
60 | return rationalDialogProvider;
61 | }
62 |
63 | @Nullable public PermissionListener getPermissionListener() {
64 | return weakPermissionListener.get();
65 | }
66 |
67 | @Nullable protected Context getContext() {
68 | return weakContextProcessor.get() == null ? null : weakContextProcessor.get().getContext();
69 | }
70 |
71 | @Nullable protected Activity getActivity() {
72 | return weakContextProcessor.get() == null ? null : weakContextProcessor.get().getActivity();
73 | }
74 |
75 | @Nullable protected Fragment getFragment() {
76 | return weakContextProcessor.get() == null ? null : weakContextProcessor.get().getFragment();
77 | }
78 |
79 | /**
80 | * This will be set internally by {@linkplain LocationManager} before any call is executed on PermissionProvider
81 | */
82 | @CallSuper
83 | public void setContextProcessor(ContextProcessor contextProcessor) {
84 | this.weakContextProcessor = new WeakReference<>(contextProcessor);
85 | }
86 |
87 | /**
88 | * This will be set internally by {@linkplain LocationManager} before any call is executed on PermissionProvider
89 | */
90 | @CallSuper public void setPermissionListener(PermissionListener permissionListener) {
91 | this.weakPermissionListener = new WeakReference<>(permissionListener);
92 | }
93 |
94 | /**
95 | * Return true if required permissions are granted, false otherwise
96 | */
97 | public boolean hasPermission() {
98 | if (getContext() == null) {
99 | LogUtils.logE("Couldn't check whether permissions are granted or not "
100 | + "because of PermissionProvider doesn't contain any context.");
101 | return false;
102 | }
103 |
104 | for (String permission : getRequiredPermissions()) {
105 | if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
106 | return false;
107 | }
108 | }
109 | return true;
110 | }
111 |
112 | // For test purposes
113 | protected int checkSelfPermission(String permission) {
114 | return ContextCompat.checkSelfPermission(getContext(), permission);
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/library/src/main/java/com/yayandroid/locationmanager/providers/permissionprovider/DefaultPermissionProvider.java:
--------------------------------------------------------------------------------
1 | package com.yayandroid.locationmanager.providers.permissionprovider;
2 |
3 | import android.content.pm.PackageManager;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.annotation.Nullable;
7 |
8 | import com.yayandroid.locationmanager.constants.RequestCode;
9 | import com.yayandroid.locationmanager.helper.LogUtils;
10 | import com.yayandroid.locationmanager.listener.DialogListener;
11 | import com.yayandroid.locationmanager.providers.dialogprovider.DialogProvider;
12 |
13 | public class DefaultPermissionProvider extends PermissionProvider implements DialogListener {
14 |
15 | private PermissionCompatSource permissionCompatSource;
16 |
17 | public DefaultPermissionProvider(String[] requiredPermissions, @Nullable DialogProvider dialogProvider) {
18 | super(requiredPermissions, dialogProvider);
19 | }
20 |
21 | @Override
22 | public boolean requestPermissions() {
23 | if (getActivity() == null) {
24 | LogUtils.logI("Cannot ask for permissions, "
25 | + "because DefaultPermissionProvider doesn't contain an Activity instance.");
26 | return false;
27 | }
28 |
29 | if (shouldShowRequestPermissionRationale()) {
30 | getDialogProvider().setDialogListener(this);
31 | getDialogProvider().getDialog(getActivity()).show();
32 | } else {
33 | executePermissionsRequest();
34 | }
35 |
36 | return true;
37 | }
38 |
39 | @Override
40 | public void onRequestPermissionsResult(int requestCode, String[] permissions, @NonNull int[] grantResults) {
41 | if (requestCode == RequestCode.RUNTIME_PERMISSION) {
42 |
43 | // Check if any of required permissions are denied.
44 | boolean isDenied = false;
45 | for (int i = 0, size = permissions.length; i < size; i++) {
46 | if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
47 | isDenied = true;
48 | }
49 | }
50 |
51 | if (isDenied) {
52 | LogUtils.logI("User denied some of required permissions, task will be aborted!");
53 | if (getPermissionListener() != null) getPermissionListener().onPermissionsDenied();
54 | } else {
55 | LogUtils.logI("We got all required permission!");
56 | if (getPermissionListener() != null) getPermissionListener().onPermissionsGranted();
57 | }
58 | }
59 | }
60 |
61 | @Override
62 | public void onPositiveButtonClick() {
63 | executePermissionsRequest();
64 | }
65 |
66 | @Override
67 | public void onNegativeButtonClick() {
68 | LogUtils.logI("User didn't even let us to ask for permission!");
69 | if (getPermissionListener() != null) getPermissionListener().onPermissionsDenied();
70 | }
71 |
72 | boolean shouldShowRequestPermissionRationale() {
73 | boolean shouldShowRationale = false;
74 | for (String permission : getRequiredPermissions()) {
75 | shouldShowRationale = shouldShowRationale || checkRationaleForPermission(permission);
76 | }
77 |
78 | LogUtils.logI("Should show rationale dialog for required permissions: " + shouldShowRationale);
79 |
80 | return shouldShowRationale && getActivity() != null && getDialogProvider() != null;
81 | }
82 |
83 | boolean checkRationaleForPermission(String permission) {
84 | if (getFragment() != null) {
85 | return getPermissionCompatSource().shouldShowRequestPermissionRationale(getFragment(), permission);
86 | } else if (getActivity() != null) {
87 | return getPermissionCompatSource().shouldShowRequestPermissionRationale(getActivity(), permission);
88 | } else {
89 | return false;
90 | }
91 | }
92 |
93 | void executePermissionsRequest() {
94 | LogUtils.logI("Asking for Runtime Permissions...");
95 | if (getFragment() != null) {
96 | getPermissionCompatSource().requestPermissions(getFragment(),
97 | getRequiredPermissions(), RequestCode.RUNTIME_PERMISSION);
98 | } else if (getActivity() != null) {
99 | getPermissionCompatSource().requestPermissions(getActivity(),
100 | getRequiredPermissions(), RequestCode.RUNTIME_PERMISSION);
101 | } else {
102 | LogUtils.logE("Something went wrong requesting for permissions.");
103 | if (getPermissionListener() != null) getPermissionListener().onPermissionsDenied();
104 | }
105 | }
106 |
107 | // For test purposes
108 | void setPermissionCompatSource(PermissionCompatSource permissionCompatSource) {
109 | this.permissionCompatSource = permissionCompatSource;
110 | }
111 |
112 | protected PermissionCompatSource getPermissionCompatSource() {
113 | if (permissionCompatSource == null) {
114 | permissionCompatSource = new PermissionCompatSource();
115 | }
116 | return permissionCompatSource;
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/library/src/test/java/com/yayandroid/locationmanager/helper/continuoustask/ContinuousTaskSchedulerTest.java:
--------------------------------------------------------------------------------
1 | package com.yayandroid.locationmanager.helper.continuoustask;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 | import org.mockito.Mock;
6 | import org.mockito.MockitoAnnotations;
7 |
8 | import static org.assertj.core.api.Assertions.assertThat;
9 | import static org.mockito.Mockito.never;
10 | import static org.mockito.Mockito.verify;
11 | import static org.mockito.Mockito.verifyNoMoreInteractions;
12 | import static org.mockito.Mockito.when;
13 |
14 | public class ContinuousTaskSchedulerTest {
15 | private static final long INITIAL_TIME = 10000L;
16 | private static final long DELAY = 1000L;
17 | private static final long DURATION = 50L;
18 |
19 | @Mock ContinuousTask continuousTask;
20 | private ContinuousTaskScheduler continuousTaskScheduler;
21 |
22 | @Before
23 | public void setUp() throws Exception {
24 | MockitoAnnotations.initMocks(this);
25 |
26 | continuousTaskScheduler = new ContinuousTaskScheduler(continuousTask);
27 |
28 | when(continuousTask.getCurrentTime()).thenReturn(INITIAL_TIME);
29 | }
30 |
31 | @Test
32 | public void whenDelayedNotCalledIsSetShouldReturnFalse() throws Exception {
33 | assertThat(continuousTaskScheduler.isSet()).isFalse();
34 | }
35 |
36 | @Test
37 | public void whenDelayedCalledIsSetShouldReturnTrue() throws Exception {
38 | continuousTaskScheduler.delayed(0);
39 | assertThat(continuousTaskScheduler.isSet()).isTrue();
40 | }
41 |
42 | @Test
43 | public void whenOnPauseCalledIsSetShouldReturnFalse() throws Exception {
44 | continuousTaskScheduler.delayed(0);
45 | continuousTaskScheduler.onPause();
46 | assertThat(continuousTaskScheduler.isSet()).isFalse();
47 | }
48 |
49 | @Test
50 | public void whenOnResumeCalledIsSetShouldReturnTrue() throws Exception {
51 | continuousTaskScheduler.delayed(0);
52 | continuousTaskScheduler.onResume();
53 | assertThat(continuousTaskScheduler.isSet()).isTrue();
54 | }
55 |
56 | @Test
57 | public void whenOnStopCalledIsSetShouldReturnFalse() throws Exception {
58 | continuousTaskScheduler.delayed(0);
59 | continuousTaskScheduler.onStop();
60 | assertThat(continuousTaskScheduler.isSet()).isFalse();
61 | }
62 |
63 | @Test
64 | public void whenCleanCalledIsSetShouldReturnFalse() throws Exception {
65 | continuousTaskScheduler.delayed(0);
66 | continuousTaskScheduler.clean();
67 | assertThat(continuousTaskScheduler.isSet()).isFalse();
68 | }
69 |
70 | @Test
71 | public void whenDelayedNotCalledTaskShouldHaveNoInteractionOnPauseAndResume() throws Exception {
72 | continuousTaskScheduler.onPause();
73 | verify(continuousTask, never()).unregister();
74 |
75 | continuousTaskScheduler.set(0);
76 | verify(continuousTask, never()).delayed(0);
77 | }
78 |
79 | @Test
80 | public void whenDelayedCalledTaskShouldSchedule() throws Exception {
81 | continuousTaskScheduler.delayed(DELAY);
82 | verify(continuousTask).schedule(DELAY);
83 | }
84 |
85 | @Test
86 | public void whenOnPauseCalledTaskShouldUnregister() throws Exception {
87 | continuousTaskScheduler.delayed(DELAY);
88 | continuousTaskScheduler.onPause();
89 | verify(continuousTask).unregister();
90 | }
91 |
92 | @Test
93 | public void whenOnResumeCalledTaskShouldReScheduled() throws Exception {
94 | continuousTaskScheduler.delayed(DELAY);
95 | verify(continuousTask).schedule(DELAY);
96 |
97 | when(continuousTask.getCurrentTime()).thenReturn(INITIAL_TIME + DURATION);
98 | continuousTaskScheduler.onPause();
99 | verify(continuousTask).unregister();
100 |
101 | continuousTaskScheduler.onResume();
102 | verify(continuousTask).schedule(DELAY - DURATION);
103 | }
104 |
105 | @Test
106 | public void whenOnStopCalledTaskShouldHaveNoInteractionOnPauseAndResume() throws Exception {
107 | continuousTaskScheduler.delayed(0);
108 | verify(continuousTask).getCurrentTime();
109 | verify(continuousTask).schedule(0);
110 |
111 | continuousTaskScheduler.onStop();
112 | verify(continuousTask).unregister();
113 |
114 | continuousTaskScheduler.onPause();
115 | continuousTaskScheduler.onResume();
116 | verifyNoMoreInteractions(continuousTask);
117 | }
118 |
119 | @Test
120 | public void whenCleanCalledTaskShouldHaveNoInteractionOnPauseAndResume() throws Exception {
121 | continuousTaskScheduler.delayed(0);
122 | verify(continuousTask).getCurrentTime();
123 | verify(continuousTask).schedule(0);
124 |
125 | continuousTaskScheduler.clean();
126 |
127 | continuousTaskScheduler.onPause();
128 | continuousTaskScheduler.onResume();
129 | verifyNoMoreInteractions(continuousTask);
130 | }
131 |
132 | @Test
133 | public void whenTaskIsAlreadyScheduledOnResumeShouldHaveNoInteraction() throws Exception {
134 | continuousTaskScheduler.delayed(0);
135 | verify(continuousTask).getCurrentTime();
136 | verify(continuousTask).schedule(0);
137 |
138 | continuousTaskScheduler.onResume();
139 | verifyNoMoreInteractions(continuousTask);
140 | }
141 |
142 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/library/src/main/java/com/yayandroid/locationmanager/configuration/GooglePlayServicesConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.yayandroid.locationmanager.configuration;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | import com.google.android.gms.location.LocationRequest;
6 | import com.yayandroid.locationmanager.providers.locationprovider.DefaultLocationProvider;
7 | import com.yayandroid.locationmanager.providers.locationprovider.GooglePlayServicesLocationProvider;
8 |
9 | public class GooglePlayServicesConfiguration {
10 |
11 | private final LocationRequest locationRequest;
12 | private final boolean fallbackToDefault;
13 | private final boolean askForGooglePlayServices;
14 | private final boolean askForSettingsApi;
15 | private final boolean failOnSettingsApiSuspended;
16 | private final boolean ignoreLastKnowLocation;
17 | private final long googlePlayServicesWaitPeriod;
18 |
19 | private GooglePlayServicesConfiguration(Builder builder) {
20 | this.locationRequest = builder.locationRequest;
21 | this.fallbackToDefault = builder.fallbackToDefault;
22 | this.askForGooglePlayServices = builder.askForGooglePlayServices;
23 | this.askForSettingsApi = builder.askForSettingsApi;
24 | this.failOnSettingsApiSuspended = builder.failOnSettingsApiSuspended;
25 | this.ignoreLastKnowLocation = builder.ignoreLastKnowLocation;
26 | this.googlePlayServicesWaitPeriod = builder.googlePlayServicesWaitPeriod;
27 | }
28 |
29 | public GooglePlayServicesConfiguration.Builder newBuilder() {
30 | return new GooglePlayServicesConfiguration.Builder()
31 | .locationRequest(locationRequest)
32 | .fallbackToDefault(fallbackToDefault)
33 | .askForGooglePlayServices(askForGooglePlayServices)
34 | .askForSettingsApi(askForSettingsApi)
35 | .failOnSettingsApiSuspended(failOnSettingsApiSuspended)
36 | .ignoreLastKnowLocation(ignoreLastKnowLocation)
37 | .setWaitPeriod(googlePlayServicesWaitPeriod);
38 | }
39 |
40 | // region Getters
41 | public LocationRequest locationRequest() {
42 | return locationRequest;
43 | }
44 |
45 | public boolean fallbackToDefault() {
46 | return fallbackToDefault;
47 | }
48 |
49 | public boolean askForGooglePlayServices() {
50 | return askForGooglePlayServices;
51 | }
52 |
53 | public boolean askForSettingsApi() {
54 | return askForSettingsApi;
55 | }
56 |
57 | public boolean failOnSettingsApiSuspended() {
58 | return failOnSettingsApiSuspended;
59 | }
60 |
61 | public boolean ignoreLastKnowLocation() {
62 | return ignoreLastKnowLocation;
63 | }
64 |
65 | public long googlePlayServicesWaitPeriod() {
66 | return googlePlayServicesWaitPeriod;
67 | }
68 |
69 | // endregion
70 |
71 | public static class Builder {
72 |
73 | private LocationRequest locationRequest = Defaults.createDefaultLocationRequest();
74 | private boolean fallbackToDefault = Defaults.FALLBACK_TO_DEFAULT;
75 | private boolean askForGooglePlayServices = Defaults.ASK_FOR_GP_SERVICES;
76 | private boolean askForSettingsApi = Defaults.ASK_FOR_SETTINGS_API;
77 | private boolean failOnSettingsApiSuspended = Defaults.FAIL_ON_SETTINGS_API_SUSPENDED;
78 | private boolean ignoreLastKnowLocation = Defaults.IGNORE_LAST_KNOW_LOCATION;
79 | private long googlePlayServicesWaitPeriod = Defaults.WAIT_PERIOD;
80 |
81 | /**
82 | * LocationRequest object that you specified to use while getting location from Google Play Services
83 | * Default is {@linkplain Defaults#createDefaultLocationRequest()}
84 | */
85 | public Builder locationRequest(@NonNull LocationRequest locationRequest) {
86 | this.locationRequest = locationRequest;
87 | return this;
88 | }
89 |
90 | /**
91 | * In case of getting location from {@linkplain GooglePlayServicesLocationProvider} fails,
92 | * library will fallback to {@linkplain DefaultLocationProvider} as a default behaviour.
93 | * If you set this to false, then library will notify fail as soon as GooglePlayServicesLocationProvider fails.
94 | */
95 | public Builder fallbackToDefault(boolean fallbackToDefault) {
96 | this.fallbackToDefault = fallbackToDefault;
97 | return this;
98 | }
99 |
100 | /**
101 | * Set true to ask user handle when there is some resolvable error
102 | * on connection GooglePlayServices, if you don't want to bother user
103 | * to configure Google Play Services to receive location then set this as false.
104 | *
105 | * Default is False.
106 | */
107 | public Builder askForGooglePlayServices(boolean askForGooglePlayServices) {
108 | this.askForGooglePlayServices = askForGooglePlayServices;
109 | return this;
110 | }
111 |
112 | /**
113 | * While trying to get location via GooglePlayServices LocationApi,
114 | * manager will check whether GPS, Wifi and Cell networks are available or not.
115 | * Then if this flag is on it will ask user to turn them on, again, via GooglePlayServices
116 | * by displaying a system dialog if not it will directly try to receive location
117 | * -which probably not going to return any values.
118 | *
119 | * Default is True.
120 | */
121 | public Builder askForSettingsApi(boolean askForSettingsApi) {
122 | this.askForSettingsApi = askForSettingsApi;
123 | return this;
124 | }
125 |
126 | /**
127 | * This flag will be checked when it is not possible to display user a settingsApi dialog
128 | * to switch necessary providers on, or when there is an error displaying the dialog.
129 | * If the flag is on, then manager will setDialogListener listener as location failed,
130 | * otherwise it will try to get location anyway -which probably not gonna happen.
131 | *
132 | * Default is False. -Because after GooglePlayServices Provider it might switch
133 | * to default providers, if we fail here then those provider will never trigger.
134 | */
135 | public Builder failOnSettingsApiSuspended(boolean failOnSettingsApiSuspended) {
136 | this.failOnSettingsApiSuspended = failOnSettingsApiSuspended;
137 | return this;
138 | }
139 |
140 | /**
141 | * GooglePlayServices Api returns the best most recent location currently available. It is highly recommended to
142 | * use this functionality unless your requirements are really specific and precise.
143 | *
144 | * Default is False. So GooglePlayServices Api will return immediately if there is location already.
145 | *
146 | * https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi.html
147 | * #getLastLocation(com.google.android.gms.common.api.GoogleApiClient)
148 | */
149 | public Builder ignoreLastKnowLocation(boolean ignore) {
150 | this.ignoreLastKnowLocation = ignore;
151 | return this;
152 | }
153 |
154 | /**
155 | * Indicates waiting time period for GooglePlayServices before switching to next possible provider.
156 | *
157 | * Default values are {@linkplain Defaults#WAIT_PERIOD}
158 | */
159 | public Builder setWaitPeriod(long milliseconds) {
160 | if (milliseconds < 0) {
161 | throw new IllegalArgumentException("waitPeriod cannot be set to negative value.");
162 | }
163 |
164 | this.googlePlayServicesWaitPeriod = milliseconds;
165 | return this;
166 | }
167 |
168 | public GooglePlayServicesConfiguration build() {
169 | return new GooglePlayServicesConfiguration(this);
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/library/src/test/java/com/yayandroid/locationmanager/configuration/DefaultProviderConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package com.yayandroid.locationmanager.configuration;
2 |
3 | import com.yayandroid.locationmanager.constants.ProviderType;
4 | import com.yayandroid.locationmanager.providers.dialogprovider.SimpleMessageDialogProvider;
5 | import com.yayandroid.locationmanager.fakes.MockDialogProvider;
6 |
7 | import org.hamcrest.CoreMatchers;
8 | import org.junit.Rule;
9 | import org.junit.Test;
10 | import org.junit.rules.ExpectedException;
11 |
12 | import static org.assertj.core.api.Assertions.assertThat;
13 |
14 | public class DefaultProviderConfigurationTest {
15 |
16 | private static final int SECOND = 1000;
17 | private static final int MINUTE = 60 * SECOND;
18 |
19 | @Rule public ExpectedException expectedException = ExpectedException.none();
20 |
21 | @Test public void checkDefaultValues() {
22 | DefaultProviderConfiguration configuration = new DefaultProviderConfiguration.Builder().build();
23 |
24 | assertThat(configuration.requiredTimeInterval()).isEqualTo(5 * MINUTE);
25 | assertThat(configuration.requiredDistanceInterval()).isEqualTo(0);
26 | assertThat(configuration.acceptableAccuracy()).isEqualTo(5.0f);
27 | assertThat(configuration.acceptableTimePeriod()).isEqualTo(5 * MINUTE);
28 | assertThat(configuration.gpsWaitPeriod()).isEqualTo(20 * SECOND);
29 | assertThat(configuration.networkWaitPeriod()).isEqualTo(20 * SECOND);
30 | assertThat(configuration.gpsDialogProvider()).isNull();
31 | }
32 |
33 | @Test public void requiredTimeIntervalShouldThrowExceptionWhenNegative() {
34 | expectedException.expect(IllegalArgumentException.class);
35 | expectedException.expectMessage(CoreMatchers.startsWith("requiredTimeInterval"));
36 |
37 | new DefaultProviderConfiguration.Builder().requiredTimeInterval(-1);
38 | }
39 |
40 | @Test public void requiredDistanceIntervalShouldThrowExceptionWhenNegative() {
41 | expectedException.expect(IllegalArgumentException.class);
42 | expectedException.expectMessage(CoreMatchers.startsWith("requiredDistanceInterval"));
43 |
44 | new DefaultProviderConfiguration.Builder().requiredDistanceInterval(-1);
45 | }
46 |
47 | @Test public void acceptableAccuracyShouldThrowExceptionWhenNegative() {
48 | expectedException.expect(IllegalArgumentException.class);
49 | expectedException.expectMessage(CoreMatchers.startsWith("acceptableAccuracy"));
50 |
51 | new DefaultProviderConfiguration.Builder().acceptableAccuracy(-1);
52 | }
53 |
54 | @Test public void acceptableTimePeriodShouldThrowExceptionWhenNegative() {
55 | expectedException.expect(IllegalArgumentException.class);
56 | expectedException.expectMessage(CoreMatchers.startsWith("acceptableTimePeriod"));
57 |
58 | new DefaultProviderConfiguration.Builder().acceptableTimePeriod(-1);
59 | }
60 |
61 | @Test public void setWaitPeriodShouldThrowExceptionWhenNetworkWaitPeriodIsNegative() {
62 | expectedException.expect(IllegalArgumentException.class);
63 | expectedException.expectMessage(CoreMatchers.startsWith("waitPeriod"));
64 |
65 | new DefaultProviderConfiguration.Builder().setWaitPeriod(ProviderType.NETWORK, -1);
66 | }
67 |
68 | @Test public void setWaitPeriodShouldThrowExceptionWhenGPSWaitPeriodIsNegative() {
69 | expectedException.expect(IllegalArgumentException.class);
70 | expectedException.expectMessage(CoreMatchers.startsWith("waitPeriod"));
71 |
72 | new DefaultProviderConfiguration.Builder().setWaitPeriod(ProviderType.GPS, -1);
73 | }
74 |
75 | @Test public void setWaitPeriodShouldThrowExceptionWhenDefaultProvidersWaitPeriodIsNegative() {
76 | expectedException.expect(IllegalArgumentException.class);
77 | expectedException.expectMessage(CoreMatchers.startsWith("waitPeriod"));
78 |
79 | new DefaultProviderConfiguration.Builder().setWaitPeriod(ProviderType.DEFAULT_PROVIDERS, -1);
80 | }
81 |
82 | @Test public void setWaitPeriodShouldThrowExceptionWhenGooglePlayServicesWaitPeriodIsSet() {
83 | expectedException.expect(IllegalStateException.class);
84 | expectedException.expectMessage(CoreMatchers.startsWith("GooglePlayServices"));
85 |
86 | new DefaultProviderConfiguration.Builder().setWaitPeriod(ProviderType.GOOGLE_PLAY_SERVICES, 1);
87 | }
88 |
89 | @Test public void setWaitPeriodShouldSetPeriodsWhenDefaultProvidersIsSet() {
90 | DefaultProviderConfiguration providerConfiguration = new DefaultProviderConfiguration.Builder().setWaitPeriod(ProviderType.DEFAULT_PROVIDERS, 1).build();
91 |
92 | assertThat(providerConfiguration.gpsWaitPeriod()).isEqualTo(1);
93 | assertThat(providerConfiguration.networkWaitPeriod()).isEqualTo(1);
94 | }
95 |
96 | @Test public void whenGpsMessageAndDialogProviderNotSetAskForGPSEnableShouldReturnFalse() {
97 | DefaultProviderConfiguration configuration = new DefaultProviderConfiguration.Builder().build();
98 | assertThat(configuration.askForEnableGPS()).isFalse();
99 | }
100 |
101 | @Test public void whenGpsMessageSetAskForGPSEnableShouldReturnTrue() {
102 | DefaultProviderConfiguration configuration = new DefaultProviderConfiguration.Builder()
103 | .gpsMessage("some_text")
104 | .build();
105 | assertThat(configuration.askForEnableGPS()).isTrue();
106 | }
107 |
108 | @Test public void whenDialogProviderSetAskForGPSEnableShouldReturnTrue() {
109 | DefaultProviderConfiguration configuration = new DefaultProviderConfiguration.Builder()
110 | .gpsDialogProvider(new MockDialogProvider("some_text"))
111 | .build();
112 | assertThat(configuration.askForEnableGPS()).isTrue();
113 | }
114 |
115 | @Test public void whenGpsMessageIsEmptyAndDialogProviderIsNotSetThenDialogProviderShouldBeNull() {
116 | DefaultProviderConfiguration configuration = new DefaultProviderConfiguration.Builder().build();
117 | assertThat(configuration.gpsDialogProvider()).isNull();
118 | }
119 |
120 | @Test public void whenGpsMessageIsNotEmptyDefaultDialogProviderShouldBeSimple() {
121 | final String GPS_MESSAGE = "some_text";
122 | DefaultProviderConfiguration configuration = new DefaultProviderConfiguration.Builder()
123 | .gpsMessage(GPS_MESSAGE)
124 | .build();
125 |
126 | assertThat(configuration.gpsDialogProvider())
127 | .isNotNull()
128 | .isExactlyInstanceOf(SimpleMessageDialogProvider.class);
129 | assertThat(((SimpleMessageDialogProvider) configuration.gpsDialogProvider()).message()).isEqualTo(GPS_MESSAGE);
130 | }
131 |
132 | @Test public void whenDialogProviderIsSetMessageShouldBeIgnored() {
133 | final String GPS_MESSAGE = "some_text";
134 | DefaultProviderConfiguration configuration = new DefaultProviderConfiguration.Builder()
135 | .gpsMessage("ignored_message")
136 | .gpsDialogProvider(new MockDialogProvider(GPS_MESSAGE))
137 | .build();
138 |
139 | assertThat(configuration.gpsDialogProvider())
140 | .isNotNull()
141 | .isExactlyInstanceOf(MockDialogProvider.class);
142 | assertThat(((MockDialogProvider) configuration.gpsDialogProvider()).message()).isEqualTo(GPS_MESSAGE);
143 | }
144 |
145 | @Test public void clonesShouldShareSameInstances() {
146 | DefaultProviderConfiguration configuration = new DefaultProviderConfiguration.Builder()
147 | .gpsDialogProvider(new MockDialogProvider("some_text"))
148 | .build();
149 |
150 | DefaultProviderConfiguration firstClone = configuration.newBuilder().build();
151 | DefaultProviderConfiguration secondClone = configuration.newBuilder().build();
152 |
153 | assertThat(firstClone.requiredTimeInterval())
154 | .isEqualTo(secondClone.requiredTimeInterval())
155 | .isEqualTo(5 * MINUTE);
156 | assertThat(firstClone.requiredDistanceInterval())
157 | .isEqualTo(secondClone.requiredDistanceInterval())
158 | .isEqualTo(0);
159 | assertThat(firstClone.acceptableAccuracy())
160 | .isEqualTo(secondClone.acceptableAccuracy())
161 | .isEqualTo(5.0f);
162 | assertThat(firstClone.acceptableTimePeriod())
163 | .isEqualTo(secondClone.acceptableTimePeriod())
164 | .isEqualTo(5 * MINUTE);
165 | assertThat(firstClone.gpsWaitPeriod())
166 | .isEqualTo(secondClone.gpsWaitPeriod())
167 | .isEqualTo(20 * SECOND);
168 | assertThat(firstClone.networkWaitPeriod())
169 | .isEqualTo(secondClone.networkWaitPeriod())
170 | .isEqualTo(20 * SECOND);
171 | assertThat(firstClone.gpsDialogProvider())
172 | .isEqualTo(secondClone.gpsDialogProvider())
173 | .isNotNull()
174 | .isExactlyInstanceOf(MockDialogProvider.class);
175 | }
176 |
177 | }
--------------------------------------------------------------------------------
/library/src/test/java/com/yayandroid/locationmanager/providers/permissionprovider/DefaultPermissionProviderTest.java:
--------------------------------------------------------------------------------
1 | package com.yayandroid.locationmanager.providers.permissionprovider;
2 |
3 | import android.app.Activity;
4 | import android.content.pm.PackageManager;
5 |
6 | import androidx.fragment.app.Fragment;
7 |
8 | import com.yayandroid.locationmanager.constants.RequestCode;
9 | import com.yayandroid.locationmanager.listener.PermissionListener;
10 | import com.yayandroid.locationmanager.fakes.MockDialogProvider;
11 | import com.yayandroid.locationmanager.view.ContextProcessor;
12 |
13 | import org.junit.Before;
14 | import org.junit.Rule;
15 | import org.junit.Test;
16 | import org.junit.rules.ExpectedException;
17 | import org.mockito.Mock;
18 | import org.mockito.MockitoAnnotations;
19 |
20 | import static org.assertj.core.api.Assertions.assertThat;
21 | import static org.mockito.ArgumentMatchers.eq;
22 | import static org.mockito.Mockito.verify;
23 | import static org.mockito.Mockito.verifyZeroInteractions;
24 | import static org.mockito.Mockito.when;
25 |
26 | public class DefaultPermissionProviderTest {
27 |
28 | private static final String[] REQUIRED_PERMISSIONS = new String[]{"really_important_permission",
29 | "even_more_important", "super_important_one"};
30 | private static final String SINGLE_PERMISSION = REQUIRED_PERMISSIONS[0];
31 | private static final int GRANTED = PackageManager.PERMISSION_GRANTED;
32 | private static final int DENIED = PackageManager.PERMISSION_DENIED;
33 |
34 | @Rule public ExpectedException expectedException = ExpectedException.none();
35 |
36 | @Mock Fragment fragment;
37 | @Mock Activity activity;
38 | @Mock ContextProcessor contextProcessor;
39 | @Mock PermissionListener permissionListener;
40 | @Mock PermissionCompatSource permissionCompatSource;
41 |
42 | private DefaultPermissionProvider defaultPermissionProvider;
43 | private MockDialogProvider mockDialogProvider;
44 |
45 | @Before
46 | public void setUp() throws Exception {
47 | MockitoAnnotations.initMocks(this);
48 |
49 | mockDialogProvider = new MockDialogProvider("");
50 | defaultPermissionProvider = new DefaultPermissionProvider(REQUIRED_PERMISSIONS, mockDialogProvider);
51 | defaultPermissionProvider.setContextProcessor(contextProcessor);
52 | defaultPermissionProvider.setPermissionListener(permissionListener);
53 | defaultPermissionProvider.setPermissionCompatSource(permissionCompatSource);
54 | }
55 |
56 | @Test
57 | public void executePermissionsRequestShouldNotifyDeniedWhenThereIsNoActivityOrFragment() {
58 | defaultPermissionProvider.executePermissionsRequest();
59 | verify(permissionListener).onPermissionsDenied();
60 | }
61 |
62 | @Test
63 | public void executePermissionsRequestShouldCallRequestPermissionsOnFragmentFirst() {
64 | when(contextProcessor.getActivity()).thenReturn(activity);
65 | when(contextProcessor.getFragment()).thenReturn(fragment);
66 |
67 | defaultPermissionProvider.executePermissionsRequest();
68 |
69 | verify(permissionCompatSource)
70 | .requestPermissions(eq(fragment), eq(REQUIRED_PERMISSIONS), eq(RequestCode.RUNTIME_PERMISSION));
71 | }
72 |
73 | @Test
74 | public void executePermissionsRequestShouldCallRequestPermissionsOnActivityIfThereIsNoFragment() {
75 | when(contextProcessor.getActivity()).thenReturn(activity);
76 |
77 | defaultPermissionProvider.executePermissionsRequest();
78 |
79 | verifyRequestPermissionOnActivity();
80 | }
81 |
82 | @Test
83 | public void checkRationaleForPermissionShouldReturnFalseIfThereIsNoActivityOrFragment() {
84 | assertThat(defaultPermissionProvider.checkRationaleForPermission(SINGLE_PERMISSION)).isFalse();
85 | }
86 |
87 | @Test
88 | public void checkRationaleForPermissionShouldCheckOnFragmentFirst() {
89 | when(contextProcessor.getActivity()).thenReturn(activity);
90 | when(contextProcessor.getFragment()).thenReturn(fragment);
91 |
92 | defaultPermissionProvider.checkRationaleForPermission(SINGLE_PERMISSION);
93 |
94 | verify(permissionCompatSource).shouldShowRequestPermissionRationale(eq(fragment), eq(SINGLE_PERMISSION));
95 | }
96 |
97 | @Test
98 | public void checkRationaleForPermissionShouldCheckOnActivityIfThereIsNoFragment() {
99 | when(contextProcessor.getActivity()).thenReturn(activity);
100 |
101 | defaultPermissionProvider.checkRationaleForPermission(SINGLE_PERMISSION);
102 |
103 | verify(permissionCompatSource).shouldShowRequestPermissionRationale(eq(activity), eq(SINGLE_PERMISSION));
104 | }
105 |
106 | @Test
107 | public void shouldShowRequestPermissionRationaleShouldReturnTrueWhenAnyIsTrue() {
108 | when(contextProcessor.getActivity()).thenReturn(activity);
109 | when(permissionCompatSource.shouldShowRequestPermissionRationale(eq(activity), eq(REQUIRED_PERMISSIONS[0])))
110 | .thenReturn(true);
111 | when(permissionCompatSource.shouldShowRequestPermissionRationale(eq(activity), eq(REQUIRED_PERMISSIONS[1])))
112 | .thenReturn(false);
113 | when(permissionCompatSource.shouldShowRequestPermissionRationale(eq(activity), eq(REQUIRED_PERMISSIONS[2])))
114 | .thenReturn(false);
115 |
116 | assertThat(defaultPermissionProvider.shouldShowRequestPermissionRationale()).isTrue();
117 | }
118 |
119 | @Test
120 | public void shouldShowRequestPermissionRationaleShouldReturnFalseWhenThereIsNoActivity() {
121 | when(contextProcessor.getActivity()).thenReturn(null);
122 | assertThat(defaultPermissionProvider.shouldShowRequestPermissionRationale()).isFalse();
123 | }
124 |
125 | @Test
126 | public void shouldShowRequestPermissionRationaleShouldReturnFalseWhenThereIsNoDialogProvider() {
127 | defaultPermissionProvider = new DefaultPermissionProvider(REQUIRED_PERMISSIONS, null);
128 | defaultPermissionProvider.setContextProcessor(contextProcessor);
129 | defaultPermissionProvider.setPermissionListener(permissionListener);
130 | defaultPermissionProvider.setPermissionCompatSource(permissionCompatSource);
131 | makeShouldShowRequestPermissionRationaleTrue();
132 |
133 | assertThat(defaultPermissionProvider.shouldShowRequestPermissionRationale()).isFalse();
134 | }
135 |
136 | @Test
137 | public void requestPermissionsShouldReturnFalseWhenThereIsNoActivity() {
138 | assertThat(defaultPermissionProvider.requestPermissions()).isFalse();
139 | }
140 |
141 | @Test
142 | public void requestPermissionsShouldRequestWhenShouldShowRequestPermissionRationaleIsFalse() {
143 | when(contextProcessor.getActivity()).thenReturn(activity);
144 |
145 | defaultPermissionProvider.requestPermissions();
146 |
147 | verifyRequestPermissionOnActivity();
148 | }
149 |
150 | @Test
151 | public void requestPermissionsShouldShowRationaleIfRequired() {
152 | makeShouldShowRequestPermissionRationaleTrue();
153 |
154 | defaultPermissionProvider.requestPermissions();
155 |
156 | verify(mockDialogProvider.getDialog(activity)).show();
157 | }
158 |
159 | @Test
160 | public void onPositiveButtonClickShouldRequestPermission() {
161 | when(contextProcessor.getActivity()).thenReturn(activity);
162 |
163 | defaultPermissionProvider.onPositiveButtonClick();
164 |
165 | verifyRequestPermissionOnActivity();
166 | }
167 |
168 | @Test
169 | public void onNegativeButtonClickShouldNotifyPermissionDenied() {
170 | defaultPermissionProvider.onNegativeButtonClick();
171 | verify(permissionListener).onPermissionsDenied();
172 | }
173 |
174 | @Test
175 | public void onRequestPermissionsResultShouldDoNothingWhenRequestCodeIsNotMatched() {
176 | defaultPermissionProvider.onRequestPermissionsResult(-1, null, new int[] {1});
177 | verifyZeroInteractions(permissionListener);
178 | }
179 |
180 | @Test
181 | public void onRequestPermissionsResultShouldNotifyDeniedIfAny() {
182 | defaultPermissionProvider.onRequestPermissionsResult(RequestCode.RUNTIME_PERMISSION,
183 | REQUIRED_PERMISSIONS, new int[] {GRANTED, GRANTED, DENIED});
184 | verify(permissionListener).onPermissionsDenied();
185 | }
186 |
187 | @Test
188 | public void onRequestPermissionsResultShouldNotifyGrantedIfAll() {
189 | defaultPermissionProvider.onRequestPermissionsResult(RequestCode.RUNTIME_PERMISSION,
190 | REQUIRED_PERMISSIONS, new int[] {GRANTED, GRANTED, GRANTED});
191 | verify(permissionListener).onPermissionsGranted();
192 | }
193 |
194 | private void makeShouldShowRequestPermissionRationaleTrue() {
195 | when(contextProcessor.getActivity()).thenReturn(activity);
196 | when(permissionCompatSource.shouldShowRequestPermissionRationale(eq(activity), eq(REQUIRED_PERMISSIONS[0])))
197 | .thenReturn(true);
198 | }
199 |
200 | private void verifyRequestPermissionOnActivity() {
201 | verify(permissionCompatSource)
202 | .requestPermissions(eq(activity), eq(REQUIRED_PERMISSIONS), eq(RequestCode.RUNTIME_PERMISSION));
203 | }
204 |
205 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/yayandroid/locationmanager/configuration/DefaultProviderConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.yayandroid.locationmanager.configuration;
2 |
3 | import androidx.annotation.Nullable;
4 |
5 | import com.yayandroid.locationmanager.constants.ProviderType;
6 | import com.yayandroid.locationmanager.helper.StringUtils;
7 | import com.yayandroid.locationmanager.providers.dialogprovider.DialogProvider;
8 | import com.yayandroid.locationmanager.providers.dialogprovider.SimpleMessageDialogProvider;
9 |
10 | public class DefaultProviderConfiguration {
11 |
12 | private final long requiredTimeInterval;
13 | private final long requiredDistanceInterval;
14 | private final float acceptableAccuracy;
15 | private final long acceptableTimePeriod;
16 | private final long gpsWaitPeriod;
17 | private final long networkWaitPeriod;
18 | private final DialogProvider gpsDialogProvider;
19 |
20 | private DefaultProviderConfiguration(Builder builder) {
21 | this.requiredTimeInterval = builder.requiredTimeInterval;
22 | this.requiredDistanceInterval = builder.requiredDistanceInterval;
23 | this.acceptableAccuracy = builder.acceptableAccuracy;
24 | this.acceptableTimePeriod = builder.acceptableTimePeriod;
25 | this.gpsWaitPeriod = builder.gpsWaitPeriod;
26 | this.networkWaitPeriod = builder.networkWaitPeriod;
27 | this.gpsDialogProvider = builder.gpsDialogProvider;
28 | }
29 |
30 | public DefaultProviderConfiguration.Builder newBuilder() {
31 | return new DefaultProviderConfiguration.Builder()
32 | .requiredTimeInterval(requiredTimeInterval)
33 | .requiredDistanceInterval(requiredDistanceInterval)
34 | .acceptableAccuracy(acceptableAccuracy)
35 | .acceptableTimePeriod(acceptableTimePeriod)
36 | .setWaitPeriod(ProviderType.GPS, gpsWaitPeriod)
37 | .setWaitPeriod(ProviderType.NETWORK, networkWaitPeriod)
38 | .gpsDialogProvider(gpsDialogProvider);
39 | }
40 |
41 | // region Getters
42 | public long requiredTimeInterval() {
43 | return requiredTimeInterval;
44 | }
45 |
46 | public long requiredDistanceInterval() {
47 | return requiredDistanceInterval;
48 | }
49 |
50 | public float acceptableAccuracy() {
51 | return acceptableAccuracy;
52 | }
53 |
54 | public long acceptableTimePeriod() {
55 | return acceptableTimePeriod;
56 | }
57 |
58 | public boolean askForEnableGPS() {
59 | return gpsDialogProvider != null;
60 | }
61 |
62 | @Nullable
63 | public DialogProvider gpsDialogProvider() {
64 | return gpsDialogProvider;
65 | }
66 |
67 | public long gpsWaitPeriod() {
68 | return gpsWaitPeriod;
69 | }
70 |
71 | public long networkWaitPeriod() {
72 | return networkWaitPeriod;
73 | }
74 | // endregion
75 |
76 |
77 | public static class Builder {
78 |
79 | private long requiredTimeInterval = Defaults.LOCATION_INTERVAL;
80 | private long requiredDistanceInterval = Defaults.LOCATION_DISTANCE_INTERVAL;
81 | private float acceptableAccuracy = Defaults.MIN_ACCURACY;
82 | private long acceptableTimePeriod = Defaults.TIME_PERIOD;
83 | private long gpsWaitPeriod = Defaults.WAIT_PERIOD;
84 | private long networkWaitPeriod = Defaults.WAIT_PERIOD;
85 | private DialogProvider gpsDialogProvider;
86 | private String gpsMessage = Defaults.EMPTY_STRING;
87 |
88 | /**
89 | * TimeInterval will be used while getting location from default location providers
90 | * It will define in which period updates need to be delivered and will be used only when
91 | * {@linkplain LocationConfiguration#keepTracking()} is set to true.
92 | * Default is {@linkplain Defaults#LOCATION_INTERVAL}
93 | */
94 | public Builder requiredTimeInterval(long requiredTimeInterval) {
95 | if (requiredTimeInterval < 0) {
96 | throw new IllegalArgumentException("requiredTimeInterval cannot be set to negative value.");
97 | }
98 |
99 | this.requiredTimeInterval = requiredTimeInterval;
100 | return this;
101 | }
102 |
103 | /**
104 | * DistanceInterval will be used while getting location from default location providers
105 | * It will define in which distance changes that we should receive an update and will be used only when
106 | * {@linkplain LocationConfiguration#keepTracking()} is set to true.
107 | * Default is {@linkplain Defaults#LOCATION_DISTANCE_INTERVAL}
108 | */
109 | public Builder requiredDistanceInterval(long requiredDistanceInterval) {
110 | if (requiredDistanceInterval < 0) {
111 | throw new IllegalArgumentException("requiredDistanceInterval cannot be set to negative value.");
112 | }
113 |
114 | this.requiredDistanceInterval = requiredDistanceInterval;
115 | return this;
116 | }
117 |
118 | /**
119 | * Minimum Accuracy that you seek location to be
120 | * Default is {@linkplain Defaults#MIN_ACCURACY}
121 | */
122 | public Builder acceptableAccuracy(float acceptableAccuracy) {
123 | if (acceptableAccuracy < 0) {
124 | throw new IllegalArgumentException("acceptableAccuracy cannot be set to negative value.");
125 | }
126 |
127 | this.acceptableAccuracy = acceptableAccuracy;
128 | return this;
129 | }
130 |
131 | /**
132 | * Indicates time period that can be count as usable location,
133 | * this needs to be considered such as "last 5 minutes"
134 | * Default is {@linkplain Defaults#TIME_PERIOD}
135 | */
136 | public Builder acceptableTimePeriod(long acceptableTimePeriod) {
137 | if (acceptableTimePeriod < 0) {
138 | throw new IllegalArgumentException("acceptableTimePeriod cannot be set to negative value.");
139 | }
140 |
141 | this.acceptableTimePeriod = acceptableTimePeriod;
142 | return this;
143 | }
144 |
145 | /**
146 | * Indicates what to display to user while asking to turn GPS on.
147 | * If you do not set this, user will not be asked to enable GPS.
148 | */
149 | public Builder gpsMessage(String gpsMessage) {
150 | this.gpsMessage = gpsMessage;
151 | return this;
152 | }
153 |
154 | /**
155 | * If you need to display a custom dialog to ask user to enable GPS, you can provide your own
156 | * implementation of {@linkplain DialogProvider} and manager will use that implementation to display the dialog.
157 | * Important, if you set your own implementation, please make sure to handle gpsMessage as well.
158 | * Because {@linkplain DefaultProviderConfiguration.Builder#gpsMessage} will be ignored in that case.
159 | *
160 | * If you don't specify any dialogProvider implementation {@linkplain SimpleMessageDialogProvider} will be used with
161 | * given {@linkplain DefaultProviderConfiguration.Builder#gpsMessage}
162 | */
163 | public Builder gpsDialogProvider(DialogProvider dialogProvider) {
164 | this.gpsDialogProvider = dialogProvider;
165 | return this;
166 | }
167 |
168 | /**
169 | * Indicates waiting time period before switching to next possible provider.
170 | * Possible to set provider wait periods separately by passing providerType as one of the
171 | * {@linkplain ProviderType} values.
172 | * Default values are {@linkplain Defaults#WAIT_PERIOD}
173 | */
174 | public Builder setWaitPeriod(@ProviderType int providerType, long milliseconds) {
175 | if (milliseconds < 0) {
176 | throw new IllegalArgumentException("waitPeriod cannot be set to negative value.");
177 | }
178 |
179 | switch (providerType) {
180 | case ProviderType.GOOGLE_PLAY_SERVICES: {
181 | throw new IllegalStateException("GooglePlayServices waiting time period should be set on "
182 | + "GooglePlayServicesConfiguration");
183 | }
184 | case ProviderType.NETWORK: {
185 | this.networkWaitPeriod = milliseconds;
186 | break;
187 | }
188 | case ProviderType.GPS: {
189 | this.gpsWaitPeriod = milliseconds;
190 | break;
191 | }
192 | case ProviderType.DEFAULT_PROVIDERS: {
193 | this.gpsWaitPeriod = milliseconds;
194 | this.networkWaitPeriod = milliseconds;
195 | break;
196 | }
197 | case ProviderType.NONE: {
198 | // ignored
199 | }
200 | }
201 |
202 | return this;
203 | }
204 |
205 | public DefaultProviderConfiguration build() {
206 | if (gpsDialogProvider == null && StringUtils.isNotEmpty(gpsMessage)) {
207 | gpsDialogProvider = new SimpleMessageDialogProvider(gpsMessage);
208 | }
209 |
210 | return new DefaultProviderConfiguration(this);
211 | }
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/library/src/test/java/com/yayandroid/locationmanager/LocationManagerTest.java:
--------------------------------------------------------------------------------
1 | package com.yayandroid.locationmanager;
2 |
3 | import android.content.Intent;
4 |
5 | import com.yayandroid.locationmanager.LocationManager.Builder;
6 | import com.yayandroid.locationmanager.configuration.LocationConfiguration;
7 | import com.yayandroid.locationmanager.constants.FailType;
8 | import com.yayandroid.locationmanager.constants.ProcessType;
9 | import com.yayandroid.locationmanager.listener.LocationListener;
10 | import com.yayandroid.locationmanager.providers.locationprovider.DispatcherLocationProvider;
11 | import com.yayandroid.locationmanager.providers.locationprovider.LocationProvider;
12 | import com.yayandroid.locationmanager.providers.permissionprovider.PermissionProvider;
13 | import com.yayandroid.locationmanager.view.ContextProcessor;
14 |
15 | import org.junit.Before;
16 | import org.junit.Rule;
17 | import org.junit.Test;
18 | import org.junit.rules.ExpectedException;
19 | import org.mockito.Answers;
20 | import org.mockito.Mock;
21 | import org.mockito.MockitoAnnotations;
22 |
23 | import static org.assertj.core.api.Assertions.assertThat;
24 | import static org.mockito.ArgumentMatchers.eq;
25 | import static org.mockito.Mockito.verify;
26 | import static org.mockito.Mockito.when;
27 |
28 | public class LocationManagerTest {
29 |
30 | @Rule public ExpectedException expectedException = ExpectedException.none();
31 |
32 | @Mock ContextProcessor contextProcessor;
33 | @Mock LocationListener locationListener;
34 | @Mock LocationProvider locationProvider;
35 | @Mock PermissionProvider permissionProvider;
36 |
37 | @Mock(answer = Answers.RETURNS_DEEP_STUBS)
38 | LocationConfiguration locationConfiguration;
39 |
40 | @Before
41 | public void setUp() throws Exception {
42 | MockitoAnnotations.initMocks(this);
43 | when(locationConfiguration.permissionConfiguration().permissionProvider()).thenReturn(permissionProvider);
44 | }
45 |
46 | @Test public void buildingWithoutContextProcessorShouldThrowException() {
47 | expectedException.expect(IllegalStateException.class);
48 |
49 | //noinspection ConstantConditions
50 | new Builder(((ContextProcessor) null))
51 | .locationProvider(locationProvider)
52 | .notify(locationListener)
53 | .build();
54 | }
55 |
56 | // region Build Tests
57 | @Test public void buildingWithoutConfigurationShouldThrowException() {
58 | expectedException.expect(IllegalStateException.class);
59 |
60 | new LocationManager.Builder(contextProcessor)
61 | .locationProvider(locationProvider)
62 | .notify(locationListener)
63 | .build();
64 | }
65 |
66 | @Test public void buildingWithoutProviderShouldUseDispatcherLocationProvider() {
67 | LocationManager locationManager = new Builder(contextProcessor)
68 | .configuration(locationConfiguration)
69 | .notify(locationListener)
70 | .build();
71 |
72 | assertThat(locationManager.activeProvider())
73 | .isNotNull()
74 | .isExactlyInstanceOf(DispatcherLocationProvider.class);
75 | }
76 |
77 | @Test public void buildingShouldCallConfigureAndSetListenerOnProvider() {
78 | buildLocationManager();
79 |
80 | verify(locationProvider).configure(contextProcessor, locationConfiguration, locationListener);
81 | }
82 |
83 | @Test public void buildingShouldSetContextProcessorAndListenerToPermissionListener() {
84 | LocationManager locationManager = buildLocationManager();
85 |
86 | verify(permissionProvider).setContextProcessor(contextProcessor);
87 | verify(permissionProvider).setPermissionListener(locationManager);
88 | }
89 | // endregion
90 |
91 | // region Redirect Tests
92 | @Test public void whenOnPauseShouldRedirectToLocationProvider() {
93 | LocationManager locationManager = buildLocationManager();
94 |
95 | locationManager.onPause();
96 |
97 | verify(locationProvider).onPause();
98 | }
99 |
100 | @Test public void whenOnResumeShouldRedirectToLocationProvider() {
101 | LocationManager locationManager = buildLocationManager();
102 |
103 | locationManager.onResume();
104 |
105 | verify(locationProvider).onResume();
106 | }
107 |
108 | @Test public void whenOnDestroyShouldRedirectToLocationProvider() {
109 | LocationManager locationManager = buildLocationManager();
110 |
111 | locationManager.onDestroy();
112 |
113 | verify(locationProvider).onDestroy();
114 | }
115 |
116 | @Test public void whenCancelShouldRedirectToLocationProvider() {
117 | LocationManager locationManager = buildLocationManager();
118 |
119 | locationManager.cancel();
120 |
121 | verify(locationProvider).cancel();
122 | }
123 |
124 | @Test public void whenOnActivityResultShouldRedirectToLocationProvider() {
125 | LocationManager locationManager = buildLocationManager();
126 | int requestCode = 1;
127 | int resultCode = 2;
128 | Intent data = new Intent();
129 |
130 | locationManager.onActivityResult(requestCode, resultCode, data);
131 |
132 | verify(locationProvider).onActivityResult(eq(requestCode), eq(resultCode), eq(data));
133 | }
134 |
135 | @Test public void whenOnRequestPermissionsResultShouldRedirectToPermissionProvider() {
136 | LocationManager locationManager = buildLocationManager();
137 | int requestCode = 1;
138 | String[] permissions = new String[1];
139 | int[] grantResults = new int[1];
140 |
141 | locationManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
142 |
143 | verify(permissionProvider).onRequestPermissionsResult(eq(requestCode), eq(permissions), eq(grantResults));
144 | }
145 |
146 | @Test public void whenGetShouldRedirectToLocationProviderWhenPermissionIsGranted() {
147 | when(permissionProvider.hasPermission()).thenReturn(true);
148 | LocationManager locationManager = buildLocationManager();
149 |
150 | locationManager.get();
151 |
152 | verify(locationProvider).get();
153 | }
154 | // endregion
155 |
156 | // region Retrieve Tests
157 | @Test public void isWaitingForLocationShouldRetrieveFromLocationProvider() {
158 | when(locationProvider.isWaiting()).thenReturn(true);
159 | LocationManager locationManager = buildLocationManager();
160 |
161 | assertThat(locationManager.isWaitingForLocation()).isTrue();
162 | verify(locationProvider).isWaiting();
163 | }
164 |
165 | @Test public void isAnyDialogShowingShouldRetrieveFromLocationProvider() {
166 | when(locationProvider.isDialogShowing()).thenReturn(true);
167 | LocationManager locationManager = buildLocationManager();
168 |
169 | assertThat(locationManager.isAnyDialogShowing()).isTrue();
170 | verify(locationProvider).isDialogShowing();
171 | }
172 | // endregion
173 |
174 | @Test public void whenGetCalledShouldStartPermissionRequest() {
175 | LocationManager locationManager = buildLocationManager();
176 |
177 | locationManager.get();
178 |
179 | verify(permissionProvider).hasPermission();
180 | verify(permissionProvider).requestPermissions();
181 | }
182 |
183 | @Test public void whenRequestPermissionsAreAlreadyGrantedShouldNotifyListenerWithTrue() {
184 | when(permissionProvider.hasPermission()).thenReturn(true);
185 | LocationManager locationManager = buildLocationManager();
186 |
187 | locationManager.askForPermission();
188 |
189 | verify(locationListener).onPermissionGranted(eq(true));
190 | }
191 |
192 | @Test public void whenRequestedPermissionsAreGrantedShouldNotifyListenerWithFalse() {
193 | LocationManager locationManager = buildLocationManager();
194 | when(permissionProvider.getPermissionListener()).thenReturn(locationManager);
195 |
196 | permissionProvider.getPermissionListener().onPermissionsGranted();
197 |
198 | verify(locationListener).onPermissionGranted(eq(false));
199 | }
200 |
201 | @Test public void whenRequestedPermissionsAreDeniedShouldCallFailOnListener() {
202 | LocationManager locationManager = buildLocationManager();
203 | when(permissionProvider.getPermissionListener()).thenReturn(locationManager);
204 |
205 | permissionProvider.getPermissionListener().onPermissionsDenied();
206 |
207 | //noinspection WrongConstant
208 | verify(locationListener).onLocationFailed(eq(FailType.PERMISSION_DENIED));
209 | }
210 |
211 | @Test public void whenAskForPermissionShouldNotifyListenerWithProcessTypeChanged() {
212 | LocationManager locationManager = buildLocationManager();
213 |
214 | locationManager.askForPermission();
215 |
216 | //noinspection WrongConstant
217 | verify(locationListener).onProcessTypeChanged(eq(ProcessType.ASKING_PERMISSIONS));
218 | }
219 |
220 | @Test public void whenRequestingPermissionIsNotPossibleThenItShouldFail() {
221 | when(permissionProvider.requestPermissions()).thenReturn(false);
222 | LocationManager locationManager = buildLocationManager();
223 |
224 | locationManager.askForPermission();
225 |
226 | //noinspection WrongConstant
227 | verify(locationListener).onLocationFailed(eq(FailType.PERMISSION_DENIED));
228 | }
229 |
230 | private LocationManager buildLocationManager() {
231 | return new Builder(contextProcessor)
232 | .locationProvider(locationProvider)
233 | .configuration(locationConfiguration)
234 | .notify(locationListener)
235 | .build();
236 | }
237 |
238 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/yayandroid/locationmanager/LocationManager.java:
--------------------------------------------------------------------------------
1 | package com.yayandroid.locationmanager;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.fragment.app.Fragment;
9 |
10 | import com.yayandroid.locationmanager.configuration.LocationConfiguration;
11 | import com.yayandroid.locationmanager.constants.FailType;
12 | import com.yayandroid.locationmanager.constants.ProcessType;
13 | import com.yayandroid.locationmanager.helper.LogUtils;
14 | import com.yayandroid.locationmanager.helper.logging.DefaultLogger;
15 | import com.yayandroid.locationmanager.helper.logging.Logger;
16 | import com.yayandroid.locationmanager.listener.LocationListener;
17 | import com.yayandroid.locationmanager.listener.PermissionListener;
18 | import com.yayandroid.locationmanager.providers.locationprovider.DispatcherLocationProvider;
19 | import com.yayandroid.locationmanager.providers.locationprovider.LocationProvider;
20 | import com.yayandroid.locationmanager.providers.permissionprovider.PermissionProvider;
21 | import com.yayandroid.locationmanager.view.ContextProcessor;
22 |
23 | public class LocationManager implements PermissionListener {
24 |
25 | private LocationListener listener;
26 | private LocationConfiguration configuration;
27 | private LocationProvider activeProvider;
28 | private PermissionProvider permissionProvider;
29 |
30 | /**
31 | * Library tries to log as much as possible in order to make it transparent to see what is actually going on
32 | * under the hood. You can enable it for debug purposes, but do not forget to disable on production.
33 | *
34 | * Log is disabled as default.
35 | */
36 | public static void enableLog(boolean enable) {
37 | LogUtils.enable(enable);
38 | }
39 |
40 | /**
41 | * The Logger specifies how this Library is logging debug information. By default {@link DefaultLogger}
42 | * is used and it can be replaced by your own custom implementation of {@link Logger}.
43 | */
44 | public static void setLogger(@NonNull Logger logger) {
45 | LogUtils.setLogger(logger);
46 | }
47 |
48 | /**
49 | * To create an instance of this manager you MUST specify a LocationConfiguration
50 | */
51 | private LocationManager(Builder builder) {
52 | this.listener = builder.listener;
53 | this.configuration = builder.configuration;
54 | this.activeProvider = builder.activeProvider;
55 |
56 | this.permissionProvider = getConfiguration().permissionConfiguration().permissionProvider();
57 | this.permissionProvider.setContextProcessor(builder.contextProcessor);
58 | this.permissionProvider.setPermissionListener(this);
59 | }
60 |
61 | public static class Builder {
62 |
63 | private ContextProcessor contextProcessor;
64 | private LocationListener listener;
65 | private LocationConfiguration configuration;
66 | private LocationProvider activeProvider;
67 |
68 | /**
69 | * Builder object to create LocationManager
70 | *
71 | * @param contextProcessor holds the address of the context,which this manager will run on
72 | */
73 | public Builder(@NonNull ContextProcessor contextProcessor) {
74 | this.contextProcessor = contextProcessor;
75 | }
76 |
77 | /**
78 | * Builder object to create LocationManager
79 | *
80 | * @param context MUST be an application context
81 | */
82 | public Builder(@NonNull Context context) {
83 | this.contextProcessor = new ContextProcessor(context);
84 | }
85 |
86 | /**
87 | * Activity is required in order to ask for permission, GPS enable dialog, Rationale dialog,
88 | * GoogleApiClient and SettingsApi.
89 | *
90 | * @param activity will be kept as weakReference
91 | */
92 | public Builder activity(Activity activity) {
93 | this.contextProcessor.setActivity(activity);
94 | return this;
95 | }
96 |
97 | /**
98 | * Fragment is required in order to ask for permission, GPS enable dialog, Rationale dialog,
99 | * GoogleApiClient and SettingsApi.
100 | *
101 | * @param fragment will be kept as weakReference
102 | */
103 | public Builder fragment(Fragment fragment) {
104 | this.contextProcessor.setFragment(fragment);
105 | return this;
106 | }
107 |
108 | /**
109 | * Configuration object in order to take decisions accordingly while trying to retrieve location
110 | */
111 | public Builder configuration(@NonNull LocationConfiguration locationConfiguration) {
112 | this.configuration = locationConfiguration;
113 | return this;
114 | }
115 |
116 | /**
117 | * Instead of using {@linkplain DispatcherLocationProvider} you can create your own,
118 | * and set it to manager so it will use given one.
119 | */
120 | public Builder locationProvider(@NonNull LocationProvider provider) {
121 | this.activeProvider = provider;
122 | return this;
123 | }
124 |
125 | /**
126 | * Specify a LocationListener to receive location when it is available,
127 | * or get knowledge of any other steps in process
128 | */
129 | public Builder notify(LocationListener listener) {
130 | this.listener = listener;
131 | return this;
132 | }
133 |
134 | public LocationManager build() {
135 | if (contextProcessor == null) {
136 | throw new IllegalStateException("You must set a context to LocationManager.");
137 | }
138 |
139 | if (configuration == null) {
140 | throw new IllegalStateException("You must set a configuration object.");
141 | }
142 |
143 | if (activeProvider == null) {
144 | locationProvider(new DispatcherLocationProvider());
145 | }
146 |
147 | this.activeProvider.configure(contextProcessor, configuration, listener);
148 |
149 | return new LocationManager(this);
150 | }
151 | }
152 |
153 | /**
154 | * Returns configuration object which is defined to this manager
155 | */
156 | public LocationConfiguration getConfiguration() {
157 | return configuration;
158 | }
159 |
160 | /**
161 | * Google suggests to stop location updates when the activity is no longer in focus
162 | * http://developer.android.com/training/location/receive-location-updates.html#stop-updates
163 | */
164 | public void onPause() {
165 | activeProvider.onPause();
166 | }
167 |
168 | /**
169 | * Restart location updates to keep continue getting locations when activity is back
170 | */
171 | public void onResume() {
172 | activeProvider.onResume();
173 | }
174 |
175 | /**
176 | * Release whatever you need to when onDestroy is called
177 | */
178 | public void onDestroy() {
179 | activeProvider.onDestroy();
180 | }
181 |
182 | /**
183 | * This is required to check when user handles with Google Play Services error, or enables GPS...
184 | */
185 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
186 | activeProvider.onActivityResult(requestCode, resultCode, data);
187 | }
188 |
189 | /**
190 | * Provide requestPermissionResult to manager so the it can handle RuntimePermission
191 | */
192 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
193 | permissionProvider.onRequestPermissionsResult(requestCode, permissions, grantResults);
194 | }
195 |
196 | /**
197 | * To determine whether LocationManager is currently waiting for location or it did already receive one!
198 | */
199 | public boolean isWaitingForLocation() {
200 | return activeProvider.isWaiting();
201 | }
202 |
203 | /**
204 | * To determine whether the manager is currently displaying any dialog or not
205 | */
206 | public boolean isAnyDialogShowing() {
207 | return activeProvider.isDialogShowing();
208 | }
209 |
210 | /**
211 | * Abort the mission and cancel all location update requests
212 | */
213 | public void cancel() {
214 | activeProvider.cancel();
215 | }
216 |
217 | /**
218 | * The only method you need to call to trigger getting location process
219 | */
220 | public void get() {
221 | askForPermission();
222 | }
223 |
224 | /**
225 | * Only For Test Purposes
226 | */
227 | LocationProvider activeProvider() {
228 | return activeProvider;
229 | }
230 |
231 | void askForPermission() {
232 | if (permissionProvider.hasPermission()) {
233 | permissionGranted(true);
234 | } else {
235 | if (listener != null) {
236 | listener.onProcessTypeChanged(ProcessType.ASKING_PERMISSIONS);
237 | }
238 |
239 | if (permissionProvider.requestPermissions()) {
240 | LogUtils.logI("Waiting until we receive any callback from PermissionProvider...");
241 | } else {
242 | LogUtils.logI("Couldn't get permission, Abort!");
243 | failed(FailType.PERMISSION_DENIED);
244 | }
245 | }
246 | }
247 |
248 | private void permissionGranted(boolean alreadyHadPermission) {
249 | LogUtils.logI("We got permission!");
250 |
251 | if (listener != null) {
252 | listener.onPermissionGranted(alreadyHadPermission);
253 | }
254 |
255 | activeProvider.get();
256 | }
257 |
258 | private void failed(@FailType int type) {
259 | if (listener != null) {
260 | listener.onLocationFailed(type);
261 | }
262 | }
263 |
264 | @Override
265 | public void onPermissionsGranted() {
266 | permissionGranted(false);
267 | }
268 |
269 | @Override
270 | public void onPermissionsDenied() {
271 | failed(FailType.PERMISSION_DENIED);
272 | }
273 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/yayandroid/locationmanager/providers/locationprovider/DispatcherLocationProvider.java:
--------------------------------------------------------------------------------
1 | package com.yayandroid.locationmanager.providers.locationprovider;
2 |
3 | import android.app.Dialog;
4 | import android.content.DialogInterface;
5 | import android.content.Intent;
6 |
7 | import androidx.annotation.NonNull;
8 |
9 | import com.google.android.gms.common.ConnectionResult;
10 | import com.yayandroid.locationmanager.constants.FailType;
11 | import com.yayandroid.locationmanager.constants.RequestCode;
12 | import com.yayandroid.locationmanager.helper.LogUtils;
13 | import com.yayandroid.locationmanager.helper.continuoustask.ContinuousTask.ContinuousTaskRunner;
14 | import com.yayandroid.locationmanager.listener.FallbackListener;
15 |
16 | public class DispatcherLocationProvider extends LocationProvider implements ContinuousTaskRunner, FallbackListener {
17 |
18 | private Dialog gpServicesDialog;
19 | private LocationProvider activeProvider;
20 | private DispatcherLocationSource dispatcherLocationSource;
21 |
22 | @Override
23 | public void onPause() {
24 | super.onPause();
25 |
26 | if (activeProvider != null) {
27 | activeProvider.onPause();
28 | }
29 |
30 | getSourceProvider().gpServicesSwitchTask().pause();
31 | }
32 |
33 | @Override
34 | public void onResume() {
35 | super.onResume();
36 |
37 | if (activeProvider != null) {
38 | activeProvider.onResume();
39 | }
40 |
41 | getSourceProvider().gpServicesSwitchTask().resume();
42 | }
43 |
44 | @Override
45 | public void onDestroy() {
46 | super.onDestroy();
47 |
48 | if (activeProvider != null) {
49 | activeProvider.onDestroy();
50 | }
51 |
52 | getSourceProvider().gpServicesSwitchTask().stop();
53 |
54 | dispatcherLocationSource = null;
55 | gpServicesDialog = null;
56 | }
57 |
58 | @Override
59 | public void cancel() {
60 | if (activeProvider != null) {
61 | activeProvider.cancel();
62 | }
63 |
64 | getSourceProvider().gpServicesSwitchTask().stop();
65 | }
66 |
67 | @Override
68 | public boolean isWaiting() {
69 | return activeProvider != null && activeProvider.isWaiting();
70 | }
71 |
72 | @Override
73 | public boolean isDialogShowing() {
74 | boolean gpServicesDialogShown = gpServicesDialog != null && gpServicesDialog.isShowing();
75 | boolean anyProviderDialogShown = activeProvider != null && activeProvider.isDialogShowing();
76 | return gpServicesDialogShown || anyProviderDialogShown;
77 | }
78 |
79 | @Override
80 | public void runScheduledTask(@NonNull String taskId) {
81 | if (taskId.equals(DispatcherLocationSource.GOOGLE_PLAY_SERVICE_SWITCH_TASK)) {
82 | if (activeProvider instanceof GooglePlayServicesLocationProvider && activeProvider.isWaiting()) {
83 | LogUtils.logI("We couldn't receive location from GooglePlayServices, so switching default providers...");
84 | cancel();
85 | continueWithDefaultProviders();
86 | }
87 | }
88 | }
89 |
90 | @Override
91 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
92 | super.onActivityResult(requestCode, resultCode, data);
93 |
94 | if (requestCode == RequestCode.GOOGLE_PLAY_SERVICES) {
95 | // Check whether do we have gpServices now or still not!
96 | checkGooglePlayServicesAvailability(false);
97 | } else {
98 | if (activeProvider != null) {
99 | activeProvider.onActivityResult(requestCode, resultCode, data);
100 | }
101 | }
102 | }
103 |
104 | @Override
105 | public void get() {
106 | if (getConfiguration().googlePlayServicesConfiguration() != null) {
107 | checkGooglePlayServicesAvailability(true);
108 | } else {
109 | LogUtils.logI("Configuration requires not to use Google Play Services, "
110 | + "so skipping that step to Default Location Providers");
111 | continueWithDefaultProviders();
112 | }
113 | }
114 |
115 | @Override
116 | public void onFallback() {
117 | // This is called from GooglePlayServicesLocationProvider when it fails to before its scheduled time
118 | cancel();
119 | continueWithDefaultProviders();
120 | }
121 |
122 | void checkGooglePlayServicesAvailability(boolean askForGooglePlayServices) {
123 | int gpServicesAvailability = getSourceProvider().isGoogleApiAvailable(getContext());
124 |
125 | if (gpServicesAvailability == ConnectionResult.SUCCESS) {
126 | LogUtils.logI("GooglePlayServices is available on device.");
127 | getLocationFromGooglePlayServices();
128 | } else {
129 | LogUtils.logI("GooglePlayServices is NOT available on device.");
130 | if (askForGooglePlayServices) {
131 | askForGooglePlayServices(gpServicesAvailability);
132 | } else {
133 | LogUtils.logI("GooglePlayServices is NOT available and even though we ask user to handle error, "
134 | + "it is still NOT available.");
135 |
136 | // This means get method is called by onActivityResult
137 | // which we already ask user to handle with gpServices error
138 | continueWithDefaultProviders();
139 | }
140 | }
141 | }
142 |
143 | void askForGooglePlayServices(int gpServicesAvailability) {
144 | if (getConfiguration().googlePlayServicesConfiguration().askForGooglePlayServices() &&
145 | getSourceProvider().isGoogleApiErrorUserResolvable(gpServicesAvailability)) {
146 |
147 | resolveGooglePlayServices(gpServicesAvailability);
148 | } else {
149 | LogUtils.logI("Either GooglePlayServices error is not resolvable "
150 | + "or the configuration doesn't wants us to bother user.");
151 | continueWithDefaultProviders();
152 | }
153 | }
154 |
155 | /**
156 | * Handle GooglePlayServices error. Try showing a dialog that maybe can fix the error by user action.
157 | * If error cannot be resolved or user cancelled dialog or dialog cannot be displayed, then {@link #continueWithDefaultProviders()} is called.
158 | *
159 | * The {@link com.google.android.gms.common.GoogleApiAvailability#isGooglePlayServicesAvailable(android.content.Context)} returns one of following in {@link ConnectionResult}:
160 | * SUCCESS, SERVICE_MISSING, SERVICE_UPDATING, SERVICE_VERSION_UPDATE_REQUIRED, SERVICE_DISABLED, SERVICE_INVALID.
161 | *
162 | * See https://developers.google.com/android/reference/com/google/android/gms/common/GoogleApiAvailability#public-int-isgoogleplayservicesavailable-context-context
163 | */
164 | void resolveGooglePlayServices(int gpServicesAvailability) {
165 | LogUtils.logI("Asking user to handle GooglePlayServices error...");
166 | gpServicesDialog = getSourceProvider().getGoogleApiErrorDialog(getActivity(), gpServicesAvailability,
167 | RequestCode.GOOGLE_PLAY_SERVICES, new DialogInterface.OnCancelListener() {
168 | @Override
169 | public void onCancel(DialogInterface dialog) {
170 | LogUtils.logI("GooglePlayServices error could've been resolved, "
171 | + "but user canceled it.");
172 | continueWithDefaultProviders();
173 | }
174 | });
175 |
176 | if (gpServicesDialog != null) {
177 |
178 | /*
179 | The SERVICE_INVALID, SERVICE_UPDATING errors cannot be resolved via user action.
180 | In these cases, when user closes dialog by clicking OK button, OnCancelListener is not called.
181 | So, to handle these errors, we attach a dismiss event listener that calls continueWithDefaultProviders(), when dialog is closed.
182 | */
183 | switch (gpServicesAvailability) {
184 | // The version of the Google Play services installed on this device is not authentic.
185 | case ConnectionResult.SERVICE_INVALID:
186 | // Google Play service is currently being updated on this device.
187 | case ConnectionResult.SERVICE_UPDATING:
188 | gpServicesDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
189 | @Override
190 | public void onDismiss(DialogInterface dialog) {
191 | LogUtils.logI("GooglePlayServices error could not have been resolved");
192 | continueWithDefaultProviders();
193 | }
194 | });
195 |
196 | break;
197 | }
198 |
199 | gpServicesDialog.show();
200 | } else {
201 | LogUtils.logI("GooglePlayServices error could've been resolved, but since LocationManager "
202 | + "is not running on an Activity, dialog cannot be displayed.");
203 | continueWithDefaultProviders();
204 | }
205 | }
206 |
207 | void getLocationFromGooglePlayServices() {
208 | LogUtils.logI("Attempting to get location from Google Play Services providers...");
209 | setLocationProvider(getSourceProvider().createGooglePlayServicesLocationProvider(this));
210 | getSourceProvider().gpServicesSwitchTask().delayed(getConfiguration()
211 | .googlePlayServicesConfiguration().googlePlayServicesWaitPeriod());
212 | activeProvider.get();
213 | }
214 |
215 | /**
216 | * Called in case of Google Play Services failed to retrieve location,
217 | * or GooglePlayServicesConfiguration doesn't provided by developer
218 | */
219 | void continueWithDefaultProviders() {
220 | if (getConfiguration().defaultProviderConfiguration() == null) {
221 | LogUtils.logI("Configuration requires not to use default providers, abort!");
222 | if (getListener() != null) {
223 | getListener().onLocationFailed(FailType.GOOGLE_PLAY_SERVICES_NOT_AVAILABLE);
224 | }
225 | } else {
226 | LogUtils.logI("Attempting to get location from default providers...");
227 | setLocationProvider(getSourceProvider().createDefaultLocationProvider());
228 | activeProvider.get();
229 | }
230 | }
231 |
232 | void setLocationProvider(LocationProvider provider) {
233 | this.activeProvider = provider;
234 | activeProvider.configure(this);
235 | }
236 |
237 | // For test purposes
238 | void setDispatcherLocationSource(DispatcherLocationSource dispatcherLocationSource) {
239 | this.dispatcherLocationSource = dispatcherLocationSource;
240 | }
241 |
242 | private DispatcherLocationSource getSourceProvider() {
243 | if (dispatcherLocationSource == null) {
244 | dispatcherLocationSource = new DispatcherLocationSource(this);
245 | }
246 | return dispatcherLocationSource;
247 | }
248 | }
249 |
--------------------------------------------------------------------------------