Add new usage stats entries for chosen candidates

This CL introduces a set of new usage stats entries so that we can understand the distribution of the number of chosen words grouped by the row number of the candidate table where the word is displayed.

No visible change is intended.

BUG=none
TEST=unittest

git-svn-id: https://mozc.googlecode.com/svn/trunk@445 a6090854-d499-a067-5803-1114d4e51264
diff --git a/src/android/src/com/google/android/inputmethod/japanese/CandidateSelectListener.java b/src/android/src/com/google/android/inputmethod/japanese/CandidateSelectListener.java
index e36a6ed..33bea8e 100644
--- a/src/android/src/com/google/android/inputmethod/japanese/CandidateSelectListener.java
+++ b/src/android/src/com/google/android/inputmethod/japanese/CandidateSelectListener.java
@@ -30,6 +30,7 @@
 package org.mozc.android.inputmethod.japanese;
 
 import org.mozc.android.inputmethod.japanese.protobuf.ProtoCandidates.CandidateWord;
+import com.google.common.base.Optional;
 
 /**
  * Callback interface to handle candidate selection.
@@ -39,6 +40,9 @@
 
   /**
    * Called {@code onCandidateSelected}, when a candidate is selected by user's action.
+   *
+   * @param candidateWord selected word
+   * @param rowIndex index of row in which the selected word is. If absent no stats are sent.
    */
-  public void onCandidateSelected(CandidateWord candidateWord);
+  public void onCandidateSelected(CandidateWord candidateWord, Optional<Integer> rowIndex);
 }
diff --git a/src/android/src/com/google/android/inputmethod/japanese/CandidateView.java b/src/android/src/com/google/android/inputmethod/japanese/CandidateView.java
index aaca421..74ceecd 100644
--- a/src/android/src/com/google/android/inputmethod/japanese/CandidateView.java
+++ b/src/android/src/com/google/android/inputmethod/japanese/CandidateView.java
@@ -44,6 +44,7 @@
 import org.mozc.android.inputmethod.japanese.ui.SpanFactory;
 import org.mozc.android.inputmethod.japanese.view.MozcDrawableFactory;
 import org.mozc.android.inputmethod.japanese.view.SkinType;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 
 import android.content.Context;
@@ -73,8 +74,9 @@
     }
 
     @Override
-    public void onCandidateSelected(CandidateWord candidateWord) {
-      viewEventListener.onConversionCandidateSelected(candidateWord.getId());
+    public void onCandidateSelected(CandidateWord candidateWord, Optional<Integer> rowIndex) {
+      viewEventListener.onConversionCandidateSelected(candidateWord.getId(),
+                                                      Preconditions.checkNotNull(rowIndex));
     }
   }
 
diff --git a/src/android/src/com/google/android/inputmethod/japanese/CandidateWordView.java b/src/android/src/com/google/android/inputmethod/japanese/CandidateWordView.java
index 458fdf6..d456b99 100644
--- a/src/android/src/com/google/android/inputmethod/japanese/CandidateWordView.java
+++ b/src/android/src/com/google/android/inputmethod/japanese/CandidateWordView.java
@@ -128,12 +128,15 @@
     @Nullable
     private CandidateWord pressedCandidate;
     private final RectF candidateRect = new RectF();
+    private Optional<Integer> pressedRowIndex = Optional.absent();
 
     public CandidateWordGestureDetector(Context context) {
       gestureDetector = new GestureDetector(context, new CandidateWordViewGestureListener());
     }
 
