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 
17 package com.android.deskclock;
18 
19 import android.app.Fragment;
20 import android.app.FragmentManager;
21 import android.app.FragmentTransaction;
22 import androidx.legacy.app.FragmentCompat;
23 import androidx.viewpager.widget.PagerAdapter;
24 import android.util.ArrayMap;
25 import android.view.View;
26 import android.view.ViewGroup;
27 
28 import com.android.deskclock.uidata.UiDataModel;
29 
30 import 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  */
39 final 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 
FragmentTabPagerAdapter(DeskClock deskClock)55     FragmentTabPagerAdapter(DeskClock deskClock) {
56         mDeskClock = deskClock;
57         mFragmentCache = new ArrayMap<>(getCount());
58         mFragmentManager = deskClock.getFragmentManager();
59     }
60 
61     @Override
getCount()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      */
getDeskClockFragment(int position)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
startUpdate(ViewGroup container)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
instantiateItem(ViewGroup container, int position)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
destroyItem(ViewGroup container, int position, Object object)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
setPrimaryItem(ViewGroup container, int position, Object object)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
finishUpdate(ViewGroup container)156     public void finishUpdate(ViewGroup container) {
157         if (mCurrentTransaction != null) {
158             mCurrentTransaction.commitAllowingStateLoss();
159             mCurrentTransaction = null;
160             mFragmentManager.executePendingTransactions();
161         }
162     }
163 
164     @Override
isViewFromObject(View view, Object object)165     public boolean isViewFromObject(View view, Object object) {
166         return ((Fragment) object).getView() == view;
167     }
168 }