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