diff --git a/Geofencing/app/build.gradle b/Geofencing/app/build.gradle index f3be04c8..8d1d30f8 100644 --- a/Geofencing/app/build.gradle +++ b/Geofencing/app/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 28 + compileSdkVersion 33 defaultConfig { applicationId "com.google.android.gms.location.sample.geofencing" - minSdkVersion 16 - targetSdkVersion 28 + minSdkVersion 19 + targetSdkVersion 33 versionCode 1 versionName "1.0" } @@ -22,10 +22,10 @@ dependencies { androidTestImplementation('androidx.test.espresso:espresso-core:3.1.1', { exclude group: 'com.android.support', module: 'support-annotations' }) - implementation 'androidx.appcompat:appcompat:1.1.0' - testImplementation 'junit:junit:4.12' + implementation 'androidx.appcompat:appcompat:1.5.1' + testImplementation 'junit:junit:4.13.2' - implementation 'com.google.android.material:material:1.0.0' - implementation 'com.google.android.gms:play-services-location:17.0.0' - implementation 'com.google.android.gms:play-services-maps:17.0.0' + implementation 'com.google.android.material:material:1.6.1' + implementation 'com.google.android.gms:play-services-location:20.0.0' + implementation 'com.google.android.gms:play-services-maps:18.1.0' } diff --git a/Geofencing/app/src/main/AndroidManifest.xml b/Geofencing/app/src/main/AndroidManifest.xml index aa92eaa4..54c72f0f 100644 --- a/Geofencing/app/src/main/AndroidManifest.xml +++ b/Geofencing/app/src/main/AndroidManifest.xml @@ -17,6 +17,9 @@ limitations under the License. package="com.google.android.gms.location.sample.geofencing"> + + // Required only when requesting background location access on Android 10 (API level 29) and higher. + diff --git a/Geofencing/app/src/main/java/com/google/android/gms/location/sample/geofencing/MainActivity.java b/Geofencing/app/src/main/java/com/google/android/gms/location/sample/geofencing/MainActivity.java index 79ee5a02..93f8e676 100644 --- a/Geofencing/app/src/main/java/com/google/android/gms/location/sample/geofencing/MainActivity.java +++ b/Geofencing/app/src/main/java/com/google/android/gms/location/sample/geofencing/MainActivity.java @@ -22,18 +22,19 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.provider.Settings; -import androidx.annotation.NonNull; -import com.google.android.material.snackbar.Snackbar; -import androidx.core.app.ActivityCompat; -import androidx.appcompat.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; + import com.google.android.gms.location.Geofence; import com.google.android.gms.location.GeofencingClient; import com.google.android.gms.location.GeofencingRequest; @@ -41,17 +42,20 @@ import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; +import com.google.android.material.snackbar.Snackbar; import java.util.ArrayList; import java.util.Map; /** - * Demonstrates how to create and remove geofences using the GeofencingApi. Uses an IntentService - * to monitor geofence transitions and creates notifications whenever a device enters or exits - * a geofence. - *

- * This sample requires a device's Location settings to be turned on. It also requires - * the ACCESS_FINE_LOCATION permission, as specified in AndroidManifest.xml. + * Demonstrates how to create and remove geofences using the GeofencingApi. Uses an IntentService to + * monitor geofence transitions and creates notifications whenever a device enters or exits a + * geofence. + * + *

This sample requires a device's Location settings to be turned on. It also requires the + * ACCESS_FINE_LOCATION permission and the ACCESS_BACKGROUND_LOCATION permission, as specified in + * AndroidManifest.xml. + * *

