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.testing.TestProtocol; 35 import com.android.launcher3.util.IntArray; 36 import com.android.launcher3.util.IntSet; 37 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.Objects; 42 import java.util.stream.IntStream; 43 44 /** 45 * Utils class for {@link com.android.launcher3.LauncherModel}. 46 */ 47 public class ModelUtils { 48 49 private static final String TAG = "ModelUtils"; 50 51 /** 52 * Filters the set of items who are directly or indirectly (via another container) on the 53 * specified screen. 54 */ filterCurrentWorkspaceItems( IntSet currentScreenIds, ArrayList<T> allWorkspaceItems, ArrayList<T> currentScreenItems, ArrayList<T> otherScreenItems)55 public static <T extends ItemInfo> void filterCurrentWorkspaceItems( 56 IntSet currentScreenIds, 57 ArrayList<T> allWorkspaceItems, 58 ArrayList<T> currentScreenItems, 59 ArrayList<T> otherScreenItems) { 60 // Purge any null ItemInfos 61 allWorkspaceItems.removeIf(Objects::isNull); 62 // Order the set of items by their containers first, this allows use to walk through the 63 // list sequentially, build up a list of containers that are in the specified screen, 64 // as well as all items in those containers. 65 IntSet itemsOnScreen = new IntSet(); 66 Collections.sort(allWorkspaceItems, 67 (lhs, rhs) -> Integer.compare(lhs.container, rhs.container)); 68 for (T info : allWorkspaceItems) { 69 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 70 if (TestProtocol.sDebugTracing) { 71 Log.d(TestProtocol.NULL_INT_SET, "filterCurrentWorkspaceItems: " 72 + currentScreenIds); 73 } 74 if (currentScreenIds.contains(info.screenId)) { 75 currentScreenItems.add(info); 76 itemsOnScreen.add(info.id); 77 } else { 78 otherScreenItems.add(info); 79 } 80 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 81 currentScreenItems.add(info); 82 itemsOnScreen.add(info.id); 83 } else { 84 if (itemsOnScreen.contains(info.container)) { 85 currentScreenItems.add(info); 86 itemsOnScreen.add(info.id); 87 } else { 88 otherScreenItems.add(info); 89 } 90 } 91 } 92 } 93 94 /** 95 * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right) 96 */ sortWorkspaceItemsSpatially(InvariantDeviceProfile profile, ArrayList<ItemInfo> workspaceItems)97 public static void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile, 98 ArrayList<ItemInfo> workspaceItems) { 99 final int screenCols = profile.numColumns; 100 final int screenCellCount = profile.numColumns * profile.numRows; 101 Collections.sort(workspaceItems, (lhs, rhs) -> { 102 if (lhs.container == rhs.container) { 103 // Within containers, order by their spatial position in that container 104 switch (lhs.container) { 105 case LauncherSettings.Favorites.CONTAINER_DESKTOP: { 106 int lr = (lhs.screenId * screenCellCount + lhs.cellY * screenCols 107 + lhs.cellX); 108 int rr = (rhs.screenId * screenCellCount + +rhs.cellY * screenCols 109 + rhs.cellX); 110 return Integer.compare(lr, rr); 111 } 112 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { 113 // We currently use the screen id as the rank 114 return Integer.compare(lhs.screenId, rhs.screenId); 115 } 116 default: 117 if (FeatureFlags.IS_STUDIO_BUILD) { 118 throw new RuntimeException( 119 "Unexpected container type when sorting workspace items."); 120 } 121 return 0; 122 } 123 } else { 124 // Between containers, order by hotseat, desktop 125 return Integer.compare(lhs.container, rhs.container); 126 } 127 }); 128 } 129 130 /** 131 * Iterates though current workspace items and returns available hotseat ranks for prediction. 132 */ getMissingHotseatRanks(List<ItemInfo> items, int len)133 public static IntArray getMissingHotseatRanks(List<ItemInfo> items, int len) { 134 IntSet seen = new IntSet(); 135 items.stream().filter( 136 info -> info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) 137 .forEach(i -> seen.add(i.screenId)); 138 IntArray result = new IntArray(len); 139 IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add); 140 return result; 141 } 142 143 144 /** 145 * Creates a workspace item info for the legacy shortcut intent 146 */ 147 @SuppressWarnings("deprecation") fromLegacyShortcutIntent(Context context, Intent data)148 public static WorkspaceItemInfo fromLegacyShortcutIntent(Context context, Intent data) { 149 if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class) 150 || !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE, 151 Intent.ShortcutIconResource.class)) 152 || !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) { 153 154 Log.e(TAG, "Invalid install shortcut intent"); 155 return null; 156 } 157 158 Intent launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); 159 String label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); 160 if (launchIntent == null || label == null) { 161 Log.e(TAG, "Invalid install shortcut intent"); 162 return null; 163 } 164 165 final WorkspaceItemInfo info = new WorkspaceItemInfo(); 166 info.user = Process.myUserHandle(); 167 168 BitmapInfo iconInfo = null; 169 try (LauncherIcons li = LauncherIcons.obtain(context)) { 170 Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); 171 if (bitmap != null) { 172 iconInfo = li.createIconBitmap(bitmap); 173 } else { 174 info.iconResource = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); 175 if (info.iconResource != null) { 176 iconInfo = li.createIconBitmap(info.iconResource); 177 } 178 } 179 } 180 181 if (iconInfo == null) { 182 Log.e(TAG, "Invalid icon by the app"); 183 return null; 184 } 185 info.bitmap = iconInfo; 186 info.contentDescription = info.title = Utilities.trim(label); 187 info.intent = launchIntent; 188 return info; 189 } 190 } 191