blob: 7066980a9dd3eced4fc866fb97d284dd1014303a [file] [log] [blame]
/*
* 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.support.v4.graphics.ColorUtils.compositeColors;
import static android.support.v4.graphics.ColorUtils.setAlphaComponent;
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 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.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
import android.support.v4.widget.ExploreByTouchHelper;
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.Themes;
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 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 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);
}
@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, setAlphaComponent(
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 : setAlphaComponent(
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);
onStateSetImmediately(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) {
onStateSetImmediately(finalState);
}
@Override
public void onStateSetImmediately(LauncherState state) {
setImportantForAccessibility(state == 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;
}
}