• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.widget;
17 
18 import android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.PropertyValuesHolder;
21 import android.content.Context;
22 import android.graphics.Rect;
23 import android.util.AttributeSet;
24 import android.util.Pair;
25 import android.view.LayoutInflater;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.view.animation.AnimationUtils;
29 
30 import com.android.launcher3.Insettable;
31 import com.android.launcher3.Launcher;
32 import com.android.launcher3.LauncherAppState;
33 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
34 import com.android.launcher3.R;
35 import com.android.launcher3.views.RecyclerViewFastScroller;
36 import com.android.launcher3.views.TopRoundedCornerView;
37 
38 /**
39  * Popup for showing the full list of available widgets
40  */
41 public class WidgetsFullSheet extends BaseWidgetSheet
42         implements Insettable, ProviderChangedListener {
43 
44     private static final long DEFAULT_OPEN_DURATION = 267;
45     private static final long FADE_IN_DURATION = 150;
46     private static final float VERTICAL_START_POSITION = 0.3f;
47 
48     private final Rect mInsets = new Rect();
49 
50     private final WidgetsListAdapter mAdapter;
51 
52     private WidgetsRecyclerView mRecyclerView;
53 
WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr)54     public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
55         super(context, attrs, defStyleAttr);
56         LauncherAppState apps = LauncherAppState.getInstance(context);
57         mAdapter = new WidgetsListAdapter(context,
58                 LayoutInflater.from(context), apps.getWidgetCache(), apps.getIconCache(),
59                 this, this);
60 
61     }
62 
WidgetsFullSheet(Context context, AttributeSet attrs)63     public WidgetsFullSheet(Context context, AttributeSet attrs) {
64         this(context, attrs, 0);
65     }
66 
67     @Override
onFinishInflate()68     protected void onFinishInflate() {
69         super.onFinishInflate();
70         mContent = findViewById(R.id.container);
71 
72         mRecyclerView = findViewById(R.id.widgets_list_view);
73         mRecyclerView.setAdapter(mAdapter);
74         mAdapter.setApplyBitmapDeferred(true, mRecyclerView);
75 
76         TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
77         springLayout.addSpringView(R.id.widgets_list_view);
78         mRecyclerView.setEdgeEffectFactory(springLayout.createEdgeEffectFactory());
79         onWidgetsBound();
80     }
81 
82     @Override
getAccessibilityTarget()83     protected Pair<View, String> getAccessibilityTarget() {
84         return Pair.create(mRecyclerView, getContext().getString(
85                 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
86     }
87 
88     @Override
onAttachedToWindow()89     protected void onAttachedToWindow() {
90         super.onAttachedToWindow();
91         mLauncher.getAppWidgetHost().addProviderChangeListener(this);
92         notifyWidgetProvidersChanged();
93     }
94 
95     @Override
onDetachedFromWindow()96     protected void onDetachedFromWindow() {
97         super.onDetachedFromWindow();
98         mLauncher.getAppWidgetHost().removeProviderChangeListener(this);
99     }
100 
101     @Override
setInsets(Rect insets)102     public void setInsets(Rect insets) {
103         mInsets.set(insets);
104 
105         mRecyclerView.setPadding(
106                 mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(),
107                 mRecyclerView.getPaddingRight(), insets.bottom);
108         if (insets.bottom > 0) {
109             setupNavBarColor();
110         } else {
111             clearNavBarColor();
112         }
113 
114         ((TopRoundedCornerView) mContent).setNavBarScrimHeight(mInsets.bottom);
115         requestLayout();
116     }
117 
118     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)119     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
120         int widthUsed;
121         if (mInsets.bottom > 0) {
122             widthUsed = 0;
123         } else {
124             Rect padding = mLauncher.getDeviceProfile().workspacePadding;
125             widthUsed = Math.max(padding.left + padding.right,
126                     2 * (mInsets.left + mInsets.right));
127         }
128 
129         int heightUsed = mInsets.top + mLauncher.getDeviceProfile().edgeMarginPx;
130         measureChildWithMargins(mContent, widthMeasureSpec,
131                 widthUsed, heightMeasureSpec, heightUsed);
132         setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
133                 MeasureSpec.getSize(heightMeasureSpec));
134     }
135 
136     @Override
onLayout(boolean changed, int l, int t, int r, int b)137     protected void onLayout(boolean changed, int l, int t, int r, int b) {
138         int width = r - l;
139         int height = b - t;
140 
141         // Content is laid out as center bottom aligned
142         int contentWidth = mContent.getMeasuredWidth();
143         int contentLeft = (width - contentWidth) / 2;
144         mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
145                 contentLeft + contentWidth, height);
146 
147         setTranslationShift(mTranslationShift);
148     }
149 
150     @Override
notifyWidgetProvidersChanged()151     public void notifyWidgetProvidersChanged() {
152         mLauncher.refreshAndBindWidgetsForPackageUser(null);
153     }
154 
155     @Override
onWidgetsBound()156     protected void onWidgetsBound() {
157         mAdapter.setWidgets(mLauncher.getPopupDataProvider().getAllWidgets());
158     }
159 
open(boolean animate)160     private void open(boolean animate) {
161         if (animate) {
162             if (mLauncher.getDragLayer().getInsets().bottom > 0) {
163                 mContent.setAlpha(0);
164                 setTranslationShift(VERTICAL_START_POSITION);
165             }
166             mOpenCloseAnimator.setValues(
167                     PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
168             mOpenCloseAnimator
169                     .setDuration(DEFAULT_OPEN_DURATION)
170                     .setInterpolator(AnimationUtils.loadInterpolator(
171                             getContext(), android.R.interpolator.linear_out_slow_in));
172             mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
173                 @Override
174                 public void onAnimationEnd(Animator animation) {
175                     mRecyclerView.setLayoutFrozen(false);
176                     mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
177                     mOpenCloseAnimator.removeListener(this);
178                 }
179             });
180             post(() -> {
181                 mRecyclerView.setLayoutFrozen(true);
182                 mOpenCloseAnimator.start();
183                 mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
184             });
185         } else {
186             setTranslationShift(TRANSLATION_SHIFT_OPENED);
187             mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
188             post(this::announceAccessibilityChanges);
189         }
190     }
191 
192     @Override
handleClose(boolean animate)193     protected void handleClose(boolean animate) {
194         handleClose(animate, DEFAULT_OPEN_DURATION);
195     }
196 
197     @Override
isOfType(int type)198     protected boolean isOfType(int type) {
199         return (type & TYPE_WIDGETS_FULL_SHEET) != 0;
200     }
201 
202     @Override
onControllerInterceptTouchEvent(MotionEvent ev)203     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
204         // Disable swipe down when recycler view is scrolling
205         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
206             mNoIntercept = false;
207             RecyclerViewFastScroller scroller = mRecyclerView.getScrollbar();
208             if (scroller.getThumbOffsetY() >= 0 &&
209                     mLauncher.getDragLayer().isEventOverView(scroller, ev)) {
210                 mNoIntercept = true;
211             } else if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
212                 mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer());
213             }
214         }
215         return super.onControllerInterceptTouchEvent(ev);
216     }
217 
show(Launcher launcher, boolean animate)218     public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
219         WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
220                 .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
221         sheet.mIsOpen = true;
222         launcher.getDragLayer().addView(sheet);
223         sheet.open(animate);
224         return sheet;
225     }
226 
227     @Override
getElementsRowCount()228     protected int getElementsRowCount() {
229         return mAdapter.getItemCount();
230     }
231 }
232