| /* |
| * 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; |
| } |
| } |