BookmarkWidgetService.java revision 1c15ace5e630e361a48cff8f83fef207436bb80b
1/*
2 * Copyright (C) 2010 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
17package com.android.browser.widget;
18
19import android.app.PendingIntent;
20import android.app.Service;
21import android.appwidget.AppWidgetManager;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.ServiceConnection;
26import android.database.Cursor;
27import android.graphics.Bitmap;
28import android.graphics.BitmapFactory;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.Message;
32import android.os.ParcelFileDescriptor;
33import android.provider.Browser;
34import android.provider.Browser.BookmarkColumns;
35import android.service.urlrenderer.UrlRenderer;
36import android.service.urlrenderer.UrlRendererService;
37import android.util.Log;
38import android.view.View;
39import android.widget.RemoteViews;
40
41import com.android.browser.R;
42
43import java.io.InputStream;
44import java.io.IOException;
45import java.util.ArrayList;
46import java.util.HashMap;
47
48public class BookmarkWidgetService extends Service
49        implements UrlRenderer.Callback {
50
51    private static final String TAG = "BookmarkWidgetService";
52
53    /** Force the bookmarks to be re-renderer. */
54    public static final String UPDATE = "com.android.browser.widget.UPDATE";
55
56    /** Change the widget to the next bookmark. */
57    private static final String NEXT = "com.android.browser.widget.NEXT";
58
59    /** Change the widget to the previous bookmark. */
60    private static final String PREV = "com.android.browser.widget.PREV";
61
62    /** Id of the current item displayed in the widget. */
63    private static final String EXTRA_ID =
64            "com.android.browser.widget.extra.ID";
65
66    // XXX: Remove these magic numbers once the dimensions of the widget can be
67    // queried.
68    private static final int WIDTH = 306;
69    private static final int HEIGHT = 386;
70
71    // Limit the number of connection attempts.
72    private static final int MAX_SERVICE_RETRY_COUNT = 5;
73
74    // No id specified.
75    private static final int NO_ID = -1;
76
77    private static final int MSG_UPDATE = 0;
78    private final Handler mHandler = new Handler() {
79        @Override
80        public void handleMessage(Message msg) {
81            switch (msg.what) {
82                case MSG_UPDATE:
83                    if (mRenderer != null) {
84                        queryCursorAndRender();
85                    } else {
86                        if (++mServiceRetryCount <= MAX_SERVICE_RETRY_COUNT) {
87                            // Service is not connected, try again in a second.
88                            mHandler.sendEmptyMessageDelayed(MSG_UPDATE, 1000);
89                        }
90                    }
91                    break;
92                default:
93                    break;
94            }
95        }
96    };
97
98    private final ServiceConnection mConnection = new ServiceConnection() {
99        public void onServiceConnected(ComponentName className,
100                IBinder service) {
101            mRenderer = new UrlRenderer(service);
102        }
103
104        public void onServiceDisconnected(ComponentName className) {
105            mRenderer = null;
106        }
107    };
108
109    // Id -> information map storing db ids and their result.
110    private final HashMap<Integer, RenderResult> mIdsToResults =
111            new HashMap<Integer, RenderResult>();
112
113    // List of ids in order
114    private final ArrayList<Integer> mIdList = new ArrayList<Integer>();
115
116    // Map of urls to ids for when a url is complete.
117    private final HashMap<String, Integer> mUrlsToIds =
118            new HashMap<String, Integer>();
119
120    // The current id used by the widget during an update.
121    private int mCurrentId = NO_ID;
122    // Class that contacts the service on the phone to render bookmarks.
123    private UrlRenderer mRenderer;
124    // Number of service retries. Stop trying to connect after
125    // MAX_SERVICE_RETRY_COUNT
126    private int mServiceRetryCount;
127
128    @Override
129    public void onCreate() {
130        bindService(new Intent(UrlRendererService.SERVICE_INTERFACE),
131                mConnection, Context.BIND_AUTO_CREATE);
132    }
133
134    @Override
135    public void onDestroy() {
136        unbindService(mConnection);
137    }
138
139    @Override
140    public android.os.IBinder onBind(Intent intent) {
141        return null;
142    }
143
144    @Override
145    public int onStartCommand(Intent intent, int flags, int startId) {
146        final String action = intent.getAction();
147        if (UPDATE.equals(action)) {
148            mHandler.sendEmptyMessage(MSG_UPDATE);
149        } else if (PREV.equals(action) && mIdList.size() > 1) {
150            int prev = getPreviousId(intent);
151            if (prev == NO_ID) {
152                Log.d(TAG, "Could not determine previous id");
153                return START_NOT_STICKY;
154            }
155            RenderResult res = mIdsToResults.get(prev);
156            if (res != null) {
157                updateWidget(res);
158            }
159        } else if (NEXT.equals(action) && mIdList.size() > 1) {
160            int next = getNextId(intent);
161            if (next == NO_ID) {
162                Log.d(TAG, "Could not determine next id");
163                return START_NOT_STICKY;
164            }
165            RenderResult res = mIdsToResults.get(next);
166            if (res != null) {
167                updateWidget(res);
168            }
169        }
170        return START_STICKY;
171    }
172
173    private int getPreviousId(Intent intent) {
174        int listSize = mIdList.size();
175        // If the list contains 1 or fewer entries, return NO_ID so that the
176        // widget does not update.
177        if (listSize <= 1) {
178            return NO_ID;
179        }
180
181        int curr = intent.getIntExtra(EXTRA_ID, NO_ID);
182        if (curr == NO_ID) {
183            return NO_ID;
184        }
185
186        // Check if the current id is the beginning of the list so we can skip
187        // iterating through.
188        if (mIdList.get(0) == curr) {
189            return mIdList.get(listSize - 1);
190        }
191
192        // Search for the current id and remember the previous id.
193        int prev = NO_ID;
194        for (int id : mIdList) {
195            if (id == curr) {
196                break;
197            }
198            prev = id;
199        }
200        return prev;
201    }
202
203    private int getNextId(Intent intent) {
204        int listSize = mIdList.size();
205        // If the list contains 1 or fewer entries, return NO_ID so that the
206        // widget does not update.
207        if (listSize <= 1) {
208            return NO_ID;
209        }
210
211        int curr = intent.getIntExtra(EXTRA_ID, NO_ID);
212        if (curr == NO_ID) {
213            return NO_ID;
214        }
215
216        // Check if the current id is at the end of the list so we can skip
217        // iterating through.
218        if (mIdList.get(listSize - 1) == curr) {
219            return mIdList.get(0);
220        }
221
222        // Iterate through the ids. i is set to the current index + 1.
223        int i = 1;
224        for (int id : mIdList) {
225            if (id == curr) {
226                break;
227            }
228            i++;
229        }
230        return mIdList.get(i);
231    }
232
233    private void updateWidget(RenderResult res) {
234        RemoteViews views = new RemoteViews(getPackageName(),
235                R.layout.bookmarkwidget);
236
237        Intent prev = new Intent(PREV, null, this, BookmarkWidgetService.class);
238        prev.putExtra(EXTRA_ID, res.mId);
239        views.setOnClickPendingIntent(R.id.previous,
240                PendingIntent.getService(this, 0, prev,
241                    PendingIntent.FLAG_CANCEL_CURRENT));
242
243        Intent next = new Intent(NEXT, null, this, BookmarkWidgetService.class);
244        next.putExtra(EXTRA_ID, res.mId);
245        views.setOnClickPendingIntent(R.id.next,
246                PendingIntent.getService(this, 0, next,
247                    PendingIntent.FLAG_CANCEL_CURRENT));
248
249        // Set the title of the bookmark. Use the url as a backup.
250        String displayTitle = res.mTitle;
251        if (displayTitle == null) {
252            displayTitle = res.mUrl;
253        }
254        views.setTextViewText(R.id.title, displayTitle);
255
256        // Set the image or revert to the progress indicator.
257        if (res.mBitmap != null) {
258            views.setImageViewBitmap(R.id.image, res.mBitmap);
259            views.setViewVisibility(R.id.image, View.VISIBLE);
260            views.setViewVisibility(R.id.progress, View.GONE);
261        } else {
262            views.setViewVisibility(R.id.progress, View.VISIBLE);
263            views.setViewVisibility(R.id.image, View.GONE);
264        }
265
266        // Update the current id.
267        mCurrentId = res.mId;
268
269        AppWidgetManager.getInstance(this).updateAppWidget(
270                new ComponentName(this, BookmarkWidgetProvider.class),
271                views);
272    }
273
274    // Default WHERE clause is all bookmarks.
275    private static final String QUERY_WHERE =
276            BookmarkColumns.BOOKMARK + " == 1";
277    private static final String[] PROJECTION = new String[] {
278            BookmarkColumns._ID, BookmarkColumns.TITLE, BookmarkColumns.URL };
279
280    // Class containing the rendering information for a specific bookmark.
281    private static class RenderResult {
282        final int    mId;
283        final String mTitle;
284        final String mUrl;
285        Bitmap       mBitmap;
286
287        RenderResult(int id, String title, String url) {
288            mId = id;
289            mTitle = title;
290            mUrl = url;
291        }
292    }
293
294    private void queryCursorAndRender() {
295        // Clear the ordered list of ids and the map of ids to bitmaps.
296        mIdList.clear();
297        mIdsToResults.clear();
298
299        // Look up all the bookmarks
300        Cursor c = getContentResolver().query(Browser.BOOKMARKS_URI, PROJECTION,
301                QUERY_WHERE, null, null);
302        if (c != null) {
303            if (c.moveToFirst()) {
304                ArrayList<String> urls = new ArrayList<String>(c.getCount());
305                boolean sawCurrentId = false;
306                do {
307                    int id = c.getInt(0);
308                    String title = c.getString(1);
309                    String url = c.getString(2);
310
311                    // Linear list of ids to obtain the previous and next.
312                    mIdList.add(id);
313
314                    // Map the url to its db id for lookup when complete.
315                    mUrlsToIds.put(url, id);
316
317                    // Is this the current id?
318                    if (mCurrentId == id) {
319                        sawCurrentId = true;
320                    }
321
322                    // Store the current information to at least display the
323                    // title.
324                    RenderResult res = new RenderResult(id, title, url);
325                    mIdsToResults.put(id, res);
326
327                    // Add the url to our list to render.
328                    urls.add(url);
329                } while (c.moveToNext());
330
331                // Request a rendering of the urls. XXX: Hard-coded dimensions
332                // until the view's orientation and size can be determined. Or
333                // in the future the image will be a picture that can be
334                // scaled/zoomed arbitrarily.
335                mRenderer.render(urls, WIDTH, HEIGHT, this);
336
337                // Set the current id to the very first id if we did not see
338                // the current id in the list (the bookmark could have been
339                // deleted or this is the first update).
340                if (!sawCurrentId) {
341                    mCurrentId = mIdList.get(0);
342                }
343            }
344            c.close();
345        }
346    }
347
348    // UrlRenderer.Callback implementation
349    public void complete(String url, ParcelFileDescriptor result) {
350        int id = mUrlsToIds.get(url);
351        if (id == NO_ID) {
352            Log.d(TAG, "No matching id found during completion of "
353                    + url);
354            return;
355        }
356
357        RenderResult res = mIdsToResults.get(id);
358        if (res == null) {
359            Log.d(TAG, "No result found during completion of "
360                    + url);
361            return;
362        }
363
364        // Set the result.
365        if (result != null) {
366            InputStream input =
367                    new ParcelFileDescriptor.AutoCloseInputStream(result);
368            Bitmap orig = BitmapFactory.decodeStream(input, null, null);
369            // XXX: Hard-coded scaled bitmap until I can query the image
370            // dimensions.
371            res.mBitmap = Bitmap.createScaledBitmap(orig, WIDTH, HEIGHT, true);
372            try {
373                input.close();
374            } catch (IOException e) {
375                // oh well...
376            }
377        }
378
379        // If we are currently looking at the bookmark that just finished,
380        // update the widget.
381        if (mCurrentId == id) {
382            updateWidget(res);
383        }
384    }
385}
386