Home | History | Annotate | Download | only in view
      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.Cursor;
     21 import android.database.DataSetObserver;
     22 import android.provider.BrowserContract;
     23 import android.util.AttributeSet;
     24 import android.view.ContextMenu;
     25 import android.view.ContextMenu.ContextMenuInfo;
     26 import android.view.LayoutInflater;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 import android.widget.BaseExpandableListAdapter;
     30 import android.widget.ExpandableListAdapter;
     31 import android.widget.ExpandableListView;
     32 import android.widget.FrameLayout;
     33 import android.widget.LinearLayout;
     34 import android.widget.TextView;
     35 
     36 import com.android.browser.BookmarkDragHandler;
     37 import com.android.browser.BookmarkDragHandler.BookmarkDragAdapter;
     38 import com.android.browser.BookmarkDragHandler.BookmarkDragState;
     39 import com.android.browser.BreadCrumbView;
     40 import com.android.browser.BrowserBookmarksAdapter;
     41 import com.android.browser.BrowserBookmarksPage;
     42 import com.android.browser.BrowserBookmarksPage.ExtraDragState;
     43 import com.android.browser.R;
     44 import com.android.internal.view.menu.MenuBuilder;
     45 
     46 import org.json.JSONException;
     47 import org.json.JSONObject;
     48 
     49 import java.util.ArrayList;
     50 import java.util.HashMap;
     51 
     52 public 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