-    private void pressCandidate(Row row, Span span) {
+    private void pressCandidate(int rowIndex, Span span) {
+      Row row = calculatedLayout.getRowList().get(rowIndex);
+      pressedRowIndex = Optional.of(rowIndex);
       pressedCandidate = span.getCandidateWord().orNull();
       // TODO(yamaguchi):maybe better to make this rect larger by several pixels to avoid that
       // users fail to select a candidate by unconscious small movement of tap point.
@@ -145,6 +148,7 @@
 
     private void releaseCandidate() {
       pressedCandidate = null;
+      pressedRowIndex = Optional.absent();
     }
 
     CandidateWord getPressedCandidate() {
@@ -166,7 +170,8 @@
       if (calculatedLayout == null) {
         return false;
       }
-      for (Row row : calculatedLayout.getRowList()) {
+      for (int rowIndex = 0; rowIndex < calculatedLayout.getRowList().size(); ++rowIndex) {
+        Row row = calculatedLayout.getRowList().get(rowIndex);
         if (scrolledY < row.getTop()) {
           break;
         }
@@ -180,7 +185,7 @@
           if (scrolledX >= span.getRight()) {
             continue;
           }
-          pressCandidate(row, span);
+          pressCandidate(rowIndex, span);
           invalidate();
           return true;
         }
@@ -225,7 +230,7 @@
         case MotionEvent.ACTION_UP:
           if (pressedCandidate != null) {
             if (candidateRect.contains(scrolledX, scrolledY) && candidateSelectListener != null) {
-              candidateSelectListener.onCandidateSelected(pressedCandidate);
+              candidateSelectListener.onCandidateSelected(pressedCandidate, pressedRowIndex);
             }
             releaseCandidate();
             invalidate();
diff --git a/src/android/src/com/google/android/inputmethod/japanese/MozcService.java b/src/android/src/com/google/android/inputmethod/japanese/MozcService.java
index 2c8bd13..c7e7686 100644
--- a/src/android/src/com/google/android/inputmethod/japanese/MozcService.java
+++ b/src/android/src/com/google/android/inputmethod/japanese/MozcService.java
@@ -219,8 +219,8 @@
   // Package private for testing.
   class MozcEventListener implements ViewEventListener {
     @Override
-    public void onConversionCandidateSelected(int candidateId) {
-      sessionExecutor.submitCandidate(candidateId, renderResultCallback);
+    public void onConversionCandidateSelected(int candidateId, Optional<Integer> rowIndex) {
+      sessionExecutor.submitCandidate(candidateId, rowIndex, renderResultCallback);
       feedbackManager.fireFeedback(FeedbackEvent.CANDIDATE_SELECTED);
     }
 
diff --git a/src/android/src/com/google/android/inputmethod/japanese/SymbolInputView.java b/src/android/src/com/google/android/inputmethod/japanese/SymbolInputView.java
index 27c2521..8e009f6 100644
--- a/src/android/src/com/google/android/inputmethod/japanese/SymbolInputView.java
+++ b/src/android/src/com/google/android/inputmethod/japanese/SymbolInputView.java
@@ -117,7 +117,7 @@
   // TODO(hidehiko): make this class static.
   class SymbolCandidateSelectListener implements CandidateSelectListener {
     @Override
-    public void onCandidateSelected(CandidateWord candidateWord) {
+    public void onCandidateSelected(CandidateWord candidateWord, Optional<Integer> row) {
       if (viewEventListener != null) {
         // If we are on password field, history shouldn't be updated to protect privacy.
         viewEventListener.onSymbolCandidateSelected(
diff --git a/src/android/src/com/google/android/inputmethod/japanese/ViewEventDelegator.java b/src/android/src/com/google/android/inputmethod/japanese/ViewEventDelegator.java
index 87728ef..c98b2f0 100644
--- a/src/android/src/com/google/android/inputmethod/japanese/ViewEventDelegator.java
+++ b/src/android/src/com/google/android/inputmethod/japanese/ViewEventDelegator.java
@@ -36,6 +36,8 @@
 import org.mozc.android.inputmethod.japanese.model.SymbolMajorCategory;
 import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands;
 import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Input.TouchEvent;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
 
 import java.util.List;
 
@@ -78,8 +80,8 @@
   }
 
   @Override
-  public void onConversionCandidateSelected(int candidateId) {
-    delegated.onConversionCandidateSelected(candidateId);
+  public void onConversionCandidateSelected(int candidateId, Optional<Integer> rowIndex) {
+    delegated.onConversionCandidateSelected(candidateId, Preconditions.checkNotNull(rowIndex));
   }
 
   @Override
diff --git a/src/android/src/com/google/android/inputmethod/japanese/ViewEventListener.java b/src/android/src/com/google/android/inputmethod/japanese/ViewEventListener.java
index 52d7cb2..e43e4a6 100644
--- a/src/android/src/com/google/android/inputmethod/japanese/ViewEventListener.java
+++ b/src/android/src/com/google/android/inputmethod/japanese/ViewEventListener.java
@@ -36,6 +36,7 @@
 import org.mozc.android.inputmethod.japanese.model.SymbolMajorCategory;
 import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands;
 import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Input.TouchEvent;
+import com.google.common.base.Optional;
 
 import java.util.List;
 
@@ -68,8 +69,9 @@
    * Called when a conversion candidate is selected.
    *
    * @param candidateId the id which Candidate and CandidateWord has.
+   * @param rowIndex index of row in which the candidate is. If absent no stats are sent.
    */
-  public void onConversionCandidateSelected(int candidateId);
+  public void onConversionCandidateSelected(int candidateId, Optional<Integer> rowIndex);
 
   /**
    * Called when a candidate on symbol input view is selected.
diff --git a/src/android/src/com/google/android/inputmethod/japanese/ViewManager.java b/src/android/src/com/google/android/inputmethod/japanese/ViewManager.java
index a2d629e..ea0f2b8 100644
--- a/src/android/src/com/google/android/inputmethod/japanese/ViewManager.java
+++ b/src/android/src/com/google/android/inputmethod/japanese/ViewManager.java
@@ -94,12 +94,12 @@
     }
 
     @Override
-    public void onConversionCandidateSelected(int candidateId) {
+    public void onConversionCandidateSelected(int candidateId, Optional<Integer> rowIndex) {
       // Restore the keyboard frame if hidden.
       if (mozcView != null) {
         mozcView.resetKeyboardFrameVisibility();
       }
-      super.onConversionCandidateSelected(candidateId);
+      super.onConversionCandidateSelected(candidateId, rowIndex);
     }
   }
 
diff --git a/src/android/src/com/google/android/inputmethod/japanese/session/SessionExecutor.java b/src/android/src/com/google/android/inputmethod/japanese/session/SessionExecutor.java
index 1411110..07f0352 100644
--- a/src/android/src/com/google/android/inputmethod/japanese/session/SessionExecutor.java
+++ b/src/android/src/com/google/android/inputmethod/japanese/session/SessionExecutor.java
@@ -53,6 +53,7 @@
 import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionaryCommand;
 import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionaryCommandStatus;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.protobuf.ByteString;
 
@@ -689,15 +690,67 @@
   /**
    * Sends {@code SUBMIT_CANDIDATE} command to the server asynchronously.
    */
-  public void submitCandidate(int candidateId, EvaluationCallback callback) {
+  public void submitCandidate(int candidateId, Optional<Integer> rowIndex,
+      EvaluationCallback callback) {
+    Preconditions.checkNotNull(rowIndex);
     Input.Builder inputBuilder = Input.newBuilder()
         .setType(CommandType.SEND_COMMAND)
         .setCommand(SessionCommand.newBuilder()
             .setType(SessionCommand.CommandType.SUBMIT_CANDIDATE)
             .setId(candidateId));
     evaluateAsynchronously(inputBuilder, null, callback);
+
+    if (rowIndex.isPresent()) {
+      candidateSubmissionStatsEvent(rowIndex.get());
+    }
   }
 
+  private void candidateSubmissionStatsEvent(int rowIndex) {
+    Preconditions.checkArgument(rowIndex >= 0);
+
+    UsageStatsEvent event;
+    switch (rowIndex) {
+      case 0:
+        event = UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_0;
+        break;
+      case 1:
+        event = UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_1;
+        break;
+      case 2:
+        event = UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_2;
+        break;
+      case 3:
+        event = UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_3;
+        break;
+      case 4:
+        event = UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_4;
+        break;
+      case 5:
+        event = UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_5;
+        break;
+      case 6:
+        event = UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_6;
+        break;
+      case 7:
+        event = UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_7;
+        break;
+      case 8:
+        event = UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_8;
+        break;
+      case 9:
+        event = UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_9;
+        break;
+      default:
+        event = UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_GE10;
+    }
+    evaluateAsynchronously(
+        Input.newBuilder()
+        .setType(CommandType.SEND_COMMAND)
+        .setCommand(SessionCommand.newBuilder()
+            .setType(SessionCommand.CommandType.USAGE_STATS_EVENT)
+            .setUsageStatsEvent(event)),
+        null, null);
+  }
   /**
    * Sends {@code RESET_CONTEXT} command to the server asynchronously.
    */
diff --git a/src/android/tests/src/com/google/android/inputmethod/japanese/CandidateViewTest.java b/src/android/tests/src/com/google/android/inputmethod/japanese/CandidateViewTest.java
index 60c2ac3..d446bb0 100644
--- a/src/android/tests/src/com/google/android/inputmethod/japanese/CandidateViewTest.java
+++ b/src/android/tests/src/com/google/android/inputmethod/japanese/CandidateViewTest.java
@@ -59,11 +59,11 @@
     ConversionCandidateSelectListener conversionCandidateSelectListener =
         new ConversionCandidateSelectListener(viewEventListener);
 
-    viewEventListener.onConversionCandidateSelected(0);
+    viewEventListener.onConversionCandidateSelected(0, Optional.<Integer>of(1));
     replayAll();
 
     conversionCandidateSelectListener.onCandidateSelected(
-        CandidateWord.newBuilder().setId(0).buildPartial());
+        CandidateWord.newBuilder().setId(0).buildPartial(), Optional.<Integer>of(1));
 
     verifyAll();
   }
diff --git a/src/android/tests/src/com/google/android/inputmethod/japanese/CandidateWordViewTest.java b/src/android/tests/src/com/google/android/inputmethod/japanese/CandidateWordViewTest.java
index 7fcee1d..2a2cc29 100644
--- a/src/android/tests/src/com/google/android/inputmethod/japanese/CandidateWordViewTest.java
+++ b/src/android/tests/src/com/google/android/inputmethod/japanese/CandidateWordViewTest.java
@@ -224,7 +224,7 @@
       resetAll();
       expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
       candidateSelectListener.onCandidateSelected(
-          ROW_DATA.get(0).getSpanList().get(0).getCandidateWord().get());
+          ROW_DATA.get(0).getSpanList().get(0).getCandidateWord().get(), Optional.<Integer>of(0));
       replayAll();
       events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 10, 10, 0));
       assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
@@ -236,7 +236,7 @@
       resetAll();
       expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
       candidateSelectListener.onCandidateSelected(
-          ROW_DATA.get(0).getSpanList().get(0).getCandidateWord().get());
+          ROW_DATA.get(0).getSpanList().get(0).getCandidateWord().get(), Optional.<Integer>of(0));
       replayAll();
       events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 20, 10, 0));
       assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
diff --git a/src/android/tests/src/com/google/android/inputmethod/japanese/MozcServiceTest.java b/src/android/tests/src/com/google/android/inputmethod/japanese/MozcServiceTest.java
index 5ff8d11..5c93851 100644
--- a/src/android/tests/src/com/google/android/inputmethod/japanese/MozcServiceTest.java
+++ b/src/android/tests/src/com/google/android/inputmethod/japanese/MozcServiceTest.java
@@ -1532,7 +1532,8 @@
     feedbackListener.onSound(AudioManager.FX_KEY_CLICK, 0.5f);
     feedbackListener.onVibrate(100L);
 
-    sessionExecutor.submitCandidate(eq(0), isA(EvaluationCallback.class));
+    sessionExecutor.submitCandidate(eq(0), eq(Optional.<Integer>absent()),
+                                    isA(EvaluationCallback.class));
     replayAll();
 
     // Invoke onConversionCandidateSelected.
diff --git a/src/android/tests/src/com/google/android/inputmethod/japanese/SymbolInputViewTest.java b/src/android/tests/src/com/google/android/inputmethod/japanese/SymbolInputViewTest.java
index dfe3a93..fa4b158 100644
--- a/src/android/tests/src/com/google/android/inputmethod/japanese/SymbolInputViewTest.java
+++ b/src/android/tests/src/com/google/android/inputmethod/japanese/SymbolInputViewTest.java
@@ -41,6 +41,7 @@
 import org.mozc.android.inputmethod.japanese.protobuf.ProtoCandidates.CandidateWord;
 import org.mozc.android.inputmethod.japanese.testing.InstrumentationTestCaseWithMock;
 import org.mozc.android.inputmethod.japanese.testing.Parameter;
+import com.google.common.base.Optional;
 
 import android.app.AlertDialog;
 import android.content.Context;
@@ -187,7 +188,7 @@
     replayAll();
 
     view.new SymbolCandidateSelectListener().onCandidateSelected(
-        CandidateWord.newBuilder().setValue("(^_^)").buildPartial());
+        CandidateWord.newBuilder().setValue("(^_^)").buildPartial(), Optional.<Integer>absent());
 
     verifyAll();
   }
diff --git a/src/android/tests/src/com/google/android/inputmethod/japanese/ViewManagerTest.java b/src/android/tests/src/com/google/android/inputmethod/japanese/ViewManagerTest.java
index 78766b9..f6a7517 100644
--- a/src/android/tests/src/com/google/android/inputmethod/japanese/ViewManagerTest.java
+++ b/src/android/tests/src/com/google/android/inputmethod/japanese/ViewManagerTest.java
@@ -137,10 +137,10 @@
 
     resetAllToDefault();
 
-    viewEventListener.onConversionCandidateSelected(0);
+    viewEventListener.onConversionCandidateSelected(0, Optional.<Integer>absent());
     replayAll();
 
-    viewManagerEventListener.onConversionCandidateSelected(0);
+    viewManagerEventListener.onConversionCandidateSelected(0, Optional.<Integer>absent());
 
     verifyAll();
 
diff --git a/src/android/tests/src/com/google/android/inputmethod/japanese/session/SessionExecutorTest.java b/src/android/tests/src/com/google/android/inputmethod/japanese/session/SessionExecutorTest.java
index cd541ba..7eed5ec 100644
--- a/src/android/tests/src/com/google/android/inputmethod/japanese/session/SessionExecutorTest.java
+++ b/src/android/tests/src/com/google/android/inputmethod/japanese/session/SessionExecutorTest.java
@@ -74,6 +74,7 @@
 import org.mozc.android.inputmethod.japanese.testing.InstrumentationTestCaseWithMock;
 import org.mozc.android.inputmethod.japanese.testing.MemoryLogger;
 import org.mozc.android.inputmethod.japanese.testing.Parameter;
+import com.google.common.base.Optional;
 import com.google.common.base.Strings;
 import com.google.protobuf.ByteString;
 
@@ -605,9 +606,17 @@
                 .setId(3))),
         isNull(KeyEventInterface.class),
         same(evaluationCallback));
