• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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