программно установить / удалить APK (PackageManager vs Intents)

143

Мое приложение устанавливает другие приложения, и ему необходимо отслеживать, какие приложения оно установило. Конечно, этого можно добиться, просто ведя список установленных приложений. Но в этом не должно быть необходимости! PackageManager должен нести ответственность за поддержание отношения installedBy (a, b). Фактически, согласно API это:

public abstract String getInstallerPackageName (String packageName) - получает имя пакета приложения, установившего пакет. Это определяет, с какого рынка поступила упаковка.

Текущий подход

Установить APK с помощью Intent

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(intent);

Удалить APK с помощью намерения:

Intent intent = new Intent(Intent.ACTION_DELETE, Uri.fromParts("package",
getPackageManager().getPackageArchiveInfo(apkUri.getPath(), 0).packageName,null));
startActivity(intent);

Очевидно, что это не тот способ, которым, например, Android Market устанавливает / удаляет пакеты. Они используют более богатую версию PackageManager. В этом можно убедиться, загрузив исходный код Android из репозитория Android Git. Ниже приведены два скрытых метода, соответствующих подходу Intent. К сожалению, они недоступны для внешних разработчиков. Но, может быть, они будут в будущем?

Лучший подход

Установка APK с помощью PackageManager

/**
 * @hide
 * 
 * Install a package. Since this may take a little while, the result will
 * be posted back to the given observer.  An installation will fail if the calling context
 * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
 * package named in the package file's manifest is already installed, or if there's no space
 * available on the device.
 *
 * @param packageURI The location of the package file to install.  This can be a 'file:' or a
 * 'content:' URI.
 * @param observer An observer callback to get notified when the package installation is
 * complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be
 * called when that happens.  observer may be null to indicate that no callback is desired.
 * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
 * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
 * @param installerPackageName Optional package name of the application that is performing the
 * installation. This identifies which market the package came from.
 */
public abstract void installPackage(
        Uri packageURI, IPackageInstallObserver observer, int flags,
        String installerPackageName);

Удаление APK с помощью PackageManager

/**
 * Attempts to delete a package.  Since this may take a little while, the result will
 * be posted back to the given observer.  A deletion will fail if the calling context
 * lacks the {@link android.Manifest.permission#DELETE_PACKAGES} permission, if the
 * named package cannot be found, or if the named package is a "system package".
 * (TODO: include pointer to documentation on "system packages")
 *
 * @param packageName The name of the package to delete
 * @param observer An observer callback to get notified when the package deletion is
 * complete. {@link android.content.pm.IPackageDeleteObserver#packageDeleted(boolean)} will be
 * called when that happens.  observer may be null to indicate that no callback is desired.
 * @param flags - possible values: {@link #DONT_DELETE_DATA}
 *
 * @hide
 */
public abstract void deletePackage(
        String packageName, IPackageDeleteObserver observer, int flags);

Отличия

  • При использовании намерений локальный менеджер пакетов не знает, из какого приложения была произведена установка. В частности, getInstallerPackageName (...) возвращает значение null.

  • Скрытый метод installPackage (...) принимает имя пакета установщика в качестве параметра и, скорее всего, способен установить это значение.

Вопрос

Можно ли указать имя установщика пакета с помощью намерений? (Может быть, имя установочного пакета может быть добавлено как дополнение к намерению установки?)

Совет: Если вы хотите загрузить исходный код Android, вы можете выполнить шаги, описанные здесь: Загрузка исходного дерева. Чтобы извлечь файлы * .java и поместить их в папки в соответствии с иерархией пакетов, вы можете проверить этот аккуратный сценарий: Просмотреть исходный код Android в Eclipse .

Håvard Geithus
источник
Некоторые URI отсутствуют в тексте. Я добавлю их, как только мне будет разрешено (у новых пользователей есть некоторые ограничения для предотвращения спама).
Håvard Geithus
1
как отключить функцию удаления?
2
@ user938893: "как отключить функцию удаления?" - Работаем над каким-то вредоносным ПО, которое сложно удалить?
Daniel

Ответы:

66

