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