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 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 = mAdapter.mChildren.get(groupPosition).getItemId(childPosition); 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.setOnClickListener(mChildClickListener); 335 v.setLongClickable(mLongClickable); 336 if (cv == null) { 337 row.addView(v); 338 } else if (cv != v) { 339 row.removeViewAt(i); 340 row.addView(v, i); 341 } else { 342 cv.setVisibility(View.VISIBLE); 343 } 344 } else if (cv != null) { 345 cv.setVisibility(View.GONE); 346 } 347 } 348 return row; 349 } 350 351 @Override 352 public int getChildrenCount(int groupPosition) { 353 BrowserBookmarksAdapter adapter = mChildren.get(groupPosition); 354 return (int) Math.ceil(adapter.getCount() / (float)mRowCount); 355 } 356 357 @Override 358 public Object getGroup(int groupPosition) { 359 return mChildren.get(groupPosition); 360 } 361 362 @Override 363 public int getGroupCount() { 364 return mGroups.size(); 365 } 366 367 public void measureChildren(int viewWidth) { 368 if (mLastViewWidth == viewWidth) return; 369 370 int rowCount = viewWidth / mColumnWidth; 371 if (mMaxColumnCount > 0) { 372 rowCount = Math.min(rowCount, mMaxColumnCount); 373 } 374 int rowPadding = (viewWidth - (rowCount * mColumnWidth)) / 2; 375 boolean notify = rowCount != mRowCount || rowPadding != mRowPadding; 376 mRowCount = rowCount; 377 mRowPadding = rowPadding; 378 mLastViewWidth = viewWidth; 379 if (notify) { 380 notifyDataSetChanged(); 381 } 382 } 383 384 @Override 385 public long getGroupId(int groupPosition) { 386 return groupPosition; 387 } 388 389 @Override 390 public View getGroupView(int groupPosition, boolean isExpanded, 391 View view, ViewGroup parent) { 392 if (view == null) { 393 view = mInflater.inflate(R.layout.bookmark_group_view, parent, false); 394 view.setOnClickListener(mGroupOnClickListener); 395 } 396 view.setTag(R.id.group_position, groupPosition); 397 FrameLayout crumbHolder = (FrameLayout) view.findViewById(R.id.crumb_holder); 398 crumbHolder.removeAllViews(); 399 BreadCrumbView crumbs = getBreadCrumbView(groupPosition); 400 if (crumbs.getParent() != null) { 401 ((ViewGroup)crumbs.getParent()).removeView(crumbs); 402 } 403 crumbHolder.addView(crumbs); 404 TextView name = (TextView) view.findViewById(R.id.group_name); 405 String groupName = mGroups.get(groupPosition); 406 if (groupName == null) { 407 groupName = mContext.getString(R.string.local_bookmarks); 408 } 409 name.setText(groupName); 410 return view; 411 } 412 413 public BreadCrumbView getBreadCrumbView(int groupPosition) { 414 BreadCrumbView crumbs = mBreadcrumbs.get(groupPosition); 415 if (crumbs == null) { 416 crumbs = (BreadCrumbView) 417 mInflater.inflate(R.layout.bookmarks_header, null); 418 crumbs.setController(BookmarkExpandableView.this); 419 crumbs.setUseBackButton(true); 420 crumbs.setMaxVisible(2); 421 String bookmarks = mContext.getString(R.string.bookmarks); 422 crumbs.pushView(bookmarks, false, 423 BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER); 424 crumbs.setTag(R.id.group_position, groupPosition); 425 crumbs.setVisibility(View.GONE); 426 mBreadcrumbs.put(groupPosition, crumbs); 427 } 428 return crumbs; 429 } 430 431 @Override 432 public boolean hasStableIds() { 433 return false; 434 } 435 436 @Override 437 public boolean isChildSelectable(int groupPosition, int childPosition) { 438 return true; 439 } 440 } 441 442 public static class BookmarkContextMenuInfo implements ContextMenuInfo { 443 444 private BookmarkContextMenuInfo(int childPosition, int groupPosition) { 445 this.childPosition = childPosition; 446 this.groupPosition = groupPosition; 447 } 448 449 public int childPosition; 450 public int groupPosition; 451 } 452 453 } 454