summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Reck <jreck@google.com>2011-10-10 15:33:48 -0700
committerJohn Reck <jreck@google.com>2012-04-18 14:29:03 -0700
commitf94abcf44fc1611f76e55461f48220e621fc31b7 (patch)
tree678fab453c53611388e27cde68371192e194082f
parentf1286a455eeacd02dfcca1335e6a7a9f87433c1b (diff)
downloadpackages_apps_browser-f94abcf44fc1611f76e55461f48220e621fc31b7.zip
packages_apps_browser-f94abcf44fc1611f76e55461f48220e621fc31b7.tar.gz
packages_apps_browser-f94abcf44fc1611f76e55461f48220e621fc31b7.tar.bz2
Load bookmarks asynchronously
Bug: 5297900 Change-Id: I8b728cfe06799099e21c402d5da7087507209ffa
-rw-r--r--res/values/ids.xml2
-rw-r--r--src/com/android/browser/BrowserBookmarksAdapter.java122
-rw-r--r--src/com/android/browser/BrowserBookmarksAdapterItem.java27
-rw-r--r--src/com/android/browser/BrowserBookmarksPage.java22
-rw-r--r--src/com/android/browser/util/ThreadedCursorAdapter.java193
-rw-r--r--src/com/android/browser/view/BookmarkContainer.java14
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();
+ }
+ }
}