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.core.content.ContextCompat; 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 = ContextCompat.getColor(context, R.color.material_color_outline_variant); 88 89 mAllAppsLabelTextColor = ContextCompat.getColor(context, 90 R.color.material_color_on_surface_variant); 91 92 mAccessibilityManager = AccessibilityManager.getInstance(context); 93 setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(context)); 94 } 95 setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden)96 public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) { 97 mParent = parent; 98 mTabsHidden = tabsHidden; 99 mRows = rows; 100 updateDividerType(); 101 } 102 103 /** {@code true} if all apps label should be shown in place of divider. */ setShowAllAppsLabel(boolean showAllAppsLabel)104 public void setShowAllAppsLabel(boolean showAllAppsLabel) { 105 if (mAccessibilityManager.isEnabled() && !Utilities.isRunningInTestHarness()) { 106 showAllAppsLabel = true; 107 } 108 if (showAllAppsLabel != mShowAllAppsLabel) { 109 mShowAllAppsLabel = showAllAppsLabel; 110 updateDividerType(); 111 } 112 } 113 114 @Override getExpectedHeight()115 public int getExpectedHeight() { 116 return getPaddingTop() + getPaddingBottom(); 117 } 118 119 @Override shouldDraw()120 public boolean shouldDraw() { 121 return mDividerType != DividerType.NONE; 122 } 123 124 @Override hasVisibleContent()125 public boolean hasVisibleContent() { 126 return false; 127 } 128 updateDividerType()129 private void updateDividerType() { 130 final DividerType dividerType; 131 if (!mTabsHidden) { 132 dividerType = DividerType.NONE; 133 } else { 134 // Check how many sections above me. 135 int sectionCount = 0; 136 for (FloatingHeaderRow row : mRows) { 137 if (row == this) { 138 break; 139 } else if (row.shouldDraw()) { 140 sectionCount++; 141 } 142 } 143 144 if (mShowAllAppsLabel && sectionCount > 0) { 145 dividerType = DividerType.ALL_APPS_LABEL; 146 } else if (sectionCount == 1) { 147 dividerType = DividerType.LINE; 148 } else { 149 dividerType = DividerType.NONE; 150 } 151 } 152 153 if (mDividerType != dividerType) { 154 mDividerType = dividerType; 155 int topPadding; 156 int bottomPadding; 157 setContentDescription(null); 158 switch (dividerType) { 159 case LINE: 160 topPadding = 0; 161 bottomPadding = getResources() 162 .getDimensionPixelSize(R.dimen.all_apps_prediction_row_divider_height); 163 mPaint.setColor(mStrokeColor); 164 break; 165 case ALL_APPS_LABEL: 166 topPadding = getAllAppsLabelLayout().getHeight() + getResources() 167 .getDimensionPixelSize(R.dimen.all_apps_label_top_padding); 168 bottomPadding = getResources() 169 .getDimensionPixelSize(R.dimen.all_apps_label_bottom_padding); 170 mPaint.setColor(mAllAppsLabelTextColor); 171 setContentDescription(mAllAppsLabelLayout.getText()); 172 break; 173 case NONE: 174 default: 175 topPadding = bottomPadding = 0; 176 break; 177 } 178 setPadding(getPaddingLeft(), topPadding, getPaddingRight(), bottomPadding); 179 updateViewVisibility(); 180 invalidate(); 181 requestLayout(); 182 if (mParent != null) { 183 mParent.onHeightUpdated(); 184 } 185 } 186 } 187 updateViewVisibility()188 private void updateViewVisibility() { 189 setVisibility(mDividerType == DividerType.NONE 190 ? GONE 191 : (mIsScrolledOut ? INVISIBLE : VISIBLE)); 192 } 193 194 @Override onDraw(Canvas canvas)195 protected void onDraw(Canvas canvas) { 196 if (mDividerType == DividerType.LINE) { 197 int l = (getWidth() - mDividerSize[0]) / 2; 198 int t = getHeight() - (getPaddingBottom() / 2); 199 int radius = mDividerSize[1]; 200 canvas.drawRoundRect(l, t, l + mDividerSize[0], t + mDividerSize[1], radius, radius, 201 mPaint); 202 } else if (mDividerType == DividerType.ALL_APPS_LABEL) { 203 Layout textLayout = getAllAppsLabelLayout(); 204 int x = getWidth() / 2 - textLayout.getWidth() / 2; 205 int y = getHeight() - getPaddingBottom() - textLayout.getHeight(); 206 canvas.translate(x, y); 207 textLayout.draw(canvas); 208 canvas.translate(-x, -y); 209 } 210 } 211 getAllAppsLabelLayout()212 private Layout getAllAppsLabelLayout() { 213 if (mAllAppsLabelLayout == null) { 214 mPaint.setAntiAlias(true); 215 mPaint.setTypeface(Typeface.create("google-sans", Typeface.NORMAL)); 216 mPaint.setTextSize( 217 getResources().getDimensionPixelSize(R.dimen.all_apps_label_text_size)); 218 219 CharSequence allAppsLabelText = getResources().getText(R.string.all_apps_label); 220 mAllAppsLabelLayout = StaticLayout.Builder.obtain( 221 allAppsLabelText, 0, allAppsLabelText.length(), mPaint, 222 Math.round(mPaint.measureText(allAppsLabelText.toString()))) 223 .setAlignment(Layout.Alignment.ALIGN_CENTER) 224 .setMaxLines(1) 225 .setIncludePad(true) 226 .build(); 227 } 228 return mAllAppsLabelLayout; 229 } 230 231 @Override hasOverlappingRendering()232 public boolean hasOverlappingRendering() { 233 return false; 234 } 235 onMeasure(int widthMeasureSpec, int heightMeasureSpec)236 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 237 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 238 getPaddingBottom() + getPaddingTop()); 239 } 240 241 @Override setVerticalScroll(int scroll, boolean isScrolledOut)242 public void setVerticalScroll(int scroll, boolean isScrolledOut) { 243 setTranslationY(scroll); 244 mIsScrolledOut = isScrolledOut; 245 updateViewVisibility(); 246 } 247 248 @Override getTypeClass()249 public Class<AppsDividerView> getTypeClass() { 250 return AppsDividerView.class; 251 } 252 253 @Override getFocusedChild()254 public View getFocusedChild() { 255 return null; 256 } 257 } 258