blob: 4c79ee32f8e83c605480b0470a8cb3dc65989da9 [file] [log] [blame]
Petr Cermaked7429c2017-12-18 19:38:04 +00001package com.android.systemui.statusbar.policy;
2
3import android.app.PendingIntent;
4import android.app.RemoteInput;
5import android.content.Context;
6import android.content.Intent;
Petr Cermak102431d2018-01-29 10:36:07 +00007import android.content.res.TypedArray;
8import android.graphics.Canvas;
9import android.graphics.drawable.GradientDrawable;
10import android.graphics.drawable.RippleDrawable;
Petr Cermaked7429c2017-12-18 19:38:04 +000011import android.os.Bundle;
Petr Cermak102431d2018-01-29 10:36:07 +000012import android.text.Layout;
13import android.text.TextPaint;
14import android.text.method.TransformationMethod;
Petr Cermaked7429c2017-12-18 19:38:04 +000015import android.util.AttributeSet;
16import android.util.Log;
17import android.view.LayoutInflater;
Petr Cermak102431d2018-01-29 10:36:07 +000018import android.view.View;
Petr Cermaked7429c2017-12-18 19:38:04 +000019import android.view.ViewGroup;
20import android.widget.Button;
Petr Cermaked7429c2017-12-18 19:38:04 +000021
Petr Cermak102431d2018-01-29 10:36:07 +000022import com.android.internal.annotations.VisibleForTesting;
Milo Sredkovb0f55e92018-04-04 16:13:28 +010023import com.android.keyguard.KeyguardHostView.OnDismissAction;
Petr Cermak10011fa2018-02-05 19:00:54 +000024import com.android.systemui.Dependency;
Petr Cermaked7429c2017-12-18 19:38:04 +000025import com.android.systemui.R;
Kenny Guy23991102018-04-05 21:18:38 +010026import com.android.systemui.statusbar.NotificationData;
27import com.android.systemui.statusbar.SmartReplyLogger;
Milo Sredkovb0f55e92018-04-04 16:13:28 +010028import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
Petr Cermaked7429c2017-12-18 19:38:04 +000029
Petr Cermak102431d2018-01-29 10:36:07 +000030import java.text.BreakIterator;
31import java.util.Comparator;
32import java.util.PriorityQueue;
33
Petr Cermaked7429c2017-12-18 19:38:04 +000034/** View which displays smart reply buttons in notifications. */
Petr Cermak102431d2018-01-29 10:36:07 +000035public class SmartReplyView extends ViewGroup {
Petr Cermaked7429c2017-12-18 19:38:04 +000036
37 private static final String TAG = "SmartReplyView";
38
Petr Cermak102431d2018-01-29 10:36:07 +000039 private static final int MEASURE_SPEC_ANY_WIDTH =
40 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
41
42 private static final Comparator<View> DECREASING_MEASURED_WIDTH_WITHOUT_PADDING_COMPARATOR =
43 (v1, v2) -> ((v2.getMeasuredWidth() - v2.getPaddingLeft() - v2.getPaddingRight())
44 - (v1.getMeasuredWidth() - v1.getPaddingLeft() - v1.getPaddingRight()));
45
46 private static final int SQUEEZE_FAILED = -1;
47
Petr Cermak10011fa2018-02-05 19:00:54 +000048 private final SmartReplyConstants mConstants;
Milo Sredkovb0f55e92018-04-04 16:13:28 +010049 private final KeyguardDismissUtil mKeyguardDismissUtil;
Petr Cermak10011fa2018-02-05 19:00:54 +000050
Petr Cermak102431d2018-01-29 10:36:07 +000051 /** Spacing to be applied between views. */
52 private final int mSpacing;
53
54 /** Horizontal padding of smart reply buttons if all of them use only one line of text. */
55 private final int mSingleLineButtonPaddingHorizontal;
56
57 /** Horizontal padding of smart reply buttons if at least one of them uses two lines of text. */
58 private final int mDoubleLineButtonPaddingHorizontal;
59
60 /** Increase in width of a smart reply button as a result of using two lines instead of one. */
61 private final int mSingleToDoubleLineButtonWidthIncrease;
62
63 private final BreakIterator mBreakIterator;
64
65 private PriorityQueue<Button> mCandidateButtonQueueForSqueezing;
66
Petr Cermaked7429c2017-12-18 19:38:04 +000067 public SmartReplyView(Context context, AttributeSet attrs) {
68 super(context, attrs);
Petr Cermak10011fa2018-02-05 19:00:54 +000069 mConstants = Dependency.get(SmartReplyConstants.class);
Milo Sredkovb0f55e92018-04-04 16:13:28 +010070 mKeyguardDismissUtil = Dependency.get(KeyguardDismissUtil.class);
Petr Cermak102431d2018-01-29 10:36:07 +000071
72 int spacing = 0;
73 int singleLineButtonPaddingHorizontal = 0;
74 int doubleLineButtonPaddingHorizontal = 0;
75
76 final TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.SmartReplyView,
77 0, 0);
78 final int length = arr.getIndexCount();
79 for (int i = 0; i < length; i++) {
80 int attr = arr.getIndex(i);
81 switch (attr) {
82 case R.styleable.SmartReplyView_spacing:
83 spacing = arr.getDimensionPixelSize(i, 0);
84 break;
85 case R.styleable.SmartReplyView_singleLineButtonPaddingHorizontal:
86 singleLineButtonPaddingHorizontal = arr.getDimensionPixelSize(i, 0);
87 break;
88 case R.styleable.SmartReplyView_doubleLineButtonPaddingHorizontal:
89 doubleLineButtonPaddingHorizontal = arr.getDimensionPixelSize(i, 0);
90 break;
91 }
92 }
93 arr.recycle();
94
95 mSpacing = spacing;
96 mSingleLineButtonPaddingHorizontal = singleLineButtonPaddingHorizontal;
97 mDoubleLineButtonPaddingHorizontal = doubleLineButtonPaddingHorizontal;
98 mSingleToDoubleLineButtonWidthIncrease =
99 2 * (doubleLineButtonPaddingHorizontal - singleLineButtonPaddingHorizontal);
100
101 mBreakIterator = BreakIterator.getLineInstance();
102 reallocateCandidateButtonQueueForSqueezing();
103 }
104
105 private void reallocateCandidateButtonQueueForSqueezing() {
106 // Instead of clearing the priority queue, we re-allocate so that it would fit all buttons
107 // exactly. This avoids (1) wasting memory because PriorityQueue never shrinks and
108 // (2) growing in onMeasure.
109 // The constructor throws an IllegalArgument exception if initial capacity is less than 1.
110 mCandidateButtonQueueForSqueezing = new PriorityQueue<>(
111 Math.max(getChildCount(), 1), DECREASING_MEASURED_WIDTH_WITHOUT_PADDING_COMPARATOR);
Petr Cermaked7429c2017-12-18 19:38:04 +0000112 }
113
Kenny Guy23991102018-04-05 21:18:38 +0100114 public void setRepliesFromRemoteInput(RemoteInput remoteInput, PendingIntent pendingIntent,
115 SmartReplyLogger smartReplyLogger, NotificationData.Entry entry) {
Petr Cermaked7429c2017-12-18 19:38:04 +0000116 removeAllViews();
117 if (remoteInput != null && pendingIntent != null) {
118 CharSequence[] choices = remoteInput.getChoices();
119 if (choices != null) {
Kenny Guy23991102018-04-05 21:18:38 +0100120 for (int i = 0; i < choices.length; ++i) {
Petr Cermaked7429c2017-12-18 19:38:04 +0000121 Button replyButton = inflateReplyButton(
Kenny Guy23991102018-04-05 21:18:38 +0100122 getContext(), this, i, choices[i], remoteInput, pendingIntent,
123 smartReplyLogger, entry);
Petr Cermaked7429c2017-12-18 19:38:04 +0000124 addView(replyButton);
125 }
126 }
127 }
Petr Cermak102431d2018-01-29 10:36:07 +0000128 reallocateCandidateButtonQueueForSqueezing();
Petr Cermaked7429c2017-12-18 19:38:04 +0000129 }
130
131 public static SmartReplyView inflate(Context context, ViewGroup root) {
132 return (SmartReplyView)
133 LayoutInflater.from(context).inflate(R.layout.smart_reply_view, root, false);
134 }
135
Petr Cermak102431d2018-01-29 10:36:07 +0000136 @VisibleForTesting
Kenny Guy23991102018-04-05 21:18:38 +0100137 Button inflateReplyButton(Context context, ViewGroup root, int replyIndex,
138 CharSequence choice, RemoteInput remoteInput, PendingIntent pendingIntent,
139 SmartReplyLogger smartReplyLogger, NotificationData.Entry entry) {
Petr Cermaked7429c2017-12-18 19:38:04 +0000140 Button b = (Button) LayoutInflater.from(context).inflate(
141 R.layout.smart_reply_button, root, false);
142 b.setText(choice);
Milo Sredkovb0f55e92018-04-04 16:13:28 +0100143
144 OnDismissAction action = () -> {
Petr Cermaked7429c2017-12-18 19:38:04 +0000145 Bundle results = new Bundle();
146 results.putString(remoteInput.getResultKey(), choice.toString());
147 Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
148 RemoteInput.addResultsToIntent(new RemoteInput[]{remoteInput}, intent, results);
Petr Cermak9a3380c2018-01-19 15:00:24 +0000149 RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
Petr Cermaked7429c2017-12-18 19:38:04 +0000150 try {
151 pendingIntent.send(context, 0, intent);
152 } catch (PendingIntent.CanceledException e) {
153 Log.w(TAG, "Unable to send smart reply", e);
154 }
Kenny Guy23991102018-04-05 21:18:38 +0100155 smartReplyLogger.smartReplySent(entry, replyIndex);
Milo Sredkovb0f55e92018-04-04 16:13:28 +0100156 return false; // do not defer
157 };
158
159 b.setOnClickListener(view -> {
160 mKeyguardDismissUtil.dismissKeyguardThenExecute(
161 action, null /* cancelAction */, false /* afterKeyguardGone */);
Petr Cermaked7429c2017-12-18 19:38:04 +0000162 });
163 return b;
164 }
Petr Cermak102431d2018-01-29 10:36:07 +0000165
166 @Override
167 public LayoutParams generateLayoutParams(AttributeSet attrs) {
168 return new LayoutParams(mContext, attrs);
169 }
170
171 @Override
172 protected LayoutParams generateDefaultLayoutParams() {
173 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
174 }
175
176 @Override
177 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams params) {
178 return new LayoutParams(params.width, params.height);
179 }
180
181 @Override
182 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
183 final int targetWidth = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED
184 ? Integer.MAX_VALUE : MeasureSpec.getSize(widthMeasureSpec);
185
186 // Mark all buttons as hidden and un-squeezed.
187 resetButtonsLayoutParams();
188
189 if (!mCandidateButtonQueueForSqueezing.isEmpty()) {
190 Log.wtf(TAG, "Single line button queue leaked between onMeasure calls");
191 mCandidateButtonQueueForSqueezing.clear();
192 }
193
194 int measuredWidth = mPaddingLeft + mPaddingRight;
195 int maxChildHeight = 0;
196 int displayedChildCount = 0;
197 int buttonPaddingHorizontal = mSingleLineButtonPaddingHorizontal;
198
199 final int childCount = getChildCount();
200 for (int i = 0; i < childCount; i++) {
201 final View child = getChildAt(i);
202 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
203 if (child.getVisibility() != View.VISIBLE || !(child instanceof Button)) {
204 continue;
205 }
206
207 child.setPadding(buttonPaddingHorizontal, child.getPaddingTop(),
208 buttonPaddingHorizontal, child.getPaddingBottom());
209 child.measure(MEASURE_SPEC_ANY_WIDTH, heightMeasureSpec);
210
211 final int lineCount = ((Button) child).getLineCount();
212 if (lineCount < 1 || lineCount > 2) {
213 // If smart reply has no text, or more than two lines, then don't show it.
214 continue;
215 }
216
217 if (lineCount == 1) {
218 mCandidateButtonQueueForSqueezing.add((Button) child);
219 }
220
221 // Remember the current measurements in case the current button doesn't fit in.
222 final int originalMaxChildHeight = maxChildHeight;
223 final int originalMeasuredWidth = measuredWidth;
224 final int originalButtonPaddingHorizontal = buttonPaddingHorizontal;
225
226 final int spacing = displayedChildCount == 0 ? 0 : mSpacing;
227 final int childWidth = child.getMeasuredWidth();
228 final int childHeight = child.getMeasuredHeight();
229 measuredWidth += spacing + childWidth;
230 maxChildHeight = Math.max(maxChildHeight, childHeight);
231
232 // Do we need to increase the number of lines in smart reply buttons to two?
233 final boolean increaseToTwoLines =
234 buttonPaddingHorizontal == mSingleLineButtonPaddingHorizontal
235 && (lineCount == 2 || measuredWidth > targetWidth);
236 if (increaseToTwoLines) {
237 measuredWidth += (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease;
238 buttonPaddingHorizontal = mDoubleLineButtonPaddingHorizontal;
239 }
240
241 // If the last button doesn't fit into the remaining width, try squeezing preceding
242 // smart reply buttons.
243 if (measuredWidth > targetWidth) {
244 // Keep squeezing preceding and current smart reply buttons until they all fit.
245 while (measuredWidth > targetWidth
246 && !mCandidateButtonQueueForSqueezing.isEmpty()) {
247 final Button candidate = mCandidateButtonQueueForSqueezing.poll();
248 final int squeezeReduction = squeezeButton(candidate, heightMeasureSpec);
249 if (squeezeReduction != SQUEEZE_FAILED) {
250 maxChildHeight = Math.max(maxChildHeight, candidate.getMeasuredHeight());
251 measuredWidth -= squeezeReduction;
252 }
253 }
254
255 // If the current button still doesn't fit after squeezing all buttons, undo the
256 // last squeezing round.
257 if (measuredWidth > targetWidth) {
258 measuredWidth = originalMeasuredWidth;
259 maxChildHeight = originalMaxChildHeight;
260 buttonPaddingHorizontal = originalButtonPaddingHorizontal;
261
262 // Mark all buttons from the last squeezing round as "failed to squeeze", so
263 // that they're re-measured without squeezing later.
264 markButtonsWithPendingSqueezeStatusAs(LayoutParams.SQUEEZE_STATUS_FAILED, i);
265
266 // The current button doesn't fit, so there's no point in measuring further
267 // buttons.
268 break;
269 }
270
271 // The current button fits, so mark all squeezed buttons as "successfully squeezed"
272 // to prevent them from being un-squeezed in a subsequent squeezing round.
273 markButtonsWithPendingSqueezeStatusAs(LayoutParams.SQUEEZE_STATUS_SUCCESSFUL, i);
274 }
275
276 lp.show = true;
277 displayedChildCount++;
278 }
279
280 // We're done squeezing buttons, so we can clear the priority queue.
281 mCandidateButtonQueueForSqueezing.clear();
282
283 // Finally, we need to update corner radius and re-measure some buttons.
284 updateCornerRadiusAndRemeasureButtonsIfNecessary(buttonPaddingHorizontal, maxChildHeight);
285
286 setMeasuredDimension(
287 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), widthMeasureSpec),
288 resolveSize(Math.max(getSuggestedMinimumHeight(),
289 mPaddingTop + maxChildHeight + mPaddingBottom), heightMeasureSpec));
290 }
291
292 private void resetButtonsLayoutParams() {
293 final int childCount = getChildCount();
294 for (int i = 0; i < childCount; i++) {
295 final View child = getChildAt(i);
296 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
297 lp.show = false;
298 lp.squeezeStatus = LayoutParams.SQUEEZE_STATUS_NONE;
299 }
300 }
301
302 private int squeezeButton(Button button, int heightMeasureSpec) {
303 final int estimatedOptimalTextWidth = estimateOptimalSqueezedButtonTextWidth(button);
304 if (estimatedOptimalTextWidth == SQUEEZE_FAILED) {
305 return SQUEEZE_FAILED;
306 }
307 return squeezeButtonToTextWidth(button, heightMeasureSpec, estimatedOptimalTextWidth);
308 }
309
310 private int estimateOptimalSqueezedButtonTextWidth(Button button) {
311 // Find a line-break point in the middle of the smart reply button text.
312 final String rawText = button.getText().toString();
313
314 // The button sometimes has a transformation affecting text layout (e.g. all caps).
315 final TransformationMethod transformation = button.getTransformationMethod();
316 final String text = transformation == null ?
317 rawText : transformation.getTransformation(rawText, button).toString();
318 final int length = text.length();
319 mBreakIterator.setText(text);
320
321 if (mBreakIterator.preceding(length / 2) == BreakIterator.DONE) {
322 if (mBreakIterator.next() == BreakIterator.DONE) {
323 // Can't find a single possible line break in either direction.
324 return SQUEEZE_FAILED;
325 }
326 }
327
328 final TextPaint paint = button.getPaint();
329 final int initialPosition = mBreakIterator.current();
330 final float initialLeftTextWidth = Layout.getDesiredWidth(text, 0, initialPosition, paint);
331 final float initialRightTextWidth =
332 Layout.getDesiredWidth(text, initialPosition, length, paint);
333 float optimalTextWidth = Math.max(initialLeftTextWidth, initialRightTextWidth);
334
335 if (initialLeftTextWidth != initialRightTextWidth) {
336 // See if there's a better line-break point (leading to a more narrow button) in
337 // either left or right direction.
338 final boolean moveLeft = initialLeftTextWidth > initialRightTextWidth;
339 final int maxSqueezeRemeasureAttempts = mConstants.getMaxSqueezeRemeasureAttempts();
340 for (int i = 0; i < maxSqueezeRemeasureAttempts; i++) {
341 final int newPosition =
342 moveLeft ? mBreakIterator.previous() : mBreakIterator.next();
343 if (newPosition == BreakIterator.DONE) {
344 break;
345 }
346
347 final float newLeftTextWidth = Layout.getDesiredWidth(text, 0, newPosition, paint);
348 final float newRightTextWidth =
349 Layout.getDesiredWidth(text, newPosition, length, paint);
350 final float newOptimalTextWidth = Math.max(newLeftTextWidth, newRightTextWidth);
351 if (newOptimalTextWidth < optimalTextWidth) {
352 optimalTextWidth = newOptimalTextWidth;
353 } else {
354 break;
355 }
356
357 boolean tooFar = moveLeft
358 ? newLeftTextWidth <= newRightTextWidth
359 : newLeftTextWidth >= newRightTextWidth;
360 if (tooFar) {
361 break;
362 }
363 }
364 }
365
366 return (int) Math.ceil(optimalTextWidth);
367 }
368
369 private int squeezeButtonToTextWidth(Button button, int heightMeasureSpec, int textWidth) {
370 int oldWidth = button.getMeasuredWidth();
371 if (button.getPaddingLeft() != mDoubleLineButtonPaddingHorizontal) {
372 // Correct for the fact that the button was laid out with single-line horizontal
373 // padding.
374 oldWidth += mSingleToDoubleLineButtonWidthIncrease;
375 }
376
377 // Re-measure the squeezed smart reply button.
378 button.setPadding(mDoubleLineButtonPaddingHorizontal, button.getPaddingTop(),
379 mDoubleLineButtonPaddingHorizontal, button.getPaddingBottom());
380 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
381 2 * mDoubleLineButtonPaddingHorizontal + textWidth, MeasureSpec.AT_MOST);
382 button.measure(widthMeasureSpec, heightMeasureSpec);
383
384 final int newWidth = button.getMeasuredWidth();
385
386 final LayoutParams lp = (LayoutParams) button.getLayoutParams();
387 if (button.getLineCount() > 2 || newWidth >= oldWidth) {
388 lp.squeezeStatus = LayoutParams.SQUEEZE_STATUS_FAILED;
389 return SQUEEZE_FAILED;
390 } else {
391 lp.squeezeStatus = LayoutParams.SQUEEZE_STATUS_PENDING;
392 return oldWidth - newWidth;
393 }
394 }
395
396 private void updateCornerRadiusAndRemeasureButtonsIfNecessary(
397 int buttonPaddingHorizontal, int maxChildHeight) {
398 final float cornerRadius = ((float) maxChildHeight) / 2;
399 final int maxChildHeightMeasure =
400 MeasureSpec.makeMeasureSpec(maxChildHeight, MeasureSpec.EXACTLY);
401
402 final int childCount = getChildCount();
403 for (int i = 0; i < childCount; i++) {
404 final View child = getChildAt(i);
405 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
406 if (!lp.show) {
407 continue;
408 }
409
410 // Update corner radius.
411 GradientDrawable backgroundDrawable =
412 (GradientDrawable) ((RippleDrawable) child.getBackground()).getDrawable(0);
413 backgroundDrawable.setCornerRadius(cornerRadius);
414
415 boolean requiresNewMeasure = false;
416 int newWidth = child.getMeasuredWidth();
417
418 // Re-measure reason 1: The button needs to be un-squeezed (either because it resulted
419 // in more than two lines or because it was unnecessary).
420 if (lp.squeezeStatus == LayoutParams.SQUEEZE_STATUS_FAILED) {
421 requiresNewMeasure = true;
422 newWidth = Integer.MAX_VALUE;
423 }
424
425 // Re-measure reason 2: The button's horizontal padding is incorrect (because it was
426 // measured with the wrong number of lines).
427 if (child.getPaddingLeft() != buttonPaddingHorizontal) {
428 requiresNewMeasure = true;
429 if (buttonPaddingHorizontal == mSingleLineButtonPaddingHorizontal) {
430 // Decrease padding (2->1 line).
431 newWidth -= mSingleToDoubleLineButtonWidthIncrease;
432 } else {
433 // Increase padding (1->2 lines).
434 newWidth += mSingleToDoubleLineButtonWidthIncrease;
435 }
436 child.setPadding(buttonPaddingHorizontal, child.getPaddingTop(),
437 buttonPaddingHorizontal, child.getPaddingBottom());
438 }
439
440 // Re-measure reason 3: The button's height is less than the max height of all buttons
441 // (all should have the same height).
442 if (child.getMeasuredHeight() != maxChildHeight) {
443 requiresNewMeasure = true;
444 }
445
446 if (requiresNewMeasure) {
447 child.measure(MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.AT_MOST),
448 maxChildHeightMeasure);
449 }
450 }
451 }
452
453 private void markButtonsWithPendingSqueezeStatusAs(int squeezeStatus, int maxChildIndex) {
454 for (int i = 0; i <= maxChildIndex; i++) {
455 final View child = getChildAt(i);
456 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
457 if (lp.squeezeStatus == LayoutParams.SQUEEZE_STATUS_PENDING) {
458 lp.squeezeStatus = squeezeStatus;
459 }
460 }
461 }
462
463 @Override
464 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
465 final boolean isRtl = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
466
467 final int width = right - left;
468 int position = isRtl ? width - mPaddingRight : mPaddingLeft;
469
470 final int childCount = getChildCount();
471 for (int i = 0; i < childCount; i++) {
472 final View child = getChildAt(i);
473 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
474 if (!lp.show) {
475 continue;
476 }
477
478 final int childWidth = child.getMeasuredWidth();
479 final int childHeight = child.getMeasuredHeight();
480 final int childLeft = isRtl ? position - childWidth : position;
481 child.layout(childLeft, 0, childLeft + childWidth, childHeight);
482
483 final int childWidthWithSpacing = childWidth + mSpacing;
484 if (isRtl) {
485 position -= childWidthWithSpacing;
486 } else {
487 position += childWidthWithSpacing;
488 }
489 }
490 }
491
492 @Override
493 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
494 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
495 return lp.show && super.drawChild(canvas, child, drawingTime);
496 }
497
498 @VisibleForTesting
499 static class LayoutParams extends ViewGroup.LayoutParams {
500
501 /** Button is not squeezed. */
502 private static final int SQUEEZE_STATUS_NONE = 0;
503
504 /**
505 * Button was successfully squeezed, but it might be un-squeezed later if the squeezing
506 * turns out to have been unnecessary (because there's still not enough space to add another
507 * button).
508 */
509 private static final int SQUEEZE_STATUS_PENDING = 1;
510
511 /** Button was successfully squeezed and it won't be un-squeezed. */
512 private static final int SQUEEZE_STATUS_SUCCESSFUL = 2;
513
514 /**
515 * Button wasn't successfully squeezed. The squeezing resulted in more than two lines of
516 * text or it didn't reduce the button's width at all. The button will have to be
517 * re-measured to use only one line of text.
518 */
519 private static final int SQUEEZE_STATUS_FAILED = 3;
520
521 private boolean show = false;
522 private int squeezeStatus = SQUEEZE_STATUS_NONE;
523
524 private LayoutParams(Context c, AttributeSet attrs) {
525 super(c, attrs);
526 }
527
528 private LayoutParams(int width, int height) {
529 super(width, height);
530 }
531
532 @VisibleForTesting
533 boolean isShown() {
534 return show;
535 }
536 }
Petr Cermaked7429c2017-12-18 19:38:04 +0000537}