Home | History | Annotate | Download | only in provider
      1 /*
      2  * Copyright (C) 2008 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 package android.provider;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.content.SearchRecentSuggestionsProvider;
     23 import android.database.Cursor;
     24 import android.net.Uri;
     25 import android.text.TextUtils;
     26 import android.util.Log;
     27 
     28 /**
     29  * This is a utility class providing access to
     30  * {@link android.content.SearchRecentSuggestionsProvider}.
     31  *
     32  * <p>Unlike some utility classes, this one must be instantiated and properly initialized, so that
     33  * it can be configured to operate with the search suggestions provider that you have created.
     34  *
     35  * <p>Typically, you will do this in your searchable activity, each time you receive an incoming
     36  * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} Intent.  The code to record each
     37  * incoming query is as follows:
     38  * <pre class="prettyprint">
     39  *      SearchSuggestions suggestions = new SearchSuggestions(this,
     40  *              MySuggestionsProvider.AUTHORITY, MySuggestionsProvider.MODE);
     41  *      suggestions.saveRecentQuery(queryString, null);
     42  * </pre>
     43  *
     44  * <p>For a working example, see SearchSuggestionSampleProvider and SearchQueryResults in
     45  * samples/ApiDemos/app.
     46  */
     47 public class SearchRecentSuggestions {
     48     // debugging support
     49     private static final String LOG_TAG = "SearchSuggestions";
     50     // DELETE ME (eventually)
     51     private static final int DBG_SUGGESTION_TIMESTAMPS = 0;
     52 
     53     // This is a superset of all possible column names (need not all be in table)
     54     private static class SuggestionColumns implements BaseColumns {
     55         public static final String DISPLAY1 = "display1";
     56         public static final String DISPLAY2 = "display2";
     57         public static final String QUERY = "query";
     58         public static final String DATE = "date";
     59     }
     60 
     61     /* if you change column order you must also change indices below */
     62     /**
     63      * This is the database projection that can be used to view saved queries, when
     64      * configured for one-line operation.
     65      */
     66     public static final String[] QUERIES_PROJECTION_1LINE = new String[] {
     67         SuggestionColumns._ID,
     68         SuggestionColumns.DATE,
     69         SuggestionColumns.QUERY,
     70         SuggestionColumns.DISPLAY1,
     71     };
     72     /* if you change column order you must also change indices below */
     73     /**
     74      * This is the database projection that can be used to view saved queries, when
     75      * configured for two-line operation.
     76      */
     77     public static final String[] QUERIES_PROJECTION_2LINE = new String[] {
     78         SuggestionColumns._ID,
     79         SuggestionColumns.DATE,
     80         SuggestionColumns.QUERY,
     81         SuggestionColumns.DISPLAY1,
     82         SuggestionColumns.DISPLAY2,
     83     };
     84 
     85     /* these indices depend on QUERIES_PROJECTION_xxx */
     86     /** Index into the provided query projections.  For use with Cursor.update methods. */
     87     public static final int QUERIES_PROJECTION_DATE_INDEX = 1;
     88     /** Index into the provided query projections.  For use with Cursor.update methods. */
     89     public static final int QUERIES_PROJECTION_QUERY_INDEX = 2;
     90     /** Index into the provided query projections.  For use with Cursor.update methods. */
     91     public static final int QUERIES_PROJECTION_DISPLAY1_INDEX = 3;
     92     /** Index into the provided query projections.  For use with Cursor.update methods. */
     93     public static final int QUERIES_PROJECTION_DISPLAY2_INDEX = 4;  // only when 2line active
     94 
     95     /* columns needed to determine whether to truncate history */
     96     private static final String[] TRUNCATE_HISTORY_PROJECTION = new String[] {
     97         SuggestionColumns._ID, SuggestionColumns.DATE
     98     };
     99 
    100     /*
    101      * Set a cap on the count of items in the suggestions table, to
    102      * prevent db and layout operations from dragging to a crawl. Revisit this
    103      * cap when/if db/layout performance improvements are made.
    104      */
    105     private static final int MAX_HISTORY_COUNT = 250;
    106 
    107     // client-provided configuration values
    108     private Context mContext;
    109     private String mAuthority;
    110     private boolean mTwoLineDisplay;
    111     private Uri mSuggestionsUri;
    112     private String[] mQueriesProjection;
    113 
    114     /**
    115      * Although provider utility classes are typically static, this one must be constructed
    116      * because it needs to be initialized using the same values that you provided in your
    117      * {@link android.content.SearchRecentSuggestionsProvider}.
    118      *
    119      * @param authority This must match the authority that you've declared in your manifest.
    120      * @param mode You can use mode flags here to determine certain functional aspects of your
    121      * database.  Note, this value should not change from run to run, because when it does change,
    122      * your suggestions database may be wiped.
    123      *
    124      * @see android.content.SearchRecentSuggestionsProvider
    125      * @see android.content.SearchRecentSuggestionsProvider#setupSuggestions
    126      */
    127     public SearchRecentSuggestions(Context context, String authority, int mode) {
    128         if (TextUtils.isEmpty(authority) ||
    129                 ((mode & SearchRecentSuggestionsProvider.DATABASE_MODE_QUERIES) == 0)) {
    130             throw new IllegalArgumentException();
    131         }
    132         // unpack mode flags
    133         mTwoLineDisplay = (0 != (mode & SearchRecentSuggestionsProvider.DATABASE_MODE_2LINES));
    134 
    135         // saved values
    136         mContext = context;
    137         mAuthority = new String(authority);
    138 
    139         // derived values
    140         mSuggestionsUri = Uri.parse("content://" + mAuthority + "/suggestions");
    141 
    142         if (mTwoLineDisplay) {
    143             mQueriesProjection = QUERIES_PROJECTION_2LINE;
    144         } else {
    145             mQueriesProjection = QUERIES_PROJECTION_1LINE;
    146         }
    147     }
    148 
    149     /**
    150      * Add a query to the recent queries list.
    151      *
    152      * @param queryString The string as typed by the user.  This string will be displayed as
    153      * the suggestion, and if the user clicks on the suggestion, this string will be sent to your
    154      * searchable activity (as a new search query).
    155      * @param line2 If you have configured your recent suggestions provider with
    156      * {@link android.content.SearchRecentSuggestionsProvider#DATABASE_MODE_2LINES}, you can
    157      * pass a second line of text here.  It will be shown in a smaller font, below the primary
    158      * suggestion.  When typing, matches in either line of text will be displayed in the list.
    159      * If you did not configure two-line mode, or if a given suggestion does not have any
    160      * additional text to display, you can pass null here.
    161      */
    162     public void saveRecentQuery(String queryString, String line2) {
    163         if (TextUtils.isEmpty(queryString)) {
    164             return;
    165         }
    166         if (!mTwoLineDisplay && !TextUtils.isEmpty(line2)) {
    167             throw new IllegalArgumentException();
    168         }
    169 
    170         ContentResolver cr = mContext.getContentResolver();
    171         long now = System.currentTimeMillis();
    172 
    173         // Use content resolver (not cursor) to insert/update this query
    174         try {
    175             ContentValues values = new ContentValues();
    176             values.put(SuggestionColumns.DISPLAY1, queryString);
    177             if (mTwoLineDisplay) {
    178                 values.put(SuggestionColumns.DISPLAY2, line2);
    179             }
    180             values.put(SuggestionColumns.QUERY, queryString);
    181             values.put(SuggestionColumns.DATE, now);
    182             cr.insert(mSuggestionsUri, values);
    183         } catch (RuntimeException e) {
    184             Log.e(LOG_TAG, "saveRecentQuery", e);
    185         }
    186 
    187         // Shorten the list (if it has become too long)
    188         truncateHistory(cr, MAX_HISTORY_COUNT);
    189     }
    190 
    191     /**
    192      * Completely delete the history.  Use this call to implement a "clear history" UI.
    193      *
    194      * Any application that implements search suggestions based on previous actions (such as
    195      * recent queries, page/items viewed, etc.) should provide a way for the user to clear the
    196      * history.  This gives the user a measure of privacy, if they do not wish for their recent
    197      * searches to be replayed by other users of the device (via suggestions).
    198      */
    199     public void clearHistory() {
    200         ContentResolver cr = mContext.getContentResolver();
    201         truncateHistory(cr, 0);
    202     }
    203 
    204     /**
    205      * Reduces the length of the history table, to prevent it from growing too large.
    206      *
    207      * @param cr Convenience copy of the content resolver.
    208      * @param maxEntries Max entries to leave in the table. 0 means remove all entries.
    209      */
    210     protected void truncateHistory(ContentResolver cr, int maxEntries) {
    211         if (maxEntries < 0) {
    212             throw new IllegalArgumentException();
    213         }
    214 
    215         try {
    216             // null means "delete all".  otherwise "delete but leave n newest"
    217             String selection = null;
    218             if (maxEntries > 0) {
    219                 selection = "_id IN " +
    220                         "(SELECT _id FROM suggestions" +
    221                         " ORDER BY " + SuggestionColumns.DATE + " DESC" +
    222                         " LIMIT -1 OFFSET " + String.valueOf(maxEntries) + ")";
    223             }
    224             cr.delete(mSuggestionsUri, selection, null);
    225         } catch (RuntimeException e) {
    226             Log.e(LOG_TAG, "truncateHistory", e);
    227         }
    228     }
    229 }
    230