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