OSDN Git Service

Demo widget using the UrlRendererService for server-side rendering of urls.
authorPatrick Scott <phanna@android.com>
Tue, 8 Dec 2009 13:51:33 +0000 (08:51 -0500)
committerPatrick Scott <phanna@android.com>
Thu, 28 Jan 2010 19:09:28 +0000 (14:09 -0500)
AndroidManifest.xml
res/drawable-hdpi/appwidget_bg.9.png [new file with mode: 0644]
res/drawable-mdpi/appwidget_bg.9.png [new file with mode: 0644]
res/layout/bookmarkwidget.xml [new file with mode: 0644]
res/xml/bookmarkwidget.xml [new file with mode: 0644]
src/com/android/browser/widget/BookmarkWidgetProvider.java [new file with mode: 0644]
src/com/android/browser/widget/BookmarkWidgetService.java [new file with mode: 0644]

index e852560..73fb710 100644 (file)
             </intent-filter>
         </activity>
 
+        <!--receiver android:name=".widget.BookmarkWidgetProvider" android:label="@string/bookmarks">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider" android:resource="@xml/bookmarkwidget"/>
+        </receiver>
+
+        <service android:name=".widget.BookmarkWidgetService" /-->
+
         <!-- Makes .BrowserActivity the search target for any activity in Browser -->
         <meta-data android:name="android.app.default_searchable" android:value=".BrowserActivity" />
 
diff --git a/res/drawable-hdpi/appwidget_bg.9.png b/res/drawable-hdpi/appwidget_bg.9.png
new file mode 100644 (file)
index 0000000..3b29eae
Binary files /dev/null and b/res/drawable-hdpi/appwidget_bg.9.png differ
diff --git a/res/drawable-mdpi/appwidget_bg.9.png b/res/drawable-mdpi/appwidget_bg.9.png
new file mode 100644 (file)
index 0000000..afe41b6
Binary files /dev/null and b/res/drawable-mdpi/appwidget_bg.9.png differ
diff --git a/res/layout/bookmarkwidget.xml b/res/layout/bookmarkwidget.xml
new file mode 100644 (file)
index 0000000..2416e4f
--- /dev/null
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="8dip"
+    android:orientation="vertical"
+    android:background="@drawable/appwidget_bg" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <ImageView
+            android:id="@+id/previous"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="10dip"
+            android:src="@drawable/ic_btn_find_prev" />
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:layout_gravity="center"
+            android:text="@string/title_bar_loading"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorPrimary" />
+
+        <ImageView
+            android:id="@+id/next"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="10dip"
+            android:src="@drawable/ic_btn_find_next" />
+
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/image"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="fitXY"
+        android:padding="5dip"
+        android:gravity="center" />
+
+    <RelativeLayout
+        android:id="@+id/progress"
+        android:visibility="visible"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center" >
+
+        <ProgressBar
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:indeterminateOnly="true" />
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/res/xml/bookmarkwidget.xml b/res/xml/bookmarkwidget.xml
new file mode 100644 (file)
index 0000000..adbe5f2
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- 4x4 Widget displaying the user's bookmarks as thumbnails. -->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="294dip"
+    android:minHeight="294dip"
+    android:updatePeriodMillis="3600000"
+    android:initialLayout="@layout/bookmarkwidget"
+    >
+</appwidget-provider>
diff --git a/src/com/android/browser/widget/BookmarkWidgetProvider.java b/src/com/android/browser/widget/BookmarkWidgetProvider.java
new file mode 100644 (file)
index 0000000..62b48c0
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * Widget that shows a preview of the user's bookmarks.
+ */
+public class BookmarkWidgetProvider extends AppWidgetProvider {
+
+    static final String TAG = "BookmarkWidgetProvider";
+
+    @Override
+    public void onUpdate(Context context, AppWidgetManager mngr, int[] ids) {
+        context.startService(new Intent(BookmarkWidgetService.UPDATE, null,
+                    context, BookmarkWidgetService.class));
+    }
+
+    @Override
+    public void onEnabled(Context context) {
+        context.startService(new Intent(context, BookmarkWidgetService.class));
+    }
+
+    @Override
+    public void onDisabled(Context context) {
+        context.stopService(new Intent(context, BookmarkWidgetService.class));
+    }
+}
+
diff --git a/src/com/android/browser/widget/BookmarkWidgetService.java b/src/com/android/browser/widget/BookmarkWidgetService.java
new file mode 100644 (file)
index 0000000..1fd9163
--- /dev/null
@@ -0,0 +1,385 @@
+/*
+ * 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);
+        }
+    }
+}