PageRenderTime 65ms CodeModel.GetById 14ms app.highlight 42ms RepoModel.GetById 1ms app.codeStats 1ms

/WebVox/src/com/marvin/webvox/BrowserProvider.java

http://eyes-free.googlecode.com/
Java | 1006 lines | 739 code | 108 blank | 159 comment | 182 complexity | cb5829d625d624720df6fdbf1471460e MD5 | raw file
   1/*
   2 * Copyright (C) 2006 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
  17package com.marvin.webvox;
  18
  19import com.marvin.webvox.R;
  20//import com.google.android.providers.GoogleSettings.Partner;
  21
  22import android.app.SearchManager;
  23//import android.backup.BackupManager;
  24import android.content.ComponentName;
  25import android.content.ContentProvider;
  26import android.content.ContentResolver;
  27import android.content.ContentUris;
  28import android.content.ContentValues;
  29import android.content.Context;
  30import android.content.Intent;
  31import android.content.SharedPreferences;
  32import android.content.UriMatcher;
  33import android.content.SharedPreferences.Editor;
  34import android.content.pm.PackageManager;
  35import android.content.pm.ResolveInfo;
  36import android.database.AbstractCursor;
  37import android.database.ContentObserver;
  38import android.database.Cursor;
  39import android.database.sqlite.SQLiteDatabase;
  40import android.database.sqlite.SQLiteOpenHelper;
  41import android.net.Uri;
  42import android.os.AsyncTask;
  43import android.os.Handler;
  44import android.preference.PreferenceManager;
  45import android.provider.Browser;
  46import android.provider.Settings;
  47import android.provider.Browser.BookmarkColumns;
  48//import android.server.search.SearchableInfo;
  49import android.text.TextUtils;
  50//import android.text.util.Regex;
  51import android.util.Log;
  52import android.util.TypedValue;
  53
  54import java.io.File;
  55import java.io.FilenameFilter;
  56import java.util.Date;
  57import java.util.regex.Matcher;
  58import java.util.regex.Pattern;
  59
  60
  61public class BrowserProvider extends ContentProvider {
  62
  63    private SQLiteOpenHelper mOpenHelper;
  64//    private BackupManager mBackupManager;
  65    private static final String sDatabaseName = "browser.db";
  66    private static final String TAG = "BrowserProvider";
  67    private static final String ORDER_BY = "visits DESC, date DESC";
  68
  69    private static final String PICASA_URL = "http://picasaweb.google.com/m/" +
  70            "viewer?source=androidclient";
  71
  72    private static final String[] TABLE_NAMES = new String[] {
  73        "bookmarks", "searches"
  74    };
  75    private static final String[] SUGGEST_PROJECTION = new String[] {
  76            "_id", "url", "title", "bookmark"
  77    };
  78    private static final String SUGGEST_SELECTION =
  79            "url LIKE ? OR url LIKE ? OR url LIKE ? OR url LIKE ?"
  80                + " OR title LIKE ?";
  81    private String[] SUGGEST_ARGS = new String[5];
  82
  83    // shared suggestion array index, make sure to match COLUMNS
  84    private static final int SUGGEST_COLUMN_INTENT_ACTION_ID = 1;
  85    private static final int SUGGEST_COLUMN_INTENT_DATA_ID = 2;
  86    private static final int SUGGEST_COLUMN_TEXT_1_ID = 3;
  87    private static final int SUGGEST_COLUMN_TEXT_2_ID = 4;
  88    private static final int SUGGEST_COLUMN_ICON_1_ID = 5;
  89    private static final int SUGGEST_COLUMN_ICON_2_ID = 6;
  90    private static final int SUGGEST_COLUMN_QUERY_ID = 7;
  91    private static final int SUGGEST_COLUMN_FORMAT = 8;
  92    private static final int SUGGEST_COLUMN_INTENT_EXTRA_DATA = 9;
  93
  94    // shared suggestion columns
  95    private static final String[] COLUMNS = new String[] {
  96            "_id",
  97            SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
  98            SearchManager.SUGGEST_COLUMN_INTENT_DATA,
  99            SearchManager.SUGGEST_COLUMN_TEXT_1,
 100            SearchManager.SUGGEST_COLUMN_TEXT_2,
 101            SearchManager.SUGGEST_COLUMN_ICON_1,
 102            SearchManager.SUGGEST_COLUMN_ICON_2,
 103            SearchManager.SUGGEST_COLUMN_QUERY,
 104            SearchManager.SUGGEST_COLUMN_FORMAT,
 105            SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA};
 106
 107    private static final int MAX_SUGGESTION_SHORT_ENTRIES = 3;
 108    private static final int MAX_SUGGESTION_LONG_ENTRIES = 6;
 109    private static final String MAX_SUGGESTION_LONG_ENTRIES_STRING =
 110            Integer.valueOf(MAX_SUGGESTION_LONG_ENTRIES).toString();
 111
 112    // make sure that these match the index of TABLE_NAMES
 113    private static final int URI_MATCH_BOOKMARKS = 0;
 114    private static final int URI_MATCH_SEARCHES = 1;
 115    // (id % 10) should match the table name index
 116    private static final int URI_MATCH_BOOKMARKS_ID = 10;
 117    private static final int URI_MATCH_SEARCHES_ID = 11;
 118    //
 119    private static final int URI_MATCH_SUGGEST = 20;
 120    private static final int URI_MATCH_BOOKMARKS_SUGGEST = 21;
 121
 122    private static final UriMatcher URI_MATCHER;
 123
 124    static {
 125        URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
 126        URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_BOOKMARKS],
 127                URI_MATCH_BOOKMARKS);
 128        URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_BOOKMARKS] + "/#",
 129                URI_MATCH_BOOKMARKS_ID);
 130        URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_SEARCHES],
 131                URI_MATCH_SEARCHES);
 132        URI_MATCHER.addURI("browser", TABLE_NAMES[URI_MATCH_SEARCHES] + "/#",
 133                URI_MATCH_SEARCHES_ID);
 134        URI_MATCHER.addURI("browser", SearchManager.SUGGEST_URI_PATH_QUERY,
 135                URI_MATCH_SUGGEST);
 136        URI_MATCHER.addURI("browser",
 137                TABLE_NAMES[URI_MATCH_BOOKMARKS] + "/" + SearchManager.SUGGEST_URI_PATH_QUERY,
 138                URI_MATCH_BOOKMARKS_SUGGEST);
 139    }
 140
 141    // 1 -> 2 add cache table
 142    // 2 -> 3 update history table
 143    // 3 -> 4 add passwords table
 144    // 4 -> 5 add settings table
 145    // 5 -> 6 ?
 146    // 6 -> 7 ?
 147    // 7 -> 8 drop proxy table
 148    // 8 -> 9 drop settings table
 149    // 9 -> 10 add form_urls and form_data
 150    // 10 -> 11 add searches table
 151    // 11 -> 12 modify cache table
 152    // 12 -> 13 modify cache table
 153    // 13 -> 14 correspond with Google Bookmarks schema
 154    // 14 -> 15 move couple of tables to either browser private database or webview database
 155    // 15 -> 17 Set it up for the SearchManager
 156    // 17 -> 18 Added favicon in bookmarks table for Home shortcuts
 157    // 18 -> 19 Remove labels table
 158    // 19 -> 20 Added thumbnail
 159    // 20 -> 21 Added touch_icon
 160    // 21 -> 22 Remove "clientid"
 161    private static final int DATABASE_VERSION = 22;
 162
 163    // Regular expression which matches http://, followed by some stuff, followed by
 164    // optionally a trailing slash, all matched as separate groups.
 165    private static final Pattern STRIP_URL_PATTERN = Pattern.compile("^(http://)(.*?)(/$)?");
 166
 167    private SearchManager mSearchManager;
 168
 169    // The ID of the ColorStateList to be applied to urls of website suggestions, as derived from
 170    // the current theme. This is not set until/unless beautifyUrl is called, at which point
 171    // this variable caches the color value.
 172    private static String mSearchUrlColorId;
 173
 174    public BrowserProvider() {
 175    }
 176
 177
 178    private static CharSequence replaceSystemPropertyInString(Context context, CharSequence srcString) {
 179        StringBuffer sb = new StringBuffer();
 180        int lastCharLoc = 0;
 181
 182//        final String client_id = Partner.getString(context.getContentResolver(),
 183//                                                    Partner.CLIENT_ID, "android-google");
 184
 185        for (int i = 0; i < srcString.length(); ++i) {
 186            char c = srcString.charAt(i);
 187            if (c == '{') {
 188                sb.append(srcString.subSequence(lastCharLoc, i));
 189                lastCharLoc = i;
 190          inner:
 191                for (int j = i; j < srcString.length(); ++j) {
 192                    char k = srcString.charAt(j);
 193                    if (k == '}') {
 194                        String propertyKeyValue = srcString.subSequence(i + 1, j).toString();
 195
 196                            sb.append("unknown");
 197
 198                        lastCharLoc = j + 1;
 199                        i = j;
 200                        break inner;
 201                    }
 202                }
 203            }
 204        }
 205        if (srcString.length() - lastCharLoc > 0) {
 206            // Put on the tail, if there is one
 207            sb.append(srcString.subSequence(lastCharLoc, srcString.length()));
 208        }
 209        return sb;
 210    }
 211
 212    private static class DatabaseHelper extends SQLiteOpenHelper {
 213        private Context mContext;
 214
 215        public DatabaseHelper(Context context) {
 216            super(context, sDatabaseName, null, DATABASE_VERSION);
 217            mContext = context;
 218        }
 219
 220        @Override
 221        public void onCreate(SQLiteDatabase db) {
 222            db.execSQL("CREATE TABLE bookmarks (" +
 223                    "_id INTEGER PRIMARY KEY," +
 224                    "title TEXT," +
 225                    "url TEXT," +
 226                    "visits INTEGER," +
 227                    "date LONG," +
 228                    "created LONG," +
 229                    "description TEXT," +
 230                    "bookmark INTEGER," +
 231                    "favicon BLOB DEFAULT NULL," +
 232                    "thumbnail BLOB DEFAULT NULL," +
 233                    "touch_icon BLOB DEFAULT NULL" +
 234                    ");");
 235
 236            final CharSequence[] bookmarks = mContext.getResources()
 237                    .getTextArray(R.array.bookmarks);
 238            int size = bookmarks.length;
 239            try {
 240                for (int i = 0; i < size; i = i + 2) {
 241                    CharSequence bookmarkDestination = replaceSystemPropertyInString(mContext, bookmarks[i + 1]);
 242                    db.execSQL("INSERT INTO bookmarks (title, url, visits, " +
 243                            "date, created, bookmark)" + " VALUES('" +
 244                            bookmarks[i] + "', '" + bookmarkDestination +
 245                            "', 0, 0, 0, 1);");
 246                }
 247            } catch (ArrayIndexOutOfBoundsException e) {
 248            }
 249
 250            db.execSQL("CREATE TABLE searches (" +
 251                    "_id INTEGER PRIMARY KEY," +
 252                    "search TEXT," +
 253                    "date LONG" +
 254                    ");");
 255        }
 256
 257        @Override
 258        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 259            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
 260                    + newVersion);
 261            if (oldVersion == 18) {
 262                db.execSQL("DROP TABLE IF EXISTS labels");
 263            }
 264            if (oldVersion <= 19) {
 265                db.execSQL("ALTER TABLE bookmarks ADD COLUMN thumbnail BLOB DEFAULT NULL;");
 266            }
 267            if (oldVersion < 21) {
 268                db.execSQL("ALTER TABLE bookmarks ADD COLUMN touch_icon BLOB DEFAULT NULL;");
 269            }
 270            if (oldVersion < 22) {
 271                db.execSQL("DELETE FROM bookmarks WHERE (bookmark = 0 AND url LIKE \"%.google.%client=ms-%\")");
 272                removeGears();
 273            } else {
 274                db.execSQL("DROP TABLE IF EXISTS bookmarks");
 275                db.execSQL("DROP TABLE IF EXISTS searches");
 276                onCreate(db);
 277            }
 278        }
 279
 280        private void removeGears() {
 281            AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
 282                public Void doInBackground(Void... unused) {
 283                    String browserDataDirString = mContext.getApplicationInfo().dataDir;
 284                    final String appPluginsDirString = "app_plugins";
 285                    final String gearsPrefix = "gears";
 286                    File appPluginsDir = new File(browserDataDirString + File.separator
 287                            + appPluginsDirString);
 288                    if (!appPluginsDir.exists()) {
 289                        return null;
 290                    }
 291                    // Delete the Gears plugin files
 292                    File[] gearsFiles = appPluginsDir.listFiles(new FilenameFilter() {
 293                        public boolean accept(File dir, String filename) {
 294                            return filename.startsWith(gearsPrefix);
 295                        }
 296                    });
 297                    for (int i = 0; i < gearsFiles.length; ++i) {
 298                        if (gearsFiles[i].isDirectory()) {
 299                            deleteDirectory(gearsFiles[i]);
 300                        } else {
 301                            gearsFiles[i].delete();
 302                        }
 303                    }
 304                    // Delete the Gears data files
 305                    File gearsDataDir = new File(browserDataDirString + File.separator
 306                            + gearsPrefix);
 307                    if (!gearsDataDir.exists()) {
 308                        return null;
 309                    }
 310                    deleteDirectory(gearsDataDir);
 311                    return null;
 312                }
 313
 314                private void deleteDirectory(File currentDir) {
 315                    File[] files = currentDir.listFiles();
 316                    for (int i = 0; i < files.length; ++i) {
 317                        if (files[i].isDirectory()) {
 318                            deleteDirectory(files[i]);
 319                        }
 320                        files[i].delete();
 321                    }
 322                    currentDir.delete();
 323                }
 324            };
 325
 326            task.execute();
 327        }
 328    }
 329
 330    @Override
 331    public boolean onCreate() {
 332        final Context context = getContext();
 333        mOpenHelper = new DatabaseHelper(context);
 334//        mBackupManager = new BackupManager(context);
 335        // we added "picasa web album" into default bookmarks for version 19.
 336        // To avoid erasing the bookmark table, we added it explicitly for
 337        // version 18 and 19 as in the other cases, we will erase the table.
 338        if (DATABASE_VERSION == 18 || DATABASE_VERSION == 19) {
 339            SharedPreferences p = PreferenceManager
 340                    .getDefaultSharedPreferences(context);
 341            boolean fix = p.getBoolean("fix_picasa", true);
 342            if (fix) {
 343                fixPicasaBookmark();
 344                Editor ed = p.edit();
 345                ed.putBoolean("fix_picasa", false);
 346                ed.commit();
 347            }
 348        }
 349        mSearchManager = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
 350        mShowWebSuggestionsSettingChangeObserver
 351            = new ShowWebSuggestionsSettingChangeObserver();
 352        context.getContentResolver().registerContentObserver(
 353                Settings.System.getUriFor(
 354                        Settings.System.SHOW_WEB_SUGGESTIONS),
 355                true, mShowWebSuggestionsSettingChangeObserver);
 356        updateShowWebSuggestions();
 357        return true;
 358    }
 359
 360    /**
 361     * This Observer will ensure that if the user changes the system
 362     * setting of whether to display web suggestions, we will
 363     * change accordingly.
 364     */
 365    /* package */ class ShowWebSuggestionsSettingChangeObserver
 366            extends ContentObserver {
 367        public ShowWebSuggestionsSettingChangeObserver() {
 368            super(new Handler());
 369        }
 370
 371        @Override
 372        public void onChange(boolean selfChange) {
 373            updateShowWebSuggestions();
 374        }
 375    }
 376
 377    private ShowWebSuggestionsSettingChangeObserver
 378            mShowWebSuggestionsSettingChangeObserver;
 379
 380    // If non-null, then the system is set to show web suggestions,
 381    // and this is the SearchableInfo to use to get them.
 382//    private SearchableInfo mSearchableInfo;
 383
 384    /**
 385     * Check the system settings to see whether web suggestions are
 386     * allowed.  If so, store the SearchableInfo to grab suggestions
 387     * while the user is typing.
 388     */
 389    private void updateShowWebSuggestions() {
 390//        mSearchableInfo = null;
 391        Context context = getContext();
 392        if (Settings.System.getInt(context.getContentResolver(),
 393                Settings.System.SHOW_WEB_SUGGESTIONS,
 394                1 /* default on */) == 1) {
 395            Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
 396            intent.addCategory(Intent.CATEGORY_DEFAULT);
 397            ResolveInfo info = context.getPackageManager().resolveActivity(
 398                    intent, PackageManager.MATCH_DEFAULT_ONLY);
 399            if (info != null) {
 400                ComponentName googleSearchComponent =
 401                        new ComponentName(info.activityInfo.packageName,
 402                                info.activityInfo.name);
 403//                mSearchableInfo = mSearchManager.getSearchableInfo(
 404//                        googleSearchComponent, false);
 405            }
 406        }
 407    }
 408
 409    private void fixPicasaBookmark() {
 410        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
 411        Cursor cursor = db.rawQuery("SELECT _id FROM bookmarks WHERE " +
 412                "bookmark = 1 AND url = ?", new String[] { PICASA_URL });
 413        try {
 414            if (!cursor.moveToFirst()) {
 415                // set "created" so that it will be on the top of the list
 416                db.execSQL("INSERT INTO bookmarks (title, url, visits, " +
 417                        "date, created, bookmark)" + " VALUES('" +
 418                        getContext().getString(R.string.picasa) + "', '"
 419                        + PICASA_URL + "', 0, 0, " + new Date().getTime()
 420                        + ", 1);");
 421            }
 422        } finally {
 423            if (cursor != null) {
 424                cursor.close();
 425            }
 426        }
 427    }
 428
 429    /*
 430     * Subclass AbstractCursor so we can combine multiple Cursors and add
 431     * "Google Search".
 432     * Here are the rules.
 433     * 1. We only have MAX_SUGGESTION_LONG_ENTRIES in the list plus
 434     *      "Google Search";
 435     * 2. If bookmark/history entries are less than
 436     *      (MAX_SUGGESTION_SHORT_ENTRIES -1), we include Google suggest.
 437     */
 438    private class MySuggestionCursor extends AbstractCursor {
 439        private Cursor  mHistoryCursor;
 440        private Cursor  mSuggestCursor;
 441        private int     mHistoryCount;
 442        private int     mSuggestionCount;
 443        private boolean mBeyondCursor;
 444        private String  mString;
 445        private int     mSuggestText1Id;
 446        private int     mSuggestText2Id;
 447        private int     mSuggestQueryId;
 448        private int     mSuggestIntentExtraDataId;
 449
 450        public MySuggestionCursor(Cursor hc, Cursor sc, String string) {
 451            mHistoryCursor = hc;
 452            mSuggestCursor = sc;
 453            mHistoryCount = hc.getCount();
 454            mSuggestionCount = sc != null ? sc.getCount() : 0;
 455            if (mSuggestionCount > (MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount)) {
 456                mSuggestionCount = MAX_SUGGESTION_LONG_ENTRIES - mHistoryCount;
 457            }
 458            mString = string;
 459            mBeyondCursor = false;
 460
 461            // Some web suggest providers only give suggestions and have no description string for
 462            // items. The order of the result columns may be different as well. So retrieve the
 463            // column indices for the fields we need now and check before using below.
 464            if (mSuggestCursor == null) {
 465                mSuggestText1Id = -1;
 466                mSuggestText2Id = -1;
 467                mSuggestQueryId = -1;
 468                mSuggestIntentExtraDataId = -1;
 469            } else {
 470                mSuggestText1Id = mSuggestCursor.getColumnIndex(
 471                                SearchManager.SUGGEST_COLUMN_TEXT_1);
 472                mSuggestText2Id = mSuggestCursor.getColumnIndex(
 473                                SearchManager.SUGGEST_COLUMN_TEXT_2);
 474                mSuggestQueryId = mSuggestCursor.getColumnIndex(
 475                                SearchManager.SUGGEST_COLUMN_QUERY);
 476                mSuggestIntentExtraDataId = mSuggestCursor.getColumnIndex(
 477                                SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
 478            }
 479        }
 480
 481        @Override
 482        public boolean onMove(int oldPosition, int newPosition) {
 483            if (mHistoryCursor == null) {
 484                return false;
 485            }
 486            if (mHistoryCount > newPosition) {
 487                mHistoryCursor.moveToPosition(newPosition);
 488                mBeyondCursor = false;
 489            } else if (mHistoryCount + mSuggestionCount > newPosition) {
 490                mSuggestCursor.moveToPosition(newPosition - mHistoryCount);
 491                mBeyondCursor = false;
 492            } else {
 493                mBeyondCursor = true;
 494            }
 495            return true;
 496        }
 497
 498        @Override
 499        public int getCount() {
 500            if (mString.length() > 0) {
 501                return mHistoryCount + mSuggestionCount + 1;
 502            } else {
 503                return mHistoryCount + mSuggestionCount;
 504            }
 505        }
 506
 507        @Override
 508        public String[] getColumnNames() {
 509            return COLUMNS;
 510        }
 511
 512        @Override
 513        public String getString(int columnIndex) {
 514            if ((mPos != -1 && mHistoryCursor != null)) {
 515                switch(columnIndex) {
 516                    case SUGGEST_COLUMN_INTENT_ACTION_ID:
 517                        if (mHistoryCount > mPos) {
 518                            return Intent.ACTION_VIEW;
 519                        } else {
 520                            return Intent.ACTION_SEARCH;
 521                        }
 522
 523                    case SUGGEST_COLUMN_INTENT_DATA_ID:
 524                        if (mHistoryCount > mPos) {
 525                            return mHistoryCursor.getString(1);
 526                        } else {
 527                            return null;
 528                        }
 529
 530                    case SUGGEST_COLUMN_TEXT_1_ID:
 531                        if (mHistoryCount > mPos) {
 532                            return getHistoryTitle();
 533                        } else if (!mBeyondCursor) {
 534                            if (mSuggestText1Id == -1) return null;
 535                            return mSuggestCursor.getString(mSuggestText1Id);
 536                        } else {
 537                            return mString;
 538                        }
 539
 540                    case SUGGEST_COLUMN_TEXT_2_ID:
 541                        if (mHistoryCount > mPos) {
 542                            return getHistorySubtitle();
 543                        } else if (!mBeyondCursor) {
 544                            if (mSuggestText2Id == -1) return null;
 545                            return mSuggestCursor.getString(mSuggestText2Id);
 546                        } else {
 547                            return getContext().getString(R.string.search_the_web);
 548                        }
 549
 550                    case SUGGEST_COLUMN_ICON_1_ID:
 551                        if (mHistoryCount > mPos) {
 552                            if (mHistoryCursor.getInt(3) == 1) {
 553                                return Integer.valueOf(
 554                                        R.drawable.ic_search_category_bookmark)
 555                                        .toString();
 556                            } else {
 557                                return Integer.valueOf(
 558                                        R.drawable.ic_search_category_history)
 559                                        .toString();
 560                            }
 561                        } else {
 562                            return Integer.valueOf(
 563                                    R.drawable.ic_search_category_suggest)
 564                                    .toString();
 565                        }
 566
 567                    case SUGGEST_COLUMN_ICON_2_ID:
 568                        return "0";
 569
 570                    case SUGGEST_COLUMN_QUERY_ID:
 571                        if (mHistoryCount > mPos) {
 572                            // Return the url in the intent query column. This is ignored
 573                            // within the browser because our searchable is set to
 574                            // android:searchMode="queryRewriteFromData", but it is used by
 575                            // global search for query rewriting.
 576                            return mHistoryCursor.getString(1);
 577                        } else if (!mBeyondCursor) {
 578                            if (mSuggestQueryId == -1) return null;
 579                            return mSuggestCursor.getString(mSuggestQueryId);
 580                        } else {
 581                            return mString;
 582                        }
 583
 584                    case SUGGEST_COLUMN_FORMAT:
 585                        return "html";
 586
 587                    case SUGGEST_COLUMN_INTENT_EXTRA_DATA:
 588                        if (mHistoryCount > mPos) {
 589                            return null;
 590                        } else if (!mBeyondCursor) {
 591                            if (mSuggestIntentExtraDataId == -1) return null;
 592                            return mSuggestCursor.getString(mSuggestIntentExtraDataId);
 593                        } else {
 594                            return null;
 595                        }
 596                }
 597            }
 598            return null;
 599        }
 600
 601        @Override
 602        public double getDouble(int column) {
 603            throw new UnsupportedOperationException();
 604        }
 605
 606        @Override
 607        public float getFloat(int column) {
 608            throw new UnsupportedOperationException();
 609        }
 610
 611        @Override
 612        public int getInt(int column) {
 613            throw new UnsupportedOperationException();
 614        }
 615
 616        @Override
 617        public long getLong(int column) {
 618            if ((mPos != -1) && column == 0) {
 619                return mPos;        // use row# as the _Id
 620            }
 621            throw new UnsupportedOperationException();
 622        }
 623
 624        @Override
 625        public short getShort(int column) {
 626            throw new UnsupportedOperationException();
 627        }
 628
 629        @Override
 630        public boolean isNull(int column) {
 631            throw new UnsupportedOperationException();
 632        }
 633
 634        // TODO Temporary change, finalize after jq's changes go in
 635        public void deactivate() {
 636            if (mHistoryCursor != null) {
 637                mHistoryCursor.deactivate();
 638            }
 639            if (mSuggestCursor != null) {
 640                mSuggestCursor.deactivate();
 641            }
 642            super.deactivate();
 643        }
 644
 645        public boolean requery() {
 646            return (mHistoryCursor != null ? mHistoryCursor.requery() : false) |
 647                    (mSuggestCursor != null ? mSuggestCursor.requery() : false);
 648        }
 649
 650        // TODO Temporary change, finalize after jq's changes go in
 651        public void close() {
 652            super.close();
 653            if (mHistoryCursor != null) {
 654                mHistoryCursor.close();
 655                mHistoryCursor = null;
 656            }
 657            if (mSuggestCursor != null) {
 658                mSuggestCursor.close();
 659                mSuggestCursor = null;
 660            }
 661        }
 662
 663        /**
 664         * Provides the title (text line 1) for a browser suggestion, which should be the
 665         * webpage title. If the webpage title is empty, returns the stripped url instead.
 666         *
 667         * @return the title string to use
 668         */
 669        private String getHistoryTitle() {
 670            String title = mHistoryCursor.getString(2 /* webpage title */);
 671            if (TextUtils.isEmpty(title) || TextUtils.getTrimmedLength(title) == 0) {
 672                title = beautifyUrl(mHistoryCursor.getString(1 /* url */));
 673            }
 674            return title;
 675        }
 676
 677        /**
 678         * Provides the subtitle (text line 2) for a browser suggestion, which should be the
 679         * webpage url. If the webpage title is empty, then the url should go in the title
 680         * instead, and the subtitle should be empty, so this would return null.
 681         *
 682         * @return the subtitle string to use, or null if none
 683         */
 684        private String getHistorySubtitle() {
 685            String title = mHistoryCursor.getString(2 /* webpage title */);
 686            if (TextUtils.isEmpty(title) || TextUtils.getTrimmedLength(title) == 0) {
 687                return null;
 688            } else {
 689                return beautifyUrl(mHistoryCursor.getString(1 /* url */));
 690            }
 691        }
 692
 693        /**
 694         * Strips "http://" from the beginning of a url and "/" from the end,
 695         * and adds html formatting to make it green.
 696         */
 697        private String beautifyUrl(String url) {
 698            if (mSearchUrlColorId == null) {
 699                // Get the color used for this purpose from the current theme.
 700                TypedValue colorValue = new TypedValue();
 701//                getContext().getTheme().resolveAttribute(
 702//                        com.android.internal.R.attr.textColorSearchUrl, colorValue, true);
 703                mSearchUrlColorId = Integer.toString(colorValue.resourceId);
 704            }
 705
 706            return "<font color=\"@" + mSearchUrlColorId + "\">" + stripUrl(url) + "</font>";
 707        }
 708    }
 709
 710    @Override
 711    public Cursor query(Uri url, String[] projectionIn, String selection,
 712            String[] selectionArgs, String sortOrder)
 713            throws IllegalStateException {
 714        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
 715
 716        int match = URI_MATCHER.match(url);
 717        if (match == -1) {
 718            throw new IllegalArgumentException("Unknown URL");
 719        }
 720
 721        if (match == URI_MATCH_SUGGEST || match == URI_MATCH_BOOKMARKS_SUGGEST) {
 722            String suggestSelection;
 723            String [] myArgs;
 724            if (selectionArgs[0] == null || selectionArgs[0].equals("")) {
 725                suggestSelection = null;
 726                myArgs = null;
 727            } else {
 728                String like = selectionArgs[0] + "%";
 729                if (selectionArgs[0].startsWith("http")
 730                        || selectionArgs[0].startsWith("file")) {
 731                    myArgs = new String[1];
 732                    myArgs[0] = like;
 733                    suggestSelection = selection;
 734                } else {
 735                    SUGGEST_ARGS[0] = "http://" + like;
 736                    SUGGEST_ARGS[1] = "http://www." + like;
 737                    SUGGEST_ARGS[2] = "https://" + like;
 738                    SUGGEST_ARGS[3] = "https://www." + like;
 739                    // To match against titles.
 740                    SUGGEST_ARGS[4] = like;
 741                    myArgs = SUGGEST_ARGS;
 742                    suggestSelection = SUGGEST_SELECTION;
 743                }
 744            }
 745
 746            Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS],
 747                    SUGGEST_PROJECTION, suggestSelection, myArgs, null, null,
 748                    ORDER_BY, MAX_SUGGESTION_LONG_ENTRIES_STRING);
 749
 750            if (match == URI_MATCH_BOOKMARKS_SUGGEST) {
 751//                    || Regex.WEB_URL_PATTERN.matcher(selectionArgs[0]).matches()) {
 752                return new MySuggestionCursor(c, null, "");
 753            } else {
 754                // get Google suggest if there is still space in the list
 755                /*
 756                if (myArgs != null && myArgs.length > 1
 757                        && mSearchableInfo != null
 758                        && c.getCount() < (MAX_SUGGESTION_SHORT_ENTRIES - 1)) {
 759                    Cursor sc = mSearchManager.getSuggestions(mSearchableInfo, selectionArgs[0]);
 760                    return new MySuggestionCursor(c, sc, selectionArgs[0]);
 761                }
 762                */
 763                return new MySuggestionCursor(c, null, selectionArgs[0]);
 764            }
 765        }
 766
 767        String[] projection = null;
 768        if (projectionIn != null && projectionIn.length > 0) {
 769            projection = new String[projectionIn.length + 1];
 770            System.arraycopy(projectionIn, 0, projection, 0, projectionIn.length);
 771            projection[projectionIn.length] = "_id AS _id";
 772        }
 773
 774        StringBuilder whereClause = new StringBuilder(256);
 775        if (match == URI_MATCH_BOOKMARKS_ID || match == URI_MATCH_SEARCHES_ID) {
 776            whereClause.append("(_id = ").append(url.getPathSegments().get(1))
 777                    .append(")");
 778        }
 779
 780        // Tack on the user's selection, if present
 781        if (selection != null && selection.length() > 0) {
 782            if (whereClause.length() > 0) {
 783                whereClause.append(" AND ");
 784            }
 785
 786            whereClause.append('(');
 787            whereClause.append(selection);
 788            whereClause.append(')');
 789        }
 790        Cursor c = db.query(TABLE_NAMES[match % 10], projection,
 791                whereClause.toString(), selectionArgs, null, null, sortOrder,
 792                null);
 793        c.setNotificationUri(getContext().getContentResolver(), url);
 794        return c;
 795    }
 796
 797    @Override
 798    public String getType(Uri url) {
 799        int match = URI_MATCHER.match(url);
 800        switch (match) {
 801            case URI_MATCH_BOOKMARKS:
 802                return "vnd.android.cursor.dir/bookmark";
 803
 804            case URI_MATCH_BOOKMARKS_ID:
 805                return "vnd.android.cursor.item/bookmark";
 806
 807            case URI_MATCH_SEARCHES:
 808                return "vnd.android.cursor.dir/searches";
 809
 810            case URI_MATCH_SEARCHES_ID:
 811                return "vnd.android.cursor.item/searches";
 812
 813            case URI_MATCH_SUGGEST:
 814                return SearchManager.SUGGEST_MIME_TYPE;
 815
 816            default:
 817                throw new IllegalArgumentException("Unknown URL");
 818        }
 819    }
 820
 821    @Override
 822    public Uri insert(Uri url, ContentValues initialValues) {
 823        boolean isBookmarkTable = false;
 824        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
 825
 826        int match = URI_MATCHER.match(url);
 827        Uri uri = null;
 828        switch (match) {
 829            case URI_MATCH_BOOKMARKS: {
 830                // Insert into the bookmarks table
 831                long rowID = db.insert(TABLE_NAMES[URI_MATCH_BOOKMARKS], "url",
 832                        initialValues);
 833                if (rowID > 0) {
 834                    uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
 835                            rowID);
 836                }
 837                isBookmarkTable = true;
 838                break;
 839            }
 840
 841            case URI_MATCH_SEARCHES: {
 842                // Insert into the searches table
 843                long rowID = db.insert(TABLE_NAMES[URI_MATCH_SEARCHES], "url",
 844                        initialValues);
 845                if (rowID > 0) {
 846                    uri = ContentUris.withAppendedId(Browser.SEARCHES_URI,
 847                            rowID);
 848                }
 849                break;
 850            }
 851
 852            default:
 853                throw new IllegalArgumentException("Unknown URL");
 854        }
 855
 856        if (uri == null) {
 857            throw new IllegalArgumentException("Unknown URL");
 858        }
 859        getContext().getContentResolver().notifyChange(uri, null);
 860
 861        // Back up the new bookmark set if we just inserted one.
 862        // A row created when bookmarks are added from scratch will have
 863        // bookmark=1 in the initial value set.
 864        if (isBookmarkTable
 865                && initialValues.containsKey(BookmarkColumns.BOOKMARK)
 866                && initialValues.getAsInteger(BookmarkColumns.BOOKMARK) != 0) {
 867//            mBackupManager.dataChanged();
 868        }
 869        return uri;
 870    }
 871
 872    @Override
 873    public int delete(Uri url, String where, String[] whereArgs) {
 874        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
 875
 876        int match = URI_MATCHER.match(url);
 877        if (match == -1 || match == URI_MATCH_SUGGEST) {
 878            throw new IllegalArgumentException("Unknown URL");
 879        }
 880
 881        // need to know whether it's the bookmarks table for a couple of reasons
 882        boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID);
 883        String id = null;
 884
 885        if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) {
 886            StringBuilder sb = new StringBuilder();
 887            if (where != null && where.length() > 0) {
 888                sb.append("( ");
 889                sb.append(where);
 890                sb.append(" ) AND ");
 891            }
 892            id = url.getPathSegments().get(1);
 893            sb.append("_id = ");
 894            sb.append(id);
 895            where = sb.toString();
 896        }
 897
 898        ContentResolver cr = getContext().getContentResolver();
 899
 900        // we'lll need to back up the bookmark set if we are about to delete one
 901        if (isBookmarkTable) {
 902            Cursor cursor = cr.query(Browser.BOOKMARKS_URI,
 903                    new String[] { BookmarkColumns.BOOKMARK },
 904                    "_id = " + id, null, null);
 905            if (cursor.moveToNext()) {
 906                if (cursor.getInt(0) != 0) {
 907                    // yep, this record is a bookmark
 908//                    mBackupManager.dataChanged();
 909                }
 910            }
 911            cursor.close();
 912        }
 913
 914        int count = db.delete(TABLE_NAMES[match % 10], where, whereArgs);
 915        cr.notifyChange(url, null);
 916        return count;
 917    }
 918
 919    @Override
 920    public int update(Uri url, ContentValues values, String where,
 921            String[] whereArgs) {
 922        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
 923
 924        int match = URI_MATCHER.match(url);
 925        if (match == -1 || match == URI_MATCH_SUGGEST) {
 926            throw new IllegalArgumentException("Unknown URL");
 927        }
 928
 929        String id = null;
 930        boolean isBookmarkTable = (match == URI_MATCH_BOOKMARKS_ID);
 931        boolean changingBookmarks = false;
 932
 933        if (isBookmarkTable || match == URI_MATCH_SEARCHES_ID) {
 934            StringBuilder sb = new StringBuilder();
 935            if (where != null && where.length() > 0) {
 936                sb.append("( ");
 937                sb.append(where);
 938                sb.append(" ) AND ");
 939            }
 940            id = url.getPathSegments().get(1);
 941            sb.append("_id = ");
 942            sb.append(id);
 943            where = sb.toString();
 944        }
 945
 946        ContentResolver cr = getContext().getContentResolver();
 947
 948        // Not all bookmark-table updates should be backed up.  Look to see
 949        // whether we changed the title, url, or "is a bookmark" state, and
 950        // request a backup if so.
 951        if (isBookmarkTable) {
 952            // Alterations to the bookmark field inherently change the bookmark
 953            // set, so we don't need to query the record; we know a priori that
 954            // we will need to back up this change.
 955            if (values.containsKey(BookmarkColumns.BOOKMARK)) {
 956                changingBookmarks = true;
 957            }
 958            // changing the title or URL of a bookmark record requires a backup,
 959            // but we don't know wether such an update is on a bookmark without
 960            // querying the record
 961            if (!changingBookmarks &&
 962                    (values.containsKey(BookmarkColumns.TITLE)
 963                     || values.containsKey(BookmarkColumns.URL))) {
 964                // when isBookmarkTable is true, the 'id' var was assigned above
 965                Cursor cursor = cr.query(Browser.BOOKMARKS_URI,
 966                        new String[] { BookmarkColumns.BOOKMARK },
 967                        "_id = " + id, null, null);
 968                if (cursor.moveToNext()) {
 969                    changingBookmarks = (cursor.getInt(0) != 0);
 970                }
 971                cursor.close();
 972            }
 973
 974            // if this *is* a bookmark row we're altering, we need to back it up.
 975            if (changingBookmarks) {
 976//                mBackupManager.dataChanged();
 977            }
 978        }
 979
 980        int ret = db.update(TABLE_NAMES[match % 10], values, where, whereArgs);
 981        cr.notifyChange(url, null);
 982        return ret;
 983    }
 984
 985    /**
 986     * Strips the provided url of preceding "http://" and any trailing "/". Does not
 987     * strip "https://". If the provided string cannot be stripped, the original string
 988     * is returned.
 989     *
 990     * TODO: Put this in TextUtils to be used by other packages doing something similar.
 991     *
 992     * @param url a url to strip, like "http://www.google.com/"
 993     * @return a stripped url like "www.google.com", or the original string if it could
 994     *         not be stripped
 995     */
 996    private static String stripUrl(String url) {
 997        if (url == null) return null;
 998        Matcher m = STRIP_URL_PATTERN.matcher(url);
 999        if (m.matches() && m.groupCount() == 3) {
1000            return m.group(2);
1001        } else {
1002            return url;
1003        }
1004    }
1005
1006}