• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.hybridhotseat;
17 
18 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
19 import static com.android.launcher3.LauncherState.NORMAL;
20 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
21 import static com.android.launcher3.hybridhotseat.HotseatEduController.getSettingsIntent;
22 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
23 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
24 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
25 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
26 
27 import android.animation.Animator;
28 import android.animation.AnimatorSet;
29 import android.animation.ObjectAnimator;
30 import android.content.ComponentName;
31 import android.util.Log;
32 import android.view.HapticFeedbackConstants;
33 import android.view.View;
34 import android.view.ViewGroup;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 import androidx.annotation.VisibleForTesting;
39 
40 import com.android.launcher3.DeviceProfile;
41 import com.android.launcher3.DragSource;
42 import com.android.launcher3.DropTarget;
43 import com.android.launcher3.Hotseat;
44 import com.android.launcher3.LauncherSettings;
45 import com.android.launcher3.R;
46 import com.android.launcher3.anim.AnimationSuccessListener;
47 import com.android.launcher3.dragndrop.DragController;
48 import com.android.launcher3.dragndrop.DragOptions;
49 import com.android.launcher3.graphics.DragPreviewProvider;
50 import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
51 import com.android.launcher3.logger.LauncherAtom.PredictedHotseatContainer;
52 import com.android.launcher3.logging.InstanceId;
53 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
54 import com.android.launcher3.model.data.ItemInfo;
55 import com.android.launcher3.model.data.WorkspaceItemInfo;
56 import com.android.launcher3.popup.SystemShortcut;
57 import com.android.launcher3.testing.TestLogging;
58 import com.android.launcher3.testing.shared.TestProtocol;
59 import com.android.launcher3.touch.ItemLongClickListener;
60 import com.android.launcher3.uioverrides.PredictedAppIcon;
61 import com.android.launcher3.uioverrides.QuickstepLauncher;
62 import com.android.launcher3.util.OnboardingPrefs;
63 import com.android.launcher3.views.Snackbar;
64 
65 import java.io.PrintWriter;
66 import java.util.ArrayList;
67 import java.util.Collections;
68 import java.util.List;
69 import java.util.StringJoiner;
70 import java.util.function.Predicate;
71 import java.util.stream.Collectors;
72 
73 /**
74  * Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows
75  * pinning of predicted apps and manages replacement of predicted apps with user drag.
76  */
77 public class HotseatPredictionController implements DragController.DragListener,
78         SystemShortcut.Factory<QuickstepLauncher>, DeviceProfile.OnDeviceProfileChangeListener,
79         DragSource, ViewGroup.OnHierarchyChangeListener {
80 
81     private static final String TAG = "HotseatPredictionController";
82     private static final int FLAG_UPDATE_PAUSED = 1 << 0;
83     private static final int FLAG_DRAG_IN_PROGRESS = 1 << 1;
84     private static final int FLAG_FILL_IN_PROGRESS = 1 << 2;
85     private static final int FLAG_REMOVING_PREDICTED_ICON = 1 << 3;
86 
87     private int mHotSeatItemsCount;
88 
89     private QuickstepLauncher mLauncher;
90     private final Hotseat mHotseat;
91     private final Runnable mUpdateFillIfNotLoading = this::updateFillIfNotLoading;
92 
93     private List<ItemInfo> mPredictedItems = Collections.emptyList();
94 
95     private AnimatorSet mIconRemoveAnimators;
96     private int mPauseFlags = 0;
97 
98     private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
99 
100     private boolean mEnableHotseatLongPressTipForTesting = true;
101 
102     private final View.OnLongClickListener mPredictionLongClickListener = v -> {
103         if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
104         if (mLauncher.getWorkspace().isSwitchingState()) return false;
105 
106         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onWorkspaceItemLongClick");
107         if (mEnableHotseatLongPressTipForTesting && !mLauncher.getOnboardingPrefs().getBoolean(
108                 OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN)) {
109             Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
110                     R.string.hotseat_prediction_settings, null,
111                     () -> mLauncher.startActivity(getSettingsIntent()));
112             mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN);
113             mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
114             return true;
115         }
116 
117         // Start the drag
118         // Use a new itemInfo so that the original predicted item is stable
119         WorkspaceItemInfo dragItem = new WorkspaceItemInfo((WorkspaceItemInfo) v.getTag());
120         v.setVisibility(View.INVISIBLE);
121         mLauncher.getWorkspace().beginDragShared(
122                 v, null, this, dragItem, new DragPreviewProvider(v), new DragOptions());
123         return true;
124     };
125 
HotseatPredictionController(QuickstepLauncher launcher)126     public HotseatPredictionController(QuickstepLauncher launcher) {
127         mLauncher = launcher;
128         mHotseat = launcher.getHotseat();
129         mHotSeatItemsCount = mLauncher.getDeviceProfile().numShownHotseatIcons;
130         mLauncher.getDragController().addDragListener(this);
131 
132         launcher.addOnDeviceProfileChangeListener(this);
133         mHotseat.getShortcutsAndWidgets().setOnHierarchyChangeListener(this);
134     }
135 
136     @Override
onChildViewAdded(View parent, View child)137     public void onChildViewAdded(View parent, View child) {
138         onHotseatHierarchyChanged();
139     }
140 
141     @Override
onChildViewRemoved(View parent, View child)142     public void onChildViewRemoved(View parent, View child) {
143         onHotseatHierarchyChanged();
144     }
145 
146     /** Enables/disabled the hotseat prediction icon long press edu for testing. */
147     @VisibleForTesting
enableHotseatEdu(boolean enable)148     public void enableHotseatEdu(boolean enable) {
149         mEnableHotseatLongPressTipForTesting = enable;
150     }
151 
onHotseatHierarchyChanged()152     private void onHotseatHierarchyChanged() {
153         if (mPauseFlags == 0 && !mLauncher.isWorkspaceLoading()) {
154             // Post update after a single frame to avoid layout within layout
155             MAIN_EXECUTOR.getHandler().removeCallbacks(mUpdateFillIfNotLoading);
156             MAIN_EXECUTOR.getHandler().post(mUpdateFillIfNotLoading);
157         }
158     }
159 
updateFillIfNotLoading()160     private void updateFillIfNotLoading() {
161         if (mPauseFlags == 0 && !mLauncher.isWorkspaceLoading()) {
162             fillGapsWithPrediction(true);
163         }
164     }
165 
166     /**
167      * Shows appropriate hotseat education based on prediction enabled and migration states.
168      */
showEdu()169     public void showEdu() {
170         mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
171             HotseatEduController eduController = new HotseatEduController(mLauncher);
172             eduController.setPredictedApps(mPredictedItems.stream()
173                     .map(i -> (WorkspaceItemInfo) i)
174                     .collect(Collectors.toList()));
175             eduController.showEdu();
176         }));
177     }
178 
179     /**
180      * Returns if hotseat client has predictions
181      */
hasPredictions()182     public boolean hasPredictions() {
183         return !mPredictedItems.isEmpty();
184     }
185 
fillGapsWithPrediction()186     private void fillGapsWithPrediction() {
187         fillGapsWithPrediction(false);
188     }
189 
fillGapsWithPrediction(boolean animate)190     private void fillGapsWithPrediction(boolean animate) {
191         Log.d(TAG, "fillGapsWithPrediction flags: " + getStateString(mPauseFlags));
192         if (mPauseFlags != 0) {
193             return;
194         }
195 
196         int predictionIndex = 0;
197         int numViewsAnimated = 0;
198         ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
199         // make sure predicted icon removal and filling predictions don't step on each other
200         if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
201             mIconRemoveAnimators.addListener(new AnimationSuccessListener() {
202                 @Override
203                 public void onAnimationSuccess(Animator animator) {
204                     fillGapsWithPrediction(animate);
205                     mIconRemoveAnimators.removeListener(this);
206                 }
207             });
208             return;
209         }
210 
211         mPauseFlags |= FLAG_FILL_IN_PROGRESS;
212         for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
213             View child = mHotseat.getChildAt(
214                     mHotseat.getCellXFromOrder(rank),
215                     mHotseat.getCellYFromOrder(rank));
216             Log.d(TAG, "Hotseat app child is: " + child + " and isPredictedIcon() evaluates to"
217                     + ": " + isPredictedIcon(child));
218 
219             if (child != null && !isPredictedIcon(child)) {
220                 continue;
221             }
222             if (mPredictedItems.size() <= predictionIndex) {
223                 // Remove predicted apps from the past
224                 Log.d(TAG, "Remove predicted apps from the past\nPrediction Index: "
225                         + predictionIndex);
226                 if (isPredictedIcon(child)) {
227                     mHotseat.removeView(child);
228                 }
229                 continue;
230             }
231             WorkspaceItemInfo predictedItem =
232                     (WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
233             Log.d(TAG, "Predicted item is: " + predictedItem);
234             if (child != null) {
235                 Log.d(TAG, "Predicted item is enabled: " + child.isEnabled());
236             }
237 
238             if (isPredictedIcon(child) && child.isEnabled()) {
239                 PredictedAppIcon icon = (PredictedAppIcon) child;
240                 boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
241                 icon.applyFromWorkspaceItem(predictedItem, animateIconChange, numViewsAnimated);
242                 if (animateIconChange) {
243                     numViewsAnimated++;
244                 }
245                 icon.finishBinding(mPredictionLongClickListener);
246             } else {
247                 newItems.add(predictedItem);
248             }
249             preparePredictionInfo(predictedItem, rank);
250         }
251         bindItems(newItems, animate);
252 
253         mPauseFlags &= ~FLAG_FILL_IN_PROGRESS;
254     }
255 
bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate)256     private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate) {
257         Log.d(TAG, "bindItems to hotseat: " + itemsToAdd);
258         AnimatorSet animationSet = new AnimatorSet();
259         for (WorkspaceItemInfo item : itemsToAdd) {
260             PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
261             mLauncher.getWorkspace().addInScreenFromBind(icon, item);
262             icon.finishBinding(mPredictionLongClickListener);
263             if (animate) {
264                 animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1));
265             }
266         }
267         if (animate) {
268             animationSet.addListener(
269                     forSuccessCallback(this::removeOutlineDrawings));
270             animationSet.start();
271         } else {
272             removeOutlineDrawings();
273         }
274     }
275 
removeOutlineDrawings()276     private void removeOutlineDrawings() {
277         if (mOutlineDrawings.isEmpty()) return;
278         for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
279             mHotseat.removeDelegatedCellDrawing(outlineDrawing);
280         }
281         mHotseat.invalidate();
282         mOutlineDrawings.clear();
283     }
284 
285 
286     /**
287      * Unregisters callbacks and frees resources
288      */
destroy()289     public void destroy() {
290         mLauncher.removeOnDeviceProfileChangeListener(this);
291     }
292 
293     /**
294      * start and pauses predicted apps update on the hotseat
295      */
setPauseUIUpdate(boolean paused)296     public void setPauseUIUpdate(boolean paused) {
297         Log.d(TAG, "setPauseUIUpdate parameter `paused` is " + paused);
298         mPauseFlags = paused
299                 ? (mPauseFlags | FLAG_UPDATE_PAUSED)
300                 : (mPauseFlags & ~FLAG_UPDATE_PAUSED);
301         if (!paused) {
302             fillGapsWithPrediction();
303         }
304     }
305 
306     /**
307      * Sets or updates the predicted items
308      */
setPredictedItems(FixedContainerItems items)309     public void setPredictedItems(FixedContainerItems items) {
310         mPredictedItems = new ArrayList(items.items);
311         if (mPredictedItems.isEmpty()) {
312             Log.d(TAG, "Predicted items is initially empty");
313             HotseatRestoreHelper.restoreBackup(mLauncher);
314         }
315         Log.d(TAG, "Predicted items: " + mPredictedItems);
316         fillGapsWithPrediction();
317     }
318 
319     /**
320      * Pins a predicted app icon into place.
321      */
pinPrediction(ItemInfo info)322     public void pinPrediction(ItemInfo info) {
323         PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
324                 mHotseat.getCellXFromOrder(info.rank),
325                 mHotseat.getCellYFromOrder(info.rank));
326         if (icon == null) {
327             return;
328         }
329         WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info);
330         mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo,
331                 LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
332                 workspaceItemInfo.cellX, workspaceItemInfo.cellY);
333         ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
334         icon.pin(workspaceItemInfo);
335         mLauncher.getStatsLogManager().logger()
336                 .withItemInfo(workspaceItemInfo)
337                 .log(LAUNCHER_HOTSEAT_PREDICTION_PINNED);
338     }
339 
getPredictedIcons()340     private List<PredictedAppIcon> getPredictedIcons() {
341         List<PredictedAppIcon> icons = new ArrayList<>();
342         ViewGroup vg = mHotseat.getShortcutsAndWidgets();
343         for (int i = 0; i < vg.getChildCount(); i++) {
344             View child = vg.getChildAt(i);
345             if (isPredictedIcon(child)) {
346                 icons.add((PredictedAppIcon) child);
347             }
348         }
349         return icons;
350     }
351 
removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines, DropTarget.DragObject dragObject)352     private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines,
353             DropTarget.DragObject dragObject) {
354         if (mIconRemoveAnimators != null) {
355             mIconRemoveAnimators.end();
356         }
357         mIconRemoveAnimators = new AnimatorSet();
358         removeOutlineDrawings();
359         for (PredictedAppIcon icon : getPredictedIcons()) {
360             if (!icon.isEnabled()) {
361                 continue;
362             }
363             if (dragObject.dragSource == this && icon.equals(dragObject.originalView)) {
364                 removeIconWithoutNotify(icon);
365                 continue;
366             }
367             int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
368             outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
369                     mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
370             icon.setEnabled(false);
371             ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0);
372             animator.addListener(new AnimationSuccessListener() {
373                 @Override
374                 public void onAnimationSuccess(Animator animator) {
375                     if (icon.getParent() != null) {
376                         removeIconWithoutNotify(icon);
377                     }
378                 }
379             });
380             mIconRemoveAnimators.play(animator);
381         }
382         mIconRemoveAnimators.start();
383     }
384 
385     /**
386      * Removes icon while suppressing any extra tasks performed on view-hierarchy changes.
387      * This avoids recursive/redundant updates as the control updates the UI anyway after
388      * it's animation.
389      */
removeIconWithoutNotify(PredictedAppIcon icon)390     private void removeIconWithoutNotify(PredictedAppIcon icon) {
391         mPauseFlags |= FLAG_REMOVING_PREDICTED_ICON;
392         mHotseat.removeView(icon);
393         mPauseFlags &= ~FLAG_REMOVING_PREDICTED_ICON;
394     }
395 
396     @Override
onDragStart(DropTarget.DragObject dragObject, DragOptions options)397     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
398         removePredictedApps(mOutlineDrawings, dragObject);
399         if (mOutlineDrawings.isEmpty()) return;
400         for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
401             mHotseat.addDelegatedCellDrawing(outlineDrawing);
402         }
403         mPauseFlags |= FLAG_DRAG_IN_PROGRESS;
404         mHotseat.invalidate();
405     }
406 
407     @Override
onDragEnd()408     public void onDragEnd() {
409         mPauseFlags &= ~FLAG_DRAG_IN_PROGRESS;
410         fillGapsWithPrediction(true);
411     }
412 
413     @Nullable
414     @Override
getShortcut(QuickstepLauncher activity, ItemInfo itemInfo, View originalView)415     public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
416             ItemInfo itemInfo, View originalView) {
417         if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
418             return null;
419         }
420         return new PinPrediction(activity, itemInfo, originalView);
421     }
422 
preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank)423     private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) {
424         itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
425         itemInfo.rank = rank;
426         itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
427         itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
428         itemInfo.screenId = rank;
429     }
430 
431     @Override
onDeviceProfileChanged(DeviceProfile profile)432     public void onDeviceProfileChanged(DeviceProfile profile) {
433         this.mHotSeatItemsCount = profile.numShownHotseatIcons;
434     }
435 
436     @Override
onDropCompleted(View target, DropTarget.DragObject d, boolean success)437     public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
438         //Does nothing
439     }
440 
441     /**
442      * Logs rank info based on current list of predicted items
443      */
logLaunchedAppRankingInfo(@onNull ItemInfo itemInfo, InstanceId instanceId)444     public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) {
445         ComponentName targetCN = itemInfo.getTargetComponent();
446         if (targetCN == null) {
447             return;
448         }
449         int rank = -1;
450         for (int i = mPredictedItems.size() - 1; i >= 0; i--) {
451             ItemInfo info = mPredictedItems.get(i);
452             if (targetCN.equals(info.getTargetComponent()) && itemInfo.user.equals(info.user)) {
453                 rank = i;
454                 break;
455             }
456         }
457         if (rank < 0) {
458             return;
459         }
460 
461         int cardinality = 0;
462         for (PredictedAppIcon icon : getPredictedIcons()) {
463             ItemInfo info = (ItemInfo) icon.getTag();
464             cardinality |= 1 << info.screenId;
465         }
466 
467         PredictedHotseatContainer.Builder containerBuilder = PredictedHotseatContainer.newBuilder();
468         containerBuilder.setCardinality(cardinality);
469         if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
470             containerBuilder.setIndex(rank);
471         }
472         mLauncher.getStatsLogManager().logger()
473                 .withInstanceId(instanceId)
474                 .withRank(rank)
475                 .withContainerInfo(ContainerInfo.newBuilder()
476                         .setPredictedHotseatContainer(containerBuilder)
477                         .build())
478                 .log(LAUNCHER_HOTSEAT_RANKED);
479     }
480 
481     /**
482      * Called when app/shortcut icon is removed by system. This is used to prune visible stale
483      * predictions while while waiting for AppAPrediction service to send new batch of predictions.
484      *
485      * @param matcher filter matching items that have been removed
486      */
onModelItemsRemoved(Predicate<ItemInfo> matcher)487     public void onModelItemsRemoved(Predicate<ItemInfo> matcher) {
488         if (mPredictedItems.removeIf(matcher)) {
489             fillGapsWithPrediction(true);
490         }
491     }
492 
493     /**
494      * Called when user completes adding item requiring a config activity to the hotseat
495      */
onDeferredDrop(int cellX, int cellY)496     public void onDeferredDrop(int cellX, int cellY) {
497         View child = mHotseat.getChildAt(cellX, cellY);
498         if (child instanceof PredictedAppIcon) {
499             removeIconWithoutNotify((PredictedAppIcon) child);
500         }
501     }
502 
503     private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
504 
PinPrediction(QuickstepLauncher target, ItemInfo itemInfo, View originalView)505         private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo, View originalView) {
506             super(R.drawable.ic_pin, R.string.pin_prediction, target,
507                     itemInfo, originalView);
508         }
509 
510         @Override
onClick(View view)511         public void onClick(View view) {
512             dismissTaskMenuView(mTarget);
513             pinPrediction(mItemInfo);
514         }
515     }
516 
isPredictedIcon(View view)517     private static boolean isPredictedIcon(View view) {
518         return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
519                 && ((WorkspaceItemInfo) view.getTag()).container
520                 == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
521     }
522 
getStateString(int flags)523     private static String getStateString(int flags) {
524         StringJoiner str = new StringJoiner("|");
525         appendFlag(str, flags, FLAG_UPDATE_PAUSED, "FLAG_UPDATE_PAUSED");
526         appendFlag(str, flags, FLAG_DRAG_IN_PROGRESS, "FLAG_DRAG_IN_PROGRESS");
527         appendFlag(str, flags, FLAG_FILL_IN_PROGRESS, "FLAG_FILL_IN_PROGRESS");
528         appendFlag(str, flags, FLAG_REMOVING_PREDICTED_ICON,
529                 "FLAG_REMOVING_PREDICTED_ICON");
530         return str.toString();
531     }
532 
dump(String prefix, PrintWriter writer)533     public void dump(String prefix, PrintWriter writer) {
534         writer.println(prefix + this.getClass().getSimpleName());
535         writer.println(prefix + "\tFlags: " + getStateString(mPauseFlags));
536         writer.println(prefix + "\tmHotSeatItemsCount: " + mHotSeatItemsCount);
537         writer.println(prefix + "\tmPredictedItems: " + mPredictedItems);
538     }
539 }
540