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.app.Activity;
20import android.app.Fragment;
21import android.app.FragmentManager;
22import android.app.FragmentManager.OnBackStackChangedListener;
23import android.app.FragmentTransaction;
24import android.content.Context;
25import android.content.Intent;
26import android.graphics.Color;
27import android.graphics.drawable.ColorDrawable;
28import android.net.Uri;
29import android.os.Build;
30import android.os.Bundle;
31import android.view.LayoutInflater;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.animation.Interpolator;
35
36import com.android.tv.settings.R;
37
38import java.util.ArrayList;
39
40/**
41 * A DialogActivity has 2 fragments, a content fragment and a list fragment.
42 * <p>
43 * Subclasses should override to supply the content fragment and list items.
44 * <p>
45 * The DialogActivity will handle animating in and out.
46 * <p>
47 * This class will use a default layout, but a custom layout can be provided by
48 * calling {@link #setLayoutProperties}
49 */
50public abstract class DialogActivity extends Activity
51        implements ActionAdapter.Listener, OnBackStackChangedListener {
52
53    /**
54     * Dialog Content Fragment title.
55     */
56    public static final String EXTRA_DIALOG_TITLE = "dialog_title";
57
58    /**
59     * Dialog Content Fragment breadcrumb.
60     */
61    public static final String EXTRA_DIALOG_BREADCRUMB = "dialog_breadcrumb";
62
63    /**
64     * Dialog Content Fragment description.
65     */
66    public static final String EXTRA_DIALOG_DESCRIPTION = "dialog_description";
67
68    /**
69     * Dialog Content Fragment image uri.
70     */
71    public static final String EXTRA_DIALOG_IMAGE_URI = "dialog_image_uri";
72
73    /**
74     * Dialog Content Fragment image background color
75     */
76    public static final String EXTRA_DIALOG_IMAGE_BACKGROUND_COLOR
77            = "dialog_image_background_color";
78
79    /**
80     * Dialog Action Fragment actions starting index.
81     */
82    public static final String EXTRA_DIALOG_ACTIONS_START_INDEX = "dialog_actions_start_index";
83
84    /**
85     * Dialog Action Fragment actions.
86     */
87    public static final String EXTRA_PARCELABLE_ACTIONS = "parcelable_actions";
88
89    /**
90     * Whether DialogActivity should create Content Fragment and Action Fragment from extras.
91     */
92    public static final String EXTRA_CREATE_FRAGMENT_FROM_EXTRA = "create_fragment_from_extra";
93
94    public static final String TAG_DIALOG = "tag_dialog";
95    public static final String BACKSTACK_NAME_DIALOG = "backstack_name_dialog";
96    public static final String KEY_BACKSTACK_COUNT = "backstack_count";
97
98    protected static final int ANIMATE_IN_DURATION = 250;
99    protected static final int ANIMATE_DELAY = 550;
100    protected static final int SLIDE_IN_STAGGER = 100;
101    protected static final int SLIDE_IN_DISTANCE = 120;
102
103    private DialogFragment mDialogFragment;
104    private int mLayoutResId = R.layout.two_pane_dialog_frame;
105    private View mContent;
106    private int mLastBackStackCount = 0;
107
108    public DialogActivity() {
109        mDialogFragment = new DialogFragment();
110        mDialogFragment.setActivity(this);
111    }
112
113    public static Intent createIntent(Context context, String title,
114            String breadcrumb, String description, String imageUri,
115            ArrayList<Action> actions) {
116        return createIntent(context, title, breadcrumb, description, imageUri,
117                Color.TRANSPARENT, actions);
118    }
119
120    public static Intent createIntent(Context context, String title,
121            String breadcrumb, String description, String imageUri,
122            int imageBackground, ArrayList<Action> actions) {
123        Intent intent = new Intent(context, DialogActivity.class);
124        intent.putExtra(EXTRA_DIALOG_TITLE, title);
125        intent.putExtra(EXTRA_DIALOG_BREADCRUMB, breadcrumb);
126        intent.putExtra(EXTRA_DIALOG_DESCRIPTION, description);
127        intent.putExtra(EXTRA_DIALOG_IMAGE_URI, imageUri);
128        intent.putExtra(EXTRA_DIALOG_IMAGE_BACKGROUND_COLOR, imageBackground);
129        intent.putParcelableArrayListExtra(EXTRA_PARCELABLE_ACTIONS, actions);
130
131        return intent;
132    }
133
134    public static Intent createIntent(Context context, String title,
135            String breadcrumb, String description, String imageUri,
136            ArrayList<Action> actions, Class<? extends DialogActivity> activityClass) {
137        return createIntent(context, title, breadcrumb, description, imageUri, Color.TRANSPARENT,
138                actions, activityClass);
139    }
140
141    public static Intent createIntent(Context context, String title,
142            String breadcrumb, String description, String imageUri, int imageBackground,
143            ArrayList<Action> actions, Class<? extends DialogActivity> activityClass) {
144        Intent intent = new Intent(context, activityClass);
145        intent.putExtra(EXTRA_DIALOG_TITLE, title);
146        intent.putExtra(EXTRA_DIALOG_BREADCRUMB, breadcrumb);
147        intent.putExtra(EXTRA_DIALOG_DESCRIPTION, description);
148        intent.putExtra(EXTRA_DIALOG_IMAGE_URI, imageUri);
149        intent.putExtra(EXTRA_DIALOG_IMAGE_BACKGROUND_COLOR, imageBackground);
150        intent.putParcelableArrayListExtra(EXTRA_PARCELABLE_ACTIONS, actions);
151
152        return intent;
153    }
154
155    public static Intent createIntent(Context context, String title,
156            String breadcrumb, String description, String imageUri, int imageBackground,
157            ArrayList<Action> actions, Class<? extends DialogActivity> activityClass,
158            int startIndex) {
159        Intent intent = new Intent(context, activityClass);
160        intent.putExtra(EXTRA_DIALOG_TITLE, title);
161        intent.putExtra(EXTRA_DIALOG_BREADCRUMB, breadcrumb);
162        intent.putExtra(EXTRA_DIALOG_DESCRIPTION, description);
163        intent.putExtra(EXTRA_DIALOG_IMAGE_URI, imageUri);
164        intent.putExtra(EXTRA_DIALOG_IMAGE_BACKGROUND_COLOR, imageBackground);
165        intent.putParcelableArrayListExtra(EXTRA_PARCELABLE_ACTIONS, actions);
166        intent.putExtra(EXTRA_DIALOG_ACTIONS_START_INDEX, startIndex);
167
168        return intent;
169    }
170
171    public View getContentView() {
172        return mContent;
173    }
174
175    @Override
176    protected void onCreate(Bundle savedInstanceState) {
177        // TODO: replace these hardcoded values with the commented constants whenever Hangouts
178        // updates their manifest to build against JB MR2.
179        if (Build.VERSION.SDK_INT >= 18 /* Build.VERSION_CODES.JELLY_BEAN_MR2 */) {
180            getWindow().addFlags(0x02000000
181                    /* WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN */);
182        }
183        if(savedInstanceState != null) {
184            mLastBackStackCount = savedInstanceState.getInt(KEY_BACKSTACK_COUNT);
185        }
186
187        super.onCreate(savedInstanceState);
188        getFragmentManager().addOnBackStackChangedListener(this);
189
190        LayoutInflater helium = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
191        mContent = helium.inflate(mLayoutResId, null);
192        setContentView(mContent);
193        if (mLayoutResId == R.layout.two_pane_dialog_frame) {
194            helium.inflate(R.layout.dialog_container, (ViewGroup) mContent);
195            setDialogFragment(mDialogFragment);
196        }
197
198        Bundle bundle = getIntent().getExtras();
199        if (bundle != null) {
200            boolean createFragmentFromExtra = bundle.getBoolean(EXTRA_CREATE_FRAGMENT_FROM_EXTRA);
201            if (createFragmentFromExtra) {
202                // If intent bundle is not null, and flag indicates that should create fragments,
203                // set ContentFragment and ActionFragment using bundle extras.
204                String title = bundle.getString(EXTRA_DIALOG_TITLE);
205                String breadcrumb = bundle.getString(EXTRA_DIALOG_BREADCRUMB);
206                String description = bundle.getString(EXTRA_DIALOG_DESCRIPTION);
207                String imageUriStr = bundle.getString(EXTRA_DIALOG_IMAGE_URI);
208                Uri imageUri = Uri.parse(imageUriStr);
209                int backgroundColor = bundle.getInt(EXTRA_DIALOG_IMAGE_BACKGROUND_COLOR);
210
211                ArrayList<Action> actions =
212                        bundle.getParcelableArrayList(EXTRA_PARCELABLE_ACTIONS);
213
214                setContentFragment(ContentFragment.newInstance(title, breadcrumb,
215                        description, imageUri, backgroundColor));
216
217                setActionFragment(ActionFragment.newInstance(actions));
218            }
219        }
220    }
221
222    @Override
223    protected void onSaveInstanceState(Bundle savedInstanceState) {
224        super.onSaveInstanceState(savedInstanceState);
225        savedInstanceState.putInt(KEY_BACKSTACK_COUNT, mLastBackStackCount);
226    }
227
228    @Override
229    protected void onStart() {
230        super.onStart();
231        if (mLayoutResId == R.layout.two_pane_dialog_frame) {
232            getDialogFragment().performEntryTransition();
233        }
234    }
235
236    @Override
237    public void onBackStackChanged() {
238        int count = getFragmentManager().getBackStackEntryCount();
239        if (count > 0 && count < mLastBackStackCount && DialogActivity.BACKSTACK_NAME_DIALOG.equals(
240                getFragmentManager().getBackStackEntryAt(count - 1).getName())) {
241            getFragmentManager().popBackStack();
242        }
243        mLastBackStackCount = count;
244    }
245
246    @Override
247    public void onActionClicked(Action action) {
248        Intent intent = action.getIntent();
249        if (intent != null) {
250            startActivity(intent);
251            finish();
252        }
253    }
254
255    /**
256     * Disables the entry animation that normally happens onStart().
257     */
258    protected void disableEntryAnimation() {
259        getDialogFragment().disableEntryAnimation();
260    }
261
262    /**
263     * This method sets the layout property of this class. <br/>
264     * Activities extending {@link DialogActivity} should call this method
265     * before calling {@link #onCreate(Bundle)} if they want to have a
266     * custom view.
267     *
268     * @param layoutResId resource if of the activity layout
269     * @param contentAreaId id of the content area
270     * @param actionAreaId id of the action area
271     */
272    protected void setLayoutProperties(int layoutResId, int contentAreaId, int actionAreaId) {
273        mLayoutResId = layoutResId;
274        getDialogFragment().setLayoutProperties(contentAreaId, actionAreaId);
275    }
276
277    /**
278     * Animates a view.
279     *
280     * @param v              view to animate
281     * @param initAlpha      initial alpha
282     * @param initTransX     initial translation in the X
283     * @param delay          delay in ms
284     * @param duration       duration in ms
285     * @param interpolator   interpolator to be used, can be null
286     * @param isIcon         if {@code true}, this is the main icon being moved
287     */
288    protected void prepareAndAnimateView(final View v, float initAlpha, float initTransX, int delay,
289            int duration, Interpolator interpolator, final boolean isIcon) {
290        getDialogFragment().prepareAndAnimateView(
291                v, initAlpha, initTransX, delay, duration, interpolator, isIcon);
292    }
293
294    /**
295     * Called when intro animation is finished.
296     * <p>
297     * If a subclass is going to alter the view, should wait until this is called.
298     */
299    protected void onIntroAnimationFinished() {
300        getDialogFragment().onIntroAnimationFinished();
301    }
302
303    protected boolean isIntroAnimationInProgress() {
304        return getDialogFragment().isIntroAnimationInProgress();
305    }
306
307    protected ColorDrawable getBackgroundDrawable() {
308        return getDialogFragment().getBackgroundDrawable();
309    }
310
311    protected void setBackgroundDrawable(ColorDrawable drawable) {
312        getDialogFragment().setBackgroundDrawable(drawable);
313    }
314
315    /**
316     * Sets the content fragment into the view.
317     */
318    protected void setContentFragment(Fragment fragment) {
319        getDialogFragment().setContentFragment(fragment);
320    }
321
322    /**
323     * Sets the action fragment into the view.
324     * <p>
325     * If an action fragment currently exists, this will be added to the back stack.
326     */
327    protected void setActionFragment(Fragment fragment) {
328        getDialogFragment().setActionFragment(fragment);
329    }
330
331    /**
332     * Sets the action fragment into the view.
333     * <p>
334     * If addToBackStack is true, and action fragment currently exists,
335     * this will be added to the back stack.
336     */
337    protected void setActionFragment(Fragment fragment, boolean addToBackStack) {
338        getDialogFragment().setActionFragment(fragment, addToBackStack);
339    }
340
341    protected Fragment getActionFragment() {
342        return getDialogFragment().getActionFragment();
343    }
344
345    protected Fragment getContentFragment() {
346        return getDialogFragment().getContentFragment();
347    }
348
349    /**
350     * Set the content and action fragments in the same transaction.
351     * <p>
352     * If an action fragment currently exists, this will be added to the back stack.
353     */
354    protected void setContentAndActionFragments(Fragment contentFragment, Fragment actionFragment) {
355        getDialogFragment().setContentAndActionFragments(contentFragment, actionFragment);
356    }
357
358    /**
359     * Set the content and action fragments in the same transaction.
360     * <p>
361     * If addToBackStack and an action fragment currently exists,
362     * this will be added to the back stack.
363     */
364    protected void setContentAndActionFragments(Fragment contentFragment, Fragment actionFragment,
365            boolean addToBackStack) {
366        getDialogFragment().setContentAndActionFragments(
367                contentFragment, actionFragment, addToBackStack);
368    }
369
370    protected void setDialogFragment(DialogFragment fragment) {
371        setDialogFragment(fragment, true);
372    }
373
374    protected void setDialogFragment(DialogFragment fragment, boolean addToBackStack) {
375        mDialogFragment = fragment;
376        fragment.setActivity(this);
377        FragmentManager fm = getFragmentManager();
378        FragmentTransaction ft = fm.beginTransaction();
379        boolean hasDialog = fm.findFragmentByTag(DialogActivity.TAG_DIALOG) != null;
380        if (hasDialog) {
381            if (addToBackStack) {
382                ft.addToBackStack(DialogActivity.BACKSTACK_NAME_DIALOG);
383            }
384        }
385        ft.replace(R.id.dialog_fragment, fragment, DialogActivity.TAG_DIALOG);
386        ft.commit();
387    }
388
389    protected DialogFragment getDialogFragment() {
390        FragmentManager fm = getFragmentManager();
391        if (fm != null) {
392            DialogFragment fragment = (DialogFragment) fm.findFragmentByTag(TAG_DIALOG);
393            if (fragment != null) {
394                mDialogFragment = fragment;
395            }
396        }
397
398        return mDialogFragment;
399    }
400}
401