Попытка UNINSTALL_SHORTCUT, но ярлык не исчезнет

Я создал тестовую активность, которая устанавливает ярлык на экране Android Home. Когда вы нажимаете кнопку, действие должно удалить тот же самый ярлык, который только что был создан. Однако я ничего не делаю, чтобы удалить ярлык.

Вот код Java (ShortcutTest.java):

import java.net.URISyntaxException;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class ShortcutTest extends Activity {
    String shortcutUri;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        addShortcut(getBaseContext());

        Button button = (Button)findViewById(R.id.Button01);
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                removeShortcut(getBaseContext());
                finish();
            }
        });
    }

    public void addShortcut(Context context) {
        Intent shortcutIntent = new Intent();
        shortcutIntent.setClassName("com.telespree.android.client", "com.telespree.android.client.ShortcutTest");
        shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

        Intent intent = new Intent();
        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "ShortcutTest");
        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.drawable.icon));
        intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
        shortcutUri = intent.toUri(MODE_WORLD_WRITEABLE);
        context.sendBroadcast(intent);
    }

    public void removeShortcut(Context context) {
        Intent intent = null;
        try {
            intent = Intent.parseUri(shortcutUri, 0);
        } catch (URISyntaxException e) {
        }
        intent.setAction("com.android.launcher.permission.UNINSTALL_SHORTCUT");
        context.sendBroadcast(intent);
    }
}

Вот манифест:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.telespree.android.client"
      android:versionCode="1"
      android:versionName="1.0">

      <permission
        android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"
        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
        android:protectionLevel="normal"
        />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".ShortcutTest"
                  android:label="@string/app_name" android:theme="@android:style/Theme.Translucent">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

    <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
    <!-- 
    <uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/>
     -->

    <uses-sdk android:minSdkVersion="7" />

</manifest> 

Я почти уверен, что есть какая-то проблема с разрешениями, хотя я видел другие сообщения в Интернете, которые указывают, что это должно быть возможно.

Приветствуются любые советы.

Спасибо.

Ответ 1

DEPRECATED; KEPT SOLELY ДЛЯ ИСТОРИЧЕСКИХ ЦЕЛЕЙ

Этот ответ был опубликован в 2014 году, когда описанный метод опирался на функциональность, существующую на большинстве Android-устройств. Однако, как упоминалось Adrian-Costin Ţundrea, эта функциональность была удалено пару лет назад из Launcher3, который является пусковой установкой AOSP, на которой находится Google Now Launcher, 1. Сообщение фиксации сказало:

Удаление поддержки из-за ее неряшливого дизайна. Удаление ярлыка вызывает полную перезагрузку. Также у нас нет понятия владельца, поэтому любое приложение может удалить любой ярлык.

По состоянию на март 2017 года эта пусковая установка постепенно прекращается в пользу "Службы Google Search Launcher Services", что означает, что производители могут интегрировать определенную библиотеку Google в свои собственные пусковые установки вместо того, чтобы полагаться на стандартную пусковую установку, предоставляемую Google.

Учитывая, что каждый производитель может свободно реализовать свою пусковую установку, какой бы они ни хотели, и предполагая, что некоторые из них основаны на Launcher3, трудно определить, на каких устройствах будет работать метод ниже, поскольку Launcher3 был запущен даже на некоторых Android 4.1, которые относятся к старым устройствам, которые все еще используются.


Привет!

Я только что рассмотрел ту же самую проблему и хотел бы поделиться своим опытом после успешного ее решения. tl; dr - перейдите к разделу "В заключение" ниже.

Некоторая предыстория:

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

Шаг 1: Настройка новой точки входа

Это самая легкая часть. Чтобы объявить точку входа , необходимо сделать только <action ...> тег <action ...> в соответствующем объявлении активности внутри вашего манифеста:

<activity
    android:name="YOUR_PACKAGE_NAME.YOUR_ACTIVITY_NAME"
    android:label="@string/app_name">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
    </intent-filter>
</activity>

То, что делает точку входа по умолчанию в некотором смысле, заключается в том, что на нее указывает ярлык запуска. Вот почему разработчики обычно также включают это в <intent-filter>:

<category android:name="android.intent.category.LAUNCHER"/>

Следует отметить, что каждое действие, которое имеет это в <intent-filter> , будет создавать элемент в вашем ящике приложения - вот почему для большинства случаев вам нужен только один экземпляр.

Шаг 2. Выяснение того, как работает старый ярлык

Имея корневое устройство, я мог получить доступ к таблице базы данных, где хранятся элементы запуска/рабочего стола/рабочего стола (см. изображение того, как выглядят записи SQLite ), который находится в:

