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)131 default RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) { 132 RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems 133 .Builder().build(); 134 Parcel capSizeTestParcel = Parcel.obtain(); 135 // restore allowSquashing to reduce the noise in error messages 136 boolean prevAllowSquashing = capSizeTestParcel.allowSquashing(); 137 138 try { 139 RemoteViews.RemoteCollectionItems.Builder itemsBuilder = 140 new RemoteViews.RemoteCollectionItems.Builder(); 141 onDataSetChanged(); 142 143 itemsBuilder.setHasStableIds(hasStableIds()); 144 final int numOfEntries = getCount(); 145 146 for (int i = 0; i < numOfEntries; i++) { 147 final long currentItemId = getItemId(i); 148 final RemoteViews currentView = getViewAt(i); 149 currentView.writeToParcel(capSizeTestParcel, 0); 150 if (capSizeTestParcel.dataSize() > capSize) { 151 break; 152 } 153 itemsBuilder.addItem(currentItemId, currentView); 154 } 155 156 items = itemsBuilder.build(); 157 } finally { 158 capSizeTestParcel.restoreAllowSquashing(prevAllowSquashing); 159 // Recycle the parcel 160 capSizeTestParcel.recycle(); 161 } 162 return items; 163 } 164 } 165 166 /** 167 * A private proxy class for the private IRemoteViewsFactory interface through the 168 * public RemoteViewsFactory interface. 169 */ 170 private static class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub { RemoteViewsFactoryAdapter(RemoteViewsFactory factory, boolean isCreated)171 public RemoteViewsFactoryAdapter(RemoteViewsFactory factory, boolean isCreated) { 172 mFactory = factory; 173 mIsCreated = isCreated; 174 } isCreated()175 public synchronized boolean isCreated() { 176 return mIsCreated; 177 } onDataSetChanged()178 public synchronized void onDataSetChanged() { 179 try { 180 mFactory.onDataSetChanged(); 181 } catch (Exception ex) { 182 Thread t = Thread.currentThread(); 183 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 184 } 185 } onDataSetChangedAsync()186 public synchronized void onDataSetChangedAsync() { 187 onDataSetChanged(); 188 } getCount()189 public synchronized int getCount() { 190 int count = 0; 191 try { 192 count = mFactory.getCount(); 193 } catch (Exception ex) { 194 Thread t = Thread.currentThread(); 195 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 196 } 197 return count; 198 } getViewAt(int position)199 public synchronized RemoteViews getViewAt(int position) { 200 RemoteViews rv = null; 201 try { 202 rv = mFactory.getViewAt(position); 203 if (rv != null) { 204 rv.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD); 205 } 206 } catch (Exception ex) { 207 Thread t = Thread.currentThread(); 208 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 209 } 210 return rv; 211 } getLoadingView()212 public synchronized RemoteViews getLoadingView() { 213 RemoteViews rv = null; 214 try { 215 rv = mFactory.getLoadingView(); 216 } catch (Exception ex) { 217 Thread t = Thread.currentThread(); 218 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 219 } 220 return rv; 221 } getViewTypeCount()222 public synchronized int getViewTypeCount() { 223 int count = 0; 224 try { 225 count = mFactory.getViewTypeCount(); 226 } catch (Exception ex) { 227 Thread t = Thread.currentThread(); 228 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 229 } 230 return count; 231 } getItemId(int position)232 public synchronized long getItemId(int position) { 233 long id = 0; 234 try { 235 id = mFactory.getItemId(position); 236 } catch (Exception ex) { 237 Thread t = Thread.currentThread(); 238 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 239 } 240 return id; 241 } hasStableIds()242 public synchronized boolean hasStableIds() { 243 boolean hasStableIds = false; 244 try { 245 hasStableIds = mFactory.hasStableIds(); 246 } catch (Exception ex) { 247 Thread t = Thread.currentThread(); 248 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 249 } 250 return hasStableIds; 251 } onDestroy(Intent intent)252 public void onDestroy(Intent intent) { 253 synchronized (sLock) { 254 Intent.FilterComparison fc = new Intent.FilterComparison(intent); 255 if (RemoteViewsService.sRemoteViewFactories.containsKey(fc)) { 256 RemoteViewsFactory factory = RemoteViewsService.sRemoteViewFactories.get(fc); 257 try { 258 factory.onDestroy(); 259 } catch (Exception ex) { 260 Thread t = Thread.currentThread(); 261 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 262 } 263 RemoteViewsService.sRemoteViewFactories.remove(fc); 264 } 265 } 266 } 267 268 @Override getRemoteCollectionItems(int capSize)269 public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) { 270 RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems 271 .Builder().build(); 272 try { 273 items = mFactory.getRemoteCollectionItems(capSize); 274 } catch (Exception ex) { 275 Thread t = Thread.currentThread(); 276 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex); 277 } 278 return items; 279 } 280 281 private RemoteViewsFactory mFactory; 282 private boolean mIsCreated; 283 } 284 285 @Override onBind(Intent intent)286 public IBinder onBind(Intent intent) { 287 synchronized (sLock) { 288 Intent.FilterComparison fc = new Intent.FilterComparison(intent); 289 RemoteViewsFactory factory = null; 290 boolean isCreated = false; 291 if (!sRemoteViewFactories.containsKey(fc)) { 292 factory = onGetViewFactory(intent); 293 sRemoteViewFactories.put(fc, factory); 294 factory.onCreate(); 295 isCreated = false; 296 } else { 297 factory = sRemoteViewFactories.get(fc); 298 isCreated = true; 299 } 300 return new RemoteViewsFactoryAdapter(factory, isCreated); 301 } 302 } 303 304 /** 305 * To be implemented by the derived service to generate appropriate factories for 306 * the data. 307 */ onGetViewFactory(Intent intent)308 public abstract RemoteViewsFactory onGetViewFactory(Intent intent); 309 } 310