TrevorWiley
TrevorWiley

Reputation: 868

Is it possible to track when an app enters "Doze on the Go" (AKA Doze Light, Doze Extended, and Doze 2)?

In Android "N", Doze has been extended with "Doze on the Go".

I'm looking for a way to detect when the device enters and leaves these new light doze IDLE and IDLE_MAINTENANCE states. (Basically the same question that was asked for regular Doze here.)

Upvotes: 2

Views: 2354

Answers (2)

ksparrow
ksparrow

Reputation: 195

The implementation in TrevorWiley's answer works, but can be simplified a bit. Yes, Nougat's PowerManager has isLightDeviceIdleMode() and it is annotated with @hide. We can use reflection to invoke it, which is more succinct and independent of PowerManager's internal implementation details.

public static boolean isLightDeviceIdleMode(final Context context) {
    boolean result = false;
    PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    if (pm != null) {
        try {
            Method isLightDeviceIdleModeMethod = pm.getClass().getDeclaredMethod("isLightDeviceIdleMode");
            result = (boolean)isLightDeviceIdleModeMethod.invoke(pm);
        } catch (IllegalAccessException | InvocationTargetException  | NoSuchMethodException e) {
            Log.e(TAG, "Reflection failed for isLightDeviceIdleMode: " + e.toString(), e);
        }
    }
    return result;
}

Agreed mostly with TrevorWiley's use of the String to register for Broadcasts. Same as the above method, you could use reflection to grab the value of field ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED and fall back to the hard-coded String "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED".

Upvotes: 4

TrevorWiley
TrevorWiley

Reputation: 868

The online documentation for PowerManager doesn't mention it, but the latest source code (API 24 revision 1) has what looks like should be the solution to this question:

String ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED
        = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED"
boolean isLightDeviceIdleMode()

In theory, you could simply register some code as a receiver for the intent and check the current value of the function. Some poking around with dumpsys activity broadcasts shows that the intent is indeed sent when the light doze state changes.

However, the latest SDK Platform (API 24 revision 2) doesn't have these symbols in it - I get compile errors (and some poking with javap and jar shows that they really aren't present). Reaching out to Google, we're told that this is the intended design.

There is a work around, which is to hard code the same string mentioned above and then use reflection to call the same function that would have been called in the API. Like this:

/**
 * Check if the device is currently in the Light IDLE mode.
 *
 * @param context The application context.
 * @return True if the device is in the Light IDLE mode.
 */
public static boolean isLightDeviceIdleMode(final Context context) {
    boolean result = false;
    PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    if (pm != null) {
        // result = pm.isLightDeviceIdleMode();
        try {
            Log.d(TAG, "Trying reflection for isLightDeviceIdleMode");
            Field pmServiceField = pm.getClass().getDeclaredField("mService");
            pmServiceField.setAccessible(true);
            Object pmService = pmServiceField.get(pm);

            Method isLightDeviceIdleMode = pmService.getClass().getDeclaredMethod("isLightDeviceIdleMode");
            isLightDeviceIdleMode.setAccessible(true);
            result = (Boolean) isLightDeviceIdleMode.invoke(pmService);
        } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            Log.e(TAG, "Reflection failed for isLightDeviceIdleMode: " + e.toString());
        } catch (RemoteException re) {
            Log.e(TAG, "Remote exception checking isLightDeviceIdleMode: " + e.toString());
        }
    }
    return result;
}

Upvotes: 5

Related Questions