1 /* 2 * Copyright (C) 2022 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.accessibility.floatingmenu; 18 19 import static android.view.View.OVER_SCROLL_ALWAYS; 20 import static android.view.View.OVER_SCROLL_NEVER; 21 22 import static com.android.systemui.accessibility.floatingmenu.MenuViewAppearance.MenuSizeType.SMALL; 23 24 import android.annotation.IntDef; 25 import android.content.Context; 26 import android.content.res.Resources; 27 import android.graphics.Insets; 28 import android.graphics.PointF; 29 import android.graphics.Rect; 30 import android.graphics.drawable.Drawable; 31 import android.view.WindowInsets; 32 import android.view.WindowManager; 33 import android.view.WindowMetrics; 34 35 import androidx.annotation.DimenRes; 36 37 import com.android.systemui.res.R; 38 39 import java.lang.annotation.Retention; 40 import java.lang.annotation.RetentionPolicy; 41 42 /** 43 * Provides the layout resources information of the {@link MenuView}. 44 */ 45 class MenuViewAppearance { 46 private final WindowManager mWindowManager; 47 private final Resources mRes; 48 private final Position mPercentagePosition = new Position(/* percentageX= */ 49 0f, /* percentageY= */ 0f); 50 private boolean mIsImeShowing; 51 // Avoid the menu view overlapping on the primary action button under the bottom as possible. 52 private int mImeShiftingSpace; 53 private int mTargetFeaturesSize; 54 private int mSizeType; 55 private int mMargin; 56 private int mSmallPadding; 57 private int mLargePadding; 58 private int mSmallIconSize; 59 private int mLargeIconSize; 60 private int mSmallSingleRadius; 61 private int mSmallMultipleRadius; 62 private int mLargeSingleRadius; 63 private int mLargeMultipleRadius; 64 private int mStrokeWidth; 65 private int mStrokeColor; 66 private int mInset; 67 private int mElevation; 68 private float mImeTop; 69 private float[] mRadii; 70 private Drawable mBackgroundDrawable; 71 private String mContentDescription; 72 73 @IntDef({ 74 SMALL, 75 MenuSizeType.LARGE 76 }) 77 @Retention(RetentionPolicy.SOURCE) 78 @interface MenuSizeType { 79 int SMALL = 0; 80 int LARGE = 1; 81 } 82 MenuViewAppearance(Context context, WindowManager windowManager)83 MenuViewAppearance(Context context, WindowManager windowManager) { 84 mWindowManager = windowManager; 85 mRes = context.getResources(); 86 87 update(); 88 } 89 update()90 void update() { 91 mMargin = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin); 92 mSmallPadding = 93 mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_padding); 94 mLargePadding = 95 mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_large_padding); 96 mSmallIconSize = 97 mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_width_height); 98 mLargeIconSize = 99 mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_large_width_height); 100 mSmallSingleRadius = 101 mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_single_radius); 102 mSmallMultipleRadius = mRes.getDimensionPixelSize( 103 R.dimen.accessibility_floating_menu_small_multiple_radius); 104 mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize)); 105 mLargeSingleRadius = 106 mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_large_single_radius); 107 mLargeMultipleRadius = mRes.getDimensionPixelSize( 108 R.dimen.accessibility_floating_menu_large_multiple_radius); 109 mStrokeWidth = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_width); 110 mStrokeColor = mRes.getColor(R.color.accessibility_floating_menu_stroke_dark); 111 mInset = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset); 112 mElevation = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation); 113 mImeShiftingSpace = mRes.getDimensionPixelSize( 114 R.dimen.accessibility_floating_menu_ime_shifting_space); 115 final Drawable drawable = 116 mRes.getDrawable(R.drawable.accessibility_floating_menu_background); 117 mBackgroundDrawable = new InstantInsetLayerDrawable(new Drawable[]{drawable}); 118 mContentDescription = mRes.getString( 119 com.android.internal.R.string.accessibility_select_shortcut_menu_title); 120 } 121 setSizeType(int sizeType)122 void setSizeType(int sizeType) { 123 mSizeType = sizeType; 124 125 mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize)); 126 } 127 setTargetFeaturesSize(int targetFeaturesSize)128 void setTargetFeaturesSize(int targetFeaturesSize) { 129 mTargetFeaturesSize = targetFeaturesSize; 130 131 mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(targetFeaturesSize)); 132 } 133 setPercentagePosition(Position percentagePosition)134 void setPercentagePosition(Position percentagePosition) { 135 mPercentagePosition.update(percentagePosition); 136 137 mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize)); 138 } 139 onImeVisibilityChanged(boolean imeShowing, float imeTop)140 void onImeVisibilityChanged(boolean imeShowing, float imeTop) { 141 mIsImeShowing = imeShowing; 142 mImeTop = imeTop; 143 } 144 getMenuDraggableBounds()145 Rect getMenuDraggableBounds() { 146 return getMenuDraggableBoundsWith(/* includeIme= */ true); 147 } 148 getMenuDraggableBoundsExcludeIme()149 Rect getMenuDraggableBoundsExcludeIme() { 150 return getMenuDraggableBoundsWith(/* includeIme= */ false); 151 } 152 getMenuDraggableBoundsWith(boolean includeIme)153 private Rect getMenuDraggableBoundsWith(boolean includeIme) { 154 final int margin = getMenuMargin(); 155 final Rect draggableBounds = new Rect(getWindowAvailableBounds()); 156 157 draggableBounds.top += margin; 158 draggableBounds.right -= getMenuWidth(); 159 160 if (includeIme && mIsImeShowing) { 161 final int imeHeight = (int) (draggableBounds.bottom - mImeTop); 162 draggableBounds.bottom -= (imeHeight + mImeShiftingSpace); 163 } 164 draggableBounds.bottom -= (calculateActualMenuHeight() + margin); 165 draggableBounds.bottom = Math.max(draggableBounds.top, draggableBounds.bottom); 166 167 return draggableBounds; 168 } 169 getMenuPosition()170 PointF getMenuPosition() { 171 final Rect draggableBounds = getMenuDraggableBoundsExcludeIme(); 172 final float x = draggableBounds.left 173 + draggableBounds.width() * mPercentagePosition.getPercentageX(); 174 175 float y = draggableBounds.top 176 + draggableBounds.height() * mPercentagePosition.getPercentageY(); 177 178 // If the bottom of the menu view and overlap on the ime, its position y will be 179 // overridden with new y. 180 final float menuBottom = y + getMenuHeight() + mMargin; 181 if (mIsImeShowing && (menuBottom >= mImeTop)) { 182 y = Math.max(draggableBounds.top, 183 mImeTop - getMenuHeight() - mMargin - mImeShiftingSpace); 184 } 185 186 return new PointF(x, y); 187 } 188 getContentDescription()189 String getContentDescription() { 190 return mContentDescription; 191 } 192 getMenuBackground()193 Drawable getMenuBackground() { 194 return mBackgroundDrawable; 195 } 196 getMenuElevation()197 int getMenuElevation() { 198 return mElevation; 199 } 200 getMenuWidth()201 int getMenuWidth() { 202 return getMenuPadding() * 2 + getMenuIconSize(); 203 } 204 getMenuHeight()205 int getMenuHeight() { 206 return Math.min(getWindowAvailableBounds().height() - mMargin * 2, 207 calculateActualMenuHeight()); 208 } 209 getMenuIconSize()210 int getMenuIconSize() { 211 return mSizeType == SMALL ? mSmallIconSize : mLargeIconSize; 212 } 213 getMenuMargin()214 private int getMenuMargin() { 215 return mMargin; 216 } 217 getMenuPadding()218 int getMenuPadding() { 219 return mSizeType == SMALL ? mSmallPadding : mLargePadding; 220 } 221 getMenuInsets()222 int[] getMenuInsets() { 223 final int left = isMenuOnLeftSide() ? mInset : 0; 224 final int right = isMenuOnLeftSide() ? 0 : mInset; 225 226 return new int[]{left, 0, right, 0}; 227 } 228 getMenuMovingStateInsets()229 int[] getMenuMovingStateInsets() { 230 return new int[]{0, 0, 0, 0}; 231 } 232 getMenuMovingStateRadii()233 float[] getMenuMovingStateRadii() { 234 final float radius = getMenuRadius(mTargetFeaturesSize); 235 return new float[]{radius, radius, radius, radius, radius, radius, radius, radius}; 236 } 237 getMenuStrokeWidth()238 int getMenuStrokeWidth() { 239 return mStrokeWidth; 240 } 241 getMenuStrokeColor()242 int getMenuStrokeColor() { 243 return mStrokeColor; 244 } 245 getMenuRadii()246 float[] getMenuRadii() { 247 return mRadii; 248 } 249 getMenuRadius(int itemCount)250 private int getMenuRadius(int itemCount) { 251 return mSizeType == SMALL ? getSmallSize(itemCount) : getLargeSize(itemCount); 252 } 253 getMenuScrollMode()254 int getMenuScrollMode() { 255 return hasExceededMaxWindowHeight() ? OVER_SCROLL_ALWAYS : OVER_SCROLL_NEVER; 256 } 257 hasExceededMaxWindowHeight()258 private boolean hasExceededMaxWindowHeight() { 259 return calculateActualMenuHeight() > getWindowAvailableBounds().height(); 260 } 261 262 @DimenRes getSmallSize(int itemCount)263 private int getSmallSize(int itemCount) { 264 return itemCount > 1 ? mSmallMultipleRadius : mSmallSingleRadius; 265 } 266 267 @DimenRes getLargeSize(int itemCount)268 private int getLargeSize(int itemCount) { 269 return itemCount > 1 ? mLargeMultipleRadius : mLargeSingleRadius; 270 } 271 createRadii(boolean isMenuOnLeftSide, float radius)272 private static float[] createRadii(boolean isMenuOnLeftSide, float radius) { 273 return isMenuOnLeftSide 274 ? new float[]{0.0f, 0.0f, radius, radius, radius, radius, 0.0f, 0.0f} 275 : new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius}; 276 } 277 getWindowAvailableBounds()278 public Rect getWindowAvailableBounds() { 279 final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); 280 final WindowInsets windowInsets = windowMetrics.getWindowInsets(); 281 final Insets insets = windowInsets.getInsetsIgnoringVisibility( 282 WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()); 283 284 final Rect bounds = new Rect(windowMetrics.getBounds()); 285 bounds.left += insets.left; 286 bounds.right -= insets.right; 287 bounds.top += insets.top; 288 bounds.bottom -= insets.bottom; 289 290 return bounds; 291 } 292 isMenuOnLeftSide()293 boolean isMenuOnLeftSide() { 294 return mPercentagePosition.getPercentageX() < 0.5f; 295 } 296 calculateActualMenuHeight()297 private int calculateActualMenuHeight() { 298 final int menuPadding = getMenuPadding(); 299 300 return (menuPadding + getMenuIconSize()) * mTargetFeaturesSize + menuPadding; 301 } 302 } 303