Android, getting strength signal (PhoneStateListener) while device in sleep mode
Asked Answered
D

4

5

I have problem and after some search I have not found any positive solutions. After research I have idea that there is not implementation for my problem but this question may be is my last chance.

What do I need to get?

There is application that gets information about mobile network strength signal. I do it by PhoneStateListener. Of course it works great but when my device goes to sleep mode, listener does not work:

https://code.google.com/p/android/issues/detail?id=10931 https://code.google.com/p/android/issues/detail?id=7592

WakeLock solves problem only in case, if device switch off by timeout. In case when I press hard power button, my device gets sleep mode as well. We can not override power button action.

My goal is get strength signal always when my device is enabled. It does not matter what mode is. All time it should collecting data.

Question:

Are there any ideas? How to achieve that? Are there ways to do this or may be there are some hacks? All solves are welcome. If you had some useful experience, please share this.

Thanks to all for help!!! I hope, this topic will get complete information about this problem.

Dramatic answered 5/7, 2013 at 7:21 Comment(3)
"In case when I press hard power button, my device gets sleep mode as well. We can not override power button action." -- a WakeLock is not affected by the power button.Waxbill
Why not use a service ?Sarcoma
it does not work in sleep mode as well.Dramatic
U
9

Alarm manager is the way to go - the tricky part is to keep the phone awake after the alarm manager receiver returns. So

  • setup an alarm (notice you should also register an "On Boot completed" receiver to set up the alarm after a reboot - your alarms do not survive a reboot) :

    Intent monitoringIntent = new Intent(context, YourReceiver.class);
    monitoringIntent.setAction("your action");
    PendingIntent pi = PendingIntent.getBroadcast(context, NOT_USED,
                             monitoringIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    AlarmManager am = (AlarmManager) 
                       context.getSystemService(Context.ALARM_SERVICE);
    // here is the alarm set up
    am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    SystemClock.elapsedRealtime() + INITIAL_DELAY,
                    INTERVAL_BETWEEN_ALARMS, pi);
    
  • receive it - the receiver holds a WakeLock in its onReceive() which never fails :

    public abstract class YourReceiver extends BroadcastReceiver {
    
        @Override
        final public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if ("your action".equals(action)) {
                // monitoring - got broadcast from ALARM
                try {
                        d("SS : " + new Signal().getSignalStrength(context));
                } catch (InterruptedException e) {
                        e.printStackTrace();
                }
                // Actu8ally the lines above will ANR
                // I did it with WakefulIntentService :
                // WakefulIntentService.sendWakefulWork(
                // context, YourWakefulService.class);
                // Will be posting it asap
            } else {
                w("Received bogus intent : " + intent);
                return;
            }
        }
    }
    

    If you are lucky (yourRetrieveSignal() is fast enough) this will work, otherwise you will need a (Wakeful)IntentService pattern in your receiver.
    The WakefulIntentService will take care of the wake lock (if you want to avoid a dependency have a look here) - EDIT : keep in mind you can't define listeners in an intent service - see here.

If the receiver ANRs on you, you have to try the WakefulIntentService pattern. In either case you might use this :

This proved the most difficult part actually :

class Signal {

    static volatile CountDownLatch latch; //volatile is an overkill quite probably
    static int asu;
    private final static String TAG = Signal.class.getName();

    int getSignalStrength(Context ctx) throws InterruptedException {
        Intent i = new Intent(TAG + ".SIGNAL_ACTION", Uri.EMPTY, ctx,
                SignalListenerService.class);
        latch = new CountDownLatch(1);
        asu = -1;
        ctx.startService(i);
        Log.d(TAG, "I wait");
        latch.await();
        ctx.stopService(i);
        return asu;
    }
}

where :

public class SignalListenerService extends Service {

    private TelephonyManager Tel;
    private SignalListener listener;
    private final static String TAG = SignalListenerService.class.getName();

    private static class SignalListener extends PhoneStateListener {

        private volatile CountDownLatch latch;

        private SignalListener(CountDownLatch la) {
            Log.w(this.getClass().getName(), "CSTOR");
            this.latch = la;
        }

        @Override
        public void onSignalStrengthChanged(int asu) {
            Signal.asu = asu;
            latch.countDown();
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.w(TAG, "Received : " + intent.getAction());
        Tel = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        listener = new SignalListener(Signal.latch);
        @SuppressWarnings("deprecation")
        final int listenSs = PhoneStateListener.LISTEN_SIGNAL_STRENGTH;
        Tel.listen(listener, listenSs);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Log.w(TAG, "onDestroy");
        Tel.listen(listener, PhoneStateListener.LISTEN_NONE);
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

This is working code (but not the pinnacle of elegance admittedly - comments/corrections welcome). Do not forget to register your services in the manifest and acquire permissions.
EDIT 2013.07.23 : I did not use the onReceive - if you use it it will ANR - this is working code if you use a WakefulIntentService in onReceive and in there you call SignalListenerService.

Unsavory answered 8/7, 2013 at 14:9 Comment(1)
If you're only supporting Android 2.1 and up, you'll need to use PhoneStateListener.LISTEN_SIGNAL_STRENGTHS instead of PhoneStateListener.LISTEN_SIGNAL_STRENGTH.Budbudapest
S
1

From my understanding of PhoneStateListener you can't do this while the application CPU is in sleep mode. You can either keep the device awake, which would ruin battery life. Alternatively you can use an alarm (see AlarmManager) to wake the device on intervals, so you can collect the data (impacts battery life still).

Some samples of using AlarmManager can be found here

Shaylynn answered 5/7, 2013 at 8:19 Comment(3)
How can I keep the device awake? After press hardware power button my device gets sleep mode in anyway. Or may be I can achieve that somehow? The main problem is case with power button.Dramatic
Then it sounds like you want to use AlarmManager. :) The AlarmManger will allow you to wake the CPU and execute a piece of code every X minutes/seconds.Shaylynn
You would still impact the battery juice if you use AlarmManager. I guess if you were to implement this in your app, you'll need to explain to the users that their battery might drain a little.Budbudapest
T
1

CommonsWare's location polling example is really good about waking the phone and putting it to sleep again. I think it might help have a look: https://github.com/commonsguy/cwac-locpoll

Trueblue answered 8/7, 2013 at 20:6 Comment(0)
L
0

One of the possible workarounds of android issue 10931 is to send the android.intent.action.SCREEN_ON intent to the 'phone' process after the screen turned off.

  1. Create and register BroadcastReceiver to listen for notifications when the screen turns off

    start(Context context) {
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        context.registerReceiver(mScreenReceiver, filter);
    }
    
    final BroadcastReceiver mScreenReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(final Context context, final Intent intent) {
            if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
                Log.v(LOGTAG, "Screen is off. Running workaround");
                new Thread(mReportScreenIsOnRunnable).start();
            }
        }
    };
    
  2. Send the SCREEN_ON intent to the phone process only.

    public final Runnable mReportScreenIsOnRunnable = new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                Runtime.getRuntime().exec(new String[] { "su", "-c",
                        "am broadcast -a android.intent.action.SCREEN_ON com.android.phone" });
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };
    

After receiving this intent the phone process would resume sending cell location updates.

Root privileges are required.

This solution is a bit hacky, dangerous and works not on all phones. It can lead to higher power consumption, but not so much more than if you keep the screen turned on.

Lehrer answered 23/11, 2014 at 15:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.