• 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 
17 package com.android.launcher3.appprediction;
18 
19 import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
20 
21 import android.annotation.TargetApi;
22 import android.content.Context;
23 import android.graphics.Canvas;
24 import android.graphics.Typeface;
25 import android.os.Build;
26 import android.text.Layout;
27 import android.text.StaticLayout;
28 import android.text.TextPaint;
29 import android.util.AttributeSet;
30 import android.view.View;
31 import android.view.accessibility.AccessibilityManager;
32 
33 import androidx.annotation.ColorInt;
34 import androidx.annotation.VisibleForTesting;
35 
36 import com.android.launcher3.R;
37 import com.android.launcher3.Utilities;
38 import com.android.launcher3.allapps.FloatingHeaderRow;
39 import com.android.launcher3.allapps.FloatingHeaderView;
40 
41 /**
42  * A view which shows a horizontal divider
43  */
44 @TargetApi(Build.VERSION_CODES.O)
45 public class AppsDividerView extends View implements FloatingHeaderRow {
46 
47     public enum DividerType {
48         NONE,
49         LINE,
50         ALL_APPS_LABEL
51     }
52 
53     private final TextPaint mPaint = new TextPaint();
54     private DividerType mDividerType = DividerType.NONE;
55 
56     private final @ColorInt int mStrokeColor;
57     private final @ColorInt int mAllAppsLabelTextColor;
58     private final AccessibilityManager mAccessibilityManager;
59 
60     private Layout mAllAppsLabelLayout;
61     private boolean mShowAllAppsLabel;
62 
63     private FloatingHeaderView mParent;
64     private boolean mTabsHidden;
65     private FloatingHeaderRow[] mRows = FloatingHeaderRow.NO_ROWS;
66 
67     private boolean mIsScrolledOut = false;
68 
69     private final int[] mDividerSize;
70 
AppsDividerView(Context context)71     public AppsDividerView(Context context) {
72         this(context, null);
73     }
74 
AppsDividerView(Context context, AttributeSet attrs)75     public AppsDividerView(Context context, AttributeSet attrs) {
76         this(context, attrs, 0);
77     }
78 
AppsDividerView(Context context, AttributeSet attrs, int defStyleAttr)79     public AppsDividerView(Context context, AttributeSet attrs, int defStyleAttr) {
80         super(context, attrs, defStyleAttr);
81 
82         mDividerSize = new int[]{
83                 getResources().getDimensionPixelSize(R.dimen.all_apps_divider_width),
84                 getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height)
85         };
86 
87         mStrokeColor = context.getColor(R.color.materialColorOutlineVariant);
88 
89         mAllAppsLabelTextColor = context.getColor(R.color.materialColorOnSurfaceVariant);
90 
91         mAccessibilityManager = AccessibilityManager.getInstance(context);
92         setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(context));
93     }
94 
setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden)95     public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
96         mParent = parent;
97         mTabsHidden = tabsHidden;
98         mRows = rows;
99         updateDividerType();
100     }
101 
102     /** {@code true} if all apps label should be shown in place of divider. */
setShowAllAppsLabel(boolean showAllAppsLabel)103     public void setShowAllAppsLabel(boolean showAllAppsLabel) {
104         if (mAccessibilityManager.isEnabled() && !Utilities.isRunningInTestHarness()) {
105             showAllAppsLabel = true;
106         }
107         if (showAllAppsLabel != mShowAllAppsLabel) {
108             mShowAllAppsLabel = showAllAppsLabel;
109             updateDividerType();
110         }
111     }
112 
113     @Override
getExpectedHeight()114     public int getExpectedHeight() {
115         return getPaddingTop() + getPaddingBottom();
116     }
117 
118     @Override
shouldDraw()119     public boolean shouldDraw() {
120         return mDividerType != DividerType.NONE;
121     }
122 
123     @Override
hasVisibleContent()124     public boolean hasVisibleContent() {
125         return false;
126     }
127 
updateDividerType()128     private void updateDividerType() {
129         final DividerType dividerType;
130         if (!mTabsHidden) {
131             dividerType = DividerType.NONE;
132         } else {
133             // Check how many sections above me.
134             int sectionCount = 0;
135             for (FloatingHeaderRow row : mRows) {
136                 if (row == this) {
137                     break;
138                 } else if (row.shouldDraw()) {
139                     sectionCount++;
140                 }
141             }
142 
143             if (mShowAllAppsLabel && sectionCount > 0) {
144                 dividerType = DividerType.ALL_APPS_LABEL;
145             } else if (sectionCount == 1) {
146                 dividerType = DividerType.LINE;
147             } else {
148                 dividerType = DividerType.NONE;
149             }
150         }
151 
152         if (mDividerType != dividerType) {
153             mDividerType = dividerType;
154             int topPadding;
155             int bottomPadding;
156             setContentDescription(null);
157             switch (dividerType) {
158                 case LINE:
159                     topPadding = 0;
160                     bottomPadding = getResources()
161                             .getDimensionPixelSize(R.dimen.all_apps_prediction_row_divider_height);
162                     mPaint.setColor(mStrokeColor);
163                     break;
164                 case ALL_APPS_LABEL:
165                     topPadding = getAllAppsLabelLayout().getHeight() + getResources()
166                             .getDimensionPixelSize(R.dimen.all_apps_label_top_padding);
167                     bottomPadding = getResources()
168                             .getDimensionPixelSize(R.dimen.all_apps_label_bottom_padding);
169                     mPaint.setColor(mAllAppsLabelTextColor);
170                     setContentDescription(mAllAppsLabelLayout.getText());
171                     break;
172                 case NONE:
173                 default:
174                     topPadding = bottomPadding = 0;
175                     break;
176             }
177             setPadding(getPaddingLeft(), topPadding, getPaddingRight(), bottomPadding);
178             updateViewVisibility();
179             invalidate();
180             requestLayout();
181             if (mParent != null) {
182                 mParent.onHeightUpdated();
183             }
184         }
185     }
186 
updateViewVisibility()187     private void updateViewVisibility() {
188         setVisibility(mDividerType == DividerType.NONE
189                 ? GONE
190                 : (mIsScrolledOut ? INVISIBLE : VISIBLE));
191     }
192 
193     @Override
onDraw(Canvas canvas)194     protected void onDraw(Canvas canvas) {
195         if (mDividerType == DividerType.LINE) {
196             int l = (getWidth() - mDividerSize[0]) / 2;
197             int t = getHeight() - (getPaddingBottom() / 2);
198             int radius = mDividerSize[1];
199             canvas.drawRoundRect(l, t, l + mDividerSize[0], t + mDividerSize[1], radius, radius,
200                     mPaint);
201         } else if (mDividerType == DividerType.ALL_APPS_LABEL) {
202             Layout textLayout = getAllAppsLabelLayout();
203             int x = getWidth() / 2 - textLayout.getWidth() / 2;
204             int y = getHeight() - getPaddingBottom() - textLayout.getHeight();
205             canvas.translate(x, y);
206             textLayout.draw(canvas);
207             canvas.translate(-x, -y);
208         }
209     }
210 
getAllAppsLabelLayout()211     private Layout getAllAppsLabelLayout() {
212         if (mAllAppsLabelLayout == null) {
213             mPaint.setAntiAlias(true);
214             mPaint.setTypeface(Typeface.create("google-sans", Typeface.NORMAL));
215             mPaint.setTextSize(
216                     getResources().getDimensionPixelSize(R.dimen.all_apps_label_text_size));
217 
218             CharSequence allAppsLabelText = getResources().getText(R.string.all_apps_label);
219             mAllAppsLabelLayout = StaticLayout.Builder.obtain(
220                             allAppsLabelText, 0, allAppsLabelText.length(), mPaint,
221                             Math.round(mPaint.measureText(allAppsLabelText.toString())))
222                     .setAlignment(Layout.Alignment.ALIGN_CENTER)
223                     .setMaxLines(1)
224                     .setIncludePad(true)
225                     .build();
226         }
227         return mAllAppsLabelLayout;
228     }
229 
230     @Override
hasOverlappingRendering()231     public boolean hasOverlappingRendering() {
232         return false;
233     }
234 
onMeasure(int widthMeasureSpec, int heightMeasureSpec)235     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
236         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
237                 getPaddingBottom() + getPaddingTop());
238     }
239 
240     @Override
setVerticalScroll(int scroll, boolean isScrolledOut)241     public void setVerticalScroll(int scroll, boolean isScrolledOut) {
242         setTranslationY(scroll);
243         mIsScrolledOut = isScrolledOut;
244         updateViewVisibility();
245     }
246 
247     @Override
getTypeClass()248     public Class<AppsDividerView> getTypeClass() {
249         return AppsDividerView.class;
250     }
251 
252     @Override
getFocusedChild()253     public View getFocusedChild() {
254         return null;
255     }
256 
257     @VisibleForTesting
getDividerType()258     public DividerType getDividerType() {
259         return mDividerType;
260     }
261 }
262