Index: chrome/android/java_staging/src/org/chromium/chrome/browser/gsa/ContextReporter.java |
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/gsa/ContextReporter.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/gsa/ContextReporter.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0ddc801230bd9b0dbac769a07c3236242e8b6cf5 |
--- /dev/null |
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/gsa/ContextReporter.java |
@@ -0,0 +1,232 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.gsa; |
+ |
+import android.text.TextUtils; |
+ |
+import org.chromium.base.Log; |
+import org.chromium.base.metrics.RecordHistogram; |
+import org.chromium.chrome.browser.ChromeActivity; |
+import org.chromium.chrome.browser.Tab; |
+import org.chromium.chrome.browser.UrlConstants; |
+import org.chromium.chrome.browser.contextualsearch.ContextualSearchObserver; |
+import org.chromium.chrome.browser.sync.ProfileSyncService; |
+import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver; |
+import org.chromium.chrome.browser.tabmodel.TabModel; |
+import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType; |
+import org.chromium.chrome.browser.tabmodel.TabModelObserver; |
+import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver; |
+import org.chromium.sync.internal_api.pub.PassphraseType; |
+import org.chromium.sync.internal_api.pub.base.ModelType; |
+ |
+import java.util.concurrent.atomic.AtomicBoolean; |
+ |
+import javax.annotation.Nullable; |
+ |
+/** |
+ * Reports context to GSA for search quality. |
+ */ |
+public class ContextReporter { |
+ private static final String TAG = Log.makeTag("GSA"); |
+ |
+ // Values for UMA histogram. |
+ public static final int STATUS_SUCCESS = 0; |
+ public static final int STATUS_GSA_NOT_AVAILABLE = 1; |
+ public static final int STATUS_SYNC_NOT_INITIALIZED = 2; |
+ public static final int STATUS_SYNC_NOT_SYNCING_URLS = 3; |
+ public static final int STATUS_SYNC_NOT_KEYSTORE_PASSPHRASE = 4; |
+ public static final int STATUS_SYNC_OTHER = 5; |
+ public static final int STATUS_SVELTE_DEVICE = 6; |
+ public static final int STATUS_NO_TAB = 7; |
+ public static final int STATUS_INCOGNITO = 8; |
+ public static final int STATUS_INVALID_SCHEME = 9; |
+ public static final int STATUS_TAB_ID_MISMATCH = 10; |
+ public static final int STATUS_DUP_TITLE_CHANGE = 11; |
+ public static final int STATUS_CONNECTION_FAILED = 12; |
+ public static final int STATUS_SYNC_NOT_READY_AT_REPORT_TIME = 13; |
+ public static final int STATUS_NOT_SIGNED_IN = 14; |
+ public static final int STATUS_GSA_ACCOUNT_MISSING = 15; |
+ public static final int STATUS_GSA_ACCOUNT_MISMATCH = 16; |
+ public static final int STATUS_RESULT_IS_NULL = 17; |
+ public static final int STATUS_RESULT_FAILED = 18; |
+ public static final int STATUS_SUCCESS_WITH_SELECTION = 19; |
+ // This should always stay last and have the highest number. |
+ private static final int STATUS_BOUNDARY = 20; |
+ |
+ private final ChromeActivity mActivity; |
+ private final GSAContextReportDelegate mDelegate; |
+ private TabModelSelectorTabObserver mSelectorTabObserver; |
+ private TabModelObserver mModelObserver; |
+ private ContextualSearchObserver mContextualSearchObserver; |
+ private boolean mLastContextWasTitleChange; |
+ private final AtomicBoolean mContextInUse; |
+ |
+ /** |
+ * Creates a ContextReporter for an Activity. |
+ * @param activity Chrome Activity which context will be reported. |
+ * @param controller used to communicate with GSA |
+ */ |
+ public ContextReporter(ChromeActivity activity, GSAContextReportDelegate controller) { |
+ mActivity = activity; |
+ mDelegate = controller; |
+ mContextInUse = new AtomicBoolean(false); |
+ Log.d(TAG, "Created a new ContextReporter"); |
+ } |
+ |
+ /** |
+ * Starts reporting context. |
+ */ |
+ public void enable() { |
+ Tab currentTab = mActivity.getActivityTab(); |
+ reportUsageOfCurrentContextIfPossible(currentTab, false, null); |
+ |
+ TabModelSelector selector = mActivity.getTabModelSelector(); |
+ assert selector != null; |
+ |
+ if (mSelectorTabObserver == null) { |
+ mSelectorTabObserver = new TabModelSelectorTabObserver(selector) { |
+ @Override |
+ public void onTitleUpdated(Tab tab) { |
+ // Report usage declaring this as a title change. |
+ reportUsageOfCurrentContextIfPossible(tab, true, null); |
+ } |
+ |
+ @Override |
+ public void onUrlUpdated(Tab tab) { |
+ reportUsageOfCurrentContextIfPossible(tab, false, null); |
+ } |
+ }; |
+ } |
+ if (mModelObserver == null) { |
+ assert !selector.getModels().isEmpty(); |
+ mModelObserver = new EmptyTabModelObserver() { |
+ @Override |
+ public void didSelectTab(Tab tab, TabSelectionType type, int lastId) { |
+ reportUsageOfCurrentContextIfPossible(tab, false, null); |
+ } |
+ }; |
+ for (TabModel model : selector.getModels()) { |
+ model.addObserver(mModelObserver); |
+ } |
+ } |
+ if (mContextualSearchObserver == null && mActivity.getContextualSearchManager() != null) { |
+ mContextualSearchObserver = new ContextualSearchObserver() { |
+ @Override |
+ public void onShowContextualSearch(GSAContextDisplaySelection contextSelection) { |
+ if (contextSelection != null) reportDisplaySelection(contextSelection); |
+ } |
+ |
+ @Override |
+ public void onHideContextualSearch() { |
+ reportDisplaySelection(null); |
+ } |
+ }; |
+ mActivity.getContextualSearchManager().addObserver(mContextualSearchObserver); |
+ } |
+ } |
+ |
+ /** |
+ * Stops reporting context. Called when the app goes to the background. |
+ */ |
+ public void disable() { |
+ reportUsageEndedIfNecessary(); |
+ |
+ if (mSelectorTabObserver != null) { |
+ mSelectorTabObserver.destroy(); |
+ mSelectorTabObserver = null; |
+ } |
+ if (mModelObserver != null) { |
+ for (TabModel model : mActivity.getTabModelSelector().getModels()) { |
+ model.removeObserver(mModelObserver); |
+ } |
+ mModelObserver = null; |
+ } |
+ if (mContextualSearchObserver != null && mActivity.getContextualSearchManager() != null) { |
+ mActivity.getContextualSearchManager().removeObserver(mContextualSearchObserver); |
+ mContextualSearchObserver = null; |
+ } |
+ } |
+ |
+ /** |
+ * Reports that the given display selection has been established for the current tab. |
+ * @param displaySelection The information about the selection being displayed. |
+ */ |
+ private void reportDisplaySelection(@Nullable GSAContextDisplaySelection displaySelection) { |
+ Tab currentTab = mActivity.getActivityTab(); |
+ reportUsageOfCurrentContextIfPossible(currentTab, false, displaySelection); |
+ } |
+ |
+ private void reportUsageEndedIfNecessary() { |
+ if (mContextInUse.compareAndSet(true, false)) mDelegate.reportContextUsageEnded(); |
+ } |
+ |
+ private void reportUsageOfCurrentContextIfPossible( |
+ Tab tab, boolean isTitleChange, @Nullable GSAContextDisplaySelection displaySelection) { |
+ Tab currentTab = mActivity.getActivityTab(); |
+ if (currentTab == null || currentTab.isIncognito()) { |
+ if (currentTab == null) { |
+ reportStatus(STATUS_NO_TAB); |
+ Log.d(TAG, "Not reporting, tab is null"); |
+ } else { |
+ reportStatus(STATUS_INCOGNITO); |
+ Log.d(TAG, "Not reporting, tab is incognito"); |
+ } |
+ reportUsageEndedIfNecessary(); |
+ return; |
+ } |
+ |
+ String currentUrl = currentTab.getUrl(); |
+ if (TextUtils.isEmpty(currentUrl) || !(currentUrl.startsWith(UrlConstants.HTTP_SCHEME) |
+ || currentUrl.startsWith(UrlConstants.HTTPS_SCHEME))) { |
+ reportStatus(STATUS_INVALID_SCHEME); |
+ Log.d(TAG, "Not reporting, URL scheme is invalid"); |
+ reportUsageEndedIfNecessary(); |
+ return; |
+ } |
+ |
+ // Check whether this is a context change we would like to report. |
+ if (currentTab.getId() != tab.getId()) { |
+ reportStatus(STATUS_TAB_ID_MISMATCH); |
+ Log.d(TAG, "Not reporting, tab ID doesn't match"); |
+ return; |
+ } |
+ if (isTitleChange && mLastContextWasTitleChange) { |
+ reportStatus(STATUS_DUP_TITLE_CHANGE); |
+ Log.d(TAG, "Not reporting, repeated title update"); |
+ return; |
+ } |
+ |
+ reportUsageEndedIfNecessary(); |
+ |
+ mDelegate.reportContext(currentTab.getUrl(), currentTab.getTitle(), displaySelection); |
+ mLastContextWasTitleChange = isTitleChange; |
+ mContextInUse.set(true); |
+ } |
+ |
+ /** |
+ * Records the given status via UMA. |
+ * Use one of the STATUS_* constants above. |
+ */ |
+ public static void reportStatus(int status) { |
+ RecordHistogram.recordEnumeratedHistogram( |
+ "Search.IcingContextReportingStatus", status, STATUS_BOUNDARY); |
+ } |
+ |
+ /** |
+ * Records an appropriate status via UMA given the current sync status. |
+ */ |
+ public static void reportSyncStatus(ProfileSyncService syncService) { |
+ if (!syncService.isSyncInitialized()) { |
+ reportStatus(STATUS_SYNC_NOT_INITIALIZED); |
+ } else if (!syncService.getActiveDataTypes().contains(ModelType.TYPED_URL)) { |
+ reportStatus(STATUS_SYNC_NOT_SYNCING_URLS); |
+ } else if (!syncService.getPassphraseType().equals(PassphraseType.KEYSTORE_PASSPHRASE)) { |
+ reportStatus(STATUS_SYNC_NOT_KEYSTORE_PASSPHRASE); |
+ } else { |
+ reportStatus(STATUS_SYNC_OTHER); |
+ } |
+ } |
+} |