Home | History | Annotate | Download | only in launcher3
      1 package com.android.launcher3;
      2 
      3 import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
      4 import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE;
      5 
      6 import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
      7 import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
      8 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
      9 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE;
     10 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
     11 
     12 import android.appwidget.AppWidgetHostView;
     13 import android.appwidget.AppWidgetManager;
     14 import android.appwidget.AppWidgetProviderInfo;
     15 import android.content.ComponentName;
     16 import android.content.Context;
     17 import android.content.Intent;
     18 import android.content.pm.ApplicationInfo;
     19 import android.content.pm.LauncherActivityInfo;
     20 import android.content.pm.PackageManager;
     21 import android.net.Uri;
     22 import android.os.Bundle;
     23 import android.os.UserHandle;
     24 import android.os.UserManager;
     25 import android.util.ArrayMap;
     26 import android.util.AttributeSet;
     27 import android.util.Log;
     28 import android.view.View;
     29 import android.widget.Toast;
     30 
     31 import com.android.launcher3.Launcher.OnResumeCallback;
     32 import com.android.launcher3.compat.LauncherAppsCompat;
     33 import com.android.launcher3.dragndrop.DragOptions;
     34 import com.android.launcher3.logging.LoggerUtils;
     35 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
     36 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
     37 import com.android.launcher3.util.Themes;
     38 
     39 import java.net.URISyntaxException;
     40 
     41 /**
     42  * Drop target which provides a secondary option for an item.
     43  *    For app targets: shows as uninstall
     44  *    For configurable widgets: shows as setup
     45  */
     46 public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmListener {
     47 
     48     private static final String TAG = "SecondaryDropTarget";
     49 
     50     private static final long CACHE_EXPIRE_TIMEOUT = 5000;
     51     private final ArrayMap<UserHandle, Boolean> mUninstallDisabledCache = new ArrayMap<>(1);
     52 
     53     private final Alarm mCacheExpireAlarm;
     54 
     55     protected int mCurrentAccessibilityAction = -1;
     56     public SecondaryDropTarget(Context context, AttributeSet attrs) {
     57         this(context, attrs, 0);
     58     }
     59 
     60     public SecondaryDropTarget(Context context, AttributeSet attrs, int defStyle) {
     61         super(context, attrs, defStyle);
     62 
     63         mCacheExpireAlarm = new Alarm();
     64         mCacheExpireAlarm.setOnAlarmListener(this);
     65     }
     66 
     67     @Override
     68     protected void onFinishInflate() {
     69         super.onFinishInflate();
     70         setupUi(UNINSTALL);
     71     }
     72 
     73     protected void setupUi(int action) {
     74         if (action == mCurrentAccessibilityAction) {
     75             return;
     76         }
     77         mCurrentAccessibilityAction = action;
     78 
     79         if (action == UNINSTALL) {
     80             mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint);
     81             setDrawable(R.drawable.ic_uninstall_shadow);
     82             updateText(R.string.uninstall_drop_target_label);
     83         } else {
     84             mHoverColor = Themes.getColorAccent(getContext());
     85             setDrawable(R.drawable.ic_setup_shadow);
     86             updateText(R.string.gadget_setup_text);
     87         }
     88     }
     89 
     90     @Override
     91     public void onAlarm(Alarm alarm) {
     92         mUninstallDisabledCache.clear();
     93     }
     94 
     95     @Override
     96     public int getAccessibilityAction() {
     97         return mCurrentAccessibilityAction;
     98     }
     99 
    100     @Override
    101     public Target getDropTargetForLogging() {
    102         Target t = LoggerUtils.newTarget(Target.Type.CONTROL);
    103         t.controlType = mCurrentAccessibilityAction == UNINSTALL ? ControlType.UNINSTALL_TARGET
    104                 : ControlType.SETTINGS_BUTTON;
    105         return t;
    106     }
    107 
    108     @Override
    109     protected boolean supportsDrop(ItemInfo info) {
    110         return supportsAccessibilityDrop(info, getViewUnderDrag(info));
    111     }
    112 
    113     @Override
    114     public boolean supportsAccessibilityDrop(ItemInfo info, View view) {
    115         if (view instanceof AppWidgetHostView) {
    116             if (getReconfigurableWidgetId(view) != INVALID_APPWIDGET_ID) {
    117                 setupUi(RECONFIGURE);
    118                 return true;
    119             }
    120             return false;
    121         }
    122 
    123         setupUi(UNINSTALL);
    124         Boolean uninstallDisabled = mUninstallDisabledCache.get(info.user);
    125         if (uninstallDisabled == null) {
    126             UserManager userManager =
    127                     (UserManager) getContext().getSystemService(Context.USER_SERVICE);
    128             Bundle restrictions = userManager.getUserRestrictions(info.user);
    129             uninstallDisabled = restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
    130                     || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false);
    131             mUninstallDisabledCache.put(info.user, uninstallDisabled);
    132         }
    133         // Cancel any pending alarm and set cache expiry after some time
    134         mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT);
    135         if (uninstallDisabled) {
    136             return false;
    137         }
    138 
    139         if (info instanceof ItemInfoWithIcon) {
    140             ItemInfoWithIcon iconInfo = (ItemInfoWithIcon) info;
    141             if ((iconInfo.runtimeStatusFlags & FLAG_SYSTEM_MASK) != 0) {
    142                 return (iconInfo.runtimeStatusFlags & FLAG_SYSTEM_NO) != 0;
    143             }
    144         }
    145         return getUninstallTarget(info) != null;
    146     }
    147 
    148     /**
    149      * @return the component name that should be uninstalled or null.
    150      */
    151     private ComponentName getUninstallTarget(ItemInfo item) {
    152         Intent intent = null;
    153         UserHandle user = null;
    154         if (item != null &&
    155                 item.itemType == LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION) {
    156             intent = item.getIntent();
    157             user = item.user;
    158         }
    159         if (intent != null) {
    160             LauncherActivityInfo info = LauncherAppsCompat.getInstance(mLauncher)
    161                     .resolveActivity(intent, user);
    162             if (info != null
    163                     && (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
    164                 return info.getComponentName();
    165             }
    166         }
    167         return null;
    168     }
    169 
    170     @Override
    171     public void onDrop(DragObject d, DragOptions options) {
    172         // Defer onComplete
    173         d.dragSource = new DeferredOnComplete(d.dragSource, getContext());
    174         super.onDrop(d, options);
    175     }
    176 
    177     @Override
    178     public void completeDrop(final DragObject d) {
    179         ComponentName target = performDropAction(getViewUnderDrag(d.dragInfo), d.dragInfo);
    180         if (d.dragSource instanceof DeferredOnComplete) {
    181             DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
    182             if (target != null) {
    183                 deferred.mPackageName = target.getPackageName();
    184                 mLauncher.setOnResumeCallback(deferred);
    185             } else {
    186                 deferred.sendFailure();
    187             }
    188         }
    189     }
    190 
    191     private View getViewUnderDrag(ItemInfo info) {
    192         if (info instanceof LauncherAppWidgetInfo && info.container == CONTAINER_DESKTOP &&
    193                 mLauncher.getWorkspace().getDragInfo() != null) {
    194             return mLauncher.getWorkspace().getDragInfo().cell;
    195         }
    196         return null;
    197     }
    198 
    199     /**
    200      * Verifies that the view is an reconfigurable widget and returns the corresponding widget Id,
    201      * otherwise return {@code INVALID_APPWIDGET_ID}
    202      */
    203     private int getReconfigurableWidgetId(View view) {
    204         if (!(view instanceof AppWidgetHostView)) {
    205             return INVALID_APPWIDGET_ID;
    206         }
    207         AppWidgetHostView hostView = (AppWidgetHostView) view;
    208         AppWidgetProviderInfo widgetInfo = hostView.getAppWidgetInfo();
    209         if (widgetInfo == null || widgetInfo.configure == null) {
    210             return INVALID_APPWIDGET_ID;
    211         }
    212         if ( (LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(), widgetInfo)
    213                 .getWidgetFeatures() & WIDGET_FEATURE_RECONFIGURABLE) == 0) {
    214             return INVALID_APPWIDGET_ID;
    215         }
    216         return hostView.getAppWidgetId();
    217     }
    218 
    219     /**
    220      * Performs the drop action and returns the target component for the dragObject or null if
    221      * the action was not performed.
    222      */
    223     protected ComponentName performDropAction(View view, ItemInfo info) {
    224         if (mCurrentAccessibilityAction == RECONFIGURE) {
    225             int widgetId = getReconfigurableWidgetId(view);
    226             if (widgetId != INVALID_APPWIDGET_ID) {
    227                 mLauncher.getAppWidgetHost().startConfigActivity(mLauncher, widgetId, -1);
    228             }
    229             return null;
    230         }
    231         // else: mCurrentAccessibilityAction == UNINSTALL
    232 
    233         ComponentName cn = getUninstallTarget(info);
    234         if (cn == null) {
    235             // System applications cannot be installed. For now, show a toast explaining that.
    236             // We may give them the option of disabling apps this way.
    237             Toast.makeText(mLauncher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
    238             return null;
    239         }
    240         try {
    241             Intent i = Intent.parseUri(mLauncher.getString(R.string.delete_package_intent), 0)
    242                     .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
    243                     .putExtra(Intent.EXTRA_USER, info.user);
    244             mLauncher.startActivity(i);
    245             return cn;
    246         } catch (URISyntaxException e) {
    247             Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);
    248             return null;
    249         }
    250     }
    251 
    252     @Override
    253     public void onAccessibilityDrop(View view, ItemInfo item) {
    254         performDropAction(view, item);
    255     }
    256 
    257     /**
    258      * A wrapper around {@link DragSource} which delays the {@link #onDropCompleted} action until
    259      * {@link #onLauncherResume}
    260      */
    261     private class DeferredOnComplete implements DragSource, OnResumeCallback {
    262 
    263         private final DragSource mOriginal;
    264         private final Context mContext;
    265 
    266         private String mPackageName;
    267         private DragObject mDragObject;
    268 
    269         public DeferredOnComplete(DragSource original, Context context) {
    270             mOriginal = original;
    271             mContext = context;
    272         }
    273 
    274         @Override
    275         public void onDropCompleted(View target, DragObject d,
    276                 boolean success) {
    277             mDragObject = d;
    278         }
    279 
    280         @Override
    281         public void fillInLogContainerData(View v, ItemInfo info, Target target,
    282                 Target targetParent) {
    283             mOriginal.fillInLogContainerData(v, info, target, targetParent);
    284         }
    285 
    286         @Override
    287         public void onLauncherResume() {
    288             // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
    289             if (LauncherAppsCompat.getInstance(mContext)
    290                     .getApplicationInfo(mPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES,
    291                             mDragObject.dragInfo.user) == null) {
    292                 mDragObject.dragSource = mOriginal;
    293                 mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, true);
    294             } else {
    295                 sendFailure();
    296             }
    297         }
    298 
    299         public void sendFailure() {
    300             mDragObject.dragSource = mOriginal;
    301             mDragObject.cancelled = true;
    302             mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, false);
    303         }
    304     }
    305 }
    306