1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.model; 17 18 import static com.android.launcher3.Utilities.isValidExtraType; 19 20 import android.content.Context; 21 import android.content.Intent; 22 import android.graphics.Bitmap; 23 import android.os.Process; 24 import android.util.Log; 25 26 import com.android.launcher3.InvariantDeviceProfile; 27 import com.android.launcher3.LauncherSettings; 28 import com.android.launcher3.Utilities; 29 import com.android.launcher3.config.FeatureFlags; 30 import com.android.launcher3.icons.BitmapInfo; 31 import com.android.launcher3.icons.LauncherIcons; 32 import com.android.launcher3.model.data.ItemInfo; 33 import com.android.launcher3.model.data.WorkspaceItemInfo; 34 import com.android.launcher3.util.IntArray; 35 import com.android.launcher3.util.IntSet; 36 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.List; 40 import java.util.Objects; 41 import java.util.stream.IntStream; 42 43 /** 44 * Utils class for {@link com.android.launcher3.LauncherModel}. 45 */ 46 public class ModelUtils { 47 48 private static final String TAG = "ModelUtils"; 49 50 /** 51 * Filters the set of items who are directly or indirectly (via another container) on the 52 * specified screen. 53 */ filterCurrentWorkspaceItems(int currentScreenId, ArrayList<T> allWorkspaceItems, ArrayList<T> currentScreenItems, ArrayList<T> otherScreenItems)54 public static <T extends ItemInfo> void filterCurrentWorkspaceItems(int currentScreenId, 55 ArrayList<T> allWorkspaceItems, 56 ArrayList<T> currentScreenItems, 57 ArrayList<T> otherScreenItems) { 58 // Purge any null ItemInfos 59 allWorkspaceItems.removeIf(Objects::isNull); 60 // Order the set of items by their containers first, this allows use to walk through the 61 // list sequentially, build up a list of containers that are in the specified screen, 62 // as well as all items in those containers. 63 IntSet itemsOnScreen = new IntSet(); 64 Collections.sort(allWorkspaceItems, 65 (lhs, rhs) -> Integer.compare(lhs.container, rhs.container)); 66 for (T info : allWorkspaceItems) { 67 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 68 if (info.screenId == currentScreenId) { 69 currentScreenItems.add(info); 70 itemsOnScreen.add(info.id); 71 } else { 72 otherScreenItems.add(info); 73 } 74 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 75 currentScreenItems.add(info); 76 itemsOnScreen.add(info.id); 77 } else { 78 if (itemsOnScreen.contains(info.container)) { 79 currentScreenItems.add(info); 80 itemsOnScreen.add(info.id); 81 } else { 82 otherScreenItems.add(info); 83 } 84 } 85 } 86 } 87 88 /** 89 * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right) 90 */ sortWorkspaceItemsSpatially(InvariantDeviceProfile profile, ArrayList<ItemInfo> workspaceItems)91 public static void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile, 92 ArrayList<ItemInfo> workspaceItems) { 93 final int screenCols = profile.numColumns; 94 final int screenCellCount = profile.numColumns * profile.numRows; 95 Collections.sort(workspaceItems, (lhs, rhs) -> { 96 if (lhs.container == rhs.container) { 97 // Within containers, order by their spatial position in that container 98 switch (lhs.container) { 99 case LauncherSettings.Favorites.CONTAINER_DESKTOP: { 100 int lr = (lhs.screenId * screenCellCount + lhs.cellY * screenCols 101 + lhs.cellX); 102 int rr = (rhs.screenId * screenCellCount + +rhs.cellY * screenCols 103 + rhs.cellX); 104 return Integer.compare(lr, rr); 105 } 106 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { 107 // We currently use the screen id as the rank 108 return Integer.compare(lhs.screenId, rhs.screenId); 109 } 110 default: 111 if (FeatureFlags.IS_STUDIO_BUILD) { 112 throw new RuntimeException( 113 "Unexpected container type when sorting workspace items."); 114 } 115 return 0; 116 } 117 } else { 118 // Between containers, order by hotseat, desktop 119 return Integer.compare(lhs.container, rhs.container); 120 } 121 }); 122 } 123 124 /** 125 * Iterates though current workspace items and returns available hotseat ranks for prediction. 126 */ getMissingHotseatRanks(List<ItemInfo> items, int len)127 public static IntArray getMissingHotseatRanks(List<ItemInfo> items, int len) { 128 IntSet seen = new IntSet(); 129 items.stream().filter( 130 info -> info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) 131 .forEach(i -> seen.add(i.screenId)); 132 IntArray result = new IntArray(len); 133 IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add); 134 return result; 135 } 136 137 138 /** 139 * Creates a workspace item info for the legacy shortcut intent 140 */ 141 @SuppressWarnings("deprecation") fromLegacyShortcutIntent(Context context, Intent data)142 public static WorkspaceItemInfo fromLegacyShortcutIntent(Context context, Intent data) { 143 if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class) 144 || !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE, 145 Intent.ShortcutIconResource.class)) 146 || !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) { 147 148 Log.e(TAG, "Invalid install shortcut intent"); 149 return null; 150 } 151 152 Intent launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); 153 String label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); 154 if (launchIntent == null || label == null) { 155 Log.e(TAG, "Invalid install shortcut intent"); 156 return null; 157 } 158 159 final WorkspaceItemInfo info = new WorkspaceItemInfo(); 160 info.user = Process.myUserHandle(); 161 162 BitmapInfo iconInfo = null; 163 try (LauncherIcons li = LauncherIcons.obtain(context)) { 164 Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); 165 if (bitmap != null) { 166 iconInfo = li.createIconBitmap(bitmap); 167 } else { 168 info.iconResource = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); 169 if (info.iconResource != null) { 170 iconInfo = li.createIconBitmap(info.iconResource); 171 } 172 } 173 } 174 175 if (iconInfo == null) { 176 Log.e(TAG, "Invalid icon by the app"); 177 return null; 178 } 179 info.bitmap = iconInfo; 180 info.contentDescription = info.title = Utilities.trim(label); 181 info.intent = launchIntent; 182 return info; 183 } 184 } 185