1 /*
2  * Copyright (C) 2011 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.browser.view;
18 
19 import android.content.Context;
20 import android.database.DataSetObserver;
21 import android.provider.BrowserContract;
22 import android.util.AttributeSet;
23 import android.view.ContextMenu;
24 import android.view.ContextMenu.ContextMenuInfo;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.widget.BaseExpandableListAdapter;
29 import android.widget.ExpandableListAdapter;
30 import android.widget.ExpandableListView;
31 import android.widget.FrameLayout;
32 import android.widget.LinearLayout;
33 import android.widget.TextView;
34 
35 import com.android.browser.BreadCrumbView;
36 import com.android.browser.BrowserBookmarksAdapter;
37 import com.android.browser.R;
38 import com.android.internal.view.menu.MenuBuilder;
39 
40 import org.json.JSONException;
41 import org.json.JSONObject;
42 
43 import java.util.ArrayList;
44 import java.util.HashMap;
45 
46 public class BookmarkExpandableView extends ExpandableListView
47         implements BreadCrumbView.Controller {
48 
49     public static final String LOCAL_ACCOUNT_NAME = "local";
50 
51     private BookmarkAccountAdapter mAdapter;
52     private int mColumnWidth;
53     private Context mContext;
54     private OnChildClickListener mOnChildClickListener;
55     private ContextMenuInfo mContextMenuInfo = null;
56     private OnCreateContextMenuListener mOnCreateContextMenuListener;
57     private boolean mLongClickable;
58     private BreadCrumbView.Controller mBreadcrumbController;
59     private int mMaxColumnCount;
60 
BookmarkExpandableView(Context context)61     public BookmarkExpandableView(Context context) {
62         super(context);
63         init(context);
64     }
65 
BookmarkExpandableView(Context context, AttributeSet attrs)66     public BookmarkExpandableView(Context context, AttributeSet attrs) {
67         super(context, attrs);
68         init(context);
69     }
70 
BookmarkExpandableView( Context context, AttributeSet attrs, int defStyle)71     public BookmarkExpandableView(
72             Context context, AttributeSet attrs, int defStyle) {
73         super(context, attrs, defStyle);
74         init(context);
75     }
76 
init(Context context)77     void init(Context context) {
78         mContext = context;
79         setItemsCanFocus(true);
80         setLongClickable(false);
81         mMaxColumnCount = mContext.getResources()
82                 .getInteger(R.integer.max_bookmark_columns);
83         setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY);
84         mAdapter = new BookmarkAccountAdapter(mContext);
85         super.setAdapter(mAdapter);
86     }
87 
88     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)89     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
90         int width = MeasureSpec.getSize(widthMeasureSpec);
91         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
92         if (width > 0) {
93             mAdapter.measureChildren(width);
94             setPadding(mAdapter.mRowPadding, 0, mAdapter.mRowPadding, 0);
95             widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, widthMode);
96         }
97         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
98         if (width != getMeasuredWidth()) {
99             mAdapter.measureChildren(getMeasuredWidth());
100         }
101     }
102 
103     @Override
setAdapter(ExpandableListAdapter adapter)104     public void setAdapter(ExpandableListAdapter adapter) {
105         throw new RuntimeException("Not supported");
106     }
107 
setColumnWidthFromLayout(int layout)108     public void setColumnWidthFromLayout(int layout) {
109         LayoutInflater infalter = LayoutInflater.from(mContext);
110         View v = infalter.inflate(layout, this, false);
111         v.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
112         mColumnWidth = v.getMeasuredWidth();
113     }
114 
clearAccounts()115     public void clearAccounts() {
116         mAdapter.clear();
117     }
118 
addAccount(String accountName, BrowserBookmarksAdapter adapter, boolean expandGroup)119     public void addAccount(String accountName, BrowserBookmarksAdapter adapter,
120             boolean expandGroup) {
121         // First, check if it already exists
122         int indexOf = mAdapter.mGroups.indexOf(accountName);
123         if (indexOf >= 0) {
124             BrowserBookmarksAdapter existing = mAdapter.mChildren.get(indexOf);
125             if (existing != adapter) {
126                 existing.unregisterDataSetObserver(mAdapter.mObserver);
127                 // Replace the existing one
128                 mAdapter.mChildren.remove(indexOf);
129                 mAdapter.mChildren.add(indexOf, adapter);
130                 adapter.registerDataSetObserver(mAdapter.mObserver);
131             }
132         } else {
133             mAdapter.mGroups.add(accountName);
134             mAdapter.mChildren.add(adapter);
135             adapter.registerDataSetObserver(mAdapter.mObserver);
136         }
137         mAdapter.notifyDataSetChanged();
138         if (expandGroup) {
139             expandGroup(mAdapter.getGroupCount() - 1);
140         }
141     }
142 
143     @Override
setOnChildClickListener(OnChildClickListener onChildClickListener)144     public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
145         mOnChildClickListener = onChildClickListener;
146     }
147 
148     @Override
setOnCreateContextMenuListener(OnCreateContextMenuListener l)149     public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) {
150         mOnCreateContextMenuListener = l;
151         if (!mLongClickable) {
152             mLongClickable = true;
153             if (mAdapter != null) {
154                 mAdapter.notifyDataSetChanged();
155             }
156         }
157     }
158 
159     @Override
createContextMenu(ContextMenu menu)160     public void createContextMenu(ContextMenu menu) {
161         // The below is copied from View - we want to bypass the override
162         // in AbsListView
163 
164         ContextMenuInfo menuInfo = getContextMenuInfo();
165 
166         // Sets the current menu info so all items added to menu will have
167         // my extra info set.
168         ((MenuBuilder)menu).setCurrentMenuInfo(menuInfo);
169 
170         onCreateContextMenu(menu);
171         if (mOnCreateContextMenuListener != null) {
172             mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo);
173         }
174 
175         // Clear the extra information so subsequent items that aren't mine don't
176         // have my extra info.
177         ((MenuBuilder)menu).setCurrentMenuInfo(null);
178 
179         if (mParent != null) {
180             mParent.createContextMenu(menu);
181         }
182     }
183 
184     @Override
showContextMenuForChild(View originalView)185     public boolean showContextMenuForChild(View originalView) {
186         Integer groupPosition = (Integer) originalView.getTag(R.id.group_position);
187         Integer childPosition = (Integer) originalView.getTag(R.id.child_position);
188 
189         if (groupPosition == null || childPosition == null) {
190             return false;
191         }
192 
193         mContextMenuInfo = new BookmarkContextMenuInfo(childPosition, groupPosition);
194         if (getParent() != null) {
195             getParent().showContextMenuForChild(this);
196         }
197 
198         return true;
199     }
200 
201     @Override
onTop(BreadCrumbView view, int level, Object data)202     public void onTop(BreadCrumbView view, int level, Object data) {
203         if (mBreadcrumbController != null) {
204             mBreadcrumbController.onTop(view, level, data);
205         }
206     }
207 
setBreadcrumbController(BreadCrumbView.Controller controller)208     public void setBreadcrumbController(BreadCrumbView.Controller controller) {
209         mBreadcrumbController = controller;
210     }
211 
212     @Override
getContextMenuInfo()213     protected ContextMenuInfo getContextMenuInfo() {
214         return mContextMenuInfo;
215     }
216 
getChildAdapter(int groupPosition)217     public BrowserBookmarksAdapter getChildAdapter(int groupPosition) {
218         return mAdapter.mChildren.get(groupPosition);
219     }
220 
221     private OnClickListener mChildClickListener = new OnClickListener() {
222 
223         @Override
224         public void onClick(View v) {
225             if (v.getVisibility() != View.VISIBLE) {
226                 return;
227             }
228             int groupPosition = (Integer) v.getTag(R.id.group_position);
229             int childPosition = (Integer) v.getTag(R.id.child_position);
230             if (mAdapter.getGroupCount() <= groupPosition
231                     || mAdapter.mChildren.get(groupPosition).getCount() <= childPosition) {
232                 return;
233             }
234             long id = mAdapter.mChildren.get(groupPosition).getItemId(childPosition);
235             if (mOnChildClickListener != null) {
236                 mOnChildClickListener.onChildClick(BookmarkExpandableView.this,
237                         v, groupPosition, childPosition, id);
238             }
239         }
240     };
241 
242     private OnClickListener mGroupOnClickListener = new OnClickListener() {
243 
244         @Override
245         public void onClick(View v) {
246             int groupPosition = (Integer) v.getTag(R.id.group_position);
247             if (isGroupExpanded(groupPosition)) {
248                 collapseGroup(groupPosition);
249             } else {
250                 expandGroup(groupPosition, true);
251             }
252         }
253     };
254 
getBreadCrumbs(int groupPosition)255     public BreadCrumbView getBreadCrumbs(int groupPosition) {
256         return mAdapter.getBreadCrumbView(groupPosition);
257     }
258 
saveGroupState()259     public JSONObject saveGroupState() throws JSONException {
260         JSONObject obj = new JSONObject();
261         int count = mAdapter.getGroupCount();
262         for (int i = 0; i < count; i++) {
263             String acctName = mAdapter.mGroups.get(i);
264             if (!isGroupExpanded(i)) {
265                 obj.put(acctName != null ? acctName : LOCAL_ACCOUNT_NAME, false);
266             }
267         }
268         return obj;
269     }
270 
271     class BookmarkAccountAdapter extends BaseExpandableListAdapter {
272         ArrayList<BrowserBookmarksAdapter> mChildren;
273         ArrayList<String> mGroups;
274         HashMap<Integer, BreadCrumbView> mBreadcrumbs =
275                 new HashMap<Integer, BreadCrumbView>();
276         LayoutInflater mInflater;
277         int mRowCount = 1; // assume at least 1 child fits in a row
278         int mLastViewWidth = -1;
279         int mRowPadding = -1;
280         DataSetObserver mObserver = new DataSetObserver() {
281             @Override
282             public void onChanged() {
283                 notifyDataSetChanged();
284             }
285 
286             @Override
287             public void onInvalidated() {
288                 notifyDataSetInvalidated();
289             }
290         };
291 
BookmarkAccountAdapter(Context context)292         public BookmarkAccountAdapter(Context context) {
293             mContext = context;
294             mInflater = LayoutInflater.from(mContext);
295             mChildren = new ArrayList<BrowserBookmarksAdapter>();
296             mGroups = new ArrayList<String>();
297         }
298 
clear()299         public void clear() {
300             mGroups.clear();
301             mChildren.clear();
302             notifyDataSetChanged();
303         }
304 
305         @Override
getChild(int groupPosition, int childPosition)306         public Object getChild(int groupPosition, int childPosition) {
307             return mChildren.get(groupPosition).getItem(childPosition);
308         }
309 
310         @Override
getChildId(int groupPosition, int childPosition)311         public long getChildId(int groupPosition, int childPosition) {
312             return childPosition;
313         }
314 
315         @Override
getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent)316         public View getChildView(int groupPosition, int childPosition,
317                 boolean isLastChild, View convertView, ViewGroup parent) {
318             if (convertView == null) {
319                 convertView = mInflater.inflate(R.layout.bookmark_grid_row, parent, false);
320             }
321             BrowserBookmarksAdapter childAdapter = mChildren.get(groupPosition);
322             int rowCount = mRowCount;
323             LinearLayout row = (LinearLayout) convertView;
324             if (row.getChildCount() > rowCount) {
325                 row.removeViews(rowCount, row.getChildCount() - rowCount);
326             }
327             for (int i = 0; i < rowCount; i++) {
328                 View cv = null;
329                 if (row.getChildCount() > i) {
330                     cv = row.getChildAt(i);
331                 }
332                 int realChildPosition = (childPosition * rowCount) + i;
333                 if (realChildPosition < childAdapter.getCount()) {
334                     View v = childAdapter.getView(realChildPosition, cv, row);
335                     v.setTag(R.id.group_position, groupPosition);
336                     v.setTag(R.id.child_position, realChildPosition);
337                     v.setOnClickListener(mChildClickListener);
338                     v.setLongClickable(mLongClickable);
339                     if (cv == null) {
340                         row.addView(v);
341                     } else if (cv != v) {
342                         row.removeViewAt(i);
343                         row.addView(v, i);
344                     } else {
345                         cv.setVisibility(View.VISIBLE);
346                     }
347                 } else if (cv != null) {
348                     cv.setVisibility(View.GONE);
349                 }
350             }
351             return row;
352         }
353 
354         @Override
getChildrenCount(int groupPosition)355         public int getChildrenCount(int groupPosition) {
356             BrowserBookmarksAdapter adapter = mChildren.get(groupPosition);
357             return (int) Math.ceil(adapter.getCount() / (float)mRowCount);
358         }
359 
360         @Override
getGroup(int groupPosition)361         public Object getGroup(int groupPosition) {
362             return mChildren.get(groupPosition);
363         }
364 
365         @Override
getGroupCount()366         public int getGroupCount() {
367             return mGroups.size();
368         }
369 
measureChildren(int viewWidth)370         public void measureChildren(int viewWidth) {
371             if (mLastViewWidth == viewWidth) return;
372 
373             int rowCount = viewWidth / mColumnWidth;
374             if (mMaxColumnCount > 0) {
375                 rowCount = Math.min(rowCount, mMaxColumnCount);
376             }
377             int rowPadding = (viewWidth - (rowCount * mColumnWidth)) / 2;
378             boolean notify = rowCount != mRowCount || rowPadding != mRowPadding;
379             mRowCount = rowCount;
380             mRowPadding = rowPadding;
381             mLastViewWidth = viewWidth;
382             if (notify) {
383                 notifyDataSetChanged();
384             }
385         }
386 
387         @Override
getGroupId(int groupPosition)388         public long getGroupId(int groupPosition) {
389             return groupPosition;
390         }
391 
392         @Override
getGroupView(int groupPosition, boolean isExpanded, View view, ViewGroup parent)393         public View getGroupView(int groupPosition, boolean isExpanded,
394                 View view, ViewGroup parent) {
395             if (view == null) {
396                 view = mInflater.inflate(R.layout.bookmark_group_view, parent, false);
397                 view.setOnClickListener(mGroupOnClickListener);
398             }
399             view.setTag(R.id.group_position, groupPosition);
400             FrameLayout crumbHolder = (FrameLayout) view.findViewById(R.id.crumb_holder);
401             crumbHolder.removeAllViews();
402             BreadCrumbView crumbs = getBreadCrumbView(groupPosition);
403             if (crumbs.getParent() != null) {
404                 ((ViewGroup)crumbs.getParent()).removeView(crumbs);
405             }
406             crumbHolder.addView(crumbs);
407             TextView name = (TextView) view.findViewById(R.id.group_name);
408             String groupName = mGroups.get(groupPosition);
409             if (groupName == null) {
410                 groupName = mContext.getString(R.string.local_bookmarks);
411             }
412             name.setText(groupName);
413             return view;
414         }
415 
getBreadCrumbView(int groupPosition)416         public BreadCrumbView getBreadCrumbView(int groupPosition) {
417             BreadCrumbView crumbs = mBreadcrumbs.get(groupPosition);
418             if (crumbs == null) {
419                 crumbs = (BreadCrumbView)
420                         mInflater.inflate(R.layout.bookmarks_header, null);
421                 crumbs.setController(BookmarkExpandableView.this);
422                 crumbs.setUseBackButton(true);
423                 crumbs.setMaxVisible(2);
424                 String bookmarks = mContext.getString(R.string.bookmarks);
425                 crumbs.pushView(bookmarks, false,
426                         BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
427                 crumbs.setTag(R.id.group_position, groupPosition);
428                 crumbs.setVisibility(View.GONE);
429                 mBreadcrumbs.put(groupPosition, crumbs);
430             }
431             return crumbs;
432         }
433 
434         @Override
hasStableIds()435         public boolean hasStableIds() {
436             return false;
437         }
438 
439         @Override
isChildSelectable(int groupPosition, int childPosition)440         public boolean isChildSelectable(int groupPosition, int childPosition) {
441             return true;
442         }
443     }
444 
445     public static class BookmarkContextMenuInfo implements ContextMenuInfo {
446 
BookmarkContextMenuInfo(int childPosition, int groupPosition)447         private BookmarkContextMenuInfo(int childPosition, int groupPosition) {
448             this.childPosition = childPosition;
449             this.groupPosition = groupPosition;
450         }
451 
452         public int childPosition;
453         public int groupPosition;
454     }
455 
456 }
457