Как приложение может определить, что оно будет удалено?
Все, что мы знаем, это обычное (практически любое) антивирусное приложение перед удалением используется для запуска простого диалога, например: "Вы собираетесь удалить приложение, вы уверены?" - "да нет".
Да, я знаю, что могу перехватить намерение удалить пакет, используя фильтр намерений, например:
<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>
Но проблема в том простом факте, что он перехватывает любые запросы на удаление, и, кроме того, он запускает диалог выбора между моим приложением и стандартной установкой. Так что, если пользователь выберет стандартный установщик - я ничего не смогу сделать.
Моя цель состоит не в том, чтобы не дать пользователю удалить мое приложение, а в том, чтобы откатить изменения, внесенные моим приложением.
Извлекая уроки из этих антивирусных приложений, я вижу, что такая операция возможна, поэтому, пожалуйста, помогите мне и объясните, как это возможно?
Обновить
Так как есть парни, которые не верят, что это реально, я бы обратился к Avast Mobile Security:
Anti-Theft защищает себя от деинсталляции, маскируя его компоненты различными способами самосохранения.
Другой пример: Kaspersky Internet Security для Android - вот специальная процедура для его удаления, которая требует ввода секретного кода.
В любом случае это означает, что есть способ перехватить процедуру удаления, чтобы либо предотвратить удаление, либо выполнить какую-либо завершающую работу.
4 ответа
Хорошо. Я много раз изучал эту проблему с 2-х дней и, наконец, нашел "дикий способ" решить ее без рута устройства:)
Во-первых, вот основные моменты для достижения решения:
1. Всякий раз, когда пользователь заходит в Настройки -> Управление приложениями -> Выбирает определенное приложение, мы получаем сообщение android.intent.action.QUERY_PACKAGE_RESTART с именем пакета приложения в качестве дополнения.
2. После этого, когда мы нажимаем кнопку " Удалить" (с установщиком пакета), он открывает действие с именем - com.android.packageinstaller.UninstallerActivity.
Поток управления будет таким:
В разделе "Настройки приложения" пользователь нажимает кнопку "Удалить" ---> Мы получаем контроль, чтобы показать диалог / начать другое действие / и т.д. ---> Мы завершаем нашу задачу перед удалением ---> Пользователь возвращается на экран подтверждения удаления - -> Пользователь подтверждает и удаляет приложение
Используемый метод:
Мы будем реализовывать BroadcastReceiver в нашем приложении для прослушивания действия "android.intent.action.QUERY_PACKAGE_RESTART" и сопоставлять имя нашего пакета внутри метода onReceive(). Если широковещание было получено для выбора желаемого пакета приложения, то мы запустим фоновый поток, который будет продолжать отслеживать действия, выполняемые на переднем плане с помощью ActivityManager.
Как только мы обнаружим, что основным видом деятельности является "com.android.packageinstaller.UninstallerActivity", это подтвердит, что пользователь хочет удалить наше приложение. На этом этапе мы выполним желаемые задачи (либо отобразим диалог, либо начнем другое действие, перекрывающее окно удаления и т. Д.), Которые необходимо выполнить перед удалением. После выполнения нашей задачи мы позволим пользователю продолжить процесс подтверждения удаления.
Реализация / Исходный код:
В manifest.xml
добавить разрешение:
<uses-permission android:name="android.permission.GET_TASKS"/>
и широковещательный приемник:
<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 (класс приемника вещания)
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 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();
}
}
Известные ограничения:
Когда пользователь нажимает кнопку " Удалить" в разделе "Управление приложениями", мы выполняем наши задачи перед удалением, а затем приглашаем пользователя в окно "Подтверждение", где пользователь может подтвердить удаление или отменить операцию.
Описанный выше подход на данный момент не распространяется на случай, когда пользователь нажимает кнопку " Отмена" после того, как мы выполнили нашу задачу. Но это может быть легко решено с некоторыми изменениями.
Например: мы можем реализовать логику, чтобы отменить изменения, которые мы внесли, если в конце не было получено сообщение "android.intent.action.PACKAGE_REMOVED".
Я надеюсь, что этот подход будет полезен для вас:) Так как это единственный способ, на мой взгляд, мы можем решить вашу проблему без рутирования устройства!
[Обновление 1]: Предложенный подход, чтобы проверить, была ли отменена задача удаления:
Забавно, что раньше у меня была совершенно иная и очень сложная идея (касающаяся трансляций, ActivityManager и т. Д. И т. Д.), Но при написании этой статьи мне в голову пришла еще одна идея, которая сравнительно очень проста:)
Когда пользователь нажимает кнопку "Удалить" в разделе "Управление приложениями" и после того, как вы выполнили свои задачи перед удалением, вы просто устанавливаете в своем приложении некоторую SharedPreference, которая выполнила задачи перед удалением и готова к удалению. После этого вам не нужно ни о чем заботиться.
Если пользователь продолжает удалять -> хорошо, так как вы уже выполнили необходимые задачи.
Хотя, если пользователь, наконец, нажимает кнопку " Отмена" и уходит -> не беспокойтесь. Пока пользователь не пойдет и не запустит ваше приложение снова. Теперь внутри "onStart()" / "onResume()" основной активности вашего приложения вы можете проверить значение SharedPreference, и если оно было установлено для удаления, это будет означать, что пользователь, наконец, не продолжил удаление. И теперь вы можете отменить изменения, сделанные ранее (отменить выполненные задачи перед удалением), чтобы обеспечить идеальную работу вашего приложения!
До Android 5.0 была опция для обнаружения удаления приложения с использованием собственного кода:
Вы должны следить за Вашим каталогом, используя inotify
рамки в раздвоенном процессе. Когда он удален, вы можете запустить некоторую системную команду, например. am
команда, которая начинается Intent
PoC такого решения: https://github.com/pelotasplus/ActionAfterUninstall/blob/master/app/src/main/jni/hello-jni.c
Это просто невозможно в Android
Ваше приложение не может знать, что оно удаляется (без изменения ядра). Все файлы, созданные в data / data / your.app.package, автоматически удаляются при установке.
Другой подход может состоять в том, чтобы иметь другое приложение, которое проверяет, установлено ли это приложение или нет. Если нет, он может сделать уборку.
ОБНОВИТЬ
Намерение ACTION_PACKAGE_REMOVED будет отправлено всем получателям, кроме вашего. Это подтверждается ЗДЕСЬ.
ОБНОВЛЕНИЕ 2
Просто еще одна мысль.
когда я искал это, я обнаружил, что это может быть сделано путем мониторинга logcat для вашего приложения. Вот пример монитора logcat
Хорошо, что для мониторинга logcat для одного и того же приложения нам не нужно иметь рутированное устройство
и когда мы читаем каждую запись в logcat, мы можем искать следующую строку
Received broadcast Intent { act=android.intent.action.PACKAGE_REMOVED dat=package:com.package.name flg=0x8000010 (has extras) }
поскольку это событие получено, мы знаем, что наше приложение будет не установлено
Не пробовал хоть
Снова мониторинг logcat от Android Jellybean запрещен
Чтобы приложение сохранялось, вам нужно иметь рутированное устройство и установить его в системный раздел. Как только они появятся, вы можете удалить обновления, поскольку они сохраняются вместе с несистемными приложениями, но удалить их из системы не так просто.
Я знаю, что некоторые из них также сохранят немного данных в системном разделе на тот случай, если устройства будут сброшены на заводские настройки, но есть также способы заставить менеджер пакетов оставить после себя сохраненные данные в случае, если они просто удалены.,
Другой вариант - зарегистрировать его в качестве администратора устройства. Как только вы это сделаете, они не смогут удалить его, если они не удалят его статус администратора вручную.
<item name="android.permission.ACCESS_SUPERUSER" />
Здесь похоже, что они используют root, а также другие методы. Если не считать какой-то сумасшедшей сложной службы, которая, как кажется, они могут иметь, нет законного способа сделать это любым другим способом.
Использование преимуществ root является практически стандартной практикой для таких приложений AV/ безопасности, как это, без них они не имеют никакого реального контроля над другими приложениями, поэтому они очень ограничены. Я думаю, что разрешение SuperUser не отображается, если вы не установили его, так что многие люди до сих пор не знают, что это вариант.
<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>