1 /*
2  * Copyright (C) 2014 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.tv.settings.dialog;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.res.Resources;
25 import android.database.DataSetObserver;
26 import android.graphics.Bitmap;
27 import android.graphics.drawable.Drawable;
28 import android.media.AudioManager;
29 import android.net.Uri;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.support.v17.leanback.R;
33 import android.support.v7.widget.RecyclerView;
34 import android.support.v7.widget.RecyclerView.ViewHolder;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.util.TypedValue;
38 import android.view.KeyEvent;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.view.WindowManager;
43 import android.view.animation.DecelerateInterpolator;
44 import android.view.animation.Interpolator;
45 import android.widget.AdapterView.OnItemSelectedListener;
46 import android.widget.ImageView;
47 import android.widget.TextView;
48 
49 import com.android.tv.settings.dialog.Layout;
50 
51 import java.security.InvalidParameterException;
52 import java.util.ArrayList;
53 
54 /**
55  * Adapter class which creates actions.
56  */
57 class SettingsLayoutAdapter extends RecyclerView.Adapter {
58     private static final String TAG = "SettingsLayoutAdapter";
59     private static final boolean DEBUG = false;
60     private static final DecelerateInterpolator ALPHA_DECEL = new DecelerateInterpolator(2F);
61     private final Handler mRefreshViewHandler = new Handler(Looper.getMainLooper());
62 
63     /**
64      * Object listening for adapter events.
65      */
66     public interface Listener {
67 
68         /**
69          * Called when the user clicks on an action.
70          */
onRowClicked(Layout.LayoutRow item)71         public void onRowClicked(Layout.LayoutRow item);
72     }
73 
74     public interface OnFocusListener {
75 
76         /**
77          * Called when the user focuses on an action.
78          */
onActionFocused(Layout.LayoutRow item)79         public void onActionFocused(Layout.LayoutRow item);
80     }
81 
82     private final ActionOnKeyPressAnimator mActionOnKeyPressAnimator;
83     private final ActionOnFocusAnimator mActionOnFocusAnimator;
84     private LayoutInflater mInflater;
85     private ArrayList<Layout.LayoutRow> mLayoutRows;
86     private Listener mListener;
87     private boolean mNoAnimateMode = false;
88     private boolean mFocusListenerEnabled = true;
89 
90     private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
91         @Override
92         public void onClick(View v) {
93             if (v != null && v.getWindowToken() != null && mListener != null) {
94                 mListener.onRowClicked(((LayoutRowViewHolder) v.getTag(R.id.action_title)).
95                         getLayoutRow());
96             }
97         }
98     };
99 
SettingsLayoutAdapter(Listener listener, OnFocusListener onFocusListener)100     public SettingsLayoutAdapter(Listener listener, OnFocusListener onFocusListener) {
101         super();
102         mListener = listener;
103         mActionOnKeyPressAnimator = new ActionOnKeyPressAnimator(listener);
104         mActionOnFocusAnimator = new ActionOnFocusAnimator(onFocusListener);
105     }
106 
setLayoutRows(ArrayList<Layout.LayoutRow> layoutRows)107     public void setLayoutRows(ArrayList<Layout.LayoutRow> layoutRows) {
108         mLayoutRows = layoutRows;
109     }
110 
setNoAnimateMode()111     public void setNoAnimateMode() {
112         mNoAnimateMode = true;
113     }
114 
setFocusListenerEnabled(boolean enabled)115     public void setFocusListenerEnabled(boolean enabled) {
116         mFocusListenerEnabled = enabled;
117     }
118 
119     @Override
getItemViewType(int position)120     public int getItemViewType(int position) {
121         return mLayoutRows.get(position).getViewType();
122     }
123 
124     @Override
onCreateViewHolder(ViewGroup parent, int viewType)125     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
126         if (mInflater == null) {
127             mInflater = (LayoutInflater) parent.getContext().getSystemService(
128                     Context.LAYOUT_INFLATER_SERVICE);
129         }
130         View v = null;
131         switch (viewType) {
132             case Layout.LayoutRow.VIEW_TYPE_ACTION:
133                 v = mInflater.inflate(R.layout.lb_dialog_action_list_item, parent, false);
134                 break;
135             case Layout.LayoutRow.VIEW_TYPE_STATIC:
136                 v = mInflater.inflate(R.layout.lb_dialog_static_list_item, parent, false);
137                 break;
138         }
139         v.setTag(R.layout.lb_dialog_action_list_item, parent);
140         LayoutRowViewHolder viewHolder = new LayoutRowViewHolder(v, mActionOnKeyPressAnimator,
141                 mActionOnFocusAnimator, mOnClickListener);
142         viewHolder.init(viewType);
143         return viewHolder;
144     }
145 
146     @Override
onBindViewHolder(RecyclerView.ViewHolder baseHolder, int position)147     public void onBindViewHolder(RecyclerView.ViewHolder baseHolder, int position) {
148         LayoutRowViewHolder holder = (LayoutRowViewHolder) baseHolder;
149         if (position < mLayoutRows.size()) {
150             holder.bind(mLayoutRows.get(position));
151         }
152     }
153 
154     @Override
getItemCount()155     public int getItemCount() {
156         return mLayoutRows.size();
157     }
158 
setListener(Listener listener)159     public void setListener(Listener listener) {
160         mListener = listener;
161         mActionOnKeyPressAnimator.setListener(listener);
162     }
163 
setOnFocusListener(OnFocusListener onFocusListener)164     public void setOnFocusListener(OnFocusListener onFocusListener) {
165         mActionOnFocusAnimator.setOnFocusListener(onFocusListener);
166     }
167 
registerDataSetObserver(DataSetObserver dataSetObserver)168     public void registerDataSetObserver(DataSetObserver dataSetObserver) {
169     }
170 
setOnItemSelectedListener(OnItemSelectedListener listener)171     public void setOnItemSelectedListener(OnItemSelectedListener listener) {
172     }
173 
174     private class LayoutRowViewHolder extends ViewHolder implements
175             Layout.ContentNodeRefreshListener {
176 
177         private class RefreshDescription implements Runnable {
178             public String mDescriptionText;
179 
180             @Override
run()181             public void run() {
182                 mDescription.setText(mDescriptionText);
183             }
184         }
185 
186         private final ActionOnKeyPressAnimator mActionOnKeyPressAnimator;
187         private final ActionOnFocusAnimator mActionOnFocusAnimator;
188         private final View.OnClickListener mViewOnClickListener;
189         private Layout.LayoutRow mLayoutRow;
190         private TextView mDescription = null;
191         private TextView mTitle;
192         private ImageView mCheckmarkView;
193         private ImageView mIndicatorView;
194         private View mContent;
195         private ImageView mChevronView;
196         private int mViewType;
197         private RefreshDescription mRefreshDescription;
198 
LayoutRowViewHolder(View v, ActionOnKeyPressAnimator actionOnKeyPressAnimator, ActionOnFocusAnimator actionOnFocusAnimator, View.OnClickListener viewOnClickListener)199         public LayoutRowViewHolder(View v, ActionOnKeyPressAnimator actionOnKeyPressAnimator,
200                 ActionOnFocusAnimator actionOnFocusAnimator,
201                 View.OnClickListener viewOnClickListener) {
202             super(v);
203             mActionOnKeyPressAnimator = actionOnKeyPressAnimator;
204             mActionOnFocusAnimator = actionOnFocusAnimator;
205             mViewOnClickListener = viewOnClickListener;
206         }
207 
getLayoutRow()208         public Layout.LayoutRow getLayoutRow() {
209             return mLayoutRow;
210         }
211 
init(int viewType)212         public void init(int viewType) {
213             mViewType = viewType;
214             mTitle = (TextView) itemView.findViewById(R.id.action_title);
215             if (mViewType == Layout.LayoutRow.VIEW_TYPE_ACTION) {
216                 mDescription = (TextView) itemView.findViewById(R.id.action_description);
217             }
218             mCheckmarkView = (ImageView) itemView.findViewById(R.id.action_checkmark);
219             mIndicatorView = (ImageView) itemView.findViewById(R.id.action_icon);
220             mContent = itemView.findViewById(R.id.action_content);
221             mChevronView = (ImageView) itemView.findViewById(R.id.action_next_chevron);
222             itemView.setTag(R.id.action_title, this);
223             itemView.setOnKeyListener(mActionOnKeyPressAnimator);
224             itemView.setOnClickListener(mViewOnClickListener);
225             itemView.setOnFocusChangeListener(mActionOnFocusAnimator);
226         }
227 
228         //TODO need to create separate xxxViewHolder classes to eliminate tests of "mViewType".
onRefreshView()229         public void onRefreshView() {
230             if (mViewType == Layout.LayoutRow.VIEW_TYPE_ACTION) {
231                 Layout.StringGetter description = mLayoutRow.getDescription();
232                 if (description != null) {
233                     String text = description.get();
234                     if (!TextUtils.equals(mRefreshDescription.mDescriptionText, text)) {
235                         mRefreshDescription.mDescriptionText = text;
236                         mRefreshViewHandler.removeCallbacks(mRefreshDescription);
237                         mRefreshViewHandler.post(mRefreshDescription);
238                     }
239                 }
240             }
241         }
242 
bind(Layout.LayoutRow layoutRow)243         public void bind(Layout.LayoutRow layoutRow) {
244             mLayoutRow = layoutRow;
245             if (mViewType != layoutRow.getViewType()) {
246                 throw new InvalidParameterException("view type does not match");
247             }
248 
249             if (mViewType == Layout.LayoutRow.VIEW_TYPE_ACTION) {
250                 Layout.StringGetter description = layoutRow.getDescription();
251                 if (description != null) {
252                     mRefreshDescription = new RefreshDescription();
253                     String text = description.get();
254                     mDescription.setText(text);
255                     mRefreshDescription.mDescriptionText = text;
256                     mDescription.setVisibility(View.VISIBLE);
257                     description.setListener(this);
258                 } else {
259                     mDescription.setVisibility(View.GONE);
260                 }
261             }
262 
263             mTitle.setText(layoutRow.getTitle());
264             mCheckmarkView.setVisibility(layoutRow.isChecked() ? View.VISIBLE : View.INVISIBLE);
265 
266             ViewGroup.LayoutParams contentLp = mContent.getLayoutParams();
267             if (setIndicator(mIndicatorView, layoutRow)) {
268                 contentLp.width = itemView.getContext().getResources()
269                         .getDimensionPixelSize(R.dimen.lb_action_text_width);
270             } else {
271                 contentLp.width = itemView.getContext().getResources()
272                         .getDimensionPixelSize(R.dimen.lb_action_text_width_no_icon);
273             }
274             mContent.setLayoutParams(contentLp);
275 
276             mChevronView.setVisibility(layoutRow.hasNext() ? View.VISIBLE : View.INVISIBLE);
277 
278             final Resources res = itemView.getContext().getResources();
279             if (layoutRow.hasMultilineDescription()) {
280                 mTitle.setMaxLines(res.getInteger(R.integer.lb_dialog_action_title_max_lines));
281                 if (mViewType == Layout.LayoutRow.VIEW_TYPE_ACTION) {
282                     mDescription.setMaxHeight(
283                             getDescriptionMaxHeight(itemView.getContext(), mTitle));
284                 }
285             } else {
286                 mTitle.setMaxLines(res.getInteger(R.integer.lb_dialog_action_title_min_lines));
287                 if (mViewType == Layout.LayoutRow.VIEW_TYPE_ACTION) {
288                     mDescription.setMaxLines(
289                             res.getInteger(R.integer.lb_dialog_action_description_min_lines));
290                 }
291             }
292 
293             mActionOnFocusAnimator.unFocus(itemView);
294         }
295 
setIndicator(final ImageView indicatorView, Layout.LayoutRow action)296         private boolean setIndicator(final ImageView indicatorView, Layout.LayoutRow action) {
297 
298             Context context = indicatorView.getContext();
299             Drawable indicator = action.getIcon();
300             if (indicator != null) {
301                 indicatorView.setImageDrawable(indicator);
302                 indicatorView.setVisibility(View.VISIBLE);
303             } else {
304                 Uri iconUri = action.getIconUri();
305                 if (iconUri != null) {
306                     indicatorView.setVisibility(View.INVISIBLE);
307                 } else {
308                     indicatorView.setVisibility(View.GONE);
309                     return false;
310                 }
311             }
312             return true;
313         }
314 
fadeIn(View v)315         private void fadeIn(View v) {
316             ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha", 0f, 1f);
317             alphaAnimator.setDuration(
318                     v.getContext().getResources().getInteger(
319                             android.R.integer.config_mediumAnimTime));
320             alphaAnimator.start();
321         }
322 
323         /**
324          * @return the max height in pixels the description can be such that the
325          *         action nicely takes up the entire screen.
326          */
getDescriptionMaxHeight(Context context, TextView title)327         private int getDescriptionMaxHeight(Context context, TextView title) {
328             final Resources res = context.getResources();
329             final float verticalPadding =
330                 res.getDimension(R.dimen.lb_dialog_list_item_vertical_padding);
331             final int titleMaxLines = res.getInteger(R.integer.lb_dialog_action_title_max_lines);
332             final int displayHeight = ((WindowManager) context.getSystemService(
333                     Context.WINDOW_SERVICE)).getDefaultDisplay().getHeight();
334 
335             // The 2 multiplier on the title height calculation is a
336             // conservative estimate for font padding which can not be
337             // calculated at this stage since the view hasn't been rendered yet.
338             return (int) (displayHeight -
339                     2 * verticalPadding - 2 * titleMaxLines * title.getLineHeight());
340         }
341 
342     }
343 
344     private class ActionOnFocusAnimator implements View.OnFocusChangeListener {
345 
346         private boolean mResourcesSet;
347         private float mUnselectedAlpha;
348         private float mSelectedTitleAlpha;
349         private float mDisabledTitleAlpha;
350         private float mSelectedDescriptionAlpha;
351         private float mDisabledDescriptionAlpha;
352         private float mUnselectedDescriptionAlpha;
353         private float mSelectedChevronAlpha;
354         private float mDisabledChevronAlpha;
355         private int mAnimationDuration;
356         private OnFocusListener mOnFocusListener;
357         private View mSelectedView;
358 
ActionOnFocusAnimator(OnFocusListener onFocusListener)359         ActionOnFocusAnimator(OnFocusListener onFocusListener) {
360             mOnFocusListener = onFocusListener;
361         }
362 
setOnFocusListener(OnFocusListener onFocusListener)363         public void setOnFocusListener(OnFocusListener onFocusListener) {
364             mOnFocusListener = onFocusListener;
365         }
366 
unFocus(View v)367         public void unFocus(View v) {
368             changeFocus((v != null) ? v : mSelectedView, false, false);
369         }
370 
371         @Override
onFocusChange(View v, boolean hasFocus)372         public void onFocusChange(View v, boolean hasFocus) {
373             if (hasFocus) {
374                 mSelectedView = v;
375                 if (mNoAnimateMode) {
376                     mNoAnimateMode = false;
377                     changeFocus(v, true /* hasFocus */, false /* shouldAnimate */);
378                 } else {
379                     changeFocus(v, true /* hasFocus */, true /* shouldAnimate */);
380                     if (mOnFocusListener != null && mFocusListenerEnabled) {
381                         // We still call onActionFocused so that listeners can clear state if they
382                         // want.
383                         mOnFocusListener.onActionFocused(
384                                 ((LayoutRowViewHolder) v.getTag(R.id.action_title)).getLayoutRow());
385                     }
386                 }
387             } else {
388                 if (mSelectedView == v) {
389                     mSelectedView = null;
390                 }
391                 changeFocus(v, false /* hasFocus */, true /* shouldAnimate */);
392             }
393         }
394 
changeFocus(View v, boolean hasFocus, boolean shouldAnimate)395         private void changeFocus(View v, boolean hasFocus, boolean shouldAnimate) {
396             if (v == null) {
397                 return;
398             }
399 
400             if (!mResourcesSet) {
401                 mResourcesSet = true;
402                 final Resources res = v.getContext().getResources();
403 
404                 mAnimationDuration = res.getInteger(R.integer.lb_dialog_animation_duration);
405 
406                 mUnselectedAlpha =
407                         getFloat(res, R.string.lb_dialog_list_item_unselected_text_alpha);
408 
409                 mSelectedTitleAlpha =
410                         getFloat(res, R.string.lb_dialog_list_item_selected_title_text_alpha);
411                 mDisabledTitleAlpha =
412                         getFloat(res, R.string.lb_dialog_list_item_disabled_title_text_alpha);
413 
414                 mSelectedDescriptionAlpha =
415                         getFloat(res, R.string.lb_dialog_list_item_selected_description_text_alpha);
416                 mUnselectedDescriptionAlpha = getFloat(res,
417                         R.string.lb_dialog_list_item_unselected_description_text_alpha);
418                 mDisabledDescriptionAlpha =
419                         getFloat(res, R.string.lb_dialog_list_item_disabled_description_text_alpha);
420 
421                 mSelectedChevronAlpha = getFloat(res,
422                         R.string.lb_dialog_list_item_selected_chevron_background_alpha);
423                 mDisabledChevronAlpha = getFloat(res,
424                         R.string.lb_dialog_list_item_disabled_chevron_background_alpha);
425             }
426 
427             Layout.LayoutRow layoutRow =
428                 ((LayoutRowViewHolder) v.getTag(R.id.action_title)).getLayoutRow();
429 
430             float titleAlpha;
431             if (layoutRow.isEnabled() && !layoutRow.infoOnly()) {
432                 titleAlpha = hasFocus ? mSelectedTitleAlpha : mUnselectedAlpha;
433             } else {
434                 titleAlpha = mDisabledTitleAlpha;
435             }
436             float descriptionAlpha;
437             if (!hasFocus || layoutRow.infoOnly()) {
438                 descriptionAlpha = mUnselectedDescriptionAlpha;
439             } else {
440                 descriptionAlpha = layoutRow.isEnabled() ? mSelectedDescriptionAlpha :
441                         mDisabledDescriptionAlpha;
442             }
443             float chevronAlpha;
444             if (layoutRow.hasNext() && !layoutRow.infoOnly()) {
445                 chevronAlpha =
446                         layoutRow.isEnabled() ? mSelectedChevronAlpha : mDisabledChevronAlpha;
447             } else {
448                 chevronAlpha = 0;
449             }
450 
451             TextView title = (TextView) v.findViewById(R.id.action_title);
452             setAlpha(title, shouldAnimate, titleAlpha);
453 
454             TextView description = (TextView) v.findViewById(R.id.action_description);
455             if (description != null) {
456                 setAlpha(description, shouldAnimate, descriptionAlpha);
457             }
458 
459             ImageView checkmark = (ImageView) v.findViewById(R.id.action_checkmark);
460             setAlpha(checkmark, shouldAnimate, titleAlpha);
461 
462             ImageView icon = (ImageView) v.findViewById(R.id.action_icon);
463             setAlpha(icon, shouldAnimate, titleAlpha);
464 
465             ImageView chevron = (ImageView) v.findViewById(R.id.action_next_chevron);
466             setAlpha(chevron, shouldAnimate, chevronAlpha);
467         }
468 
setAlpha(View view, boolean shouldAnimate, float alpha)469         private void setAlpha(View view, boolean shouldAnimate, float alpha) {
470             if (shouldAnimate) {
471                 view.animate().alpha(alpha)
472                         .setDuration(mAnimationDuration)
473                         .setInterpolator(ALPHA_DECEL)
474                         .start();
475             } else {
476                 view.setAlpha(alpha);
477             }
478         }
479     }
480 
481     private class ActionOnKeyPressAnimator implements View.OnKeyListener {
482 
483         private static final int SELECT_ANIM_DURATION = 100;
484         private static final int SELECT_ANIM_DELAY = 0;
485         private static final float SELECT_ANIM_SELECTED_ALPHA = 0.2f;
486         private static final float SELECT_ANIM_UNSELECTED_ALPHA = 1.0f;
487         private static final float CHECKMARK_ANIM_UNSELECTED_ALPHA = 0.0f;
488         private static final float CHECKMARK_ANIM_SELECTED_ALPHA = 1.0f;
489 
490         private boolean mKeyPressed = false;
491         private Listener mListener;
492 
ActionOnKeyPressAnimator(Listener listener)493         public ActionOnKeyPressAnimator(Listener listener) {
494             mListener = listener;
495         }
496 
setListener(Listener listener)497         public void setListener(Listener listener) {
498             mListener = listener;
499         }
500 
501         /**
502          * Now only handles KEYCODE_ENTER and KEYCODE_NUMPAD_ENTER key event.
503          */
504         @Override
onKey(View v, int keyCode, KeyEvent event)505         public boolean onKey(View v, int keyCode, KeyEvent event) {
506             if (v == null) {
507                 return false;
508             }
509             boolean handled = false;
510             Layout.LayoutRow layoutRow =
511                 ((LayoutRowViewHolder) v.getTag(R.id.action_title)).getLayoutRow();
512             switch (keyCode) {
513                 case KeyEvent.KEYCODE_DPAD_CENTER:
514                 case KeyEvent.KEYCODE_NUMPAD_ENTER:
515                 case KeyEvent.KEYCODE_BUTTON_X:
516                 case KeyEvent.KEYCODE_BUTTON_Y:
517                 case KeyEvent.KEYCODE_ENTER:
518 
519                     if (!layoutRow.isEnabled() || layoutRow.infoOnly()) {
520                         if (v.isSoundEffectsEnabled()
521                                 && event.getAction() == KeyEvent.ACTION_DOWN) {
522                             playSound(v.getContext(), AudioManager.FX_KEYPRESS_INVALID);
523                         }
524                         return true;
525                     }
526 
527                     switch (event.getAction()) {
528                         case KeyEvent.ACTION_DOWN:
529                             if (!mKeyPressed) {
530                                 mKeyPressed = true;
531 
532                                 if (v.isSoundEffectsEnabled()) {
533                                     playSound(v.getContext(), AudioManager.FX_KEY_CLICK);
534                                 }
535 
536                                 if (DEBUG) {
537                                     Log.d(TAG, "Enter Key down");
538                                 }
539 
540                                 prepareAndAnimateView(v, SELECT_ANIM_UNSELECTED_ALPHA,
541                                         SELECT_ANIM_SELECTED_ALPHA, SELECT_ANIM_DURATION,
542                                         SELECT_ANIM_DELAY, null, mKeyPressed);
543                                 handled = true;
544                             }
545                             break;
546                         case KeyEvent.ACTION_UP:
547                             if (mKeyPressed) {
548                                 mKeyPressed = false;
549 
550                                 if (DEBUG) {
551                                     Log.d(TAG, "Enter Key up");
552                                 }
553 
554                                 prepareAndAnimateView(v, SELECT_ANIM_SELECTED_ALPHA,
555                                         SELECT_ANIM_UNSELECTED_ALPHA, SELECT_ANIM_DURATION,
556                                         SELECT_ANIM_DELAY, null, mKeyPressed);
557                                 handled = true;
558                             }
559                             break;
560                         default:
561                             break;
562                     }
563                     break;
564                 default:
565                     break;
566             }
567             return handled;
568         }
569 
playSound(Context context, int soundEffect)570         private void playSound(Context context, int soundEffect) {
571             AudioManager manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
572             manager.playSoundEffect(soundEffect);
573         }
574 
prepareAndAnimateView(final View v, float initAlpha, float destAlpha, int duration, int delay, Interpolator interpolator, final boolean pressed)575         private void prepareAndAnimateView(final View v, float initAlpha, float destAlpha,
576                 int duration,
577                 int delay, Interpolator interpolator, final boolean pressed) {
578             if (v != null && v.getWindowToken() != null) {
579                 final Layout.LayoutRow layoutRow =
580                         ((LayoutRowViewHolder) v.getTag(R.id.action_title)).getLayoutRow();
581 
582                 if (!pressed) {
583                     fadeCheckmarks(v, layoutRow, duration, delay, interpolator);
584                 }
585 
586                 v.setAlpha(initAlpha);
587                 v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
588                 v.buildLayer();
589                 v.animate().alpha(destAlpha).setDuration(duration).setStartDelay(delay);
590                 if (interpolator != null) {
591                     v.animate().setInterpolator(interpolator);
592                 }
593                 v.animate().setListener(new AnimatorListenerAdapter() {
594                     @Override
595                     public void onAnimationEnd(Animator animation) {
596                         v.setLayerType(View.LAYER_TYPE_NONE, null);
597                         if (!pressed) {
598                             if (mListener != null) {
599                                 mListener.onRowClicked(layoutRow);
600                             }
601                         }
602                     }
603                 });
604                 v.animate().start();
605             }
606         }
607 
fadeCheckmarks(final View v, final Layout.LayoutRow action, int duration, int delay, Interpolator interpolator)608         private void fadeCheckmarks(final View v, final Layout.LayoutRow action, int duration,
609                 int delay, Interpolator interpolator) {
610             int actionCheckSetId = action.getCheckSetId();
611             if (actionCheckSetId != Layout.LayoutRow.NO_CHECK_SET) {
612                 ViewGroup parent = (ViewGroup) v.getTag(R.layout.lb_dialog_action_list_item);
613 
614                 // Find any actions that are checked and are in the same group as the selected
615                 // action. Fade their checkmarks out.
616                 for (int i = 0, size = mLayoutRows.size(); i < size; i++) {
617                     Layout.LayoutRow a = mLayoutRows.get(i);
618                     if (a != action && a.getCheckSetId() == actionCheckSetId && a.isChecked()) {
619                         a.setChecked(false);
620                         View viewToAnimateOut = parent.getChildAt(i);
621                         if (viewToAnimateOut != null) {
622                             final View checkView = viewToAnimateOut.findViewById(
623                                     R.id.action_checkmark);
624                             checkView.animate().alpha(CHECKMARK_ANIM_UNSELECTED_ALPHA)
625                                     .setDuration(duration).setStartDelay(delay);
626                             if (interpolator != null) {
627                                 checkView.animate().setInterpolator(interpolator);
628                             }
629                             checkView.animate().setListener(new AnimatorListenerAdapter() {
630                                     @Override
631                                 public void onAnimationEnd(Animator animation) {
632                                     checkView.setVisibility(View.INVISIBLE);
633                                 }
634                             });
635                         }
636                     }
637                 }
638 
639                 // If we we'ren't already checked, fade our checkmark in.
640                 if (!action.isChecked()) {
641                     action.setChecked(true);
642                     final View checkView = v.findViewById(R.id.action_checkmark);
643                     checkView.setVisibility(View.VISIBLE);
644                     checkView.setAlpha(CHECKMARK_ANIM_UNSELECTED_ALPHA);
645                     checkView.animate().alpha(CHECKMARK_ANIM_SELECTED_ALPHA).setDuration(duration)
646                             .setStartDelay(delay);
647                     if (interpolator != null) {
648                         checkView.animate().setInterpolator(interpolator);
649                     }
650                     checkView.animate().setListener(null);
651                 }
652             }
653         }
654     }
655 
getFloat(Resources res, int floatResId)656     private static float getFloat(Resources res, int floatResId) {
657         TypedValue tv = new TypedValue();
658         res.getValue(floatResId, tv, true);
659         return tv.getFloat();
660     }
661 }
662