How can an app detect that it's going to be uninstalled? [duplicate]
Asked Answered
R

3

95

All we know that usual (in practice any) antivirus application before uninstall used to fire simple dialog like: "You're going to uninstall app, are you sure?" - "yes/no".

Yes, I know that I can intercept package delete intent using intent-filter like:

<activity
    android:name=".UninstallIntentActivity"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <action android:name="android.intent.action.DELETE" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="package"  />
    </intent-filter>
</activity>

But problem is in simple fact that this intercepts any delete requests and moreover this will fire chooser dialog between my app and stock installer. So if user will select stock installer - I won't be able to do anything.

My goal is not to prevent user from uninstalling of my app, but just rollback changes made by my app.

Learning from those antivirus apps I see that this kind of operation is possible, so please help me and explain how it is possible?

Update

Since there are some guys who doesn't believe that it's real - I would refer to Avast Mobile Security:

Anti-Theft protects itself from uninstall by disguising its components with various self-preservation techniques.

Another example: Kaspersky Internet Security for Android - here's special procedure for uninstalling it, which requires entering of secret code.

Anyway it means that there's way to intercept uninstallation procedure in order to either prevent uninstall or do some finalizing job.

Rockery answered 9/9, 2013 at 6:28 Comment(11)
What changes does your app make that you need to rollback?Ferree
These apps monitor the logcat and asks you to enter a pin when they detect that you are trying to uninstall an app. They ask you so, by simply starting an activity at the right time: this activity will naturally appear between you and the stock installed.Loree
Side note: I just installed the "antivirus" app and uninstalled it without the app asking me anything. API 18.Bianchi
@SherifelKhatib - it's been a few android releases now since the ability to monitor logcat was dropped.Codd
I have Samsung Galaxy S4 with 4.2.2 (API 17) and Avast installed on it asks before uninstall "Are you sure"? So it proves my point - it's possible at least for API 17Rockery
Most of the AntiVirus and AntiTheft apps install themeselves as device administrators. See this - developer.android.com/guide/topics/admin/device-admin.htmlRoadside
you need to install your app as device administrator.Sayres
@Roadside 1) I don't see a policy for uninstalling apps in the Device Admin API. 2) Avast doesn't appear to require/request any Device Admin permissions.Neurogenic
Some of the features avast has do require it to be an Admin(or root). Remote wiping especially is one of them.Spiceberry
Can we verify that you can set up an app as Device Admin without it showing up in the permissions list? I'm definitely not seeing it on mine. That would seem like a big security hole if any random app could do it with no notice to the user.Neurogenic
They use root for some things, and It's safe to say their "premium" app has some extra features the free one doesn't. ■ Firewall (rooted devices only): Block hackers from getting in.Spiceberry
K
168

Okay. I have been investigating a lot on this problem since 2 days and finally found a "wild way" to solve it without rooting the device :)

First, here are the highlights to achieve the solution:

1. Whenever user goes to Settings -> Manage Apps -> Selects a particular application we receive a broadcast android.intent.action.QUERY_PACKAGE_RESTART with name of the application's package as extras.

2. After that when we click on the Uninstall button (with package installer), it opens an activity named - com.android.packageinstaller.UninstallerActivity

Control flow will be like:

Under App Settings the User Clicks on Uninstall button ---> We get control to show a dialogue / start another activity / etc ---> We finish our Pre-Uninstallation task ---> User is Returned back to Uninstallation confirmation screen ---> User confirms and uninstalls the app

Used Method:

We will implement a BroadcastReceiver in our application for listening the action "android.intent.action.QUERY_PACKAGE_RESTART" and match our package name inside onReceive() method. If the broadcast was received for selection of our desired application package, then we'll initiate a background thread that will keep monitoring the foreground running activities using the ActivityManager.

