• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 
18 package com.android.browser;
19 
20 import android.content.ContentResolver;
21 import android.content.ContentUris;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.database.Cursor;
25 import android.database.sqlite.SQLiteException;
26 import android.graphics.Bitmap;
27 import android.net.Uri;
28 import android.os.Handler;
29 import android.os.Message;
30 import android.provider.BrowserContract;
31 import android.provider.BrowserContract.History;
32 import android.util.Log;
33 
34 import com.android.browser.provider.BrowserProvider2.Thumbnails;
35 
36 import java.nio.ByteBuffer;
37 import java.util.concurrent.BlockingQueue;
38 import java.util.concurrent.LinkedBlockingQueue;
39 
40 public class DataController {
41     private static final String LOGTAG = "DataController";
42     // Message IDs
43     private static final int HISTORY_UPDATE_VISITED = 100;
44     private static final int HISTORY_UPDATE_TITLE = 101;
45     private static final int QUERY_URL_IS_BOOKMARK = 200;
46     private static final int TAB_LOAD_THUMBNAIL = 201;
47     private static final int TAB_SAVE_THUMBNAIL = 202;
48     private static final int TAB_DELETE_THUMBNAIL = 203;
49     private static DataController sInstance;
50 
51     private Context mContext;
52     private DataControllerHandler mDataHandler;
53     private Handler mCbHandler; // To respond on the UI thread
54     private ByteBuffer mBuffer; // to capture thumbnails
55 
56     /* package */ static interface OnQueryUrlIsBookmark {
onQueryUrlIsBookmark(String url, boolean isBookmark)57         void onQueryUrlIsBookmark(String url, boolean isBookmark);
58     }
59     private static class CallbackContainer {
60         Object replyTo;
61         Object[] args;
62     }
63 
64     private static class DCMessage {
65         int what;
66         Object obj;
67         Object replyTo;
DCMessage(int w, Object o)68         DCMessage(int w, Object o) {
69             what = w;
70             obj = o;
71         }
72     }
73 
getInstance(Context c)74     /* package */ static DataController getInstance(Context c) {
75         if (sInstance == null) {
76             sInstance = new DataController(c);
77         }
78         return sInstance;
79     }
80 
DataController(Context c)81     private DataController(Context c) {
82         mContext = c.getApplicationContext();
83         mDataHandler = new DataControllerHandler();
84         mDataHandler.start();
85         mCbHandler = new Handler() {
86             @Override
87             public void handleMessage(Message msg) {
88                 CallbackContainer cc = (CallbackContainer) msg.obj;
89                 switch (msg.what) {
90                     case QUERY_URL_IS_BOOKMARK: {
91                         OnQueryUrlIsBookmark cb = (OnQueryUrlIsBookmark) cc.replyTo;
92                         String url = (String) cc.args[0];
93                         boolean isBookmark = (Boolean) cc.args[1];
94                         cb.onQueryUrlIsBookmark(url, isBookmark);
95                         break;
96                     }
97                 }
98             }
99         };
100     }
101 
updateVisitedHistory(String url)102     public void updateVisitedHistory(String url) {
103         mDataHandler.sendMessage(HISTORY_UPDATE_VISITED, url);
104     }
105 
updateHistoryTitle(String url, String title)106     public void updateHistoryTitle(String url, String title) {
107         mDataHandler.sendMessage(HISTORY_UPDATE_TITLE, new String[] { url, title });
108     }
109 
queryBookmarkStatus(String url, OnQueryUrlIsBookmark replyTo)110     public void queryBookmarkStatus(String url, OnQueryUrlIsBookmark replyTo) {
111         if (url == null || url.trim().length() == 0) {
112             // null or empty url is never a bookmark
113             replyTo.onQueryUrlIsBookmark(url, false);
114             return;
115         }
116         mDataHandler.sendMessage(QUERY_URL_IS_BOOKMARK, url.trim(), replyTo);
117     }
118 
loadThumbnail(Tab tab)119     public void loadThumbnail(Tab tab) {
120         mDataHandler.sendMessage(TAB_LOAD_THUMBNAIL, tab);
121     }
122 
deleteThumbnail(Tab tab)123     public void deleteThumbnail(Tab tab) {
124         mDataHandler.sendMessage(TAB_DELETE_THUMBNAIL, tab.getId());
125     }
126 
saveThumbnail(Tab tab)127     public void saveThumbnail(Tab tab) {
128         mDataHandler.sendMessage(TAB_SAVE_THUMBNAIL, tab);
129     }
130 
131     // The standard Handler and Message classes don't allow the queue manipulation
132     // we want (such as peeking). So we use our own queue.
133     class DataControllerHandler extends Thread {
134         private BlockingQueue<DCMessage> mMessageQueue
135                 = new LinkedBlockingQueue<DCMessage>();
136 
DataControllerHandler()137         public DataControllerHandler() {
138             super("DataControllerHandler");
139         }
140 
141         @Override
run()142         public void run() {
143             setPriority(Thread.MIN_PRIORITY);
144             while (true) {
145                 try {
146                     handleMessage(mMessageQueue.take());
147                 } catch (InterruptedException ex) {
148                     break;
149                 }
150             }
151         }
152 
sendMessage(int what, Object obj)153         void sendMessage(int what, Object obj) {
154             DCMessage m = new DCMessage(what, obj);
155             mMessageQueue.add(m);
156         }
157 
sendMessage(int what, Object obj, Object replyTo)158         void sendMessage(int what, Object obj, Object replyTo) {
159             DCMessage m = new DCMessage(what, obj);
160             m.replyTo = replyTo;
161             mMessageQueue.add(m);
162         }
163 
handleMessage(DCMessage msg)164         private void handleMessage(DCMessage msg) {
165             switch (msg.what) {
166             case HISTORY_UPDATE_VISITED:
167                 doUpdateVisitedHistory((String) msg.obj);
168                 break;
169             case HISTORY_UPDATE_TITLE:
170                 String[] args = (String[]) msg.obj;
171                 doUpdateHistoryTitle(args[0], args[1]);
172                 break;
173             case QUERY_URL_IS_BOOKMARK:
174                 // TODO: Look for identical messages in the queue and remove them
175                 // TODO: Also, look for partial matches and merge them (such as
176                 //       multiple callbacks querying the same URL)
177                 doQueryBookmarkStatus((String) msg.obj, msg.replyTo);
178                 break;
179             case TAB_LOAD_THUMBNAIL:
180                 doLoadThumbnail((Tab) msg.obj);
181                 break;
182             case TAB_DELETE_THUMBNAIL:
183                 ContentResolver cr = mContext.getContentResolver();
184                 try {
185                     cr.delete(ContentUris.withAppendedId(
186                             Thumbnails.CONTENT_URI, (Long)msg.obj),
187                             null, null);
188                 } catch (Throwable t) {}
189                 break;
190             case TAB_SAVE_THUMBNAIL:
191                 doSaveThumbnail((Tab)msg.obj);
192                 break;
193             }
194         }
195 
getCaptureBlob(Tab tab)196         private byte[] getCaptureBlob(Tab tab) {
197             synchronized (tab) {
198                 Bitmap capture = tab.getScreenshot();
199                 if (capture == null) {
200                     return null;
201                 }
202                 if (mBuffer == null || mBuffer.limit() < capture.getByteCount()) {
203                     mBuffer = ByteBuffer.allocate(capture.getByteCount());
204                 }
205                 capture.copyPixelsToBuffer(mBuffer);
206                 mBuffer.rewind();
207                 return mBuffer.array();
208             }
209         }
210 
doSaveThumbnail(Tab tab)211         private void doSaveThumbnail(Tab tab) {
212             byte[] blob = getCaptureBlob(tab);
213             if (blob == null) {
214                 return;
215             }
216             ContentResolver cr = mContext.getContentResolver();
217             ContentValues values = new ContentValues();
218             values.put(Thumbnails._ID, tab.getId());
219             values.put(Thumbnails.THUMBNAIL, blob);
220             cr.insert(Thumbnails.CONTENT_URI, values);
221         }
222 
doLoadThumbnail(Tab tab)223         private void doLoadThumbnail(Tab tab) {
224             ContentResolver cr = mContext.getContentResolver();
225             Cursor c = null;
226             try {
227                 Uri uri = ContentUris.withAppendedId(Thumbnails.CONTENT_URI, tab.getId());
228                 c = cr.query(uri, new String[] {Thumbnails._ID,
229                         Thumbnails.THUMBNAIL}, null, null, null);
230                 if (c.moveToFirst()) {
231                     byte[] data = c.getBlob(1);
232                     if (data != null && data.length > 0) {
233                         tab.updateCaptureFromBlob(data);
234                     }
235                 }
236             } finally {
237                 if (c != null) {
238                     c.close();
239                 }
240             }
241         }
242 
doUpdateVisitedHistory(String url)243         private void doUpdateVisitedHistory(String url) {
244             ContentResolver cr = mContext.getContentResolver();
245             Cursor c = null;
246             try {
247                 c = cr.query(History.CONTENT_URI, new String[] { History._ID, History.VISITS },
248                         History.URL + "=?", new String[] { url }, null);
249                 if (c.moveToFirst()) {
250                     ContentValues values = new ContentValues();
251                     values.put(History.VISITS, c.getInt(1) + 1);
252                     values.put(History.DATE_LAST_VISITED, System.currentTimeMillis());
253                     cr.update(ContentUris.withAppendedId(History.CONTENT_URI, c.getLong(0)),
254                             values, null, null);
255                 } else {
256                     android.provider.Browser.truncateHistory(cr);
257                     ContentValues values = new ContentValues();
258                     values.put(History.URL, url);
259                     values.put(History.VISITS, 1);
260                     values.put(History.DATE_LAST_VISITED, System.currentTimeMillis());
261                     values.put(History.TITLE, url);
262                     values.put(History.DATE_CREATED, 0);
263                     values.put(History.USER_ENTERED, 0);
264                     cr.insert(History.CONTENT_URI, values);
265                 }
266             } finally {
267                 if (c != null) c.close();
268             }
269         }
270 
doQueryBookmarkStatus(String url, Object replyTo)271         private void doQueryBookmarkStatus(String url, Object replyTo) {
272             // Check to see if the site is bookmarked
273             Cursor cursor = null;
274             boolean isBookmark = false;
275             try {
276                 cursor = mContext.getContentResolver().query(
277                         BookmarkUtils.getBookmarksUri(mContext),
278                         new String[] { BrowserContract.Bookmarks.URL },
279                         BrowserContract.Bookmarks.URL + " == ?",
280                         new String[] { url },
281                         null);
282                 isBookmark = cursor.moveToFirst();
283             } catch (SQLiteException e) {
284                 Log.e(LOGTAG, "Error checking for bookmark: " + e);
285             } finally {
286                 if (cursor != null) cursor.close();
287             }
288             CallbackContainer cc = new CallbackContainer();
289             cc.replyTo = replyTo;
290             cc.args = new Object[] { url, isBookmark };
291             mCbHandler.obtainMessage(QUERY_URL_IS_BOOKMARK, cc).sendToTarget();
292         }
293 
doUpdateHistoryTitle(String url, String title)294         private void doUpdateHistoryTitle(String url, String title) {
295             ContentResolver cr = mContext.getContentResolver();
296             ContentValues values = new ContentValues();
297             values.put(History.TITLE, title);
298             cr.update(History.CONTENT_URI, values, History.URL + "=?",
299                     new String[] { url });
300         }
301     }
302 }
303