title: How to notify the calling activity from a BroadcastReceiver when using Geofencing
body: <p>I have defined a BroacastReceiver according to the Geofencing docs in order to receive ENTRY &amp; EXIT updates when the user interacts with a geofence. My issue is that the app is going to be used on the road so, when a user drives and enters a geofence I'm getting notified about it and the same happens when he exits it. However, when the exit event is received I need to remove the triggered geofence(s) from both the Client &amp; the Google Maps map. Both of those exist in my MapsActivity (which is where I set up the receiver &amp; event notification process based on the documentation) so I'd like to call the <code>removeGeofences(...)</code> method of my activity from the receiver. I looked at a TON of posts regarding this matter but none seems to cover the Geofencing use case. I've tried declaring the receiver dynamically through code instead of statically through the manifest but in that case, I'd need intent filters which I can't find for Geofencing. Any ideas on how to achieve this?</p>
  <p>BroadcastReceiver:</p>
  <pre><code>class GeofenceReceiver : BroadcastReceiver() {
      private val TAG = GeofenceReceiver::class.java.simpleName
  
      override fun onReceive(context: Context?, intent: Intent?) {
          val geofencingEvent = GeofencingEvent.fromIntent(intent)
          if (geofencingEvent.hasError()) {
              val errorMessage = GeofenceStatusCodes
                  .getStatusCodeString(geofencingEvent.errorCode)
              Log.e(TAG, &quot;error: $errorMessage&quot;)
              return
          }
  
          // Get the transition type.
          val geofenceTransition = geofencingEvent.geofenceTransition
  
          // Test that the reported transition was of interest.
          if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
              geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT
          ) {
  
              // Get the geofences that were triggered. A single event can trigger
              // multiple geofences.
              val triggeringGeofences = geofencingEvent.triggeringGeofences
  
              val ids = arrayListOf&lt;String&gt;()
              for (geofence in triggeringGeofences) {
                  Log.d(TAG, &quot;Geofence ${geofence.requestId} triggered!&quot;)
                  ids.add(geofence.requestId)
              }
  
              if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER)
                  Log.d(TAG, &quot;User entered geofence!&quot;)
              else {
                  Log.d(TAG, &quot;User exited geofence!&quot;)
                  //activity.removeGeofences(ids)
              }
          } else {
              // Log the error.
              Log.e(TAG, &quot;Invalid transition&quot;)
          }
      }
  }
  </code></pre>
  <p>MapsActivity:</p>
  <pre><code>class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
  
      private var mMap: GoogleMap? = null
      private var geofenceClient: GeofencingClient? = null
      private var geofenceList: ArrayList&lt;Geofence&gt; = arrayListOf()
      private var geofenceMapMarks: MutableMap&lt;String, Pair&lt;Marker, Circle&gt;&gt; = mutableMapOf()
      private var currLocationMarker: Marker? = null
  
      private val geofencePendingIntent: PendingIntent by lazy {
          val intent = Intent(this, GeofenceReceiver::class.java)
          // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
          // addGeofences() and removeGeofences()
          PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
      }
  
      private val mLocationListener: LocationListener = LocationListener {
          currLocationMarker?.remove()
          currLocationMarker = mMap?.addMarker(
              MarkerOptions().position(LatLng(it.latitude, it.longitude)).title(&quot;Current Location&quot;)
                  .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE))
          )
          animateCameraToLocation(currLocationMarker?.position!!)
      }
  
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_maps)
  
          geofenceClient = LocationServices.getGeofencingClient(this)
          // Obtain the SupportMapFragment and get notified when the map is ready to be used.
          val mapFragment = supportFragmentManager
              .findFragmentById(R.id.map) as SupportMapFragment
          mapFragment.getMapAsync(this)
  
          val mLocationManager = getSystemService(LOCATION_SERVICE) as LocationManager
  
          if (!shouldRequestPermissions()) subscribeToLiveCurrentLocation(mLocationManager)
      }
  
      @SuppressLint(&quot;MissingPermission&quot;)
      private fun subscribeToLiveCurrentLocation(mLocationManager: LocationManager) {
          mLocationManager.requestLocationUpdates(
              LocationManager.GPS_PROVIDER, 1000,
              50F, mLocationListener
          )
      }
  
      private fun requestPermissionsIfNeeded() {
          if (!shouldRequestPermissions()) return
  
          if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.Q)
              ActivityCompat.requestPermissions(
                  this,
                  arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                  2
              )
          else ActivityCompat.requestPermissions(
              this,
              arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
              1
          )
      }
  
      @SuppressLint(&quot;InlinedApi&quot;)
      private fun shouldRequestPermissions(): Boolean = ContextCompat.checkSelfPermission(
          this,
          Manifest.permission.ACCESS_FINE_LOCATION
      ) != PackageManager.PERMISSION_GRANTED
              &amp;&amp; ContextCompat.checkSelfPermission(
          this,
          Manifest.permission.ACCESS_BACKGROUND_LOCATION
      ) != PackageManager.PERMISSION_GRANTED
  
      /**
       * Manipulates the map once available.
       * This callback is triggered when the map is ready to be used.
       * This is where we can add markers or lines, add listeners or move the camera. In this case,
       * we just add a marker near Sydney, Australia.
       * If Google Play services is not installed on the device, the user will be prompted to install
       * it inside the SupportMapFragment. This method will only be triggered once the user has
       * installed Google Play services and returned to the app.
       */
      override fun onMapReady(googleMap: GoogleMap) {
          mMap = googleMap
  
          mMap?.setOnMapClickListener { latlong -&gt;
              drawGeofenceOnMap(latlong)
              addGeofenceToList(latlong)
          }
      }
  
      private fun drawGeofenceOnMap(latlong: LatLng) {
          val marker = mMap?.addMarker(MarkerOptions().position(latlong).title(&quot;Geofence&quot;))
          val circle = mMap?.addCircle(
              CircleOptions().center(latlong).radius(defaultGeofenceRadius.toDouble()).strokeColor(
                  resources.getColor(android.R.color.holo_red_light, null)
              ).fillColor(
                  resources.getColor(android.R.color.transparent, null)
              )
          )
          mMap?.moveCamera(CameraUpdateFactory.newLatLng(latlong))
          geofenceMapMarks[&quot;Geofence #&quot; + (geofenceList.size + 1)] = Pair(marker!!, circle!!)
      }
  
      private fun animateCameraToLocation(latlong: LatLng) {
          val cameraPosition = CameraPosition.Builder()
              .target(latlong)
              .zoom(17f)
              .build()
  
          mMap?.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
      }
  
      private fun addGeofenceToList(latlong: LatLng) {
          geofenceList.add(
              Geofence.Builder()
                  // Set the request ID of the geofence. This is a string to identify this
                  // geofence.
                  .setRequestId(&quot;Geofence #&quot; + (geofenceList.size + 1))
  
                  // Set the circular region of this geofence.
                  .setCircularRegion(
                      latlong.latitude,
                      latlong.longitude,
                      defaultGeofenceRadius
                  )
  
                  // Set the expiration duration of the geofence. This geofence gets automatically
                  // removed after this period of time.
                  .setExpirationDuration(Geofence.NEVER_EXPIRE)
  
                  // 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 or Geofence.GEOFENCE_TRANSITION_EXIT)
  
                  // Create the geofence.
                  .build()
          )
      }
  
      private fun getGeofencingRequest(): GeofencingRequest {
          return GeofencingRequest.Builder().apply {
              setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
              addGeofences(geofenceList)
          }.build()
      }
  
      private fun addGeofences() {
          if (ActivityCompat.checkSelfPermission(
                  this,
                  Manifest.permission.ACCESS_FINE_LOCATION
              ) != PackageManager.PERMISSION_GRANTED
          ) {
              Toast.makeText(this, &quot;Need to grant permissions to continue&quot;, Toast.LENGTH_LONG).show()
              requestPermissionsIfNeeded()
              return
          }
          geofenceClient?.addGeofences(getGeofencingRequest(), geofencePendingIntent)?.run {
              addOnSuccessListener {
                  Toast.makeText(this@MapsActivity, &quot;Geofences added!&quot;, Toast.LENGTH_LONG).show()
              }
              addOnFailureListener {
                  Toast.makeText(this@MapsActivity, &quot;Failed to add geofences!&quot;, Toast.LENGTH_LONG)
                      .show()
                  Log.d(&quot;MAPSACTIVITY&quot;, it.message.toString())
              }
          }
      }
  
      fun removeGeofences(ids: ArrayList&lt;String&gt;) {
          removeGeofencesFromClient(ids)
          removeGeofencesFromMap(ids)
      }
  
      private fun removeGeofencesFromMap(ids: ArrayList&lt;String&gt;) {
          for (id in ids) {
              if (geofenceMapMarks.keys.contains(id)) geofenceMapMarks.remove(id)
          }
      }
  
      private fun removeGeofencesFromClient(ids: ArrayList&lt;String&gt;) {
          for (fence in geofenceList) {
              if (ids.contains(fence.requestId)) geofenceList.remove(fence)
          }
  
          geofenceClient?.removeGeofences(ids)
              ?.addOnCompleteListener { task -&gt;
                  if (task.isSuccessful) {
                      Toast.makeText(
                          this,
                          &quot;Geofences have been removed!&quot;,
                          Toast.LENGTH_LONG
                      ).show()
                  } else {
                      Toast.makeText(
                          this,
                          &quot;Failed to remove geofences&quot;,
                          Toast.LENGTH_LONG
                      ).show()
                      Log.d(&quot;MAPSACTIVITY&quot;, &quot;error ==&gt; &quot; + task.exception)
                  }
              }
      }
  
      override fun onDestroy() {
          super.onDestroy()
          mMap = null
          geofenceClient = null
      }
  
      @SuppressLint(&quot;InlinedApi&quot;)
      override fun onRequestPermissionsResult(
          requestCode: Int,
          permissions: Array&lt;out String&gt;,
          grantResults: IntArray
      ) {
          var grantFailed = false
          when (requestCode) {
              1 -&gt; {
                  if (grantResults.isNotEmpty() &amp;&amp; grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                      Toast.makeText(this, &quot;GRANTED&quot;, Toast.LENGTH_SHORT).show()
                  } else grantFailed = true
              }
              2 -&gt; {
                  if (grantResults.isNotEmpty() &amp;&amp; grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                      Toast.makeText(this, &quot;GRANTED first&quot;, Toast.LENGTH_SHORT).show()
                      ActivityCompat.requestPermissions(
                          this,
                          arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
                          3
                      )
                  } else grantFailed = true
              }
              3 -&gt; {
                  if (grantResults.isNotEmpty() &amp;&amp; grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                      Toast.makeText(this, &quot;GRANTED second&quot;, Toast.LENGTH_SHORT).show()
                  } else grantFailed = true
              }
          }
          if (grantFailed) {
              if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) ||
                  shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
              ) {
                  Toast.makeText(this, &quot;Show permission rationale&quot;, Toast.LENGTH_LONG).show()
              } else Toast.makeText(
                  this,
                  &quot;You must grant the requested permissions to continue&quot;,
                  Toast.LENGTH_SHORT
              ).show()
          }
      }
  
      override fun onOptionsItemSelected(item: MenuItem): Boolean {
          return when (item.itemId) {
              R.id.addGeofence -&gt; {
                  addGeofences()
                  true
              }
              else -&gt; super.onOptionsItemSelected(item)
          }
      }
  
      override fun onCreateOptionsMenu(menu: Menu?): Boolean {
          val inflater = menuInflater
          inflater.inflate(R.menu.options_menu, menu)
          return true
      }
  }
  </code></pre>
  <p>Manifest:</p>
  <pre><code>&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
      xmlns:tools=&quot;http://schemas.android.com/tools&quot;
      package=&quot;com.spap.geofencepoc&quot;&gt;
  
      &lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot; /&gt;
      &lt;uses-permission android:name=&quot;android.permission.ACCESS_BACKGROUND_LOCATION&quot; /&gt;
  
      &lt;application
          android:icon=&quot;@mipmap/ic_launcher&quot;
          android:label=&quot;@string/app_name&quot;
          android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
          android:supportsRtl=&quot;true&quot;
          android:theme=&quot;@style/Theme.GeoFencePoC&quot;
          tools:ignore=&quot;AllowBackup&quot;&gt;
  
          &lt;receiver android:name=&quot;.receivers.GeofenceReceiver&quot;/&gt;
  
          &lt;meta-data
              android:name=&quot;com.google.android.geo.API_KEY&quot;
              android:value=&quot;@string/google_maps_key&quot; /&gt;
  
          &lt;activity
              android:name=&quot;.presentation.MapsActivity&quot;
              android:label=&quot;@string/title_activity_maps&quot;&gt;
              &lt;intent-filter&gt;
                  &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;
  
                  &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
              &lt;/intent-filter&gt;
          &lt;/activity&gt;
      &lt;/application&gt;
  
  &lt;/manifest&gt;
  </code></pre>