Once we find the foreground activity to be "com.android.packageinstaller.UninstallerActivity", it'll be confirm that user wants to uninstall our application. At this point we'll perform the desired tasks (either display a dialogue, or start another activity overlapping the uninstallation window, etc..) that are to be performed before uninstallation. After performing our task, we'll allow the user to continue with confirming the uninstallation process.

Implementation / Source Code:

In manifest.xml

add permission:

<uses-permission android:name="android.permission.GET_TASKS"/>

and broadcast receiver:

<receiver android:name=".UninstallIntentReceiver">
      <intent-filter android:priority="0">
            <action android:name="android.intent.action.QUERY_PACKAGE_RESTART" />
            <data android:scheme="package" />
      </intent-filter>
 </receiver>

UninstallIntentReceiver.java (broadcast receiver class)

public class UninstallIntentReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        // fetching package names from extras
        String[] packageNames = intent.getStringArrayExtra("android.intent.extra.PACKAGES"); 

        if(packageNames!=null){
            for(String packageName: packageNames){
                if(packageName!=null && packageName.equals("YOUR_APPLICATION_PACKAGE_NAME")){
                    // User has selected our application under the Manage Apps settings
                    // now initiating background thread to watch for activity
                    new ListenActivities(context).start();

                }
            }
        }
    }

}

ListenActivities class - for monitoring the foreground activities

class ListenActivities extends Thread{
    boolean exit = false;
    ActivityManager am = null;
    Context context = null;

    public ListenActivities(Context con){
        context = con;
        am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    }

    public void run(){

        Looper.prepare();

        while(!exit){

             // get the info from the currently running task
             List< ActivityManager.RunningTaskInfo > taskInfo = am.getRunningTasks(MAX_PRIORITY); 

             String activityName = taskInfo.get(0).topActivity.getClassName();


             Log.d("topActivity", "CURRENT Activity ::"
                     + activityName);

             if (activityName.equals("com.android.packageinstaller.UninstallerActivity")) {
                // User has clicked on the Uninstall button under the Manage Apps settings

                 //do whatever pre-uninstallation task you want to perform here
                 // show dialogue or start another activity or database operations etc..etc..

                // context.startActivity(new Intent(context, MyPreUninstallationMsgActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
                 exit = true;
                 Toast.makeText(context, "Done with preuninstallation tasks... Exiting Now", Toast.LENGTH_SHORT).show();
            } else if(activityName.equals("com.android.settings.ManageApplications")) {
                // back button was pressed and the user has been taken back to Manage Applications window
                          // we should close the activity monitoring now
                exit=true;
            }
        }
        Looper.loop();
    }
}

Known Limitations:

When the user clicks on the Uninstall button under Manage Apps settings, we'll perform our pre-uninstallation tasks and then promt the user to the Confirmation window where user can either confirm to uninstall or can Cancel the operation.

The approach described above is as of now not covering the case if user clicks on Cancel button after we have performed our task. But this could be tackled easily with some ammendments.

E.g.: We can implement a logic to revert the changes we made if the broadcast "android.intent.action.PACKAGE_REMOVED" was not received in the end.

I hope this approach will be helpful to you :) As this is the only way in my opinion we can solve your problem without rooting the device!

[Update 1]: Suggested Approach to check if the Uninstallation task was Canceled:

Its kind of funny that I had entirely different and much complex idea earlier(involving broadcasts, ActivityManager, etc.. etc..), but while writing it here just another idea struck into my mind which is comparatively very simple :)

When the User clicks on Uninstall button under Manage Apps settings and after you have performed your pre-uninstallation tasks, you just set some SharedPreference in your app that you have performed the pre-uninstall tasks and are ready for uninstallation. After this you need not to care about anything.

If the user continues to uninstall -> its well and good as you have already performed required tasks.

While if user finally clicks on Cancel button and goes away -> don't bother. Until the user goes and run your application again. Now inside "onStart()" / "onResume()" of your application's main activity, you can check the SharedPreference's value and if it was set for uninstallation, that will mean that user didn't finally proceeded with the uninstallation. And now you could revert the changes made earlier(reversing the pre-uninstall tasks performed) to ensure that your application runs perfectly!

