Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to recognise that the user has just uninstalled your app

How to recognise that the user has just uninstalled your app

Presented during Droidcon.de 2015 barcamp session.

Hacking Android to get a notification when your app is being uninstalled. Mixing JNI and native C code to do something that was never meant to be done ;-)


Aleksander Piotrowski

June 03, 2015


  1. How to recognise that the user has just uninstalled your

    Android app fb.me/pjakubczyk +AleksanderPiotrowski @pelotasplus
  2. Opera Max

  3. None
  4. The Java way

  5. Read the broadcast <receiver android:name=".PackageWatcher"> <intent-filter> <action android:name="android.intent.action. PACKAGE_ADDED"/> <action

    android:name="android.intent.action. PACKAGE_REMOVED"/> <action android:name="android.intent.action. PACKAGE_REPLACED" /> <data android:scheme="package"/> </intent-filter> </receiver>
  6. Read the broadcast void onReceive(Context context, Intent intent) { Bundle

    bundle = intent.getExtras(); Iterator<String> it = bundle.keySet().iterator; while (it.hasNext()) { String key = it.next(); Log.e("DDD", key +"="+bundle.get(key)); }
  7. Usually we see (install) E/DDD (29199): Dumping Intent start [android.intent.extra.UID=10089]

    [android.intent.extra.user_handle=0] E/DDD (29199): Dumping Intent end
  8. Usually we see (reinstall) E/DDD (29199): Dumping Intent start [android.intent.extra.REMOVED_FOR_ALL_USERS=false]

    [android.intent.extra.UID=10089] [android.intent.extra.DATA_REMOVED=false] [android.intent.extra.REPLACING=true] [android.intent.extra.user_handle=0] E/DDD (29199): Dumping Intent end
  9. Usually we see (uninstall) E/DDD (29199): Dumping Intent start [android.intent.extra.REMOVED_FOR_ALL_USERS=true]

    [android.intent.extra.UID=10089] [android.intent.extra.DATA_REMOVED=true] [android.intent.extra.user_handle=0] E/DDD (29199): Dumping Intent end
  10. Let’s uninstall our app and there’s nothing …. Why ?

    OS unregisters listener during removal
  11. What Opera does? It does not listen for package removal

    it does some magic ;-) … not in Java code
  12. Getting the APK

  13. Getting the APK • genymotion with gapps installed • get

    app from play store • be careful with the right ABI
  14. Getting the APK 1. adb shell 2. pm list packages

  15. Getting the APK 3. pm path com.opera.max 4. adb pull

    /data/app/com. opera.max.apk
  16. Hacking APK

  17. Apktool A tool for reverse engineering Android apk files Made

    with <3 in Poland ;-)
  18. Apktool Easy to use $ apktool d com.opera.max. apk

  19. Apktool • decoded XML files • smali assembly code •

    PNGs, layouts, resources • id-s mapping
  20. with Opera Max APK live apktool demo

  21. Opera Findings

  22. Found a clue! There are *.so files We can inspect

    them to see more Tools: strings, objdump, nm, readelf
  23. rudy$ strings opera/lib/armeabi/libuo.so (II) ... inotify_init inotify_add_watch inotify_rm_watch /data/data/%s/ %s%s

  24. inotify framework http://linux.die.net/man/7/inotify The inotify API provides a mechanism for

    monitoring file system events. Inotify can be used to monitor individual files, or to monitor directories.
  25. rudy$ strings opera/lib/armeabi/libuo.so (I) ... Android start android.intent.action.VIEW --user ...

  26. am command part of Android system /system/bin/am A way to

    start apps, intents and whatnot
  27. more details $ ps USER PID PPID u0_a91 24318 20265

    246900 27716 ffffffff b6edf5cc S com.opera.max u0_a91 24337 24318 856 336 c00e4944 b6f72158 S /data/app-lib/com.opera.max-2/libuo.so
  28. The scenario 1. Fork the native process 2. Inside the

    child process use inotify to watch a file 3. Watcher is woken up on file deletion. Start another native process 4. The last process run the ‘am’ (ActivityManager) command to run intent.
  29. Setup JNI

  30. local.properties # Location of the SDK. This is only used

    by Gradle. # For customization when using a Version Control System, please read the sdk.dir=/Users/alek/android-sdk ndk.dir=/Users/alek/android-ndk-r10e
  31. build.gradle android.defaultConfig { applicationId "pl.pelotasplus.actionafteruninstall" ndk { moduleName "hello-jni" ldLibs

    "log", "android" stl "stlport_static" } }
  32. MainActivity.java declaring public class MainActivity extends AppCompatActivity { public native

    String stringFromJNI(); public native void observer(); static { System.loadLibrary("hello-jni"); // System.loadLibrary("/data/data/com.foo.test/lib/liba.so"); } }
  33. MainActivity.java calling protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); textView =

    (TextView) findViewById(R.id.textView); textView.setText(stringFromJNI()); observer(); }
  34. project structure

  35. Native code JNI

  36. Sample by Google jstring Java_pl_pelotasplus_actionafteruninstall_MainActivity_stringFro mJNI (JNIEnv* env, jobject thiz)

    { return (*env)->NewStringUTF( env, "Hello from JNI ! Compiled with ABI foo." ); }
  37. Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello-jni

    LOCAL_SRC_FILES := hello-jni.c LOCAL_LDFLAGS += -llog -lpthread include $(BUILD_SHARED_LIBRARY)
  38. Application.mk APP_ABI := armeabi-v7a # all APP_STL := stlport_static

  39. inotify on Linux int main( int argc, char **argv) {

    int length, i = 0; int fd; int wd; char buffer[BUF_LEN]; fd = inotify_init(); printf("fd=%d\n", fd); }
  40. inotify on Linux int main( int argc, char **argv) {

    [...] wd = inotify_add_watch(fd, "/var/tmp", IN_MODIFY | IN_CREATE | IN_DELETE); length = read( fd, buffer, BUF_LEN ); printf("length=%d\n", length); if (length < 0) { perror("read"); }
  41. inotify on Linux while (i < length) { struct inotify_event

    *event = (struct inotify_event*)&buffer[ i]; printf("Event len %d\n", event->len); if (event->len) { if (event->mask & IN_DELETE) { if (event->mask & IN_ISDIR) { printf( "The directory %s was deleted.\n", event->name ); } else { printf( "The file %s was deleted.\n", event->name );
  42. inotify on Android (pseudo code) void observer(void) { inotify_init(); inotify_add_watch(fd,

    DIRECTORY, IN_DELETE); if (event->mask & IN_DELETE) { startIntent(); } }
  43. first attempt void Java_pl_pelotasplus_actionafteruninstall_MainActivity_observer(JNIEnv* env, jobject thiz) { observer(); }

    App blocked as native code blocked app
  44. second attempt, with thread void Java_pl_pelotasplus_actionafteruninstall_MainActivity_observer (JNIEnv* env, jobject thiz)

    { pthread_attr_init(&attr); pthread_create(&thread, &attr, &observer_thread, NULL); } App not blocked but native code stopped when stopping app for uninstalling
  45. third attempt, with fork void Java_pl_pelotasplus_actionafteruninstall_MainActivity_observer(JNIEnv* env, jobject thiz) {

    pid_t pid; pid = fork(); if (pid == 0) { __android_log_print(ANDROID_LOG_INFO, TAG, "Fork child\n"); observer(); } }
  46. start intent, another fork void startIntent(void) { pid_t p =

    fork(); if (p == 0) { __android_log_print(ANDROID_LOG_INFO, TAG, "startIntent %d", getpid()); system("/system/bin/am start --user 0 -a android.intent. action.VIEW -d http://droidcon.de"); } }
  47. Live demo of our app

  48. https://github. com/pelotasplus/A ctionAfterUninstall Check the dirty source code

  49. Moral > What happens when I call fork() in JNI

    code? Will this totally break the > Activity lifecycle model in Android? Don't do this. Just don't. -- Dianne Hackborn Android framework engineer hack...@android.com http://markmail.org/message/ruqp2t6gvhnhv654