Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to UNINSTALL_SHORTCUT but shortcut won't go away

I created a test Activity that installs a shortcut of itself on the Android Home screen. When you click a button, the Activity is supposed to remove the same shortcut it just created. However, nothing I do seems to delete the shortcut.

Here is the Java code (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);
    }
}

Here is the Manifest:

<?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> 

I'm almost positive there is some kind of permissions problem, though I've seen other posts on the Internet that indicates this should be possible.

Any advice is greatly appreciated.

Thanks.

like image 233
Hunter D Avatar asked Aug 13 '10 19:08

Hunter D


2 Answers

DEPRECATED; KEPT SOLELY FOR HISTORICAL PURPOSES

This answer was posted in 2014, when the described method relied on functionality that existed in most Android devices. However, as mentioned by Adrian-Costin Țundrea, this functionality was removed a couple of years ago from Launcher3, which is the AOSP launcher upon which the Google Now Launcher is based1. The commit message said:

Removing support due to its flacky design. Removing a shortcut causes a full reload. Also we do not have any concept of owner, so any app can remove any shortcut.

As of March 2017, this launcher, too, is being phased out in favor of "Google Search Launcher Services", which means that manufacturers could integrate a certain Google library into their own custom launchers, instead of relying on a standardized launcher provided by Google.

Considering that each manufacturer is free to implement their launcher whichever way they want, and assuming some of them are based off Launcher3, it's difficult to tell which devices the method below will work on, as Launcher3 was made to run even on some Android 4.1 devices, which are among the oldest devices still in use.


Greetings!

I have just dealt with the same exact problem, and would like to share my experience after successfully resolving it. tl;dr - skip to "In Conclusion" below.

Some background:

While working on the "next version" of an app, a need arose to change the default entry point (i.e. to rename the "Main Activity"). This is frowned upon because users who would be upgrading from an old version will still have the old shortcut, pointing to the wrong place. In order to avoid problems as much as possible, on the first launch, unbeknownst to them, the old shortcut was to be replaced with a new one.

Step 1: Setting up a new entry point

This is the easiest part. To declare an entry point the only essential thing to do is to put the following <action ...> tag in the appropriate activity declaration inside your Manifest:

<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>

What makes an entry point default in some sense, is that the launcher shortcut points to it. This is why developers usually also include this in the <intent-filter>:

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

It should be noted that every activity that has this in its <intent-filter> will create an item in your app drawer - this is why for most cases 1 instance is all you need.

Step 2: Figuring out how the old shortcut is working

Having a rooted device, I could access the database table where the launcher/homescreen/desktop items are stored (see image of what the SQLite entries looks like) that's located in:

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

Here's a more readable version of the highlighted entry from the image:

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

Note the 0x10200000 - this is explained in Step 4 - Attempt 1 below.

Step 3: Figuring out what the Shortcut Uninstaller is expecting

Lines 38-42 in UninstallShortcutReceiver.java tell us that:

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) { ... }

Meaning that the "uninstallation intent" has to have both Intent.EXTRA_SHORTCUT_INTENT and Intent.EXTRA_SHORTCUT_NAME or else it will not even consider executing.

Step 4: Finding the Right Syntax

This is a case of trial an error with a happy ending.

Attempt 1: Reconstructing the intent

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);

Result: Icon not removed. The 0x10200000 is actually a sum of two arguments as explained here.

Attempt 2: Using as-is code from 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);

Result: Icon not removed.

Attempt 3: "Brute Force"

Trying to copy-paste the intent exactly as it appears in the 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);

Result: Icon removed!!

In Conclusion

  1. Make sure that your "Icon Uninstaller" intent uses the exact same URI used to create the icon you're trying to delete, by either storing the URI used to create it, or by obtaining it from launcher.db.
  2. Wait about 2-3 seconds for the "icon removed" toast to appear.

Sources

1) This guide at viralpatel.net

2) Google's implementation of UninstallShortcutReceiver.java

3) This thread at xdadevelopers

P.S.

In order to simulate and debug a Google Play update (which keeps the old shortcut) I did the following:

  1. Installed the old version of the app from the store - an icon with the "old shortcut" was automatically placed on my screen.
  2. Backed-up my launcher.db using Total Commander.
  3. Installed the new version through my IDE (you can also use an .apk for that) - the "old shortcut" was now gone.
  4. Opened Total Commander and minimized it (so that a shortcut is available in the "ALT-TAB" menu).
  5. Went to the Device Settings >> Apps >> ALL, found my launcher (for me it was "Trebuchet" since I'm on CM11) and Force stopped it.
  6. ALT-TAB into Total Commander and restored the DB.
  7. Clicked the hardware "home" button to re-launch the launcher.
  8. Viola! The old shortcut was now restored.

Note1: In retrospective, it might have been easier to create the old shortcut manually using the URI obtained from the database instead of going through all backing-up and force-stopping ordeal.

Note2: I haven't tried removing icons belonging to other apps using this method, but it might just be crazy enough to work.

like image 94
Dev-iL Avatar answered Nov 15 '22 22:11

Dev-iL


While both solutions from Dev-iL and Funt work be advised they do so until Marshmallow. With Android 6.0 (which has Launcher v3) Google has removed the UninstallShortcutReceiver because of its security problems (probably because it became apparent here). So do not expect it to work with Android 6.0. Hopefully in some future release it will be readded in a form or another.

PS: Normally this should be a comment, but I am not allowed to comment because of the reputation...

like image 43
Adrian-Costin Țundrea Avatar answered Nov 15 '22 22:11

Adrian-Costin Țundrea