Ender87
Ender87

Reputation: 181

How to? Listen for Location Setting being turned ON (Android App)

So I've spent the past few weeks working on my Android App and looking into the best way of implementing what I need to do, but still can't quite get it right.. Any/all help is greatly appreciated, as I am still getting the hang of things..

Task:

(Assuming the "Location"/GPS Setting is currently off), I need to have my App constantly be listening for the "Location" Setting to be turned ON.. At this point, simply start an Activity.

Ideas:

These are all the different ways I think it could possibly work:

Questions:

Any kind of advice or answers for any of the following questions are very much appreciated..

I truly appreciate any advice or help that you can provide me with.. I'm still getting the hang of all this, but confident enough to do it, and eager to publish my first App.. So thank you, it means a lot and will greatly help me along.


EDIT:

OK So here's what I've got so far...
Heres my Receiver:

MyReceiver.Java

public class MyReceiver extends BroadcastReceiver {

    private final static String TAG = "LocationProviderChanged";

    boolean isGpsEnabled;
    boolean isNetworkEnabled;



    public MyReceiver() {
    // EMPTY

    // MyReceiver Close Bracket
    }



    // START OF onReceive
    @Override
    public void onReceive(Context context, Intent intent) {


        // PRIMARY RECEIVER
        if (intent.getAction().matches("android.location.PROVIDERS_CHANGED")) {

            Log.i(TAG, "Location Providers Changed");

            LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
            isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
            isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

            Toast.makeText(context, "GPS Enabled: " + isGpsEnabled + " Network Location Enabled: " + isNetworkEnabled, Toast.LENGTH_LONG).show();

            // START DIALOG ACTIVITY
            if (isGpsEnabled || isNetworkEnabled) {
                Intent runDialogActivity = new Intent(context, DialogActivity.class);
                context.startActivity(runDialogActivity);

            }

        }



        // BOOT COMPLETED (REPLICA OF PRIMARY RECEIVER CODE FOR WHEN BOOT_COMPLETED)
        if (intent.getAction().matches("android.intent.action.BOOT_COMPLETED")) {

            Log.i(TAG, "Location Providers Changed Boot");

            LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
            isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
            isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

            Toast.makeText(context, "GPS Enabled Boot: " + isGpsEnabled + " Network Location Enabled Boot: " + isNetworkEnabled, Toast.LENGTH_LONG).show();

            // START DIALOG ACTIVITY
            if (isGpsEnabled || isNetworkEnabled) {
                Intent runDialogActivity = new Intent(context, DialogActivity.class);
                context.startActivity(runDialogActivity);

            }

        }



    // onReceive CLOSE BRACKET
    }



// CLOSE OF FULL CLASS
}

And heres what the Manifest looks like:

Manifest.XML

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ender.projects.receivertest">

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        android:allowBackup="true"
        android:fullBackupContent="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".DialogActivity">
        </activity>

        <receiver
            android:name=".MyReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.location.PROVIDERS_CHANGED" />

                <action android:name="android.intent.action.BOOT_COMPLETED" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </receiver>

    </application>

</manifest>

Aside from these files, I have the following files:
MainActivity.Java and DialogActivity.Java, both with Layout files,
activity_main.xml and activity_dialog.xml to match with them.

So if I understand correctly, when the user downloads the App and opens it, my MainActivity.Java and corresponding layout will launch. But I am only going to use this simply as a preferences screen. So once they open the App for the first time, the Broadcast Receiver should automatically start listening for the LOCATION setting to be turned on, correct? I also want the Broadcast Listener to remain listening, even after it receives the initial Broadcast (so that my onReceive still fires if they turn the LOCATION setting off, and then later they turn it ON again..

So (A) How does my code look thus far?
and (B) To accomplish what I just described, what would need to be added?
and (C) Running it throws this error when I turn the LOCATION setting ON.
..How can I fix it?:
java.lang.RuntimeException: Unable to start receiver com.bryce.projects.servicesthreadsetc.MyReceiver: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?


Thanks again for all the help!

Upvotes: 14

Views: 16725

Answers (5)

user2288580
user2288580

Reputation: 2258

If you're using LocationManagerCompat you can do the following to detect when location is disabled

//Top of block location parameters    
val locationRequestCompat = LocationRequestCompat.Builder(5000L)
    .setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY)
    .setMinUpdateDistanceMeters(0f)
    .setMinUpdateIntervalMillis(5000L)
    .setMaxUpdateDelayMillis(5000L)
    .build()

//inner class TestThread: Thread() {
val exec = Executor { runnable ->
    Thread(runnable).start()
}

val locationListenerCompat = object : LocationListenerCompat {
    override fun onFlushComplete(requestCode: Int) {}
    override fun onLocationChanged(locations: List<Location>) {
        //Do your thing here on certain API levels
    }
    override fun onLocationChanged(location: Location) {
        //Do your thing here on certain API levels
    }
    override fun onProviderDisabled(provider: String) {
        if(mainActivity!= null) {
            mainActivity?.runOnUiThread {
                Toast.makeText(mainActivity, "$provider disabled", Toast.LENGTH_SHORT).show()
            }
        }
    }
    override fun onProviderEnabled(provider: String) {
        if(mainActivity!= null) {
            mainActivity?.runOnUiThread {
                Toast.makeText(mainActivity, "$provider enabled", Toast.LENGTH_SHORT).show()
            }
        }
    }
}
//Bottom of block location parameters


LocationManagerCompat.requestLocationUpdates(app.locationManager, "gps", locationRequestCompat, exec, locationListenerCompat)

To test use:

To enable GPS: adb shell settings put secure location_mode 3

