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.os.Handler; 27 import android.os.Message; 28 import android.provider.BrowserContract; 29 import android.provider.BrowserContract.History; 30 import android.util.Log; 31 32 import java.util.concurrent.BlockingQueue; 33 import java.util.concurrent.LinkedBlockingQueue; 34 35 public class DataController { 36 private static final String LOGTAG = "DataController"; 37 // Message IDs 38 private static final int HISTORY_UPDATE_VISITED = 100; 39 private static final int HISTORY_UPDATE_TITLE = 101; 40 public static final int QUERY_URL_IS_BOOKMARK = 200; 41 private static DataController sInstance; 42 43 private Context mContext; 44 private DataControllerHandler mDataHandler; 45 private Handler mCbHandler; // To respond on the UI thread 46 47 /* package */ static interface OnQueryUrlIsBookmark { 48 void onQueryUrlIsBookmark(String url, boolean isBookmark); 49 } 50 private static class CallbackContainer { 51 Object replyTo; 52 Object[] args; 53 } 54 55 private static class DCMessage { 56 int what; 57 Object obj; 58 Object replyTo; 59 DCMessage(int w, Object o) { 60 what = w; 61 obj = o; 62 } 63 } 64 65 /* package */ static DataController getInstance(Context c) { 66 if (sInstance == null) { 67 sInstance = new DataController(c); 68 } 69 return sInstance; 70 } 71 72 private DataController(Context c) { 73 mContext = c.getApplicationContext(); 74 mDataHandler = new DataControllerHandler(); 75 mDataHandler.setDaemon(true); 76 mDataHandler.start(); 77 mCbHandler = new Handler() { 78 @Override 79 public void handleMessage(Message msg) { 80 CallbackContainer cc = (CallbackContainer) msg.obj; 81 switch (msg.what) { 82 case QUERY_URL_IS_BOOKMARK: { 83 OnQueryUrlIsBookmark cb = (OnQueryUrlIsBookmark) cc.replyTo; 84 String url = (String) cc.args[0]; 85 boolean isBookmark = (Boolean) cc.args[1]; 86 cb.onQueryUrlIsBookmark(url, isBookmark); 87 break; 88 } 89 } 90 } 91 }; 92 } 93 94 public void updateVisitedHistory(String url) { 95 mDataHandler.sendMessage(HISTORY_UPDATE_VISITED, url); 96 } 97 98 public void updateHistoryTitle(String url, String title) { 99 mDataHandler.sendMessage(HISTORY_UPDATE_TITLE, new String[] { url, title }); 100 } 101 102 public void queryBookmarkStatus(String url, OnQueryUrlIsBookmark replyTo) { 103 if (url == null || url.trim().length() == 0) { 104 // null or empty url is never a bookmark 105 replyTo.onQueryUrlIsBookmark(url, false); 106 return; 107 } 108 mDataHandler.sendMessage(QUERY_URL_IS_BOOKMARK, url.trim(), replyTo); 109 } 110 111 // The standard Handler and Message classes don't allow the queue manipulation 112 // we want (such as peeking). So we use our own queue. 113 class DataControllerHandler extends Thread { 114 private BlockingQueue<DCMessage> mMessageQueue 115 = new LinkedBlockingQueue<DCMessage>(); 116 117 @Override 118 public void run() { 119 super.run(); 120 while (true) { 121 try { 122 handleMessage(mMessageQueue.take()); 123 } catch (InterruptedException ex) { 124 break; 125 } 126 } 127 } 128 129 void sendMessage(int what, Object obj) { 130 DCMessage m = new DCMessage(what, obj); 131 mMessageQueue.add(m); 132 } 133 134 void sendMessage(int what, Object obj, Object replyTo) { 135 DCMessage m = new DCMessage(what, obj); 136 m.replyTo = replyTo; 137 mMessageQueue.add(m); 138 } 139 140 private void handleMessage(DCMessage msg) { 141 switch (msg.what) { 142 case HISTORY_UPDATE_VISITED: 143 doUpdateVisitedHistory((String) msg.obj); 144 break; 145 case HISTORY_UPDATE_TITLE: 146 String[] args = (String[]) msg.obj; 147 doUpdateHistoryTitle(args[0], args[1]); 148 break; 149 case QUERY_URL_IS_BOOKMARK: 150 // TODO: Look for identical messages in the queue and remove them 151 // TODO: Also, look for partial matches and merge them (such as 152 // multiple callbacks querying the same URL) 153 doQueryBookmarkStatus((String) msg.obj, msg.replyTo); 154 break; 155 } 156 } 157 158 private void doUpdateVisitedHistory(String url) { 159 ContentResolver cr = mContext.getContentResolver(); 160 Cursor c = null; 161 try { 162 c = cr.query(History.CONTENT_URI, new String[] { History._ID, History.VISITS }, 163 History.URL + "=?", new String[] { url }, null); 164 if (c.moveToFirst()) { 165 ContentValues values = new ContentValues(); 166 values.put(History.VISITS, c.getInt(1) + 1); 167 values.put(History.DATE_LAST_VISITED, System.currentTimeMillis()); 168 cr.update(ContentUris.withAppendedId(History.CONTENT_URI, c.getLong(0)), 169 values, null, null); 170 } else { 171 android.provider.Browser.truncateHistory(cr); 172 ContentValues values = new ContentValues(); 173 values.put(History.URL, url); 174 values.put(History.VISITS, 1); 175 values.put(History.DATE_LAST_VISITED, System.currentTimeMillis()); 176 values.put(History.TITLE, url); 177 values.put(History.DATE_CREATED, 0); 178 values.put(History.USER_ENTERED, 0); 179 cr.insert(History.CONTENT_URI, values); 180 } 181 } finally { 182 if (c != null) c.close(); 183 } 184 } 185 186 private void doQueryBookmarkStatus(String url, Object replyTo) { 187 ContentResolver cr = mContext.getContentResolver(); 188 // Check to see if the site is bookmarked 189 Cursor cursor = null; 190 boolean isBookmark = false; 191 try { 192 cursor = mContext.getContentResolver().query( 193 BookmarkUtils.getBookmarksUri(mContext), 194 new String[] { BrowserContract.Bookmarks.URL }, 195 BrowserContract.Bookmarks.URL + " == ?", 196 new String[] { url }, 197 null); 198 isBookmark = cursor.moveToFirst(); 199 } catch (SQLiteException e) { 200 Log.e(LOGTAG, "Error checking for bookmark: " + e); 201 } finally { 202 if (cursor != null) cursor.close(); 203 } 204 CallbackContainer cc = new CallbackContainer(); 205 cc.replyTo = replyTo; 206 cc.args = new Object[] { url, isBookmark }; 207 mCbHandler.obtainMessage(QUERY_URL_IS_BOOKMARK, cc).sendToTarget(); 208 } 209 210 private void doUpdateHistoryTitle(String url, String title) { 211 ContentResolver cr = mContext.getContentResolver(); 212 ContentValues values = new ContentValues(); 213 values.put(History.TITLE, title); 214 cr.update(History.CONTENT_URI, values, History.URL + "=?", 215 new String[] { url }); 216 } 217 } 218 } 219