Fields Summary |
---|
private static final String | MULTI_USER_PERM |
private static final String | TAG |
private static final int | sDefaultCacheSize |
private static final int | sUnbindServiceDelay |
private static final int | sDefaultLoadingViewHeight |
private static final int | sDefaultMessageType |
private static final int | sUnbindServiceMessageType |
private final android.content.Context | mContext |
private final android.content.Intent | mIntent |
private final int | mAppWidgetId |
private android.view.LayoutInflater | mLayoutInflater |
private RemoteViewsAdapterServiceConnection | mServiceConnection |
private WeakReference | mCallback |
private android.widget.RemoteViews.OnClickHandler | mRemoteViewsOnClickHandler |
private FixedSizeRemoteViewsCache | mCache |
private int | mVisibleWindowLowerBound |
private int | mVisibleWindowUpperBound |
private boolean | mNotifyDataSetChangedAfterOnServiceConnected |
private RemoteViewsFrameLayoutRefSet | mRequestedViews |
private android.os.HandlerThread | mWorkerThread |
private android.os.Handler | mWorkerQueue |
private android.os.Handler | mMainQueue |
private static final HashMap | sCachedRemoteViewsCaches |
private static final HashMap | sRemoteViewsCacheRemoveRunnables |
private static android.os.HandlerThread | sCacheRemovalThread |
private static android.os.Handler | sCacheRemovalQueue |
private static final int | REMOTE_VIEWS_CACHE_DURATION |
private boolean | mDataReady |
Methods Summary |
---|
private void | enqueueDeferredUnbindServiceMessage()
// Remove any existing deferred-unbind messages
mMainQueue.removeMessages(sUnbindServiceMessageType);
mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
|
protected void | finalize()
try {
if (mWorkerThread != null) {
mWorkerThread.quit();
}
} finally {
super.finalize();
}
|
private int | getConvertViewTypeId(android.view.View convertView)Returns the item type id for the specified convert view. Returns -1 if the convert view
is invalid.
int typeId = -1;
if (convertView != null) {
Object tag = convertView.getTag(com.android.internal.R.id.rowTypeId);
if (tag != null) {
typeId = (Integer) tag;
}
}
return typeId;
|
public int | getCount()
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
return metaData.count;
}
|
public java.lang.Object | getItem(int position)
// Disallow arbitrary object to be associated with an item for the time being
return null;
|
public long | getItemId(int position)
synchronized (mCache) {
if (mCache.containsMetaDataAt(position)) {
return mCache.getMetaDataAt(position).itemId;
}
return 0;
}
|
public int | getItemViewType(int position)
int typeId = 0;
synchronized (mCache) {
if (mCache.containsMetaDataAt(position)) {
typeId = mCache.getMetaDataAt(position).typeId;
} else {
return 0;
}
}
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
return metaData.getMappedViewType(typeId);
}
|
public android.content.Intent | getRemoteViewsServiceIntent()
return mIntent;
|
public android.view.View | getView(int position, android.view.View convertView, android.view.ViewGroup parent)
// "Request" an index so that we can queue it for loading, initiate subsequent
// preloading, etc.
synchronized (mCache) {
boolean isInCache = mCache.containsRemoteViewAt(position);
boolean isConnected = mServiceConnection.isConnected();
boolean hasNewItems = false;
if (convertView != null && convertView instanceof RemoteViewsFrameLayout) {
mRequestedViews.removeView((RemoteViewsFrameLayout) convertView);
}
if (!isInCache && !isConnected) {
// Requesting bind service will trigger a super.notifyDataSetChanged(), which will
// in turn trigger another request to getView()
requestBindService();
} else {
// Queue up other indices to be preloaded based on this position
hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
}
if (isInCache) {
View convertViewChild = null;
int convertViewTypeId = 0;
RemoteViewsFrameLayout layout = null;
if (convertView instanceof RemoteViewsFrameLayout) {
layout = (RemoteViewsFrameLayout) convertView;
convertViewChild = layout.getChildAt(0);
convertViewTypeId = getConvertViewTypeId(convertViewChild);
}
// Second, we try and retrieve the RemoteViews from the cache, returning a loading
// view and queueing it to be loaded if it has not already been loaded.
Context context = parent.getContext();
RemoteViews rv = mCache.getRemoteViewsAt(position);
RemoteViewsIndexMetaData indexMetaData = mCache.getMetaDataAt(position);
int typeId = indexMetaData.typeId;
try {
// Reuse the convert view where possible
if (layout != null) {
if (convertViewTypeId == typeId) {
rv.reapply(context, convertViewChild, mRemoteViewsOnClickHandler);
return layout;
}
layout.removeAllViews();
} else {
layout = new RemoteViewsFrameLayout(context);
}
// Otherwise, create a new view to be returned
View newView = rv.apply(context, parent, mRemoteViewsOnClickHandler);
newView.setTagInternal(com.android.internal.R.id.rowTypeId,
new Integer(typeId));
layout.addView(newView);
return layout;
} catch (Exception e){
// We have to make sure that we successfully inflated the RemoteViews, if not
// we return the loading view instead.
Log.w(TAG, "Error inflating RemoteViews at position: " + position + ", using" +
"loading view instead" + e);
RemoteViewsFrameLayout loadingView = null;
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
loadingView = metaData.createLoadingView(position, convertView, parent,
mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
}
return loadingView;
} finally {
if (hasNewItems) loadNextIndexInBackground();
}
} else {
// If the cache does not have the RemoteViews at this position, then create a
// loading view and queue the actual position to be loaded in the background
RemoteViewsFrameLayout loadingView = null;
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
loadingView = metaData.createLoadingView(position, convertView, parent,
mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
}
mRequestedViews.add(position, loadingView);
mCache.queueRequestedPositionToLoad(position);
loadNextIndexInBackground();
return loadingView;
}
}
|
public int | getViewTypeCount()
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
return metaData.viewTypeCount;
}
|
private java.util.ArrayList | getVisibleWindow(int lower, int upper, int count)
ArrayList<Integer> window = new ArrayList<Integer>();
// In the case that the window is invalid or uninitialized, return an empty window.
if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
return window;
}
if (lower <= upper) {
for (int i = lower; i <= upper; i++){
window.add(i);
}
} else {
// If the upper bound is less than the lower bound it means that the visible window
// wraps around.
for (int i = lower; i < count; i++) {
window.add(i);
}
for (int i = 0; i <= upper; i++) {
window.add(i);
}
}
return window;
|
public boolean | handleMessage(android.os.Message msg)
boolean result = false;
switch (msg.what) {
case sUnbindServiceMessageType:
if (mServiceConnection.isConnected()) {
mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
}
result = true;
break;
default:
break;
}
return result;
|
public boolean | hasStableIds()
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
return metaData.hasStableIds;
}
|
public boolean | isDataReady()
return mDataReady;
|
public boolean | isEmpty()
return getCount() <= 0;
|
private void | loadNextIndexInBackground()
mWorkerQueue.post(new Runnable() {
@Override
public void run() {
if (mServiceConnection.isConnected()) {
// Get the next index to load
int position = -1;
synchronized (mCache) {
int[] res = mCache.getNextIndexToLoad();
position = res[0];
}
if (position > -1) {
// Load the item, and notify any existing RemoteViewsFrameLayouts
updateRemoteViews(position, true);
// Queue up for the next one to load
loadNextIndexInBackground();
} else {
// No more items to load, so queue unbind
enqueueDeferredUnbindServiceMessage();
}
}
}
});
|
public void | notifyDataSetChanged()
// Dequeue any unbind messages
mMainQueue.removeMessages(sUnbindServiceMessageType);
// If we are not connected, queue up the notifyDataSetChanged to be handled when we do
// connect
if (!mServiceConnection.isConnected()) {
if (mNotifyDataSetChangedAfterOnServiceConnected) {
return;
}
mNotifyDataSetChangedAfterOnServiceConnected = true;
requestBindService();
return;
}
mWorkerQueue.post(new Runnable() {
@Override
public void run() {
onNotifyDataSetChanged();
}
});
|
private void | onNotifyDataSetChanged()
// Complete the actual notifyDataSetChanged() call initiated earlier
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
try {
factory.onDataSetChanged();
} catch (RemoteException e) {
Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
// Return early to prevent from further being notified (since nothing has
// changed)
return;
} catch (RuntimeException e) {
Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
return;
}
// Flush the cache so that we can reload new items from the service
synchronized (mCache) {
mCache.reset();
}
// Re-request the new metadata (only after the notification to the factory)
updateTemporaryMetaData();
int newCount;
ArrayList<Integer> visibleWindow;
synchronized(mCache.getTemporaryMetaData()) {
newCount = mCache.getTemporaryMetaData().count;
visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
mVisibleWindowUpperBound, newCount);
}
// Pre-load (our best guess of) the views which are currently visible in the AdapterView.
// This mitigates flashing and flickering of loading views when a widget notifies that
// its data has changed.
for (int i: visibleWindow) {
// Because temporary meta data is only ever modified from this thread (ie.
// mWorkerThread), it is safe to assume that count is a valid representation.
if (i < newCount) {
updateRemoteViews(i, false);
}
}
// Propagate the notification back to the base adapter
mMainQueue.post(new Runnable() {
@Override
public void run() {
synchronized (mCache) {
mCache.commitTemporaryMetaData();
}
superNotifyDataSetChanged();
enqueueDeferredUnbindServiceMessage();
}
});
// Reset the notify flagflag
mNotifyDataSetChangedAfterOnServiceConnected = false;
|
private void | processException(java.lang.String method, java.lang.Exception e)
Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
// If we encounter a crash when updating, we should reset the metadata & cache and trigger
// a notifyDataSetChanged to update the widget accordingly
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
metaData.reset();
}
synchronized (mCache) {
mCache.reset();
}
mMainQueue.post(new Runnable() {
@Override
public void run() {
superNotifyDataSetChanged();
}
});
|
private boolean | requestBindService()
// Try binding the service (which will start it if it's not already running)
if (!mServiceConnection.isConnected()) {
mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
}
// Remove any existing deferred-unbind messages
mMainQueue.removeMessages(sUnbindServiceMessageType);
return mServiceConnection.isConnected();
|
public void | saveRemoteViewsCache()
final RemoteViewsCacheKey key = new RemoteViewsCacheKey(
new Intent.FilterComparison(mIntent), mAppWidgetId);
synchronized(sCachedRemoteViewsCaches) {
// If we already have a remove runnable posted for this key, remove it.
if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
sCacheRemovalQueue.removeCallbacks(sRemoteViewsCacheRemoveRunnables.get(key));
sRemoteViewsCacheRemoveRunnables.remove(key);
}
int metaDataCount = 0;
int numRemoteViewsCached = 0;
synchronized (mCache.mMetaData) {
metaDataCount = mCache.mMetaData.count;
}
synchronized (mCache) {
numRemoteViewsCached = mCache.mIndexRemoteViews.size();
}
if (metaDataCount > 0 && numRemoteViewsCached > 0) {
sCachedRemoteViewsCaches.put(key, mCache);
}
Runnable r = new Runnable() {
@Override
public void run() {
synchronized (sCachedRemoteViewsCaches) {
if (sCachedRemoteViewsCaches.containsKey(key)) {
sCachedRemoteViewsCaches.remove(key);
}
if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
sRemoteViewsCacheRemoveRunnables.remove(key);
}
}
}
};
sRemoteViewsCacheRemoveRunnables.put(key, r);
sCacheRemovalQueue.postDelayed(r, REMOTE_VIEWS_CACHE_DURATION);
}
|
public void | setRemoteViewsOnClickHandler(android.widget.RemoteViews.OnClickHandler handler)
mRemoteViewsOnClickHandler = handler;
|
public void | setVisibleRangeHint(int lowerBound, int upperBound)This method allows an AdapterView using this Adapter to provide information about which
views are currently being displayed. This allows for certain optimizations and preloading
which wouldn't otherwise be possible.
mVisibleWindowLowerBound = lowerBound;
mVisibleWindowUpperBound = upperBound;
|
void | superNotifyDataSetChanged()
super.notifyDataSetChanged();
|
private void | updateRemoteViews(int position, boolean notifyWhenLoaded)
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
// Load the item information from the remote service
RemoteViews remoteViews = null;
long itemId = 0;
try {
remoteViews = factory.getViewAt(position);
itemId = factory.getItemId(position);
} catch (RemoteException e) {
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
// Return early to prevent additional work in re-centering the view cache, and
// swapping from the loading view
return;
} catch (RuntimeException e) {
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
return;
}
if (remoteViews == null) {
// If a null view was returned, we break early to prevent it from getting
// into our cache and causing problems later. The effect is that the child at this
// position will remain as a loading view until it is updated.
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
"returned from RemoteViewsFactory.");
return;
}
int layoutId = remoteViews.getLayoutId();
RemoteViewsMetaData metaData = mCache.getMetaData();
boolean viewTypeInRange;
int cacheCount;
synchronized (metaData) {
viewTypeInRange = metaData.isViewTypeInRange(layoutId);
cacheCount = mCache.mMetaData.count;
}
synchronized (mCache) {
if (viewTypeInRange) {
ArrayList<Integer> visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
mVisibleWindowUpperBound, cacheCount);
// Cache the RemoteViews we loaded
mCache.insert(position, remoteViews, itemId, visibleWindow);
// Notify all the views that we have previously returned for this index that
// there is new data for it.
final RemoteViews rv = remoteViews;
if (notifyWhenLoaded) {
mMainQueue.post(new Runnable() {
@Override
public void run() {
mRequestedViews.notifyOnRemoteViewsLoaded(position, rv);
}
});
}
} else {
// We need to log an error here, as the the view type count specified by the
// factory is less than the number of view types returned. We don't return this
// view to the AdapterView, as this will cause an exception in the hosting process,
// which contains the associated AdapterView.
Log.e(TAG, "Error: widget's RemoteViewsFactory returns more view types than " +
" indicated by getViewTypeCount() ");
}
}
|
private void | updateTemporaryMetaData()
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
try {
// get the properties/first view (so that we can use it to
// measure our dummy views)
boolean hasStableIds = factory.hasStableIds();
int viewTypeCount = factory.getViewTypeCount();
int count = factory.getCount();
RemoteViews loadingView = factory.getLoadingView();
RemoteViews firstView = null;
if ((count > 0) && (loadingView == null)) {
firstView = factory.getViewAt(0);
}
final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
synchronized (tmpMetaData) {
tmpMetaData.hasStableIds = hasStableIds;
// We +1 because the base view type is the loading view
tmpMetaData.viewTypeCount = viewTypeCount + 1;
tmpMetaData.count = count;
tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
}
} catch(RemoteException e) {
processException("updateMetaData", e);
} catch(RuntimeException e) {
processException("updateMetaData", e);
}
|