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