Kettledrummer answered 15/9, 2013 at 19:40 Comment(24)
I ensured its running fine before posting it here :) Though I could not test it on higher versions as my Jelly Bean phone is out for servicing :( Let me know if you need any further assistance here.Kettledrummer
Yeeaahh, it works. 1) But I'm unable to ask user either he's sure or not 2) It's unclear what to do if user cancels operation, since PACKAGE_REMOVED broadcast won't be received my app (in accordance with docs). Anyway I'm granting bounty for your efforts, nevertheless would be happy if you'd help me resolve 2 aforementioned issues :)Rockery
Thanks for that awesome bounty! it was almost 1.5 times of my total current reputation,so means a lot to me :) About your 2 issues -- 1) You can create an activity for that purpose and launch it as shown in the line commented in my code. In this activity you can warn / re-ask the user to be sure.. accordingly you can either forward him to home screen (as in case of minimize window, which will hide the uninstall screen or maybe there would be a way using ActivityManager by which you could actually close the original package uninstallation window. I have not tried it, but you can explore it out.Kettledrummer
2.) You're right, "PACKAGE_REMOVED" will not be received by same application. This will have to be implemented again with a tweak(requires some efforts). I'll update my answer to address my idea (again a wild one which I have not tried yet).Kettledrummer
check the [Update 1] in my answer above and let me know what you think about my suggested approach :)Kettledrummer
did you check the update??Kettledrummer
No, this approach doesn't fit to my case. I can't rollback changes made in ListenActivities. It's too complicated logic. I would be pleased to find other way to solve "cancel" issue.Rockery
ok. I'll try to explore more options then.Kettledrummer
this doesn't work for me on android 4.4. haven't tested other versions of android. the intent QUERY_PACKAGE_RESTART never gets received. i believe that intent is only receivable by system apps. see #18034094Marcelenemarcelia
It was good while it lasted, but this doesn't work reliably since 4.0. See https://mcmap.net/q/89474/-can-39-t-get-receiver-when-the-app-uninstallKyser
There is any solution for android 4. xRayraya
@Chetan any luck for android 4.x i m still in difficulties this is not working in my caseEvictee
@Kettledrummer i have tried your code but this is for uninstall i want to open enable /disable app settings how can i do that i have successfully open settings page but failed to read users "Eanble" disable actionEvictee
The getRunningTasks approach apparently doesn't work in Android 5, but you can still use an AccessibilityService to detect the uninstall dialog.Natation
It's do not work in android 4.3 :|Kelsi
<uses-permission android:name="android.permission.GET_TASKS"/> It has been deprecated at API level 21.Togetherness
It doesn't work in 4.2.2Togetherness
Adding <action android:name="android.intent.action.PACKAGE_REMOVED" /> to intent-filter in manifest works for me perfectInterbreed
@Kettledrummer I have implemented your code as it is but it is not working for me. Please answer this question #42228080Motorcade
How about if the user doesn't go to the Settings to uninstall the app? In my opinion, There are other ways you can uninstall the app now which invalidates this solution.Migrate
Nope, it works as CommonsWare said here https://mcmap.net/q/89371/-perform-a-task-on-uninstall-in-android-duplicateCuster
I want to get the package name of the uninstalled app. (Not mine). How i get package name in extras of android.intent.action.PACKAGE_REMOVEDMilton
Does this procedure still work? Has anyone tried it on latest API levels?Varix
Looking for same update, is this solution working on latest APIs? @Migrate what are the other extra ways of uninstalling the app? Is it just uninstalling from play store?Tallu
K
10

Up to Android 5.0 there was option for detecting app uninstall is using native code:

You need to monitor for Your directory using inotify framework in forked process. When it's deleted You can run some system command eg. am command that starts Intent

