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