Delay shortcut DB updates to avoid priority inversion.

When working with a large clicklog, updating the shortcuts in the database
after a refresh can be timeconsuming. The update is done on a low priority
background thread, but blocks DB reads which are done on the UI thread. This
can cause priority inversion. By delaying when the updates are performed, we
can reduce how often this priority inversion occurs, which reduces the user
visible latency in the UI.

Change-Id: I58ea6dbd6d1f75bcb477851b49c621d50cd61eb4
diff --git a/src/com/android/quicksearchbox/ShortcutRepositoryImplLog.java b/src/com/android/quicksearchbox/ShortcutRepositoryImplLog.java
index 51fdb42..af93d9b 100644
--- a/src/com/android/quicksearchbox/ShortcutRepositoryImplLog.java
+++ b/src/com/android/quicksearchbox/ShortcutRepositoryImplLog.java
@@ -75,6 +75,8 @@
     private final DbOpenHelper mOpenHelper;
     private final String mSearchSpinner;
 
+    private final UpdateScheduler mUpdateScheduler;
+
     /**
      * Create an instance to the repo.
      */
@@ -98,12 +100,19 @@
         mRefresher = refresher;
         mUiThread = uiThread;
         mLogExecutor = logExecutor;
+        mUpdateScheduler = new UpdateScheduler(uiThread);
         mOpenHelper = new DbOpenHelper(context, name, DB_VERSION, config);
         buildShortcutQueries();
 
         mSearchSpinner = Util.getResourceUri(mContext, R.drawable.search_spinner).toString();
     }
 
+    @VisibleForTesting
+    ShortcutRepositoryImplLog disableUpdateDelay() {
+        mUpdateScheduler.disable();
+        return this;
+    }
+
     // clicklog first, since that's where restrict the result set
     private static final String TABLES = ClickLog.TABLE_NAME + " INNER JOIN " +
             Shortcuts.TABLE_NAME + " ON " + ClickLog.intent_key.fullName + " = " +
@@ -195,6 +204,7 @@
     private void runTransactionAsync(final SQLiteTransaction transaction) {
         mLogExecutor.execute(new Runnable() {
             public void run() {
+                mUpdateScheduler.waitUntilUpdatesCanBeRun();
                 transaction.run(mOpenHelper.getWritableDatabase());
             }
         });
@@ -242,6 +252,7 @@
     public SuggestionCursor getShortcutsForQuery(String query, Collection<Corpus> allowedCorpora) {
         ShortcutCursor shortcuts = getShortcutsForQuery(query, allowedCorpora,
                         System.currentTimeMillis());
+        mUpdateScheduler.delayUpdates();
         if (shortcuts != null) {
             startRefresh(shortcuts);
         }
@@ -557,6 +568,43 @@
         });
     }
 
+    private class UpdateScheduler implements Runnable {
+        private static final int UPDATE_DELAY_MILLIS = 300;
+        private final Handler mHandler;
+        private boolean mCanUpdateNow;
+        private boolean mDisabled;
+
+        public UpdateScheduler(Handler handler) {
+            mHandler = handler;
+            mCanUpdateNow = false;
+        }
+
+        // for testing only
+        public void disable() {
+            mDisabled = true;
+        }
+
+        public synchronized void run() {
+            mCanUpdateNow = true;
+            notifyAll();
+        }
+
+        public synchronized void delayUpdates() {
+            mCanUpdateNow = false;
+            mHandler.removeCallbacks(this);
+            mHandler.postDelayed(this, UPDATE_DELAY_MILLIS);
+        }
+
+        public synchronized void waitUntilUpdatesCanBeRun() {
+            if (mDisabled) return;
+            while (!mCanUpdateNow) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {}
+            }
+        }
+    }
+
 // -------------------------- TABLES --------------------------
 
     /**
diff --git a/tests/src/com/android/quicksearchbox/ShortcutRepositoryTest.java b/tests/src/com/android/quicksearchbox/ShortcutRepositoryTest.java
index 2e01a62..7f0cbbe 100644
--- a/tests/src/com/android/quicksearchbox/ShortcutRepositoryTest.java
+++ b/tests/src/com/android/quicksearchbox/ShortcutRepositoryTest.java
@@ -91,7 +91,7 @@
     protected ShortcutRepositoryImplLog createShortcutRepository() {
         return new ShortcutRepositoryImplLog(getContext(), mConfig, mCorpora,
                 mRefresher, new MockHandler(), mLogExecutor,
-                "test-shortcuts-log.db");
+                "test-shortcuts-log.db").disableUpdateDelay();
     }
 
     @Override