Allow app shortcuts to have a preferred placement
Look for a preferred placement when somebody asks to add a new shortcut.
The preferred placements are part of the resources configuration
(config.xml).
Issue: FPIIM-1909
Issue: FP2N-245
Change-Id: I871196fe37dae35172d609fcbf9efd5496a7e4b2
(adapted from commit c01efc9a3c3ff04bcb52924a533211891ed9be5b)
diff --git a/res/values/config.xml b/res/values/config.xml
index a942f02..6157921 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -109,4 +109,8 @@
<item type="id" name="action_move_screen_forwards" />
<item type="id" name="action_resize" />
<item type="id" name="action_deep_shortcuts" />
+
+<!-- Shortcut preferred placements. An item denotes a component (component name) and its
+ placement on a screen (screen and coordinates). These elements have to be comma-separated. -->
+ <string-array name="shortcut_preferred_placements" translatable="false" />
</resources>
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 3ac9773..b7dae4b 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -28,6 +28,7 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
@@ -391,8 +392,30 @@
runOnWorkerThread(r);
}
- private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
+ /**
+ * Validate that a specific icon space is available.
+ * <p>
+ * Heavily inspired by #findNextAvailableIconSpaceInScreen(ArrayList<>,
+ * int[], int, int) - re-uses factored out code in
+ * itemInfosToGridOccupancy().
+ *
+ * @return true if the specific icon space is available.
+ */
+ private static boolean isAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
int[] xy, int spanX, int spanY) {
+ GridOccupancy occupied = itemInfosToGridOccupancy(occupiedPos);
+ return occupied.isRegionVacant(xy[0], xy[1], spanX, spanY);
+ }
+
+ /*
+ * Generate GridOccupancy from array of ItemInfo
+ *
+ * Factored out from #findNextAvailableIconSpaceInScreen(ArrayList<>, int[], int, int)
+ * to be re-used by #isAvailableIconSpaceInScreen(ArrayList<>, int[], int, int)
+ *
+ * @return GridOccupancy with coordinates from items marked as occupied.
+ */
+ private static GridOccupancy itemInfosToGridOccupancy(ArrayList<ItemInfo> occupiedPos) {
LauncherAppState app = LauncherAppState.getInstance();
InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
@@ -402,6 +425,12 @@
occupied.markCells(r, true);
}
}
+ return occupied;
+ }
+
+ private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
+ int[] xy, int spanX, int spanY) {
+ GridOccupancy occupied = itemInfosToGridOccupancy(occupiedPos);
return occupied.findVacantCell(xy, spanX, spanY);
}
@@ -414,6 +443,20 @@
ArrayList<Long> workspaceScreens,
ArrayList<Long> addedWorkspaceScreensFinal,
int spanX, int spanY) {
+ return findSpaceForItem(context, null, workspaceScreens,
+ addedWorkspaceScreensFinal, spanX, spanY);
+ }
+
+ /**
+ * Find a position on the screen for the given size or adds a new screen.
+ * @return screenId and the coordinates for the item.
+ */
+ @Thunk Pair<Long, int[]> findSpaceForItem(
+ Context context,
+ Pair<Long, int[]> shortcutPreferredPlacement,
+ ArrayList<Long> workspaceScreens,
+ ArrayList<Long> addedWorkspaceScreensFinal,
+ int spanX, int spanY) {
LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
// Use sBgItemsIdMap as all the items are already loaded.
@@ -437,12 +480,25 @@
boolean found = false;
int screenCount = workspaceScreens.size();
- // First check the preferred screen.
- int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
- if (preferredScreenIndex < screenCount) {
- screenId = workspaceScreens.get(preferredScreenIndex);
- found = findNextAvailableIconSpaceInScreen(
- screenItems.get(screenId), cordinates, spanX, spanY);
+
+ if (shortcutPreferredPlacement != null) {
+ // First, check the preferred placement.
+ screenId = shortcutPreferredPlacement.first;
+ cordinates = shortcutPreferredPlacement.second;
+ if (screenId < screenCount) {
+ found = isAvailableIconSpaceInScreen(
+ screenItems.get(screenId), cordinates, spanX, spanY);
+ }
+ }
+
+ if (!found) {
+ // Then, check the preferred screen.
+ int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
+ if (preferredScreenIndex < screenCount) {
+ screenId = workspaceScreens.get(preferredScreenIndex);
+ found = findNextAvailableIconSpaceInScreen(
+ screenItems.get(screenId), cordinates, spanX, spanY);
+ }
}
if (!found) {
@@ -492,6 +548,10 @@
final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
+ // Generic shortcut preferred placements
+ final Map<ComponentName, Pair<Long, int[]>> shortcutPreferredPlacements
+ = getShortcutPreferredPlacements(context, R.array.shortcut_preferred_placements);
+
// Get the list of workspace screens. We need to append to this list and
// can not use sBgWorkspaceScreens because loadWorkspace() may not have been
// called.
@@ -505,8 +565,8 @@
}
}
- // Find appropriate space for the item.
Pair<Long, int[]> coords = findSpaceForItem(context,
+ shortcutPreferredPlacements.get(item.getIntent().getComponent()),
workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
long screenId = coords.first;
int[] cordinates = coords.second;
@@ -3866,4 +3926,47 @@
public static Looper getWorkerLooper() {
return sWorkerThread.getLooper();
}
+
+ /**
+ * Get the shortcut preferred placements from a resource array.
+ * <p>
+ * An item (a String) should contain the following information separated by commas:
+ * <ul>
+ * <li>the {@link ComponentName}</li>
+ * <li>the screen id (e.g. 0 for the main screen</li>
+ * <li>the abscissa (aka cell X)</li>
+ * <li>the ordinate (aka cell Y)</li>
+ * </ul>
+ * @param context the context to read the resources from.
+ * @param array the string array resource to inflate from.
+ * @return a map of valid placements found in the resource array.
+ * @throws Resources.NotFoundException if the string array cannot be loaded from the context resources.
+ */
+ private static Map<ComponentName, Pair<Long, int[]>> getShortcutPreferredPlacements(Context context, int array)
+ throws Resources.NotFoundException {
+ final String[] rawPlacements = context.getResources().getStringArray(array);
+ final Map<ComponentName, Pair<Long, int[]>> placements = new HashMap<>(rawPlacements.length);
+
+ for (String rawPlacement : rawPlacements) {
+ final String[] items = rawPlacement.split(",");
+
+ if (items.length != 4) {
+ Log.e(TAG, "Invalid preferred placement : " + rawPlacement
+ + ", 4 comma-separated items were expected");
+ continue;
+ }
+
+ try {
+ final ComponentName component = ComponentName.unflattenFromString(items[0]);
+ final long screenId = Long.valueOf(items[1]);
+ final int cellX = Integer.parseInt(items[2]);
+ final int cellY = Integer.parseInt(items[3]);
+ placements.put(component, new Pair<>(screenId, new int[] {cellX, cellY}));
+ } catch (NumberFormatException ex) {
+ Log.e(TAG, "Invalid preferred placement: " + rawPlacement, ex);
+ }
+ }
+
+ return placements;
+ }
}