1 /**
2  * Copyright (C) 2014 Google Inc.
3  * Licensed to The Android Open Source Project.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package com.android.mail.ui;
18 
19 import android.content.Context;
20 import android.database.DataSetObserver;
21 import android.graphics.Rect;
22 import android.util.AttributeSet;
23 import android.view.LayoutInflater;
24 import android.view.View;
25 import android.widget.ImageView;
26 import android.widget.LinearLayout;
27 import android.widget.ListAdapter;
28 
29 import com.android.mail.R;
30 import com.android.mail.content.ObjectCursor;
31 import com.android.mail.providers.Folder;
32 import com.android.mail.utils.LogUtils;
33 
34 import java.util.ArrayDeque;
35 import java.util.Queue;
36 
37 /**
38  * A smaller version of the account- and folder-switching drawer view for tablet UIs.
39  */
40 public class MiniDrawerView extends LinearLayout {
41 
42     private FolderListFragment mController;
43 
44     private View mSpacer;
45 
46     private final LayoutInflater mInflater;
47 
48     private static final int NUM_RECENT_ACCOUNTS = 2;
49 
MiniDrawerView(Context context)50     public MiniDrawerView(Context context) {
51         this(context, null);
52     }
53 
MiniDrawerView(Context context, AttributeSet attrs)54     public MiniDrawerView(Context context, AttributeSet attrs) {
55         super(context, attrs);
56 
57         mInflater = LayoutInflater.from(context);
58     }
59 
60     @Override
onFinishInflate()61     protected void onFinishInflate() {
62         super.onFinishInflate();
63 
64         mSpacer = findViewById(R.id.spacer);
65     }
66 
67     @Override
requestFocus(int direction, Rect previouslyFocusedRect)68     public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
69         // This ViewGroup is focusable purely so it can act as a stable target for other views to
70         // designate as their left/right focus ID. When focus comes to this view, the XML
71         // declaration of descendantFocusability=FOCUS_AFTER_DESCENDANTS means it will always try
72         // to focus one of its children before resorting to this (great! we basically never want
73         // this container to gain focus).
74         //
75         // But the usual focus search towards the LEFT (in LTR) actually starts at the bottom,
76         // which is weird. So override all focus requests that land on this parent to use the
77         // FORWARD direction so the top-most item gets first focus. This will not affect focus
78         // traversal within this ViewGroup as the descendantFocusability prevents the parent from
79         // gaining focus.
80         return super.requestFocus(FOCUS_DOWN, previouslyFocusedRect);
81     }
82 
setController(FolderListFragment flf)83     public void setController(FolderListFragment flf) {
84         mController = flf;
85         final ListAdapter adapter = mController.getMiniDrawerAccountsAdapter();
86         adapter.registerDataSetObserver(new Observer());
87     }
88 
89     private class Observer extends DataSetObserver {
90 
91         @Override
onChanged()92         public void onChanged() {
93             refresh();
94         }
95     }
96 
refresh()97     public void refresh() {
98         if (mController == null || !mController.isAdded()) {
99             return;
100         }
101 
102         final ListAdapter adapter =
103                 mController.getMiniDrawerAccountsAdapter();
104 
105         if (adapter.getCount() > 0) {
106             final View oldCurrentAccountView = getChildAt(0);
107             if (oldCurrentAccountView != null) {
108                 removeView(oldCurrentAccountView);
109             }
110             final View newCurrentAccountView = adapter.getView(0, oldCurrentAccountView, this);
111             newCurrentAccountView.setClickable(false);
112             newCurrentAccountView.setFocusable(false);
113             addView(newCurrentAccountView, 0);
114         }
115 
116         final int removePos = indexOfChild(mSpacer) + 1;
117         final int recycleCount = getChildCount() - removePos;
118         final Queue<View> recycleViews = new ArrayDeque<>(recycleCount);
119         for (int recycleIndex = 0; recycleIndex < recycleCount; recycleIndex++) {
120             final View recycleView = getChildAt(removePos);
121             recycleViews.add(recycleView);
122             removeView(recycleView);
123         }
124 
125         final int adapterCount = Math.min(adapter.getCount(), NUM_RECENT_ACCOUNTS + 1);
126         for (int accountIndex = 1; accountIndex < adapterCount; accountIndex++) {
127             final View recycleView = recycleViews.poll();
128             final View accountView = adapter.getView(accountIndex, recycleView, this);
129             addView(accountView);
130         }
131 
132         View child;
133         // reset the inbox views for this account
134         while ((child=getChildAt(1)) != mSpacer) {
135             removeView(child);
136         }
137 
138         final ObjectCursor<Folder> folderCursor = mController.getFoldersCursor();
139         if (folderCursor != null && !folderCursor.isClosed()) {
140             int pos = -1;
141             int numInboxes = 0;
142             while (folderCursor.moveToPosition(++pos)) {
143                 final Folder f = folderCursor.getModel();
144                 if (f.isInbox()) {
145                     final View view = mInflater.inflate(
146                             R.layout.mini_drawer_folder_item, this, false /* attachToRoot */);
147                     final ImageView iv = (ImageView) view.findViewById(R.id.image_view);
148                     iv.setTag(new FolderItem(f, iv));
149                     iv.setContentDescription(f.name);
150                     view.setActivated(mController.isSelectedFolder(f));
151                     addView(view, 1 + numInboxes);
152                     numInboxes++;
153                 }
154             }
155         }
156     }
157 
158     private class FolderItem implements View.OnClickListener {
159         public final Folder folder;
160         public final ImageView view;
161 
FolderItem(Folder f, ImageView iv)162         public FolderItem(Folder f, ImageView iv) {
163             folder = f;
164             view = iv;
165             Folder.setIcon(folder, view);
166             view.setOnClickListener(this);
167         }
168 
169         @Override
onClick(View v)170         public void onClick(View v) {
171             mController.onFolderSelected(folder, "mini-drawer");
172         }
173     }
174 
175     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)176     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
177         // We want to make sure that all children get measured. These will be re-hidden in onLayout
178         // according to space constraints.
179         // This means we can't set views to Gone elsewhere, which is kind of unfortunate.
180         final int childCount = getChildCount();
181         for (int i = 0; i < childCount; i++) {
182             final View child = getChildAt(i);
183             child.setVisibility(View.VISIBLE);
184         }
185         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
186     }
187 
188     @Override
onLayout(boolean changed, int l, int t, int r, int b)189     protected void onLayout(boolean changed, int l, int t, int r, int b) {
190         if (getChildCount() == 0) {
191             return;
192         }
193         final int availableHeight = getMeasuredHeight() - getPaddingBottom() - getPaddingTop();
194 
195         int childHeight = 0;
196         final int childCount = getChildCount();
197         for (int i = 0; i < childCount; i++) {
198             final View child = getChildAt(i);
199             if (child.equals(mSpacer) || child.getVisibility() == View.GONE) {
200                 continue;
201             }
202             final LayoutParams params = (LayoutParams) child.getLayoutParams();
203             childHeight += params.topMargin + params.bottomMargin + child.getMeasuredHeight();
204         }
205 
206         if (childHeight <= availableHeight) {
207             // Nothing to do here
208             super.onLayout(changed, l, t, r, b);
209             return;
210         }
211 
212         // Check again
213         if (childHeight <= availableHeight) {
214             // Fit the spacer to the remaining height
215             measureSpacer(availableHeight - childHeight);
216             super.onLayout(changed, l, t, r, b);
217             return;
218         }
219 
220         // Sanity check
221         if (getChildAt(getChildCount() - 1).equals(mSpacer)) {
222             LogUtils.v(LogUtils.TAG, "The ellipsis was the last item in the minidrawer and " +
223                     "hiding it didn't help fit all the views");
224             return;
225         }
226 
227         final View childToHide = getChildAt(indexOfChild(mSpacer) + 1);
228         childToHide.setVisibility(View.GONE);
229 
230         final LayoutParams childToHideParams = (LayoutParams) childToHide.getLayoutParams();
231         childHeight -= childToHideParams.topMargin + childToHideParams.bottomMargin +
232                 childToHide.getMeasuredHeight();
233 
234         // Check again
235         if (childHeight <= availableHeight) {
236             // Fit the spacer to the remaining height
237             measureSpacer(availableHeight - childHeight);
238             super.onLayout(changed, l, t, r, b);
239             return;
240         }
241 
242         LogUtils.v(LogUtils.TAG, "Hid two children in the minidrawer and still couldn't fit " +
243                 "all the views");
244     }
245 
measureSpacer(int height)246     private void measureSpacer(int height) {
247         final LayoutParams spacerParams = (LayoutParams) mSpacer.getLayoutParams();
248         final int spacerHeight = height -
249                 spacerParams.bottomMargin - spacerParams.topMargin;
250         final int spacerWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
251         mSpacer.measure(MeasureSpec.makeMeasureSpec(spacerWidth, MeasureSpec.AT_MOST),
252                 MeasureSpec.makeMeasureSpec(spacerHeight, MeasureSpec.EXACTLY));
253 
254     }
255 }
256