From a03669c67a9a12be0966ffb52d83b93ec4120275 Mon Sep 17 00:00:00 2001 From: Ada Date: Mon, 19 Sep 2022 16:54:15 +0800 Subject: [PATCH 1/3] Upgrade the Gradle to the latest version --- Geofencing/app/build.gradle | 16 +++--- Geofencing/app/src/main/AndroidManifest.xml | 2 + .../sample/geofencing/MainActivity.java | 51 +++++++++---------- Geofencing/build.gradle | 2 +- Geofencing/gradle.properties | 3 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 6 files changed, 39 insertions(+), 37 deletions(-) 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..07466ad0 100644 --- a/Geofencing/app/src/main/AndroidManifest.xml +++ b/Geofencing/app/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ limitations under the License. package="com.google.android.gms.location.sample.geofencing"> + 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..7c97691f 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,6 +22,7 @@ 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; @@ -94,8 +95,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<>(); @@ -214,7 +215,7 @@ public void onComplete(@NonNull Task task) { } 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 +234,11 @@ 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 mGeofencePendingIntent; + } } /** @@ -363,14 +367,11 @@ private void requestPermissions() { 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); - } + view -> { + // Request permission + ActivityCompat.requestPermissions(MainActivity.this, + new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, + REQUEST_PERMISSIONS_REQUEST_CODE); }); } else { Log.i(TAG, "Requesting permission"); @@ -389,6 +390,7 @@ public void onClick(View view) { @Override 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) { @@ -411,19 +413,16 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis // 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); - } + 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 From 09e1cbd19ac8f7e5e09e2e341ca816f8aa97a9a7 Mon Sep 17 00:00:00 2001 From: Ada Date: Tue, 20 Sep 2022 11:45:56 +0800 Subject: [PATCH 2/3] Add tristate support for location permission --- Geofencing/app/src/main/AndroidManifest.xml | 2 + .../sample/geofencing/MainActivity.java | 705 +++++++++--------- 2 files changed, 357 insertions(+), 350 deletions(-) diff --git a/Geofencing/app/src/main/AndroidManifest.xml b/Geofencing/app/src/main/AndroidManifest.xml index 07466ad0..54c72f0f 100644 --- a/Geofencing/app/src/main/AndroidManifest.xml +++ b/Geofencing/app/src/main/AndroidManifest.xml @@ -18,6 +18,8 @@ limitations under the License. + // Required only when requesting background location access on Android 10 (API level 29) and higher. + - * 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 { - private static final String TAG = MainActivity.class.getSimpleName(); - - private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34; - - /** - * Tracks whether the user requested to add or remove geofences, or to do neither. - */ - private enum PendingGeofenceTask { - ADD, REMOVE, NONE - } - - /** - * Provides access to the Geofencing API. - */ - private GeofencingClient mGeofencingClient; - - /** - * The list of geofences used in this sample. - */ - private ArrayList mGeofenceList; - - /** - * Used when requesting to add or remove geofences. - */ - private PendingIntent mGeofencePendingIntent; + private static final String TAG = MainActivity.class.getSimpleName(); - // Buttons for kicking off the process of adding or removing geofences. - private Button mAddGeofencesButton; - private Button mRemoveGeofencesButton; + private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34; - private PendingGeofenceTask mPendingGeofenceTask = PendingGeofenceTask.NONE; + /** Tracks whether the user requested to add or remove geofences, or to do neither. */ + private enum PendingGeofenceTask { + ADD, + REMOVE, + NONE + } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main_activity); + /** Provides access to the Geofencing API. */ + private GeofencingClient mGeofencingClient; - // Get the UI widgets. - mAddGeofencesButton = findViewById(R.id.add_geofences_button); - mRemoveGeofencesButton = findViewById(R.id.remove_geofences_button); + /** The list of geofences used in this sample. */ + private ArrayList mGeofenceList; - // Empty list for storing geofences. - mGeofenceList = new ArrayList<>(); + /** Used when requesting to add or remove geofences. */ + private PendingIntent mGeofencePendingIntent; - // Initially set the PendingIntent used in addGeofences() and removeGeofences() to null. - mGeofencePendingIntent = null; + // Buttons for kicking off the process of adding or removing geofences. + private Button mAddGeofencesButton; + private Button mRemoveGeofencesButton; - setButtonsEnabledState(); + private PendingGeofenceTask mPendingGeofenceTask = PendingGeofenceTask.NONE; - // Get the geofences used. Geofence data is hard coded in this sample. - populateGeofenceList(); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_activity); - mGeofencingClient = LocationServices.getGeofencingClient(this); - } + // Get the UI widgets. + mAddGeofencesButton = findViewById(R.id.add_geofences_button); + mRemoveGeofencesButton = findViewById(R.id.remove_geofences_button); - @Override - public void onStart() { - super.onStart(); + // Empty list for storing geofences. + mGeofenceList = new ArrayList<>(); - if (!checkPermissions()) { - requestPermissions(); - } else { - performPendingGeofenceTask(); - } - } + // Initially set the PendingIntent used in addGeofences() and removeGeofences() to null. + mGeofencePendingIntent = null; - /** - * 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(); + setButtonsEnabledState(); - // The INITIAL_TRIGGER_ENTER flag indicates that geofencing service should trigger a - // GEOFENCE_TRANSITION_ENTER notification when the geofence is added and if the device - // is already inside that geofence. - builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER); + // Get the geofences used. Geofence data is hard coded in this sample. + populateGeofenceList(); - // Add the geofences to be monitored by geofencing service. - builder.addGeofences(mGeofenceList); + mGeofencingClient = LocationServices.getGeofencingClient(this); + } - // Return a GeofencingRequest. - return builder.build(); - } + @Override + public void onStart() { + super.onStart(); - /** - * Adds geofences, which sets alerts to be notified when the device enters or exits one of the - * specified geofences. Handles the success or failure results returned by addGeofences(). - */ - public void addGeofencesButtonHandler(View view) { - if (!checkPermissions()) { - mPendingGeofenceTask = PendingGeofenceTask.ADD; - requestPermissions(); - return; - } - addGeofences(); + if (!checkPermissions()) { + requestPermissions(); + } else { + performPendingGeofenceTask(); } - - /** - * Adds geofences. This method should be called after the user has granted the location - * permission. - */ - @SuppressWarnings("MissingPermission") - private void addGeofences() { - if (!checkPermissions()) { - showSnackbar(getString(R.string.insufficient_permissions)); - return; - } - - mGeofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent()) - .addOnCompleteListener(this); + } + + /** + * 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(); + + // The INITIAL_TRIGGER_ENTER flag indicates that geofencing service should trigger a + // GEOFENCE_TRANSITION_ENTER notification when the geofence is added and if the device + // is already inside that geofence. + builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER); + + // Add the geofences to be monitored by geofencing service. + builder.addGeofences(mGeofenceList); + + // Return a GeofencingRequest. + return builder.build(); + } + + /** + * Adds geofences, which sets alerts to be notified when the device enters or exits one of the + * specified geofences. Handles the success or failure results returned by addGeofences(). + */ + public void addGeofencesButtonHandler(View view) { + if (!checkPermissions()) { + mPendingGeofenceTask = PendingGeofenceTask.ADD; + requestPermissions(); + return; } - - /** - * Removes geofences, which stops further notifications when the device enters or exits - * previously registered geofences. - */ - public void removeGeofencesButtonHandler(View view) { - if (!checkPermissions()) { - mPendingGeofenceTask = PendingGeofenceTask.REMOVE; - requestPermissions(); - return; - } - removeGeofences(); + addGeofences(); + } + + /** + * Adds geofences. This method should be called after the user has granted the location + * permission. + */ + @SuppressWarnings("MissingPermission") + private void addGeofences() { + if (!checkPermissions()) { + showSnackbar(getString(R.string.insufficient_permissions)); + return; } - /** - * Removes geofences. This method should be called after the user has granted the location - * permission. - */ - @SuppressWarnings("MissingPermission") - private void removeGeofences() { - if (!checkPermissions()) { - showSnackbar(getString(R.string.insufficient_permissions)); - return; - } - - mGeofencingClient.removeGeofences(getGeofencePendingIntent()).addOnCompleteListener(this); + mGeofencingClient + .addGeofences(getGeofencingRequest(), getGeofencePendingIntent()) + .addOnCompleteListener(this); + } + + /** + * Removes geofences, which stops further notifications when the device enters or exits previously + * registered geofences. + */ + public void removeGeofencesButtonHandler(View view) { + if (!checkPermissions()) { + mPendingGeofenceTask = PendingGeofenceTask.REMOVE; + requestPermissions(); + return; } - - /** - * 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 - public void onComplete(@NonNull Task task) { - mPendingGeofenceTask = PendingGeofenceTask.NONE; - if (task.isSuccessful()) { - updateGeofencesAdded(!getGeofencesAdded()); - setButtonsEnabledState(); - - 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, task.getException()); - } + removeGeofences(); + } + + /** + * Removes geofences. This method should be called after the user has granted the location + * permission. + */ + @SuppressWarnings("MissingPermission") + private void removeGeofences() { + if (!checkPermissions()) { + showSnackbar(getString(R.string.insufficient_permissions)); + return; } - /** - * Gets a PendingIntent to send with the request to add or remove Geofences. Location Services - * issues the Intent inside this PendingIntent whenever a geofence transition occurs for the - * current list of geofences. - * - * @return A PendingIntent for the IntentService that handles geofence transitions. - */ - private PendingIntent getGeofencePendingIntent() { - // Reuse the PendingIntent if we already have it. - if (mGeofencePendingIntent != null) { - return mGeofencePendingIntent; - } - 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(). - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); - } else { - return mGeofencePendingIntent; - } + mGeofencingClient.removeGeofences(getGeofencePendingIntent()).addOnCompleteListener(this); + } + + /** + * 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 + public void onComplete(@NonNull Task task) { + mPendingGeofenceTask = PendingGeofenceTask.NONE; + if (task.isSuccessful()) { + updateGeofencesAdded(!getGeofencesAdded()); + setButtonsEnabledState(); + + 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, task.getException()); } - - /** - * This sample hard codes geofence data. A real app might dynamically create geofences based on - * the user's location. - */ - 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()); - } + } + + /** + * Gets a PendingIntent to send with the request to add or remove Geofences. Location Services + * issues the Intent inside this PendingIntent whenever a geofence transition occurs for the + * current list of geofences. + * + * @return A PendingIntent for the IntentService that handles geofence transitions. + */ + private PendingIntent getGeofencePendingIntent() { + // Reuse the PendingIntent if we already have it. + if (mGeofencePendingIntent != null) { + return mGeofencePendingIntent; } - - /** - * 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()) { - mAddGeofencesButton.setEnabled(false); - mRemoveGeofencesButton.setEnabled(true); - } else { - mAddGeofencesButton.setEnabled(true); - mRemoveGeofencesButton.setEnabled(false); - } + 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(). + 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); } - - /** - * Shows a {@link Snackbar} using {@code text}. - * - * @param text The Snackbar text. - */ - private void showSnackbar(final String text) { - View container = findViewById(android.R.id.content); - if (container != null) { - Snackbar.make(container, text, Snackbar.LENGTH_LONG).show(); - } + } + + /** + * This sample hard codes geofence data. A real app might dynamically create geofences based on + * the user's location. + */ + 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()); } - - /** - * 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. - */ - 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(); + } + + /** + * 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()) { + mAddGeofencesButton.setEnabled(false); + mRemoveGeofencesButton.setEnabled(true); + } else { + mAddGeofencesButton.setEnabled(true); + mRemoveGeofencesButton.setEnabled(false); } - - /** - * Returns true if geofences were added, otherwise false. - */ - private boolean getGeofencesAdded() { - return PreferenceManager.getDefaultSharedPreferences(this).getBoolean( - Constants.GEOFENCES_ADDED_KEY, false); + } + + /** + * Shows a {@link Snackbar} using {@code text}. + * + * @param text The Snackbar text. + */ + private void showSnackbar(final String text) { + View container = findViewById(android.R.id.content); + if (container != null) { + Snackbar.make(container, text, Snackbar.LENGTH_LONG).show(); } - - /** - * Stores whether geofences were added ore removed in {@link SharedPreferences}; - * - * @param added Whether geofences were added or removed. - */ - private void updateGeofencesAdded(boolean added) { - PreferenceManager.getDefaultSharedPreferences(this) - .edit() - .putBoolean(Constants.GEOFENCES_ADDED_KEY, added) - .apply(); + } + + /** + * 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. + */ + 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(); + } + + /** Returns true if geofences were added, otherwise false. */ + private boolean getGeofencesAdded() { + return PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean(Constants.GEOFENCES_ADDED_KEY, false); + } + + /** + * Stores whether geofences were added ore removed in {@link SharedPreferences}; + * + * @param added Whether geofences were added or removed. + */ + private void updateGeofencesAdded(boolean added) { + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putBoolean(Constants.GEOFENCES_ADDED_KEY, added) + .apply(); + } + + /** Performs the geofencing task that was pending until location permission was granted. */ + private void performPendingGeofenceTask() { + if (mPendingGeofenceTask == PendingGeofenceTask.ADD) { + addGeofences(); + } else if (mPendingGeofenceTask == PendingGeofenceTask.REMOVE) { + removeGeofences(); } - - /** - * Performs the geofencing task that was pending until location permission was granted. - */ - private void performPendingGeofenceTask() { - if (mPendingGeofenceTask == PendingGeofenceTask.ADD) { - addGeofences(); - } else if (mPendingGeofenceTask == PendingGeofenceTask.REMOVE) { - removeGeofences(); - } + } + + /** Return the current state of the permissions needed. */ + private boolean checkPermissions() { + int permissionState = + ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION); + if (permissionState != PackageManager.PERMISSION_GRANTED) { + return false; } - - /** - * Return the current state of the permissions needed. - */ - private boolean checkPermissions() { - int permissionState = ActivityCompat.checkSelfPermission(this, - Manifest.permission.ACCESS_FINE_LOCATION); - return permissionState == PackageManager.PERMISSION_GRANTED; + 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_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, + 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, Manifest.permission.ACCESS_BACKGROUND_LOCATION + }, + REQUEST_PERMISSIONS_REQUEST_CODE); } - - private void requestPermissions() { - boolean shouldProvideRationale = - ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.ACCESS_FINE_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, - view -> { - // Request permission - ActivityCompat.requestPermissions(MainActivity.this, - new String[]{Manifest.permission.ACCESS_FINE_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}, - REQUEST_PERMISSIONS_REQUEST_CODE); - } - } - - /** - * Callback received when a permissions request has been completed. - */ - @Override - 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) { - // If user interaction was interrupted, the permission request is cancelled and you - // receive empty arrays. - Log.i(TAG, "User interaction was cancelled."); - } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Log.i(TAG, "Permission granted."); - performPendingGeofenceTask(); - } else { - // Permission denied. - - // Notify the user via a SnackBar that they have rejected a core permission for the - // app, which makes the Activity useless. In a real app, core permissions would - // typically be best requested during a welcome-screen flow. - - // Additionally, it is important to remember that a permission might have been - // rejected without asking the user for permission (device policy or "Never ask - // 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, - 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; - } - } + } + + /** Callback received when a permissions request has been completed. */ + @Override + 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) { + // If user interaction was interrupted, the permission request is cancelled and you + // receive empty arrays. + Log.i(TAG, "User interaction was cancelled."); + } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Log.i(TAG, "Permission granted."); + performPendingGeofenceTask(); + } else { + // Permission denied. + + // Notify the user via a SnackBar that they have rejected a core permission for the + // app, which makes the Activity useless. In a real app, core permissions would + // typically be best requested during a welcome-screen flow. + + // Additionally, it is important to remember that a permission might have been + // rejected without asking the user for permission (device policy or "Never ask + // 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, + 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; + } } + } } From 5cc3a31f1a6b7559dd07a26c7938ea478b46e66c Mon Sep 17 00:00:00 2001 From: Ada Date: Wed, 21 Sep 2022 08:31:47 +0800 Subject: [PATCH 3/3] Reformat the code to AOSP --- .../sample/geofencing/MainActivity.java | 696 +++++++++--------- 1 file changed, 354 insertions(+), 342 deletions(-) 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 8356e561..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 @@ -30,9 +30,11 @@ 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,6 +43,7 @@ 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; @@ -57,380 +60,389 @@ */ public class MainActivity extends AppCompatActivity implements OnCompleteListener { - private static final String TAG = MainActivity.class.getSimpleName(); + private static final String TAG = MainActivity.class.getSimpleName(); + + private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34; + + /** Tracks whether the user requested to add or remove geofences, or to do neither. */ + private enum PendingGeofenceTask { + ADD, + REMOVE, + NONE + } + + /** Provides access to the Geofencing API. */ + private GeofencingClient mGeofencingClient; - private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34; + /** The list of geofences used in this sample. */ + private ArrayList mGeofenceList; - /** Tracks whether the user requested to add or remove geofences, or to do neither. */ - private enum PendingGeofenceTask { - ADD, - REMOVE, - NONE - } + /** Used when requesting to add or remove geofences. */ + private PendingIntent mGeofencePendingIntent; - /** Provides access to the Geofencing API. */ - private GeofencingClient mGeofencingClient; + // Buttons for kicking off the process of adding or removing geofences. + private Button mAddGeofencesButton; + private Button mRemoveGeofencesButton; - /** The list of geofences used in this sample. */ - private ArrayList mGeofenceList; + private PendingGeofenceTask mPendingGeofenceTask = PendingGeofenceTask.NONE; - /** Used when requesting to add or remove geofences. */ - private PendingIntent mGeofencePendingIntent; + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_activity); - // Buttons for kicking off the process of adding or removing geofences. - private Button mAddGeofencesButton; - private Button mRemoveGeofencesButton; + // Get the UI widgets. + mAddGeofencesButton = findViewById(R.id.add_geofences_button); + mRemoveGeofencesButton = findViewById(R.id.remove_geofences_button); - private PendingGeofenceTask mPendingGeofenceTask = PendingGeofenceTask.NONE; + // Empty list for storing geofences. + mGeofenceList = new ArrayList<>(); - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main_activity); + // Initially set the PendingIntent used in addGeofences() and removeGeofences() to null. + mGeofencePendingIntent = null; - // Get the UI widgets. - mAddGeofencesButton = findViewById(R.id.add_geofences_button); - mRemoveGeofencesButton = findViewById(R.id.remove_geofences_button); + setButtonsEnabledState(); - // Empty list for storing geofences. - mGeofenceList = new ArrayList<>(); + // Get the geofences used. Geofence data is hard coded in this sample. + populateGeofenceList(); - // Initially set the PendingIntent used in addGeofences() and removeGeofences() to null. - mGeofencePendingIntent = null; + mGeofencingClient = LocationServices.getGeofencingClient(this); + } + + @Override + public void onStart() { + super.onStart(); + + if (!checkPermissions()) { + requestPermissions(); + } else { + performPendingGeofenceTask(); + } + } - setButtonsEnabledState(); + /** + * 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(); - // Get the geofences used. Geofence data is hard coded in this sample. - populateGeofenceList(); + // The INITIAL_TRIGGER_ENTER flag indicates that geofencing service should trigger a + // GEOFENCE_TRANSITION_ENTER notification when the geofence is added and if the device + // is already inside that geofence. + builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER); - mGeofencingClient = LocationServices.getGeofencingClient(this); - } + // Add the geofences to be monitored by geofencing service. + builder.addGeofences(mGeofenceList); - @Override - public void onStart() { - super.onStart(); + // Return a GeofencingRequest. + return builder.build(); + } + + /** + * Adds geofences, which sets alerts to be notified when the device enters or exits one of the + * specified geofences. Handles the success or failure results returned by addGeofences(). + */ + public void addGeofencesButtonHandler(View view) { + if (!checkPermissions()) { + mPendingGeofenceTask = PendingGeofenceTask.ADD; + requestPermissions(); + return; + } + addGeofences(); + } - if (!checkPermissions()) { - requestPermissions(); - } else { - performPendingGeofenceTask(); + /** + * Adds geofences. This method should be called after the user has granted the location + * permission. + */ + @SuppressWarnings("MissingPermission") + private void addGeofences() { + if (!checkPermissions()) { + showSnackbar(getString(R.string.insufficient_permissions)); + return; + } + + mGeofencingClient + .addGeofences(getGeofencingRequest(), getGeofencePendingIntent()) + .addOnCompleteListener(this); } - } - - /** - * 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(); - - // The INITIAL_TRIGGER_ENTER flag indicates that geofencing service should trigger a - // GEOFENCE_TRANSITION_ENTER notification when the geofence is added and if the device - // is already inside that geofence. - builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER); - - // Add the geofences to be monitored by geofencing service. - builder.addGeofences(mGeofenceList); - - // Return a GeofencingRequest. - return builder.build(); - } - - /** - * Adds geofences, which sets alerts to be notified when the device enters or exits one of the - * specified geofences. Handles the success or failure results returned by addGeofences(). - */ - public void addGeofencesButtonHandler(View view) { - if (!checkPermissions()) { - mPendingGeofenceTask = PendingGeofenceTask.ADD; - requestPermissions(); - return; + + /** + * Removes geofences, which stops further notifications when the device enters or exits + * previously registered geofences. + */ + public void removeGeofencesButtonHandler(View view) { + if (!checkPermissions()) { + mPendingGeofenceTask = PendingGeofenceTask.REMOVE; + requestPermissions(); + return; + } + removeGeofences(); } - addGeofences(); - } - - /** - * Adds geofences. This method should be called after the user has granted the location - * permission. - */ - @SuppressWarnings("MissingPermission") - private void addGeofences() { - if (!checkPermissions()) { - showSnackbar(getString(R.string.insufficient_permissions)); - return; + + /** + * Removes geofences. This method should be called after the user has granted the location + * permission. + */ + @SuppressWarnings("MissingPermission") + private void removeGeofences() { + if (!checkPermissions()) { + showSnackbar(getString(R.string.insufficient_permissions)); + return; + } + + mGeofencingClient.removeGeofences(getGeofencePendingIntent()).addOnCompleteListener(this); } - mGeofencingClient - .addGeofences(getGeofencingRequest(), getGeofencePendingIntent()) - .addOnCompleteListener(this); - } - - /** - * Removes geofences, which stops further notifications when the device enters or exits previously - * registered geofences. - */ - public void removeGeofencesButtonHandler(View view) { - if (!checkPermissions()) { - mPendingGeofenceTask = PendingGeofenceTask.REMOVE; - requestPermissions(); - return; + /** + * 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 + public void onComplete(@NonNull Task task) { + mPendingGeofenceTask = PendingGeofenceTask.NONE; + if (task.isSuccessful()) { + updateGeofencesAdded(!getGeofencesAdded()); + setButtonsEnabledState(); + + 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, task.getException()); + } } - removeGeofences(); - } - - /** - * Removes geofences. This method should be called after the user has granted the location - * permission. - */ - @SuppressWarnings("MissingPermission") - private void removeGeofences() { - if (!checkPermissions()) { - showSnackbar(getString(R.string.insufficient_permissions)); - return; + + /** + * Gets a PendingIntent to send with the request to add or remove Geofences. Location Services + * issues the Intent inside this PendingIntent whenever a geofence transition occurs for the + * current list of geofences. + * + * @return A PendingIntent for the IntentService that handles geofence transitions. + */ + private PendingIntent getGeofencePendingIntent() { + // Reuse the PendingIntent if we already have it. + if (mGeofencePendingIntent != null) { + return mGeofencePendingIntent; + } + 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(). + 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); + } } - mGeofencingClient.removeGeofences(getGeofencePendingIntent()).addOnCompleteListener(this); - } - - /** - * 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 - public void onComplete(@NonNull Task task) { - mPendingGeofenceTask = PendingGeofenceTask.NONE; - if (task.isSuccessful()) { - updateGeofencesAdded(!getGeofencesAdded()); - setButtonsEnabledState(); - - 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, task.getException()); + /** + * This sample hard codes geofence data. A real app might dynamically create geofences based on + * the user's location. + */ + 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()); + } } - } - - /** - * Gets a PendingIntent to send with the request to add or remove Geofences. Location Services - * issues the Intent inside this PendingIntent whenever a geofence transition occurs for the - * current list of geofences. - * - * @return A PendingIntent for the IntentService that handles geofence transitions. - */ - private PendingIntent getGeofencePendingIntent() { - // Reuse the PendingIntent if we already have it. - if (mGeofencePendingIntent != null) { - return mGeofencePendingIntent; + + /** + * 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()) { + mAddGeofencesButton.setEnabled(false); + mRemoveGeofencesButton.setEnabled(true); + } else { + mAddGeofencesButton.setEnabled(true); + mRemoveGeofencesButton.setEnabled(false); + } } - 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(). - 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); + + /** + * Shows a {@link Snackbar} using {@code text}. + * + * @param text The Snackbar text. + */ + private void showSnackbar(final String text) { + View container = findViewById(android.R.id.content); + if (container != null) { + Snackbar.make(container, text, Snackbar.LENGTH_LONG).show(); + } } - } - - /** - * This sample hard codes geofence data. A real app might dynamically create geofences based on - * the user's location. - */ - 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()); + + /** + * 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. + */ + 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(); } - } - - /** - * 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()) { - mAddGeofencesButton.setEnabled(false); - mRemoveGeofencesButton.setEnabled(true); - } else { - mAddGeofencesButton.setEnabled(true); - mRemoveGeofencesButton.setEnabled(false); + + /** Returns true if geofences were added, otherwise false. */ + private boolean getGeofencesAdded() { + return PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean(Constants.GEOFENCES_ADDED_KEY, false); } - } - - /** - * Shows a {@link Snackbar} using {@code text}. - * - * @param text The Snackbar text. - */ - private void showSnackbar(final String text) { - View container = findViewById(android.R.id.content); - if (container != null) { - Snackbar.make(container, text, Snackbar.LENGTH_LONG).show(); + + /** + * Stores whether geofences were added ore removed in {@link SharedPreferences}; + * + * @param added Whether geofences were added or removed. + */ + private void updateGeofencesAdded(boolean added) { + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putBoolean(Constants.GEOFENCES_ADDED_KEY, added) + .apply(); } - } - - /** - * 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. - */ - 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(); - } - - /** Returns true if geofences were added, otherwise false. */ - private boolean getGeofencesAdded() { - return PreferenceManager.getDefaultSharedPreferences(this) - .getBoolean(Constants.GEOFENCES_ADDED_KEY, false); - } - - /** - * Stores whether geofences were added ore removed in {@link SharedPreferences}; - * - * @param added Whether geofences were added or removed. - */ - private void updateGeofencesAdded(boolean added) { - PreferenceManager.getDefaultSharedPreferences(this) - .edit() - .putBoolean(Constants.GEOFENCES_ADDED_KEY, added) - .apply(); - } - - /** Performs the geofencing task that was pending until location permission was granted. */ - private void performPendingGeofenceTask() { - if (mPendingGeofenceTask == PendingGeofenceTask.ADD) { - addGeofences(); - } else if (mPendingGeofenceTask == PendingGeofenceTask.REMOVE) { - removeGeofences(); + + /** Performs the geofencing task that was pending until location permission was granted. */ + private void performPendingGeofenceTask() { + if (mPendingGeofenceTask == PendingGeofenceTask.ADD) { + addGeofences(); + } else if (mPendingGeofenceTask == PendingGeofenceTask.REMOVE) { + removeGeofences(); + } } - } - - /** Return the current state of the permissions needed. */ - private boolean checkPermissions() { - int permissionState = - ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION); - if (permissionState != PackageManager.PERMISSION_GRANTED) { - return false; + + /** Return the current state of the permissions needed. */ + private boolean checkPermissions() { + 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; } - 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_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, - view -> { - // Request permission + + private void requestPermissions() { + boolean shouldProvideRationale = + 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, + 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, - 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, Manifest.permission.ACCESS_BACKGROUND_LOCATION - }, - REQUEST_PERMISSIONS_REQUEST_CODE); + 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. */ - @Override - 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) { - // If user interaction was interrupted, the permission request is cancelled and you - // receive empty arrays. - Log.i(TAG, "User interaction was cancelled."); - } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Log.i(TAG, "Permission granted."); - performPendingGeofenceTask(); - } else { - // Permission denied. - - // Notify the user via a SnackBar that they have rejected a core permission for the - // app, which makes the Activity useless. In a real app, core permissions would - // typically be best requested during a welcome-screen flow. - - // Additionally, it is important to remember that a permission might have been - // rejected without asking the user for permission (device policy or "Never ask - // 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, - 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; - } + + /** Callback received when a permissions request has been completed. */ + @Override + 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) { + // If user interaction was interrupted, the permission request is cancelled and you + // receive empty arrays. + Log.i(TAG, "User interaction was cancelled."); + } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Log.i(TAG, "Permission granted."); + performPendingGeofenceTask(); + } else { + // Permission denied. + + // Notify the user via a SnackBar that they have rejected a core permission for the + // app, which makes the Activity useless. In a real app, core permissions would + // typically be best requested during a welcome-screen flow. + + // Additionally, it is important to remember that a permission might have been + // rejected without asking the user for permission (device policy or "Never ask + // 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, + 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; + } + } } - } }