PoC of such solution: https://github.com/pelotasplus/ActionAfterUninstall/blob/master/app/src/main/jni/hello-jni.c

Knocker answered 18/6, 2015 at 18:8 Comment(2)
While i am running whole project in 4.4 then no logs is showing. Can you please tell me how i can see log when user click on uninstall button.Zambia
Not that this no longer works as of Android 5.0: github.com/pelotasplus/ActionAfterUninstall/issues/1Natation
S
1

In order to make your app persist you'll need to have a rooted device and be able to install it to the system partition. Once it's on there you can uninstall the updates, since they are saved along side non-system apps, but it's not as cut and dry to uninstall it from the system.

I know some of them will also save a little bit of data on the system partition just in case the devices is factory reset, but there are also ways to get the package manager to leave behind your saved data in the event that it is just uninstalled.

Another option would be to register it as a device administrator. Once you do that they will be unable to uninstall it unless they manually remove it's admin status.

enter image description here

<item name="android.permission.ACCESS_SUPERUSER" />

Here it looks like they're using root as well as other methods. Short of making some crazy elaborate service, which it appears they may have, there is no legitimate way to do this any other way.

Taking advantage of root is almost standard practice for AV/security apps like this, without it they don't have any real authority over any other apps so they're very limited. I think the SuperUser permission isn't shown unless you have it installed either, so many people are still unaware it's an option.

<perms>
<item name="android.permission.READ_EXTERNAL_STORAGE" />
<item name="android.permission.GET_TASKS" />
<item name="android.permission.PROCESS_OUTGOING_CALLS" />
<item name="android.permission.WRITE_EXTERNAL_STORAGE" />
<item name="android.permission.WRITE_CALL_LOG" />
<item name="com.avast.android.generic.CENTRAL_SERVICE_PERMISSION" />
<item name="android.permission.WRITE_SMS" />
<item name="android.permission.ACCESS_WIFI_STATE" />
<item name="android.permission.RECEIVE_SMS" />
<item name="android.permission.GET_ACCOUNTS" />
<item name="android.permission.READ_CONTACTS" />
<item name="android.permission.CALL_PHONE" />
<item name="android.permission.WRITE_CONTACTS" />
<item name="android.permission.READ_PHONE_STATE" />
<item name="android.permission.READ_SMS" />
<item name="android.permission.RECEIVE_BOOT_COMPLETED" />
<item name="android.permission.ACCESS_SUPERUSER" />
<item name="com.avast.android.mobilesecurity.permission.C2D_MESSAGE" />
<item name="android.permission.GET_PACKAGE_SIZE" />
<item name="android.permission.WAKE_LOCK" />
<item name="android.permission.ACCESS_NETWORK_STATE" />
<item name="android.permission.USE_CREDENTIALS" />
<item name="android.permission.SEND_SMS" />
<item name="android.permission.RECEIVE_MMS" />
<item name="com.google.android.c2dm.permission.RECEIVE" />
<item name="android.permission.KILL_BACKGROUND_PROCESSES" />
<item name="com.android.vending.BILLING" />
<item name="android.permission.WRITE_SETTINGS" />
<item name="android.permission.INTERNET" />
<item name="android.permission.VIBRATE" />
<item name="android.permission.READ_CALL_LOG" />
<item name="com.avast.android.generic.COMM_PERMISSION" />
<item name="com.dolphin.browser.permission.ACCESS_PROVIDER" />
<item name="com.android.browser.permission.READ_HISTORY_BOOKMARKS" />
</perms>
Spiceberry answered 13/9, 2013 at 14:7 Comment(2)
As noted in the OP and comments, Avast Mobile Security does this without root. Tested and verified on an unrooted 4.2.2 Galaxy Nexus.Neurogenic
There are many ways you could go about it, just depends on how much effort you are willing to devote to it and how thorough you want it to be.Spiceberry

© 2022 - 2024 — McMap. All rights reserved.