/*
|
* 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 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 static androidx.core.graphics.ColorUtils.compositeColors;
|
|
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 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 java.util.List;
|
|
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;
|
|
/**
|
* 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(mLauncher.getStateManager().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) {
|
return OptionsPopupView.onWidgetsClicked(ScrimView.this);
|
} else if (action == SETTINGS) {
|
return OptionsPopupView.startSettings(ScrimView.this);
|
}
|
|
return false;
|
}
|
}
|
|
public int getDragHandleSize() {
|
return mDragHandleSize;
|
}
|
}
|