Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc7aea57 authored by Amit Kumar's avatar Amit Kumar 💻
Browse files

Add shortcut support

parent 1e4acffa
Loading
Loading
Loading
Loading
Loading
+6 −6
Original line number Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="deploymentTargetDropDown">
    <runningDeviceTargetSelectedWithDropDown>
    <targetSelectedWithDropDown>
      <Target>
        <type value="RUNNING_DEVICE_TARGET" />
        <type value="QUICK_BOOT_TARGET" />
        <deviceKey>
          <Key>
            <type value="SERIAL_NUMBER" />
            <value value="ZF65256BV3" />
            <type value="VIRTUAL_DEVICE_PATH" />
            <value value="$USER_HOME$/.android/avd/Pixel_3a_XL_API_29.avd" />
          </Key>
        </deviceKey>
      </Target>
    </runningDeviceTargetSelectedWithDropDown>
    <timeTargetWasSelectedWithDropDown value="2021-09-30T08:05:54.967084Z" />
    </targetSelectedWithDropDown>
    <timeTargetWasSelectedWithDropDown value="2021-09-30T08:57:06.401616Z" />
  </component>
</project>
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
@@ -90,7 +90,7 @@
            </intent-filter>
        </activity>
        <activity
            android:name=".features.shortcuts.AddItemActivity"
            android:name=".features.test.dragndrop.AddItemActivity"
            android:autoRemoveFromRecents="true"
            android:excludeFromRecents="true"
            android:label="@string/action_add_to_workspace"
+23 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
@@ -589,4 +590,26 @@ public class Utilities {
        float progress = getProgress(t, fromMin, fromMax);
        return mapRange(interpolator.getInterpolation(progress), toMin, toMax);
    }

    /**
     * Returns true if the intent is a valid launch intent for a launcher activity of an app.
     * This is used to identify shortcuts which are different from the ones exposed by the
     * applications' manifest file.
     *
     * @param launchIntent The intent that will be launched when the shortcut is clicked.
     */
    public static boolean isLauncherAppTarget(Intent launchIntent) {
        if (launchIntent != null
            && Intent.ACTION_MAIN.equals(launchIntent.getAction())
            && launchIntent.getComponent() != null
            && launchIntent.getCategories() != null
            && launchIntent.getCategories().size() == 1
            && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
            && TextUtils.isEmpty(launchIntent.getDataString())) {
            // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE.
            Bundle extras = launchIntent.getExtras();
            return extras == null || extras.keySet().isEmpty();
        }
        return false;
    }
}
+280 −12
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ import static foundation.e.blisslauncher.features.test.dragndrop.DragLayer.ALPHA

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
@@ -14,6 +15,7 @@ import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.graphics.Point;
@@ -23,6 +25,7 @@ import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MutableInt;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -31,6 +34,7 @@ import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.OvershootInterpolator;
import android.widget.GridLayout;
import android.widget.Toast;
import foundation.e.blisslauncher.BuildConfig;
@@ -51,6 +55,9 @@ import foundation.e.blisslauncher.core.utils.IntegerArray;
import foundation.e.blisslauncher.core.utils.PackageUserKey;
import foundation.e.blisslauncher.features.launcher.Hotseat;
import foundation.e.blisslauncher.features.notification.FolderDotInfo;
import foundation.e.blisslauncher.features.shortcuts.DeepShortcutManager;
import foundation.e.blisslauncher.features.shortcuts.InstallShortcutReceiver;
import foundation.e.blisslauncher.features.shortcuts.ShortcutKey;
import foundation.e.blisslauncher.features.test.Alarm;
import foundation.e.blisslauncher.features.test.CellLayout;
import foundation.e.blisslauncher.features.test.IconTextView;
@@ -62,6 +69,7 @@ import foundation.e.blisslauncher.features.test.VariantDeviceProfile;
import foundation.e.blisslauncher.features.test.WorkspaceStateTransitionAnimation;
import foundation.e.blisslauncher.features.test.anim.AnimatorSetBuilder;
import foundation.e.blisslauncher.features.test.anim.Interpolators;
import foundation.e.blisslauncher.features.test.anim.PropertyListBuilder;
import foundation.e.blisslauncher.features.test.dragndrop.DragController;
import foundation.e.blisslauncher.features.test.dragndrop.DragOptions;
import foundation.e.blisslauncher.features.test.dragndrop.DragSource;
@@ -71,8 +79,11 @@ import foundation.e.blisslauncher.features.test.dragndrop.SpringLoadedDragContro
import foundation.e.blisslauncher.features.test.graphics.DragPreviewProvider;
import foundation.e.blisslauncher.features.test.uninstall.UninstallHelper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import org.jetbrains.annotations.NotNull;
@@ -116,6 +127,12 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
    final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
    final static float TOUCH_SLOP_DAMPING_FACTOR = 4;

    // How long to wait before the new-shortcut animation automatically pans the workspace
    private static final int NEW_APPS_PAGE_MOVE_DELAY = 500;
    private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
    static final int NEW_APPS_ANIMATION_DELAY = 500;
    private static final float BOUNCE_ANIMATION_TENSION = 1.3f;

    private final static Paint sPaint = new Paint();

    Runnable mRemoveEmptyScreenRunnable;
