blob: 909c2a3dbeb053a10d7ad739d526d636bc14e557 [file] [log] [blame]
John Reck0ebd3ac2010-12-09 11:14:04 -08001/*
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
18package com.android.browser;
19
20import android.content.ContentResolver;
21import android.content.ContentUris;
22import android.content.ContentValues;
23import android.content.Context;
24import android.database.Cursor;
John Recke969cc52010-12-21 17:24:43 -080025import android.database.sqlite.SQLiteException;
John Reck4eadc342011-10-31 14:04:10 -070026import android.graphics.Bitmap;
27import android.net.Uri;
John Reck0ebd3ac2010-12-09 11:14:04 -080028import android.os.Handler;
John Reck0ebd3ac2010-12-09 11:14:04 -080029import android.os.Message;
John Recke969cc52010-12-21 17:24:43 -080030import android.provider.BrowserContract;
John Reck0ebd3ac2010-12-09 11:14:04 -080031import android.provider.BrowserContract.History;
John Recke969cc52010-12-21 17:24:43 -080032import android.util.Log;
33
John Reck4eadc342011-10-31 14:04:10 -070034import com.android.browser.provider.BrowserProvider2.Thumbnails;
35
36import java.nio.ByteBuffer;
John Recke969cc52010-12-21 17:24:43 -080037import java.util.concurrent.BlockingQueue;
38import java.util.concurrent.LinkedBlockingQueue;
John Reck0ebd3ac2010-12-09 11:14:04 -080039
40public class DataController {
John Recke969cc52010-12-21 17:24:43 -080041 private static final String LOGTAG = "DataController";
John Reck0ebd3ac2010-12-09 11:14:04 -080042 // Message IDs
43 private static final int HISTORY_UPDATE_VISITED = 100;
44 private static final int HISTORY_UPDATE_TITLE = 101;
John Reck4eadc342011-10-31 14:04:10 -070045 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;
John Reck0ebd3ac2010-12-09 11:14:04 -080049 private static DataController sInstance;
50
51 private Context mContext;
John Recke969cc52010-12-21 17:24:43 -080052 private DataControllerHandler mDataHandler;
53 private Handler mCbHandler; // To respond on the UI thread
John Reck4eadc342011-10-31 14:04:10 -070054 private ByteBuffer mBuffer; // to capture thumbnails
John Recke969cc52010-12-21 17:24:43 -080055
56 /* package */ static interface OnQueryUrlIsBookmark {
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;
68 DCMessage(int w, Object o) {
69 what = w;
70 obj = o;
71 }
72 }
John Reck0ebd3ac2010-12-09 11:14:04 -080073
74 /* package */ static DataController getInstance(Context c) {
75 if (sInstance == null) {
76 sInstance = new DataController(c);
77 }
78 return sInstance;
79 }
80
81 private DataController(Context c) {
82 mContext = c.getApplicationContext();
John Recke969cc52010-12-21 17:24:43 -080083 mDataHandler = new DataControllerHandler();
John Recke969cc52010-12-21 17:24:43 -080084 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 };
John Reck0ebd3ac2010-12-09 11:14:04 -0800100 }
101
102 public void updateVisitedHistory(String url) {
John Recke969cc52010-12-21 17:24:43 -0800103 mDataHandler.sendMessage(HISTORY_UPDATE_VISITED, url);
John Reck0ebd3ac2010-12-09 11:14:04 -0800104 }
105
106 public void updateHistoryTitle(String url, String title) {
John Recke969cc52010-12-21 17:24:43 -0800107 mDataHandler.sendMessage(HISTORY_UPDATE_TITLE, new String[] { url, title });
John Reck0ebd3ac2010-12-09 11:14:04 -0800108 }
109
John Recke969cc52010-12-21 17:24:43 -0800110 public void queryBookmarkStatus(String url, OnQueryUrlIsBookmark replyTo) {
John Reck6c702ee2011-01-07 09:41:53 -0800111 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);
John Recke969cc52010-12-21 17:24:43 -0800117 }
118
John Reck4eadc342011-10-31 14:04:10 -0700119 public void loadThumbnail(Tab tab) {
120 mDataHandler.sendMessage(TAB_LOAD_THUMBNAIL, tab);
121 }
122
123 public void deleteThumbnail(Tab tab) {
124 mDataHandler.sendMessage(TAB_DELETE_THUMBNAIL, tab.getId());
125 }
126
127 public void saveThumbnail(Tab tab) {
128 mDataHandler.sendMessage(TAB_SAVE_THUMBNAIL, tab);
129 }
130
John Recke969cc52010-12-21 17:24:43 -0800131 // 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>();
John Reck0ebd3ac2010-12-09 11:14:04 -0800136
John Reck4eadc342011-10-31 14:04:10 -0700137 public DataControllerHandler() {
138 super("DataControllerHandler");
139 }
140
John Reck0ebd3ac2010-12-09 11:14:04 -0800141 @Override
John Recke969cc52010-12-21 17:24:43 -0800142 public void run() {
John Reck4eadc342011-10-31 14:04:10 -0700143 setPriority(Thread.MIN_PRIORITY);
John Recke969cc52010-12-21 17:24:43 -0800144 while (true) {
145 try {
146 handleMessage(mMessageQueue.take());
147 } catch (InterruptedException ex) {
148 break;
149 }
150 }
151 }
152
153 void sendMessage(int what, Object obj) {
154 DCMessage m = new DCMessage(what, obj);
155 mMessageQueue.add(m);
156 }
157
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
164 private void handleMessage(DCMessage msg) {
John Reck0ebd3ac2010-12-09 11:14:04 -0800165 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;
John Recke969cc52010-12-21 17:24:43 -0800173 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;
John Reck4eadc342011-10-31 14:04:10 -0700179 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
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
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
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 }
John Reck0ebd3ac2010-12-09 11:14:04 -0800240 }
241 }
John Reck0ebd3ac2010-12-09 11:14:04 -0800242
John Recke969cc52010-12-21 17:24:43 -0800243 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();
John Reck0ebd3ac2010-12-09 11:14:04 -0800268 }
John Reck0ebd3ac2010-12-09 11:14:04 -0800269 }
John Reck0ebd3ac2010-12-09 11:14:04 -0800270
John Recke969cc52010-12-21 17:24:43 -0800271 private void doQueryBookmarkStatus(String url, Object replyTo) {
John Recke969cc52010-12-21 17:24:43 -0800272 // 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
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 }
John Reck0ebd3ac2010-12-09 11:14:04 -0800301 }
302}