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 package com.android.launcher3.views; 17 18 import static android.content.Context.ACCESSIBILITY_SERVICE; 19 import static android.view.MotionEvent.ACTION_DOWN; 20 21 import static com.android.launcher3.LauncherState.ALL_APPS; 22 import static com.android.launcher3.LauncherState.NORMAL; 23 import static com.android.launcher3.anim.Interpolators.ACCEL; 24 import static com.android.launcher3.anim.Interpolators.DEACCEL; 25 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; 26 27 import static androidx.core.graphics.ColorUtils.compositeColors; 28 29 import android.animation.Animator; 30 import android.animation.AnimatorListenerAdapter; 31 import android.animation.Keyframe; 32 import android.animation.ObjectAnimator; 33 import android.animation.PropertyValuesHolder; 34 import android.animation.RectEvaluator; 35 import android.content.Context; 36 import android.graphics.Canvas; 37 import android.graphics.Color; 38 import android.graphics.Rect; 39 import android.graphics.RectF; 40 import android.graphics.drawable.Drawable; 41 import android.os.Bundle; 42 import android.util.AttributeSet; 43 import android.util.Property; 44 import android.view.KeyEvent; 45 import android.view.MotionEvent; 46 import android.view.View; 47 import android.view.accessibility.AccessibilityManager; 48 import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; 49 50 import com.android.launcher3.DeviceProfile; 51 import com.android.launcher3.Insettable; 52 import com.android.launcher3.Launcher; 53 import com.android.launcher3.LauncherState; 54 import com.android.launcher3.LauncherStateManager; 55 import com.android.launcher3.LauncherStateManager.StateListener; 56 import com.android.launcher3.R; 57 import com.android.launcher3.Utilities; 58 import com.android.launcher3.uioverrides.WallpaperColorInfo; 59 import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener; 60 import com.android.launcher3.userevent.nano.LauncherLogProto.Action; 61 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; 62 import com.android.launcher3.util.MultiValueAlpha; 63 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; 64 import com.android.launcher3.util.Themes; 65 66 import java.util.List; 67 68 import androidx.annotation.NonNull; 69 import androidx.annotation.Nullable; 70 import androidx.core.view.ViewCompat; 71 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 72 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; 73 import androidx.customview.widget.ExploreByTouchHelper; 74 75 /** 76 * Simple scrim which draws a flat color 77 */ 78 public class ScrimView extends View implements Insettable, OnChangeListener, 79 AccessibilityStateChangeListener, StateListener { 80 81 public static final Property<ScrimView, Integer> DRAG_HANDLE_ALPHA = 82 new Property<ScrimView, Integer>(Integer.TYPE, "dragHandleAlpha") { 83 84 @Override 85 public Integer get(ScrimView scrimView) { 86 return scrimView.mDragHandleAlpha; 87 } 88 89 @Override 90 public void set(ScrimView scrimView, Integer value) { 91 scrimView.setDragHandleAlpha(value); 92 } 93 }; 94 private static final int WALLPAPERS = R.string.wallpaper_button_text; 95 private static final int WIDGETS = R.string.widget_button_text; 96 private static final int SETTINGS = R.string.settings_button_text; 97 private static final int ALPHA_CHANNEL_COUNT = 1; 98 99 private final Rect mTempRect = new Rect(); 100 private final int[] mTempPos = new int[2]; 101 102 protected final Launcher mLauncher; 103 private final WallpaperColorInfo mWallpaperColorInfo; 104 private final AccessibilityManager mAM; 105 protected final int mEndScrim; 106 107 protected float mMaxScrimAlpha; 108 109 protected float mProgress = 1; 110 protected int mScrimColor; 111 112 protected int mCurrentFlatColor; 113 protected int mEndFlatColor; 114 protected int mEndFlatColorAlpha; 115 116 protected final int mDragHandleSize; 117 protected float mDragHandleOffset; 118 private final Rect mDragHandleBounds; 119 private final RectF mHitRect = new RectF(); 120 121 private final MultiValueAlpha mMultiValueAlpha; 122 123 private final AccessibilityHelper mAccessibilityHelper; 124 @Nullable 125 protected Drawable mDragHandle; 126 127 private int mDragHandleAlpha = 255; 128 ScrimView(Context context, AttributeSet attrs)129 public ScrimView(Context context, AttributeSet attrs) { 130 super(context, attrs); 131 mLauncher = Launcher.getLauncher(context); 132 mWallpaperColorInfo = WallpaperColorInfo.getInstance(context); 133 mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor); 134 135 mMaxScrimAlpha = 0.7f; 136 137 mDragHandleSize = context.getResources() 138 .getDimensionPixelSize(R.dimen.vertical_drag_handle_size); 139 mDragHandleBounds = new Rect(0, 0, mDragHandleSize, mDragHandleSize); 140 141 mAccessibilityHelper = createAccessibilityHelper(); 142 ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper); 143 144 mAM = (AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE); 145 setFocusable(false); 146 mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT); 147 } 148 getAlphaProperty(int index)149 public AlphaProperty getAlphaProperty(int index) { 150 return mMultiValueAlpha.getProperty(index); 151 } 152 153 @NonNull createAccessibilityHelper()154 protected AccessibilityHelper createAccessibilityHelper() { 155 return new AccessibilityHelper(); 156 } 157 158 @Override setInsets(Rect insets)159 public void setInsets(Rect insets) { 160 updateDragHandleBounds(); 161 updateDragHandleVisibility(null); 162 } 163 164 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)165 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 166 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 167 updateDragHandleBounds(); 168 } 169 170 @Override onAttachedToWindow()171 protected void onAttachedToWindow() { 172 super.onAttachedToWindow(); 173 mWallpaperColorInfo.addOnChangeListener(this); 174 onExtractedColorsChanged(mWallpaperColorInfo); 175 176 mAM.addAccessibilityStateChangeListener(this); 177 onAccessibilityStateChanged(mAM.isEnabled()); 178 } 179 180 @Override onDetachedFromWindow()181 protected void onDetachedFromWindow() { 182 super.onDetachedFromWindow(); 183 mWallpaperColorInfo.removeOnChangeListener(this); 184 mAM.removeAccessibilityStateChangeListener(this); 185 } 186 187 @Override hasOverlappingRendering()188 public boolean hasOverlappingRendering() { 189 return false; 190 } 191 192 @Override onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo)193 public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) { 194 mScrimColor = wallpaperColorInfo.getMainColor(); 195 mEndFlatColor = compositeColors(mEndScrim, setColorAlphaBound( 196 mScrimColor, Math.round(mMaxScrimAlpha * 255))); 197 mEndFlatColorAlpha = Color.alpha(mEndFlatColor); 198 updateColors(); 199 invalidate(); 200 } 201 setProgress(float progress)202 public void setProgress(float progress) { 203 if (mProgress != progress) { 204 mProgress = progress; 205 updateColors(); 206 updateDragHandleAlpha(); 207 invalidate(); 208 } 209 } 210 reInitUi()211 public void reInitUi() { } 212 updateColors()213 protected void updateColors() { 214 mCurrentFlatColor = mProgress >= 1 ? 0 : setColorAlphaBound( 215 mEndFlatColor, Math.round((1 - mProgress) * mEndFlatColorAlpha)); 216 } 217 updateDragHandleAlpha()218 protected void updateDragHandleAlpha() { 219 if (mDragHandle != null) { 220 mDragHandle.setAlpha(mDragHandleAlpha); 221 } 222 } 223 setDragHandleAlpha(int alpha)224 private void setDragHandleAlpha(int alpha) { 225 if (alpha != mDragHandleAlpha) { 226 mDragHandleAlpha = alpha; 227 if (mDragHandle != null) { 228 mDragHandle.setAlpha(mDragHandleAlpha); 229 invalidate(); 230 } 231 } 232 } 233 234 @Override onDraw(Canvas canvas)235 protected void onDraw(Canvas canvas) { 236 if (mCurrentFlatColor != 0) { 237 canvas.drawColor(mCurrentFlatColor); 238 } 239 drawDragHandle(canvas); 240 } 241 drawDragHandle(Canvas canvas)242 protected void drawDragHandle(Canvas canvas) { 243 if (mDragHandle != null) { 244 canvas.translate(0, -mDragHandleOffset); 245 mDragHandle.draw(canvas); 246 canvas.translate(0, mDragHandleOffset); 247 } 248 } 249 250 @Override onTouchEvent(MotionEvent event)251 public boolean onTouchEvent(MotionEvent event) { 252 boolean value = super.onTouchEvent(event); 253 if (!value && mDragHandle != null && event.getAction() == ACTION_DOWN 254 && mDragHandle.getAlpha() == 255 255 && mHitRect.contains(event.getX(), event.getY())) { 256 257 final Drawable drawable = mDragHandle; 258 mDragHandle = null; 259 260 Rect bounds = new Rect(mDragHandleBounds); 261 bounds.offset(0, -(int) mDragHandleOffset); 262 drawable.setBounds(bounds); 263 264 Rect topBounds = new Rect(bounds); 265 topBounds.offset(0, -bounds.height() / 2); 266 267 Rect invalidateRegion = new Rect(bounds); 268 invalidateRegion.top = topBounds.top; 269 270 Keyframe frameTop = Keyframe.ofObject(0.6f, topBounds); 271 frameTop.setInterpolator(DEACCEL); 272 Keyframe frameBot = Keyframe.ofObject(1, bounds); 273 frameBot.setInterpolator(ACCEL); 274 PropertyValuesHolder holder = PropertyValuesHolder .ofKeyframe("bounds", 275 Keyframe.ofObject(0, bounds), frameTop, frameBot); 276 holder.setEvaluator(new RectEvaluator()); 277 278 ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, holder); 279 anim.addListener(new AnimatorListenerAdapter() { 280 @Override 281 public void onAnimationEnd(Animator animation) { 282 getOverlay().remove(drawable); 283 updateDragHandleVisibility(drawable); 284 } 285 }); 286 anim.addUpdateListener((v) -> invalidate(invalidateRegion)); 287 getOverlay().add(drawable); 288 anim.start(); 289 } 290 return value; 291 } 292 updateDragHandleBounds()293 protected void updateDragHandleBounds() { 294 DeviceProfile grid = mLauncher.getDeviceProfile(); 295 final int left; 296 final int width = getMeasuredWidth(); 297 final int top = getMeasuredHeight() - mDragHandleSize - grid.getInsets().bottom; 298 final int topMargin; 299 300 if (grid.isVerticalBarLayout()) { 301 topMargin = grid.workspacePadding.bottom; 302 if (grid.isSeascape()) { 303 left = width - grid.getInsets().right - mDragHandleSize; 304 } else { 305 left = mDragHandleSize + grid.getInsets().left; 306 } 307 } else { 308 left = (width - mDragHandleSize) / 2; 309 topMargin = grid.hotseatBarSizePx; 310 } 311 mDragHandleBounds.offsetTo(left, top - topMargin); 312 mHitRect.set(mDragHandleBounds); 313 float inset = -mDragHandleSize / 2; 314 mHitRect.inset(inset, inset); 315 316 if (mDragHandle != null) { 317 mDragHandle.setBounds(mDragHandleBounds); 318 } 319 } 320 321 @Override onAccessibilityStateChanged(boolean enabled)322 public void onAccessibilityStateChanged(boolean enabled) { 323 LauncherStateManager stateManager = mLauncher.getStateManager(); 324 stateManager.removeStateListener(this); 325 326 if (enabled) { 327 stateManager.addStateListener(this); 328 handleStateChangedComplete(mLauncher.getStateManager().getState()); 329 } else { 330 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 331 } 332 updateDragHandleVisibility(null); 333 } 334 updateDragHandleVisibility(Drawable recycle)335 private void updateDragHandleVisibility(Drawable recycle) { 336 boolean visible = mLauncher.getDeviceProfile().isVerticalBarLayout() || mAM.isEnabled(); 337 boolean wasVisible = mDragHandle != null; 338 if (visible != wasVisible) { 339 if (visible) { 340 mDragHandle = recycle != null ? recycle : 341 mLauncher.getDrawable(R.drawable.drag_handle_indicator); 342 mDragHandle.setBounds(mDragHandleBounds); 343 344 updateDragHandleAlpha(); 345 } else { 346 mDragHandle = null; 347 } 348 invalidate(); 349 } 350 } 351 352 @Override dispatchHoverEvent(MotionEvent event)353 public boolean dispatchHoverEvent(MotionEvent event) { 354 return mAccessibilityHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event); 355 } 356 357 @Override dispatchKeyEvent(KeyEvent event)358 public boolean dispatchKeyEvent(KeyEvent event) { 359 return mAccessibilityHelper.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); 360 } 361 362 @Override onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)363 public void onFocusChanged(boolean gainFocus, int direction, 364 Rect previouslyFocusedRect) { 365 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 366 mAccessibilityHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 367 } 368 369 @Override onStateTransitionStart(LauncherState toState)370 public void onStateTransitionStart(LauncherState toState) {} 371 372 @Override onStateTransitionComplete(LauncherState finalState)373 public void onStateTransitionComplete(LauncherState finalState) { 374 handleStateChangedComplete(finalState); 375 } 376 handleStateChangedComplete(LauncherState finalState)377 private void handleStateChangedComplete(LauncherState finalState) { 378 setImportantForAccessibility(finalState == ALL_APPS 379 ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS 380 : IMPORTANT_FOR_ACCESSIBILITY_AUTO); 381 } 382 383 protected class AccessibilityHelper extends ExploreByTouchHelper { 384 385 private static final int DRAG_HANDLE_ID = 1; 386 AccessibilityHelper()387 public AccessibilityHelper() { 388 super(ScrimView.this); 389 } 390 391 @Override getVirtualViewAt(float x, float y)392 protected int getVirtualViewAt(float x, float y) { 393 return mDragHandleBounds.contains((int) x, (int) y) 394 ? DRAG_HANDLE_ID : INVALID_ID; 395 } 396 397 @Override getVisibleVirtualViews(List<Integer> virtualViewIds)398 protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { 399 virtualViewIds.add(DRAG_HANDLE_ID); 400 } 401 402 @Override onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat node)403 protected void onPopulateNodeForVirtualView(int virtualViewId, 404 AccessibilityNodeInfoCompat node) { 405 node.setContentDescription(getContext().getString(R.string.all_apps_button_label)); 406 node.setBoundsInParent(mDragHandleBounds); 407 408 getLocationOnScreen(mTempPos); 409 mTempRect.set(mDragHandleBounds); 410 mTempRect.offset(mTempPos[0], mTempPos[1]); 411 node.setBoundsInScreen(mTempRect); 412 413 node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); 414 node.setClickable(true); 415 node.setFocusable(true); 416 417 if (mLauncher.isInState(NORMAL)) { 418 Context context = getContext(); 419 if (Utilities.isWallpaperAllowed(context)) { 420 node.addAction( 421 new AccessibilityActionCompat(WALLPAPERS, context.getText(WALLPAPERS))); 422 } 423 node.addAction(new AccessibilityActionCompat(WIDGETS, context.getText(WIDGETS))); 424 node.addAction(new AccessibilityActionCompat(SETTINGS, context.getText(SETTINGS))); 425 } 426 } 427 428 @Override onPerformActionForVirtualView( int virtualViewId, int action, Bundle arguments)429 protected boolean onPerformActionForVirtualView( 430 int virtualViewId, int action, Bundle arguments) { 431 if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) { 432 mLauncher.getUserEventDispatcher().logActionOnControl( 433 Action.Touch.TAP, ControlType.ALL_APPS_BUTTON, 434 mLauncher.getStateManager().getState().containerType); 435 mLauncher.getStateManager().goToState(ALL_APPS); 436 return true; 437 } else if (action == WALLPAPERS) { 438 return OptionsPopupView.startWallpaperPicker(ScrimView.this); 439 } else if (action == WIDGETS) { 440 return OptionsPopupView.onWidgetsClicked(ScrimView.this); 441 } else if (action == SETTINGS) { 442 return OptionsPopupView.startSettings(ScrimView.this); 443 } 444 445 return false; 446 } 447 } 448 getDragHandleSize()449 public int getDragHandleSize() { 450 return mDragHandleSize; 451 } 452 } 453