/data/data/com.android.launcher/databases/launcher.db -> SELECT * FROM favorites`

Здесь более читаемая версия выделенной записи из изображения:

#Intent;
    action=android.intent.action.MAIN;
    category=android.intent.category.LAUNCHER;
    launchFlags=0x10200000;
    package=gidutz.soft.bluecard;
    component=gidutz.soft.bluecard/.LoadingScreen;
 end

Обратите внимание на 0x10200000 - это объясняется в Шаг 4 - Попытка 1 ниже.

Шаг 3: Выясните, что ожидает Uninstaller Shortcut

Строки 38-42 в UninstallShortcutReceiver.java говорят нам, что:

Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);

if (intent != null && name != null) { ... }

Значение "намерение удаления" должно иметь как Intent.EXTRA_SHORTCUT_INTENT, так и Intent.EXTRA_SHORTCUT_NAME, иначе он даже не рассмотрит выполнение.

Шаг 4: Поиск правильного синтаксиса

Это случай пробной ошибки со счастливым концом.

Попытка 1: Реконструкция намерения

Intent oldShortcutIntent = new Intent();
oldShortcutIntent.setAction(Intent.ACTION_MAIN);
oldShortcutIntent.addCategory(Intent.CATEGORY_LAUNCHER);
oldShortcutIntent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED +
                           Intent.FLAG_ACTIVITY_NEW_TASK);
oldShortcutIntent.setPackage("gidutz.soft.bluecard");
oldShortcutIntent.setComponent(new ComponentName("gidutz.soft.bluecard",
                                                     ".LoadingScreen"));
//  The above line is equivalent to:
Intent oldShortcutIntent = new Intent(getApplicationContext(),LoadingScreen.class);
Intent uninstaller = new Intent();
uninstaller.putExtra(Intent.EXTRA_SHORTCUT_INTENT, oldShortcutIntent);
uninstaller.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Blue Card");
uninstaller.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
getApplicationContext().sendBroadcast(uninstaller);

Результат: Значок не удален. 0x10200000 на самом деле представляет собой сумму двух аргументов, как описано здесь.

Попытка 2: Использование кода as-is из viralpatel

Intent shortcutIntent = new Intent(getApplicationContext(),LoadingScreen.class);
shortcutIntent.setAction(Intent.ACTION_MAIN);

Intent addIntent = new Intent();
addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Blue Card");

addIntent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
getApplicationContext().sendBroadcast(addIntent);

Результат: Иконка не удалена.

Попытка 3: "Грубая сила"

Попытка скопировать-вставить намерение точно так же, как оно появляется в файле launcher.db:

Intent intent = new Intent();
String oldShortcutUri = "#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;package=gidutz.soft.bluecard;component=gidutz.soft.bluecard/.LoadingScreen;end";
try {
    Intent altShortcutIntent  = Intent.parseUri(oldShortcutUri,0);
    intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, altShortcutIntent);
    intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Blue Card");
} catch (URISyntaxException e) {
}
intent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
getApplicationContext().sendBroadcast(intent);

Результат: Удалена Icon!

Заключение

  • Убедитесь, что ваш намерение "Иконки Uninstaller" использует тот же URI, который используется для создания значка, который вы пытаетесь удалить, либо сохраняя URI, который использовался для его создания, либо, получив его из launcher.db.
  • Подождите около 2-3 секунд, пока не появится значок "icon removed".

Источники

1) Это руководство на viralpatel.net

2) Реализация Google UninstallShortcutReceiver.java

3) Этот поток в xdadevelopers

P.S.

Чтобы смоделировать и отладить обновление Google Play (которое хранит старый ярлык), я сделал следующее:

  • Установлена ​​старая версия приложения из магазина - значок со старым ярлыком был автоматически помещен на мой экран.
  • Резервное копирование my launcher.db с помощью Total Commander.
  • Установил новую версию через мою IDE (для этого также можно использовать .apk) - теперь "старый ярлык" исчез.
  • Открыл Total Commander и минимизировал его (чтобы в меню "ALT-TAB" был доступен ярлык).
  • Пошел на Настройки устройства → Приложения → ALL, нашел мою пусковую установку (для меня это было "Trebuchet", так как я на CM11), и Force остановила его.
  • ALT-TAB в Total Commander и восстановил DB.
  • Нажмите кнопку "home" для перезапуска запуска.
  • Viola! Теперь был восстановлен старый ярлык.

Примечание1. В ретроспективе было бы проще создать старый ярлык вручную, используя URI, полученный из базы данных, вместо того, чтобы проходить все испытания на резервное копирование и принудительное прекращение.

Примечание2: Я не пробовал удалять иконки, принадлежащие другим приложениям, используя этот метод, но это может быть просто безумно достаточно для работы.

Ответ 2

В то время как оба решения от Dev-iL и Funt работают, они делают это до Marshmallow. С Android 6.0 (с Launcher v3) Google удалил UninstallShortcutReceiver из-за его проблем с безопасностью (возможно, потому, что стало очевидно здесь). Поэтому не ожидайте, что он будет работать с Android 6.0. Надеюсь, в какой-то будущей версии он будет прочтён в той или иной форме.

PS: Обычно это должен быть комментарий, но мне не разрешено комментировать из-за репутации...

Ответ 3

Для shortcutIntent вам нужно setAction:

shortcutIntent.setAction(Intent.ACTION_MAIN);

Ответ 4

Попробуйте использовать

public void removeShortcut(Context context) {
        Intent intent = new Intent();

        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "ShortcutTest");

        try {
            Intent shortcutIntent = Intent.parseUri(shortcutUri, 0);
            intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
        } catch (URISyntaxException e) {
        }
        intent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
        context.sendBroadcast(intent);
    }

Примечание. Чтобы удалить ярлык, вам не нужно сохранять shortcutUri. Вместо этого вы можете использовать

Intent shortcutIntent = new Intent();
shortcutIntent.setClassName("com.telespree.android.client",
        "com.telespree.android.client.ShortcutTest");
shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

Intent intent = new Intent();
try {
    intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT,
            Intent.parseUri(shortcutIntent.toUri(0), 0));
} catch (URISyntaxException e) {
    e.printStackTrace();
}
...
intent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
context.sendBroadcast(intent);

Если вы хотите использовать intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); вместо

intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT,
                Intent.parseUri(shortcutIntent.toUri(0), 0));

тогда вам нужно каждый раз устанавливать действие для shortcutIntent, то есть при установке, а также при удалении, например. Intent shortcutIntent = new Intent(Intent.ACTION_MAIN);

Ответ 5

Мне потребовался час отладки и тестирования каждого примера в stackoverflow, но решение было очень простым. В коде есть опечатка: вам нужно использовать com.android.launcher. действие.UNINSTALL_SHORTCUT (в отличие от разрешения, как это показано в манифесте)

intent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");

Изменить: ответ изменен