*/ public class MainActivity extends AppCompatActivity implements OnCompleteListener { @@ -60,26 +64,20 @@ public class MainActivity extends AppCompatActivity implements OnCompleteListene private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34; - /** - * Tracks whether the user requested to add or remove geofences, or to do neither. - */ + /** Tracks whether the user requested to add or remove geofences, or to do neither. */ private enum PendingGeofenceTask { - ADD, REMOVE, NONE + ADD, + REMOVE, + NONE } - /** - * Provides access to the Geofencing API. - */ + /** Provides access to the Geofencing API. */ private GeofencingClient mGeofencingClient; - /** - * The list of geofences used in this sample. - */ + /** The list of geofences used in this sample. */ private ArrayList mGeofenceList; - /** - * Used when requesting to add or remove geofences. - */ + /** Used when requesting to add or remove geofences. */ private PendingIntent mGeofencePendingIntent; // Buttons for kicking off the process of adding or removing geofences. @@ -94,8 +92,8 @@ public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.main_activity); // Get the UI widgets. - mAddGeofencesButton = (Button) findViewById(R.id.add_geofences_button); - mRemoveGeofencesButton = (Button) findViewById(R.id.remove_geofences_button); + mAddGeofencesButton = findViewById(R.id.add_geofences_button); + mRemoveGeofencesButton = findViewById(R.id.remove_geofences_button); // Empty list for storing geofences. mGeofenceList = new ArrayList<>(); @@ -108,7 +106,7 @@ public void onCreate(Bundle savedInstanceState) { // Get the geofences used. Geofence data is hard coded in this sample. populateGeofenceList(); - mGeofencingClient = LocationServices.getGeofencingClient(this); + mGeofencingClient = LocationServices.getGeofencingClient(this); } @Override @@ -123,8 +121,8 @@ public void onStart() { } /** - * Builds and returns a GeofencingRequest. Specifies the list of geofences to be monitored. - * Also specifies how the geofence notifications are initially triggered. + * Builds and returns a GeofencingRequest. Specifies the list of geofences to be monitored. Also + * specifies how the geofence notifications are initially triggered. */ private GeofencingRequest getGeofencingRequest() { GeofencingRequest.Builder builder = new GeofencingRequest.Builder(); @@ -165,7 +163,8 @@ private void addGeofences() { return; } - mGeofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent()) + mGeofencingClient + .addGeofences(getGeofencingRequest(), getGeofencePendingIntent()) .addOnCompleteListener(this); } @@ -197,8 +196,9 @@ private void removeGeofences() { } /** - * Runs when the result of calling {@link #addGeofences()} and/or {@link #removeGeofences()} - * is available. + * Runs when the result of calling {@link #addGeofences()} and/or {@link #removeGeofences()} is + * available. + * * @param task the resulting Task, containing either a result or error. */ @Override @@ -208,13 +208,13 @@ public void onComplete(@NonNull Task task) { updateGeofencesAdded(!getGeofencesAdded()); setButtonsEnabledState(); - int messageId = getGeofencesAdded() ? R.string.geofences_added : - R.string.geofences_removed; + int messageId = + getGeofencesAdded() ? R.string.geofences_added : R.string.geofences_removed; Toast.makeText(this, getString(messageId), Toast.LENGTH_SHORT).show(); } else { // Get the status code for the error and log it using a user-friendly message. String errorMessage = GeofenceErrorMessages.getErrorString(this, task.getException()); - Log.w(TAG, errorMessage); + Log.w(TAG, errorMessage, task.getException()); } } @@ -233,8 +233,15 @@ private PendingIntent getGeofencePendingIntent() { Intent intent = new Intent(this, GeofenceBroadcastReceiver.class); // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling // addGeofences() and removeGeofences(). - mGeofencePendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - return mGeofencePendingIntent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + return PendingIntent.getBroadcast( + this, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + } else { + return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } } /** @@ -244,36 +251,39 @@ private PendingIntent getGeofencePendingIntent() { private void populateGeofenceList() { for (Map.Entry entry : Constants.BAY_AREA_LANDMARKS.entrySet()) { - mGeofenceList.add(new Geofence.Builder() - // Set the request ID of the geofence. This is a string to identify this - // geofence. - .setRequestId(entry.getKey()) - - // Set the circular region of this geofence. - .setCircularRegion( - entry.getValue().latitude, - entry.getValue().longitude, - Constants.GEOFENCE_RADIUS_IN_METERS - ) - - // Set the expiration duration of the geofence. This geofence gets automatically - // removed after this period of time. - .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS) - - // Set the transition types of interest. Alerts are only generated for these - // transition. We track entry and exit transitions in this sample. - .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | - Geofence.GEOFENCE_TRANSITION_EXIT) - - // Create the geofence. - .build()); + mGeofenceList.add( + new Geofence.Builder() + // Set the request ID of the geofence. This is a string to identify this + // geofence. + .setRequestId(entry.getKey()) + + // Set the circular region of this geofence. + .setCircularRegion( + entry.getValue().latitude, + entry.getValue().longitude, + Constants.GEOFENCE_RADIUS_IN_METERS) + + // Set the expiration duration of the geofence. This geofence gets + // automatically + // removed after this period of time. + .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS) + + // Set the transition types of interest. Alerts are only generated for + // these + // transition. We track entry and exit transitions in this sample. + .setTransitionTypes( + Geofence.GEOFENCE_TRANSITION_ENTER + | Geofence.GEOFENCE_TRANSITION_EXIT) + + // Create the geofence. + .build()); } } /** - * Ensures that only one button is enabled at any time. The Add Geofences button is enabled - * if the user hasn't yet added geofences. The Remove Geofences button is enabled if the - * user has added geofences. + * Ensures that only one button is enabled at any time. The Add Geofences button is enabled if + * the user hasn't yet added geofences. The Remove Geofences button is enabled if the user has + * added geofences. */ private void setButtonsEnabledState() { if (getGeofencesAdded()) { @@ -301,24 +311,23 @@ private void showSnackbar(final String text) { * Shows a {@link Snackbar}. * * @param mainTextStringId The id for the string resource for the Snackbar text. - * @param actionStringId The text of the action item. - * @param listener The listener associated with the Snackbar action. + * @param actionStringId The text of the action item. + * @param listener The listener associated with the Snackbar action. */ - private void showSnackbar(final int mainTextStringId, final int actionStringId, - View.OnClickListener listener) { + private void showSnackbar( + final int mainTextStringId, final int actionStringId, View.OnClickListener listener) { Snackbar.make( - findViewById(android.R.id.content), - getString(mainTextStringId), - Snackbar.LENGTH_INDEFINITE) - .setAction(getString(actionStringId), listener).show(); + findViewById(android.R.id.content), + getString(mainTextStringId), + Snackbar.LENGTH_INDEFINITE) + .setAction(getString(actionStringId), listener) + .show(); } - /** - * Returns true if geofences were added, otherwise false. - */ + /** Returns true if geofences were added, otherwise false. */ private boolean getGeofencesAdded() { - return PreferenceManager.getDefaultSharedPreferences(this).getBoolean( - Constants.GEOFENCES_ADDED_KEY, false); + return PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean(Constants.GEOFENCES_ADDED_KEY, false); } /** @@ -333,9 +342,7 @@ private void updateGeofencesAdded(boolean added) { .apply(); } - /** - * Performs the geofencing task that was pending until location permission was granted. - */ + /** Performs the geofencing task that was pending until location permission was granted. */ private void performPendingGeofenceTask() { if (mPendingGeofenceTask == PendingGeofenceTask.ADD) { addGeofences(); @@ -344,51 +351,63 @@ private void performPendingGeofenceTask() { } } - /** - * Return the current state of the permissions needed. - */ + /** Return the current state of the permissions needed. */ private boolean checkPermissions() { - int permissionState = ActivityCompat.checkSelfPermission(this, - Manifest.permission.ACCESS_FINE_LOCATION); + int permissionState = + ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION); + if (permissionState != PackageManager.PERMISSION_GRANTED) { + return false; + } + permissionState = + ActivityCompat.checkSelfPermission( + this, Manifest.permission.ACCESS_BACKGROUND_LOCATION); return permissionState == PackageManager.PERMISSION_GRANTED; } private void requestPermissions() { boolean shouldProvideRationale = - ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.ACCESS_FINE_LOCATION); + ActivityCompat.shouldShowRequestPermissionRationale( + this, Manifest.permission.ACCESS_FINE_LOCATION) + && ActivityCompat.shouldShowRequestPermissionRationale( + this, Manifest.permission.ACCESS_BACKGROUND_LOCATION); // Provide an additional rationale to the user. This would happen if the user denied the // request previously, but didn't check the "Don't ask again" checkbox. if (shouldProvideRationale) { Log.i(TAG, "Displaying permission rationale to provide additional context."); - showSnackbar(R.string.permission_rationale, android.R.string.ok, - new View.OnClickListener() { - @Override - public void onClick(View view) { - // Request permission - ActivityCompat.requestPermissions(MainActivity.this, - new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, - REQUEST_PERMISSIONS_REQUEST_CODE); - } + showSnackbar( + R.string.permission_rationale, + android.R.string.ok, + view -> { + // Request permission + ActivityCompat.requestPermissions( + MainActivity.this, + new String[] { + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_BACKGROUND_LOCATION + }, + REQUEST_PERMISSIONS_REQUEST_CODE); }); } else { Log.i(TAG, "Requesting permission"); // Request permission. It's possible this can be auto answered if device policy // sets the permission in a given state or the user denied the permission // previously and checked "Never ask again". - ActivityCompat.requestPermissions(MainActivity.this, - new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, + ActivityCompat.requestPermissions( + MainActivity.this, + new String[] { + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_BACKGROUND_LOCATION + }, REQUEST_PERMISSIONS_REQUEST_CODE); } } - /** - * Callback received when a permissions request has been completed. - */ + /** Callback received when a permissions request has been completed. */ @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { + public void onRequestPermissionsResult( + int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); Log.i(TAG, "onRequestPermissionResult"); if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) { if (grantResults.length <= 0) { @@ -410,20 +429,17 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis // again" prompts). Therefore, a user interface affordance is typically implemented // when permissions are denied. Otherwise, your app could appear unresponsive to // touches or interactions which have required permissions. - showSnackbar(R.string.permission_denied_explanation, R.string.settings, - new View.OnClickListener() { - @Override - public void onClick(View view) { - // Build intent that displays the App settings screen. - Intent intent = new Intent(); - intent.setAction( - Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - Uri uri = Uri.fromParts("package", - BuildConfig.APPLICATION_ID, null); - intent.setData(uri); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } + showSnackbar( + R.string.permission_denied_explanation, + R.string.settings, + view -> { + // Build intent that displays the App settings screen. + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + Uri uri = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null); + intent.setData(uri); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); }); mPendingGeofenceTask = PendingGeofenceTask.NONE; } diff --git a/Geofencing/build.gradle b/Geofencing/build.gradle index df8f9e2f..357f64ec 100644 --- a/Geofencing/build.gradle +++ b/Geofencing/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.1' + classpath 'com.android.tools.build:gradle:7.2.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/Geofencing/gradle.properties b/Geofencing/gradle.properties index 5d08ba75..64c14731 100644 --- a/Geofencing/gradle.properties +++ b/Geofencing/gradle.properties @@ -15,4 +15,5 @@ # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true +android.useAndroidX=true \ No newline at end of file diff --git a/Geofencing/gradle/wrapper/gradle-wrapper.properties b/Geofencing/gradle/wrapper/gradle-wrapper.properties index 2c302a99..a98f0b14 100644 --- a/Geofencing/gradle/wrapper/gradle-wrapper.properties +++ b/Geofencing/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip