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