+    executor.evaluateAsynchronously(
+        matchesBuilder(Input.newBuilder()
+            .setType(CommandType.SEND_COMMAND)
+            .setCommand(SessionCommand.newBuilder()
+                .setType(SessionCommand.CommandType.USAGE_STATS_EVENT)
+                .setUsageStatsEvent(UsageStatsEvent.SUBMITTED_CANDIDATE_ROW_GE10))),
+        isNull(KeyEventInterface.class),
+        isNull(EvaluationCallback.class));
     replayAll();
 
-    executor.submitCandidate(3, evaluationCallback);
+    executor.submitCandidate(3, Optional.<Integer>of(100), evaluationCallback);
 
     verifyAll();
   }
diff --git a/src/data/usage_stats/stats.def b/src/data/usage_stats/stats.def
index 9d494b1..f1d2f4f 100644
--- a/src/data/usage_stats/stats.def
+++ b/src/data/usage_stats/stats.def
@@ -526,6 +526,18 @@
 SoftwareKeyboardLayoutLandscape
 # Selected software keyboard layout for portrait.
 SoftwareKeyboardLayoutPortrait
+# The count of submitted candidate in n-th row (0-origin)
+SubmittedCandidateRow0
+SubmittedCandidateRow1
+SubmittedCandidateRow2
+SubmittedCandidateRow3
+SubmittedCandidateRow4
+SubmittedCandidateRow5
+SubmittedCandidateRow6
+SubmittedCandidateRow7
+SubmittedCandidateRow8
+SubmittedCandidateRow9
+SubmittedCandidateRowGE10
 
 # for windows
 
