androidreverse-engineeringapktoolandroid-doze

How to fix apps that miss alarms in doze mode?


Some apps, especially those no longer maintained, severely misbehave when an android device with Marshmallow enters doze mode. Apps designed as alarm clock replacements or schedulers to perform certain tasks in the future no longer work reliably, completely defeating their purpose.

Workarounds are well known for developers, or when the affected apps are open-source, e.g. answered here and documented here.

But how can closed-source apps, whose authors no longer maintain them, be fixed?

Note that disabling battery optimization for an app will not help with doze mode as answered here.


Solution

  • This answer applies to apps that suffer from late wake-ups due to using AlarmManager's set and setExact methods. These methods set alarms that are only delivered during doze "maintenance" windows or when the device wakes up due to user intervention or other circumstances.

    The solution is to use setAndAllowWhileIdle and setExactAndAllowWhileIdle methods, respectively; but being able to modify and recompile apks is the trick.

    1. Get apktool: click on the "Current Release" link to download the jar, apktool_2.1.1.jar as of this writing.
    2. Get the apk of the app you wish to fix. I have used apk20.com to find a downlaodable apk of the app I sought to fix, but there are also ways to get the apk of an app installed on the phone.
    3. Assuming java binaries are in your path, and the apktool jar and the apk are in the current dir, disassemble the apk with apktool:

      $ java -jar apktool_2.1.1.jar d com.example.android-app.apk
      

      The disassembled files (smali format) will be under com.example.android-app/ in the current dir.

    4. Replace all occurences of AlarmManager;->set/setExact with AlarmManager;->setExactAndAllowWhileIdle in all the disassembled smali files. You can either do this manually, or automate it with find and sed:

      $ find com.example.android-app -name *.smali -exec \
          sed -i -e 's/AlarmManager;->set\(Exact\)\?(/AlarmManager;->setExactAndAllowWhileIdle(/g' {} +
      

      This will replace both set and setExact with setExactAndAllowWhileIdle.

    5. Rebuild the apk:

      $ java -jar apktool_2.1.1.jar b com.example.android-app
      

      Note the b for build, and the lack of .apk to refer to the directory where the modified files are. The resulting apk is created as com.example.android-app/dist/com.example.android-app.apk

    6. Sign the apk. This is the annoying part if you never signed a jar file before:

      • First create a keystore and a signing key in one step (official documentation):

        $ keytool -genkeypair -validity 36500
        Enter keystore password:  android
        Re-enter new password: android
        What is your first and last name?
          [Unknown]:
        What is the name of your organizational unit?
          [Unknown]:
        What is the name of your organization?
          [Unknown]:
        What is the name of your City or Locality?
          [Unknown]:
        What is the name of your State or Province?
          [Unknown]:
        What is the two-letter country code for this unit?
          [Unknown]:
        Is CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct?
          [no]:  yes
        
        Enter key password for <mykey>
                (RETURN if same as keystore password):
        

        This creates a keystore with password android in the default location and a signing key named mykey, also with password android.

      • Now sign the rebuilt apk (official documentation):

        $ jarsigner -tsa http://timestamp.digicert.com com.example.android-app/dist/com.example.android-app.apk mykey
        Enter Passphrase for keystore: android
        jar signed.
        

        This signing procedure is essential, as your android phone will reject your apk if it is unsigned with a cryptic message like:

        Parse error

        There was a problem parsing the package.

    7. Uninstall the original app from your phone. This is essential, as the new signing key is different from the original author's signing key, and android will refuse to update an app when the signing key changes. If you try to update the pak, the built-in package manager will simply tell you:

      App not installed

      You may wish to make a backup or save the configuration of the app, if necessary.

    8. Enable installation from unknown sources
    9. Get the rebuilt apk to your phone and install it. There are various ways to do this, but these two are the easiest:

      • enable USB debugging and then run:

        $ adb install com.example.android-app/dist/com.example.android-app.apk
        
      • copy the apk to your phone's storage via USB, open file:///sdcard/ in your phone's browser and clik on the apk to download it, then open it from the downloader to install it.

    The app should now work properly with doze mode, while your doze mode may be slightly less effective in conserving battery than before, depending on the amount of wake-ups the fixed app causes.