• 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.support.v4.graphics.ColorUtils.compositeColors;
20 import static android.support.v4.graphics.ColorUtils.setAlphaComponent;
21 import static android.view.MotionEvent.ACTION_DOWN;
22 
23 import static com.android.launcher3.LauncherState.ALL_APPS;
24 import static com.android.launcher3.LauncherState.NORMAL;
25 import static com.android.launcher3.anim.Interpolators.ACCEL;
26 import static com.android.launcher3.anim.Interpolators.DEACCEL;
27 
28 import android.animation.Animator;
29 import android.animation.AnimatorListenerAdapter;
30 import android.animation.Keyframe;
31 import android.animation.ObjectAnimator;
32 import android.animation.PropertyValuesHolder;
33 import android.animation.RectEvaluator;
34 import android.content.Context;
35 import android.graphics.Canvas;
36 import android.graphics.Color;
37 import android.graphics.Rect;
38 import android.graphics.RectF;
39 import android.graphics.drawable.Drawable;
40 import android.os.Bundle;
41 import android.support.annotation.NonNull;
42 import android.support.annotation.Nullable;
43 import android.support.v4.view.ViewCompat;
44 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
45 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
46 import android.support.v4.widget.ExploreByTouchHelper;
47 import android.util.AttributeSet;
48 import android.util.Property;
49 import android.view.KeyEvent;
50 import android.view.MotionEvent;
51 import android.view.View;
52 import android.view.accessibility.AccessibilityManager;
53 import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
54 
55 import com.android.launcher3.DeviceProfile;
56 import com.android.launcher3.Insettable;
57 import com.android.launcher3.Launcher;
58 import com.android.launcher3.LauncherState;
59 import com.android.launcher3.LauncherStateManager;
60 import com.android.launcher3.LauncherStateManager.StateListener;
61 import com.android.launcher3.R;
62 import com.android.launcher3.Utilities;
63 import com.android.launcher3.uioverrides.WallpaperColorInfo;
64 import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener;
65 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
66 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
67 import com.android.launcher3.util.Themes;
68 
69 import java.util.List;
70 
71 /**
72  * Simple scrim which draws a flat color
73  */
74 public class ScrimView extends View implements Insettable, OnChangeListener,
75         AccessibilityStateChangeListener, StateListener {
76 
77     public static final Property<ScrimView, Integer> DRAG_HANDLE_ALPHA =
78             new Property<ScrimView, Integer>(Integer.TYPE, "dragHandleAlpha") {
79 
80                 @Override
81                 public Integer get(ScrimView scrimView) {
82                     return scrimView.mDragHandleAlpha;
83                 }
84 
85                 @Override
86                 public void set(ScrimView scrimView, Integer value) {
87                     scrimView.setDragHandleAlpha(value);
88                 }
89             };
90     private static final int WALLPAPERS = R.string.wallpaper_button_text;
91     private static final int WIDGETS = R.string.widget_button_text;
92     private static final int SETTINGS = R.string.settings_button_text;
93 
94     private final Rect mTempRect = new Rect();
95     private final int[] mTempPos = new int[2];
96 
97     protected final Launcher mLauncher;
98     private final WallpaperColorInfo mWallpaperColorInfo;
99     private final AccessibilityManager mAM;
100     protected final int mEndScrim;
101 
102     protected float mMaxScrimAlpha;
103 
104     protected float mProgress = 1;
105     protected int mScrimColor;
106 
107     protected int mCurrentFlatColor;
108     protected int mEndFlatColor;
109     protected int mEndFlatColorAlpha;
110 
111     protected final int mDragHandleSize;
112     private final Rect mDragHandleBounds;
113     private final RectF mHitRect = new RectF();
114 
115     private final AccessibilityHelper mAccessibilityHelper;
116     @Nullable
117     protected Drawable mDragHandle;
118 
119     private int mDragHandleAlpha = 255;
120 
ScrimView(Context context, AttributeSet attrs)121     public ScrimView(Context context, AttributeSet attrs) {
122         super(context, attrs);
123         mLauncher = Launcher.getLauncher(context);
124         mWallpaperColorInfo = WallpaperColorInfo.getInstance(context);
125         mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
126 
127         mMaxScrimAlpha = 0.7f;
128 
129         mDragHandleSize = context.getResources()
130                 .getDimensionPixelSize(R.dimen.vertical_drag_handle_size);
131         mDragHandleBounds = new Rect(0, 0, mDragHandleSize, mDragHandleSize);
132 
133         mAccessibilityHelper = createAccessibilityHelper();
134         ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
135 
136         mAM = (AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE);
137         setFocusable(false);
138     }
139 
140     @NonNull
createAccessibilityHelper()141     protected AccessibilityHelper createAccessibilityHelper() {
142         return new AccessibilityHelper();
143     }
144 
145     @Override
setInsets(Rect insets)146     public void setInsets(Rect insets) {
147         updateDragHandleBounds();
148         updateDragHandleVisibility(null);
149     }
150 
151     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)152     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
153         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
154         updateDragHandleBounds();
155     }
156 
157     @Override
onAttachedToWindow()158     protected void onAttachedToWindow() {
159         super.onAttachedToWindow();
160         mWallpaperColorInfo.addOnChangeListener(this);
161         onExtractedColorsChanged(mWallpaperColorInfo);
162 
163         mAM.addAccessibilityStateChangeListener(this);
164         onAccessibilityStateChanged(mAM.isEnabled());
165     }
166 
167     @Override
onDetachedFromWindow()168     protected void onDetachedFromWindow() {
169         super.onDetachedFromWindow();
170         mWallpaperColorInfo.removeOnChangeListener(this);
171         mAM.removeAccessibilityStateChangeListener(this);
172     }
173 
174     @Override
hasOverlappingRendering()175     public boolean hasOverlappingRendering() {
176         return false;
177     }
178 
179     @Override
onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo)180     public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
181         mScrimColor = wallpaperColorInfo.getMainColor();
182         mEndFlatColor = compositeColors(mEndScrim, setAlphaComponent(
183                 mScrimColor, Math.round(mMaxScrimAlpha * 255)));
184         mEndFlatColorAlpha = Color.alpha(mEndFlatColor);
185         updateColors();
186         invalidate();
187     }
188 
setProgress(float progress)189     public void setProgress(float progress) {
190         if (mProgress != progress) {
191             mProgress = progress;
192             updateColors();
193             updateDragHandleAlpha();
194             invalidate();
195         }
196     }
197 
reInitUi()198     public void reInitUi() { }
199 
updateColors()200     protected void updateColors() {
201         mCurrentFlatColor = mProgress >= 1 ? 0 : setAlphaComponent(
202                 mEndFlatColor, Math.round((1 - mProgress) * mEndFlatColorAlpha));
203     }
204 
updateDragHandleAlpha()205     protected void updateDragHandleAlpha() {
206         if (mDragHandle != null) {
207             mDragHandle.setAlpha(mDragHandleAlpha);
208         }
209     }
210 
setDragHandleAlpha(int alpha)211     private void setDragHandleAlpha(int alpha) {
212         if (alpha != mDragHandleAlpha) {
213             mDragHandleAlpha = alpha;
214             if (mDragHandle != null) {
215                 mDragHandle.setAlpha(mDragHandleAlpha);
216                 invalidate();
217             }
218         }
219     }
220 
221     @Override
onDraw(Canvas canvas)222     protected void onDraw(Canvas canvas) {
223         if (mCurrentFlatColor != 0) {
224             canvas.drawColor(mCurrentFlatColor);
225         }
226         if (mDragHandle != null) {
227             mDragHandle.draw(canvas);
228         }
229     }
230 
231     @Override
onTouchEvent(MotionEvent event)232     public boolean onTouchEvent(MotionEvent event) {
233         boolean value = super.onTouchEvent(event);
234         if (!value && mDragHandle != null && event.getAction() == ACTION_DOWN
235                 && mDragHandle.getAlpha() == 255
236                 && mHitRect.contains(event.getX(), event.getY())) {
237 
238             final Drawable drawable = mDragHandle;
239             mDragHandle = null;
240             drawable.setBounds(mDragHandleBounds);
241 
242             Rect topBounds = new Rect(mDragHandleBounds);
243             topBounds.offset(0, -mDragHandleBounds.height() / 2);
244 
245             Rect invalidateRegion = new Rect(mDragHandleBounds);
246             invalidateRegion.top = topBounds.top;
247 
248             Keyframe frameTop = Keyframe.ofObject(0.6f, topBounds);
249             frameTop.setInterpolator(DEACCEL);
250             Keyframe frameBot = Keyframe.ofObject(1, mDragHandleBounds);
251             frameBot.setInterpolator(ACCEL);
252             PropertyValuesHolder holder = PropertyValuesHolder .ofKeyframe("bounds",
253                     Keyframe.ofObject(0, mDragHandleBounds), frameTop, frameBot);
254             holder.setEvaluator(new RectEvaluator());
255 
256             ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, holder);
257             anim.addListener(new AnimatorListenerAdapter() {
258                 @Override
259                 public void onAnimationEnd(Animator animation) {
260                     getOverlay().remove(drawable);
261                     updateDragHandleVisibility(drawable);
262                 }
263             });
264             anim.addUpdateListener((v) -> invalidate(invalidateRegion));
265             getOverlay().add(drawable);
266             anim.start();
267         }
268         return value;
269     }
270 
updateDragHandleBounds()271     protected void updateDragHandleBounds() {
272         DeviceProfile grid = mLauncher.getDeviceProfile();
273         final int left;
274         final int width = getMeasuredWidth();
275         final int top = getMeasuredHeight() - mDragHandleSize - grid.getInsets().bottom;
276         final int topMargin;
277 
278         if (grid.isVerticalBarLayout()) {
279             topMargin = grid.workspacePadding.bottom;
280             if (grid.isSeascape()) {
281                 left = width - grid.getInsets().right - mDragHandleSize;
282             } else {
283                 left = mDragHandleSize + grid.getInsets().left;
284             }
285         } else {
286             left = (width - mDragHandleSize) / 2;
287             topMargin = grid.hotseatBarSizePx;
288         }
289         mDragHandleBounds.offsetTo(left, top - topMargin);
290         mHitRect.set(mDragHandleBounds);
291         float inset = -mDragHandleSize / 2;
292         mHitRect.inset(inset, inset);
293 
294         if (mDragHandle != null) {
295             mDragHandle.setBounds(mDragHandleBounds);
296         }
297     }
298 
299     @Override
onAccessibilityStateChanged(boolean enabled)300     public void onAccessibilityStateChanged(boolean enabled) {
301         LauncherStateManager stateManager = mLauncher.getStateManager();
302         stateManager.removeStateListener(this);
303 
304         if (enabled) {
305             stateManager.addStateListener(this);
306             onStateSetImmediately(mLauncher.getStateManager().getState());
307         } else {
308             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
309         }
310         updateDragHandleVisibility(null);
311     }
312 
updateDragHandleVisibility(Drawable recycle)313     private void updateDragHandleVisibility(Drawable recycle) {
314         boolean visible = mLauncher.getDeviceProfile().isVerticalBarLayout() || mAM.isEnabled();
315         boolean wasVisible = mDragHandle != null;
316         if (visible != wasVisible) {
317             if (visible) {
318                 mDragHandle = recycle != null ? recycle :
319                         mLauncher.getDrawable(R.drawable.drag_handle_indicator);
320                 mDragHandle.setBounds(mDragHandleBounds);
321 
322                 updateDragHandleAlpha();
323             } else {
324                 mDragHandle = null;
325             }
326             invalidate();
327         }
328     }
329 
330     @Override
dispatchHoverEvent(MotionEvent event)331     public boolean dispatchHoverEvent(MotionEvent event) {
332         return mAccessibilityHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event);
333     }
334 
335     @Override
dispatchKeyEvent(KeyEvent event)336     public boolean dispatchKeyEvent(KeyEvent event) {
337         return mAccessibilityHelper.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
338     }
339 
340     @Override
onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)341     public void onFocusChanged(boolean gainFocus, int direction,
342             Rect previouslyFocusedRect) {
343         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
344         mAccessibilityHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
345     }
346 
347     @Override
onStateTransitionStart(LauncherState toState)348     public void onStateTransitionStart(LauncherState toState) {}
349 
350     @Override
onStateTransitionComplete(LauncherState finalState)351     public void onStateTransitionComplete(LauncherState finalState) {
352         onStateSetImmediately(finalState);
353     }
354 
355     @Override
onStateSetImmediately(LauncherState state)356     public void onStateSetImmediately(LauncherState state) {
357         setImportantForAccessibility(state == ALL_APPS
358                 ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
359                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
360     }
361 
362     protected class AccessibilityHelper extends ExploreByTouchHelper {
363 
364         private static final int DRAG_HANDLE_ID = 1;
365 
AccessibilityHelper()366         public AccessibilityHelper() {
367             super(ScrimView.this);
368         }
369 
370         @Override
getVirtualViewAt(float x, float y)371         protected int getVirtualViewAt(float x, float y) {
372             return  mDragHandleBounds.contains((int) x, (int) y)
373                     ? DRAG_HANDLE_ID : INVALID_ID;
374         }
375 
376         @Override
getVisibleVirtualViews(List<Integer> virtualViewIds)377         protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
378             virtualViewIds.add(DRAG_HANDLE_ID);
379         }
380 
381         @Override
onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat node)382         protected void onPopulateNodeForVirtualView(int virtualViewId,
383                 AccessibilityNodeInfoCompat node) {
384             node.setContentDescription(getContext().getString(R.string.all_apps_button_label));
385             node.setBoundsInParent(mDragHandleBounds);
386 
387             getLocationOnScreen(mTempPos);
388             mTempRect.set(mDragHandleBounds);
389             mTempRect.offset(mTempPos[0], mTempPos[1]);
390             node.setBoundsInScreen(mTempRect);
391 
392             node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
393             node.setClickable(true);
394             node.setFocusable(true);
395 
396             if (mLauncher.isInState(NORMAL)) {
397                 Context context = getContext();
398                 if (Utilities.isWallpaperAllowed(context)) {
399                     node.addAction(
400                             new AccessibilityActionCompat(WALLPAPERS, context.getText(WALLPAPERS)));
401                 }
402                 node.addAction(new AccessibilityActionCompat(WIDGETS, context.getText(WIDGETS)));
403                 node.addAction(new AccessibilityActionCompat(SETTINGS, context.getText(SETTINGS)));
404             }
405         }
406 
407         @Override
onPerformActionForVirtualView( int virtualViewId, int action, Bundle arguments)408         protected boolean onPerformActionForVirtualView(
409                 int virtualViewId, int action, Bundle arguments) {
410             if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
411                 mLauncher.getUserEventDispatcher().logActionOnControl(
412                         Action.Touch.TAP, ControlType.ALL_APPS_BUTTON,
413                         mLauncher.getStateManager().getState().containerType);
414                 mLauncher.getStateManager().goToState(ALL_APPS);
415                 return true;
416             } else if (action == WALLPAPERS) {
417                 return OptionsPopupView.startWallpaperPicker(ScrimView.this);
418             } else if (action == WIDGETS) {
419                 return OptionsPopupView.onWidgetsClicked(ScrimView.this);
420             } else if (action == SETTINGS) {
421                 return OptionsPopupView.startSettings(ScrimView.this);
422             }
423 
424             return false;
425         }
426     }
427 
getDragHandleSize()428     public int getDragHandleSize() {
429         return mDragHandleSize;
430     }
431 }
432