diff --git a/src/mozc_version_template.txt b/src/mozc_version_template.txt
index 402b9a4..9a523c5 100644
--- a/src/mozc_version_template.txt
+++ b/src/mozc_version_template.txt
@@ -1,6 +1,6 @@
 MAJOR=2
 MINOR=16
-BUILD=1984
+BUILD=1985
 REVISION=102
 # NACL_DICTIONARY_VERSION is the target version of the system dictionary to be
 # downloaded by NaCl Mozc.
diff --git a/src/session/commands.proto b/src/session/commands.proto
index feae699..ea6727f 100644
--- a/src/session/commands.proto
+++ b/src/session/commands.proto
@@ -468,6 +468,17 @@
     CHARACTER_PALETTE_COMMIT_EVENT = 6;
     SOFTWARE_KEYBOARD_LAYOUT_LANDSCAPE = 7;
     SOFTWARE_KEYBOARD_LAYOUT_PORTRAIT = 8;
+    SUBMITTED_CANDIDATE_ROW_0 = 9;
+    SUBMITTED_CANDIDATE_ROW_1 = 10;
+    SUBMITTED_CANDIDATE_ROW_2 = 11;
+    SUBMITTED_CANDIDATE_ROW_3 = 12;
+    SUBMITTED_CANDIDATE_ROW_4 = 13;
+    SUBMITTED_CANDIDATE_ROW_5 = 14;
+    SUBMITTED_CANDIDATE_ROW_6 = 15;
+    SUBMITTED_CANDIDATE_ROW_7 = 16;
+    SUBMITTED_CANDIDATE_ROW_8 = 17;
+    SUBMITTED_CANDIDATE_ROW_9 = 18;
+    SUBMITTED_CANDIDATE_ROW_GE10 = 19;
   }
   optional UsageStatsEvent usage_stats_event = 7;
   optional int32 usage_stats_event_int_value = 9;