В настоящее время это недоступно для сторонних приложений. Обратите внимание, что даже использование отражения или других уловок для доступа к installPackage () не поможет, потому что только системные приложения могут использовать его. (Это связано с тем, что это низкоуровневый механизм установки после того, как разрешения были утверждены пользователем, поэтому для обычных приложений небезопасно иметь доступ к нему.)

Кроме того, аргументы функции installPackage () часто меняются между выпусками платформы, поэтому все, что вы делаете, пытаясь получить к нему доступ, будет терпеть неудачу в различных других версиях платформы.

РЕДАКТИРОВАТЬ:

Также стоит отметить, что этот пакет installerPackage был добавлен на платформу совсем недавно (2.2?) И изначально фактически не использовался для отслеживания того, кто установил приложение - он используется платформой для определения того, кого запускать при сообщении об ошибках с приложение для реализации отзывов Android. (Это также был один из случаев, когда менялись аргументы метода API.) По крайней мере, долгое время после того, как он был представлен, Market все еще не использовал его для отслеживания установленных приложений (и вполне может, что он все еще не использует его. ), но вместо этого просто использовал это, чтобы установить приложение Android Feedback (которое было отдельным от Market) в качестве «владельца», чтобы заботиться об обратной связи.

хакбод
источник
«Обратите внимание, что даже использование отражения или других уловок для доступа к installPackage () не поможет, потому что только системные приложения могут использовать его». Предположим, я делаю приложение для установки / удаления / управления пакетом для данной платформы, кроме самого Android. Как мне получить доступ к установке / удалению?
dascandy
startActivity () с соответствующим образом сформированным намерением. (Я уверен, что на этот вопрос ответили в другом месте на StackOverflow, поэтому я не буду пытаться давать здесь точный ответ, рискуя ошибиться.)
hackbod
mmmkay, это вызывает стандартные диалоговые окна установки / удаления Android. Эти детали уже были обработаны - я ищу функции «просто ****, установите этот пакет» и «просто **** удалите этот пакет», буквально без вопросов.
dascandy
2
Как я уже сказал, они недоступны для сторонних приложений. Если вы создаете свой собственный образ системы, у вас есть реализация платформы, и вы можете найти там функции, но они не являются частью API-интерфейсов, доступных для обычных сторонних приложений.
hackbod
Я делаю обозреватель файлов apk с функциями установки, удаления и резервного копирования, так что может ли Google разрешить мне публиковать мое приложение в Google Play? и какую политику мы собираемся нарушить?
Рахул Мандалия
91

Android P + требует это разрешение в AndroidManifest.xml

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

Затем:

Intent intent = new Intent(Intent.ACTION_DELETE);
intent.setData(Uri.parse("package:com.example.mypackage"));
startActivity(intent);

удалить. Вроде проще ...

JohnyTex
источник
Может ли это приложение запускать код? как в onDestroy()методе?
Mahdi-Malv
как насчет ACTION_INSTALL_PACKAGE? можем ли мы загрузить и установить последнюю версию нашего приложения из игрового магазина?
МАС. John
3
Поскольку для удаления приложений Android P требуется разрешение манифеста «android.permission.REQUEST_DELETE_PACKAGES», независимо от того, используете ли вы «ACTION_DELETE» или «ACTION_UNINSTALL_PACKAGE» developer.android.com/reference/android/content/…
Darklord5
Спасибо, что упомянули разрешение Android P, я застрял и не был уверен, что происходит раньше.
Ави Паршан 07
44

Уровень API 14 представил два новых действия: ACTION_INSTALL_PACKAGE и ACTION_UNINSTALL_PACKAGE . Эти действия позволяют передать дополнительное логическое значение EXTRA_RETURN_RESULT для получения уведомления о результате (не) установки.

Пример кода для вызова диалогового окна удаления:

String app_pkg_name = "com.example.app";
int UNINSTALL_REQUEST_CODE = 1;

Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);  
intent.setData(Uri.parse("package:" + app_pkg_name));  
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
startActivityForResult(intent, UNINSTALL_REQUEST_CODE);

