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