Home | History | Annotate | Download | only in old
      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.old;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.content.Context;
     22 import android.content.res.Resources;
     23 import android.graphics.drawable.Drawable;
     24 import android.media.AudioManager;
     25 import android.text.TextUtils;
     26 import android.util.Log;
     27 import android.util.TypedValue;
     28 import android.view.KeyEvent;
     29 import android.view.LayoutInflater;
     30 import android.view.View;
     31 import android.view.ViewGroup;
     32 import android.view.WindowManager;
     33 import android.view.animation.DecelerateInterpolator;
     34 import android.view.animation.Interpolator;
     35 import android.widget.BaseAdapter;
     36 import android.widget.ImageView;
     37 import android.widget.TextView;
     38 
     39 import com.android.tv.settings.R;
     40 import com.android.tv.settings.widget.ScrollAdapter;
     41 import com.android.tv.settings.widget.ScrollAdapterBase;
     42 import com.android.tv.settings.widget.ScrollAdapterView;
     43 import com.android.tv.settings.widget.ScrollAdapterView.OnScrollListener;
     44 
     45 import java.util.ArrayList;
     46 import java.util.List;
     47 
     48 /**
     49  * Adapter class which creates actions.
     50  */
     51 public class ActionAdapter extends BaseAdapter implements ScrollAdapter,
     52         OnScrollListener, View.OnKeyListener, View.OnClickListener {
     53     private static final String TAG = "ActionAdapter";
     54 
     55     private static final boolean DEBUG = false;
     56 
     57     private static final int SELECT_ANIM_DURATION = 100;
     58     private static final int SELECT_ANIM_DELAY = 0;
     59     private static final float SELECT_ANIM_SELECTED_ALPHA = 0.2f;
     60     private static final float SELECT_ANIM_UNSELECTED_ALPHA = 1.0f;
     61     private static final float CHECKMARK_ANIM_UNSELECTED_ALPHA = 0.0f;
     62     private static final float CHECKMARK_ANIM_SELECTED_ALPHA = 1.0f;
     63 
     64     private static Integer sDescriptionMaxHeight = null;
     65 
     66     // TODO: this constant is only in KLP: update when KLP has a more standard SDK.
     67     private static final int FX_KEYPRESS_INVALID = 9; // AudioManager.FX_KEYPRESS_INVALID;
     68 
     69     /**
     70      * Object listening for adapter events.
     71      */
     72     public interface Listener {
     73 
     74         /**
     75          * Called when the user clicks on an action.
     76          */
     77         public void onActionClicked(Action action);
     78     }
     79 
     80     public interface OnFocusListener {
     81 
     82         /**
     83          * Called when the user focuses on an action.
     84          */
     85         public void onActionFocused(Action action);
     86     }
     87 
     88     /**
     89      * Object listening for adapter action select/unselect events.
     90      */
     91     public interface OnKeyListener {
     92 
     93         /**
     94          * Called when user finish selecting an action.
     95          */
     96         public void onActionSelect(Action action);
     97 
     98         /**
     99          * Called when user finish unselecting an action.
    100          */
    101         public void onActionUnselect(Action action);
    102     }
    103 
    104 
    105     private final Context mContext;
    106     private final float mUnselectedAlpha;
    107     private final float mSelectedTitleAlpha;
    108     private final float mDisabledTitleAlpha;
    109     private final float mSelectedDescriptionAlpha;
    110     private final float mDisabledDescriptionAlpha;
    111     private final float mUnselectedDescriptionAlpha;
    112     private final float mSelectedChevronAlpha;
    113     private final float mDisabledChevronAlpha;
    114     private final List<Action> mActions;
    115     private Listener mListener;
    116     private OnFocusListener mOnFocusListener;
    117     private OnKeyListener mOnKeyListener;
    118     private boolean mKeyPressed;
    119     private ScrollAdapterView mScrollAdapterView;
    120     private final int mAnimationDuration;
    121     private View mSelectedView = null;
    122 
    123     public ActionAdapter(Context context) {
    124         super();
    125         mContext = context;
    126         final Resources res = context.getResources();
    127 
    128         mAnimationDuration = res.getInteger(R.integer.dialog_animation_duration);
    129         mUnselectedAlpha = getFloat(R.dimen.list_item_unselected_text_alpha);
    130 
    131         mSelectedTitleAlpha = getFloat(R.dimen.list_item_selected_title_text_alpha);
    132         mDisabledTitleAlpha = getFloat(R.dimen.list_item_disabled_title_text_alpha);
    133 
    134         mSelectedDescriptionAlpha = getFloat(R.dimen.list_item_selected_description_text_alpha);
    135         mUnselectedDescriptionAlpha = getFloat(R.dimen.list_item_unselected_description_text_alpha);
    136         mDisabledDescriptionAlpha = getFloat(R.dimen.list_item_disabled_description_text_alpha);
    137 
    138         mSelectedChevronAlpha = getFloat(R.dimen.list_item_selected_chevron_background_alpha);
    139         mDisabledChevronAlpha = getFloat(R.dimen.list_item_disabled_chevron_background_alpha);
    140 
    141         mActions = new ArrayList<>();
    142         mKeyPressed = false;
    143     }
    144 
    145     @Override
    146     public void viewRemoved(View view) {
    147         // Do nothing.
    148     }
    149 
    150     @Override
    151     public View getScrapView(ViewGroup parent) {
    152         LayoutInflater inflater = LayoutInflater.from(mContext);
    153         View view = inflater.inflate(R.layout.settings_list_item, parent, false);
    154         return view;
    155     }
    156 
    157     @Override
    158     public int getCount() {
    159         return mActions.size();
    160     }
    161 
    162     @Override
    163     public Object getItem(int position) {
    164         return mActions.get(position);
    165     }
    166 
    167     @Override
    168     public long getItemId(int position) {
    169         return position;
    170     }
    171 
    172     @Override
    173     public boolean hasStableIds() {
    174         return true;
    175     }
    176 
    177     @Override
    178     public View getView(int position, View convertView, ViewGroup parent) {
    179         if (convertView == null) {
    180             convertView = getScrapView(parent);
    181         }
    182         Action action = mActions.get(position);
    183         TextView title = (TextView) convertView.findViewById(R.id.action_title);
    184         TextView description = (TextView) convertView.findViewById(R.id.action_description);
    185         description.setText(action.getDescription());
    186         description.setVisibility(
    187                 TextUtils.isEmpty(action.getDescription()) ? View.GONE : View.VISIBLE);
    188         title.setText(action.getTitle());
    189         ImageView checkmarkView = (ImageView) convertView.findViewById(R.id.action_checkmark);
    190         checkmarkView.setVisibility(action.isChecked() ? View.VISIBLE : View.INVISIBLE);
    191 
    192         ImageView indicatorView = (ImageView) convertView.findViewById(R.id.action_icon);
    193         setIndicator(indicatorView, action);
    194 
    195         ImageView chevronView = (ImageView) convertView.findViewById(R.id.action_next_chevron);
    196         chevronView.setVisibility(action.hasNext() ? View.VISIBLE : View.GONE);
    197 
    198         View chevronBackgroundView = convertView.findViewById(R.id.action_next_chevron_background);
    199         chevronBackgroundView.setVisibility(action.hasNext() ? View.VISIBLE : View.INVISIBLE);
    200 
    201         final Resources res = convertView.getContext().getResources();
    202         if (action.hasMultilineDescription()) {
    203             title.setMaxLines(res.getInteger(R.integer.action_title_max_lines));
    204             description.setMaxHeight(
    205                     getDescriptionMaxHeight(convertView.getContext(), title, description));
    206         } else {
    207             title.setMaxLines(res.getInteger(R.integer.action_title_min_lines));
    208             description.setMaxLines(
    209                     res.getInteger(R.integer.action_description_min_lines));
    210         }
    211 
    212         convertView.setTag(R.id.action_title, action);
    213         convertView.setOnKeyListener(this);
    214         convertView.setOnClickListener(this);
    215         changeFocus(convertView, false /* hasFocus */, false /* shouldAnimate */);
    216 
    217         return convertView;
    218     }
    219 
    220     @Override
    221     public ScrollAdapterBase getExpandAdapter() {
    222         return null;
    223     }
    224 
    225     public void setListener(Listener listener) {
    226         mListener = listener;
    227     }
    228 
    229     public void setOnFocusListener(OnFocusListener onFocusListener) {
    230         mOnFocusListener = onFocusListener;
    231     }
    232 
    233     public void setOnKeyListener(OnKeyListener onKeyListener) {
    234         mOnKeyListener = onKeyListener;
    235     }
    236 
    237     public void addAction(Action action) {
    238         mActions.add(action);
    239         notifyDataSetChanged();
    240     }
    241 
    242     /**
    243      * Used for serialization only.
    244      */
    245     public ArrayList<Action> getActions() {
    246         return new ArrayList<>(mActions);
    247     }
    248 
    249     public void setActions(ArrayList<Action> actions) {
    250         changeFocus(mSelectedView, false /* hasFocus */, false /* shouldAnimate */);
    251         mActions.clear();
    252         mActions.addAll(actions);
    253         notifyDataSetChanged();
    254     }
    255 
    256     // We want to highlight a view if we've stopped scrolling on it (mainPosition = 0).
    257     // If mainPosition is not 0, we don't want to do anything with view, but we do want to ensure
    258     // we dim the last highlighted view so that while a user is scrolling, nothing is highlighted.
    259     @Override
    260     public void onScrolled(View view, int position, float mainPosition, float secondPosition) {
    261         boolean hasFocus = (mainPosition == 0.0);
    262         if (hasFocus) {
    263             if (view != null) {
    264                 changeFocus(view, true /* hasFocus */, true /* shouldAniamte */);
    265                 mSelectedView = view;
    266             }
    267         } else if (mSelectedView != null) {
    268             changeFocus(mSelectedView, false /* hasFocus */, true /* shouldAniamte */);
    269             mSelectedView = null;
    270         }
    271     }
    272 
    273     private void changeFocus(View v, boolean hasFocus, boolean shouldAnimate) {
    274         if (v == null) {
    275             return;
    276         }
    277         Action action = (Action) v.getTag(R.id.action_title);
    278 
    279         float titleAlpha = action.isEnabled() && !action.infoOnly()
    280                 ? (hasFocus ? mSelectedTitleAlpha : mUnselectedAlpha) : mDisabledTitleAlpha;
    281         float descriptionAlpha = (!hasFocus || action.infoOnly()) ? mUnselectedDescriptionAlpha
    282                 : (action.isEnabled() ? mSelectedDescriptionAlpha : mDisabledDescriptionAlpha);
    283         float chevronAlpha = action.hasNext() && !action.infoOnly()
    284                 ? (action.isEnabled() ? mSelectedChevronAlpha : mDisabledChevronAlpha) : 0;
    285 
    286         TextView title = (TextView) v.findViewById(R.id.action_title);
    287         setAlpha(title, shouldAnimate, titleAlpha);
    288 
    289         TextView description = (TextView) v.findViewById(R.id.action_description);
    290         setAlpha(description, shouldAnimate, descriptionAlpha);
    291 
    292         ImageView checkmark = (ImageView) v.findViewById(R.id.action_checkmark);
    293         setAlpha(checkmark, shouldAnimate, titleAlpha);
    294 
    295         ImageView icon = (ImageView) v.findViewById(R.id.action_icon);
    296         setAlpha(icon, shouldAnimate, titleAlpha);
    297 
    298         View chevronBackground = v.findViewById(R.id.action_next_chevron_background);
    299         setAlpha(chevronBackground, shouldAnimate, chevronAlpha);
    300 
    301         if (mOnFocusListener != null && hasFocus) {
    302             // We still call onActionFocused so that listeners can clear state if they want.
    303             mOnFocusListener.onActionFocused((Action) v.getTag(R.id.action_title));
    304         }
    305     }
    306 
    307     private void setIndicator(final ImageView indicatorView, Action action) {
    308 
    309         Drawable indicator = action.getIndicator(mContext);
    310         if (indicator != null) {
    311             indicatorView.setImageDrawable(indicator);
    312             indicatorView.setVisibility(View.VISIBLE);
    313         } else {
    314             indicatorView.setVisibility(View.GONE);
    315         }
    316     }
    317 
    318     private void setAlpha(View view, boolean shouldAnimate, float alpha) {
    319         if (shouldAnimate) {
    320             view.animate().alpha(alpha)
    321                     .setDuration(mAnimationDuration)
    322                     .setInterpolator(new DecelerateInterpolator(2F))
    323                     .start();
    324         } else {
    325             view.setAlpha(alpha);
    326         }
    327     }
    328 
    329     void setScrollAdapterView(ScrollAdapterView scrollAdapterView) {
    330         mScrollAdapterView = scrollAdapterView;
    331     }
    332 
    333     @Override
    334     public void onClick(View v) {
    335         if (v != null && v.getWindowToken() != null && mListener != null) {
    336             final Action action = (Action) v.getTag(R.id.action_title);
    337             mListener.onActionClicked(action);
    338         }
    339     }
    340 
    341     /**
    342      * Now only handles KEYCODE_ENTER and KEYCODE_NUMPAD_ENTER key event.
    343      */
    344     @Override
    345     public boolean onKey(View v, int keyCode, KeyEvent event) {
    346         if (v == null) {
    347             return false;
    348         }
    349         boolean handled = false;
    350         Action action = (Action) v.getTag(R.id.action_title);
    351         switch (keyCode) {
    352             case KeyEvent.KEYCODE_DPAD_CENTER:
    353             case KeyEvent.KEYCODE_NUMPAD_ENTER:
    354             case KeyEvent.KEYCODE_BUTTON_X:
    355             case KeyEvent.KEYCODE_BUTTON_Y:
    356             case KeyEvent.KEYCODE_ENTER:
    357                 AudioManager manager = (AudioManager) v.getContext().getSystemService(
    358                         Context.AUDIO_SERVICE);
    359                 if (!action.isEnabled() || action.infoOnly()) {
    360                     if (v.isSoundEffectsEnabled() && event.getAction() == KeyEvent.ACTION_DOWN) {
    361                         manager.playSoundEffect(FX_KEYPRESS_INVALID);
    362                     }
    363                     return true;
    364                 }
    365 
    366                 switch (event.getAction()) {
    367                     case KeyEvent.ACTION_DOWN:
    368                         if (!mKeyPressed) {
    369                             mKeyPressed = true;
    370 
    371                             if (v.isSoundEffectsEnabled()) {
    372                                 manager.playSoundEffect(AudioManager.FX_KEY_CLICK);
    373                             }
    374 
    375                             if (DEBUG) {
    376                                 Log.d(TAG, "Enter Key down");
    377                             }
    378 
    379                             prepareAndAnimateView(v, SELECT_ANIM_UNSELECTED_ALPHA,
    380                                     SELECT_ANIM_SELECTED_ALPHA, SELECT_ANIM_DURATION,
    381                                     SELECT_ANIM_DELAY, null, mKeyPressed);
    382                             handled = true;
    383                         }
    384                         break;
    385                     case KeyEvent.ACTION_UP:
    386                         if (mKeyPressed) {
    387                             mKeyPressed = false;
    388 
    389                             if (DEBUG) {
    390                                 Log.d(TAG, "Enter Key up");
    391                             }
    392 
    393                             prepareAndAnimateView(v, SELECT_ANIM_SELECTED_ALPHA,
    394                                     SELECT_ANIM_UNSELECTED_ALPHA, SELECT_ANIM_DURATION,
    395                                     SELECT_ANIM_DELAY, null, mKeyPressed);
    396                             handled = true;
    397                         }
    398                         break;
    399                     default:
    400                         break;
    401                 }
    402                 break;
    403             default:
    404                 break;
    405         }
    406         return handled;
    407     }
    408 
    409     private void prepareAndAnimateView(final View v, float initAlpha, float destAlpha, int duration,
    410             int delay, Interpolator interpolator, final boolean pressed) {
    411         if (v != null && v.getWindowToken() != null) {
    412             final Action action = (Action) v.getTag(R.id.action_title);
    413 
    414             if (!pressed) {
    415                 fadeCheckmarks(v, action, duration, delay, interpolator);
    416             }
    417 
    418             v.setAlpha(initAlpha);
    419             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    420             v.buildLayer();
    421             v.animate().alpha(destAlpha).setDuration(duration).setStartDelay(delay);
    422             if (interpolator != null) {
    423                 v.animate().setInterpolator(interpolator);
    424             }
    425             v.animate().setListener(new AnimatorListenerAdapter() {
    426                 @Override
    427                 public void onAnimationEnd(Animator animation) {
    428 
    429                     v.setLayerType(View.LAYER_TYPE_NONE, null);
    430                     if (pressed) {
    431                         if (mOnKeyListener != null) {
    432                             mOnKeyListener.onActionSelect(action);
    433                         }
    434                     } else {
    435                         if (mOnKeyListener != null) {
    436                             mOnKeyListener.onActionUnselect(action);
    437                         }
    438                         if (mListener != null) {
    439                             mListener.onActionClicked(action);
    440                         }
    441                     }
    442                 }
    443             });
    444             v.animate().start();
    445         }
    446     }
    447 
    448     private void fadeCheckmarks(final View v, final Action action, int duration, int delay,
    449             Interpolator interpolator) {
    450         int actionCheckSetId = action.getCheckSetId();
    451         if (actionCheckSetId != Action.NO_CHECK_SET) {
    452             // Find any actions that are checked and are in the same group
    453             // as the selected action. Fade their checkmarks out.
    454             for (int i = 0, size = mActions.size(); i < size; i++) {
    455                 Action a = mActions.get(i);
    456                 if (a != action && a.getCheckSetId() == actionCheckSetId && a.isChecked()) {
    457                     a.setChecked(false);
    458                     if (mScrollAdapterView != null) {
    459                         View viewToAnimateOut = mScrollAdapterView.getItemView(i);
    460                         if (viewToAnimateOut != null) {
    461                             final View checkView = viewToAnimateOut.findViewById(
    462                                     R.id.action_checkmark);
    463                             checkView.animate().alpha(CHECKMARK_ANIM_UNSELECTED_ALPHA)
    464                                     .setDuration(duration).setStartDelay(delay);
    465                             if (interpolator != null) {
    466                                 checkView.animate().setInterpolator(interpolator);
    467                             }
    468                             checkView.animate().setListener(new AnimatorListenerAdapter() {
    469                                 @Override
    470                                 public void onAnimationEnd(Animator animation) {
    471                                     checkView.setVisibility(View.INVISIBLE);
    472                                 }
    473                             });
    474                         }
    475                     }
    476                 }
    477             }
    478 
    479             // If we we'ren't already checked, fade our checkmark in.
    480             if (!action.isChecked()) {
    481                 action.setChecked(true);
    482                 if (mScrollAdapterView != null) {
    483                     final View checkView = v.findViewById(R.id.action_checkmark);
    484                     checkView.setVisibility(View.VISIBLE);
    485                     checkView.setAlpha(CHECKMARK_ANIM_UNSELECTED_ALPHA);
    486                     checkView.animate().alpha(CHECKMARK_ANIM_SELECTED_ALPHA).setDuration(duration)
    487                             .setStartDelay(delay);
    488                     if (interpolator != null) {
    489                         checkView.animate().setInterpolator(interpolator);
    490                     }
    491                     checkView.animate().setListener(null);
    492                 }
    493             }
    494         }
    495     }
    496 
    497     /**
    498      * @return the max height in pixels the description can be such that the
    499      * action nicely takes up the entire screen.
    500      */
    501     private static Integer getDescriptionMaxHeight(Context context, TextView title,
    502             TextView description) {
    503         if (sDescriptionMaxHeight == null) {
    504             final Resources res = context.getResources();
    505             final float verticalPadding = res.getDimension(R.dimen.list_item_vertical_padding);
    506             final int titleMaxLines = res.getInteger(R.integer.action_title_max_lines);
    507             final int displayHeight = ((WindowManager) context.getSystemService(
    508                     Context.WINDOW_SERVICE)).getDefaultDisplay().getHeight();
    509 
    510             // The 2 multiplier on the title height calculation is a conservative
    511             // estimate for font padding which can not be calculated at this stage
    512             // since the view hasn't been rendered yet.
    513             sDescriptionMaxHeight = (int) (displayHeight -
    514                     2 * verticalPadding - 2 * titleMaxLines * title.getLineHeight());
    515         }
    516         return sDescriptionMaxHeight;
    517     }
    518 
    519     private float getFloat(int resourceId) {
    520         TypedValue buffer = new TypedValue();
    521         mContext.getResources().getValue(resourceId, buffer, true);
    522         return buffer.getFloat();
    523     }
    524 }
    525