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
17package com.android.tv.settings.dialog;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.content.Context;
23import android.content.Intent;
24import android.content.res.Resources;
25import android.database.DataSetObserver;
26import android.graphics.Bitmap;
27import android.graphics.drawable.Drawable;
28import android.media.AudioManager;
29import android.net.Uri;
30import android.os.Handler;
31import android.os.Looper;
32import android.support.v17.leanback.R;
33import android.support.v7.widget.RecyclerView;
34import android.support.v7.widget.RecyclerView.ViewHolder;
35import android.text.TextUtils;
36import android.util.Log;
37import android.util.TypedValue;
38import android.view.KeyEvent;
39import android.view.LayoutInflater;
40import android.view.View;
41import android.view.ViewGroup;
42import android.view.WindowManager;
43import android.view.animation.DecelerateInterpolator;
44import android.view.animation.Interpolator;
45import android.widget.AdapterView.OnItemSelectedListener;
46import android.widget.ImageView;
47import android.widget.TextView;
48
49import com.android.tv.settings.dialog.Layout;
50
51import java.security.InvalidParameterException;
52import java.util.ArrayList;
53
54/**
55 * Adapter class which creates actions.
56 */
57class 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         */
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         */
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
100    public SettingsLayoutAdapter(Listener listener, OnFocusListener onFocusListener) {
101        super();
102        mListener = listener;
103        mActionOnKeyPressAnimator = new ActionOnKeyPressAnimator(listener);
104        mActionOnFocusAnimator = new ActionOnFocusAnimator(onFocusListener);
105    }
106
107    public void setLayoutRows(ArrayList<Layout.LayoutRow> layoutRows) {
108        mLayoutRows = layoutRows;
109    }
110
111    public void setNoAnimateMode() {
112        mNoAnimateMode = true;
113    }
114
115    public void setFocusListenerEnabled(boolean enabled) {
116        mFocusListenerEnabled = enabled;
117    }
118
119    @Override
120    public int getItemViewType(int position) {
121        return mLayoutRows.get(position).getViewType();
122    }
123
124    @Override
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
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
155    public int getItemCount() {
156        return mLayoutRows.size();
157    }
158
159    public void setListener(Listener listener) {
160        mListener = listener;
161        mActionOnKeyPressAnimator.setListener(listener);
162    }
163
164    public void setOnFocusListener(OnFocusListener onFocusListener) {
165        mActionOnFocusAnimator.setOnFocusListener(onFocusListener);
166    }
167
168    public void registerDataSetObserver(DataSetObserver dataSetObserver) {
169    }
170
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
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
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
208        public Layout.LayoutRow getLayoutRow() {
209            return mLayoutRow;
210        }
211
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".
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
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
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
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         */
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
359        ActionOnFocusAnimator(OnFocusListener onFocusListener) {
360            mOnFocusListener = onFocusListener;
361        }
362
363        public void setOnFocusListener(OnFocusListener onFocusListener) {
364            mOnFocusListener = onFocusListener;
365        }
366
367        public void unFocus(View v) {
368            changeFocus((v != null) ? v : mSelectedView, false, false);
369        }
370
371        @Override
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
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
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
493        public ActionOnKeyPressAnimator(Listener listener) {
494            mListener = listener;
495        }
496
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
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
570        private void playSound(Context context, int soundEffect) {
571            AudioManager manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
572            manager.playSoundEffect(soundEffect);
573        }
574
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
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
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