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