diff --git a/src/session/session_usage_observer.cc b/src/session/session_usage_observer.cc
index bea666a..3a2dfc3 100644
--- a/src/session/session_usage_observer.cc
+++ b/src/session/session_usage_observer.cc
@@ -328,6 +328,39 @@
       UsageStats::SetInteger("SoftwareKeyboardLayoutPortrait",
                              input.command().usage_stats_event_int_value());
       break;
+    case commands::SessionCommand::SUBMITTED_CANDIDATE_ROW_0:
+      UsageStats::IncrementCount("SubmittedCandidateRow0");
+      break;
+    case commands::SessionCommand::SUBMITTED_CANDIDATE_ROW_1:
+      UsageStats::IncrementCount("SubmittedCandidateRow1");
+      break;
+    case commands::SessionCommand::SUBMITTED_CANDIDATE_ROW_2:
+      UsageStats::IncrementCount("SubmittedCandidateRow2");
+      break;
+    case commands::SessionCommand::SUBMITTED_CANDIDATE_ROW_3:
+      UsageStats::IncrementCount("SubmittedCandidateRow3");
+      break;
+    case commands::SessionCommand::SUBMITTED_CANDIDATE_ROW_4:
+      UsageStats::IncrementCount("SubmittedCandidateRow4");
+      break;
+    case commands::SessionCommand::SUBMITTED_CANDIDATE_ROW_5:
+      UsageStats::IncrementCount("SubmittedCandidateRow5");
+      break;
+    case commands::SessionCommand::SUBMITTED_CANDIDATE_ROW_6:
+      UsageStats::IncrementCount("SubmittedCandidateRow6");
+      break;
+    case commands::SessionCommand::SUBMITTED_CANDIDATE_ROW_7:
+      UsageStats::IncrementCount("SubmittedCandidateRow7");
+      break;
+    case commands::SessionCommand::SUBMITTED_CANDIDATE_ROW_8:
+      UsageStats::IncrementCount("SubmittedCandidateRow8");
+      break;
+    case commands::SessionCommand::SUBMITTED_CANDIDATE_ROW_9:
+      UsageStats::IncrementCount("SubmittedCandidateRow9");
+      break;
+    case commands::SessionCommand::SUBMITTED_CANDIDATE_ROW_GE10:
+      UsageStats::IncrementCount("SubmittedCandidateRowGE10");
+      break;
     default:
       LOG(WARNING) << "client side usage stats event has invalid category";
       break;
