1/*
2 * Copyright (C) 2016 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.deskclock;
18
19import android.app.Fragment;
20import android.app.FragmentManager;
21import android.app.FragmentTransaction;
22import android.support.v13.app.FragmentCompat;
23import android.support.v4.view.PagerAdapter;
24import android.util.ArrayMap;
25import android.view.View;
26import android.view.ViewGroup;
27
28import com.android.deskclock.uidata.UiDataModel;
29
30import java.util.Map;
31
32/**
33 * This adapter produces the DeskClockFragments that are the content of the DeskClock tabs. The
34 * adapter presents the tabs in LTR and RTL order depending on the text layout direction for the
35 * current locale. To prevent issues when switching between LTR and RTL, fragments are registered
36 * with the manager using position-independent tags, which is an important departure from
37 * FragmentPagerAdapter.
38 */
39final class FragmentTabPagerAdapter extends PagerAdapter {
40
41    private final DeskClock mDeskClock;
42
43    /** The manager into which fragments are added. */
44    private final FragmentManager mFragmentManager;
45
46    /** A fragment cache that can be accessed before {@link #instantiateItem} is called. */
47    private final Map<UiDataModel.Tab, DeskClockFragment> mFragmentCache;
48
49    /** The active fragment transaction if one exists. */
50    private FragmentTransaction mCurrentTransaction;
51
52    /** The current fragment displayed to the user. */
53    private Fragment mCurrentPrimaryItem;
54
55    FragmentTabPagerAdapter(DeskClock deskClock) {
56        mDeskClock = deskClock;
57        mFragmentCache = new ArrayMap<>(getCount());
58        mFragmentManager = deskClock.getFragmentManager();
59    }
60
61    @Override
62    public int getCount() {
63        return UiDataModel.getUiDataModel().getTabCount();
64    }
65
66    /**
67     * @param position the left-to-right index of the fragment to be returned
68     * @return the fragment displayed at the given {@code position}
69     */
70    DeskClockFragment getDeskClockFragment(int position) {
71        // Fetch the tab the UiDataModel reports for the position.
72        final UiDataModel.Tab tab = UiDataModel.getUiDataModel().getTabAt(position);
73
74        // First check the local cache for the fragment.
75        DeskClockFragment fragment = mFragmentCache.get(tab);
76        if (fragment != null) {
77            return fragment;
78        }
79
80        // Next check the fragment manager; relevant when app is rebuilt after locale changes
81        // because this adapter will be new and mFragmentCache will be empty, but the fragment
82        // manager will retain the Fragments built on original application launch.
83        fragment = (DeskClockFragment) mFragmentManager.findFragmentByTag(tab.name());
84        if (fragment != null) {
85            fragment.setFabContainer(mDeskClock);
86            mFragmentCache.put(tab, fragment);
87            return fragment;
88        }
89
90        // Otherwise, build the fragment from scratch.
91        final String fragmentClassName = tab.getFragmentClassName();
92        fragment = (DeskClockFragment) Fragment.instantiate(mDeskClock, fragmentClassName);
93        fragment.setFabContainer(mDeskClock);
94        mFragmentCache.put(tab, fragment);
95        return fragment;
96    }
97
98    @Override
99    public void startUpdate(ViewGroup container) {
100        if (container.getId() == View.NO_ID) {
101            throw new IllegalStateException("ViewPager with adapter " + this + " has no id");
102        }
103    }
104
105    @Override
106    public Object instantiateItem(ViewGroup container, int position) {
107        if (mCurrentTransaction == null) {
108            mCurrentTransaction = mFragmentManager.beginTransaction();
109        }
110
111        // Use the fragment located in the fragment manager if one exists.
112        final UiDataModel.Tab tab = UiDataModel.getUiDataModel().getTabAt(position);
113        Fragment fragment = mFragmentManager.findFragmentByTag(tab.name());
114        if (fragment != null) {
115            mCurrentTransaction.attach(fragment);
116        } else {
117            fragment = getDeskClockFragment(position);
118            mCurrentTransaction.add(container.getId(), fragment, tab.name());
119        }
120
121        if (fragment != mCurrentPrimaryItem) {
122            FragmentCompat.setMenuVisibility(fragment, false);
123            FragmentCompat.setUserVisibleHint(fragment, false);
124        }
125
126        return fragment;
127    }
128
129    @Override
130    public void destroyItem(ViewGroup container, int position, Object object) {
131        if (mCurrentTransaction == null) {
132            mCurrentTransaction = mFragmentManager.beginTransaction();
133        }
134        final DeskClockFragment fragment = (DeskClockFragment) object;
135        fragment.setFabContainer(null);
136        mCurrentTransaction.detach(fragment);
137    }
138
139    @Override
140    public void setPrimaryItem(ViewGroup container, int position, Object object) {
141        final Fragment fragment = (Fragment) object;
142        if (fragment != mCurrentPrimaryItem) {
143            if (mCurrentPrimaryItem != null) {
144                FragmentCompat.setMenuVisibility(mCurrentPrimaryItem, false);
145                FragmentCompat.setUserVisibleHint(mCurrentPrimaryItem, false);
146            }
147            if (fragment != null) {
148                FragmentCompat.setMenuVisibility(fragment, true);
149                FragmentCompat.setUserVisibleHint(fragment, true);
150            }
151            mCurrentPrimaryItem = fragment;
152        }
153    }
154
155    @Override
156    public void finishUpdate(ViewGroup container) {
157        if (mCurrentTransaction != null) {
158            mCurrentTransaction.commitAllowingStateLoss();
159            mCurrentTransaction = null;
160            mFragmentManager.executePendingTransactions();
161        }
162    }
163
164    @Override
165    public boolean isViewFromObject(View view, Object object) {
166        return ((Fragment) object).getView() == view;
167    }
168}