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