Turn on the Internet on Android when in sleep
Asked Answered
H

3

7

I have an Android app that needs to sync to the internet, but as soon as the phone goes to sleep I can't access the internet. It only happens when the user uses the "battery mode", when it turns off the data after 15 minutes. I wrote a test app and its turning the data on, but it still does connect to the server.

What I tried:

  • When I turn the data manually off, then the app is turning it on and it works
  • I also tried WakeLock, but it did not help.
  • The alarm works as expected, even when the phone goes to sleep for hours

Tested on Motorola Atrix Android 2.3.3. I can't rely on Wifi. In real life it will sync every week. How can we make it possible?

AlarmManager:

alarm_manager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, AlarmReceiver.class);
PendingIntent pending = PendingIntent.getBroadcast(this, 0, intent, 
                        PendingIntent.FLAG_UPDATE_CURRENT);
alarm_manager.setRepeating(AlarmManager.RTC_WAKEUP, 
                        System.currentTimeMillis(), 15000, pending);

AlarmReceiver:

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("MYTAG", "RECEIVED getMobileDataEnabled: " + getMobileDataEnabled(context));  
        if (!isOnline(context)) {
            Log.d("MYTAG", "NO INET");
            if (turnOnInet(context)) {
                Log.d("MYTAG", "INET IS ON");
            }
        }

        HttpClient httpclient = new DefaultHttpClient();
        HttpPost httppost = new HttpPost("http://xxx.xxx.xxx.xxx/ping/pong/moto/");
            try {
                List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
                nameValuePairs.add(new BasicNameValuePair("short_code", "ROFL"));
                httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
                httpclient.execute(httppost);
                Log.d("MYTAG", "POST FINISHED");
            }
            catch (Exception e) {
                Log.e("MYTAG", "MYTAG", e);
            }
    }

    public boolean isOnline(Context context) {
        ConnectivityManager cm = (ConnectivityManager)context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo netInfo = cm.getActiveNetworkInfo();
        if (netInfo != null){
            Log.d("MYTAG", "isAvailable: "+netInfo.isAvailable());
        }
        if (netInfo != null && netInfo.isConnectedOrConnecting()) {
            return true;
        }
        return false;
    }

    public boolean turnOnInet(Context context) {
        ConnectivityManager mgr = (ConnectivityManager)context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        if (mgr == null) {
            Log.d("MYTAG", "ConnectivityManager == NULL");
            return false;
        }
        try {
            Method setMobileDataEnabledMethod = mgr.getClass().getDeclaredMethod("setMobileDataEnabled", boolean.class);
            if (null == setMobileDataEnabledMethod) {
                Log.d("MYTAG", "setMobileDataEnabledMethod == null");
                return false;
            }    
            setMobileDataEnabledMethod.invoke(mgr, true);
        }
        catch(Exception e) {
            Log.e("MYTAG", "MYTAG", e);
            return false;
        }
        return true;
    }   


    private boolean getMobileDataEnabled(Context context) {
        ConnectivityManager mgr = (ConnectivityManager)context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        if (mgr == null) {
            Log.d("MYTAG", "getMobileDataEnabled ConnectivityManager == null");
            return false;
        }
        try {
            Method method = mgr.getClass().getMethod("getMobileDataEnabled");
            return (Boolean) method.invoke(mgr);
        } catch (Exception e) {
            Log.e("MYTAG", "MYTAG", e);
            return false;
        }
    }
}

AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
Hannon answered 12/8, 2012 at 23:23 Comment(6)
Please don't kill your users' battery.Harder
In "real life" it will sync every weekHannon
have you tried using RTC flag instead of RTC_WAKEUP? Since the wakeup is not forced, maybe framework will switch on the network. Since you are using sync once in a week you dont need WAKEUP flagUwton
When it won't wakeup when its in sleep. I would like to keep the "timing".Hannon
And if the user turns off the phone for two weeks (such as foreign vacation) your "timing" is broken.Creatine
https://mcmap.net/q/369762/-how-to-disable-mobile-data-on-androidEuell
K
16

First, you need to get that HttpPost code out of the BroadcastReceiver and into an IntentService. Never do network I/O on the main application thread, and onReceive() is called on the main application thread. For example, if you take too long, Android will terminate your code partway through your Internet operation.