To disable GPS: adb shell settings put secure location_mode 3

This also works on the FusedLocationProviderClient setup.

Depending on your usage case you may want to call the following in onProviderDisabled

LocationManagerCompat.removeUpdates(app.locationManager, locationListenerCompat)

Upvotes: 0

argenkiwi
argenkiwi

Reputation: 2427

Here is an extension property that produces a flow from a Broadcast receiver:

val Context.locationEnabledFlow
    get() = callbackFlow {
        val receiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                trySend(LocationManagerCompat.isLocationEnabled(locationManager))
            }
        }

        registerReceiver(receiver, IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION))
        awaitClose { unregisterReceiver(receiver) }
    }

For example, this is how you could use it with Jetpack Compose:

val isLocationEnabled by LocalContext.current.locationEnabledFlow
    .collectAsStateWithLifecycle(initialValue = false)

Text(text = when(isLocationEnabled){
    true -> "Location enabled."
    false -> "Location disabled."
})

Upvotes: 1

Daniel Nugent
Daniel Nugent

Reputation: 43322

Since you don't need to actually get a Location, the best implementation for your needs would be a BroadcastReceiver.

This is the best option because you wouldn't need to have a Service running at all times (resulting in extra batter drain), and you would be able to start your Activity from the BroadcastReceiver.

With the intent filter and BroadcastReceiver, your app will be started by the OS whenever the Location setting has changed (enabled or disabled), and in the case that it is enabled, you can start your Activity from the BroadcastReceiver.

First add the intent filter, which will be captured when the OS sends out the implicit Intent that the setting has changed.

<receiver
    android:name=".LocationProviderChangedReceiver"
    android:exported="false" >
    <intent-filter>
        <action android:name="android.location.PROVIDERS_CHANGED" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</receiver>

Note that for this to work on Android Oreo and above, you'll need to register the broadcast receiver at runtime, see here: https://developer.android.com/guide/components/broadcasts#context-registered-receivers

Then, in LocationProviderChangedReceiver.java, your implementation would be something like this:

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.location.LocationManager;
import android.util.Log;
import android.widget.Toast;

public class LocationProviderChangedReceiver extends BroadcastReceiver {
    private final static String TAG = "LocationProviderChanged";

    boolean isGpsEnabled;
    boolean isNetworkEnabled;

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().matches("android.location.PROVIDERS_CHANGED"))
        {
            Log.i(TAG, "Location Providers changed");

            LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
            isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
            isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

            //Start your Activity if location was enabled:
            if (isGpsEnabled || isNetworkEnabled) {
                  Intent i = new Intent(context, YourActivity.class);
                  context.startActivity(i);
            }
        }
    }
}

Edit

Updated solution with Kotlin and registering receiver at runtime for Android 9.

The BroadcastReceiver class in Kotlin:

class LocationProviderChangedReceiver : BroadcastReceiver() {

    internal var isGpsEnabled: Boolean = false
    internal var isNetworkEnabled: Boolean = false

    override fun onReceive(context: Context, intent: Intent) {
        intent.action?.let { act ->
            if (act.matches("android.location.PROVIDERS_CHANGED".toRegex())) {
                val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
                isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
                isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)

                Log.i(TAG, "Location Providers changed, is GPS Enabled: " + isGpsEnabled)

                //Start your Activity if location was enabled:
                if (isGpsEnabled || isNetworkEnabled) {
                    val i = Intent(context, YourActivity::class.java)
                    context.startActivity(i)
                }
            }
        }
    }

    companion object {
        private val TAG = "LocationProviderChanged"
    }
}

Register at runtime, for example in onCreate() of your app's MainActivity:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val br: BroadcastReceiver = LocationProviderChangedReceiver()
    val filter = IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
    registerReceiver(br, filter)
}

Upvotes: 28

Mohanakrrishna
Mohanakrrishna

Reputation: 168

LocationListeners's onProvideEnabled() method will work instantly. When you call this remember if you are doing LocationManager.removeUpdates() in between onProviderEnabled() won't work.

And this is pretty much simple when you are reading locations from Location API's of Android. You don't need the broadcast receivers listening and communication further

But when you are using FusedLocationProviderClient from google API and if you use LocationListener's onProviderEnabled and onProviderDisabled methods you will be having two services running in parallel, checking for the provider changes. So using BroadcastReceiver is better

Upvotes: 1

matdev
matdev

Reputation: 4283

Here is a solution using dynamic registration:

In your fragment or activity's onResume() method, listen to changes in LocationManager.PROVIDERS_CHANGED_ACTION

IntentFilter filter = new IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION);
filter.addAction(Intent.ACTION_PROVIDER_CHANGED);
mActivity.registerReceiver(gpsSwitchStateReceiver, filter);

Here is a code sample for the gpsSwitchStateReceiver object:

private BroadcastReceiver gpsSwitchStateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {


            if (LocationManager.PROVIDERS_CHANGED_ACTION.equals(intent.getAction())) {
                // Make an action or refresh an already managed state.

                LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
                boolean isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
                boolean isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

                if (isGpsEnabled || isNetworkEnabled) {
                    Log.i(this.getClass().getName(), "gpsSwitchStateReceiver.onReceive() location is enabled : isGpsEnabled = " + isGpsEnabled + " isNetworkEnabled = " + isNetworkEnabled);
                } else {
                    Log.w(this.getClass().getName(), "gpsSwitchStateReceiver.onReceive() location disabled ");
                }
            }
        }
    };

In your onPause() method, unregister the receiver

mActivity.unregisterReceiver(gpsSwitchStateReceiver);

Upvotes: 4

Related Questions