1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.widget;
15
16import android.os.Handler;
17import android.support.v4.app.ActivityCompat;
18import android.support.v4.app.SharedElementCallback;
19import android.support.v4.view.ViewCompat;
20import android.support.v17.leanback.R;
21import android.support.v17.leanback.transition.TransitionListener;
22import android.support.v17.leanback.transition.TransitionHelper;
23import android.support.v17.leanback.widget.DetailsOverviewRowPresenter.ViewHolder;
24import android.app.Activity;
25import android.content.Intent;
26import android.graphics.Matrix;
27import android.text.TextUtils;
28import android.util.Log;
29import android.view.View;
30import android.view.ViewGroup;
31import android.view.View.MeasureSpec;
32import android.widget.ImageView;
33import android.widget.ImageView.ScaleType;
34
35import java.util.List;
36
37final class DetailsOverviewSharedElementHelper extends SharedElementCallback {
38
39    private static final String TAG = "DetailsOverviewSharedElementHelper";
40    private static final boolean DEBUG = false;
41
42    private ViewHolder mViewHolder;
43    private Activity mActivityToRunTransition;
44    private boolean mStartedPostpone;
45    private String mSharedElementName;
46    private int mRightPanelWidth;
47    private int mRightPanelHeight;
48
49    private ScaleType mSavedScaleType;
50    private Matrix mSavedMatrix;
51
52    private boolean hasImageViewScaleChange(View snapshotView) {
53        return snapshotView instanceof ImageView;
54    }
55
56    private void saveImageViewScale() {
57        if (mSavedScaleType == null) {
58            // only save first time after initialize/restoreImageViewScale()
59            ImageView imageView = mViewHolder.mImageView;
60            mSavedScaleType = imageView.getScaleType();
61            mSavedMatrix = mSavedScaleType == ScaleType.MATRIX ? imageView.getMatrix() : null;
62            if (DEBUG) {
63                Log.d(TAG, "saveImageViewScale: "+mSavedScaleType);
64            }
65        }
66    }
67
68    private static void updateImageViewAfterScaleTypeChange(ImageView imageView) {
69        // enforcing imageView to update its internal bounds/matrix immediately
70        imageView.measure(
71                MeasureSpec.makeMeasureSpec(imageView.getMeasuredWidth(), MeasureSpec.EXACTLY),
72                MeasureSpec.makeMeasureSpec(imageView.getMeasuredHeight(), MeasureSpec.EXACTLY));
73        imageView.layout(imageView.getLeft(), imageView.getTop(),
74                imageView.getRight(), imageView.getBottom());
75    }
76
77    private void changeImageViewScale(View snapshotView) {
78        ImageView snapshotImageView = (ImageView) snapshotView;
79        ImageView imageView = mViewHolder.mImageView;
80        if (DEBUG) {
81            Log.d(TAG, "changeImageViewScale to "+snapshotImageView.getScaleType());
82        }
83        imageView.setScaleType(snapshotImageView.getScaleType());
84        if (snapshotImageView.getScaleType() == ScaleType.MATRIX) {
85            imageView.setImageMatrix(snapshotImageView.getImageMatrix());
86        }
87        updateImageViewAfterScaleTypeChange(imageView);
88    }
89
90    private void restoreImageViewScale() {
91        if (mSavedScaleType != null) {
92            if (DEBUG) {
93                Log.d(TAG, "restoreImageViewScale to "+mSavedScaleType);
94            }
95            ImageView imageView = mViewHolder.mImageView;
96            imageView.setScaleType(mSavedScaleType);
97            if (mSavedScaleType == ScaleType.MATRIX) {
98                imageView.setImageMatrix(mSavedMatrix);
99            }
100            // only restore once unless another save happens
101            mSavedScaleType = null;
102            updateImageViewAfterScaleTypeChange(imageView);
103        }
104    }
105
106    @Override
107    public void onSharedElementStart(List<String> sharedElementNames,
108            List<View> sharedElements, List<View> sharedElementSnapshots) {
109        if (DEBUG) {
110            Log.d(TAG, "onSharedElementStart " + mActivityToRunTransition);
111        }
112        if (sharedElements.size() < 1) {
113            return;
114        }
115        View overviewView = sharedElements.get(0);
116        if (mViewHolder == null || mViewHolder.mOverviewFrame != overviewView) {
117            return;
118        }
119        View snapshot = sharedElementSnapshots.get(0);
120        if (hasImageViewScaleChange(snapshot)) {
121            saveImageViewScale();
122            changeImageViewScale(snapshot);
123        }
124        View imageView = mViewHolder.mImageView;
125        final int width = overviewView.getWidth();
126        final int height = overviewView.getHeight();
127        imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
128                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
129        imageView.layout(0, 0, width, height);
130        final View rightPanel = mViewHolder.mRightPanel;
131        if (mRightPanelWidth != 0 && mRightPanelHeight != 0) {
132            rightPanel.measure(MeasureSpec.makeMeasureSpec(mRightPanelWidth, MeasureSpec.EXACTLY),
133                    MeasureSpec.makeMeasureSpec(mRightPanelHeight, MeasureSpec.EXACTLY));
134            rightPanel.layout(width, rightPanel.getTop(), width + mRightPanelWidth,
135                    rightPanel.getTop() + mRightPanelHeight);
136        } else {
137            rightPanel.offsetLeftAndRight(width - rightPanel.getLeft());
138        }
139        mViewHolder.mActionsRow.setVisibility(View.INVISIBLE);
140        mViewHolder.mDetailsDescriptionFrame.setVisibility(View.INVISIBLE);
141    }
142
143    @Override
144    public void onSharedElementEnd(List<String> sharedElementNames,
145            List<View> sharedElements, List<View> sharedElementSnapshots) {
146        if (DEBUG) {
147            Log.d(TAG, "onSharedElementEnd " + mActivityToRunTransition);
148        }
149        if (sharedElements.size() < 1) {
150            return;
151        }
152        View overviewView = sharedElements.get(0);
153        if (mViewHolder == null || mViewHolder.mOverviewFrame != overviewView) {
154            return;
155        }
156        restoreImageViewScale();
157        // temporary let action row take focus so we defer button background animation
158        mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
159        mViewHolder.mActionsRow.setVisibility(View.VISIBLE);
160        mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
161        mViewHolder.mDetailsDescriptionFrame.setVisibility(View.VISIBLE);
162    }
163
164    void setSharedElementEnterTransition(Activity activity, String sharedElementName,
165            long timeoutMs) {
166        if (activity == null && !TextUtils.isEmpty(sharedElementName) ||
167                activity != null && TextUtils.isEmpty(sharedElementName)) {
168            throw new IllegalArgumentException();
169        }
170        if (activity == mActivityToRunTransition &&
171                TextUtils.equals(sharedElementName, mSharedElementName)) {
172            return;
173        }
174        if (mActivityToRunTransition != null) {
175            ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, null);
176        }
177        mActivityToRunTransition = activity;
178        mSharedElementName = sharedElementName;
179        if (DEBUG) {
180            Log.d(TAG, "postponeEnterTransition " + mActivityToRunTransition);
181        }
182        ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, this);
183        ActivityCompat.postponeEnterTransition(mActivityToRunTransition);
184        if (timeoutMs > 0) {
185            new Handler().postDelayed(new Runnable() {
186                @Override
187                public void run() {
188                    if (mStartedPostpone) {
189                        return;
190                    }
191                    if (DEBUG) {
192                        Log.d(TAG, "timeout " + mActivityToRunTransition);
193                    }
194                    startPostponedEnterTransition();
195                }
196            }, timeoutMs);
197        }
198    }
199
200    void onBindToDrawable(ViewHolder vh) {
201        if (DEBUG) {
202            Log.d(TAG, "onBindToDrawable, could start transition of " + mActivityToRunTransition);
203        }
204        if (mViewHolder != null) {
205            if (DEBUG) {
206                Log.d(TAG, "rebind? clear transitionName on current viewHolder "
207                        + mViewHolder.mOverviewFrame);
208            }
209            ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, null);
210        }
211        // After we got a image drawable,  we can determine size of right panel.
212        // We want right panel to have fixed size so that the right panel don't change size
213        // when the overview is layout as a small bounds in transition.
214        mViewHolder = vh;
215        mViewHolder.mRightPanel.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
216            @Override
217            public void onLayoutChange(View v, int left, int top, int right, int bottom,
218                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
219                mViewHolder.mRightPanel.removeOnLayoutChangeListener(this);
220                mRightPanelWidth = mViewHolder.mRightPanel.getWidth();
221                mRightPanelHeight = mViewHolder.mRightPanel.getHeight();
222                if (DEBUG) {
223                    Log.d(TAG, "onLayoutChange records size of right panel as "
224                            + mRightPanelWidth + ", "+ mRightPanelHeight);
225                }
226            }
227        });
228        mViewHolder.mRightPanel.postOnAnimation(new Runnable() {
229            @Override
230            public void run() {
231                if (DEBUG) {
232                    Log.d(TAG, "setTransitionName "+mViewHolder.mOverviewFrame);
233                }
234                ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, mSharedElementName);
235                Object transition = TransitionHelper.getSharedElementEnterTransition(
236                        mActivityToRunTransition.getWindow());
237                if (transition != null) {
238                    TransitionHelper.addTransitionListener(transition, new TransitionListener() {
239                        @Override
240                        public void onTransitionEnd(Object transition) {
241                            if (DEBUG) {
242                                Log.d(TAG, "onTransitionEnd " + mActivityToRunTransition);
243                            }
244                            // after transition if the action row still focused, transfer
245                            // focus to its children
246                            if (mViewHolder.mActionsRow.isFocused()) {
247                                mViewHolder.mActionsRow.requestFocus();
248                            }
249                            TransitionHelper.removeTransitionListener(transition, this);
250                        }
251                    });
252                }
253                startPostponedEnterTransition();
254            }
255        });
256    }
257
258    private void startPostponedEnterTransition() {
259        if (!mStartedPostpone) {
260            if (DEBUG) {
261                Log.d(TAG, "startPostponedEnterTransition " + mActivityToRunTransition);
262            }
263            ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition);
264            mStartedPostpone = true;
265        }
266    }
267}
268