Home | History | Annotate | Download | only in dialog
      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.AnimatorInflater;
     21 import android.animation.AnimatorListenerAdapter;
     22 import android.animation.AnimatorSet;
     23 import android.animation.ObjectAnimator;
     24 import android.animation.TimeInterpolator;
     25 import android.animation.ValueAnimator;
     26 import android.app.Activity;
     27 import android.app.Fragment;
     28 import android.app.FragmentManager;
     29 import android.app.FragmentTransaction;
     30 import android.content.Context;
     31 import android.content.Intent;
     32 import android.content.pm.PackageManager;
     33 import android.content.res.Resources;
     34 import android.graphics.Bitmap;
     35 import android.graphics.Color;
     36 import android.graphics.drawable.BitmapDrawable;
     37 import android.graphics.drawable.ColorDrawable;
     38 import android.graphics.drawable.Drawable;
     39 import android.net.Uri;
     40 import android.os.Bundle;
     41 import android.os.Parcel;
     42 import android.os.Parcelable;
     43 import android.support.v17.leanback.R;
     44 import android.support.v17.leanback.widget.VerticalGridView;
     45 import android.support.v7.widget.RecyclerView;
     46 import android.util.Log;
     47 import android.view.LayoutInflater;
     48 import android.view.View;
     49 import android.view.ViewGroup;
     50 import android.view.ViewPropertyAnimator;
     51 import android.view.ViewTreeObserver;
     52 import android.view.ViewGroup.LayoutParams;
     53 import android.view.animation.DecelerateInterpolator;
     54 import android.view.animation.Interpolator;
     55 import android.widget.ImageView;
     56 import android.widget.RelativeLayout;
     57 import android.widget.TextView;
     58 
     59 import com.android.tv.settings.util.AccessibilityHelper;
     60 import com.android.tv.settings.widget.BitmapWorkerOptions;
     61 import com.android.tv.settings.widget.DrawableDownloader;
     62 import com.android.tv.settings.widget.DrawableDownloader.BitmapCallback;
     63 
     64 import java.util.ArrayList;
     65 import java.util.List;
     66 
     67 /**
     68  * Displays content on the left and actions on the right.
     69  */
     70 public class DialogFragment extends Fragment {
     71 
     72     private static final String TAG_LEAN_BACK_DIALOG_FRAGMENT = "leanBackDialogFragment";
     73     private static final String EXTRA_CONTENT_TITLE = "title";
     74     private static final String EXTRA_CONTENT_BREADCRUMB = "breadcrumb";
     75     private static final String EXTRA_CONTENT_DESCRIPTION = "description";
     76     private static final String EXTRA_CONTENT_ICON_RESOURCE_ID = "iconResourceId";
     77     private static final String EXTRA_CONTENT_ICON_URI = "iconUri";
     78     private static final String EXTRA_CONTENT_ICON_BITMAP = "iconBitmap";
     79     private static final String EXTRA_CONTENT_ICON_BACKGROUND = "iconBackground";
     80     private static final String EXTRA_ACTION_NAME = "name";
     81     private static final String EXTRA_ACTION_ACTIONS = "actions";
     82     private static final String EXTRA_ACTION_SELECTED_INDEX = "selectedIndex";
     83     private static final String EXTRA_ENTRY_TRANSITION_PERFORMED = "entryTransitionPerformed";
     84     private static final int ANIMATE_IN_DURATION = 250;
     85     private static final int ANIMATE_DELAY = 550;
     86     private static final int SECONDARY_ANIMATE_DELAY = 120;
     87     private static final int SLIDE_IN_STAGGER = 100;
     88     private static final int SLIDE_IN_DISTANCE = 120;
     89     private static final int ANIMATION_FRAGMENT_ENTER = 1;
     90     private static final int ANIMATION_FRAGMENT_EXIT = 2;
     91     private static final int ANIMATION_FRAGMENT_ENTER_POP = 3;
     92     private static final int ANIMATION_FRAGMENT_EXIT_POP = 4;
     93 
     94     /**
     95      * Builds a LeanBackDialogFragment object.
     96      */
     97     public static class Builder {
     98 
     99         private String mContentTitle;
    100         private String mContentBreadcrumb;
    101         private String mContentDescription;
    102         private int mIconResourceId;
    103         private Uri mIconUri;
    104         private Bitmap mIconBitmap;
    105         private int mIconBackgroundColor = Color.TRANSPARENT;
    106         private ArrayList<Action> mActions;
    107         private String mName;
    108         private int mSelectedIndex;
    109 
    110         public DialogFragment build() {
    111             DialogFragment fragment = new DialogFragment();
    112             Bundle args = new Bundle();
    113             args.putString(EXTRA_CONTENT_TITLE, mContentTitle);
    114             args.putString(EXTRA_CONTENT_BREADCRUMB, mContentBreadcrumb);
    115             args.putString(EXTRA_CONTENT_DESCRIPTION, mContentDescription);
    116             args.putInt(EXTRA_CONTENT_ICON_RESOURCE_ID, mIconResourceId);
    117             args.putParcelable(EXTRA_CONTENT_ICON_URI, mIconUri);
    118             args.putParcelable(EXTRA_CONTENT_ICON_BITMAP, mIconBitmap);
    119             args.putInt(EXTRA_CONTENT_ICON_BACKGROUND, mIconBackgroundColor);
    120             args.putParcelableArrayList(EXTRA_ACTION_ACTIONS, mActions);
    121             args.putString(EXTRA_ACTION_NAME, mName);
    122             args.putInt(EXTRA_ACTION_SELECTED_INDEX, mSelectedIndex);
    123             fragment.setArguments(args);
    124             return fragment;
    125         }
    126 
    127         public Builder title(String title) {
    128             mContentTitle = title;
    129             return this;
    130         }
    131 
    132         public Builder breadcrumb(String breadcrumb) {
    133             mContentBreadcrumb = breadcrumb;
    134             return this;
    135         }
    136 
    137         public Builder description(String description) {
    138             mContentDescription = description;
    139             return this;
    140         }
    141 
    142         public Builder iconResourceId(int iconResourceId) {
    143             mIconResourceId = iconResourceId;
    144             return this;
    145         }
    146 
    147         public Builder iconUri(Uri iconUri) {
    148             mIconUri = iconUri;
    149             return this;
    150         }
    151 
    152         public Builder iconBitmap(Bitmap iconBitmap) {
    153             mIconBitmap = iconBitmap;
    154             return this;
    155         }
    156 
    157         public Builder iconBackgroundColor(int iconBackgroundColor) {
    158             mIconBackgroundColor = iconBackgroundColor;
    159             return this;
    160         }
    161 
    162         public Builder actions(ArrayList<Action> actions) {
    163             mActions = actions;
    164             return this;
    165         }
    166 
    167         public Builder name(String name) {
    168             mName = name;
    169             return this;
    170         }
    171 
    172         public Builder selectedIndex(int selectedIndex) {
    173             mSelectedIndex = selectedIndex;
    174             return this;
    175         }
    176     }
    177 
    178     public static void add(FragmentManager fm, DialogFragment f) {
    179         boolean hasDialog = fm.findFragmentByTag(TAG_LEAN_BACK_DIALOG_FRAGMENT) != null;
    180         FragmentTransaction ft = fm.beginTransaction();
    181 
    182         if (hasDialog) {
    183             ft.setCustomAnimations(ANIMATION_FRAGMENT_ENTER,
    184                     ANIMATION_FRAGMENT_EXIT, ANIMATION_FRAGMENT_ENTER_POP,
    185                     ANIMATION_FRAGMENT_EXIT_POP);
    186             ft.addToBackStack(null);
    187         }
    188         ft.replace(android.R.id.content, f, TAG_LEAN_BACK_DIALOG_FRAGMENT).commit();
    189     }
    190 
    191     private DialogActionAdapter mAdapter;
    192     private SelectorAnimator mSelectorAnimator;
    193     private VerticalGridView mListView;
    194     private Action.Listener mListener;
    195     private String mTitle;
    196     private String mBreadcrumb;
    197     private String mDescription;
    198     private int mIconResourceId;
    199     private Uri mIconUri;
    200     private Bitmap mIconBitmap;
    201     private int mIconBackgroundColor = Color.TRANSPARENT;
    202     private ArrayList<Action> mActions;
    203     private String mName;
    204     private int mSelectedIndex = -1;
    205     private boolean mEntryTransitionPerformed;
    206     private boolean mIntroAnimationInProgress;
    207     private BitmapCallback mBitmapCallBack;
    208 
    209     @Override
    210     public void onCreate(Bundle savedInstanceState) {
    211         android.util.Log.v("DialogFragment", "onCreate");
    212         super.onCreate(savedInstanceState);
    213         Bundle state = (savedInstanceState != null) ? savedInstanceState : getArguments();
    214         if (mTitle == null) {
    215             mTitle = state.getString(EXTRA_CONTENT_TITLE);
    216         }
    217         if (mBreadcrumb == null) {
    218             mBreadcrumb = state.getString(EXTRA_CONTENT_BREADCRUMB);
    219         }
    220         if (mDescription == null) {
    221             mDescription = state.getString(EXTRA_CONTENT_DESCRIPTION);
    222         }
    223         if (mIconResourceId == 0) {
    224             mIconResourceId = state.getInt(EXTRA_CONTENT_ICON_RESOURCE_ID, 0);
    225         }
    226         if (mIconUri == null) {
    227             mIconUri = state.getParcelable(EXTRA_CONTENT_ICON_URI);
    228         }
    229         if (mIconBitmap == null) {
    230             mIconBitmap = state.getParcelable(EXTRA_CONTENT_ICON_BITMAP);
    231         }
    232         if (mIconBackgroundColor == Color.TRANSPARENT) {
    233             mIconBackgroundColor = state.getInt(EXTRA_CONTENT_ICON_BACKGROUND, Color.TRANSPARENT);
    234         }
    235         if (mActions == null) {
    236             mActions = state.getParcelableArrayList(EXTRA_ACTION_ACTIONS);
    237         }
    238         if (mName == null) {
    239             mName = state.getString(EXTRA_ACTION_NAME);
    240         }
    241         if (mSelectedIndex == -1) {
    242             mSelectedIndex = state.getInt(EXTRA_ACTION_SELECTED_INDEX, -1);
    243         }
    244         mEntryTransitionPerformed = state.getBoolean(EXTRA_ENTRY_TRANSITION_PERFORMED, false);
    245     }
    246 
    247     @Override
    248     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    249             Bundle savedInstanceState) {
    250 
    251         View v = inflater.inflate(R.layout.lb_dialog_fragment, container, false);
    252 
    253         View contentContainer = v.findViewById(R.id.content_fragment);
    254         View content = inflater.inflate(R.layout.lb_dialog_content, container, false);
    255         ((ViewGroup) contentContainer).addView(content);
    256         setContentView(content);
    257         v.setTag(R.id.content_fragment, content);
    258 
    259         View actionContainer = v.findViewById(R.id.action_fragment);
    260         View action = inflater.inflate(R.layout.lb_dialog_action_list, container, false);
    261         ((ViewGroup) actionContainer).addView(action);
    262         setActionView(action);
    263         v.setTag(R.id.action_fragment, action);
    264 
    265         return v;
    266     }
    267 
    268     @Override
    269     public void onSaveInstanceState(Bundle outState) {
    270         super.onSaveInstanceState(outState);
    271         outState.putString(EXTRA_CONTENT_TITLE, mTitle);
    272         outState.putString(EXTRA_CONTENT_BREADCRUMB, mBreadcrumb);
    273         outState.putString(EXTRA_CONTENT_DESCRIPTION, mDescription);
    274         outState.putInt(EXTRA_CONTENT_ICON_RESOURCE_ID, mIconResourceId);
    275         outState.putParcelable(EXTRA_CONTENT_ICON_URI, mIconUri);
    276         outState.putParcelable(EXTRA_CONTENT_ICON_BITMAP, mIconBitmap);
    277         outState.putInt(EXTRA_CONTENT_ICON_BACKGROUND, mIconBackgroundColor);
    278         outState.putParcelableArrayList(EXTRA_ACTION_ACTIONS, mActions);
    279         outState.putInt(EXTRA_ACTION_SELECTED_INDEX,
    280                 (mListView != null) ? getSelectedItemPosition() : mSelectedIndex);
    281         outState.putString(EXTRA_ACTION_NAME, mName);
    282         outState.putBoolean(EXTRA_ENTRY_TRANSITION_PERFORMED, mEntryTransitionPerformed);
    283     }
    284 
    285     @Override
    286     public void onStart() {
    287         super.onStart();
    288         if (!mEntryTransitionPerformed) {
    289             mEntryTransitionPerformed = true;
    290             performEntryTransition();
    291         } else {
    292             performSelectorTransition();
    293         }
    294     }
    295 
    296     @Override
    297     public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
    298         View dialogView = getView();
    299         View contentView = (View) dialogView.getTag(R.id.content_fragment);
    300         View actionView = (View) dialogView.getTag(R.id.action_fragment);
    301         View actionContainerView = dialogView.findViewById(R.id.action_fragment);
    302         View titleView = (View) contentView.getTag(R.id.title);
    303         View breadcrumbView = (View) contentView.getTag(R.id.breadcrumb);
    304         View descriptionView = (View) contentView.getTag(R.id.description);
    305         View iconView = (View) contentView.getTag(R.id.icon);
    306         View listView = (View) actionView.getTag(R.id.list);
    307         View selectorView = (View) actionView.getTag(R.id.selector);
    308 
    309         ArrayList<Animator> animators = new ArrayList<Animator>();
    310 
    311         switch (nextAnim) {
    312             case ANIMATION_FRAGMENT_ENTER:
    313                 animators.add(createSlideLeftInAnimator(titleView));
    314                 animators.add(createSlideLeftInAnimator(breadcrumbView));
    315                 animators.add(createSlideLeftInAnimator(descriptionView));
    316                 animators.add(createSlideLeftInAnimator(iconView));
    317                 animators.add(createSlideLeftInAnimator(listView));
    318                 animators.add(createSlideLeftInAnimator(selectorView));
    319                 break;
    320             case ANIMATION_FRAGMENT_EXIT:
    321                 animators.add(createSlideLeftOutAnimator(titleView));
    322                 animators.add(createSlideLeftOutAnimator(breadcrumbView));
    323                 animators.add(createSlideLeftOutAnimator(descriptionView));
    324                 animators.add(createSlideLeftOutAnimator(iconView));
    325                 animators.add(createSlideLeftOutAnimator(listView));
    326                 animators.add(createSlideLeftOutAnimator(selectorView));
    327                 animators.add(createFadeOutAnimator(actionContainerView));
    328                 break;
    329             case ANIMATION_FRAGMENT_ENTER_POP:
    330                 animators.add(createSlideRightInAnimator(titleView));
    331                 animators.add(createSlideRightInAnimator(breadcrumbView));
    332                 animators.add(createSlideRightInAnimator(descriptionView));
    333                 animators.add(createSlideRightInAnimator(iconView));
    334                 animators.add(createSlideRightInAnimator(listView));
    335                 animators.add(createSlideRightInAnimator(selectorView));
    336                 break;
    337             case ANIMATION_FRAGMENT_EXIT_POP:
    338                 animators.add(createSlideRightOutAnimator(titleView));
    339                 animators.add(createSlideRightOutAnimator(breadcrumbView));
    340                 animators.add(createSlideRightOutAnimator(descriptionView));
    341                 animators.add(createSlideRightOutAnimator(iconView));
    342                 animators.add(createSlideRightOutAnimator(listView));
    343                 animators.add(createSlideRightOutAnimator(selectorView));
    344                 animators.add(createFadeOutAnimator(actionContainerView));
    345                 break;
    346             default:
    347                 return super.onCreateAnimator(transit, enter, nextAnim);
    348         }
    349 
    350         mEntryTransitionPerformed = true;
    351         return createDummyAnimator(dialogView, animators);
    352     }
    353 
    354     public void setIcon(int iconResourceId) {
    355         mIconResourceId = iconResourceId;
    356         View v = getView();
    357         if (v != null) {
    358             final ImageView iconImageView = (ImageView) v.findViewById(R.id.icon);
    359             if (iconImageView != null) {
    360                 if (iconResourceId != 0) {
    361                     iconImageView.setImageResource(iconResourceId);
    362                     iconImageView.setVisibility(View.VISIBLE);
    363                     updateViewSize(iconImageView);
    364                 }
    365             }
    366         }
    367     }
    368 
    369     /**
    370      * Fragments need to call this method in its {@link #onResume()} to set the
    371      * custom listener. <br/>
    372      * Activities do not need to call this method
    373      *
    374      * @param listener
    375      */
    376     public void setListener(Action.Listener listener) {
    377         mListener = listener;
    378     }
    379 
    380     public boolean hasListener() {
    381         return mListener != null;
    382     }
    383 
    384     public ArrayList<Action> getActions() {
    385         return mActions;
    386     }
    387 
    388     public void setActions(ArrayList<Action> actions) {
    389         mActions = actions;
    390         if (mAdapter != null) {
    391             mAdapter.setActions(mActions);
    392         }
    393     }
    394 
    395     public View getItemView(int position) {
    396         return mListView.getChildAt(position);
    397     }
    398 
    399     public void setSelectedPosition(int position) {
    400         mListView.setSelectedPosition(position);
    401     }
    402 
    403     public int getSelectedItemPosition() {
    404         return mListView.indexOfChild(mListView.getFocusedChild());
    405     }
    406 
    407     /**
    408      * Called when intro animation is finished.
    409      * <p>
    410      * If a subclass is going to alter the view, should wait until this is
    411      * called.
    412      */
    413     public void onIntroAnimationFinished() {
    414         mIntroAnimationInProgress = false;
    415     }
    416 
    417     public boolean isIntroAnimationInProgress() {
    418         return mIntroAnimationInProgress;
    419     }
    420 
    421     private void setContentView(View content) {
    422         TextView titleView = (TextView) content.findViewById(R.id.title);
    423         TextView breadcrumbView = (TextView) content.findViewById(R.id.breadcrumb);
    424         TextView descriptionView = (TextView) content.findViewById(R.id.description);
    425         titleView.setText(mTitle);
    426         breadcrumbView.setText(mBreadcrumb);
    427         descriptionView.setText(mDescription);
    428         final ImageView iconImageView = (ImageView) content.findViewById(R.id.icon);
    429         if (mIconBackgroundColor != Color.TRANSPARENT) {
    430             iconImageView.setBackgroundColor(mIconBackgroundColor);
    431         }
    432 
    433         if (AccessibilityHelper.forceFocusableViews(getActivity())) {
    434             titleView.setFocusable(true);
    435             titleView.setFocusableInTouchMode(true);
    436             descriptionView.setFocusable(true);
    437             descriptionView.setFocusableInTouchMode(true);
    438             breadcrumbView.setFocusable(true);
    439             breadcrumbView.setFocusableInTouchMode(true);
    440         }
    441 
    442         if (mIconResourceId != 0) {
    443             iconImageView.setImageResource(mIconResourceId);
    444             updateViewSize(iconImageView);
    445         } else {
    446             if (mIconBitmap != null) {
    447                 iconImageView.setImageBitmap(mIconBitmap);
    448                 updateViewSize(iconImageView);
    449             } else {
    450                 if (mIconUri != null) {
    451                     iconImageView.setVisibility(View.INVISIBLE);
    452 
    453                     DrawableDownloader bitmapDownloader = DrawableDownloader.getInstance(
    454                             content.getContext());
    455                     mBitmapCallBack = new BitmapCallback() {
    456                         @Override
    457                         public void onBitmapRetrieved(Drawable bitmap) {
    458                             if (bitmap != null) {
    459                                 mIconBitmap = (bitmap instanceof BitmapDrawable) ? ((BitmapDrawable) bitmap)
    460                                         .getBitmap()
    461                                         : null;
    462                                 iconImageView.setVisibility(View.VISIBLE);
    463                                 iconImageView.setImageDrawable(bitmap);
    464                                 updateViewSize(iconImageView);
    465                             }
    466                         }
    467                     };
    468 
    469                     bitmapDownloader.getBitmap(new BitmapWorkerOptions.Builder(
    470                             content.getContext()).resource(mIconUri)
    471                             .width(iconImageView.getLayoutParams().width).build(),
    472                             mBitmapCallBack);
    473                 } else {
    474                     iconImageView.setVisibility(View.GONE);
    475                 }
    476             }
    477         }
    478 
    479         content.setTag(R.id.title, titleView);
    480         content.setTag(R.id.breadcrumb, breadcrumbView);
    481         content.setTag(R.id.description, descriptionView);
    482         content.setTag(R.id.icon, iconImageView);
    483     }
    484 
    485     private void setActionView(View action) {
    486         mAdapter = new DialogActionAdapter(new Action.Listener() {
    487             @Override
    488             public void onActionClicked(Action action) {
    489                 // eat events if action is disabled or only displays info
    490                 if (!action.isEnabled() || action.infoOnly()) {
    491                     return;
    492                 }
    493 
    494                 /**
    495                  * If the custom lister has been set using
    496                  * {@link #setListener(DialogActionAdapter.Listener)}, use it.
    497                  * If not, use the activity's default listener.
    498                  */
    499                 if (mListener != null) {
    500                     mListener.onActionClicked(action);
    501                 } else if (getActivity() instanceof Action.Listener) {
    502                     Action.Listener listener = (Action.Listener) getActivity();
    503                     listener.onActionClicked(action);
    504                 }
    505             }
    506         }, new Action.OnFocusListener() {
    507             @Override
    508             public void onActionFocused(Action action) {
    509                 if (getActivity() instanceof Action.OnFocusListener) {
    510                     Action.OnFocusListener listener = (Action.OnFocusListener) getActivity();
    511                     listener.onActionFocused(action);
    512                 }
    513             }
    514         }, mActions);
    515 
    516         if (action instanceof VerticalGridView) {
    517             mListView = (VerticalGridView) action;
    518         } else {
    519             mListView = (VerticalGridView) action.findViewById(R.id.list);
    520             if (mListView == null) {
    521                 throw new IllegalStateException("No ListView exists.");
    522             }
    523 //            mListView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
    524 //            mListView.setWindowAlignmentOffsetPercent(0.5f);
    525 //            mListView.setItemAlignmentOffset(0);
    526 //            mListView.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
    527             mListView.setWindowAlignmentOffset(0);
    528             mListView.setWindowAlignmentOffsetPercent(50f);
    529             mListView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
    530             View selectorView = action.findViewById(R.id.selector);
    531             if (selectorView != null) {
    532                 mSelectorAnimator = new SelectorAnimator(selectorView, mListView);
    533                 mListView.setOnScrollListener(mSelectorAnimator);
    534             }
    535         }
    536 
    537         mListView.requestFocusFromTouch();
    538         mListView.setAdapter(mAdapter);
    539         mListView.setSelectedPosition(
    540                 (mSelectedIndex >= 0 && mSelectedIndex < mActions.size()) ? mSelectedIndex
    541                 : getFirstCheckedAction());
    542 
    543         action.setTag(R.id.list, mListView);
    544         action.setTag(R.id.selector, action.findViewById(R.id.selector));
    545     }
    546 
    547     private int getFirstCheckedAction() {
    548         for (int i = 0, size = mActions.size(); i < size; i++) {
    549             if (mActions.get(i).isChecked()) {
    550                 return i;
    551             }
    552         }
    553         return 0;
    554     }
    555 
    556     private void updateViewSize(ImageView iconView) {
    557         int intrinsicWidth = iconView.getDrawable().getIntrinsicWidth();
    558         LayoutParams lp = iconView.getLayoutParams();
    559         if (intrinsicWidth > 0) {
    560             lp.height = lp.width * iconView.getDrawable().getIntrinsicHeight()
    561                     / intrinsicWidth;
    562         } else {
    563             // If no intrinsic width, then just mke this a square.
    564             lp.height = lp.width;
    565         }
    566     }
    567 
    568     private void fadeIn(View v) {
    569         v.setAlpha(0f);
    570         ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha", 1f);
    571         alphaAnimator.setDuration(v.getContext().getResources().getInteger(
    572                 android.R.integer.config_mediumAnimTime));
    573         alphaAnimator.start();
    574     }
    575 
    576     private void runDelayedAnim(final Runnable runnable) {
    577         final View dialogView = getView();
    578         final View contentView = (View) dialogView.getTag(R.id.content_fragment);
    579 
    580         contentView.getViewTreeObserver().addOnGlobalLayoutListener(
    581                 new ViewTreeObserver.OnGlobalLayoutListener() {
    582                 @Override
    583                     public void onGlobalLayout() {
    584                         contentView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    585                         // if we buildLayer() at this time, the texture is
    586                         // actually not created delay a little so we can make
    587                         // sure all hardware layer is created before animation,
    588                         // in that way we can avoid the jittering of start
    589                         // animation
    590                         contentView.postOnAnimationDelayed(runnable, ANIMATE_DELAY);
    591                     }
    592                 });
    593 
    594     }
    595 
    596     private void performSelectorTransition() {
    597         runDelayedAnim(new Runnable() {
    598             @Override
    599             public void run() {
    600                 // Fade in the selector.
    601                 if (mSelectorAnimator != null) {
    602                     mSelectorAnimator.fadeIn();
    603                 }
    604             }
    605         });
    606     }
    607 
    608     private void performEntryTransition() {
    609         final View dialogView = getView();
    610         final View contentView = (View) dialogView.getTag(R.id.content_fragment);
    611         final View actionContainerView = dialogView.findViewById(R.id.action_fragment);
    612 
    613         mIntroAnimationInProgress = true;
    614 
    615         // Fade out the old activity.
    616         getActivity().overridePendingTransition(0, R.anim.lb_dialog_fade_out);
    617 
    618         int bgColor = contentView.getContext().getResources()
    619                 .getColor(R.color.lb_dialog_activity_background);
    620         final ColorDrawable bgDrawable = new ColorDrawable();
    621         bgDrawable.setColor(bgColor);
    622         bgDrawable.setAlpha(0);
    623         dialogView.setBackground(bgDrawable);
    624         dialogView.setVisibility(View.INVISIBLE);
    625 
    626         runDelayedAnim(new Runnable() {
    627             @Override
    628             public void run() {
    629                 if (!isAdded()) {
    630                     // We have been detached before this could run,
    631                     // so just bail
    632                     return;
    633                 }
    634 
    635                 dialogView.setVisibility(View.VISIBLE);
    636 
    637                 // Fade in the activity background protection
    638                 ObjectAnimator oa = ObjectAnimator.ofInt(bgDrawable, "alpha", 255);
    639                 oa.setDuration(ANIMATE_IN_DURATION);
    640                 oa.setStartDelay(SECONDARY_ANIMATE_DELAY);
    641                 oa.setInterpolator(new DecelerateInterpolator(1.0f));
    642                 oa.start();
    643 
    644                 // Fade in and slide in the ContentFragment
    645                 // TextViews from the left.
    646                 prepareAndAnimateView((View) contentView.getTag(R.id.title),
    647                         -SLIDE_IN_DISTANCE, false);
    648                 prepareAndAnimateView((View) contentView.getTag(R.id.breadcrumb),
    649                         -SLIDE_IN_DISTANCE, false);
    650                 prepareAndAnimateView((View) contentView.getTag(R.id.description),
    651                         -SLIDE_IN_DISTANCE, false);
    652 
    653                 // Fade in and slide in the ActionFragment from the
    654                 // right.
    655                 prepareAndAnimateView(actionContainerView,
    656                         actionContainerView.getMeasuredWidth(), false);
    657                 prepareAndAnimateView((View) contentView.getTag(R.id.icon),
    658                         -SLIDE_IN_DISTANCE, true);
    659 
    660                 // Fade in the selector.
    661                 if (mSelectorAnimator != null) {
    662                     mSelectorAnimator.fadeIn();
    663                 }
    664             }
    665         });
    666     }
    667 
    668     private void prepareAndAnimateView(final View v, float initTransX,
    669             final boolean notifyAnimationFinished) {
    670         v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    671         v.buildLayer();
    672         v.setAlpha(0);
    673         v.setTranslationX(initTransX);
    674         v.animate().alpha(1f).translationX(0).setDuration(ANIMATE_IN_DURATION)
    675                 .setStartDelay(SECONDARY_ANIMATE_DELAY);
    676         v.animate().setInterpolator(new DecelerateInterpolator(1.0f));
    677         v.animate().setListener(new AnimatorListenerAdapter() {
    678                 @Override
    679             public void onAnimationEnd(Animator animation) {
    680                 v.setLayerType(View.LAYER_TYPE_NONE, null);
    681                 if (notifyAnimationFinished) {
    682                     onIntroAnimationFinished();
    683                 }
    684             }
    685         });
    686         v.animate().start();
    687     }
    688 
    689     private Animator createDummyAnimator(final View v, ArrayList<Animator> animators) {
    690         final AnimatorSet animatorSet = new AnimatorSet();
    691         animatorSet.playTogether(animators);
    692         return new UntargetableAnimatorSet(animatorSet);
    693     }
    694 
    695     private Animator createAnimator(View v, int resourceId) {
    696         Animator animator = AnimatorInflater.loadAnimator(v.getContext(), resourceId);
    697         animator.setTarget(v);
    698         return animator;
    699     }
    700 
    701     private Animator createSlideLeftOutAnimator(View v) {
    702         return createTranslateAlphaAnimator(v, 0, -200f, 1f, 0);
    703     }
    704 
    705     private Animator createSlideLeftInAnimator(View v) {
    706         return createTranslateAlphaAnimator(v, 200f, 0, 0, 1f);
    707     }
    708 
    709     private Animator createSlideRightInAnimator(View v) {
    710         return createTranslateAlphaAnimator(v, -200f, 0, 0, 1f);
    711     }
    712 
    713     private Animator createSlideRightOutAnimator(View v) {
    714         return createTranslateAlphaAnimator(v, 0, 200f, 1f, 0);
    715     }
    716 
    717     private Animator createFadeOutAnimator(View v) {
    718         return createAlphaAnimator(v, 1f, 0);
    719     }
    720 
    721     private Animator createTranslateAlphaAnimator(View v, float fromTranslateX, float toTranslateX,
    722             float fromAlpha, float toAlpha) {
    723         ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(v, "translationX", fromTranslateX,
    724                 toTranslateX);
    725         translateAnimator.setDuration(
    726                 getResources().getInteger(android.R.integer.config_longAnimTime));
    727         Animator alphaAnimator = createAlphaAnimator(v, fromAlpha, toAlpha);
    728         AnimatorSet animatorSet = new AnimatorSet();
    729         animatorSet.play(translateAnimator).with(alphaAnimator);
    730         return animatorSet;
    731     }
    732 
    733     private Animator createAlphaAnimator(View v, float fromAlpha, float toAlpha) {
    734         ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(v, "alpha", fromAlpha, toAlpha);
    735         alphaAnimator.setDuration(getResources().getInteger(android.R.integer.config_longAnimTime));
    736         return alphaAnimator;
    737     }
    738 
    739     private static class SelectorAnimator extends RecyclerView.OnScrollListener {
    740 
    741         private final View mSelectorView;
    742         private final ViewGroup mParentView;
    743         private final int mAnimationDuration;
    744         private volatile boolean mFadedOut = true;
    745 
    746         SelectorAnimator(View selectorView, ViewGroup parentView) {
    747             mSelectorView = selectorView;
    748             mParentView = parentView;
    749             mAnimationDuration = selectorView.getContext()
    750                     .getResources().getInteger(R.integer.lb_dialog_animation_duration);
    751         }
    752 
    753         // We want to fade in the selector if we've stopped scrolling on it. If
    754         // we're scrolling, we want to ensure to dim the selector if we haven't
    755         // already. We dim the last highlighted view so that while a user is
    756         // scrolling, nothing is highlighted.
    757         @Override
    758         public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
    759             if (newState == RecyclerView.SCROLL_STATE_IDLE) {
    760                 fadeIn();
    761             } else {
    762                 fadeOut();
    763             }
    764         }
    765 
    766         public void fadeIn() {
    767             // The selector starts with a height of 0. In order to scale up
    768             // from
    769             // 0 we first need the set the height to 1 and scale form there.
    770             int selectorHeight = mSelectorView.getHeight();
    771             if (selectorHeight == 0) {
    772                 LayoutParams lp = mSelectorView.getLayoutParams();
    773                 lp.height = selectorHeight = mSelectorView.getContext().getResources()
    774                         .getDimensionPixelSize(R.dimen.lb_action_fragment_selector_min_height);
    775                 mSelectorView.setLayoutParams(lp);
    776             }
    777             View focusedChild = mParentView.getFocusedChild();
    778             if (focusedChild != null) {
    779                 float scaleY = (float) focusedChild.getHeight() / selectorHeight;
    780                 ViewPropertyAnimator animation = mSelectorView.animate()
    781                         .alpha(1f)
    782                         .setListener(new Listener(false))
    783                         .setDuration(mAnimationDuration)
    784                         .setInterpolator(new DecelerateInterpolator(2f));
    785                 if (mFadedOut) {
    786                     // selector is completely faded out, so we can just
    787                     // scale
    788                     // before fading in.
    789                     mSelectorView.setScaleY(scaleY);
    790                 } else {
    791                     // selector is not faded out, so we must animate the
    792                     // scale
    793                     // as we fade in.
    794                     animation.scaleY(scaleY);
    795                 }
    796                 animation.start();
    797             }
    798         }
    799 
    800         public void fadeOut() {
    801             mSelectorView.animate()
    802             .alpha(0f)
    803             .setDuration(mAnimationDuration)
    804             .setInterpolator(new DecelerateInterpolator(2f))
    805             .setListener(new Listener(true))
    806             .start();
    807         }
    808 
    809         /**
    810          * Sets {@link BaseScrollAdapterFragment#mFadedOut}
    811          * {@link BaseScrollAdapterFragment#mFadedOut} is true, iff
    812          * {@link BaseScrollAdapterFragment#mSelectorView} has an alpha of 0
    813          * (faded out). If false the view either has an alpha of 1 (visible) or
    814          * is in the process of animating.
    815          */
    816         private class Listener implements Animator.AnimatorListener {
    817             private boolean mFadingOut;
    818             private boolean mCanceled;
    819 
    820             public Listener(boolean fadingOut) {
    821                 mFadingOut = fadingOut;
    822             }
    823 
    824             @Override
    825             public void onAnimationStart(Animator animation) {
    826                 if (!mFadingOut) {
    827                     mFadedOut = false;
    828                 }
    829             }
    830 
    831             @Override
    832             public void onAnimationEnd(Animator animation) {
    833                 if (!mCanceled && mFadingOut) {
    834                     mFadedOut = true;
    835                 }
    836             }
    837 
    838             @Override
    839             public void onAnimationCancel(Animator animation) {
    840                 mCanceled = true;
    841             }
    842 
    843             @Override
    844             public void onAnimationRepeat(Animator animation) {
    845             }
    846         }
    847     }
    848 
    849     private static class UntargetableAnimatorSet extends Animator {
    850 
    851         private final AnimatorSet mAnimatorSet;
    852 
    853         UntargetableAnimatorSet(AnimatorSet animatorSet) {
    854             mAnimatorSet = animatorSet;
    855         }
    856 
    857         @Override
    858         public void addListener(Animator.AnimatorListener listener) {
    859             mAnimatorSet.addListener(listener);
    860         }
    861 
    862         @Override
    863         public void cancel() {
    864             mAnimatorSet.cancel();
    865         }
    866 
    867         @Override
    868         public Animator clone() {
    869             return mAnimatorSet.clone();
    870         }
    871 
    872         @Override
    873         public void end() {
    874             mAnimatorSet.end();
    875         }
    876 
    877         @Override
    878         public long getDuration() {
    879             return mAnimatorSet.getDuration();
    880         }
    881 
    882         @Override
    883         public ArrayList<Animator.AnimatorListener> getListeners() {
    884             return mAnimatorSet.getListeners();
    885         }
    886 
    887         @Override
    888         public long getStartDelay() {
    889             return mAnimatorSet.getStartDelay();
    890         }
    891 
    892         @Override
    893         public boolean isRunning() {
    894             return mAnimatorSet.isRunning();
    895         }
    896 
    897         @Override
    898         public boolean isStarted() {
    899             return mAnimatorSet.isStarted();
    900         }
    901 
    902         @Override
    903         public void removeAllListeners() {
    904             mAnimatorSet.removeAllListeners();
    905         }
    906 
    907         @Override
    908         public void removeListener(Animator.AnimatorListener listener) {
    909             mAnimatorSet.removeListener(listener);
    910         }
    911 
    912         @Override
    913         public Animator setDuration(long duration) {
    914             return mAnimatorSet.setDuration(duration);
    915         }
    916 
    917         @Override
    918         public void setInterpolator(TimeInterpolator value) {
    919             mAnimatorSet.setInterpolator(value);
    920         }
    921 
    922         @Override
    923         public void setStartDelay(long startDelay) {
    924             mAnimatorSet.setStartDelay(startDelay);
    925         }
    926 
    927         @Override
    928         public void setTarget(Object target) {
    929             // ignore
    930         }
    931 
    932         @Override
    933         public void setupEndValues() {
    934             mAnimatorSet.setupEndValues();
    935         }
    936 
    937         @Override
    938         public void setupStartValues() {
    939             mAnimatorSet.setupStartValues();
    940         }
    941 
    942         @Override
    943         public void start() {
    944             mAnimatorSet.start();
    945         }
    946     }
    947 
    948     /**
    949      * An data class which represents an action within an
    950      * {@link DialogFragment}. A list of Actions represent a list of choices the
    951      * user can make, a radio-button list of configuration options, or just a
    952      * list of information.
    953      */
    954     public static class Action implements Parcelable {
    955 
    956         private static final String TAG = "Action";
    957 
    958         public static final int NO_DRAWABLE = 0;
    959         public static final int NO_CHECK_SET = 0;
    960         public static final int DEFAULT_CHECK_SET_ID = 1;
    961 
    962         /**
    963          * Object listening for adapter events.
    964          */
    965         public interface Listener {
    966 
    967             /**
    968              * Called when the user clicks on an action.
    969              */
    970             public void onActionClicked(Action action);
    971         }
    972 
    973         public interface OnFocusListener {
    974 
    975             /**
    976              * Called when the user focuses on an action.
    977              */
    978             public void onActionFocused(Action action);
    979         }
    980 
    981         /**
    982          * Builds a Action object.
    983          */
    984         public static class Builder {
    985             private String mKey;
    986             private String mTitle;
    987             private String mDescription;
    988             private Intent mIntent;
    989             private String mResourcePackageName;
    990             private int mDrawableResource = NO_DRAWABLE;
    991             private Uri mIconUri;
    992             private boolean mChecked;
    993             private boolean mMultilineDescription;
    994             private boolean mHasNext;
    995             private boolean mInfoOnly;
    996             private int mCheckSetId = NO_CHECK_SET;
    997             private boolean mEnabled = true;
    998 
    999             public Action build() {
   1000                 Action action = new Action();
   1001                 action.mKey = mKey;
   1002                 action.mTitle = mTitle;
   1003                 action.mDescription = mDescription;
   1004                 action.mIntent = mIntent;
   1005                 action.mResourcePackageName = mResourcePackageName;
   1006                 action.mDrawableResource = mDrawableResource;
   1007                 action.mIconUri = mIconUri;
   1008                 action.mChecked = mChecked;
   1009                 action.mMultilineDescription = mMultilineDescription;
   1010                 action.mHasNext = mHasNext;
   1011                 action.mInfoOnly = mInfoOnly;
   1012                 action.mCheckSetId = mCheckSetId;
   1013                 action.mEnabled = mEnabled;
   1014                 return action;
   1015             }
   1016 
   1017             public Builder key(String key) {
   1018                 mKey = key;
   1019                 return this;
   1020             }
   1021 
   1022             public Builder title(String title) {
   1023                 mTitle = title;
   1024                 return this;
   1025             }
   1026 
   1027             public Builder description(String description) {
   1028                 mDescription = description;
   1029                 return this;
   1030             }
   1031 
   1032             public Builder intent(Intent intent) {
   1033                 mIntent = intent;
   1034                 return this;
   1035             }
   1036 
   1037             public Builder resourcePackageName(String resourcePackageName) {
   1038                 mResourcePackageName = resourcePackageName;
   1039                 return this;
   1040             }
   1041 
   1042             public Builder drawableResource(int drawableResource) {
   1043                 mDrawableResource = drawableResource;
   1044                 return this;
   1045             }
   1046 
   1047             public Builder iconUri(Uri iconUri) {
   1048                 mIconUri = iconUri;
   1049                 return this;
   1050             }
   1051 
   1052             public Builder checked(boolean checked) {
   1053                 mChecked = checked;
   1054                 return this;
   1055             }
   1056 
   1057             public Builder multilineDescription(boolean multilineDescription) {
   1058                 mMultilineDescription = multilineDescription;
   1059                 return this;
   1060             }
   1061 
   1062             public Builder hasNext(boolean hasNext) {
   1063                 mHasNext = hasNext;
   1064                 return this;
   1065             }
   1066 
   1067             public Builder infoOnly(boolean infoOnly) {
   1068                 mInfoOnly = infoOnly;
   1069                 return this;
   1070             }
   1071 
   1072             public Builder checkSetId(int checkSetId) {
   1073                 mCheckSetId = checkSetId;
   1074                 return this;
   1075             }
   1076 
   1077             public Builder enabled(boolean enabled) {
   1078                 mEnabled = enabled;
   1079                 return this;
   1080             }
   1081         }
   1082 
   1083         private String mKey;
   1084         private String mTitle;
   1085         private String mDescription;
   1086         private Intent mIntent;
   1087 
   1088         /**
   1089          * If not {@code null}, the package name to use to retrieve
   1090          * {@link #mDrawableResource}.
   1091          */
   1092         private String mResourcePackageName;
   1093 
   1094         private int mDrawableResource;
   1095         private Uri mIconUri;
   1096         private boolean mChecked;
   1097         private boolean mMultilineDescription;
   1098         private boolean mHasNext;
   1099         private boolean mInfoOnly;
   1100         private int mCheckSetId;
   1101         private boolean mEnabled;
   1102 
   1103         private Action() {
   1104         }
   1105 
   1106         public String getKey() {
   1107             return mKey;
   1108         }
   1109 
   1110         public String getTitle() {
   1111             return mTitle;
   1112         }
   1113 
   1114         public String getDescription() {
   1115             return mDescription;
   1116         }
   1117 
   1118         public void setDescription(String description) {
   1119             mDescription = description;
   1120         }
   1121 
   1122         public Intent getIntent() {
   1123             return mIntent;
   1124         }
   1125 
   1126         public boolean isChecked() {
   1127             return mChecked;
   1128         }
   1129 
   1130         public int getDrawableResource() {
   1131             return mDrawableResource;
   1132         }
   1133 
   1134         public Uri getIconUri() {
   1135             return mIconUri;
   1136         }
   1137 
   1138         public String getResourcePackageName() {
   1139             return mResourcePackageName;
   1140         }
   1141 
   1142         /**
   1143          * Returns the check set id this action is a part of. All actions in the
   1144          * same list with the same check set id are considered linked. When one
   1145          * of the actions within that set is selected that action becomes
   1146          * checked while all the other actions become unchecked.
   1147          *
   1148          * @return an integer representing the check set this action is a part
   1149          *         of or {@link NO_CHECK_SET} if this action isn't a
   1150          *         part of a check set.
   1151          */
   1152         public int getCheckSetId() {
   1153             return mCheckSetId;
   1154         }
   1155 
   1156         public boolean hasMultilineDescription() {
   1157             return mMultilineDescription;
   1158         }
   1159 
   1160         public boolean isEnabled() {
   1161             return mEnabled;
   1162         }
   1163 
   1164         public void setChecked(boolean checked) {
   1165             mChecked = checked;
   1166         }
   1167 
   1168         public void setEnabled(boolean enabled) {
   1169             mEnabled = enabled;
   1170         }
   1171 
   1172         /**
   1173          * @return true if the action will request further user input when
   1174          *         selected (such as showing another dialog or launching a new
   1175          *         activity). False, otherwise.
   1176          */
   1177         public boolean hasNext() {
   1178             return mHasNext;
   1179         }
   1180 
   1181         /**
   1182          * @return true if the action will only display information and is thus
   1183          *         unactionable. If both this and {@link #hasNext()} are true,
   1184          *         infoOnly takes precedence. (default is false) e.g. the amount
   1185          *         of storage a document uses or cost of an app.
   1186          */
   1187         public boolean infoOnly() {
   1188             return mInfoOnly;
   1189         }
   1190 
   1191         /**
   1192          * Returns an indicator to be drawn. If null is returned, no space for
   1193          * the indicator will be made.
   1194          *
   1195          * @param context the context of the Activity this Action belongs to
   1196          * @return an indicator to draw or null if no indicator space should
   1197          *         exist.
   1198          */
   1199         public Drawable getIndicator(Context context) {
   1200             if (mDrawableResource == NO_DRAWABLE) {
   1201                 return null;
   1202             }
   1203             if (mResourcePackageName == null) {
   1204                 return context.getResources().getDrawable(mDrawableResource);
   1205             }
   1206             // If we get to here, need to load the resources.
   1207             Drawable icon = null;
   1208             try {
   1209                 Context packageContext = context.createPackageContext(mResourcePackageName, 0);
   1210                 icon = packageContext.getResources().getDrawable(mDrawableResource);
   1211             } catch (PackageManager.NameNotFoundException e) {
   1212                 if (Log.isLoggable(TAG, Log.WARN)) {
   1213                     Log.w(TAG, "No icon for this action.");
   1214                 }
   1215             } catch (Resources.NotFoundException e) {
   1216                 if (Log.isLoggable(TAG, Log.WARN)) {
   1217                     Log.w(TAG, "No icon for this action.");
   1218                 }
   1219             }
   1220             return icon;
   1221         }
   1222 
   1223         public static Parcelable.Creator<Action> CREATOR = new Parcelable.Creator<Action>() {
   1224 
   1225                 @Override
   1226             public Action createFromParcel(Parcel source) {
   1227 
   1228                 return new Action.Builder()
   1229                         .key(source.readString())
   1230                         .title(source.readString())
   1231                         .description(source.readString())
   1232                         .intent((Intent) source.readParcelable(Intent.class.getClassLoader()))
   1233                         .resourcePackageName(source.readString())
   1234                         .drawableResource(source.readInt())
   1235                         .iconUri((Uri) source.readParcelable(Uri.class.getClassLoader()))
   1236                         .checked(source.readInt() != 0)
   1237                         .multilineDescription(source.readInt() != 0)
   1238                         .checkSetId(source.readInt())
   1239                         .build();
   1240             }
   1241 
   1242                 @Override
   1243             public Action[] newArray(int size) {
   1244                 return new Action[size];
   1245             }
   1246         };
   1247 
   1248         @Override
   1249         public int describeContents() {
   1250             return 0;
   1251         }
   1252 
   1253         @Override
   1254         public void writeToParcel(Parcel dest, int flags) {
   1255             dest.writeString(mKey);
   1256             dest.writeString(mTitle);
   1257             dest.writeString(mDescription);
   1258             dest.writeParcelable(mIntent, flags);
   1259             dest.writeString(mResourcePackageName);
   1260             dest.writeInt(mDrawableResource);
   1261             dest.writeParcelable(mIconUri, flags);
   1262             dest.writeInt(mChecked ? 1 : 0);
   1263             dest.writeInt(mMultilineDescription ? 1 : 0);
   1264             dest.writeInt(mCheckSetId);
   1265         }
   1266     }
   1267 }
   1268