@@ -176,6 +193,11 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
    private Alarm wobbleExpireAlarm = new Alarm();
    private static final int WOBBLE_EXPIRATION_TIMEOUT = 25000;

    /**
     * Map of ShortcutKey to the number of times it is pinned.
     */
    public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();

    public LauncherPagedView(Context context, AttributeSet attributeSet) {
        this(context, attributeSet, 0);
    }
@@ -297,8 +319,14 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
        }
    }

    public void bindItems(@NotNull List<? extends LauncherItem> launcherItems) {
        for (LauncherItem launcherItem : launcherItems) {
    public void bindItems(
        @NotNull List<? extends LauncherItem> launcherItems,
        boolean animateIcons
    ) {
        final Collection<Animator> bounceAnims = new ArrayList<>();
        int newItemsScreenId = -1;
        for (int i = 0; i < launcherItems.size(); i++) {
            LauncherItem launcherItem = launcherItems.get(i);
            IconTextView appView = (IconTextView) LayoutInflater.from(getContext())
                .inflate(R.layout.app_icon, null, false);
            appView.applyFromShortcutItem(launcherItem);
@@ -317,7 +345,6 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
                iconLayoutParams.width = mLauncher.getDeviceProfile().getCellWidthPx();
                appView.setLayoutParams(iconLayoutParams);
                appView.setTextVisibility(true);
                addInScreenFromBind(appView, launcherItem);
            } else if (launcherItem.container == Constants.CONTAINER_HOTSEAT) {
                GridLayout.Spec rowSpec = GridLayout.spec(GridLayout.UNDEFINED);
                GridLayout.Spec colSpec = GridLayout.spec(GridLayout.UNDEFINED);
@@ -326,11 +353,254 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
                iconLayoutParams.height = mLauncher.getDeviceProfile().getHotseatCellHeightPx();
                iconLayoutParams.width = mLauncher.getDeviceProfile().getCellWidthPx();
                appView.setLayoutParams(iconLayoutParams);
            }
            addInScreenFromBind(appView, launcherItem);
            if (animateIcons) {
                // Animate all the applications up now
                appView.setAlpha(0f);
                appView.setScaleX(0f);
                appView.setScaleY(0f);
                bounceAnims.add(createNewAppBounceAnimation(appView, i));
                newItemsScreenId = launcherItem.screenId;
            }
        }

        // Animate to the correct page
        if (animateIcons && newItemsScreenId > -1) {
            AnimatorSet anim = new AnimatorSet();
            anim.playTogether(bounceAnims);
            int currentScreenId = getScreenIdForPageIndex(getNextPage());
            final int newScreenIndex = getPageIndexForScreenId(newItemsScreenId);
            final Runnable startBounceAnimRunnable = anim::start;

            if (newItemsScreenId != currentScreenId) {
                // We post the animation slightly delayed to prevent slowdowns
                // when we are loading right after we return to launcher.
                this.postDelayed((Runnable) () -> {
                    AbstractFloatingView.closeAllOpenViews(mLauncher, false);

                    snapToPage(newScreenIndex);
                    postDelayed(
                        startBounceAnimRunnable,
                        NEW_APPS_ANIMATION_DELAY
                    );
                }, NEW_APPS_PAGE_MOVE_DELAY);
            } else {
                postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
            }
        }
        requestLayout();
    }

    public void bindItemsAdded(@NotNull List<? extends LauncherItem> items) {
        final ArrayList<LauncherItem> addedItemsFinal = new ArrayList<>();
        final IntegerArray addedWorkspaceScreensFinal = new IntegerArray();
        List<LauncherItem> filteredItems = new ArrayList<>();
        for (LauncherItem item : items) {
            if (item.itemType == Constants.ITEM_TYPE_APPLICATION ||
                item.itemType == Constants.ITEM_TYPE_SHORTCUT
            ) {
                // Short-circuit this logic if the icon exists somewhere on the workspace
                if (shortcutExists(item.getIntent(), item.user.getRealHandle())) {
                    continue;
                }
            }
            if (item != null) {
                filteredItems.add(item);
            }
        }

        for (LauncherItem item : filteredItems) {
            // Find appropriate space for the item.
            int[] coords = findSpaceForItem(addedWorkspaceScreensFinal);
            int screenId = coords[0];

            LauncherItem itemInfo;
            if (item instanceof ApplicationItem || item instanceof ShortcutItem ||
                item instanceof FolderItem) {
                itemInfo = item;
                itemInfo.screenId = screenId;
                itemInfo.cell = coords[1];
                itemInfo.container = Constants.CONTAINER_DESKTOP;
            } else {
                throw new RuntimeException("Unexpected info type");
            }

            if(item.itemType == Constants.ITEM_TYPE_SHORTCUT) {
                // Increment the count for the given shortcut
                ShortcutKey pinnedShortcut = ShortcutKey.fromItem((ShortcutItem) item);
                MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
                if (count == null) {
                    count = new MutableInt(1);
                    pinnedShortcutCounts.put(pinnedShortcut, count);
                } else {
                    count.value++;
                }

                // Since this is a new item, pin the shortcut in the system server.
                if (count.value == 1) {
                    DeepShortcutManager.getInstance(getContext()).pinShortcut(pinnedShortcut);
                }

            }
            // Save the WorkspaceItemInfo for binding in the workspace
            addedItemsFinal.add(itemInfo);
        }

        if (!addedItemsFinal.isEmpty()) {
            final ArrayList<LauncherItem> addAnimated = new ArrayList<>();
            final ArrayList<LauncherItem> addNotAnimated = new ArrayList<>();
            if (!addedItemsFinal.isEmpty()) {
                LauncherItem info = addedItemsFinal.get(addedItemsFinal.size() - 1);
                int lastScreenId = info.screenId;
                for (LauncherItem i : addedItemsFinal) {
                    if (i.screenId == lastScreenId) {
                        addAnimated.add(i);
                    } else {
                        addNotAnimated.add(i);
                    }
                }
            }

            if (!addedWorkspaceScreensFinal.isEmpty()) {
                bindScreens(addedWorkspaceScreensFinal);
            }

            // We add the items without animation on non-visible pages, and with
            // animations on the new page (which we will try and snap to).
            if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
                bindItems(addNotAnimated, false);
            }
            if (addAnimated != null && !addAnimated.isEmpty()) {
                bindItems(addAnimated, true);
            }

            // Remove the extra empty screen
            removeExtraEmptyScreen(false, false);
            updateDatabase(getWorkspaceAndHotseatCellLayouts());
        }
    }

    private int[] findSpaceForItem(IntegerArray addedWorkspaceScreensFinal) {
        // Find appropriate space for the item.
        int screenId = 0;
        int cell = 0;
        boolean found = false;

        int screenCount = getChildCount();
        for (int screen = 0; screen < screenCount; screen++) {
            View child = getChildAt(screen);
            if (child instanceof CellLayout) {
                CellLayout cellLayout = (CellLayout) child;
                int index = mWorkspaceScreens.indexOfValue(cellLayout);
                screenId = mWorkspaceScreens.keyAt(index);
                if (cellLayout.getChildCount() < cellLayout.getMaxChildCount()) {
                    found = true;
                    cell = cellLayout.getChildCount();
                    break;
                }
            }
        }

        if (!found) {
            screenId = screenId + 1;
            addedWorkspaceScreensFinal.add(screenId);
            cell = 0;
        }
        return new int[]{screenId, cell};
    }

    /**
     * Returns true if the shortcuts already exists on the workspace. This must be called after
     * the workspace has been loaded. We identify a shortcut by its intent.
     */
    protected boolean shortcutExists(Intent intent, UserHandle user) {
        final String compPkgName, intentWithPkg, intentWithoutPkg;
        if (intent == null) {
            // Skip items with null intents
            return true;
        }
        if (intent.getComponent() != null) {
            // If component is not null, an intent with null package will produce
            // the same result and should also be a match.
            compPkgName = intent.getComponent().getPackageName();
            if (intent.getPackage() != null) {
                intentWithPkg = intent.toUri(0);
                intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
            } else {
                intentWithPkg = new Intent(intent).setPackage(compPkgName).toUri(0);
                intentWithoutPkg = intent.toUri(0);
            }
        } else {
            compPkgName = null;
            intentWithPkg = intent.toUri(0);
            intentWithoutPkg = intent.toUri(0);
        }

        boolean isLauncherAppTarget = Utilities.isLauncherAppTarget(intent);

        for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
            // map over all the shortcuts on the workspace
            final int itemCount = layout.getChildCount();
            for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
                View item = layout.getChildAt(itemIdx);
                LauncherItem info = (LauncherItem) item.getTag();
                if (info instanceof FolderItem) {
                    FolderItem folder = (FolderItem) info;
                    List<LauncherItem> folderChildren = folder.items;
                    // map over all the children in the folder
                    final int childCount = folder.items.size();
                    for (int childIdx = 0; childIdx < childCount; childIdx++) {
                        LauncherItem childItem = folderChildren.get(childIdx);
                        if (childItem.getIntent() != null && childItem.user.equals(user)) {
                            Intent copyIntent = new Intent(childItem.getIntent());
                            copyIntent.setSourceBounds(intent.getSourceBounds());
                            String s = copyIntent.toUri(0);
                            if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
                                return true;
                            }

                            // checking for existing promise icon with same package name
                            if (isLauncherAppTarget
                                && childItem.getTargetComponent() != null
                                && compPkgName != null
                                && compPkgName
                                .equals(childItem.getTargetComponent().getPackageName())) {
                                return true;
                            }
                        }
                    }
                } else {
                    if (info.getIntent() != null && info.user.equals(user)) {
                        Intent copyIntent = new Intent(info.getIntent());
                        copyIntent.setSourceBounds(intent.getSourceBounds());
                        String s = copyIntent.toUri(0);
                        if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
                            return true;
                        }

                        // checking for existing promise icon with same package name
                        if (isLauncherAppTarget
                            && info.getTargetComponent() != null
                            && compPkgName != null
                            && compPkgName.equals(info.getTargetComponent().getPackageName())) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private ValueAnimator createNewAppBounceAnimation(View v, int i) {
        ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
            .setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
        bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
        bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
        return bounceAnim;
    }

    public GridLayout insertNewWorkspaceScreen(int screenId) {
        return insertNewWorkspaceScreen(screenId, getChildCount());
    }
@@ -637,7 +907,7 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
        return indexOfChild(mWorkspaceScreens.get(screenId));
    }

    public long getScreenIdForPageIndex(int index) {
    public int getScreenIdForPageIndex(int index) {
        if (0 <= index && index < mScreenOrder.size()) {
            return mScreenOrder.get(index);
        }
@@ -2466,8 +2736,6 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
        computeScrollHelper(false);
    }



    public interface ItemOperator {
        /**
         * Process the next itemInfo, possibly with side-effect on the next item.
+0 −7
Original line number Diff line number Diff line
package foundation.e.blisslauncher.core.executors;


import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.TimeUnit;

public class MainThreadExecutor extends LooperExecutor {

    public MainThreadExecutor() {
        super(Looper.getMainLooper());
    }

}
Loading