Files
Lawnchair/src/com/android/launcher3/views/ScrimView.java
T
Andy Wickham c40872b913 Marks ScrimView unimportant for accessibility when Widgets screen is opened.
This prevents it from holding focus while the Widgets screen is visible
(after using Widgets action from the All Apps caret thingy).

Test: Manually followed steps provided in bug, and issue not seen after this change.
Fixes: 139918680
Change-Id: I280ac97fb7ff9fa67f1c6a1ce9cdfa9e451231eb
2019-09-03 21:20:22 +00:00

472 lines
17 KiB
Java

/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.views;
import static android.content.Context.ACCESSIBILITY_SERVICE;
import static android.view.MotionEvent.ACTION_DOWN;
import static androidx.core.graphics.ColorUtils.compositeColors;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.Keyframe;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.RectEvaluator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Property;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
import androidx.customview.widget.ExploreByTouchHelper;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.LauncherStateManager.StateListener;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.Themes;
import com.android.launcher3.widget.WidgetsFullSheet;
import java.util.List;
/**
* Simple scrim which draws a flat color
*/
public class ScrimView extends View implements Insettable, OnChangeListener,
AccessibilityStateChangeListener, StateListener {
public static final Property<ScrimView, Integer> DRAG_HANDLE_ALPHA =
new Property<ScrimView, Integer>(Integer.TYPE, "dragHandleAlpha") {
@Override
public Integer get(ScrimView scrimView) {
return scrimView.mDragHandleAlpha;
}
@Override
public void set(ScrimView scrimView, Integer value) {
scrimView.setDragHandleAlpha(value);
}
};
private static final int WALLPAPERS = R.string.wallpaper_button_text;
private static final int WIDGETS = R.string.widget_button_text;
private static final int SETTINGS = R.string.settings_button_text;
private static final int ALPHA_CHANNEL_COUNT = 1;
private final Rect mTempRect = new Rect();
private final int[] mTempPos = new int[2];
protected final Launcher mLauncher;
private final WallpaperColorInfo mWallpaperColorInfo;
private final AccessibilityManager mAM;
protected final int mEndScrim;
protected float mMaxScrimAlpha;
protected float mProgress = 1;
protected int mScrimColor;
protected int mCurrentFlatColor;
protected int mEndFlatColor;
protected int mEndFlatColorAlpha;
protected final int mDragHandleSize;
protected float mDragHandleOffset;
private final Rect mDragHandleBounds;
private final RectF mHitRect = new RectF();
private final MultiValueAlpha mMultiValueAlpha;
private final AccessibilityHelper mAccessibilityHelper;
@Nullable
protected Drawable mDragHandle;
private int mDragHandleAlpha = 255;
public ScrimView(Context context, AttributeSet attrs) {
super(context, attrs);
mLauncher = Launcher.getLauncher(context);
mWallpaperColorInfo = WallpaperColorInfo.getInstance(context);
mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
mMaxScrimAlpha = 0.7f;
mDragHandleSize = context.getResources()
.getDimensionPixelSize(R.dimen.vertical_drag_handle_size);
mDragHandleBounds = new Rect(0, 0, mDragHandleSize, mDragHandleSize);
mAccessibilityHelper = createAccessibilityHelper();
ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
mAM = (AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE);
setFocusable(false);
mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);
}
public AlphaProperty getAlphaProperty(int index) {
return mMultiValueAlpha.getProperty(index);
}
@NonNull
protected AccessibilityHelper createAccessibilityHelper() {
return new AccessibilityHelper();
}
@Override
public void setInsets(Rect insets) {
updateDragHandleBounds();
updateDragHandleVisibility(null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
updateDragHandleBounds();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mWallpaperColorInfo.addOnChangeListener(this);
onExtractedColorsChanged(mWallpaperColorInfo);
mAM.addAccessibilityStateChangeListener(this);
onAccessibilityStateChanged(mAM.isEnabled());
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mWallpaperColorInfo.removeOnChangeListener(this);
mAM.removeAccessibilityStateChangeListener(this);
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
@Override
public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
mScrimColor = wallpaperColorInfo.getMainColor();
mEndFlatColor = compositeColors(mEndScrim, setColorAlphaBound(
mScrimColor, Math.round(mMaxScrimAlpha * 255)));
mEndFlatColorAlpha = Color.alpha(mEndFlatColor);
updateColors();
invalidate();
}
public void setProgress(float progress) {
if (mProgress != progress) {
mProgress = progress;
updateColors();
updateDragHandleAlpha();
invalidate();
}
}
public void reInitUi() { }
protected void updateColors() {
mCurrentFlatColor = mProgress >= 1 ? 0 : setColorAlphaBound(
mEndFlatColor, Math.round((1 - mProgress) * mEndFlatColorAlpha));
}
protected void updateDragHandleAlpha() {
if (mDragHandle != null) {
mDragHandle.setAlpha(mDragHandleAlpha);
}
}
private void setDragHandleAlpha(int alpha) {
if (alpha != mDragHandleAlpha) {
mDragHandleAlpha = alpha;
if (mDragHandle != null) {
mDragHandle.setAlpha(mDragHandleAlpha);
invalidate();
}
}
}
@Override
protected void onDraw(Canvas canvas) {
if (mCurrentFlatColor != 0) {
canvas.drawColor(mCurrentFlatColor);
}
drawDragHandle(canvas);
}
protected void drawDragHandle(Canvas canvas) {
if (mDragHandle != null) {
canvas.translate(0, -mDragHandleOffset);
mDragHandle.draw(canvas);
canvas.translate(0, mDragHandleOffset);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean value = super.onTouchEvent(event);
if (!value && mDragHandle != null && event.getAction() == ACTION_DOWN
&& mDragHandle.getAlpha() == 255
&& mHitRect.contains(event.getX(), event.getY())) {
final Drawable drawable = mDragHandle;
mDragHandle = null;
Rect bounds = new Rect(mDragHandleBounds);
bounds.offset(0, -(int) mDragHandleOffset);
drawable.setBounds(bounds);
Rect topBounds = new Rect(bounds);
topBounds.offset(0, -bounds.height() / 2);
Rect invalidateRegion = new Rect(bounds);
invalidateRegion.top = topBounds.top;
Keyframe frameTop = Keyframe.ofObject(0.6f, topBounds);
frameTop.setInterpolator(DEACCEL);
Keyframe frameBot = Keyframe.ofObject(1, bounds);
frameBot.setInterpolator(ACCEL);
PropertyValuesHolder holder = PropertyValuesHolder .ofKeyframe("bounds",
Keyframe.ofObject(0, bounds), frameTop, frameBot);
holder.setEvaluator(new RectEvaluator());
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, holder);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
getOverlay().remove(drawable);
updateDragHandleVisibility(drawable);
}
});
anim.addUpdateListener((v) -> invalidate(invalidateRegion));
getOverlay().add(drawable);
anim.start();
}
return value;
}
protected void updateDragHandleBounds() {
DeviceProfile grid = mLauncher.getDeviceProfile();
final int left;
final int width = getMeasuredWidth();
final int top = getMeasuredHeight() - mDragHandleSize - grid.getInsets().bottom;
final int topMargin;
if (grid.isVerticalBarLayout()) {
topMargin = grid.workspacePadding.bottom;
if (grid.isSeascape()) {
left = width - grid.getInsets().right - mDragHandleSize;
} else {
left = mDragHandleSize + grid.getInsets().left;
}
} else {
left = (width - mDragHandleSize) / 2;
topMargin = grid.hotseatBarSizePx;
}
mDragHandleBounds.offsetTo(left, top - topMargin);
mHitRect.set(mDragHandleBounds);
float inset = -mDragHandleSize / 2;
mHitRect.inset(inset, inset);
if (mDragHandle != null) {
mDragHandle.setBounds(mDragHandleBounds);
}
}
@Override
public void onAccessibilityStateChanged(boolean enabled) {
LauncherStateManager stateManager = mLauncher.getStateManager();
stateManager.removeStateListener(this);
if (enabled) {
stateManager.addStateListener(this);
handleStateChangedComplete(stateManager.getState());
} else {
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
updateDragHandleVisibility(null);
}
private void updateDragHandleVisibility(Drawable recycle) {
boolean visible = mLauncher.getDeviceProfile().isVerticalBarLayout() || mAM.isEnabled();
boolean wasVisible = mDragHandle != null;
if (visible != wasVisible) {
if (visible) {
mDragHandle = recycle != null ? recycle :
mLauncher.getDrawable(R.drawable.drag_handle_indicator);
mDragHandle.setBounds(mDragHandleBounds);
updateDragHandleAlpha();
} else {
mDragHandle = null;
}
invalidate();
}
}
@Override
public boolean dispatchHoverEvent(MotionEvent event) {
return mAccessibilityHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return mAccessibilityHelper.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
}
@Override
public void onFocusChanged(boolean gainFocus, int direction,
Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
mAccessibilityHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
}
@Override
public void onStateTransitionStart(LauncherState toState) {}
@Override
public void onStateTransitionComplete(LauncherState finalState) {
handleStateChangedComplete(finalState);
}
private void handleStateChangedComplete(LauncherState finalState) {
setImportantForAccessibility(finalState == ALL_APPS
? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
: IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
protected class AccessibilityHelper extends ExploreByTouchHelper {
private static final int DRAG_HANDLE_ID = 1;
public AccessibilityHelper() {
super(ScrimView.this);
}
@Override
protected int getVirtualViewAt(float x, float y) {
return mDragHandleBounds.contains((int) x, (int) y)
? DRAG_HANDLE_ID : INVALID_ID;
}
@Override
protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
virtualViewIds.add(DRAG_HANDLE_ID);
}
@Override
protected void onPopulateNodeForVirtualView(int virtualViewId,
AccessibilityNodeInfoCompat node) {
node.setContentDescription(getContext().getString(R.string.all_apps_button_label));
node.setBoundsInParent(mDragHandleBounds);
getLocationOnScreen(mTempPos);
mTempRect.set(mDragHandleBounds);
mTempRect.offset(mTempPos[0], mTempPos[1]);
node.setBoundsInScreen(mTempRect);
node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
node.setClickable(true);
node.setFocusable(true);
if (mLauncher.isInState(NORMAL)) {
Context context = getContext();
if (Utilities.isWallpaperAllowed(context)) {
node.addAction(
new AccessibilityActionCompat(WALLPAPERS, context.getText(WALLPAPERS)));
}
node.addAction(new AccessibilityActionCompat(WIDGETS, context.getText(WIDGETS)));
node.addAction(new AccessibilityActionCompat(SETTINGS, context.getText(SETTINGS)));
}
}
@Override
protected boolean onPerformActionForVirtualView(
int virtualViewId, int action, Bundle arguments) {
if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
mLauncher.getUserEventDispatcher().logActionOnControl(
Action.Touch.TAP, ControlType.ALL_APPS_BUTTON,
mLauncher.getStateManager().getState().containerType);
mLauncher.getStateManager().goToState(ALL_APPS);
return true;
} else if (action == WALLPAPERS) {
return OptionsPopupView.startWallpaperPicker(ScrimView.this);
} else if (action == WIDGETS) {
int originalImportanceForAccessibility = getImportantForAccessibility();
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
WidgetsFullSheet widgetsFullSheet = OptionsPopupView.openWidgets(mLauncher);
if (widgetsFullSheet == null) {
setImportantForAccessibility(originalImportanceForAccessibility);
return false;
}
widgetsFullSheet.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {}
@Override
public void onViewDetachedFromWindow(View view) {
setImportantForAccessibility(originalImportanceForAccessibility);
widgetsFullSheet.removeOnAttachStateChangeListener(this);
}
});
return true;
} else if (action == SETTINGS) {
return OptionsPopupView.startSettings(ScrimView.this);
}
return false;
}
}
public int getDragHandleSize() {
return mDragHandleSize;
}
}