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 package com.android.quicksearchbox; 18 19 import com.google.common.annotations.VisibleForTesting; 20 21 import android.os.Handler; 22 import android.util.Log; 23 24 import java.util.HashSet; 25 26 /** 27 * A SuggestionCursor that allows shortcuts to be updated by overlaying 28 * with results from another cursor. 29 */ 30 public class ShortcutCursor extends ListSuggestionCursor { 31 32 private static final boolean DBG = false; 33 private static final String TAG = "QSB.ShortcutCursor"; 34 35 // mShortcuts is used to close the underlying cursor when we're closed. 36 private final SuggestionCursor mShortcuts; 37 // mRefreshed contains all the cursors that have been refreshed, so that 38 // they can be closed when ShortcutCursor is closed. 39 private final HashSet<SuggestionCursor> mRefreshed; 40 41 private boolean mClosed = false; 42 43 private final ShortcutRefresher mRefresher; 44 private final ShortcutRepository mShortcutRepo; 45 private final Handler mUiThread; 46 ShortcutCursor(String query, SuggestionCursor shortcuts, Handler uiThread, ShortcutRefresher refresher, ShortcutRepository repository)47 private ShortcutCursor(String query, SuggestionCursor shortcuts, Handler uiThread, 48 ShortcutRefresher refresher, ShortcutRepository repository) { 49 super(query); 50 mShortcuts = shortcuts; 51 mUiThread = uiThread; 52 mRefresher = refresher; 53 mShortcutRepo = repository; 54 mRefreshed = new HashSet<SuggestionCursor>(); 55 } 56 57 @VisibleForTesting ShortcutCursor(String query, Handler uiThread, ShortcutRefresher refresher, ShortcutRepository repository)58 ShortcutCursor(String query, Handler uiThread, 59 ShortcutRefresher refresher, ShortcutRepository repository) { 60 this(query, null, uiThread, refresher, repository); 61 } 62 63 @VisibleForTesting ShortcutCursor(SuggestionCursor suggestions)64 ShortcutCursor(SuggestionCursor suggestions) { 65 this(suggestions, true, null, null, null); 66 } 67 ShortcutCursor(SuggestionCursor suggestions, boolean allowWebSearchShortcuts, Handler uiThread, ShortcutRefresher refresher, ShortcutRepository repository)68 public ShortcutCursor(SuggestionCursor suggestions, boolean allowWebSearchShortcuts, 69 Handler uiThread, ShortcutRefresher refresher, ShortcutRepository repository) { 70 this(suggestions.getUserQuery(), suggestions, uiThread, refresher, repository); 71 int count = suggestions.getCount(); 72 if (DBG) Log.d(TAG, "Total shortcuts: " + count); 73 for (int i = 0; i < count; i++) { 74 suggestions.moveTo(i); 75 if (suggestions.getSuggestionSource() != null 76 && (allowWebSearchShortcuts || !suggestions.isWebSearchSuggestion())) { 77 add(new SuggestionPosition(suggestions)); 78 } else { 79 if (DBG) Log.d(TAG, "Skipping shortcut " + i); 80 } 81 } 82 } 83 84 @Override isSuggestionShortcut()85 public boolean isSuggestionShortcut() { 86 // Needed to make refreshed shortcuts be treated as shortcuts 87 return true; 88 } 89 90 /** 91 * Refresh a shortcut from this cursor. 92 * 93 * @param shortcut The shortcut to refresh. Should be a shortcut taken from this cursor. 94 */ refresh(Suggestion shortcut)95 public void refresh(Suggestion shortcut) { 96 mRefresher.refresh(shortcut, new ShortcutRefresher.Listener() { 97 public void onShortcutRefreshed(final Source source, 98 final String shortcutId, final SuggestionCursor refreshed) { 99 if (DBG) Log.d(TAG, "Shortcut refreshed: " + shortcutId); 100 mShortcutRepo.updateShortcut(source, shortcutId, refreshed); 101 mUiThread.post(new Runnable() { 102 public void run() { 103 refresh(source, shortcutId, refreshed); 104 } 105 }); 106 } 107 }); 108 } 109 110 /** 111 * Updates this SuggestionCursor with a refreshed result from another. 112 * Since this modifies the cursor, it should be called on the UI thread. 113 * This class assumes responsibility for closing refreshed. 114 */ refresh(Source source, String shortcutId, SuggestionCursor refreshed)115 private void refresh(Source source, String shortcutId, SuggestionCursor refreshed) { 116 if (DBG) Log.d(TAG, "refresh " + shortcutId); 117 if (mClosed) { 118 if (refreshed != null) { 119 refreshed.close(); 120 } 121 return; 122 } 123 if (refreshed != null) { 124 mRefreshed.add(refreshed); 125 } 126 for (int i = 0; i < getCount(); i++) { 127 moveTo(i); 128 if (shortcutId.equals(getShortcutId()) && source.equals(getSuggestionSource())) { 129 if (refreshed != null && refreshed.getCount() > 0) { 130 if (DBG) Log.d(TAG, "replacing row " + i); 131 replaceRow(new SuggestionPosition(refreshed)); 132 } else { 133 if (DBG) Log.d(TAG, "removing row " + i); 134 removeRow(); 135 } 136 notifyDataSetChanged(); 137 break; 138 } 139 } 140 } 141 142 @Override close()143 public void close() { 144 if (DBG) Log.d(TAG, "close()"); 145 if (mClosed) { 146 throw new IllegalStateException("double close"); 147 } 148 mClosed = true; 149 if (mShortcuts != null) { 150 mShortcuts.close(); 151 } 152 for (SuggestionCursor cursor : mRefreshed) { 153 cursor.close(); 154 } 155 super.close(); 156 } 157 }