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.old;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.app.Activity;
23import android.content.Intent;
24import android.graphics.drawable.ColorDrawable;
25import android.net.Uri;
26import android.support.v4.view.ViewCompat;
27import android.view.View;
28import android.view.ViewGroup;
29import android.view.ViewGroup.LayoutParams;
30import android.view.ViewTreeObserver;
31import android.view.animation.DecelerateInterpolator;
32import android.view.animation.Interpolator;
33import android.widget.ImageView;
34import android.widget.TextView;
35
36import com.android.tv.settings.R;
37import com.android.tv.settings.util.TransitionImage;
38import com.android.tv.settings.util.TransitionImageAnimation;
39import com.android.tv.settings.util.UriUtils;
40import com.android.tv.settings.widget.FrameLayoutWithShadows;
41
42import java.util.List;
43
44/**
45 * This class exists to make extending both v4 DialogFragments and regular DialogFragments easy
46 */
47public class BaseDialogFragment {
48
49    public static final int ANIMATE_IN_DURATION = 250;
50    public static final int ANIMATE_DELAY = 550;
51    public static final int SLIDE_IN_DISTANCE = 120;
52
53    public static final String TAG_CONTENT = "content";
54    public static final String TAG_ACTION = "action";
55
56    public int mContentAreaId = R.id.content_fragment;
57    public int mActionAreaId = R.id.action_fragment;
58
59    public FrameLayoutWithShadows mShadowLayer;
60    public boolean mFirstOnStart = true;
61    public boolean mIntroAnimationInProgress = false;
62
63    private final LiteFragment mFragment;
64
65    // Related to activity entry transition
66    public ColorDrawable mBgDrawable = new ColorDrawable();
67
68    public BaseDialogFragment(LiteFragment fragment) {
69        mFragment = fragment;
70    }
71
72    public void onActionClicked(Activity activity, Action action) {
73        if (activity instanceof ActionAdapter.Listener) {
74            ((ActionAdapter.Listener) activity).onActionClicked(action);
75        } else {
76            Intent intent = action.getIntent();
77            if (intent != null) {
78                activity.startActivity(intent);
79                activity.finish();
80            }
81        }
82    }
83
84    public void disableEntryAnimation() {
85        mFirstOnStart = false;
86    }
87
88    /**
89     * This method sets the layout property of this class. <br/>
90     * Activities extending {@link DialogFragment} should call this method
91     * before calling {@link #onCreate(Bundle)} if they want to have a
92     * custom view.
93     *
94     * @param contentAreaId id of the content area
95     * @param actionAreaId id of the action area
96     */
97    public void setLayoutProperties(int contentAreaId, int actionAreaId) {
98        mContentAreaId = contentAreaId;
99        mActionAreaId = actionAreaId;
100    }
101
102    public void performEntryTransition(final Activity activity, final ViewGroup contentView,
103            int iconResourceId, Uri iconResourceUri, final ImageView icon, final TextView title,
104            final TextView description, final TextView breadcrumb) {
105        // Pull out the root layout of the dialog and set the background drawable, to be
106        // faded in during the transition.
107        final ViewGroup twoPane = (ViewGroup) contentView.getChildAt(0);
108        twoPane.setVisibility(View.INVISIBLE);
109
110        // If the appropriate data is embedded in the intent and there is an icon specified
111        // in the content fragment, we animate the icon from its initial position to the final
112        // position. Otherwise, we perform a simpler transition in which the ActionFragment
113        // slides in and the ContentFragment text fields slide in.
114        mIntroAnimationInProgress = true;
115        List<TransitionImage> images = TransitionImage.readMultipleFromIntent(
116                activity, activity.getIntent());
117        TransitionImageAnimation ltransitionAnimation = null;
118        final Uri iconUri;
119        final int color;
120        if (images != null && images.size() > 0) {
121            if (iconResourceId != 0) {
122                iconUri = Uri.parse(UriUtils.getAndroidResourceUri(
123                        activity, iconResourceId));
124            } else if (iconResourceUri != null) {
125                iconUri = iconResourceUri;
126            } else {
127                iconUri = null;
128            }
129            TransitionImage src = images.get(0);
130            color = src.getBackground();
131            if (iconUri != null) {
132                ltransitionAnimation = new TransitionImageAnimation(contentView);
133                ltransitionAnimation.addTransitionSource(src);
134                ltransitionAnimation.transitionDurationMs(ANIMATE_IN_DURATION)
135                        .transitionStartDelayMs(0)
136                        .interpolator(new DecelerateInterpolator(1f));
137            }
138        } else {
139            iconUri = null;
140            color = 0;
141        }
142        final TransitionImageAnimation transitionAnimation = ltransitionAnimation;
143
144        // Fade out the old activity, and hard cut the new activity.
145        activity.overridePendingTransition(R.anim.hard_cut_in, R.anim.fade_out);
146
147        int bgColor = mFragment.getResources().getColor(R.color.dialog_activity_background);
148        mBgDrawable.setColor(bgColor);
149        mBgDrawable.setAlpha(0);
150        twoPane.setBackground(mBgDrawable);
151
152        // If we're animating the icon, we create a new ImageView in which to place the embedded
153        // bitmap. We place it in the root layout to match its location in the previous activity.
154        mShadowLayer = (FrameLayoutWithShadows) twoPane.findViewById(R.id.shadow_layout);
155        if (transitionAnimation != null) {
156            transitionAnimation.listener(new TransitionImageAnimation.Listener() {
157                @Override
158                public void onRemovedView(TransitionImage src, TransitionImage dst) {
159                    if (icon != null) {
160                        //want to make sure that users still see at least the source image
161                        // if the dst image is too large to finish downloading before the
162                        // animation is done. Check if the icon is not visible. This mean
163                        // BaseContentFragement have not finish downloading the image yet.
164                        if (icon.getVisibility() != View.VISIBLE) {
165                            icon.setImageDrawable(src.getBitmap());
166                            int intrinsicWidth = icon.getDrawable().getIntrinsicWidth();
167                            LayoutParams lp = icon.getLayoutParams();
168                            lp.height = lp.width * icon.getDrawable().getIntrinsicHeight()
169                                    / intrinsicWidth;
170                            icon.setVisibility(View.VISIBLE);
171                        }
172                        icon.setAlpha(1f);
173                    }
174                    if (mShadowLayer != null) {
175                        mShadowLayer.setShadowsAlpha(1f);
176                    }
177                    onIntroAnimationFinished();
178                }
179            });
180            icon.setAlpha(0f);
181            if (mShadowLayer != null) {
182                mShadowLayer.setShadowsAlpha(0f);
183            }
184        }
185
186        // We need to defer the remainder of the animation preparation until the first
187        // layout has occurred, as we don't yet know the final location of the icon.
188        twoPane.getViewTreeObserver().addOnGlobalLayoutListener(
189                new ViewTreeObserver.OnGlobalLayoutListener() {
190            @Override
191            public void onGlobalLayout() {
192                twoPane.getViewTreeObserver().removeOnGlobalLayoutListener(this);
193                // if we buildLayer() at this time,  the texture is actually not created
194                // delay a little so we can make sure all hardware layer is created before
195                // animation, in that way we can avoid the jittering of start animation
196                twoPane.postOnAnimationDelayed(mEntryAnimationRunnable, ANIMATE_DELAY);
197            }
198
199            final Runnable mEntryAnimationRunnable = new Runnable() {
200                @Override
201                public void run() {
202                    if (!mFragment.isAdded()) {
203                        // We have been detached before this could run, so just bail
204                        return;
205                    }
206
207                    twoPane.setVisibility(View.VISIBLE);
208                    final int secondaryDelay = SLIDE_IN_DISTANCE;
209
210                    // Fade in the activity background protection
211                    ObjectAnimator oa = ObjectAnimator.ofInt(mBgDrawable, "alpha", 255);
212                    oa.setDuration(ANIMATE_IN_DURATION);
213                    oa.setStartDelay(secondaryDelay);
214                    oa.setInterpolator(new DecelerateInterpolator(1.0f));
215                    oa.start();
216
217                    View actionFragmentView = activity.findViewById(mActionAreaId);
218                    boolean isRtl = ViewCompat.getLayoutDirection(contentView) ==
219                            ViewCompat.LAYOUT_DIRECTION_RTL;
220                    int startDist = isRtl ? SLIDE_IN_DISTANCE : -SLIDE_IN_DISTANCE;
221                    int endDist = isRtl ? -actionFragmentView.getMeasuredWidth() :
222                            actionFragmentView.getMeasuredWidth();
223
224                    // Fade in and slide in the ContentFragment TextViews from the start.
225                    prepareAndAnimateView(title, 0, startDist,
226                            secondaryDelay, ANIMATE_IN_DURATION,
227                            new DecelerateInterpolator(1.0f),
228                            false);
229                    prepareAndAnimateView(breadcrumb, 0, startDist,
230                            secondaryDelay, ANIMATE_IN_DURATION,
231                            new DecelerateInterpolator(1.0f),
232                            false);
233                    prepareAndAnimateView(description, 0,
234                            startDist,
235                            secondaryDelay, ANIMATE_IN_DURATION,
236                            new DecelerateInterpolator(1.0f),
237                            false);
238
239                    // Fade in and slide in the ActionFragment from the end.
240                    prepareAndAnimateView(actionFragmentView, 0,
241                            endDist, secondaryDelay,
242                            ANIMATE_IN_DURATION, new DecelerateInterpolator(1.0f),
243                            false);
244
245                    if (icon != null && transitionAnimation != null) {
246                        // now we get the icon view in place,  update the transition target
247                        TransitionImage target = new TransitionImage();
248                        target.setUri(iconUri);
249                        target.createFromImageView(icon);
250                        if (icon.getBackground() instanceof ColorDrawable) {
251                            ColorDrawable d = (ColorDrawable) icon.getBackground();
252                            target.setBackground(d.getColor());
253                        }
254                        transitionAnimation.addTransitionTarget(target);
255                        transitionAnimation.startTransition();
256                    } else if (icon != null) {
257                        prepareAndAnimateView(icon, 0, startDist,
258                                secondaryDelay, ANIMATE_IN_DURATION,
259                                new DecelerateInterpolator(1.0f), true /* is the icon */);
260                        if (mShadowLayer != null) {
261                            mShadowLayer.setShadowsAlpha(0f);
262                        }
263                    }
264                }
265            };
266        });
267    }
268
269    /**
270     * Animates a view.
271     *
272     * @param v              view to animate
273     * @param initAlpha      initial alpha
274     * @param initTransX     initial translation in the X
275     * @param delay          delay in ms
276     * @param duration       duration in ms
277     * @param interpolator   interpolator to be used, can be null
278     * @param isIcon         if {@code true}, this is the main icon being moved
279     */
280    public void prepareAndAnimateView(final View v, float initAlpha, float initTransX, int delay,
281            int duration, Interpolator interpolator, final boolean isIcon) {
282        if (v != null && v.getWindowToken() != null) {
283            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
284            v.buildLayer();
285            v.setAlpha(initAlpha);
286            v.setTranslationX(initTransX);
287            v.animate().alpha(1f).translationX(0).setDuration(duration).setStartDelay(delay);
288            if (interpolator != null) {
289                v.animate().setInterpolator(interpolator);
290            }
291            v.animate().setListener(new AnimatorListenerAdapter() {
292                @Override
293                public void onAnimationEnd(Animator animation) {
294                    v.setLayerType(View.LAYER_TYPE_NONE, null);
295                    if (isIcon) {
296                        if (mShadowLayer != null) {
297                            mShadowLayer.setShadowsAlpha(1f);
298                        }
299                        onIntroAnimationFinished();
300                    }
301                }
302            });
303            v.animate().start();
304        }
305    }
306
307    /**
308     * Called when intro animation is finished.
309     * <p>
310     * If a subclass is going to alter the view, should wait until this is called.
311     */
312    public void onIntroAnimationFinished() {
313        mIntroAnimationInProgress = false;
314    }
315
316    public boolean isIntroAnimationInProgress() {
317        return mIntroAnimationInProgress;
318    }
319
320    public ColorDrawable getBackgroundDrawable() {
321        return mBgDrawable;
322    }
323
324    public void setBackgroundDrawable(ColorDrawable drawable) {
325        mBgDrawable = drawable;
326    }
327
328}
329