1/*
2 * Copyright (C) 2015 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.common.ui.setup;
18
19import android.app.Activity;
20import android.app.Fragment;
21import android.app.FragmentTransaction;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.Looper;
25import android.os.Message;
26import android.support.annotation.NonNull;
27import android.transition.Transition;
28import android.transition.TransitionInflater;
29import android.view.View;
30import android.view.ViewTreeObserver.OnPreDrawListener;
31
32import com.android.tv.common.R;
33import com.android.tv.common.WeakHandler;
34import com.android.tv.common.ui.setup.animation.SetupAnimationHelper;
35
36/**
37 * Setup activity for onboarding screens or TIS.
38 *
39 * <p>The inherited class should add theme {@code Theme.Setup.GuidedStep} to its definition in
40 * AndroidManifest.xml.
41 */
42public abstract class SetupActivity extends Activity implements OnActionClickListener {
43    private static final int MSG_EXECUTE_ACTION = 1;
44
45    private boolean mShowInitialFragment = true;
46    private long mFragmentTransitionDuration;
47    private final Handler mHandler = new SetupActivityHandler(this);
48
49    @Override
50    protected void onCreate(Bundle savedInstanceState) {
51        super.onCreate(savedInstanceState);
52        setContentView(R.layout.activity_setup);
53        mFragmentTransitionDuration = getResources().getInteger(
54                R.integer.setup_fragment_transition_duration);
55        // Show initial fragment only when the saved state is not restored, because the last
56        // fragment is restored if savesInstanceState is not null.
57        if (savedInstanceState == null) {
58            // This is the workaround to show the first fragment with delay to show the fragment
59            // enter transition. See http://b/26255145
60            getWindow().getDecorView().getViewTreeObserver().addOnPreDrawListener(
61                    new OnPreDrawListener() {
62                        @Override
63                        public boolean onPreDraw() {
64                            getWindow().getDecorView().getViewTreeObserver()
65                                    .removeOnPreDrawListener(this);
66                            showInitialFragment();
67                            return true;
68                        }
69                    });
70        } else {
71            mShowInitialFragment = false;
72        }
73    }
74
75    /**
76     * The inherited class should provide the initial fragment to show.
77     *
78     * <p>If this method returns {@code null} during {@link #onCreate}, then call
79     * {@link #showInitialFragment} explicitly later with non null initial fragment.
80     */
81    protected abstract Fragment onCreateInitialFragment();
82
83    /**
84     * Shows the initial fragment.
85     *
86     * <p>The inherited class can call this method later explicitly if it doesn't want the initial
87     * fragment to be shown in onCreate().
88     */
89    protected void showInitialFragment() {
90        if (!mShowInitialFragment) {
91            return;
92        }
93        Fragment fragment = onCreateInitialFragment();
94        if (fragment != null) {
95            showFragment(fragment, false);
96            mShowInitialFragment = false;
97        }
98    }
99
100    /**
101     * Shows the given fragment.
102     */
103    protected FragmentTransaction showFragment(Fragment fragment, boolean addToBackStack) {
104        FragmentTransaction ft = getFragmentManager().beginTransaction();
105        if (fragment instanceof SetupFragment) {
106            int[] sharedElements = ((SetupFragment) fragment).getSharedElementIds();
107            if (sharedElements != null && sharedElements.length > 0) {
108                Transition sharedTransition = TransitionInflater.from(this)
109                        .inflateTransition(R.transition.transition_action_background);
110                sharedTransition.setDuration(getSharedElementTransitionDuration());
111                SetupAnimationHelper.applyAnimationTimeScale(sharedTransition);
112                fragment.setSharedElementEnterTransition(sharedTransition);
113                fragment.setSharedElementReturnTransition(sharedTransition);
114                for (int id : sharedElements) {
115                    View sharedView = findViewById(id);
116                    if (sharedView != null) {
117                        ft.addSharedElement(sharedView, sharedView.getTransitionName());
118                    }
119                }
120            }
121        }
122        String tag = fragment.getClass().getCanonicalName();
123        if (addToBackStack) {
124            ft.addToBackStack(tag);
125        }
126        ft.replace(R.id.fragment_container, fragment, tag).commit();
127
128        return ft;
129    }
130
131    @Override
132    public void onActionClick(String category, int actionId) {
133        if (mHandler.hasMessages(MSG_EXECUTE_ACTION)) {
134            return;
135        }
136        executeAction(category, actionId);
137    }
138
139    protected void executeActionWithDelay(Runnable action, int delayMs) {
140        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_EXECUTE_ACTION, action), delayMs);
141    }
142
143    // Override this method if the inherited class wants to handle the action.
144    protected void executeAction(String category, int actionId) { }
145
146    /**
147     * Returns the duration of the shared element transition.
148     *
149     * <p>It's (exit transition) + (delayed animation) + (enter transition).
150     */
151    private long getSharedElementTransitionDuration() {
152        return (mFragmentTransitionDuration + SetupAnimationHelper.DELAY_BETWEEN_SIBLINGS_MS) * 2;
153    }
154
155    private static class SetupActivityHandler extends WeakHandler<SetupActivity> {
156        SetupActivityHandler(SetupActivity activity) {
157            // Should run on main thread because onAc3SupportChanged will be called on main thread.
158            super(Looper.getMainLooper(), activity);
159        }
160
161        @Override
162        protected void handleMessage(Message msg, @NonNull SetupActivity activity) {
163            if (msg.what == MSG_EXECUTE_ACTION) {
164                ((Runnable) msg.obj).run();
165            }
166        }
167    }
168}
169