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