initial checkin of stacked thumbnail bookmarks widget
  bug #2930549
  uses a StackView to let the user flip through the browser
  bookmark thumbnails

Change-Id: I3665e98e5fd4c14ede8f28380ec6c85e7cc032f2
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3a0cee5..f50c9f0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -215,6 +215,21 @@
 
         <service android:name=".widget.BookmarkWidgetService" /-->
 
+        <receiver
+            android:name=".widget.BookmarkStackWidgetProvider"
+            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/bookmarkstackwidget" />
+        </receiver>
+        <service
+            android:name=".widget.BookmarkStackWidgetService"
+            android:exported="true" />
+
         <!-- 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/layout/bookmarkstackwidget.xml b/res/layout/bookmarkstackwidget.xml
new file mode 100644
index 0000000..c8c88c4
--- /dev/null
+++ b/res/layout/bookmarkstackwidget.xml
@@ -0,0 +1,40 @@
+<?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.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <StackView
+        android:id="@+id/stackwidget_stack"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:paddingTop="16dip"
+        android:paddingBottom="8dip"
+        android:paddingLeft="8dip"
+        android:paddingRight="8dip"
+        android:background="#00000000"
+        android:cacheColorHint="#00000000"
+        android:autoStart="true" />
+    <ImageView
+        android:id="@+id/logo"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:src="@drawable/ic_launcher_shortcut_browser_bookmark" />
+</RelativeLayout>
diff --git a/res/layout/bookmarkstackwidget_item.xml b/res/layout/bookmarkstackwidget_item.xml
new file mode 100644
index 0000000..e7992f7
--- /dev/null
+++ b/res/layout/bookmarkstackwidget_item.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/stack_item"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="16dip">
+    <ImageView
+        android:id="@+id/thumb"
+        android:src="@drawable/browser_thumbnail"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:scaleType="fitXY"
+        android:layout_centerInParent="true" />
+    <TextView
+        android:id="@+id/label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textStyle="bold"
+        android:textColor="@color/white"
+        android:maxLines="1"
+        android:paddingLeft="2dip"
+        android:paddingRight="2dip"
+        android:paddingTop="0dip"
+        android:paddingBottom="0dip"
+        android:scrollHorizontally="true"
+        android:ellipsize="marquee"
+        android:layout_alignBottom="@id/thumb"
+        android:layout_alignLeft="@id/thumb"
+        android:layout_alignRight="@id/thumb" />
+</RelativeLayout>
diff --git a/res/xml/bookmarkstackwidget.xml b/res/xml/bookmarkstackwidget.xml
new file mode 100644
index 0000000..1879923
--- /dev/null
+++ b/res/xml/bookmarkstackwidget.xml
@@ -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.
+-->
+
+<!-- 2x2 Widget displaying the user's bookmarks as thumbnails. -->
+<appwidget-provider
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="146dip"
+    android:minHeight="146dip"
+    android:updatePeriodMillis="3600000"
+    android:initialLayout="@layout/bookmarkstackwidget">
+</appwidget-provider>
diff --git a/src/com/android/browser/widget/BookmarkStackWidgetProvider.java b/src/com/android/browser/widget/BookmarkStackWidgetProvider.java
new file mode 100644
index 0000000..0684c61
--- /dev/null
+++ b/src/com/android/browser/widget/BookmarkStackWidgetProvider.java
@@ -0,0 +1,45 @@
+/*
+ * 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.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Widget that shows a preview of the user's bookmarks.
+ */
+public class BookmarkStackWidgetProvider extends AppWidgetProvider {
+
+    @Override
+    public void onUpdate(Context context, AppWidgetManager mngr, int[] ids) {
+        context.startService(new Intent(BookmarkStackWidgetService.UPDATE, null,
+                    context, BookmarkStackWidgetService.class));
+    }
+
+    @Override
+    public void onEnabled(Context context) {
+        context.startService(new Intent(context, BookmarkStackWidgetService.class));
+    }
+
+    @Override
+    public void onDisabled(Context context) {
+        context.stopService(new Intent(context, BookmarkStackWidgetService.class));
+    }
+
+}
diff --git a/src/com/android/browser/widget/BookmarkStackWidgetService.java b/src/com/android/browser/widget/BookmarkStackWidgetService.java
new file mode 100644
index 0000000..5334499
--- /dev/null
+++ b/src/com/android/browser/widget/BookmarkStackWidgetService.java
@@ -0,0 +1,222 @@
+/*
+ * 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.content.ComponentName;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.BrowserContract;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import com.android.browser.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BookmarkStackWidgetService extends RemoteViewsService {
+
+    private static final String LOGTAG = "browserwidget";
+
+    /** Force the bookmarks to be re-renderer. */
+    public static final String UPDATE = "com.android.browser.widget.UPDATE";
+
+    /** the adapter intent action */
+    public static final String ADAPTER = "com.android.browser.widget.ADAPTER";
+
+    private static final String EXTRA_ID = "_id";
+    private static final String EXTRA_URL = "_url";
+
+    private static final String[] PROJECTION = new String[] {
+        BrowserContract.Bookmarks._ID,
+        BrowserContract.Bookmarks.TITLE,
+        BrowserContract.Bookmarks.URL,
+        BrowserContract.Bookmarks.THUMBNAIL };
+
+    private static final String WHERE_CLAUSE = BrowserContract.Bookmarks.IS_FOLDER +
+            " == 0";
+
+    // No id specified.
+    private static final int NO_ID = -1;
+
+    private static final int MSG_UPDATE = 0;
+
+    List<RenderResult> mBookmarks;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE:
+                    updateWidget();
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        final String action = intent.getAction();
+        if (UPDATE.equals(action)) {
+            mHandler.sendEmptyMessage(MSG_UPDATE);
+        }
+        return START_STICKY;
+    }
+
+    private void updateWidget() {
+        RemoteViews views = new RemoteViews(getPackageName(),
+                R.layout.bookmarkstackwidget);
+        Intent adapter = new Intent(BookmarkStackWidgetService.ADAPTER, null,
+                this, BookmarkStackWidgetService.class);
+        views.setRemoteAdapter(R.id.stackwidget_stack, adapter);
+        AppWidgetManager.getInstance(this).updateAppWidget(
+                new ComponentName(this, BookmarkStackWidgetProvider.class),
+                views);
+    }
+
+    @Override
+    public RemoteViewsFactory onGetViewFactory(Intent intent) {
+        return mViewFactory;
+    }
+
+    RemoteViewsService.RemoteViewsFactory mViewFactory = new RemoteViewsFactory () {
+
+        @Override
+        public int getCount() {
+            return mBookmarks.size();
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public RemoteViews getLoadingView() {
+            return null;
+        }
+
+        @Override
+        public RemoteViews getViewAt(int position) {
+            RenderResult res = mBookmarks.get(position);
+            RemoteViews views = new RemoteViews(getPackageName(),
+                    R.layout.bookmarkstackwidget_item);
+            views.setOnClickPendingIntent(R.id.stack_item,
+                    getOpenUrlPendingIntent(res.mUrl));
+
+            // Set the title of the bookmark. Use the url as a backup.
+            String displayTitle = res.mTitle;
+            if (TextUtils.isEmpty(displayTitle)) {
+                displayTitle = res.mUrl;
+            }
+            views.setTextViewText(R.id.label, displayTitle);
+            if (res.mBitmap != null) {
+                views.setImageViewBitmap(R.id.thumb, res.mBitmap);
+                views.setViewVisibility(R.id.label, View.GONE);
+            }
+            return views;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 1;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return false;
+        }
+
+        @Override
+        public void onCreate() {
+            update();
+        }
+
+        @Override
+        public void onDestroy() {
+        }
+
+        public void update() {
+            mBookmarks = new ArrayList<RenderResult>();
+            // Look up all the bookmarks
+            Cursor c = null;
+            try {
+                c = getContentResolver().query(BrowserContract.Bookmarks.CONTENT_URI,
+                        PROJECTION, WHERE_CLAUSE, null, null);
+                if (c != null) {
+                    while (c.moveToNext()) {
+                        int id = c.getInt(0);
+                        String title = c.getString(1);
+                        String url = c.getString(2);
+                        RenderResult res = new RenderResult(id, title, url);
+                        byte[] blob = c.getBlob(3);
+                        if (blob != null) {
+                            res.mBitmap = BitmapFactory.decodeByteArray(blob, 0, blob.length);
+                        }
+                        mBookmarks.add(res);
+                    }
+                }
+            } catch (IllegalStateException e) {
+                Log.e(LOGTAG, "update bookmark widget", e);
+            } finally {
+                if (c != null) {
+                    c.close();
+                }
+            }
+        }
+
+        @Override
+        public void onDataSetChanged() {
+        }
+    };
+
+    private PendingIntent getOpenUrlPendingIntent(String url) {
+        Intent vi = new Intent(Intent.ACTION_VIEW);
+        vi.setData(Uri.parse(url));
+        return PendingIntent.getActivity(this, 0, vi, PendingIntent.FLAG_CANCEL_CURRENT);
+    }
+
+
+    // 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;
+        }
+
+    }
+
+
+}