Home | History | Annotate | Download | only in browser
      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 {
     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     }
     73 
     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();
     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 
    102     public void updateVisitedHistory(String url) {
    103         mDataHandler.sendMessage(HISTORY_UPDATE_VISITED, url);
    104     }
    105 
    106     public void updateHistoryTitle(String url, String title) {
    107         mDataHandler.sendMessage(HISTORY_UPDATE_TITLE, new String[] { url, title });
    108     }
    109 
    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 
    119     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 
    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 
    137         public DataControllerHandler() {
    138             super("DataControllerHandler");
    139         }
    140 
    141         @Override
    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 
    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) {
    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 
    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                 }
    240             }
    241         }
    242 
    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 
    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 
    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