1 package com.android.launcher3;
2 
3 import android.content.ComponentName;
4 import android.content.Context;
5 import android.content.Intent;
6 import android.content.pm.ApplicationInfo;
7 import android.content.pm.LauncherActivityInfo;
8 import android.content.pm.PackageManager;
9 import android.net.Uri;
10 import android.os.Bundle;
11 import android.os.UserHandle;
12 import android.os.UserManager;
13 import android.util.AttributeSet;
14 import android.widget.Toast;
15 
16 import com.android.launcher3.compat.LauncherAppsCompat;
17 
18 public class UninstallDropTarget extends ButtonDropTarget {
19 
UninstallDropTarget(Context context, AttributeSet attrs)20     public UninstallDropTarget(Context context, AttributeSet attrs) {
21         this(context, attrs, 0);
22     }
23 
UninstallDropTarget(Context context, AttributeSet attrs, int defStyle)24     public UninstallDropTarget(Context context, AttributeSet attrs, int defStyle) {
25         super(context, attrs, defStyle);
26     }
27 
28     @Override
onFinishInflate()29     protected void onFinishInflate() {
30         super.onFinishInflate();
31         // Get the hover color
32         mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint);
33 
34         setDrawable(R.drawable.ic_uninstall_launcher);
35     }
36 
37     @Override
supportsDrop(DragSource source, ItemInfo info)38     protected boolean supportsDrop(DragSource source, ItemInfo info) {
39         return supportsDrop(getContext(), info);
40     }
41 
supportsDrop(Context context, Object info)42     public static boolean supportsDrop(Context context, Object info) {
43         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
44         Bundle restrictions = userManager.getUserRestrictions();
45         if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
46                 || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false)) {
47             return false;
48         }
49 
50         return getUninstallTarget(context, info) != null;
51     }
52 
53     /**
54      * @return the component name that should be uninstalled or null.
55      */
getUninstallTarget(Context context, Object item)56     private static ComponentName getUninstallTarget(Context context, Object item) {
57         Intent intent = null;
58         UserHandle user = null;
59         if (item instanceof AppInfo) {
60             AppInfo info = (AppInfo) item;
61             intent = info.intent;
62             user = info.user;
63         } else if (item instanceof ShortcutInfo) {
64             ShortcutInfo info = (ShortcutInfo) item;
65             if (info.itemType == LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION) {
66                 // Do not use restore/target intent here as we cannot uninstall an app which is
67                 // being installed/restored.
68                 intent = info.intent;
69                 user = info.user;
70             }
71         }
72         if (intent != null) {
73             LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
74                     .resolveActivity(intent, user);
75             if (info != null
76                     && (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
77                 return info.getComponentName();
78             }
79         }
80         return null;
81     }
82 
83     @Override
onDrop(DragObject d)84     public void onDrop(DragObject d) {
85         // Differ item deletion
86         if (d.dragSource instanceof DropTargetSource) {
87             ((DropTargetSource) d.dragSource).deferCompleteDropAfterUninstallActivity();
88         }
89         super.onDrop(d);
90     }
91 
92     @Override
completeDrop(final DragObject d)93     public void completeDrop(final DragObject d) {
94         DropTargetResultCallback callback = d.dragSource instanceof DropTargetResultCallback
95                 ? (DropTargetResultCallback) d.dragSource : null;
96         startUninstallActivity(mLauncher, d.dragInfo, callback);
97     }
98 
startUninstallActivity(Launcher launcher, ItemInfo info)99     public static boolean startUninstallActivity(Launcher launcher, ItemInfo info) {
100         return startUninstallActivity(launcher, info, null);
101     }
102 
startUninstallActivity( final Launcher launcher, ItemInfo info, DropTargetResultCallback callback)103     public static boolean startUninstallActivity(
104             final Launcher launcher, ItemInfo info, DropTargetResultCallback callback) {
105         final ComponentName cn = getUninstallTarget(launcher, info);
106 
107         final boolean isUninstallable;
108         if (cn == null) {
109             // System applications cannot be installed. For now, show a toast explaining that.
110             // We may give them the option of disabling apps this way.
111             Toast.makeText(launcher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
112             isUninstallable = false;
113         } else {
114             Intent intent = new Intent(Intent.ACTION_DELETE,
115                     Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
116                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
117                             | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
118             intent.putExtra(Intent.EXTRA_USER, info.user);
119             launcher.startActivity(intent);
120             isUninstallable = true;
121         }
122         if (callback != null) {
123             sendUninstallResult(launcher, isUninstallable, cn, info.user, callback);
124         }
125         return isUninstallable;
126     }
127 
128     /**
129      * Notifies the {@param callback} whether the uninstall was successful or not.
130      *
131      * Since there is no direct callback for an uninstall request, we check the package existence
132      * when the launch resumes next time. This assumes that the uninstall activity will finish only
133      * after the task is completed
134      */
sendUninstallResult( final Launcher launcher, boolean activityStarted, final ComponentName cn, final UserHandle user, final DropTargetResultCallback callback)135     protected static void sendUninstallResult(
136             final Launcher launcher, boolean activityStarted,
137             final ComponentName cn, final UserHandle user,
138             final DropTargetResultCallback callback) {
139         if (activityStarted)  {
140             final Runnable checkIfUninstallWasSuccess = new Runnable() {
141                 @Override
142                 public void run() {
143                     // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
144                     boolean uninstallSuccessful = LauncherAppsCompat.getInstance(launcher)
145                             .getApplicationInfo(cn.getPackageName(),
146                                     PackageManager.MATCH_UNINSTALLED_PACKAGES, user) == null;
147                     callback.onDragObjectRemoved(uninstallSuccessful);
148                 }
149             };
150             launcher.addOnResumeCallback(checkIfUninstallWasSuccess);
151         } else {
152             callback.onDragObjectRemoved(false);
153         }
154     }
155 
156     public interface DropTargetResultCallback {
157         /**
158          * A drag operation was complete.
159          * @param isRemoved true if the drag object should be removed, false otherwise.
160          */
onDragObjectRemoved(boolean isRemoved)161         void onDragObjectRemoved(boolean isRemoved);
162     }
163 
164     /**
165      * Interface defining an object that can provide uninstallable drag objects.
166      */
167     public interface DropTargetSource extends DropTargetResultCallback {
168 
169         /**
170          * Indicates that an uninstall request are made and the actual result may come
171          * after some time.
172          */
deferCompleteDropAfterUninstallActivity()173         void deferCompleteDropAfterUninstallActivity();
174     }
175 }
176