И получите уведомление в своем методе Activity # onActivityResult :

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == UNINSTALL_REQUEST_CODE) {
        if (resultCode == RESULT_OK) {
            Log.d("TAG", "onActivityResult: user accepted the (un)install");
        } else if (resultCode == RESULT_CANCELED) {
            Log.d("TAG", "onActivityResult: user canceled the (un)install");
        } else if (resultCode == RESULT_FIRST_USER) {
            Log.d("TAG", "onActivityResult: failed to (un)install");
        }
    }
}
Пир Фахим Шах
источник
как я могу подтвердить из этого диалогового окна действия, что пользователь нажал кнопку ОК или Отмена, чтобы я мог принять решение на основе этого
Эрам
2
@Erum Я добавил пример того, что вы спросили
Алекс Липов
При установке кнопка отмены не возвращала результат обратно к методу
onActivityResult
2
Начиная с API 25 для вызова ACTION_INSTALL_PACKAGEпотребуется REQUEST_INSTALL_PACKAGESразрешение уровня подписи . Точно так же, начиная с API 28 (Android P), для вызова ACTION_UNINSTALL_PACKAGEпотребуется неопасное REQUEST_DELETE_PACKAGESразрешение. По крайней мере, согласно документам.
Стив Блэквелл
24

Если у вас есть разрешение владельца устройства (или владельца профиля, я не пробовал), вы можете автоматически устанавливать / удалять пакеты с помощью API владельца устройства.

для удаления:

public boolean uninstallPackage(Context context, String packageName) {
    ComponentName name = new ComponentName(MyAppName, MyDeviceAdminReceiver.class.getCanonicalName());
    PackageManager packageManger = context.getPackageManager();
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        PackageInstaller packageInstaller = packageManger.getPackageInstaller();
        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
        params.setAppPackageName(packageName);
        int sessionId = 0;
        try {
            sessionId = packageInstaller.createSession(params);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        packageInstaller.uninstall(packageName, PendingIntent.getBroadcast(context, sessionId,
                new Intent("android.intent.action.MAIN"), 0).getIntentSender());
        return true;
    }
    System.err.println("old sdk");
    return false;
}

и установить пакет:

public boolean installPackage(Context context,
                                     String packageName, String packagePath) {
    ComponentName name = new ComponentName(MyAppName, MyDeviceAdminReceiver.class.getCanonicalName());
    PackageManager packageManger = context.getPackageManager();
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        PackageInstaller packageInstaller = packageManger.getPackageInstaller();
        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
        params.setAppPackageName(packageName);
        try {
            int sessionId = packageInstaller.createSession(params);
            PackageInstaller.Session session = packageInstaller.openSession(sessionId);
            OutputStream out = session.openWrite(packageName + ".apk", 0, -1);
            readTo(packagePath, out); //read the apk content and write it to out
            session.fsync(out);
            out.close();
            System.out.println("installing...");
            session.commit(PendingIntent.getBroadcast(context, sessionId,
                    new Intent("android.intent.action.MAIN"), 0).getIntentSender());
            System.out.println("install request sent");
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }
    System.err.println("old sdk");
    return false;
}
Охад Коэн
источник
Я знал, что это должно быть возможно, будучи владельцем устройства. Спасибо за ответ!
Люк Каутен,
@sandeep, он просто считывает содержимое APK в выходной поток
Охад Коэн,
@LukeCauthen вы пробовали стать владельцем устройства? это сработало?
NetStarter,
@NetStarter Да, есть. Получить статус приложения в качестве владельца устройства - просто головная боль. Как только вы это сделаете, вы получите много возможностей, для которых обычно требуется root.
Люк
1
Обратите внимание, что вы должны добавить android.permission.DELETE_PACKAGES в свой манифест, чтобы удаление работало (проверено на уровне Api 22 или ниже)
benchuk
4

Единственный способ получить доступ к этим методам - ​​это отражение. Вы можете получить дескриптор PackageManagerобъекта, вызвав getApplicationContext().getPackageManager()и используя эти методы с помощью отражения. Ознакомьтесь с этим руководством.

HandlerExploit
источник
Это отлично работает с 2.2, но мне не повезло с 2.3
Someone Somewhere
3
Отражение нестабильно во всех версиях api
HandlerExploit 02
3

Согласно исходному коду Froyo, дополнительный ключ Intent.EXTRA_INSTALLER_PACKAGE_NAME запрашивается для имени пакета установщика в PackageInstallerActivity.

njzk2
источник
1
Глядя на этот коммит, я думаю, что он должен сработать
sergio91pt
2

