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