• 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 
17 package com.android.launcher3.views;
18 
19 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
20 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT;
21 
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.graphics.Rect;
25 import android.util.AttributeSet;
26 import android.util.TypedValue;
27 import android.view.Gravity;
28 import android.view.MotionEvent;
29 import android.widget.TextView;
30 
31 import com.android.launcher3.AbstractFloatingView;
32 import com.android.launcher3.BaseDraggingActivity;
33 import com.android.launcher3.R;
34 import com.android.launcher3.anim.Interpolators;
35 import com.android.launcher3.compat.AccessibilityManagerCompat;
36 import com.android.launcher3.dragndrop.DragLayer;
37 
38 /**
39  * A toast-like UI at the bottom of the screen with a label, button action, and dismiss action.
40  */
41 public class Snackbar extends AbstractFloatingView {
42 
43     private static final long SHOW_DURATION_MS = 180;
44     private static final long HIDE_DURATION_MS = 180;
45     private static final int TIMEOUT_DURATION_MS = 4000;
46 
47     private final BaseDraggingActivity mActivity;
48     private Runnable mOnDismissed;
49 
Snackbar(Context context, AttributeSet attrs)50     public Snackbar(Context context, AttributeSet attrs) {
51         this(context, attrs, 0);
52     }
53 
Snackbar(Context context, AttributeSet attrs, int defStyleAttr)54     public Snackbar(Context context, AttributeSet attrs, int defStyleAttr) {
55         super(context, attrs, defStyleAttr);
56         mActivity = BaseDraggingActivity.fromContext(context);
57         inflate(context, R.layout.snackbar, this);
58     }
59 
show(BaseDraggingActivity activity, int labelStringResId, int actionStringResId, Runnable onDismissed, Runnable onActionClicked)60     public static void show(BaseDraggingActivity activity, int labelStringResId,
61             int actionStringResId, Runnable onDismissed, Runnable onActionClicked) {
62         closeOpenViews(activity, true, TYPE_SNACKBAR);
63         Snackbar snackbar = new Snackbar(activity, null);
64         // Set some properties here since inflated xml only contains the children.
65         snackbar.setOrientation(HORIZONTAL);
66         snackbar.setGravity(Gravity.CENTER_VERTICAL);
67         Resources res = activity.getResources();
68         snackbar.setElevation(res.getDimension(R.dimen.snackbar_elevation));
69         int padding = res.getDimensionPixelSize(R.dimen.snackbar_padding);
70         snackbar.setPadding(padding, padding, padding, padding);
71         snackbar.setBackgroundResource(R.drawable.round_rect_primary);
72 
73         snackbar.mIsOpen = true;
74         BaseDragLayer dragLayer = activity.getDragLayer();
75         dragLayer.addView(snackbar);
76 
77         DragLayer.LayoutParams params = (DragLayer.LayoutParams) snackbar.getLayoutParams();
78         params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
79         params.height = res.getDimensionPixelSize(R.dimen.snackbar_height);
80         int maxMarginLeftRight = res.getDimensionPixelSize(R.dimen.snackbar_max_margin_left_right);
81         int minMarginLeftRight = res.getDimensionPixelSize(R.dimen.snackbar_min_margin_left_right);
82         int marginBottom = res.getDimensionPixelSize(R.dimen.snackbar_margin_bottom);
83         Rect insets = activity.getDeviceProfile().getInsets();
84         int maxWidth = dragLayer.getWidth() - minMarginLeftRight * 2 - insets.left - insets.right;
85         int minWidth = dragLayer.getWidth() - maxMarginLeftRight * 2 - insets.left - insets.right;
86         params.width = minWidth;
87         params.setMargins(0, 0, 0, marginBottom + insets.bottom);
88 
89         TextView labelView = snackbar.findViewById(R.id.label);
90         TextView actionView = snackbar.findViewById(R.id.action);
91         String labelText = res.getString(labelStringResId);
92         String actionText = res.getString(actionStringResId);
93         int totalContentWidth = (int) (labelView.getPaint().measureText(labelText)
94                 + actionView.getPaint().measureText(actionText))
95                 + labelView.getPaddingRight() + labelView.getPaddingLeft()
96                 + actionView.getPaddingRight() + actionView.getPaddingLeft()
97                 + padding * 2;
98         if (totalContentWidth > params.width) {
99             // The text doesn't fit in our standard width so update width to accommodate.
100             if (totalContentWidth <= maxWidth) {
101                 params.width = totalContentWidth;
102             } else {
103                 // One line will be cut off, fallback to 2 lines and smaller font. (This should only
104                 // happen in some languages if system display and font size are set to largest.)
105                 int textHeight = res.getDimensionPixelSize(R.dimen.snackbar_content_height);
106                 float textSizePx = res.getDimension(R.dimen.snackbar_min_text_size);
107                 labelView.setLines(2);
108                 labelView.getLayoutParams().height = textHeight * 2;
109                 actionView.getLayoutParams().height = textHeight * 2;
110                 labelView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx);
111                 actionView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx);
112                 params.height += textHeight;
113                 params.width = maxWidth;
114             }
115         }
116         labelView.setText(labelText);
117         actionView.setText(actionText);
118         actionView.setOnClickListener(v -> {
119             if (onActionClicked != null) {
120                 onActionClicked.run();
121             }
122             snackbar.mOnDismissed = null;
123             snackbar.close(true);
124         });
125         snackbar.mOnDismissed = onDismissed;
126 
127         snackbar.setAlpha(0);
128         snackbar.setScaleX(0.8f);
129         snackbar.setScaleY(0.8f);
130         snackbar.animate()
131                 .alpha(1f)
132                 .withLayer()
133                 .scaleX(1)
134                 .scaleY(1)
135                 .setDuration(SHOW_DURATION_MS)
136                 .setInterpolator(Interpolators.ACCEL_DEACCEL)
137                 .start();
138         int timeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(activity,
139                 TIMEOUT_DURATION_MS, FLAG_CONTENT_TEXT | FLAG_CONTENT_CONTROLS);
140         snackbar.postDelayed(() -> snackbar.close(true), timeout);
141     }
142 
143     @Override
handleClose(boolean animate)144     protected void handleClose(boolean animate) {
145         if (mIsOpen) {
146             if (animate) {
147                 animate().alpha(0f)
148                         .withLayer()
149                         .setStartDelay(0)
150                         .setDuration(HIDE_DURATION_MS)
151                         .setInterpolator(Interpolators.ACCEL)
152                         .withEndAction(this::onClosed)
153                         .start();
154             } else {
155                 animate().cancel();
156                 onClosed();
157             }
158             mIsOpen = false;
159         }
160     }
161 
onClosed()162     private void onClosed() {
163         mActivity.getDragLayer().removeView(this);
164         if (mOnDismissed != null) {
165             mOnDismissed.run();
166         }
167     }
168 
169     @Override
logActionCommand(int command)170     public void logActionCommand(int command) {
171         // TODO
172     }
173 
174     @Override
isOfType(int type)175     protected boolean isOfType(int type) {
176         return (type & TYPE_SNACKBAR) != 0;
177     }
178 
179     @Override
onControllerInterceptTouchEvent(MotionEvent ev)180     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
181         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
182             BaseDragLayer dl = mActivity.getDragLayer();
183             if (!dl.isEventOverView(this, ev)) {
184                 close(true);
185             }
186         }
187         return false;
188     }
189 }
190