BookmarkExpandableView.java revision 821e426ad369be3d6decb51245dd1c3df653a0b7
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
17package com.android.browser.view;
18
19import android.content.Context;
20import android.database.DataSetObserver;
21import android.provider.BrowserContract;
22import android.util.AttributeSet;
23import android.view.ContextMenu;
24import android.view.ContextMenu.ContextMenuInfo;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.BaseExpandableListAdapter;
29import android.widget.ExpandableListAdapter;
30import android.widget.ExpandableListView;
31import android.widget.FrameLayout;
32import android.widget.LinearLayout;
33import android.widget.TextView;
34
35import com.android.browser.BreadCrumbView;
36import com.android.browser.BrowserBookmarksAdapter;
37import com.android.browser.R;
38import com.android.internal.view.menu.MenuBuilder;
39
40import org.json.JSONException;
41import org.json.JSONObject;
42
43import java.util.ArrayList;
44import java.util.HashMap;
45
46public 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
61    public BookmarkExpandableView(Context context) {
62        super(context);
63        init(context);
64    }
65
66    public BookmarkExpandableView(Context context, AttributeSet attrs) {
67        super(context, attrs);
68        init(context);
69    }
70
71    public BookmarkExpandableView(
72            Context context, AttributeSet attrs, int defStyle) {
73        super(context, attrs, defStyle);
74        init(context);
75    }
76
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
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
104    public void setAdapter(ExpandableListAdapter adapter) {
105        throw new RuntimeException("Not supported");
106    }
107
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
115    public void clearAccounts() {
116        mAdapter.clear();
117    }
118
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
144    public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
145        mOnChildClickListener = onChildClickListener;
146    }
147
148    @Override
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
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
185    public boolean showContextMenuForChild(View originalView) {
186        int groupPosition = (Integer) originalView.getTag(R.id.group_position);
187        int childPosition = (Integer) originalView.getTag(R.id.child_position);
188
189        mContextMenuInfo = new BookmarkContextMenuInfo(childPosition,
190                groupPosition);
191        if (getParent() != null) {
192            getParent().showContextMenuForChild(this);
193        }
194
195        return true;
196    }
197
198    @Override
199    public void onTop(BreadCrumbView view, int level, Object data) {
200        if (mBreadcrumbController != null) {
201            mBreadcrumbController.onTop(view, level, data);
202        }
203    }
204
205    public void setBreadcrumbController(BreadCrumbView.Controller controller) {
206        mBreadcrumbController = controller;
207    }
208
209    @Override
210    protected ContextMenuInfo getContextMenuInfo() {
211        return mContextMenuInfo;
212    }
213
214    public BrowserBookmarksAdapter getChildAdapter(int groupPosition) {
215        return mAdapter.mChildren.get(groupPosition);
216    }
217
218    private OnClickListener mChildClickListener = new OnClickListener() {
219
220        @Override
221        public void onClick(View v) {
222            if (v.getVisibility() != View.VISIBLE) {
223                return;
224            }
225            int groupPosition = (Integer) v.getTag(R.id.group_position);
226            int childPosition = (Integer) v.getTag(R.id.child_position);
227            if (mAdapter.getGroupCount() <= groupPosition
228                    || mAdapter.mChildren.get(groupPosition).getCount() <= childPosition) {
229                return;
230            }
231            long id = (Long) v.getTag(R.id.child_id);
232            if (mOnChildClickListener != null) {
233                mOnChildClickListener.onChildClick(BookmarkExpandableView.this,
234                        v, groupPosition, childPosition, id);
235            }
236        }
237    };
238
239    private OnClickListener mGroupOnClickListener = new OnClickListener() {
240
241        @Override
242        public void onClick(View v) {
243            int groupPosition = (Integer) v.getTag(R.id.group_position);
244            if (isGroupExpanded(groupPosition)) {
245                collapseGroup(groupPosition);
246            } else {
247                expandGroup(groupPosition, true);
248            }
249        }
250    };
251
252    public BreadCrumbView getBreadCrumbs(int groupPosition) {
253        return mAdapter.getBreadCrumbView(groupPosition);
254    }
255
256    public JSONObject saveGroupState() throws JSONException {
257        JSONObject obj = new JSONObject();
258        int count = mAdapter.getGroupCount();
259        for (int i = 0; i < count; i++) {
260            String acctName = mAdapter.mGroups.get(i);
261            if (!isGroupExpanded(i)) {
262                obj.put(acctName != null ? acctName : LOCAL_ACCOUNT_NAME, false);
263            }
264        }
265        return obj;
266    }
267
268    class BookmarkAccountAdapter extends BaseExpandableListAdapter {
269        ArrayList<BrowserBookmarksAdapter> mChildren;
270        ArrayList<String> mGroups;
271        HashMap<Integer, BreadCrumbView> mBreadcrumbs =
272                new HashMap<Integer, BreadCrumbView>();
273        LayoutInflater mInflater;
274        int mRowCount = 1; // assume at least 1 child fits in a row
275        int mLastViewWidth = -1;
276        int mRowPadding = -1;
277        DataSetObserver mObserver = new DataSetObserver() {
278            @Override
279            public void onChanged() {
280                notifyDataSetChanged();
281            }
282
283            @Override
284            public void onInvalidated() {
285                notifyDataSetInvalidated();
286            }
287        };
288
289        public BookmarkAccountAdapter(Context context) {
290            mContext = context;
291            mInflater = LayoutInflater.from(mContext);
292            mChildren = new ArrayList<BrowserBookmarksAdapter>();
293            mGroups = new ArrayList<String>();
294        }
295
296        public void clear() {
297            mGroups.clear();
298            mChildren.clear();
299            notifyDataSetChanged();
300        }
301
302        @Override
303        public Object getChild(int groupPosition, int childPosition) {
304            return mChildren.get(groupPosition).getItem(childPosition);
305        }
306
307        @Override
308        public long getChildId(int groupPosition, int childPosition) {
309            return childPosition;
310        }
311
312        @Override
313        public View getChildView(int groupPosition, int childPosition,
314                boolean isLastChild, View convertView, ViewGroup parent) {
315            if (convertView == null) {
316                convertView = mInflater.inflate(R.layout.bookmark_grid_row, parent, false);
317            }
318            BrowserBookmarksAdapter childAdapter = mChildren.get(groupPosition);
319            int rowCount = mRowCount;
320            LinearLayout row = (LinearLayout) convertView;
321            if (row.getChildCount() > rowCount) {
322                row.removeViews(rowCount, row.getChildCount() - rowCount);
323            }
324            for (int i = 0; i < rowCount; i++) {
325                View cv = null;
326                if (row.getChildCount() > i) {
327                    cv = row.getChildAt(i);
328                }
329                int realChildPosition = (childPosition * rowCount) + i;
330                if (realChildPosition < childAdapter.getCount()) {
331                    View v = childAdapter.getView(realChildPosition, cv, row);
332                    v.setTag(R.id.group_position, groupPosition);
333                    v.setTag(R.id.child_position, realChildPosition);
334                    v.setTag(R.id.child_id, childAdapter.getItemId(realChildPosition));
335                    v.setOnClickListener(mChildClickListener);
336                    v.setLongClickable(mLongClickable);
337                    if (cv == null) {
338                        row.addView(v);
339                    } else if (cv != v) {
340                        row.removeViewAt(i);
341                        row.addView(v, i);
342                    } else {
343                        cv.setVisibility(View.VISIBLE);
344                    }
345                } else if (cv != null) {
346                    cv.setVisibility(View.GONE);
347                }
348            }
349            return row;
350        }
351
352        @Override
353        public int getChildrenCount(int groupPosition) {
354            BrowserBookmarksAdapter adapter = mChildren.get(groupPosition);
355            return (int) Math.ceil(adapter.getCount() / (float)mRowCount);
356        }
357
358        @Override
359        public Object getGroup(int groupPosition) {
360            return mChildren.get(groupPosition);
361        }
362
363        @Override
364        public int getGroupCount() {
365            return mGroups.size();
366        }
367
368        public void measureChildren(int viewWidth) {
369            if (mLastViewWidth == viewWidth) return;
370
371            int rowCount = viewWidth / mColumnWidth;
372            if (mMaxColumnCount > 0) {
373                rowCount = Math.min(rowCount, mMaxColumnCount);
374            }
375            int rowPadding = (viewWidth - (rowCount * mColumnWidth)) / 2;
376            boolean notify = rowCount != mRowCount || rowPadding != mRowPadding;
377            mRowCount = rowCount;
378            mRowPadding = rowPadding;
379            mLastViewWidth = viewWidth;
380            if (notify) {
381                notifyDataSetChanged();
382            }
383        }
384
385        @Override
386        public long getGroupId(int groupPosition) {
387            return groupPosition;
388        }
389
390        @Override
391        public View getGroupView(int groupPosition, boolean isExpanded,
392                View view, ViewGroup parent) {
393            if (view == null) {
394                view = mInflater.inflate(R.layout.bookmark_group_view, parent, false);
395                view.setOnClickListener(mGroupOnClickListener);
396            }
397            view.setTag(R.id.group_position, groupPosition);
398            FrameLayout crumbHolder = (FrameLayout) view.findViewById(R.id.crumb_holder);
399            crumbHolder.removeAllViews();
400            BreadCrumbView crumbs = getBreadCrumbView(groupPosition);
401            if (crumbs.getParent() != null) {
402                ((ViewGroup)crumbs.getParent()).removeView(crumbs);
403            }
404            crumbHolder.addView(crumbs);
405            TextView name = (TextView) view.findViewById(R.id.group_name);
406            String groupName = mGroups.get(groupPosition);
407            if (groupName == null) {
408                groupName = mContext.getString(R.string.local_bookmarks);
409            }
410            name.setText(groupName);
411            return view;
412        }
413
414        public BreadCrumbView getBreadCrumbView(int groupPosition) {
415            BreadCrumbView crumbs = mBreadcrumbs.get(groupPosition);
416            if (crumbs == null) {
417                crumbs = (BreadCrumbView)
418                        mInflater.inflate(R.layout.bookmarks_header, null);
419                crumbs.setController(BookmarkExpandableView.this);
420                crumbs.setUseBackButton(true);
421                crumbs.setMaxVisible(2);
422                String bookmarks = mContext.getString(R.string.bookmarks);
423                crumbs.pushView(bookmarks, false,
424                        BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
425                crumbs.setTag(R.id.group_position, groupPosition);
426                crumbs.setVisibility(View.GONE);
427                mBreadcrumbs.put(groupPosition, crumbs);
428            }
429            return crumbs;
430        }
431
432        @Override
433        public boolean hasStableIds() {
434            return false;
435        }
436
437        @Override
438        public boolean isChildSelectable(int groupPosition, int childPosition) {
439            return true;
440        }
441    }
442
443    public static class BookmarkContextMenuInfo implements ContextMenuInfo {
444
445        private BookmarkContextMenuInfo(int childPosition, int groupPosition) {
446            this.childPosition = childPosition;
447            this.groupPosition = groupPosition;
448        }
449
450        public int childPosition;
451        public int groupPosition;
452    }
453
454}
455