Second, given the IntentService, you need to use a WakeLock. That may steer you to use my WakefulIntentService, which handles both problems. Or, use WakefulBroadcastReceiver, which has the same purpose.

Third, delete turnOnInet() and getMobileDataEnabled(). You do not need them, they are unreliable, and in particular turnOnInet() is user-hostile -- if the user wanted mobile data on, they would have turned it on.

Now, given all of that, in your onHandleIntent() of your IntentService() (or your doWakefulWork() of your WakefulIntentService), if you do not have an Internet connection right away, as a temporary workaround, SystemClock.sleep() for a second and try again, repeating a few times in a loop. If you find that you are getting Internet access after a bit, then you can consider getting more sophisticated (e.g., listening for connectivity change broadcasts rather than polling, though this would drive you away from WakefulIntentService and into a regular Service with your own background thread and a state machine for WakeLock management). Or, just stick with the sleep() -- it's unlikely to be the end of the world if you tie up this background thread for a few seconds. If you do not get connectivity after a modest amount of time, though, please do not keep trying indefinitely, as there are any number of reasons why you might not get a connection, including user-driven bandwidth management on Android 4.0+.

Katricekatrina answered 19/8, 2012 at 22:37 Comment(6)
I've been struggling for weeks now. Any ideas why using your WakefulIntentService properly still doesn't give my background service internet when woken from deep sleep despite trying multiple times to connect with sleeps? I am scheduling the service with the alarm manager, and I know I should be scheduling a broadcast that then actually starts the service, regardless I'm seeing the service fire on time, but the device just doesn't have internet (pinging google). I'm really at a loss.Pastorship
@CordRehn: Doze mode, perhaps. Or, if the device does not have mobile data, it takes time to establish a WiFi connection.Katricekatrina
Thanks for your time, I'll research Doze mode now. I've been generous giving the device 3 minutes to ping google successfully with 10 second timeouts and 3 second intervals. I don't think the solution is just "wait longer"?Pastorship
@CordRehn: Probably not. But with Doze mode and app standby, just because your app can run (occasionally) does not mean that you have Internet access at those points in time.Katricekatrina
Ah that makes sense, and researching doze mode more sounds increasingly like what's going on. My alarms are exact and non-repeating as they are time-sensitive. I think the only real solution here is to offer to whitelist my app to users, and warn about the downsides if they don't. How's that sound?Pastorship
@CordRehn: Agreed. Just be careful with the "offer to whitelist my app to users" part.Katricekatrina
P
5

I'd suggest to change a bit that approach, which is not bad at all, it's even nice to make sure that you are always synced, with the only problem that you are not giving the user the chance to decide, since if I, as an user, decide to turn off my data I just don't want anybody to turn it on. There could be several reasons for that, and any of them should be enough, but say that you get out of the country and you have no international data plan and by accident or default you have Data roaming activated. If I'd discover that some certain app turned my data on and spent a sensitive amount of money I'd be pretty pissed, and I'll be personal.

A more proper approach and straight solution as well would be to make a hard/full sync from time to time whenever the user opens your app or has access to some wifi connection (ConnectivityManager is your friend) based on some easy conditions (last time sync longer than a week, outdated saved data, inconsistencies, etc) and make a soft sync (update data in the background) in the rest of cases.

Moreover syncing periodically means wasting user data in case that user doesn't use the app. Ultimately, this turns your app into perfect candidate to be shut down by the system every once in a while.

Hope it helps. Keep us updated with your progress.

Related read: Optimizing downloads for efficient network access

Pickle answered 18/8, 2012 at 10:28 Comment(1)
Good point. Also, take a look at the articles here about writing optimized applications that use the network. There are pieces about grouping network transfers and handling multiple connections. developer.att.com/developer/forward.jsp?passedItemId=7200042Pap
E
1

Not an exact answer - but this approach would absolutely ruin the battery life. The whole point of sleep is to save battery power and this app would do nothing more than be an annoyance for customers no matter how useful the features are.

What I would suggest is that if it is absolutely necessary to connect to the internet while the app is not in use - set a trigger for when the phone wakes up. If it is not completely necessary it probably would be best to simply reconnect to the internet every time the app is opened.

Ember answered 20/8, 2012 at 23:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.