Revert "Revert "Adding support for hiding widgets from the widget tray and reconfiguring widgets""

This reverts commit f502e5faecbd460ad3b8258168c5812cbaa34c4e.

Reason for revert: Reverting post-build

Change-Id: I09eb292dbbbe37ffaf3abc477aa2ddb5700093b8
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
new file mode 100644
index 0000000..024b4eb
--- /dev/null
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -0,0 +1,302 @@
+package com.android.launcher3;
+
+import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE;
+
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE;
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.SETTINGS_BUTTON;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.UNINSTALL_TARGET;
+
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import com.android.launcher3.Launcher.OnResumeCallback;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.Themes;
+
+import java.net.URISyntaxException;
+
+/**
+ * Drop target which provides a secondary option for an item.
+ *    For app targets: shows as uninstall
+ *    For configurable widgets: shows as setup
+ */
+public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmListener {
+
+    private static final String TAG = "SecondaryDropTarget";
+
+    private static final long CACHE_EXPIRE_TIMEOUT = 5000;
+    private final ArrayMap<UserHandle, Boolean> mUninstallDisabledCache = new ArrayMap<>(1);
+
+    private final Alarm mCacheExpireAlarm;
+
+    private int mCurrentAccessibilityAction = -1;
+    public SecondaryDropTarget(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SecondaryDropTarget(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mCacheExpireAlarm = new Alarm();
+        mCacheExpireAlarm.setOnAlarmListener(this);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        setupUi(UNINSTALL);
+    }
+
+    private void setupUi(int action) {
+        if (action == mCurrentAccessibilityAction) {
+            return;
+        }
+        mCurrentAccessibilityAction = action;
+
+        if (action == UNINSTALL) {
+            mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint);
+            setDrawable(R.drawable.ic_uninstall_shadow);
+            updateText(R.string.uninstall_drop_target_label);
+        } else {
+            mHoverColor = Themes.getColorAccent(getContext());
+            setDrawable(R.drawable.ic_setup_shadow);
+            updateText(R.string.gadget_setup_text);
+        }
+    }
+
+    @Override
+    public void onAlarm(Alarm alarm) {
+        mUninstallDisabledCache.clear();
+    }
+
+    @Override
+    public int getAccessibilityAction() {
+        return mCurrentAccessibilityAction;
+    }
+
+    @Override
+    public int getControlTypeForLogging() {
+        return mCurrentAccessibilityAction == UNINSTALL ? UNINSTALL_TARGET : SETTINGS_BUTTON;
+    }
+
+    @Override
+    protected boolean supportsDrop(ItemInfo info) {
+        return supportsAccessibilityDrop(info, getViewUnderDrag(info));
+    }
+
+    @Override
+    public boolean supportsAccessibilityDrop(ItemInfo info, View view) {
+        if (view instanceof AppWidgetHostView) {
+            if (getReconfigurableWidgetId(view) != INVALID_APPWIDGET_ID) {
+                setupUi(RECONFIGURE);
+                return true;
+            }
+            return false;
+        }
+
+        setupUi(UNINSTALL);
+        Boolean uninstallDisabled = mUninstallDisabledCache.get(info.user);
+        if (uninstallDisabled == null) {
+            UserManager userManager =
+                    (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+            Bundle restrictions = userManager.getUserRestrictions(info.user);
+            uninstallDisabled = restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
+                    || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false);
+            mUninstallDisabledCache.put(info.user, uninstallDisabled);
+        }
+        // Cancel any pending alarm and set cache expiry after some time
+        mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT);
+        if (uninstallDisabled) {
+            return false;
+        }
+
+        if (info instanceof ItemInfoWithIcon) {
+            ItemInfoWithIcon iconInfo = (ItemInfoWithIcon) info;
+            if ((iconInfo.runtimeStatusFlags & FLAG_SYSTEM_MASK) != 0) {
+                return (iconInfo.runtimeStatusFlags & FLAG_SYSTEM_NO) != 0;
+            }
+        }
+        return getUninstallTarget(info) != null;
+    }
+
+    /**
+     * @return the component name that should be uninstalled or null.
+     */
+    private ComponentName getUninstallTarget(ItemInfo item) {
+        Intent intent = null;
+        UserHandle user = null;
+        if (item != null &&
+                item.itemType == LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION) {
+            intent = item.getIntent();
+            user = item.user;
+        }
+        if (intent != null) {
+            LauncherActivityInfo info = LauncherAppsCompat.getInstance(mLauncher)
+                    .resolveActivity(intent, user);
+            if (info != null
+                    && (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+                return info.getComponentName();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void onDrop(DragObject d, DragOptions options) {
+        // Defer onComplete
+        d.dragSource = new DeferredOnComplete(d.dragSource, getContext());
+        super.onDrop(d, options);
+    }
+
+    @Override
+    public void completeDrop(final DragObject d) {
+        ComponentName target = performDropAction(getViewUnderDrag(d.dragInfo), d.dragInfo);
+        if (d.dragSource instanceof DeferredOnComplete) {
+            DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
+            if (target != null) {
+                deferred.mPackageName = target.getPackageName();
+                mLauncher.setOnResumeCallback(deferred);
+            } else {
+                deferred.sendFailure();
+            }
+        }
+    }
+
+    private View getViewUnderDrag(ItemInfo info) {
+        if (info instanceof LauncherAppWidgetInfo && info.container == CONTAINER_DESKTOP &&
+                mLauncher.getWorkspace().getDragInfo() != null) {
+            return mLauncher.getWorkspace().getDragInfo().cell;
+        }
+        return null;
+    }
+
+    /**
+     * Verifies that the view is an reconfigurable widget and returns the corresponding widget Id,
+     * otherwise return {@code INVALID_APPWIDGET_ID}
+     */
+    private int getReconfigurableWidgetId(View view) {
+        if (!(view instanceof AppWidgetHostView)) {
+            return INVALID_APPWIDGET_ID;
+        }
+        AppWidgetHostView hostView = (AppWidgetHostView) view;
+        AppWidgetProviderInfo widgetInfo = hostView.getAppWidgetInfo();
+        if (widgetInfo == null || widgetInfo.configure == null) {
+            return INVALID_APPWIDGET_ID;
+        }
+        if ( (LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(), widgetInfo)
+                .getWidgetFeatures() & WIDGET_FEATURE_RECONFIGURABLE) == 0) {
+            return INVALID_APPWIDGET_ID;
+        }
+        return hostView.getAppWidgetId();
+    }
+
+    /**
+     * Performs the drop action and returns the target component for the dragObject or null if
+     * the action was not performed.
+     */
+    protected ComponentName performDropAction(View view, ItemInfo info) {
+        if (mCurrentAccessibilityAction == RECONFIGURE) {
+            int widgetId = getReconfigurableWidgetId(view);
+            if (widgetId != INVALID_APPWIDGET_ID) {
+                mLauncher.getAppWidgetHost().startConfigActivity(mLauncher, widgetId, -1);
+            }
+            return null;
+        }
+        // else: mCurrentAccessibilityAction == UNINSTALL
+
+        ComponentName cn = getUninstallTarget(info);
+        if (cn == null) {
+            // System applications cannot be installed. For now, show a toast explaining that.
+            // We may give them the option of disabling apps this way.
+            Toast.makeText(mLauncher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
+            return null;
+        }
+        try {
+            Intent i = Intent.parseUri(mLauncher.getString(R.string.delete_package_intent), 0)
+                    .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
+                    .putExtra(Intent.EXTRA_USER, info.user);
+            mLauncher.startActivity(i);
+            return cn;
+        } catch (URISyntaxException e) {
+            Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);
+            return null;
+        }
+    }
+
+    @Override
+    public void onAccessibilityDrop(View view, ItemInfo item) {
+        performDropAction(view, item);
+    }
+
+    /**
+     * A wrapper around {@link DragSource} which delays the {@link #onDropCompleted} action until
+     * {@link #onLauncherResume}
+     */
+    private class DeferredOnComplete implements DragSource, OnResumeCallback {
+
+        private final DragSource mOriginal;
+        private final Context mContext;
+
+        private String mPackageName;
+        private DragObject mDragObject;
+
+        public DeferredOnComplete(DragSource original, Context context) {
+            mOriginal = original;
+            mContext = context;
+        }
+
+        @Override
+        public void onDropCompleted(View target, DragObject d,
+                boolean success) {
+            mDragObject = d;
+        }
+
+        @Override
+        public void fillInLogContainerData(View v, ItemInfo info, Target target,
+                Target targetParent) {
+            mOriginal.fillInLogContainerData(v, info, target, targetParent);
+        }
+
+        @Override
+        public void onLauncherResume() {
+            // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
+            if (LauncherAppsCompat.getInstance(mContext)
+                    .getApplicationInfo(mPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES,
+                            mDragObject.dragInfo.user) == null) {
+                mDragObject.dragSource = mOriginal;
+                mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, true);
+            } else {
+                sendFailure();
+            }
+        }
+
+        public void sendFailure() {
+            mDragObject.dragSource = mOriginal;
+            mDragObject.cancelled = true;
+            mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, false);
+        }
+    }
+}