diff options
-rw-r--r-- | res/values/ids.xml | 2 | ||||
-rw-r--r-- | src/com/android/browser/BrowserBookmarksAdapter.java | 122 | ||||
-rw-r--r-- | src/com/android/browser/BrowserBookmarksAdapterItem.java | 27 | ||||
-rw-r--r-- | src/com/android/browser/BrowserBookmarksPage.java | 22 | ||||
-rw-r--r-- | src/com/android/browser/util/ThreadedCursorAdapter.java | 193 | ||||
-rw-r--r-- | src/com/android/browser/view/BookmarkContainer.java | 14 |
6 files changed, 334 insertions, 46 deletions
diff --git a/res/values/ids.xml b/res/values/ids.xml index f342d4c..211b02f 100644 --- a/res/values/ids.xml +++ b/res/values/ids.xml @@ -20,4 +20,6 @@ <item type="id" name="child_position" /> <item type="id" name="child_id" /> <item type="id" name="tab_view" /> + <item type="id" name="position" /> + <item type="id" name="load_object" /> </resources> diff --git a/src/com/android/browser/BrowserBookmarksAdapter.java b/src/com/android/browser/BrowserBookmarksAdapter.java index fcc3f27..be3c211 100644 --- a/src/com/android/browser/BrowserBookmarksAdapter.java +++ b/src/com/android/browser/BrowserBookmarksAdapter.java @@ -19,19 +19,24 @@ package com.android.browser; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; import android.provider.BrowserContract.Bookmarks; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.CursorAdapter; import android.widget.ImageView; import android.widget.ImageView.ScaleType; import android.widget.TextView; -public class BrowserBookmarksAdapter extends CursorAdapter { +import com.android.browser.util.ThreadedCursorAdapter; +import com.android.browser.view.BookmarkContainer; + +public class BrowserBookmarksAdapter extends + ThreadedCursorAdapter<BrowserBookmarksAdapterItem> { + LayoutInflater mInflater; int mCurrentView; + Context mContext; /** * Create a new BrowserBookmarksAdapter. @@ -39,30 +44,53 @@ public class BrowserBookmarksAdapter extends CursorAdapter { public BrowserBookmarksAdapter(Context context, int defaultView) { // Make sure to tell the CursorAdapter to avoid the observer and auto-requery // since the Loader will do that for us. - super(context, null, 0); + super(context, null); mInflater = LayoutInflater.from(context); + mContext = context; selectView(defaultView); } @Override - public void bindView(View view, Context context, Cursor cursor) { + public View newView(Context context, ViewGroup parent) { if (mCurrentView == BrowserBookmarksPage.VIEW_LIST) { - bindListView(view, context, cursor); + return mInflater.inflate(R.layout.bookmark_list, parent, false); } else { - bindGridView(view, context, cursor); + return mInflater.inflate(R.layout.bookmark_thumbnail, parent, false); } } - CharSequence getTitle(Cursor cursor, Context context) { + @Override + public void bindView(View view, BrowserBookmarksAdapterItem object) { + BookmarkContainer container = (BookmarkContainer) view; + container.setIgnoreRequestLayout(true); + if (mCurrentView == BrowserBookmarksPage.VIEW_LIST) { + bindListView(view, mContext, object); + } else { + bindGridView(view, mContext, object); + } + container.setIgnoreRequestLayout(false); + } + + void clearView(View view) { + if (mCurrentView == BrowserBookmarksPage.VIEW_LIST) { + ImageView favicon = (ImageView) view.findViewById(R.id.favicon); + favicon.setImageBitmap(null); + } else { + ImageView thumb = (ImageView) view.findViewById(R.id.thumb); + thumb.setImageBitmap(null); + } + } + + CharSequence getTitle(Cursor cursor) { int type = cursor.getInt(BookmarksLoader.COLUMN_INDEX_TYPE); switch (type) { case Bookmarks.BOOKMARK_TYPE_OTHER_FOLDER: - return context.getText(R.string.other_bookmarks); + return mContext.getText(R.string.other_bookmarks); } return cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE); } - void bindGridView(View view, Context context, Cursor cursor) { + void bindGridView(View view, Context context, BrowserBookmarksAdapterItem item) { // We need to set this to handle rotation and other configuration change // events. If the padding didn't change, this is a no op. int padding = context.getResources() @@ -72,63 +100,42 @@ public class BrowserBookmarksAdapter extends CursorAdapter { ImageView thumb = (ImageView) view.findViewById(R.id.thumb); TextView tv = (TextView) view.findViewById(R.id.label); - tv.setText(getTitle(cursor, context)); - if (cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0) { + tv.setText(item.title); + if (item.is_folder) { // folder thumb.setImageResource(R.drawable.thumb_bookmark_widget_folder_holo); thumb.setScaleType(ScaleType.FIT_END); - thumb.setBackgroundDrawable(null); + thumb.setBackground(null); } else { - byte[] thumbData = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_THUMBNAIL); - Bitmap thumbBitmap = null; - if (thumbData != null) { - thumbBitmap = BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length); - } - thumb.setScaleType(ScaleType.CENTER_CROP); - if (thumbBitmap == null) { + if (item.thumbnail == null) { thumb.setImageResource(R.drawable.browser_thumbnail); } else { - thumb.setImageBitmap(thumbBitmap); + thumb.setImageDrawable(item.thumbnail); } thumb.setBackgroundResource(R.drawable.border_thumb_bookmarks_widget_holo); } } - void bindListView(View view, Context context, Cursor cursor) { + void bindListView(View view, Context context, BrowserBookmarksAdapterItem item) { ImageView favicon = (ImageView) view.findViewById(R.id.favicon); TextView tv = (TextView) view.findViewById(R.id.label); - tv.setText(getTitle(cursor, context)); - if (cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0) { + tv.setText(item.title); + if (item.is_folder) { // folder favicon.setImageResource(R.drawable.ic_folder_holo_dark); - favicon.setBackgroundDrawable(null); + favicon.setBackground(null); } else { - byte[] faviconData = cursor.getBlob(BookmarksLoader.COLUMN_INDEX_FAVICON); - Bitmap faviconBitmap = null; - if (faviconData != null) { - faviconBitmap = BitmapFactory.decodeByteArray(faviconData, 0, faviconData.length); - } - - if (faviconBitmap == null) { + if (item.favicon == null) { favicon.setImageResource(R.drawable.app_web_browser_sm); } else { - favicon.setImageBitmap(faviconBitmap); + favicon.setImageDrawable(item.favicon); } favicon.setBackgroundResource(R.drawable.bookmark_list_favicon_bg); } } - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - if (mCurrentView == BrowserBookmarksPage.VIEW_LIST) { - return mInflater.inflate(R.layout.bookmark_list, parent, false); - } else { - return mInflater.inflate(R.layout.bookmark_thumbnail, parent, false); - } - } - public void selectView(int view) { if (view != BrowserBookmarksPage.VIEW_LIST && view != BrowserBookmarksPage.VIEW_THUMBNAILS) { @@ -142,7 +149,34 @@ public class BrowserBookmarksAdapter extends CursorAdapter { } @Override - public Cursor getItem(int position) { - return (Cursor) super.getItem(position); + public BrowserBookmarksAdapterItem getRowObject(Cursor c, + BrowserBookmarksAdapterItem item) { + if (item == null) { + item = new BrowserBookmarksAdapterItem(); + } + Bitmap favicon = item.favicon != null ? item.favicon.getBitmap() : null; + Bitmap thumbnail = item.thumbnail != null ? item.thumbnail.getBitmap() : null; + favicon = BrowserBookmarksPage.getBitmap(c, + BookmarksLoader.COLUMN_INDEX_FAVICON, favicon); + thumbnail = BrowserBookmarksPage.getBitmap(c, + BookmarksLoader.COLUMN_INDEX_THUMBNAIL, thumbnail); + if (favicon != null + && (item.favicon == null || item.favicon.getBitmap() != favicon)) { + item.favicon = new BitmapDrawable(mContext.getResources(), favicon); + } + if (thumbnail != null + && (item.thumbnail == null || item.thumbnail.getBitmap() != thumbnail)) { + item.thumbnail = new BitmapDrawable(mContext.getResources(), thumbnail); + } + item.is_folder = c.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0; + item.title = getTitle(c); + item.url = c.getString(BookmarksLoader.COLUMN_INDEX_URL); + return item; + } + + @Override + public BrowserBookmarksAdapterItem getLoadingObject() { + BrowserBookmarksAdapterItem item = new BrowserBookmarksAdapterItem(); + return item; } } diff --git a/src/com/android/browser/BrowserBookmarksAdapterItem.java b/src/com/android/browser/BrowserBookmarksAdapterItem.java new file mode 100644 index 0000000..913b0fd --- /dev/null +++ b/src/com/android/browser/BrowserBookmarksAdapterItem.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.browser; + +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; + +public class BrowserBookmarksAdapterItem { + public String url; + public CharSequence title; + public BitmapDrawable favicon; + public BitmapDrawable thumbnail; + public boolean is_folder; +} diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java index 2c8a27a..5a609b1 100644 --- a/src/com/android/browser/BrowserBookmarksPage.java +++ b/src/com/android/browser/BrowserBookmarksPage.java @@ -32,6 +32,7 @@ import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.BitmapFactory.Options; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -235,11 +236,30 @@ public class BrowserBookmarksPage extends Fragment implements View.OnCreateConte } static Bitmap getBitmap(Cursor cursor, int columnIndex) { + return getBitmap(cursor, columnIndex, null); + } + + static ThreadLocal<Options> sOptions = new ThreadLocal<Options>() { + @Override + protected Options initialValue() { + return new Options(); + }; + }; + static Bitmap getBitmap(Cursor cursor, int columnIndex, Bitmap inBitmap) { byte[] data = cursor.getBlob(columnIndex); if (data == null) { return null; } - return BitmapFactory.decodeByteArray(data, 0, data.length); + Options opts = sOptions.get(); + opts.inBitmap = inBitmap; + opts.inSampleSize = 1; + opts.inScaled = false; + try { + return BitmapFactory.decodeByteArray(data, 0, data.length, opts); + } catch (IllegalArgumentException ex) { + // Failed to re-use bitmap, create a new one + return BitmapFactory.decodeByteArray(data, 0, data.length); + } } private MenuItem.OnMenuItemClickListener mContextItemClickListener = diff --git a/src/com/android/browser/util/ThreadedCursorAdapter.java b/src/com/android/browser/util/ThreadedCursorAdapter.java new file mode 100644 index 0000000..fe59ad1 --- /dev/null +++ b/src/com/android/browser/util/ThreadedCursorAdapter.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.browser.util; + +import android.content.Context; +import android.database.Cursor; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.os.Process; +import android.util.Log; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Adapter; +import android.widget.BaseAdapter; +import android.widget.CursorAdapter; + +import com.android.browser.R; + +import java.lang.ref.WeakReference; +import java.util.HashMap; + +public abstract class ThreadedCursorAdapter<T> extends BaseAdapter { + + private static final String LOGTAG = "tca"; + private static final boolean DEBUG = false; + + private Context mContext; + private Object mCursorLock = new Object(); + private CursorAdapter mCursorAdapter; + private T mLoadingObject; + private Handler mLoadHandler; + private Handler mHandler; + private int mSize; + + private class LoadContainer { + WeakReference<View> view; + int position; + T bind_object; + Adapter owner; + boolean loaded; + } + + public ThreadedCursorAdapter(Context context, Cursor c) { + mContext = context; + mCursorAdapter = new CursorAdapter(context, c, 0) { + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + throw new IllegalStateException("not supported"); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + throw new IllegalStateException("not supported"); + } + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + mSize = getCount(); + ThreadedCursorAdapter.this.notifyDataSetChanged(); + } + + @Override + public void notifyDataSetInvalidated() { + super.notifyDataSetInvalidated(); + mSize = getCount(); + ThreadedCursorAdapter.this.notifyDataSetInvalidated(); + } + + }; + mSize = mCursorAdapter.getCount(); + HandlerThread thread = new HandlerThread("threaded_adapter_" + this, + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + mLoadHandler = new Handler(thread.getLooper()) { + @SuppressWarnings("unchecked") + @Override + public void handleMessage(Message msg) { + if (DEBUG) { + Log.d(LOGTAG, "loading: " + msg.what); + } + loadRowObject(msg.what, (LoadContainer) msg.obj); + } + }; + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + @SuppressWarnings("unchecked") + LoadContainer container = (LoadContainer) msg.obj; + if (container == null) { + return; + } + View view = container.view.get(); + if (view == null + || container.owner != ThreadedCursorAdapter.this + || container.position != msg.what) { + return; + } + container.loaded = true; + bindView(view, container.bind_object); + } + }; + } + + @Override + public int getCount() { + return mSize; + } + + @Override + public Cursor getItem(int position) { + return (Cursor) mCursorAdapter.getItem(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + private void loadRowObject(int position, LoadContainer container) { + if (container == null + || container.position != position + || container.owner != ThreadedCursorAdapter.this + || container.view.get() == null) { + return; + } + synchronized (mCursorLock) { + Cursor c = (Cursor) mCursorAdapter.getItem(position); + container.bind_object = getRowObject(c, container.bind_object); + } + mHandler.obtainMessage(position, container).sendToTarget(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = newView(mContext, parent); + } + @SuppressWarnings("unchecked") + LoadContainer container = (LoadContainer) convertView.getTag(R.id.load_object); + if (container == null) { + container = new LoadContainer(); + container.view = new WeakReference<View>(convertView); + convertView.setTag(R.id.load_object, container); + } + if (container.position == position + && container.owner == this + && container.loaded) { + bindView(convertView, container.bind_object); + } else { + bindView(convertView, cachedLoadObject()); + container.position = position; + container.loaded = false; + container.owner = this; + mLoadHandler.obtainMessage(position, container).sendToTarget(); + } + return convertView; + } + + private T cachedLoadObject() { + if (mLoadingObject == null) { + mLoadingObject = getLoadingObject(); + } + return mLoadingObject; + } + + public void changeCursor(Cursor cursor) { + synchronized (mCursorLock) { + mCursorAdapter.changeCursor(cursor); + } + } + + public abstract View newView(Context context, ViewGroup parent); + public abstract void bindView(View view, T object); + public abstract T getRowObject(Cursor c, T recycleObject); + public abstract T getLoadingObject(); +}
\ No newline at end of file diff --git a/src/com/android/browser/view/BookmarkContainer.java b/src/com/android/browser/view/BookmarkContainer.java index 260b05e..5175589 100644 --- a/src/com/android/browser/view/BookmarkContainer.java +++ b/src/com/android/browser/view/BookmarkContainer.java @@ -29,7 +29,8 @@ import android.widget.RelativeLayout; public class BookmarkContainer extends RelativeLayout implements OnClickListener { private OnClickListener mClickListener; - + private boolean mIgnoreRequestLayout = false; + public BookmarkContainer(Context context) { super(context); init(); @@ -89,4 +90,15 @@ public class BookmarkContainer extends RelativeLayout implements OnClickListener mClickListener.onClick(view); } } + + public void setIgnoreRequestLayout(boolean ignore) { + mIgnoreRequestLayout = ignore; + } + + @Override + public void requestLayout() { + if (!mIgnoreRequestLayout) { + super.requestLayout(); + } + } } |