1 /* 2 * Copyright (C) 2007 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 android.widget; 18 19 import android.app.Service; 20 import android.content.Intent; 21 import android.os.IBinder; 22 23 import com.android.internal.widget.IRemoteViewsFactory; 24 25 import java.util.HashMap; 26 27 /** 28 * The service to be connected to for a remote adapter to request RemoteViews. Users should 29 * extend the RemoteViewsService to provide the appropriate RemoteViewsFactory's used to 30 * populate the remote collection view (ListView, GridView, etc). 31 */ 32 public abstract class RemoteViewsService extends Service { 33 34 private static final String LOG_TAG = "RemoteViewsService"; 35 36 // Used for reference counting of RemoteViewsFactories 37 // Because we are now unbinding when we are not using the Service (to allow them to be 38 // reclaimed), the references to the factories that are created need to be stored and used when 39 // the service is restarted (in response to user input for example). When the process is 40 // destroyed, so is this static cache of RemoteViewsFactories. 41 private static final HashMap<Intent.FilterComparison, RemoteViewsFactory> sRemoteViewFactories = 42 new HashMap<Intent.FilterComparison, RemoteViewsFactory>(); 43 private static final Object sLock = new Object(); 44 45 /** 46 * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory 47 * 48 * @hide 49 */ 50 private static final int MAX_NUM_ENTRY = 10; 51 52 /** 53 * An interface for an adapter between a remote collection view (ListView, GridView, etc) and 54 * the underlying data for that view. The implementor is responsible for making a RemoteView 55 * for each item in the data set. This interface is a thin wrapper around {@link Adapter}. 56 * 57 * @see android.widget.Adapter 58 * @see android.appwidget.AppWidgetManager 59 */ 60 public interface RemoteViewsFactory { 61 /** 62 * Called when your factory is first constructed. The same factory may be shared across 63 * multiple RemoteViewAdapters depending on the intent passed. 64 */ onCreate()65 public void onCreate(); 66 67 /** 68 * Called when notifyDataSetChanged() is triggered on the remote adapter. This allows a 69 * RemoteViewsFactory to respond to data changes by updating any internal references. 70 * 71 * Note: expensive tasks can be safely performed synchronously within this method. In the 72 * interim, the old data will be displayed within the widget. 73 * 74 * @see android.appwidget.AppWidgetManager#notifyAppWidgetViewDataChanged(int[], int) 75 */ onDataSetChanged()76 public void onDataSetChanged(); 77 78 /** 79 * Called when the last RemoteViewsAdapter that is associated with this factory is 80 * unbound. 81 */ onDestroy()82 public void onDestroy(); 83 84 /** 85 * See {@link Adapter#getCount()} 86 * 87 * @return Count of items. 88 */ getCount()89 public int getCount(); 90 91 /** 92 * See {@link Adapter#getView(int, android.view.View, android.view.ViewGroup)}. 93 * 94 * Note: expensive tasks can be safely performed synchronously within this method, and a 95 * loading view will be displayed in the interim. See {@link #getLoadingView()}. 96 * 97 * @param position The position of the item within the Factory's data set of the item whose 98 * view we want. 99 * @return A RemoteViews object corresponding to the data at the specified position. 100 */ getViewAt(int position)101 public RemoteViews getViewAt(int position); 102 103 /** 104 * This allows for the use of a custom loading view which appears between the time that 105 * {@link #getViewAt(int)} is called and returns. If null is returned, a default loading 106 * view will be used. 107 * 108 * @return The RemoteViews representing the desired loading view. 109 */ getLoadingView()110 public RemoteViews getLoadingView(); 111 112 /** 113 * See {@link Adapter#getViewTypeCount()}. 114 * 115 * @return The number of types of Views that will be returned by this factory. 116 */ getViewTypeCount()117 public int getViewTypeCount(); 118 119 /** 120 * See {@link Adapter#getItemId(int)}. 121 * 122 * @param position The position of the item within the data set whose row id we want. 123 * @return The id of the item at the specified position. 124 */ getItemId(int position)125 public long getItemId(int position); 126 127 /** 128 * See {@link Adapter#hasStableIds()}. 129 * 130 * @return True if the same id always refers to the same object. 131 */ hasStableIds()132 public boolean hasStableIds(); 133 } 134 135 /** 136 * A private proxy class for the private IRemoteViewsFactory interface through the 137 * public RemoteViewsFactory interface. 138 */ 139 private static class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub { RemoteViewsFactoryAdapter(RemoteViewsFactory factory, boolean isCreated)140 public RemoteViewsFactoryAdapter(RemoteViewsFactory factory, boolean isCreated) { 141 mFactory = factory; 142 mIsCreated = isCreated; 143 } isCreated()144 public synchronized boolean isCreated() { 145 return mIsCreated; 146 } onDataSetChanged()147 public synchronized void onDataSetChanged() { 148 try { 149 mFactory.onDataSetChanged(); 150 } catch (Exception ex) { 151 Thread t = Thread.currentThread(); 152 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 153 } 154 } onDataSetChangedAsync()155 public synchronized void onDataSetChangedAsync() { 156 onDataSetChanged(); 157 } getCount()158 public synchronized int getCount() { 159 int count = 0; 160 try { 161 count = mFactory.getCount(); 162 } catch (Exception ex) { 163 Thread t = Thread.currentThread(); 164 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 165 } 166 return count; 167 } getViewAt(int position)168 public synchronized RemoteViews getViewAt(int position) { 169 RemoteViews rv = null; 170 try { 171 rv = mFactory.getViewAt(position); 172 if (rv != null) { 173 rv.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD); 174 } 175 } catch (Exception ex) { 176 Thread t = Thread.currentThread(); 177 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 178 } 179 return rv; 180 } getLoadingView()181 public synchronized RemoteViews getLoadingView() { 182 RemoteViews rv = null; 183 try { 184 rv = mFactory.getLoadingView(); 185 } catch (Exception ex) { 186 Thread t = Thread.currentThread(); 187 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 188 } 189 return rv; 190 } getViewTypeCount()191 public synchronized int getViewTypeCount() { 192 int count = 0; 193 try { 194 count = mFactory.getViewTypeCount(); 195 } catch (Exception ex) { 196 Thread t = Thread.currentThread(); 197 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 198 } 199 return count; 200 } getItemId(int position)201 public synchronized long getItemId(int position) { 202 long id = 0; 203 try { 204 id = mFactory.getItemId(position); 205 } catch (Exception ex) { 206 Thread t = Thread.currentThread(); 207 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 208 } 209 return id; 210 } hasStableIds()211 public synchronized boolean hasStableIds() { 212 boolean hasStableIds = false; 213 try { 214 hasStableIds = mFactory.hasStableIds(); 215 } catch (Exception ex) { 216 Thread t = Thread.currentThread(); 217 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 218 } 219 return hasStableIds; 220 } onDestroy(Intent intent)221 public void onDestroy(Intent intent) { 222 synchronized (sLock) { 223 Intent.FilterComparison fc = new Intent.FilterComparison(intent); 224 if (RemoteViewsService.sRemoteViewFactories.containsKey(fc)) { 225 RemoteViewsFactory factory = RemoteViewsService.sRemoteViewFactories.get(fc); 226 try { 227 factory.onDestroy(); 228 } catch (Exception ex) { 229 Thread t = Thread.currentThread(); 230 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 231 } 232 RemoteViewsService.sRemoteViewFactories.remove(fc); 233 } 234 } 235 } 236 237 @Override getRemoteCollectionItems()238 public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() { 239 RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems 240 .Builder().build(); 241 242 try { 243 RemoteViews.RemoteCollectionItems.Builder itemsBuilder = 244 new RemoteViews.RemoteCollectionItems.Builder(); 245 mFactory.onDataSetChanged(); 246 247 itemsBuilder.setHasStableIds(mFactory.hasStableIds()); 248 final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY); 249 for (int i = 0; i < numOfEntries; i++) { 250 itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i)); 251 } 252 253 items = itemsBuilder.build(); 254 } catch (Exception ex) { 255 Thread t = Thread.currentThread(); 256 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 257 } 258 return items; 259 } 260 261 private RemoteViewsFactory mFactory; 262 private boolean mIsCreated; 263 } 264 265 @Override onBind(Intent intent)266 public IBinder onBind(Intent intent) { 267 synchronized (sLock) { 268 Intent.FilterComparison fc = new Intent.FilterComparison(intent); 269 RemoteViewsFactory factory = null; 270 boolean isCreated = false; 271 if (!sRemoteViewFactories.containsKey(fc)) { 272 factory = onGetViewFactory(intent); 273 sRemoteViewFactories.put(fc, factory); 274 factory.onCreate(); 275 isCreated = false; 276 } else { 277 factory = sRemoteViewFactories.get(fc); 278 isCreated = true; 279 } 280 return new RemoteViewsFactoryAdapter(factory, isCreated); 281 } 282 } 283 284 /** 285 * To be implemented by the derived service to generate appropriate factories for 286 * the data. 287 */ onGetViewFactory(Intent intent)288 public abstract RemoteViewsFactory onGetViewFactory(Intent intent); 289 } 290