blob: 1fd916377e965aa4eda0a49820c5104304e40516 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.browser.widget;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.provider.Browser;
import android.provider.Browser.BookmarkColumns;
import android.service.urlrenderer.UrlRenderer;
import android.service.urlrenderer.UrlRendererService;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import com.android.browser.R;
import java.io.InputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
public class BookmarkWidgetService extends Service
implements UrlRenderer.Callback {
private static final String TAG = "BookmarkWidgetService";
/** Force the bookmarks to be re-renderer. */
public static final String UPDATE = "com.android.browser.widget.UPDATE";
/** Change the widget to the next bookmark. */
private static final String NEXT = "com.android.browser.widget.NEXT";
/** Change the widget to the previous bookmark. */
private static final String PREV = "com.android.browser.widget.PREV";
/** Id of the current item displayed in the widget. */
private static final String EXTRA_ID =
"com.android.browser.widget.extra.ID";
// XXX: Remove these magic numbers once the dimensions of the widget can be
// queried.
private static final int WIDTH = 306;
private static final int HEIGHT = 386;
// Limit the number of connection attempts.
private static final int MAX_SERVICE_RETRY_COUNT = 5;
// No id specified.
private static final int NO_ID = -1;
private static final int MSG_UPDATE = 0;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE:
if (mRenderer != null) {
queryCursorAndRender();
} else {
if (++mServiceRetryCount <= MAX_SERVICE_RETRY_COUNT) {
// Service is not connected, try again in a second.
mHandler.sendEmptyMessageDelayed(MSG_UPDATE, 1000);
}
}
break;
default:
break;
}
}
};
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
mRenderer = new UrlRenderer(service);
}
public void onServiceDisconnected(ComponentName className) {
mRenderer = null;
}
};
// Id -> information map storing db ids and their result.
private final HashMap<Integer, RenderResult> mIdsToResults =
new HashMap<Integer, RenderResult>();
// List of ids in order
private final ArrayList<Integer> mIdList = new ArrayList<Integer>();
// Map of urls to ids for when a url is complete.
private final HashMap<String, Integer> mUrlsToIds =
new HashMap<String, Integer>();
// The current id used by the widget during an update.
private int mCurrentId = NO_ID;
// Class that contacts the service on the phone to render bookmarks.
private UrlRenderer mRenderer;
// Number of service retries. Stop trying to connect after
// MAX_SERVICE_RETRY_COUNT
private int mServiceRetryCount;
@Override
public void onCreate() {
bindService(new Intent(UrlRendererService.SERVICE_INTERFACE),
mConnection, Context.BIND_AUTO_CREATE);
}
@Override
public void onDestroy() {
unbindService(mConnection);
}
@Override
public android.os.IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String action = intent.getAction();
if (UPDATE.equals(action)) {
mHandler.sendEmptyMessage(MSG_UPDATE);
} else if (PREV.equals(action) && mIdList.size() > 1) {
int prev = getPreviousId(intent);
if (prev == NO_ID) {
Log.d(TAG, "Could not determine previous id");
return START_NOT_STICKY;
}
RenderResult res = mIdsToResults.get(prev);
if (res != null) {
updateWidget(res);
}
} else if (NEXT.equals(action) && mIdList.size() > 1) {
int next = getNextId(intent);
if (next == NO_ID) {
Log.d(TAG, "Could not determine next id");
return START_NOT_STICKY;
}
RenderResult res = mIdsToResults.get(next);
if (res != null) {
updateWidget(res);
}
}
return START_STICKY;
}
private int getPreviousId(Intent intent) {
int listSize = mIdList.size();
// If the list contains 1 or fewer entries, return NO_ID so that the
// widget does not update.
if (listSize <= 1) {
return NO_ID;
}
int curr = intent.getIntExtra(EXTRA_ID, NO_ID);
if (curr == NO_ID) {
return NO_ID;
}
// Check if the current id is the beginning of the list so we can skip
// iterating through.
if (mIdList.get(0) == curr) {
return mIdList.get(listSize - 1);
}
// Search for the current id and remember the previous id.
int prev = NO_ID;
for (int id : mIdList) {
if (id == curr) {
break;
}
prev = id;
}
return prev;
}
private int getNextId(Intent intent) {
int listSize = mIdList.size();
// If the list contains 1 or fewer entries, return NO_ID so that the
// widget does not update.
if (listSize <= 1) {
return NO_ID;
}
int curr = intent.getIntExtra(EXTRA_ID, NO_ID);
if (curr == NO_ID) {
return NO_ID;
}
// Check if the current id is at the end of the list so we can skip
// iterating through.
if (mIdList.get(listSize - 1) == curr) {
return mIdList.get(0);
}
// Iterate through the ids. i is set to the current index + 1.
int i = 1;
for (int id : mIdList) {
if (id == curr) {
break;
}
i++;
}
return mIdList.get(i);
}
private void updateWidget(RenderResult res) {
RemoteViews views = new RemoteViews(getPackageName(),
R.layout.bookmarkwidget);
Intent prev = new Intent(PREV, null, this, BookmarkWidgetService.class);
prev.putExtra(EXTRA_ID, res.mId);
views.setOnClickPendingIntent(R.id.previous,
PendingIntent.getService(this, 0, prev,
PendingIntent.FLAG_CANCEL_CURRENT));
Intent next = new Intent(NEXT, null, this, BookmarkWidgetService.class);
next.putExtra(EXTRA_ID, res.mId);
views.setOnClickPendingIntent(R.id.next,
PendingIntent.getService(this, 0, next,
PendingIntent.FLAG_CANCEL_CURRENT));
// Set the title of the bookmark. Use the url as a backup.
String displayTitle = res.mTitle;
if (displayTitle == null) {
displayTitle = res.mUrl;
}
views.setTextViewText(R.id.title, displayTitle);
// Set the image or revert to the progress indicator.
if (res.mBitmap != null) {
views.setImageViewBitmap(R.id.image, res.mBitmap);
views.setViewVisibility(R.id.image, View.VISIBLE);
views.setViewVisibility(R.id.progress, View.GONE);
} else {
views.setViewVisibility(R.id.progress, View.VISIBLE);
views.setViewVisibility(R.id.image, View.GONE);
}
// Update the current id.
mCurrentId = res.mId;
AppWidgetManager.getInstance(this).updateAppWidget(
new ComponentName(this, BookmarkWidgetProvider.class),
views);
}
// Default WHERE clause is all bookmarks.
private static final String QUERY_WHERE =
BookmarkColumns.BOOKMARK + " == 1";
private static final String[] PROJECTION = new String[] {
BookmarkColumns._ID, BookmarkColumns.TITLE, BookmarkColumns.URL };
// Class containing the rendering information for a specific bookmark.
private static class RenderResult {
final int mId;
final String mTitle;
final String mUrl;
Bitmap mBitmap;
RenderResult(int id, String title, String url) {
mId = id;
mTitle = title;
mUrl = url;
}
}
private void queryCursorAndRender() {
// Clear the ordered list of ids and the map of ids to bitmaps.
mIdList.clear();
mIdsToResults.clear();
// Look up all the bookmarks
Cursor c = getContentResolver().query(Browser.BOOKMARKS_URI, PROJECTION,
QUERY_WHERE, null, null);
if (c != null) {
if (c.moveToFirst()) {
ArrayList<String> urls = new ArrayList<String>(c.getCount());
boolean sawCurrentId = false;
do {
int id = c.getInt(0);
String title = c.getString(1);
String url = c.getString(2);
// Linear list of ids to obtain the previous and next.
mIdList.add(id);
// Map the url to its db id for lookup when complete.
mUrlsToIds.put(url, id);
// Is this the current id?
if (mCurrentId == id) {
sawCurrentId = true;
}
// Store the current information to at least display the
// title.
RenderResult res = new RenderResult(id, title, url);
mIdsToResults.put(id, res);
// Add the url to our list to render.
urls.add(url);
} while (c.moveToNext());
// Request a rendering of the urls. XXX: Hard-coded dimensions
// until the view's orientation and size can be determined. Or
// in the future the image will be a picture that can be
// scaled/zoomed arbitrarily.
mRenderer.render(urls, WIDTH, HEIGHT, this);
// Set the current id to the very first id if we did not see
// the current id in the list (the bookmark could have been
// deleted or this is the first update).
if (!sawCurrentId) {
mCurrentId = mIdList.get(0);
}
}
c.close();
}
}
// UrlRenderer.Callback implementation
public void complete(String url, ParcelFileDescriptor result) {
int id = mUrlsToIds.get(url);
if (id == NO_ID) {
Log.d(TAG, "No matching id found during completion of "
+ url);
return;
}
RenderResult res = mIdsToResults.get(id);
if (res == null) {
Log.d(TAG, "No result found during completion of "
+ url);
return;
}
// Set the result.
if (result != null) {
InputStream input =
new ParcelFileDescriptor.AutoCloseInputStream(result);
Bitmap orig = BitmapFactory.decodeStream(input, null, null);
// XXX: Hard-coded scaled bitmap until I can query the image
// dimensions.
res.mBitmap = Bitmap.createScaledBitmap(orig, WIDTH, HEIGHT, true);
try {
input.close();
} catch (IOException e) {
// oh well...
}
}
// If we are currently looking at the bookmark that just finished,
// update the widget.
if (mCurrentId == id) {
updateWidget(res);
}
}
}