1 /* 2 * Copyright (C) 2020 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 package com.android.launcher3.model; 17 18 import static android.text.format.DateUtils.DAY_IN_MILLIS; 19 import static android.text.format.DateUtils.formatElapsedTime; 20 21 import static com.android.launcher3.LauncherPrefs.getDevicePrefs; 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_WIDGETS_PREDICTION; 25 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; 26 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; 27 import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle; 28 import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo; 29 import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation; 30 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; 31 32 import static java.util.stream.Collectors.toCollection; 33 34 import android.app.StatsManager; 35 import android.app.prediction.AppPredictionContext; 36 import android.app.prediction.AppPredictionManager; 37 import android.app.prediction.AppPredictor; 38 import android.app.prediction.AppTarget; 39 import android.app.prediction.AppTargetEvent; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.content.SharedPreferences; 43 import android.content.pm.LauncherActivityInfo; 44 import android.content.pm.LauncherApps; 45 import android.content.pm.ShortcutInfo; 46 import android.os.Bundle; 47 import android.os.UserHandle; 48 import android.util.Log; 49 import android.util.StatsEvent; 50 51 import androidx.annotation.AnyThread; 52 import androidx.annotation.CallSuper; 53 import androidx.annotation.NonNull; 54 import androidx.annotation.Nullable; 55 import androidx.annotation.VisibleForTesting; 56 import androidx.annotation.WorkerThread; 57 58 import com.android.launcher3.InvariantDeviceProfile; 59 import com.android.launcher3.LauncherAppState; 60 import com.android.launcher3.config.FeatureFlags; 61 import com.android.launcher3.logger.LauncherAtom; 62 import com.android.launcher3.logging.InstanceId; 63 import com.android.launcher3.logging.InstanceIdSequence; 64 import com.android.launcher3.model.BgDataModel.FixedContainerItems; 65 import com.android.launcher3.model.data.AppInfo; 66 import com.android.launcher3.model.data.FolderInfo; 67 import com.android.launcher3.model.data.ItemInfo; 68 import com.android.launcher3.model.data.WorkspaceItemInfo; 69 import com.android.launcher3.shortcuts.ShortcutKey; 70 import com.android.launcher3.util.Executors; 71 import com.android.launcher3.util.IntSparseArrayMap; 72 import com.android.launcher3.util.PersistedItemArray; 73 import com.android.quickstep.logging.SettingsChangeLogger; 74 import com.android.quickstep.logging.StatsLogCompatManager; 75 import com.android.systemui.shared.system.SysUiStatsLog; 76 77 import java.util.ArrayList; 78 import java.util.Collections; 79 import java.util.List; 80 import java.util.Map; 81 import java.util.Objects; 82 import java.util.stream.IntStream; 83 84 /** 85 * Model delegate which loads prediction items 86 */ 87 public class QuickstepModelDelegate extends ModelDelegate { 88 89 public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state"; 90 private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS"; 91 private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets"; 92 private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20; 93 94 private static final boolean IS_DEBUG = false; 95 private static final String TAG = "QuickstepModelDelegate"; 96 97 @VisibleForTesting 98 final PredictorState mAllAppsState = 99 new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions"); 100 @VisibleForTesting 101 final PredictorState mHotseatState = 102 new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions"); 103 @VisibleForTesting 104 final PredictorState mWidgetsRecommendationState = 105 new PredictorState(CONTAINER_WIDGETS_PREDICTION, "widgets_prediction"); 106 107 private final InvariantDeviceProfile mIDP; 108 private final AppEventProducer mAppEventProducer; 109 private final StatsManager mStatsManager; 110 private final Context mContext; 111 112 protected boolean mActive = false; 113 QuickstepModelDelegate(Context context)114 public QuickstepModelDelegate(Context context) { 115 mContext = context; 116 mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent); 117 118 mIDP = InvariantDeviceProfile.INSTANCE.get(context); 119 StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer); 120 mStatsManager = context.getSystemService(StatsManager.class); 121 } 122 123 @CallSuper 124 @Override loadAndBindWorkspaceItems(@onNull UserManagerState ums, @NonNull BgDataModel.Callbacks[] callbacks, @NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts)125 public void loadAndBindWorkspaceItems(@NonNull UserManagerState ums, 126 @NonNull BgDataModel.Callbacks[] callbacks, 127 @NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { 128 loadAndBindItems(ums, pinnedShortcuts, callbacks, mIDP.numDatabaseHotseatIcons, 129 mHotseatState); 130 } 131 132 @CallSuper 133 @Override loadAndBindAllAppsItems(@onNull UserManagerState ums, @NonNull BgDataModel.Callbacks[] callbacks, @NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts)134 public void loadAndBindAllAppsItems(@NonNull UserManagerState ums, 135 @NonNull BgDataModel.Callbacks[] callbacks, 136 @NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { 137 loadAndBindItems(ums, pinnedShortcuts, callbacks, mIDP.numDatabaseAllAppsColumns, 138 mAllAppsState); 139 } 140 141 @WorkerThread loadAndBindItems(@onNull UserManagerState ums, @NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, @NonNull BgDataModel.Callbacks[] callbacks, int numColumns, @NonNull PredictorState state)142 private void loadAndBindItems(@NonNull UserManagerState ums, 143 @NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, 144 @NonNull BgDataModel.Callbacks[] callbacks, 145 int numColumns, @NonNull PredictorState state) { 146 // TODO: Implement caching and preloading 147 148 WorkspaceItemFactory factory = 149 new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, numColumns, state.containerId); 150 FixedContainerItems fci = new FixedContainerItems(state.containerId, 151 state.storage.read(mApp.getContext(), factory, ums.allUsers::get)); 152 if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) { 153 bindPredictionItems(callbacks, fci); 154 } 155 mDataModel.extraItems.put(state.containerId, fci); 156 } 157 158 @CallSuper 159 @Override loadAndBindOtherItems(@onNull BgDataModel.Callbacks[] callbacks)160 public void loadAndBindOtherItems(@NonNull BgDataModel.Callbacks[] callbacks) { 161 FixedContainerItems widgetPredictionFCI = new FixedContainerItems( 162 mWidgetsRecommendationState.containerId, new ArrayList<>()); 163 164 // Widgets prediction isn't used frequently. And thus, it is not persisted on disk. 165 mDataModel.extraItems.put(mWidgetsRecommendationState.containerId, widgetPredictionFCI); 166 167 bindPredictionItems(callbacks, widgetPredictionFCI); 168 loadStringCache(mDataModel.stringCache); 169 } 170 171 @AnyThread bindPredictionItems(@onNull BgDataModel.Callbacks[] callbacks, @NonNull FixedContainerItems fci)172 private void bindPredictionItems(@NonNull BgDataModel.Callbacks[] callbacks, 173 @NonNull FixedContainerItems fci) { 174 Executors.MAIN_EXECUTOR.execute(() -> { 175 for (BgDataModel.Callbacks c : callbacks) { 176 c.bindExtraContainerItems(fci); 177 } 178 }); 179 } 180 181 @Override 182 @WorkerThread bindAllModelExtras(@onNull BgDataModel.Callbacks[] callbacks)183 public void bindAllModelExtras(@NonNull BgDataModel.Callbacks[] callbacks) { 184 Iterable<FixedContainerItems> containerItems; 185 synchronized (mDataModel.extraItems) { 186 containerItems = mDataModel.extraItems.clone(); 187 } 188 Executors.MAIN_EXECUTOR.execute(() -> { 189 for (BgDataModel.Callbacks c : callbacks) { 190 for (FixedContainerItems fci : containerItems) { 191 c.bindExtraContainerItems(fci); 192 } 193 } 194 }); 195 } 196 markActive()197 public void markActive() { 198 super.markActive(); 199 mActive = true; 200 } 201 202 @Override workspaceLoadComplete()203 public void workspaceLoadComplete() { 204 super.workspaceLoadComplete(); 205 recreatePredictors(); 206 } 207 208 @Override 209 @WorkerThread modelLoadComplete()210 public void modelLoadComplete() { 211 super.modelLoadComplete(); 212 213 // Log snapshot of the model 214 SharedPreferences prefs = getDevicePrefs(mApp.getContext()); 215 long lastSnapshotTimeMillis = prefs.getLong(LAST_SNAPSHOT_TIME_MILLIS, 0); 216 // Log snapshot only if previous snapshot was older than a day 217 long now = System.currentTimeMillis(); 218 if (now - lastSnapshotTimeMillis < DAY_IN_MILLIS) { 219 if (IS_DEBUG) { 220 String elapsedTime = formatElapsedTime((now - lastSnapshotTimeMillis) / 1000); 221 Log.d(TAG, String.format( 222 "Skipped snapshot logging since previous snapshot was %s old.", 223 elapsedTime)); 224 } 225 } else { 226 IntSparseArrayMap<ItemInfo> itemsIdMap; 227 synchronized (mDataModel) { 228 itemsIdMap = mDataModel.itemsIdMap.clone(); 229 } 230 InstanceId instanceId = new InstanceIdSequence().newInstanceId(); 231 for (ItemInfo info : itemsIdMap) { 232 FolderInfo parent = getContainer(info, itemsIdMap); 233 StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId); 234 } 235 additionalSnapshotEvents(instanceId); 236 prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply(); 237 } 238 239 // Only register for launcher snapshot logging if this is the primary ModelDelegate 240 // instance, as there will be additional instances that may be destroyed at any time. 241 if (mIsPrimaryInstance) { 242 registerSnapshotLoggingCallback(); 243 } 244 } 245 additionalSnapshotEvents(InstanceId snapshotInstanceId)246 protected void additionalSnapshotEvents(InstanceId snapshotInstanceId){} 247 248 /** 249 * Registers a callback to log launcher workspace layout using Statsd pulled atom. 250 */ registerSnapshotLoggingCallback()251 protected void registerSnapshotLoggingCallback() { 252 if (mStatsManager == null) { 253 Log.d(TAG, "Failed to get StatsManager"); 254 } 255 256 try { 257 mStatsManager.setPullAtomCallback( 258 SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT, 259 null /* PullAtomMetadata */, 260 MODEL_EXECUTOR, 261 (i, eventList) -> { 262 InstanceId instanceId = new InstanceIdSequence().newInstanceId(); 263 IntSparseArrayMap<ItemInfo> itemsIdMap; 264 synchronized (mDataModel) { 265 itemsIdMap = mDataModel.itemsIdMap.clone(); 266 } 267 268 for (ItemInfo info : itemsIdMap) { 269 FolderInfo parent = getContainer(info, itemsIdMap); 270 LauncherAtom.ItemInfo itemInfo = info.buildProto(parent); 271 Log.d(TAG, itemInfo.toString()); 272 StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo, 273 instanceId); 274 eventList.add(statsEvent); 275 } 276 Log.d(TAG, 277 String.format( 278 "Successfully logged %d workspace items with instanceId=%d", 279 itemsIdMap.size(), instanceId.getId())); 280 additionalSnapshotEvents(instanceId); 281 SettingsChangeLogger.INSTANCE.get(mContext).logSnapshot(instanceId); 282 return StatsManager.PULL_SUCCESS; 283 } 284 ); 285 Log.d(TAG, "Successfully registered for launcher snapshot logging!"); 286 } catch (RuntimeException e) { 287 Log.e(TAG, "Failed to register launcher snapshot logging callback with StatsManager", 288 e); 289 } 290 } 291 getContainer(ItemInfo info, IntSparseArrayMap<ItemInfo> itemsIdMap)292 private static FolderInfo getContainer(ItemInfo info, IntSparseArrayMap<ItemInfo> itemsIdMap) { 293 if (info.container > 0) { 294 ItemInfo containerInfo = itemsIdMap.get(info.container); 295 296 if (!(containerInfo instanceof FolderInfo)) { 297 Log.e(TAG, String.format( 298 "Item info: %s found with invalid container: %s", 299 info, 300 containerInfo)); 301 } 302 // Allow crash to help debug b/173838775 303 return (FolderInfo) containerInfo; 304 } 305 return null; 306 } 307 308 @Override validateData()309 public void validateData() { 310 super.validateData(); 311 if (mAllAppsState.predictor != null) { 312 mAllAppsState.predictor.requestPredictionUpdate(); 313 } 314 if (mWidgetsRecommendationState.predictor != null) { 315 mWidgetsRecommendationState.predictor.requestPredictionUpdate(); 316 } 317 } 318 319 @Override destroy()320 public void destroy() { 321 super.destroy(); 322 mActive = false; 323 StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer); 324 if (mIsPrimaryInstance) { 325 mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT); 326 } 327 destroyPredictors(); 328 } 329 destroyPredictors()330 private void destroyPredictors() { 331 mAllAppsState.destroyPredictor(); 332 mHotseatState.destroyPredictor(); 333 mWidgetsRecommendationState.destroyPredictor(); 334 } 335 336 @WorkerThread recreatePredictors()337 private void recreatePredictors() { 338 destroyPredictors(); 339 if (!mActive) { 340 return; 341 } 342 Context context = mApp.getContext(); 343 AppPredictionManager apm = context.getSystemService(AppPredictionManager.class); 344 if (apm == null) { 345 return; 346 } 347 348 registerPredictor(mAllAppsState, apm.createAppPredictionSession( 349 new AppPredictionContext.Builder(context) 350 .setUiSurface("home") 351 .setPredictedTargetCount(mIDP.numDatabaseAllAppsColumns) 352 .build())); 353 354 // TODO: get bundle 355 registerHotseatPredictor(apm, context); 356 357 registerWidgetsPredictor(apm.createAppPredictionSession( 358 new AppPredictionContext.Builder(context) 359 .setUiSurface("widgets") 360 .setExtras(getBundleForWidgetsOnWorkspace(context, mDataModel)) 361 .setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION) 362 .build())); 363 } 364 365 @WorkerThread recreateHotseatPredictor()366 private void recreateHotseatPredictor() { 367 mHotseatState.destroyPredictor(); 368 if (!mActive) { 369 return; 370 } 371 Context context = mApp.getContext(); 372 AppPredictionManager apm = context.getSystemService(AppPredictionManager.class); 373 if (apm == null) { 374 return; 375 } 376 registerHotseatPredictor(apm, context); 377 } 378 registerHotseatPredictor(AppPredictionManager apm, Context context)379 private void registerHotseatPredictor(AppPredictionManager apm, Context context) { 380 registerPredictor(mHotseatState, apm.createAppPredictionSession( 381 new AppPredictionContext.Builder(context) 382 .setUiSurface("hotseat") 383 .setPredictedTargetCount(mIDP.numDatabaseHotseatIcons) 384 .setExtras(convertDataModelToAppTargetBundle(context, mDataModel)) 385 .build())); 386 } 387 registerPredictor(PredictorState state, AppPredictor predictor)388 private void registerPredictor(PredictorState state, AppPredictor predictor) { 389 state.setTargets(Collections.emptyList()); 390 state.predictor = predictor; 391 state.predictor.registerPredictionUpdates( 392 MODEL_EXECUTOR, t -> handleUpdate(state, t)); 393 state.predictor.requestPredictionUpdate(); 394 } 395 handleUpdate(PredictorState state, List<AppTarget> targets)396 private void handleUpdate(PredictorState state, List<AppTarget> targets) { 397 if (state.setTargets(targets)) { 398 // No diff, skip 399 return; 400 } 401 mApp.getModel().enqueueModelUpdateTask(new PredictionUpdateTask(state, targets)); 402 } 403 registerWidgetsPredictor(AppPredictor predictor)404 private void registerWidgetsPredictor(AppPredictor predictor) { 405 mWidgetsRecommendationState.predictor = predictor; 406 mWidgetsRecommendationState.predictor.registerPredictionUpdates( 407 MODEL_EXECUTOR, targets -> { 408 if (mWidgetsRecommendationState.setTargets(targets)) { 409 // No diff, skip 410 return; 411 } 412 mApp.getModel().enqueueModelUpdateTask( 413 new WidgetsPredictionUpdateTask(mWidgetsRecommendationState, targets)); 414 }); 415 mWidgetsRecommendationState.predictor.requestPredictionUpdate(); 416 } 417 418 @VisibleForTesting onAppTargetEvent(AppTargetEvent event, int client)419 void onAppTargetEvent(AppTargetEvent event, int client) { 420 PredictorState state; 421 switch(client) { 422 case CONTAINER_PREDICTION: 423 state = mAllAppsState; 424 break; 425 case CONTAINER_WIDGETS_PREDICTION: 426 state = mWidgetsRecommendationState; 427 break; 428 case CONTAINER_HOTSEAT_PREDICTION: 429 default: 430 state = mHotseatState; 431 break; 432 } 433 if (state.predictor != null) { 434 state.predictor.notifyAppTargetEvent(event); 435 Log.d(TAG, "notifyAppTargetEvent action=" + event.getAction() 436 + " launchLocation=" + event.getLaunchLocation()); 437 if (state == mHotseatState 438 && (event.getAction() == AppTargetEvent.ACTION_PIN 439 || event.getAction() == AppTargetEvent.ACTION_UNPIN)) { 440 // Recreate hot seat predictor when we need to query for hot seat due to pin or 441 // unpin app icons. 442 recreateHotseatPredictor(); 443 } 444 } 445 } 446 getBundleForWidgetsOnWorkspace(Context context, BgDataModel dataModel)447 private Bundle getBundleForWidgetsOnWorkspace(Context context, BgDataModel dataModel) { 448 Bundle bundle = new Bundle(); 449 ArrayList<AppTargetEvent> widgetEvents = 450 dataModel.getAllWorkspaceItems().stream() 451 .filter(PredictionHelper::isTrackedForWidgetPrediction) 452 .map(item -> { 453 AppTarget target = getAppTargetFromItemInfo(context, item); 454 if (target == null) return null; 455 return wrapAppTargetWithItemLocation( 456 target, AppTargetEvent.ACTION_PIN, item); 457 }) 458 .filter(Objects::nonNull) 459 .collect(toCollection(ArrayList::new)); 460 bundle.putParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, widgetEvents); 461 return bundle; 462 } 463 464 static class PredictorState { 465 466 public final int containerId; 467 public final PersistedItemArray<ItemInfo> storage; 468 public AppPredictor predictor; 469 470 private List<AppTarget> mLastTargets; 471 PredictorState(int containerId, String storageName)472 PredictorState(int containerId, String storageName) { 473 this.containerId = containerId; 474 storage = new PersistedItemArray<>(storageName); 475 mLastTargets = Collections.emptyList(); 476 } 477 destroyPredictor()478 public void destroyPredictor() { 479 if (predictor != null) { 480 predictor.destroy(); 481 predictor = null; 482 } 483 } 484 485 /** 486 * Sets the new targets and returns true if it was the same as before. 487 */ setTargets(List<AppTarget> newTargets)488 boolean setTargets(List<AppTarget> newTargets) { 489 List<AppTarget> oldTargets = mLastTargets; 490 mLastTargets = newTargets; 491 492 int size = oldTargets.size(); 493 return size == newTargets.size() && IntStream.range(0, size) 494 .allMatch(i -> areAppTargetsSame(oldTargets.get(i), newTargets.get(i))); 495 } 496 } 497 498 /** 499 * Compares two targets for the properties which we care about 500 */ areAppTargetsSame(AppTarget t1, AppTarget t2)501 private static boolean areAppTargetsSame(AppTarget t1, AppTarget t2) { 502 if (!Objects.equals(t1.getPackageName(), t2.getPackageName()) 503 || !Objects.equals(t1.getUser(), t2.getUser()) 504 || !Objects.equals(t1.getClassName(), t2.getClassName())) { 505 return false; 506 } 507 508 ShortcutInfo s1 = t1.getShortcutInfo(); 509 ShortcutInfo s2 = t2.getShortcutInfo(); 510 if (s1 != null) { 511 if (s2 == null || !Objects.equals(s1.getId(), s2.getId())) { 512 return false; 513 } 514 } else if (s2 != null) { 515 return false; 516 } 517 return true; 518 } 519 520 private static class WorkspaceItemFactory implements PersistedItemArray.ItemFactory<ItemInfo> { 521 522 private final LauncherAppState mAppState; 523 private final UserManagerState mUMS; 524 private final Map<ShortcutKey, ShortcutInfo> mPinnedShortcuts; 525 private final int mMaxCount; 526 private final int mContainer; 527 528 private int mReadCount = 0; 529 WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, int maxCount, int container)530 protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums, 531 Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, int maxCount, int container) { 532 mAppState = appState; 533 mUMS = ums; 534 mPinnedShortcuts = pinnedShortcuts; 535 mMaxCount = maxCount; 536 mContainer = container; 537 } 538 539 @Nullable 540 @Override createInfo(int itemType, UserHandle user, Intent intent)541 public ItemInfo createInfo(int itemType, UserHandle user, Intent intent) { 542 if (mReadCount >= mMaxCount) { 543 return null; 544 } 545 switch (itemType) { 546 case ITEM_TYPE_APPLICATION: { 547 LauncherActivityInfo lai = mAppState.getContext() 548 .getSystemService(LauncherApps.class) 549 .resolveActivity(intent, user); 550 if (lai == null) { 551 return null; 552 } 553 AppInfo info = new AppInfo(lai, user, mUMS.isUserQuiet(user)); 554 info.container = mContainer; 555 mAppState.getIconCache().getTitleAndIcon(info, lai, false); 556 mReadCount++; 557 return info.makeWorkspaceItem(mAppState.getContext()); 558 } 559 case ITEM_TYPE_DEEP_SHORTCUT: { 560 ShortcutKey key = ShortcutKey.fromIntent(intent, user); 561 if (key == null) { 562 return null; 563 } 564 ShortcutInfo si = mPinnedShortcuts.get(key); 565 if (si == null) { 566 return null; 567 } 568 WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mAppState.getContext()); 569 wii.container = mContainer; 570 mAppState.getIconCache().getShortcutIcon(wii, si); 571 mReadCount++; 572 return wii; 573 } 574 } 575 return null; 576 } 577 } 578 } 579