diff --git a/src/session/session_usage_observer_test.cc b/src/session/session_usage_observer_test.cc
index b211dc2..b4817cb 100644
--- a/src/session/session_usage_observer_test.cc
+++ b/src/session/session_usage_observer_test.cc
@@ -230,6 +230,41 @@
   EXPECT_INTEGER_STATS("SoftwareKeyboardLayoutPortrait", 3);
 }
 
+TEST_F(SessionUsageObserverTest, SubmittedCandidateRow) {
+  SessionUsageObserver observer;
+
+  // create session
+  commands::Command command;
+  command.mutable_input()->set_type(commands::Input::CREATE_SESSION);
+  command.mutable_input()->set_id(1);
+  command.mutable_output()->set_id(1);
+  observer.EvalCommandHandler(command);
+
+  EXPECT_STATS_NOT_EXIST("SubmittedCandidateRow0");
+  EXPECT_STATS_NOT_EXIST("SubmittedCandidateRow1");
+  EXPECT_STATS_NOT_EXIST("SubmittedCandidateRow2");
+  EXPECT_STATS_NOT_EXIST("SubmittedCandidateRow3");
+  EXPECT_STATS_NOT_EXIST("SubmittedCandidateRow4");
+  EXPECT_STATS_NOT_EXIST("SubmittedCandidateRow5");
+  EXPECT_STATS_NOT_EXIST("SubmittedCandidateRow6");
+  EXPECT_STATS_NOT_EXIST("SubmittedCandidateRow7");
+  EXPECT_STATS_NOT_EXIST("SubmittedCandidateRow8");
+  EXPECT_STATS_NOT_EXIST("SubmittedCandidateRow9");
+  EXPECT_STATS_NOT_EXIST("SubmittedCandidateRowGE10");
+
+  command.mutable_input()->set_type(commands::Input::SEND_COMMAND);
+  commands::SessionCommand *session_command =
+      command.mutable_input()->mutable_command();
+  session_command->set_type(commands::SessionCommand::USAGE_STATS_EVENT);
+  session_command->set_usage_stats_event(
+      commands::SessionCommand::SUBMITTED_CANDIDATE_ROW_0);
+  observer.EvalCommandHandler(command);
+  EXPECT_COUNT_STATS("SubmittedCandidateRow0", 1);
+
+  observer.EvalCommandHandler(command);
+  EXPECT_COUNT_STATS("SubmittedCandidateRow0", 2);
+}
+
 TEST_F(SessionUsageObserverTest, LogTouchEvent) {
   scoped_ptr<SessionUsageObserver> observer(new SessionUsageObserver);