1 /*
2  * Copyright (C) 2018 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.systemui.recents;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
21 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
22 
23 import static com.android.systemui.Prefs.Key.DISMISSED_RECENTS_SWIPE_UP_ONBOARDING_COUNT;
24 import static com.android.systemui.Prefs.Key.HAS_DISMISSED_RECENTS_QUICK_SCRUB_ONBOARDING_ONCE;
25 import static com.android.systemui.Prefs.Key.HAS_SEEN_RECENTS_QUICK_SCRUB_ONBOARDING;
26 import static com.android.systemui.Prefs.Key.HAS_SEEN_RECENTS_SWIPE_UP_ONBOARDING;
27 import static com.android.systemui.Prefs.Key.OVERVIEW_OPENED_COUNT;
28 import static com.android.systemui.Prefs.Key.OVERVIEW_OPENED_FROM_HOME_COUNT;
29 import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS;
30 import static com.android.systemui.shared.system.LauncherEventUtil
31         .RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
32 import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP;
33 import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
34 
35 import android.annotation.StringRes;
36 import android.annotation.TargetApi;
37 import android.app.ActivityManager;
38 import android.content.BroadcastReceiver;
39 import android.content.ComponentName;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.res.Configuration;
44 import android.content.res.Resources;
45 import android.graphics.CornerPathEffect;
46 import android.graphics.Paint;
47 import android.graphics.PixelFormat;
48 import android.graphics.drawable.ShapeDrawable;
49 import android.os.Build;
50 import android.os.RemoteException;
51 import android.os.SystemProperties;
52 import android.os.UserManager;
53 import android.util.TypedValue;
54 import android.view.Gravity;
55 import android.view.LayoutInflater;
56 import android.view.View;
57 import android.view.ViewGroup;
58 import android.view.WindowManager;
59 import android.view.animation.AccelerateInterpolator;
60 import android.view.animation.DecelerateInterpolator;
61 import android.widget.ImageView;
62 import android.widget.TextView;
63 
64 import com.android.systemui.Prefs;
65 import com.android.systemui.R;
66 import com.android.systemui.shared.recents.IOverviewProxy;
67 import com.android.systemui.shared.system.ActivityManagerWrapper;
68 import com.android.systemui.shared.system.QuickStepContract;
69 import com.android.systemui.shared.system.TaskStackChangeListener;
70 
71 import java.io.PrintWriter;
72 import java.util.Collections;
73 import java.util.HashSet;
74 import java.util.Set;
75 
76 /**
77  * Shows onboarding for the new recents interaction in P (codenamed quickstep).
78  */
79 @TargetApi(Build.VERSION_CODES.P)
80 public class RecentsOnboarding {
81 
82     private static final String TAG = "RecentsOnboarding";
83     private static final boolean RESET_PREFS_FOR_DEBUG = false;
84     private static final boolean ONBOARDING_ENABLED = true;
85     private static final long SHOW_DELAY_MS = 500;
86     private static final long SHOW_DURATION_MS = 300;
87     private static final long HIDE_DURATION_MS = 100;
88     // Show swipe-up tips after opening overview from home this number of times.
89     private static final int SWIPE_UP_SHOW_ON_OVERVIEW_OPENED_FROM_HOME_COUNT = 3;
90     // Show quick scrub tips after opening overview this number of times.
91     private static final int QUICK_SCRUB_SHOW_ON_OVERVIEW_OPENED_COUNT = 10;
92     // Maximum number of dismissals while still showing swipe-up tips.
93     private static final int MAX_DISMISSAL_ON_SWIPE_UP_SHOW = 2;
94     // Number of dismissals for swipe-up tips when exponential backoff starts.
95     private static final int BACKOFF_DISMISSAL_COUNT_ON_SWIPE_UP_SHOW = 1;
96     // After explicitly dismissing for <= BACKOFF_DISMISSAL_COUNT_ON_SWIPE_UP_SHOW times, show again
97     // after launching this number of apps for swipe-up tips.
98     private static final int SWIPE_UP_SHOW_ON_APP_LAUNCH_AFTER_DISMISS = 5;
99     // After explicitly dismissing for > BACKOFF_DISMISSAL_COUNT_ON_SWIPE_UP_SHOW but
100     // <= MAX_DISMISSAL_ON_SWIPE_UP_SHOW times, show again after launching this number of apps for
101     // swipe-up tips.
102     private static final int SWIPE_UP_SHOW_ON_APP_LAUNCH_AFTER_DISMISS_BACK_OFF = 40;
103 
104     private final Context mContext;
105     private final WindowManager mWindowManager;
106     private final OverviewProxyService mOverviewProxyService;
107     private Set<String> mBlacklistedPackages;
108     private final View mLayout;
109     private final TextView mTextView;
110     private final ImageView mDismissView;
111     private final View mArrowView;
112     private final int mOnboardingToastColor;
113     private final int mOnboardingToastArrowRadius;
114     private int mNavBarHeight;
115     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
116 
117     private boolean mOverviewProxyListenerRegistered;
118     private boolean mTaskListenerRegistered;
119     private boolean mLayoutAttachedToWindow;
120     private boolean mHasDismissedSwipeUpTip;
121     private boolean mHasDismissedQuickScrubTip;
122     private int mNumAppsLaunchedSinceSwipeUpTipDismiss;
123     private int mOverviewOpenedCountSinceQuickScrubTipDismiss;
124 
125     private final TaskStackChangeListener mTaskListener = new TaskStackChangeListener() {
126         private String mLastPackageName;
127 
128         @Override
129         public void onTaskCreated(int taskId, ComponentName componentName) {
130             onAppLaunch();
131         }
132 
133         @Override
134         public void onTaskMovedToFront(int taskId) {
135             onAppLaunch();
136         }
137 
138         private void onAppLaunch() {
139             ActivityManager.RunningTaskInfo info = ActivityManagerWrapper.getInstance()
140                     .getRunningTask(ACTIVITY_TYPE_UNDEFINED /* ignoreActivityType */);
141             if (info == null) {
142                 return;
143             }
144             if (mBlacklistedPackages.contains(info.baseActivity.getPackageName())) {
145                 hide(true);
146                 return;
147             }
148             if (info.baseActivity.getPackageName().equals(mLastPackageName)) {
149                 return;
150             }
151             mLastPackageName = info.baseActivity.getPackageName();
152             int activityType = info.configuration.windowConfiguration.getActivityType();
153             if (activityType == ACTIVITY_TYPE_STANDARD) {
154                 boolean alreadySeenSwipeUpOnboarding = hasSeenSwipeUpOnboarding();
155                 boolean alreadySeenQuickScrubsOnboarding = hasSeenQuickScrubOnboarding();
156                 if (alreadySeenSwipeUpOnboarding && alreadySeenQuickScrubsOnboarding) {
157                     onDisconnectedFromLauncher();
158                     return;
159                 }
160 
161                 boolean shouldLog = false;
162                 if (!alreadySeenSwipeUpOnboarding) {
163                     if (getOpenedOverviewFromHomeCount()
164                             >= SWIPE_UP_SHOW_ON_OVERVIEW_OPENED_FROM_HOME_COUNT) {
165                         if (mHasDismissedSwipeUpTip) {
166                             int hasDimissedSwipeUpOnboardingCount =
167                                     getDismissedSwipeUpOnboardingCount();
168                             if (hasDimissedSwipeUpOnboardingCount
169                                     > MAX_DISMISSAL_ON_SWIPE_UP_SHOW) {
170                                 return;
171                             }
172                             final int swipeUpShowOnAppLauncherAfterDismiss =
173                                     hasDimissedSwipeUpOnboardingCount
174                                             <= BACKOFF_DISMISSAL_COUNT_ON_SWIPE_UP_SHOW
175                                             ? SWIPE_UP_SHOW_ON_APP_LAUNCH_AFTER_DISMISS
176                                             : SWIPE_UP_SHOW_ON_APP_LAUNCH_AFTER_DISMISS_BACK_OFF;
177                             mNumAppsLaunchedSinceSwipeUpTipDismiss++;
178                             if (mNumAppsLaunchedSinceSwipeUpTipDismiss
179                                     >= swipeUpShowOnAppLauncherAfterDismiss) {
180                                 mNumAppsLaunchedSinceSwipeUpTipDismiss = 0;
181                                 shouldLog = show(R.string.recents_swipe_up_onboarding);
182                             }
183                         } else {
184                             shouldLog = show(R.string.recents_swipe_up_onboarding);
185                         }
186                         if (shouldLog) {
187                             notifyOnTip(VISIBLE, RECENTS_SWIPE_UP_ONBOARDING_TIP);
188                         }
189                     }
190                 } else {
191                     if (getOpenedOverviewCount() >= QUICK_SCRUB_SHOW_ON_OVERVIEW_OPENED_COUNT) {
192                         if (mHasDismissedQuickScrubTip) {
193                             if (mOverviewOpenedCountSinceQuickScrubTipDismiss
194                                     >= QUICK_SCRUB_SHOW_ON_OVERVIEW_OPENED_COUNT) {
195                                 mOverviewOpenedCountSinceQuickScrubTipDismiss = 0;
196                                 shouldLog = show(R.string.recents_quick_scrub_onboarding);
197                             }
198                         } else {
199                             shouldLog = show(R.string.recents_quick_scrub_onboarding);
200                         }
201                         if (shouldLog) {
202                             notifyOnTip(VISIBLE, RECENTS_QUICK_SCRUB_ONBOARDING_TIP);
203                         }
204                     }
205 
206                 }
207             } else {
208                 hide(false);
209             }
210         }
211     };
212 
213     private OverviewProxyService.OverviewProxyListener mOverviewProxyListener =
214             new OverviewProxyService.OverviewProxyListener() {
215                 @Override
216                 public void onOverviewShown(boolean fromHome) {
217                     if (!hasSeenSwipeUpOnboarding() && !fromHome) {
218                         setHasSeenSwipeUpOnboarding(true);
219                     }
220                     if (fromHome) {
221                         incrementOpenedOverviewFromHomeCount();
222                     }
223                     incrementOpenedOverviewCount();
224 
225                     if (getOpenedOverviewCount() >= QUICK_SCRUB_SHOW_ON_OVERVIEW_OPENED_COUNT) {
226                         if (mHasDismissedQuickScrubTip) {
227                             mOverviewOpenedCountSinceQuickScrubTipDismiss++;
228                         }
229                     }
230                 }
231 
232                 @Override
233                 public void onQuickStepStarted() {
234                     hide(true);
235                 }
236 
237                 @Override
238                 public void onQuickScrubStarted() {
239                     boolean alreadySeenQuickScrubsOnboarding = hasSeenQuickScrubOnboarding();
240                     if (!alreadySeenQuickScrubsOnboarding) {
241                         setHasSeenQuickScrubOnboarding(true);
242                     }
243                 }
244             };
245 
246     private final View.OnAttachStateChangeListener mOnAttachStateChangeListener
247             = new View.OnAttachStateChangeListener() {
248         @Override
249         public void onViewAttachedToWindow(View view) {
250             if (view == mLayout) {
251                 mContext.registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
252                 mLayoutAttachedToWindow = true;
253                 if (view.getTag().equals(R.string.recents_swipe_up_onboarding)) {
254                     mHasDismissedSwipeUpTip = false;
255                 } else {
256                     mHasDismissedQuickScrubTip = false;
257                 }
258             }
259         }
260 
261         @Override
262         public void onViewDetachedFromWindow(View view) {
263             if (view == mLayout) {
264                 mLayoutAttachedToWindow = false;
265                 if (view.getTag().equals(R.string.recents_quick_scrub_onboarding)) {
266                     mHasDismissedQuickScrubTip = true;
267                     if (hasDismissedQuickScrubOnboardingOnce()) {
268                         // If user dismisses the quick scrub tip twice, we consider user has seen it
269                         // and do not show it again.
270                         setHasSeenQuickScrubOnboarding(true);
271                     } else {
272                         setHasDismissedQuickScrubOnboardingOnce(true);
273                     }
274                     mOverviewOpenedCountSinceQuickScrubTipDismiss = 0;
275                 }
276                 mContext.unregisterReceiver(mReceiver);
277             }
278         }
279     };
280 
RecentsOnboarding(Context context, OverviewProxyService overviewProxyService)281     public RecentsOnboarding(Context context, OverviewProxyService overviewProxyService) {
282         mContext = context;
283         mOverviewProxyService = overviewProxyService;
284         final Resources res = context.getResources();
285         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
286         mBlacklistedPackages = new HashSet<>();
287         Collections.addAll(mBlacklistedPackages, res.getStringArray(
288                 R.array.recents_onboarding_blacklisted_packages));
289         mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_onboarding, null);
290         mTextView = mLayout.findViewById(R.id.onboarding_text);
291         mDismissView = mLayout.findViewById(R.id.dismiss);
292         mArrowView = mLayout.findViewById(R.id.arrow);
293 
294         TypedValue typedValue = new TypedValue();
295         context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
296         mOnboardingToastColor = res.getColor(typedValue.resourceId);
297         mOnboardingToastArrowRadius = res.getDimensionPixelSize(
298                 R.dimen.recents_onboarding_toast_arrow_corner_radius);
299 
300         mLayout.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
301         mDismissView.setOnClickListener(v -> {
302             hide(true);
303             if (v.getTag().equals(R.string.recents_swipe_up_onboarding)) {
304                 mHasDismissedSwipeUpTip = true;
305                 mNumAppsLaunchedSinceSwipeUpTipDismiss = 0;
306                 setDismissedSwipeUpOnboardingCount(getDismissedSwipeUpOnboardingCount() + 1);
307                 if (getDismissedSwipeUpOnboardingCount() > MAX_DISMISSAL_ON_SWIPE_UP_SHOW) {
308                     setHasSeenSwipeUpOnboarding(true);
309                 }
310                 notifyOnTip(DISMISS, RECENTS_SWIPE_UP_ONBOARDING_TIP);
311             } else {
312                 notifyOnTip(DISMISS, RECENTS_QUICK_SCRUB_ONBOARDING_TIP);
313             }
314         });
315 
316         ViewGroup.LayoutParams arrowLp = mArrowView.getLayoutParams();
317         ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
318                 arrowLp.width, arrowLp.height, false));
319         Paint arrowPaint = arrowDrawable.getPaint();
320         arrowPaint.setColor(mOnboardingToastColor);
321         // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
322         arrowPaint.setPathEffect(new CornerPathEffect(mOnboardingToastArrowRadius));
323         mArrowView.setBackground(arrowDrawable);
324 
325         if (RESET_PREFS_FOR_DEBUG) {
326             setHasSeenSwipeUpOnboarding(false);
327             setHasSeenQuickScrubOnboarding(false);
328             setDismissedSwipeUpOnboardingCount(0);
329             setHasDismissedQuickScrubOnboardingOnce(false);
330             setOpenedOverviewCount(0);
331             setOpenedOverviewFromHomeCount(0);
332         }
333     }
334 
notifyOnTip(int action, int target)335     private void notifyOnTip(int action, int target) {
336         try {
337             IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
338             if(overviewProxy != null) {
339                 overviewProxy.onTip(action, target);
340             }
341         } catch (RemoteException e) {}
342     }
343 
onNavigationModeChanged(int mode)344     public void onNavigationModeChanged(int mode) {
345         mNavBarMode = mode;
346     }
347 
onConnectedToLauncher()348     public void onConnectedToLauncher() {
349         if (!ONBOARDING_ENABLED || QuickStepContract.isGesturalMode(mNavBarMode)) {
350             return;
351         }
352 
353         if (hasSeenSwipeUpOnboarding() && hasSeenQuickScrubOnboarding()) {
354             return;
355         }
356 
357         if (!mOverviewProxyListenerRegistered) {
358             mOverviewProxyService.addCallback(mOverviewProxyListener);
359             mOverviewProxyListenerRegistered = true;
360         }
361         if (!mTaskListenerRegistered) {
362             ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
363             mTaskListenerRegistered = true;
364         }
365     }
366 
onDisconnectedFromLauncher()367     public void onDisconnectedFromLauncher() {
368 
369         if (mOverviewProxyListenerRegistered) {
370             mOverviewProxyService.removeCallback(mOverviewProxyListener);
371             mOverviewProxyListenerRegistered = false;
372         }
373         if (mTaskListenerRegistered) {
374             ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskListener);
375             mTaskListenerRegistered = false;
376         }
377 
378         mHasDismissedSwipeUpTip = false;
379         mHasDismissedQuickScrubTip = false;
380         mNumAppsLaunchedSinceSwipeUpTipDismiss = 0;
381         mOverviewOpenedCountSinceQuickScrubTipDismiss = 0;
382         hide(true);
383     }
384 
onConfigurationChanged(Configuration newConfiguration)385     public void onConfigurationChanged(Configuration newConfiguration) {
386         if (newConfiguration.orientation != Configuration.ORIENTATION_PORTRAIT) {
387             hide(false);
388         }
389     }
390 
show(@tringRes int stringRes)391     public boolean show(@StringRes int stringRes) {
392         if (!shouldShow()) {
393             return false;
394         }
395         mDismissView.setTag(stringRes);
396         mLayout.setTag(stringRes);
397         mTextView.setText(stringRes);
398         // Only show in portrait.
399         int orientation = mContext.getResources().getConfiguration().orientation;
400         if (!mLayoutAttachedToWindow && orientation == Configuration.ORIENTATION_PORTRAIT) {
401             mLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
402 
403             final int gravity;
404             final int x;
405             if (stringRes == R.string.recents_swipe_up_onboarding) {
406                 gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
407                 x = 0;
408             } else {
409                 int layoutDirection =
410                         mContext.getResources().getConfiguration().getLayoutDirection();
411                 gravity = Gravity.BOTTOM | (layoutDirection == View.LAYOUT_DIRECTION_LTR
412                         ? Gravity.LEFT : Gravity.RIGHT);
413                 x = mContext.getResources().getDimensionPixelSize(
414                         R.dimen.recents_quick_scrub_onboarding_margin_start);
415             }
416             mWindowManager.addView(mLayout, getWindowLayoutParams(gravity, x));
417             mLayout.setAlpha(0);
418             mLayout.animate()
419                     .alpha(1f)
420                     .withLayer()
421                     .setStartDelay(SHOW_DELAY_MS)
422                     .setDuration(SHOW_DURATION_MS)
423                     .setInterpolator(new DecelerateInterpolator())
424                     .start();
425             return true;
426         }
427         return false;
428     }
429 
430     /**
431      * @return True unless setprop has been set to false, we're in demo mode, or running tests in
432      * automation.
433      */
shouldShow()434     private boolean shouldShow() {
435         return SystemProperties.getBoolean("persist.quickstep.onboarding.enabled",
436                 !(mContext.getSystemService(UserManager.class)).isDemoUser() &&
437                         !ActivityManager.isRunningInTestHarness());
438     }
439 
hide(boolean animate)440     public void hide(boolean animate) {
441         if (mLayoutAttachedToWindow) {
442             if (animate) {
443                 mLayout.animate()
444                         .alpha(0f)
445                         .withLayer()
446                         .setStartDelay(0)
447                         .setDuration(HIDE_DURATION_MS)
448                         .setInterpolator(new AccelerateInterpolator())
449                         .withEndAction(() -> mWindowManager.removeViewImmediate(mLayout))
450                         .start();
451             } else {
452                 mLayout.animate().cancel();
453                 mWindowManager.removeViewImmediate(mLayout);
454             }
455         }
456     }
457 
setNavBarHeight(int navBarHeight)458     public void setNavBarHeight(int navBarHeight) {
459         mNavBarHeight = navBarHeight;
460     }
461 
dump(PrintWriter pw)462     public void dump(PrintWriter pw) {
463         pw.println("RecentsOnboarding {");
464         pw.println("      mTaskListenerRegistered: " + mTaskListenerRegistered);
465         pw.println("      mOverviewProxyListenerRegistered: " + mOverviewProxyListenerRegistered);
466         pw.println("      mLayoutAttachedToWindow: " + mLayoutAttachedToWindow);
467         pw.println("      mHasDismissedSwipeUpTip: " + mHasDismissedSwipeUpTip);
468         pw.println("      mHasDismissedQuickScrubTip: " + mHasDismissedQuickScrubTip);
469         pw.println("      mNumAppsLaunchedSinceSwipeUpTipDismiss: "
470                 + mNumAppsLaunchedSinceSwipeUpTipDismiss);
471         pw.println("      hasSeenSwipeUpOnboarding: " + hasSeenSwipeUpOnboarding());
472         pw.println("      hasSeenQuickScrubOnboarding: " + hasSeenQuickScrubOnboarding());
473         pw.println("      getDismissedSwipeUpOnboardingCount: "
474                 + getDismissedSwipeUpOnboardingCount());
475         pw.println("      hasDismissedQuickScrubOnboardingOnce: "
476                 + hasDismissedQuickScrubOnboardingOnce());
477         pw.println("      getOpenedOverviewCount: " + getOpenedOverviewCount());
478         pw.println("      getOpenedOverviewFromHomeCount: " + getOpenedOverviewFromHomeCount());
479         pw.println("    }");
480     }
481 
getWindowLayoutParams(int gravity, int x)482     private WindowManager.LayoutParams getWindowLayoutParams(int gravity, int x) {
483         int flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
484                 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
485         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
486                 ViewGroup.LayoutParams.WRAP_CONTENT,
487                 ViewGroup.LayoutParams.WRAP_CONTENT,
488                 x, -mNavBarHeight / 2,
489                 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
490                 flags,
491                 PixelFormat.TRANSLUCENT);
492         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
493         lp.setTitle("RecentsOnboarding");
494         lp.gravity = gravity;
495         return lp;
496     }
497 
hasSeenSwipeUpOnboarding()498     private boolean hasSeenSwipeUpOnboarding() {
499         return Prefs.getBoolean(mContext, HAS_SEEN_RECENTS_SWIPE_UP_ONBOARDING, false);
500     }
501 
setHasSeenSwipeUpOnboarding(boolean hasSeenSwipeUpOnboarding)502     private void setHasSeenSwipeUpOnboarding(boolean hasSeenSwipeUpOnboarding) {
503         Prefs.putBoolean(mContext, HAS_SEEN_RECENTS_SWIPE_UP_ONBOARDING, hasSeenSwipeUpOnboarding);
504         if (hasSeenSwipeUpOnboarding && hasSeenQuickScrubOnboarding()) {
505             onDisconnectedFromLauncher();
506         }
507     }
508 
hasSeenQuickScrubOnboarding()509     private boolean hasSeenQuickScrubOnboarding() {
510         return Prefs.getBoolean(mContext, HAS_SEEN_RECENTS_QUICK_SCRUB_ONBOARDING, false);
511     }
512 
setHasSeenQuickScrubOnboarding(boolean hasSeenQuickScrubOnboarding)513     private void setHasSeenQuickScrubOnboarding(boolean hasSeenQuickScrubOnboarding) {
514         Prefs.putBoolean(mContext, HAS_SEEN_RECENTS_QUICK_SCRUB_ONBOARDING,
515                 hasSeenQuickScrubOnboarding);
516         if (hasSeenQuickScrubOnboarding && hasSeenSwipeUpOnboarding()) {
517             onDisconnectedFromLauncher();
518         }
519     }
520 
getDismissedSwipeUpOnboardingCount()521     private int getDismissedSwipeUpOnboardingCount() {
522         return Prefs.getInt(mContext, DISMISSED_RECENTS_SWIPE_UP_ONBOARDING_COUNT, 0);
523     }
524 
setDismissedSwipeUpOnboardingCount(int dismissedSwipeUpOnboardingCount)525     private void setDismissedSwipeUpOnboardingCount(int dismissedSwipeUpOnboardingCount) {
526         Prefs.putInt(mContext, DISMISSED_RECENTS_SWIPE_UP_ONBOARDING_COUNT,
527                 dismissedSwipeUpOnboardingCount);
528     }
529 
hasDismissedQuickScrubOnboardingOnce()530     private boolean hasDismissedQuickScrubOnboardingOnce() {
531         return Prefs.getBoolean(mContext, HAS_DISMISSED_RECENTS_QUICK_SCRUB_ONBOARDING_ONCE, false);
532     }
533 
setHasDismissedQuickScrubOnboardingOnce( boolean hasDismissedQuickScrubOnboardingOnce)534     private void setHasDismissedQuickScrubOnboardingOnce(
535             boolean hasDismissedQuickScrubOnboardingOnce) {
536         Prefs.putBoolean(mContext, HAS_DISMISSED_RECENTS_QUICK_SCRUB_ONBOARDING_ONCE,
537                 hasDismissedQuickScrubOnboardingOnce);
538     }
539 
getOpenedOverviewFromHomeCount()540     private int getOpenedOverviewFromHomeCount() {
541         return Prefs.getInt(mContext, OVERVIEW_OPENED_FROM_HOME_COUNT, 0);
542     }
543 
incrementOpenedOverviewFromHomeCount()544     private void incrementOpenedOverviewFromHomeCount() {
545         int openedOverviewFromHomeCount = getOpenedOverviewFromHomeCount();
546         if (openedOverviewFromHomeCount >= SWIPE_UP_SHOW_ON_OVERVIEW_OPENED_FROM_HOME_COUNT) {
547             return;
548         }
549         setOpenedOverviewFromHomeCount(openedOverviewFromHomeCount + 1);
550     }
551 
setOpenedOverviewFromHomeCount(int openedOverviewFromHomeCount)552     private void setOpenedOverviewFromHomeCount(int openedOverviewFromHomeCount) {
553         Prefs.putInt(mContext, OVERVIEW_OPENED_FROM_HOME_COUNT, openedOverviewFromHomeCount);
554     }
555 
getOpenedOverviewCount()556     private int getOpenedOverviewCount() {
557         return Prefs.getInt(mContext, OVERVIEW_OPENED_COUNT, 0);
558     }
559 
incrementOpenedOverviewCount()560     private void incrementOpenedOverviewCount() {
561         int openedOverviewCount = getOpenedOverviewCount();
562         if (openedOverviewCount >= QUICK_SCRUB_SHOW_ON_OVERVIEW_OPENED_COUNT) {
563             return;
564         }
565         setOpenedOverviewCount(openedOverviewCount + 1);
566     }
567 
setOpenedOverviewCount(int openedOverviewCount)568     private void setOpenedOverviewCount(int openedOverviewCount) {
569         Prefs.putInt(mContext, OVERVIEW_OPENED_COUNT, openedOverviewCount);
570     }
571 
572     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
573         @Override
574         public void onReceive(Context context, Intent intent) {
575             if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
576                 hide(false);
577             }
578         }
579     };
580 }
581