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