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