1 package com.android.systemui.qs;
2 
3 import android.content.Context;
4 import android.content.res.Configuration;
5 import android.content.res.Resources;
6 import android.support.v4.view.PagerAdapter;
7 import android.support.v4.view.ViewPager;
8 import android.util.AttributeSet;
9 import android.util.Log;
10 import android.view.LayoutInflater;
11 import android.view.View;
12 import android.view.ViewGroup;
13 import com.android.systemui.R;
14 import com.android.systemui.qs.QSPanel.QSTileLayout;
15 import com.android.systemui.qs.QSPanel.TileRecord;
16 
17 import java.util.ArrayList;
18 
19 public class PagedTileLayout extends ViewPager implements QSTileLayout {
20 
21     private static final boolean DEBUG = false;
22 
23     private static final String TAG = "PagedTileLayout";
24 
25     private final ArrayList<TileRecord> mTiles = new ArrayList<TileRecord>();
26     private final ArrayList<TilePage> mPages = new ArrayList<TilePage>();
27 
28     private PageIndicator mPageIndicator;
29 
30     private int mNumPages;
31     private View mDecorGroup;
32     private PageListener mPageListener;
33 
34     private int mPosition;
35     private boolean mOffPage;
36     private boolean mListening;
37 
PagedTileLayout(Context context, AttributeSet attrs)38     public PagedTileLayout(Context context, AttributeSet attrs) {
39         super(context, attrs);
40         setAdapter(mAdapter);
41         setOnPageChangeListener(new OnPageChangeListener() {
42             @Override
43             public void onPageSelected(int position) {
44                 if (mPageIndicator == null) return;
45                 if (mPageListener != null) {
46                     mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1
47                             : position == 0);
48                 }
49             }
50 
51             @Override
52             public void onPageScrolled(int position, float positionOffset,
53                     int positionOffsetPixels) {
54                 if (mPageIndicator == null) return;
55                 setCurrentPage(position, positionOffset != 0);
56                 mPageIndicator.setLocation(position + positionOffset);
57                 if (mPageListener != null) {
58                     mPageListener.onPageChanged(positionOffsetPixels == 0 &&
59                             (isLayoutRtl() ? position == mPages.size() - 1 : position == 0));
60                 }
61             }
62 
63             @Override
64             public void onPageScrollStateChanged(int state) {
65             }
66         });
67         setCurrentItem(0);
68     }
69 
70     @Override
onRtlPropertiesChanged(int layoutDirection)71     public void onRtlPropertiesChanged(int layoutDirection) {
72         super.onRtlPropertiesChanged(layoutDirection);
73         setAdapter(mAdapter);
74         setCurrentItem(0, false);
75     }
76 
77     @Override
setCurrentItem(int item, boolean smoothScroll)78     public void setCurrentItem(int item, boolean smoothScroll) {
79         if (isLayoutRtl()) {
80             item = mPages.size() - 1 - item;
81         }
82         super.setCurrentItem(item, smoothScroll);
83     }
84 
85     @Override
setListening(boolean listening)86     public void setListening(boolean listening) {
87         if (mListening == listening) return;
88         mListening = listening;
89         if (mListening) {
90             mPages.get(mPosition).setListening(listening);
91             if (mOffPage) {
92                 mPages.get(mPosition + 1).setListening(listening);
93             }
94         } else {
95             // Make sure no pages are listening.
96             for (int i = 0; i < mPages.size(); i++) {
97                 mPages.get(i).setListening(false);
98             }
99         }
100     }
101 
102     /**
103      * Sets individual pages to listening or not.  If offPage it will set
104      * the next page after position to listening as well since we are in between
105      * pages.
106      */
setCurrentPage(int position, boolean offPage)107     private void setCurrentPage(int position, boolean offPage) {
108         if (mPosition == position && mOffPage == offPage) return;
109         if (mListening) {
110             if (mPosition != position) {
111                 // Clear out the last pages from listening.
112                 setPageListening(mPosition, false);
113                 if (mOffPage) {
114                     setPageListening(mPosition + 1, false);
115                 }
116                 // Set the new pages to listening
117                 setPageListening(position, true);
118                 if (offPage) {
119                     setPageListening(position + 1, true);
120                 }
121             } else if (mOffPage != offPage) {
122                 // Whether we are showing position + 1 has changed.
123                 setPageListening(mPosition + 1, offPage);
124             }
125         }
126         // Save the current state.
127         mPosition = position;
128         mOffPage = offPage;
129     }
130 
setPageListening(int position, boolean listening)131     private void setPageListening(int position, boolean listening) {
132         if (position >= mPages.size()) return;
133         mPages.get(position).setListening(listening);
134     }
135 
136     @Override
hasOverlappingRendering()137     public boolean hasOverlappingRendering() {
138         return false;
139     }
140 
141     @Override
onFinishInflate()142     protected void onFinishInflate() {
143         super.onFinishInflate();
144         mPageIndicator = (PageIndicator) findViewById(R.id.page_indicator);
145         mDecorGroup = findViewById(R.id.page_decor);
146         ((LayoutParams) mDecorGroup.getLayoutParams()).isDecor = true;
147 
148         mPages.add((TilePage) LayoutInflater.from(mContext)
149                 .inflate(R.layout.qs_paged_page, this, false));
150     }
151 
152     @Override
getOffsetTop(TileRecord tile)153     public int getOffsetTop(TileRecord tile) {
154         final ViewGroup parent = (ViewGroup) tile.tileView.getParent();
155         if (parent == null) return 0;
156         return parent.getTop() + getTop();
157     }
158 
159     @Override
addTile(TileRecord tile)160     public void addTile(TileRecord tile) {
161         mTiles.add(tile);
162         postDistributeTiles();
163     }
164 
165     @Override
removeTile(TileRecord tile)166     public void removeTile(TileRecord tile) {
167         if (mTiles.remove(tile)) {
168             postDistributeTiles();
169         }
170     }
171 
setPageListener(PageListener listener)172     public void setPageListener(PageListener listener) {
173         mPageListener = listener;
174     }
175 
postDistributeTiles()176     private void postDistributeTiles() {
177         removeCallbacks(mDistribute);
178         post(mDistribute);
179     }
180 
distributeTiles()181     private void distributeTiles() {
182         if (DEBUG) Log.d(TAG, "Distributing tiles");
183         final int NP = mPages.size();
184         for (int i = 0; i < NP; i++) {
185             mPages.get(i).removeAllViews();
186         }
187         int index = 0;
188         final int NT = mTiles.size();
189         for (int i = 0; i < NT; i++) {
190             TileRecord tile = mTiles.get(i);
191             if (mPages.get(index).isFull()) {
192                 if (++index == mPages.size()) {
193                     if (DEBUG) Log.d(TAG, "Adding page for "
194                             + tile.tile.getClass().getSimpleName());
195                     mPages.add((TilePage) LayoutInflater.from(mContext)
196                             .inflate(R.layout.qs_paged_page, this, false));
197                 }
198             }
199             if (DEBUG) Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
200                     + index);
201             mPages.get(index).addTile(tile);
202         }
203         if (mNumPages != index + 1) {
204             mNumPages = index + 1;
205             while (mPages.size() > mNumPages) {
206                 mPages.remove(mPages.size() - 1);
207             }
208             if (DEBUG) Log.d(TAG, "Size: " + mNumPages);
209             mPageIndicator.setNumPages(mNumPages);
210             setAdapter(mAdapter);
211             mAdapter.notifyDataSetChanged();
212             setCurrentItem(0, false);
213         }
214     }
215 
216     @Override
updateResources()217     public boolean updateResources() {
218         boolean changed = false;
219         for (int i = 0; i < mPages.size(); i++) {
220             changed |= mPages.get(i).updateResources();
221         }
222         if (changed) {
223             distributeTiles();
224         }
225         return changed;
226     }
227 
228     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)229     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
230         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
231         // The ViewPager likes to eat all of the space, instead force it to wrap to the max height
232         // of the pages.
233         int maxHeight = 0;
234         final int N = getChildCount();
235         for (int i = 0; i < N; i++) {
236             int height = getChildAt(i).getMeasuredHeight();
237             if (height > maxHeight) {
238                 maxHeight = height;
239             }
240         }
241         setMeasuredDimension(getMeasuredWidth(), maxHeight + mDecorGroup.getMeasuredHeight());
242     }
243 
244     private final Runnable mDistribute = new Runnable() {
245         @Override
246         public void run() {
247             distributeTiles();
248         }
249     };
250 
getColumnCount()251     public int getColumnCount() {
252         if (mPages.size() == 0) return 0;
253         return mPages.get(0).mColumns;
254     }
255 
256     public static class TilePage extends TileLayout {
257         private int mMaxRows = 3;
258 
TilePage(Context context, AttributeSet attrs)259         public TilePage(Context context, AttributeSet attrs) {
260             super(context, attrs);
261             updateResources();
262             setContentDescription(mContext.getString(R.string.accessibility_desc_quick_settings));
263         }
264 
265         @Override
updateResources()266         public boolean updateResources() {
267             final int rows = getRows();
268             boolean changed = rows != mMaxRows;
269             if (changed) {
270                 mMaxRows = rows;
271                 requestLayout();
272             }
273             return super.updateResources() || changed;
274         }
275 
getRows()276         private int getRows() {
277             final Resources res = getContext().getResources();
278             if (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
279                 // Always have 3 rows in portrait.
280                 return 3;
281             }
282             return Math.max(1, res.getInteger(R.integer.quick_settings_num_rows));
283         }
284 
setMaxRows(int maxRows)285         public void setMaxRows(int maxRows) {
286             mMaxRows = maxRows;
287         }
288 
isFull()289         public boolean isFull() {
290             return mRecords.size() >= mColumns * mMaxRows;
291         }
292     }
293 
294     private final PagerAdapter mAdapter = new PagerAdapter() {
295         public void destroyItem(ViewGroup container, int position, Object object) {
296             if (DEBUG) Log.d(TAG, "Destantiating " + position);
297             container.removeView((View) object);
298         }
299 
300         public Object instantiateItem(ViewGroup container, int position) {
301             if (DEBUG) Log.d(TAG, "Instantiating " + position);
302             if (isLayoutRtl()) {
303                 position = mPages.size() - 1 - position;
304             }
305             ViewGroup view = mPages.get(position);
306             container.addView(view);
307             return view;
308         }
309 
310         @Override
311         public int getCount() {
312             return mNumPages;
313         }
314 
315         @Override
316         public boolean isViewFromObject(View view, Object object) {
317             return view == object;
318         }
319     };
320 
321     public interface PageListener {
onPageChanged(boolean isFirst)322         void onPageChanged(boolean isFirst);
323     }
324 }
325