На корневом устройстве вы можете использовать:

String pkg = context.getPackageName();
String shellCmd = "rm -r /data/app/" + pkg + "*.apk\n"
                + "rm -r /data/data/" + pkg + "\n"
                // TODO remove data on the sd card
                + "sync\n"
                + "reboot\n";
Util.sudo(shellCmd);

Util.sudo() определяется здесь.

18446744073709551615
источник
Есть ли способ установить предварительно загруженное приложение на sdcard? Или вы можете предложить мне перейти на какую-нибудь страницу, чтобы проверить, какие команды мы можем использовать в оболочке на платформе Android?
yahya
1
@yahya developer.android.com/tools/help/shell.html найдено по фразе "pm android", pm = package manager
18446744073709551615,
1
@yahya cheatography.com/citguy/cheat-sheets/android-package-manager-pm set-install-location
18446744073709551615
Большое спасибо! Эти ссылки - действительно отличное руководство для начала :)
yahya
@ V.Kalyuzhnyu Работало еще в 2015 году. IIRC это был Samsung Galaxy, может, S5.
18446744073709551615
2

Если вы передаете имя пакета в качестве параметра любой из ваших пользовательских функций, используйте следующий код:

    Intent intent=new Intent(Intent.ACTION_DELETE);
    intent.setData(Uri.parse("package:"+packageName));
    startActivity(intent);
Рашвин С.М.
источник
0

Если вы используете Kotlin, API 14+ и просто хотите показать диалоговое окно удаления для своего приложения:

startActivity(Intent(Intent.ACTION_UNINSTALL_PACKAGE).apply {
    data = Uri.parse("package:$packageName")
})

Вы можете изменить packageNameимя пакета на любое другое, если хотите предложить пользователю удалить другое приложение на устройстве.

Луи CAD
источник
0

Предпосылка:

Ваш APK должен быть подписан системой, как правильно указывалось ранее. Один из способов добиться этого - самостоятельно создать образ AOSP и добавить исходный код в сборку.

Код:

После установки в качестве системного приложения вы можете использовать методы диспетчера пакетов для установки и удаления APK следующим образом:

Установить:

public boolean install(final String apkPath, final Context context) {
    Log.d(TAG, "Installing apk at " + apkPath);
    try {
        final Uri apkUri = Uri.fromFile(new File(apkPath));
        final String installerPackageName = "MyInstaller";
        context.getPackageManager().installPackage(apkUri, installObserver, PackageManager.INSTALL_REPLACE_EXISTING, installerPackageName);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

Удалить:

public boolean uninstall(final String packageName, final Context context) {
    Log.d(TAG, "Uninstalling package " + packageName);
    try {
        context.getPackageManager().deletePackage(packageName, deleteObserver, PackageManager.DELETE_ALL_USERS);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

Чтобы получить обратный вызов после установки / удаления APK, вы можете использовать это:

/**
 * Callback after a package was installed be it success or failure.
 */
private class InstallObserver implements IPackageInstallObserver {

    @Override
    public void packageInstalled(String packageName, int returnCode) throws RemoteException {

        if (packageName != null) {
            Log.d(TAG, "Successfully installed package " + packageName);
            callback.onAppInstalled(true, packageName);
        } else {
            Log.e(TAG, "Failed to install package.");
            callback.onAppInstalled(false, null);
        }
    }

    @Override
    public IBinder asBinder() {
        return null;
    }
}

/**
 * Callback after a package was deleted be it success or failure.
 */
private class DeleteObserver implements IPackageDeleteObserver {

    @Override
    public void packageDeleted(String packageName, int returnCode) throws RemoteException {
        if (packageName != null) {
            Log.d(TAG, "Successfully uninstalled package " + packageName);
            callback.onAppUninstalled(true, packageName);
        } else {
            Log.e(TAG, "Failed to uninstall package.");
            callback.onAppUninstalled(false, null);
        }
    }

    @Override
    public IBinder asBinder() {
        return null;
    }
}

/**
 * Callback to give the flow back to the calling class.
 */
public interface InstallerCallback {
    void onAppInstalled(final boolean success, final String packageName);
    void onAppUninstalled(final boolean success, final String packageName);
}
Феб
источник