How to recognise that the user has just uninstalled your app
Click here to load reader
-
Upload
aleksander-piotrowski -
Category
Engineering
-
view
151 -
download
2
Transcript of How to recognise that the user has just uninstalled your app
How to recognise that the user has just uninstalled your Android app
fb.me/pjakubczyk+AleksanderPiotrowski@pelotasplus
Opera Max
The Java way
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>
Read the broadcastvoid 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)); }
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
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
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
Let’s uninstall our app
and there’s nothing ….
Why ?
OS unregisters listener during removal
What Opera does?
It does not listen for package removal
it does some magic ;-)
… not in Java code
Getting the APK
Getting the APK
● genymotion withgapps installed
● get app from play store● be careful with the right ABI
Getting the APK
1. adb shell2. pm list packages
Getting the APK
3. pm path com.opera.max4. adb pull /data/app/com.opera.max.apk
Hacking APK
Apktool
A tool for reverse engineering Android apk files
Made with <3 in Poland ;-)
Apktool
Easy to use
$ apktool d com.opera.max.apk
Apktool
● decoded XML files● smali assembly code● PNGs, layouts, resources● id-s mapping
with Opera Max APK
live apktool demo
Opera Findings
Found a clue!
There are *.so files
We can inspect them to see more
Tools: strings, objdump, nm, readelf
rudy$ strings opera/lib/armeabi/libuo.so (II)
...inotify_initinotify_add_watchinotify_rm_watch/data/data/%s/%s%s
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.
rudy$ strings opera/lib/armeabi/libuo.so (I)
...Androidstartandroid.intent.action.VIEW--user...
am command
part of Android system/system/bin/am
A way to start apps, intents and whatnot
more details
$ ps
USER PID PPIDu0_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
The scenario
1. Fork the native process2. Inside the child process use inotify to watch
a file3. Watcher is woken up on file deletion. Start
another native process4. The last process run the ‘am’
(ActivityManager) command to run intent.
Setup
JNI
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-sdkndk.dir=/Users/alek/android-ndk-r10e
build.gradle
android.defaultConfig { applicationId "pl.pelotasplus.actionafteruninstall"
ndk { moduleName "hello-jni" ldLibs "log", "android" stl "stlport_static" } }
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");
}}
MainActivity.java calling
protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
textView.setText(stringFromJNI());
observer(); }
project structure
Native code
JNI
Sample by GooglejstringJava_pl_pelotasplus_actionafteruninstall_MainActivity_stringFromJNI
(JNIEnv* env, jobject thiz){ return (*env)->NewStringUTF(
env,"Hello from JNI ! Compiled with ABI foo."
);}
Android.mkLOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jniLOCAL_SRC_FILES := hello-jni.cLOCAL_LDFLAGS += -llog -lpthreadinclude $(BUILD_SHARED_LIBRARY)
Application.mkAPP_ABI := armeabi-v7a# allAPP_STL := stlport_static
inotify on Linuxint 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);}
inotify on Linuxint 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"); }
inotify on Linuxwhile (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 );
inotify on Android (pseudo code)
void observer(void) { inotify_init(); inotify_add_watch(fd, DIRECTORY, IN_DELETE); if (event->mask & IN_DELETE) { startIntent(); }}
first attemptvoidJava_pl_pelotasplus_actionafteruninstall_MainActivity_observer(JNIEnv* env, jobject thiz){
observer();}
App blocked as native code blocked app
second attempt, with threadvoidJava_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
third attempt, with forkvoidJava_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(); }}
start intent, another forkvoid 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"); }}
Live demo of our app
https://github.com/pelotasplus/ActionAfterUninstall
Check the dirty source code
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 HackbornAndroid framework [email protected]
http://markmail.org/message/ruqp2t6gvhnhv654