From 1c15ace5e630e361a48cff8f83fef207436bb80b Mon Sep 17 00:00:00 2001 From: Patrick Scott Date: Tue, 8 Dec 2009 08:51:33 -0500 Subject: [PATCH] Demo widget using the UrlRendererService for server-side rendering of urls. --- AndroidManifest.xml | 9 + res/drawable-hdpi/appwidget_bg.9.png | Bin 0 -> 2909 bytes res/drawable-mdpi/appwidget_bg.9.png | Bin 0 -> 1784 bytes res/layout/bookmarkwidget.xml | 79 +++++ res/xml/bookmarkwidget.xml | 24 ++ .../browser/widget/BookmarkWidgetProvider.java | 49 +++ .../browser/widget/BookmarkWidgetService.java | 385 +++++++++++++++++++++ 7 files changed, 546 insertions(+) create mode 100644 res/drawable-hdpi/appwidget_bg.9.png create mode 100644 res/drawable-mdpi/appwidget_bg.9.png create mode 100644 res/layout/bookmarkwidget.xml create mode 100644 res/xml/bookmarkwidget.xml create mode 100644 src/com/android/browser/widget/BookmarkWidgetProvider.java create mode 100644 src/com/android/browser/widget/BookmarkWidgetService.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index e852560..73fb710 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -178,6 +178,15 @@ + + diff --git a/res/drawable-hdpi/appwidget_bg.9.png b/res/drawable-hdpi/appwidget_bg.9.png new file mode 100644 index 0000000000000000000000000000000000000000..3b29eae77be9d42e4fafd518822c4807266ed59b GIT binary patch literal 2909 zcmV-j3!?OiP)P7@lNmCzKR8_P5=jjQGr^b{e1b$8thLHQ2P!N z1Z8Ws8?e8Hfa!je098i-WIKLh>eO6kdhg!7&#S8XXuI9MyFMcB)i1x^zJ2>W_3@E?pn)qW z+we&xvY^AGqoeDpVc)8Tor7(kcK8HKs{Wzc66`*-NPB&I-hPjwI@`I_^{FkkFxUA| z`|W-0cU9k1%A&qgr{9No5-Kn)m^D;KTG6drx4uvmcP*FY0QHmfP1`q+SUh*WTy#gdRevT9D> z?Al_ncr)jdZqG9wuHZF89SrHIZ4i|^xM4s#i1AAj;ZPw<(lj^e1GV%zn0n@$rbawq zODb|(QUxr@TGOScNlFnh-*3Z5<$BPl>wt3yu?nve0EE@ufwa*WP^u$wr@H9K)gky= zKEQH-!|{V=OHmyQs7f$#kpimMG}E(qYzo9zC80ZMoCKeM34Z!^r>!Pv-N%f?bL zX43muaJbs$OBZp@$5hbQ6C(MXjqY%)^n&AaDY8^Q<)cB4GE1R1SKJoq<0`@zl^n2Y zffW^f&uB|Kvtyd2hIm?`yGzzZJ>}FsPWixNnEtWU<)S;75~7l-F1o`BS4px}njZ34 za7p!H?l{T&6y$tred;bI6A+W3Xt9)17OE0&)@EE09TT+~_jCzP4BZ7gCZy~jwYffr zv<4{*Zhhxmaf(Z!QFP}UEOb#OhXP|u)+t@moE!J)r&L@hFbyd`h7XBSjBQY8R*?FZ z$Lq2^vB}}q=`J+*6$(@_?O{b{e>7HjA!Qc|XhVa56#d1}TrcU4lW*fFE!QxDPwGnO zjtYcB-)}2v+=(UmEDN85aS~-wuGk1#i%kJRi*iwouPu~CxZ)>iS}3>Dta4aQ$U<_< zVvdvN-cK^=sZxD2sQ!;Z>Mj<-n2#-|y9Heldtj2!slHRUlsudw22_7TE zLkU=pMGiuOUqvwLvlvN-bu}NNbC7aOsp1NT)C_k>V6;WkS59|Oy}4o{Onua_I3*Zd zSq2x?xs)Vsi)2E-lmcCXVi0u4ed@WE6q9&Ox!+ZlESBW67HaZ{Ym0MrAxm<}Z;5c= zWFWW`_Brud=OE$EaHVCa^H8PX@4#c<55(7ygbAEs@4XTZLn}j@8eQf7(YYoiO60QS zZ3^l1uaLCQl|$@=?n2ZTqB@HDpn9Z!td#CJ$J}8I>9puxNbu0KF5i!ave-8)L4h3t zh?9)vh=>@W(0d6UJL#U2MQFp($IP=BoCNnJJYgoBlG-pw=gNG$@_3m3d=Cn44-Oe5 zgMxz_KIhKJ$(=8YodX`q>7@vv-p>}w0dC5WfMhT)rIWj!5!;$@fZu`H2S|OG206Jp zNwy(}aNy1?YHNN&c1x1TVCnZlYEd!^N&1^&|E`z90b7(W`H8IwN^W=#R`_NHCBHes zfon}ELSZlxoa@k2Gu|$rl*59!Aw{m~1FdEpaMT}~V-2kcf5+#KuqC-Hh!ZMEUk&wH zlk1wffU0s?M#_`KowV;0CpmI=u|6$!;(w<;$*1~!YLK!7*EOl34DOHyhh}|&Zw|0~ zM|T*A&=2HvFeSe!>f;8mDRR8a0l%!mA!Xu5YFv>sP?g>*=Rck5gzl(Wt3HVhk3+%% zT9YJ@<>VG)CAskbIP^c5%_WC(`G&syU%bwIQplJ1j$H!57o_5A`QEb2p+0>SJ(%1b zB^G=QcTpXtJ4!hCsLrJTb7UW4hbRV-ao`RIaE75dD$zF*?8^OJRZgyRO9~BRQ=d~- z1JAi~bn9!1E~*+%=`Gw-P+X!G}6JWRcQAN$CXtQ09J6308@s%Kh49Y^>7< zby(*Hfs^#41c&eMeY0R)>P|X3o4dn%m0-=4f#4=DxkD9PT66t-hb*t;9Iv1g8hN6xG0oJ4N+T6TF4hG#l}vY_jh}laiD@q$`85Ttf1sO^>-2p z9*UK??>G5qa8Pu|rI6SRy2K~t^q-y6ow{im-_K2})`uz3HQ2{hlAM{gG4#d^X4$K} zq|#lJavbB>(nJ$`ZPt3imRnS?I1o_4#U#lgVVZ*=%lb60O*Ggx)!F`5ubyB&j0~ z-Gu^MsWh+adRAOsC9{!Ola%gu2&msfi-E*iOUMGbS%#QV-CQfhfHwSQ`s>nCrfv8n zdtrT}^OYV?PfvePPsuukDq?eKF>h7m@OP32QJ*_FIC%8%;luBXXDei4pt6C~5CG}A zQj|4U^&9;ewTx$s^>AP1j8#`T{gSW!razkHa`~0oE)7U{2~(Cu)k3PXAg#=e&*t;_ zABTsB_f`FDy^84r)FQDdkX09+@eCM8wuTQO^4gYz*V)Z zuvnW$LXaje0Z7mdEDR8HQy-h-MRBpbwutU(2A4M=_p}3zfr~ZS^R|J9$dal(25E=- zWohW_3|P(+0Fw1djp^QO!U0-Sfh@zGUzvs-78eUMP6)ay_XM1R2=G^+o*7_lUB-xp z7?7YfQP#C6E|%%ERx}}i-6yImK>V`>3T_2_kF}spYalfjQf=n}Z4JZ)q`7L(tcbFv z9k8Y?00eHgkmhW^X0;>&sD_B2@DiQ-NOeBS!(eS)=^)8qoM=1=0aYT9Ffig(AO}u) zIJdb&z%(%ci9td@Q4K5rKn1d%eFdpN{$=Ormm&tBD3I`t2w#N(*++0(U=@@gNS$Yp zV@&lW6um--tJIaR8?2^%y_M= zEtTfScK6ihs;_=#+GW>uMc-Fhmi-4A@a1$Wd+Rg=Fc0xn9UwRWa{%T5%mJ7KFnjZ= z6LOUjfmwRlcVJscYrM2R$LR(E#sd;SnE3D1e!oKClzRe3hd=>ooNT-_PMy}{(`5sL zK!MLqoy^Y9&%dau>dVb$^Dclgolc9ouD1gFlgZ@%7)v-0TP$v@dx7cNwk?{bd1>qB z+U|pdeb{QUUaz;{xH+!e7~fo6Tzu<}S5DxK4a_Bh1DU*-`4UiOP9B||oqczEd;2(e z6)1z11jXT8*kUlkG2T*80x%$OT>%&hRuBpr1`dw{GM~?Xy}Z2q)Lr(%1GI7HTM3xT zK+a$(k4{fdKfk)VdVF_xSAc}c!^9xr7@x-%h_{?@K+x|?rJ`Et3O z_W>b%myVGOgy)E)`B$seTL_q1)87KjF%0I+vZB@!E-)9Q9kn1Zse`_6dYx)5lGYJx zns|RamXt~{e+ir%E3$qETbADjj+tnNQ3C@Djhzw!b3_&4$PS_l-iAmMtO{>K)WH#n zj;#?fC4BCBxbvV3%EPkAw?G;0WJnjmRwjLCD0gGVnV{^wS4nU`h}C>kU4yem&P`)r z8b4<$Wom=P{BJVu;yF{suEY^He9q!ZmRv}3A%zFa!K>?3;*|JFBXMmU&LX5B5F4|1 zbtX`rpj3iwj3_`qPdQgk4Ub>0$?vsP=ujPVxfq$Igz%x7V53=#W<64U zgo&36=US2%v!WqLxEk78`ZTXJ3$fH%2^IG6$Wq{;LoIAzBvE)h;*SwMhFQwv*g=kBe zp^dRKSIX(hP;23e$CxX{I(lP}Z=FN5TH22Hse9nu1-p~w27O8ceCOngm7)1eNb^Ik zLM?{gowS7OSPLSxfZR!=3ImYiLRy_ffeGWq`Y;xk8kKx4eUiRyQmOBv-CG*c#j-4h zGjZ%r((Mz?TBJr*jD%Af;-hx(D5+|{R?JN>DAwvYw}>6xVz>+!TMEYyQka&alI4*T zEi*RWyB?NfED2YvkCNLf))GK%jGQ_uJM*x3kxgH~IOUFNU&&k>}2OSEuM5nLCfWUH=Mbk8I zNW~tt7Kw1N&`R4<$;T{0YmiT;(|?K=%XGRv7Ss7bU>VDulOI!l-IV&s`XrR)*}Vuy zeeZQyBY={DX%R5X>+9>Ej*gC=;3YBE#<(Q{SvV#&QsQMiv)Sy4)8S{(1pyob*IEN4VU5rC4g95i|NsSJ(Bz?f-If zbMu2c{>{nnPX2Q8H-O~cYZ%?tgW^1m zx9q6YdVgH_g0000 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/bookmarkwidget.xml b/res/xml/bookmarkwidget.xml new file mode 100644 index 0000000..adbe5f2 --- /dev/null +++ b/res/xml/bookmarkwidget.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/src/com/android/browser/widget/BookmarkWidgetProvider.java b/src/com/android/browser/widget/BookmarkWidgetProvider.java new file mode 100644 index 0000000..62b48c0 --- /dev/null +++ b/src/com/android/browser/widget/BookmarkWidgetProvider.java @@ -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 index 0000000..1fd9163 --- /dev/null +++ b/src/com/android/browser/widget/BookmarkWidgetService.java @@ -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 mIdsToResults = + new HashMap(); + + // List of ids in order + private final ArrayList mIdList = new ArrayList(); + + // Map of urls to ids for when a url is complete. + private final HashMap mUrlsToIds = + new HashMap(); + + // 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 urls = new ArrayList(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); + } + } +} -- 2.11.0