/*
* Copyright (C) 2007 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 android.widget;
import java.util.HashMap;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import com.android.internal.widget.IRemoteViewsFactory;
/**
* The service to be connected to for a remote adapter to request RemoteViews. Users should
* extend the RemoteViewsService to provide the appropriate RemoteViewsFactory's used to
* populate the remote collection view (ListView, GridView, etc).
*/
public abstract class RemoteViewsService extends Service {
private static final String LOG_TAG = "RemoteViewsService";
// Used for reference counting of RemoteViewsFactories
// Because we are now unbinding when we are not using the Service (to allow them to be
// reclaimed), the references to the factories that are created need to be stored and used when
// the service is restarted (in response to user input for example). When the process is
// destroyed, so is this static cache of RemoteViewsFactories.
private static final HashMap<Intent.FilterComparison, RemoteViewsFactory> sRemoteViewFactories =
new HashMap<Intent.FilterComparison, RemoteViewsFactory>();
private static final Object sLock = new Object();
/**
* An interface for an adapter between a remote collection view (ListView, GridView, etc) and
* the underlying data for that view. The implementor is responsible for making a RemoteView
* for each item in the data set. This interface is a thin wrapper around {@link Adapter}.
*
* @see android.widget.Adapter
* @see android.appwidget.AppWidgetManager
*/
public interface RemoteViewsFactory {
/**
* Called when your factory is first constructed. The same factory may be shared across
* multiple RemoteViewAdapters depending on the intent passed.
*/
public void onCreate();
/**
* Called when notifyDataSetChanged() is triggered on the remote adapter. This allows a
* RemoteViewsFactory to respond to data changes by updating any internal references.
*
* Note: expensive tasks can be safely performed synchronously within this method. In the
* interim, the old data will be displayed within the widget.
*
* @see android.appwidget.AppWidgetManager#notifyAppWidgetViewDataChanged(int[], int)
*/
public void onDataSetChanged();
/**
* Called when the last RemoteViewsAdapter that is associated with this factory is
* unbound.
*/
public void onDestroy();
/**
* See {@link Adapter#getCount()}
*
* @return Count of items.
*/
public int getCount();
/**
* See {@link Adapter#getView(int, android.view.View, android.view.ViewGroup)}.
*
* Note: expensive tasks can be safely performed synchronously within this method, and a
* loading view will be displayed in the interim. See {@link #getLoadingView()}.
*
* @param position The position of the item within the Factory's data set of the item whose
* view we want.
* @return A RemoteViews object corresponding to the data at the specified position.
*/
public RemoteViews getViewAt(int position);
/**
* This allows for the use of a custom loading view which appears between the time that
* {@link #getViewAt(int)} is called and returns. If null is returned, a default loading
* view will be used.
*
* @return The RemoteViews representing the desired loading view.
*/
public RemoteViews getLoadingView();
/**
* See {@link Adapter#getViewTypeCount()}.
*
* @return The number of types of Views that will be returned by this factory.
*/
public int getViewTypeCount();
/**
* See {@link Adapter#getItemId(int)}.
*
* @param position The position of the item within the data set whose row id we want.
* @return The id of the item at the specified position.
*/
public long getItemId(int position);
/**
* See {@link Adapter#hasStableIds()}.
*
* @return True if the same id always refers to the same object.
*/
public boolean hasStableIds();
}
/**
* A private proxy class for the private IRemoteViewsFactory interface through the
* public RemoteViewsFactory interface.
*/
private static class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub {
public RemoteViewsFactoryAdapter(RemoteViewsFactory factory, boolean isCreated) {
mFactory = factory;
mIsCreated = isCreated;
}
public synchronized boolean isCreated() {
return mIsCreated;
}
public synchronized void onDataSetChanged() {
try {
mFactory.onDataSetChanged();
} catch (Exception ex) {
Thread t = Thread.currentThread();
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
}
}
public synchronized void onDataSetChangedAsync() {
onDataSetChanged();
}
public synchronized int getCount() {
int count = 0;
try {
count = mFactory.getCount();
} catch (Exception ex) {
Thread t = Thread.currentThread();
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
}
return count;
}
public synchronized RemoteViews getViewAt(int position) {
RemoteViews rv = null;
try {
rv = mFactory.getViewAt(position);
if (rv != null) {
rv.setIsWidgetCollectionChild(true);
}
} catch (Exception ex) {
Thread t = Thread.currentThread();
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
}
return rv;
}
public synchronized RemoteViews getLoadingView() {
RemoteViews rv = null;
try {
rv = mFactory.getLoadingView();
} catch (Exception ex) {
Thread t = Thread.currentThread();
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
}
return rv;
}
public synchronized int getViewTypeCount() {
int count = 0;
try {
count = mFactory.getViewTypeCount();
} catch (Exception ex) {
Thread t = Thread.currentThread();
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
}
return count;
}
public synchronized long getItemId(int position) {
long id = 0;
try {
id = mFactory.getItemId(position);
} catch (Exception ex) {
Thread t = Thread.currentThread();
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
}
return id;
}
public synchronized boolean hasStableIds() {
boolean hasStableIds = false;
try {
hasStableIds = mFactory.hasStableIds();
} catch (Exception ex) {
Thread t = Thread.currentThread();
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
}
return hasStableIds;
}
public void onDestroy(Intent intent) {
synchronized (sLock) {
Intent.FilterComparison fc = new Intent.FilterComparison(intent);
if (RemoteViewsService.sRemoteViewFactories.containsKey(fc)) {
RemoteViewsFactory factory = RemoteViewsService.sRemoteViewFactories.get(fc);
try {
factory.onDestroy();
} catch (Exception ex) {
Thread t = Thread.currentThread();
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
}
RemoteViewsService.sRemoteViewFactories.remove(fc);
}
}
}
private RemoteViewsFactory mFactory;
private boolean mIsCreated;
}
@Override
public IBinder onBind(Intent intent) {
synchronized (sLock) {
Intent.FilterComparison fc = new Intent.FilterComparison(intent);
RemoteViewsFactory factory = null;
boolean isCreated = false;
if (!sRemoteViewFactories.containsKey(fc)) {
factory = onGetViewFactory(intent);
sRemoteViewFactories.put(fc, factory);
factory.onCreate();
isCreated = false;
} else {
factory = sRemoteViewFactories.get(fc);
isCreated = true;
}
return new RemoteViewsFactoryAdapter(factory, isCreated);
}
}
/**
* To be implemented by the derived service to generate appropriate factories for
* the data.
*/
public abstract RemoteViewsFactory onGetViewFactory(Intent intent);
}