1 /* 2 * Copyright (C) 2008 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 17 package com.android.launcher3.model.data; 18 19 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS; 20 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; 21 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; 22 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; 23 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION; 24 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SEARCH_RESULTS; 25 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SETTINGS; 26 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS; 27 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_TASKSWITCHER; 28 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY; 29 import static com.android.launcher3.LauncherSettings.Favorites.EXTENDED_CONTAINERS; 30 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; 31 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; 32 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; 33 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; 34 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK; 35 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET; 36 import static com.android.launcher3.shortcuts.ShortcutKey.EXTRA_SHORTCUT_ID; 37 38 import android.content.ComponentName; 39 import android.content.ContentValues; 40 import android.content.Intent; 41 import android.os.Process; 42 import android.os.UserHandle; 43 44 import androidx.annotation.Nullable; 45 46 import com.android.launcher3.LauncherSettings; 47 import com.android.launcher3.LauncherSettings.Favorites; 48 import com.android.launcher3.Workspace; 49 import com.android.launcher3.logger.LauncherAtom; 50 import com.android.launcher3.logger.LauncherAtom.AllAppsContainer; 51 import com.android.launcher3.logger.LauncherAtom.ContainerInfo; 52 import com.android.launcher3.logger.LauncherAtom.PredictionContainer; 53 import com.android.launcher3.logger.LauncherAtom.SearchResultContainer; 54 import com.android.launcher3.logger.LauncherAtom.SettingsContainer; 55 import com.android.launcher3.logger.LauncherAtom.Shortcut; 56 import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer; 57 import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer; 58 import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers; 59 import com.android.launcher3.model.ModelWriter; 60 import com.android.launcher3.util.ContentWriter; 61 62 import java.util.Optional; 63 64 /** 65 * Represents an item in the launcher. 66 */ 67 public class ItemInfo { 68 69 public static final boolean DEBUG = false; 70 public static final int NO_ID = -1; 71 // An id that doesn't match any item, including predicted apps with have an id=NO_ID 72 public static final int NO_MATCHING_ID = Integer.MIN_VALUE; 73 74 /** 75 * The id in the settings database for this item 76 */ 77 public int id = NO_ID; 78 79 /** 80 * One of {@link Favorites#ITEM_TYPE_APPLICATION}, 81 * {@link Favorites#ITEM_TYPE_SHORTCUT}, 82 * {@link Favorites#ITEM_TYPE_DEEP_SHORTCUT} 83 * {@link Favorites#ITEM_TYPE_FOLDER}, 84 * {@link Favorites#ITEM_TYPE_APPWIDGET} or 85 * {@link Favorites#ITEM_TYPE_CUSTOM_APPWIDGET}. 86 */ 87 public int itemType; 88 89 /** 90 * The id of the container that holds this item. For the desktop, this will be 91 * {@link Favorites#CONTAINER_DESKTOP}. For the all applications folder it 92 * will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders 93 * it will be the id of the folder. 94 */ 95 public int container = NO_ID; 96 97 /** 98 * Indicates the screen in which the shortcut appears if the container types is 99 * {@link Favorites#CONTAINER_DESKTOP}. (i.e., ignore if the container type is 100 * {@link Favorites#CONTAINER_HOTSEAT}) 101 */ 102 public int screenId = -1; 103 104 /** 105 * Indicates the X position of the associated cell. 106 */ 107 public int cellX = -1; 108 109 /** 110 * Indicates the Y position of the associated cell. 111 */ 112 public int cellY = -1; 113 114 /** 115 * Indicates the X cell span. 116 */ 117 public int spanX = 1; 118 119 /** 120 * Indicates the Y cell span. 121 */ 122 public int spanY = 1; 123 124 /** 125 * Indicates the minimum X cell span. 126 */ 127 public int minSpanX = 1; 128 129 /** 130 * Indicates the minimum Y cell span. 131 */ 132 public int minSpanY = 1; 133 134 /** 135 * Indicates the position in an ordered list. 136 */ 137 public int rank = 0; 138 139 /** 140 * Title of the item 141 */ 142 public CharSequence title; 143 144 /** 145 * Content description of the item. 146 */ 147 public CharSequence contentDescription; 148 149 /** 150 * When the instance is created using {@link #copyFrom}, this field is used to keep track of 151 * original {@link ComponentName}. 152 */ 153 private ComponentName mComponentName; 154 155 public UserHandle user; 156 ItemInfo()157 public ItemInfo() { 158 user = Process.myUserHandle(); 159 } 160 ItemInfo(ItemInfo info)161 protected ItemInfo(ItemInfo info) { 162 copyFrom(info); 163 } 164 copyFrom(ItemInfo info)165 public void copyFrom(ItemInfo info) { 166 id = info.id; 167 cellX = info.cellX; 168 cellY = info.cellY; 169 spanX = info.spanX; 170 spanY = info.spanY; 171 minSpanX = info.minSpanX; 172 minSpanY = info.minSpanY; 173 rank = info.rank; 174 screenId = info.screenId; 175 itemType = info.itemType; 176 container = info.container; 177 user = info.user; 178 contentDescription = info.contentDescription; 179 mComponentName = info.getTargetComponent(); 180 } 181 getIntent()182 public Intent getIntent() { 183 return null; 184 } 185 186 @Nullable getTargetComponent()187 public ComponentName getTargetComponent() { 188 return Optional.ofNullable(getIntent()).map(Intent::getComponent).orElse(mComponentName); 189 } 190 191 /** 192 * Returns this item's package name. 193 * 194 * Prioritizes the component package name, then uses the intent package name as a fallback. 195 * This ensures deep shortcuts are supported. 196 */ 197 @Nullable getTargetPackage()198 public String getTargetPackage() { 199 ComponentName component = getTargetComponent(); 200 Intent intent = getIntent(); 201 202 return component != null 203 ? component.getPackageName() 204 : intent != null 205 ? intent.getPackage() 206 : null; 207 } 208 writeToValues(ContentWriter writer)209 public void writeToValues(ContentWriter writer) { 210 writer.put(LauncherSettings.Favorites.ITEM_TYPE, itemType) 211 .put(LauncherSettings.Favorites.CONTAINER, container) 212 .put(LauncherSettings.Favorites.SCREEN, screenId) 213 .put(LauncherSettings.Favorites.CELLX, cellX) 214 .put(LauncherSettings.Favorites.CELLY, cellY) 215 .put(LauncherSettings.Favorites.SPANX, spanX) 216 .put(LauncherSettings.Favorites.SPANY, spanY) 217 .put(LauncherSettings.Favorites.RANK, rank); 218 } 219 readFromValues(ContentValues values)220 public void readFromValues(ContentValues values) { 221 itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE); 222 container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER); 223 screenId = values.getAsInteger(LauncherSettings.Favorites.SCREEN); 224 cellX = values.getAsInteger(LauncherSettings.Favorites.CELLX); 225 cellY = values.getAsInteger(LauncherSettings.Favorites.CELLY); 226 spanX = values.getAsInteger(LauncherSettings.Favorites.SPANX); 227 spanY = values.getAsInteger(LauncherSettings.Favorites.SPANY); 228 rank = values.getAsInteger(LauncherSettings.Favorites.RANK); 229 } 230 231 /** 232 * Write the fields of this item to the DB 233 */ onAddToDatabase(ContentWriter writer)234 public void onAddToDatabase(ContentWriter writer) { 235 if (screenId == Workspace.EXTRA_EMPTY_SCREEN_ID) { 236 // We should never persist an item on the extra empty screen. 237 throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID"); 238 } 239 240 writeToValues(writer); 241 writer.put(LauncherSettings.Favorites.PROFILE_ID, user); 242 } 243 244 @Override toString()245 public final String toString() { 246 return getClass().getSimpleName() + "(" + dumpProperties() + ")"; 247 } 248 dumpProperties()249 protected String dumpProperties() { 250 return "id=" + id 251 + " type=" + LauncherSettings.Favorites.itemTypeToString(itemType) 252 + " container=" + getContainerInfo() 253 + " targetComponent=" + getTargetComponent() 254 + " screen=" + screenId 255 + " cell(" + cellX + "," + cellY + ")" 256 + " span(" + spanX + "," + spanY + ")" 257 + " minSpan(" + minSpanX + "," + minSpanY + ")" 258 + " rank=" + rank 259 + " user=" + user 260 + " title=" + title; 261 } 262 263 /** 264 * Whether this item is disabled. 265 */ isDisabled()266 public boolean isDisabled() { 267 return false; 268 } 269 getViewId()270 public int getViewId() { 271 // aapt-generated IDs have the high byte nonzero; clamp to the range under that. 272 // This cast is safe as long as the id < 0x00FFFFFF 273 // Since we jail all the dynamically generated views, there should be no clashes 274 // with any other views. 275 return id; 276 } 277 278 /** 279 * Returns if an Item is a predicted item 280 */ isPredictedItem()281 public boolean isPredictedItem() { 282 return container == CONTAINER_HOTSEAT_PREDICTION || container == CONTAINER_PREDICTION; 283 } 284 285 /** 286 * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info. 287 */ buildProto()288 public LauncherAtom.ItemInfo buildProto() { 289 return buildProto(null); 290 } 291 292 /** 293 * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info. 294 */ buildProto(FolderInfo fInfo)295 public LauncherAtom.ItemInfo buildProto(FolderInfo fInfo) { 296 LauncherAtom.ItemInfo.Builder itemBuilder = getDefaultItemInfoBuilder(); 297 Optional<ComponentName> nullableComponent = Optional.ofNullable(getTargetComponent()); 298 switch (itemType) { 299 case ITEM_TYPE_APPLICATION: 300 itemBuilder 301 .setApplication(nullableComponent 302 .map(component -> LauncherAtom.Application.newBuilder() 303 .setComponentName(component.flattenToShortString()) 304 .setPackageName(component.getPackageName())) 305 .orElse(LauncherAtom.Application.newBuilder())); 306 break; 307 case ITEM_TYPE_DEEP_SHORTCUT: 308 itemBuilder 309 .setShortcut(nullableComponent 310 .map(component -> { 311 Shortcut.Builder lsb = Shortcut.newBuilder() 312 .setShortcutName(component.flattenToShortString()); 313 Optional.ofNullable(getIntent()) 314 .map(i -> i.getStringExtra(EXTRA_SHORTCUT_ID)) 315 .ifPresent(lsb::setShortcutId); 316 return lsb; 317 }) 318 .orElse(LauncherAtom.Shortcut.newBuilder())); 319 break; 320 case ITEM_TYPE_SHORTCUT: 321 itemBuilder 322 .setShortcut(nullableComponent 323 .map(component -> LauncherAtom.Shortcut.newBuilder() 324 .setShortcutName(component.flattenToShortString())) 325 .orElse(LauncherAtom.Shortcut.newBuilder())); 326 break; 327 case ITEM_TYPE_APPWIDGET: 328 itemBuilder 329 .setWidget(nullableComponent 330 .map(component -> LauncherAtom.Widget.newBuilder() 331 .setComponentName(component.flattenToShortString()) 332 .setPackageName(component.getPackageName())) 333 .orElse(LauncherAtom.Widget.newBuilder()) 334 .setSpanX(spanX) 335 .setSpanY(spanY)); 336 break; 337 case ITEM_TYPE_TASK: 338 itemBuilder 339 .setTask(LauncherAtom.Task.newBuilder() 340 .setComponentName(getTargetComponent().flattenToShortString()) 341 .setIndex(screenId)); 342 break; 343 default: 344 break; 345 } 346 if (fInfo != null) { 347 LauncherAtom.FolderContainer.Builder folderBuilder = 348 LauncherAtom.FolderContainer.newBuilder(); 349 folderBuilder.setGridX(cellX).setGridY(cellY).setPageIndex(screenId); 350 351 switch (fInfo.container) { 352 case CONTAINER_HOTSEAT: 353 case CONTAINER_HOTSEAT_PREDICTION: 354 folderBuilder.setHotseat(LauncherAtom.HotseatContainer.newBuilder() 355 .setIndex(fInfo.screenId)); 356 break; 357 case CONTAINER_DESKTOP: 358 folderBuilder.setWorkspace(LauncherAtom.WorkspaceContainer.newBuilder() 359 .setPageIndex(fInfo.screenId) 360 .setGridX(fInfo.cellX).setGridY(fInfo.cellY)); 361 break; 362 } 363 itemBuilder.setContainerInfo(ContainerInfo.newBuilder().setFolder(folderBuilder)); 364 } else { 365 ContainerInfo containerInfo = getContainerInfo(); 366 if (!containerInfo.getContainerCase().equals(CONTAINER_NOT_SET)) { 367 itemBuilder.setContainerInfo(containerInfo); 368 } 369 } 370 return itemBuilder.build(); 371 } 372 getDefaultItemInfoBuilder()373 protected LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder() { 374 LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder(); 375 itemBuilder.setIsWork(user != Process.myUserHandle()); 376 return itemBuilder; 377 } 378 379 /** 380 * Returns {@link ContainerInfo} used when logging this item. 381 */ getContainerInfo()382 public ContainerInfo getContainerInfo() { 383 switch (container) { 384 case CONTAINER_HOTSEAT: 385 return ContainerInfo.newBuilder() 386 .setHotseat(LauncherAtom.HotseatContainer.newBuilder().setIndex(screenId)) 387 .build(); 388 case CONTAINER_HOTSEAT_PREDICTION: 389 return ContainerInfo.newBuilder().setPredictedHotseatContainer( 390 LauncherAtom.PredictedHotseatContainer.newBuilder().setIndex(screenId)) 391 .build(); 392 case CONTAINER_DESKTOP: 393 return ContainerInfo.newBuilder() 394 .setWorkspace( 395 LauncherAtom.WorkspaceContainer.newBuilder() 396 .setGridX(cellX) 397 .setGridY(cellY) 398 .setPageIndex(screenId)) 399 .build(); 400 case CONTAINER_ALL_APPS: 401 return ContainerInfo.newBuilder() 402 .setAllAppsContainer( 403 AllAppsContainer.getDefaultInstance()) 404 .build(); 405 case CONTAINER_WIDGETS_TRAY: 406 return ContainerInfo.newBuilder() 407 .setWidgetsContainer( 408 LauncherAtom.WidgetsContainer.getDefaultInstance()) 409 .build(); 410 case CONTAINER_PREDICTION: 411 return ContainerInfo.newBuilder() 412 .setPredictionContainer(PredictionContainer.getDefaultInstance()) 413 .build(); 414 case CONTAINER_SEARCH_RESULTS: 415 return ContainerInfo.newBuilder() 416 .setSearchResultContainer(SearchResultContainer.getDefaultInstance()) 417 .build(); 418 case CONTAINER_SHORTCUTS: 419 return ContainerInfo.newBuilder() 420 .setShortcutsContainer(ShortcutsContainer.getDefaultInstance()) 421 .build(); 422 case CONTAINER_SETTINGS: 423 return ContainerInfo.newBuilder() 424 .setSettingsContainer(SettingsContainer.getDefaultInstance()) 425 .build(); 426 case CONTAINER_TASKSWITCHER: 427 return ContainerInfo.newBuilder() 428 .setTaskSwitcherContainer(TaskSwitcherContainer.getDefaultInstance()) 429 .build(); 430 case EXTENDED_CONTAINERS: 431 return ContainerInfo.newBuilder() 432 .setExtendedContainers(getExtendedContainer()) 433 .build(); 434 } 435 return ContainerInfo.getDefaultInstance(); 436 } 437 438 /** 439 * Returns non-AOSP container wrapped by {@link ExtendedContainers} object. Should be overridden 440 * by build variants. 441 */ getExtendedContainer()442 protected ExtendedContainers getExtendedContainer() { 443 return ExtendedContainers.getDefaultInstance(); 444 } 445 446 /** 447 * Returns shallow copy of the object. 448 */ makeShallowCopy()449 public ItemInfo makeShallowCopy() { 450 ItemInfo itemInfo = new ItemInfo(); 451 itemInfo.copyFrom(this); 452 return itemInfo; 453 } 454 455 /** 456 * Sets the title of the item and writes to DB model if needed. 457 */ setTitle(CharSequence title, ModelWriter modelWriter)458 public void setTitle(CharSequence title, ModelWriter modelWriter) { 459 this.title = title; 460 } 461 } 462