1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.chrome.browser.contextualsearch;
6 
7 import android.text.TextUtils;
8 
9 import androidx.annotation.NonNull;
10 import androidx.annotation.Nullable;
11 import androidx.annotation.VisibleForTesting;
12 
13 import org.chromium.base.annotations.CalledByNative;
14 import org.chromium.base.annotations.NativeMethods;
15 
16 /**
17  * Provides a context in which to search, and links to the native ContextualSearchContext.
18  * Includes the selection, selection offsets, surrounding page content, etc.
19  * Requires an override of #onSelectionChanged to call when a non-empty selection is established
20  * or changed.
21  */
22 public abstract class ContextualSearchContext {
23     private static final String TAG = "TTS Context";
24     static final int INVALID_OFFSET = -1;
25 
26     // Non-visible word-break marker.
27     private static final int SOFT_HYPHEN_CHAR = '\u00AD';
28 
29     // Pointer to the native instance of this class.
30     private long mNativePointer;
31 
32     // Whether this context has had the required properties set so it can Resolve a Search Term.
33     private boolean mHasSetResolveProperties;
34 
35     // A shortened version of the actual text content surrounding the selection, or null if not yet
36     // established.
37     private String mSurroundingText;
38 
39     // The start and end offsets of the selection within the text content.
40     private int mSelectionStartOffset = INVALID_OFFSET;
41     private int mSelectionEndOffset = INVALID_OFFSET;
42 
43     // The home country, or an empty string if not set.
44     @NonNull
45     private String mHomeCountry = "";
46 
47     // The detected language of the context, or {@code null} if not yet detected, and empty if
48     // it cannot be reliably determined.
49     private String mDetectedLanguage;
50 
51     // The offset of an initial Tap gesture within the text content.
52     private int mTapOffset = INVALID_OFFSET;
53 
54     // The initial word selected by a Tap, or null.
55     private String mInitialSelectedWord;
56 
57     // The original encoding of the base page.
58     private String mEncoding;
59 
60     // The word that was tapped, as analyzed internally before selection takes place,
61     // or {@code null} if no analysis has been done yet.
62     private String mWordTapped;
63 
64     // The offset of the tapped word within the surrounding text or {@code INVALID_OFFSET} if not
65     // yet analyzed.
66     private int mWordTappedStartOffset = INVALID_OFFSET;
67 
68     // The offset of the tap within the tapped word, or {@code INVALID_OFFSET} if not yet analyzed.
69     private int mTapWithinWordOffset = INVALID_OFFSET;
70 
71     // The words before and after the tapped word, and their offsets.
72     private String mWordPreviousToTap;
73     private int mWordPreviousToTapOffset = INVALID_OFFSET;
74     private String mWordFollowingTap;
75     private int mWordFollowingTapOffset = INVALID_OFFSET;
76 
77     // Data about the previous user interactions and the event-ID from the server that will log it.
78     private long mPreviousEventId;
79     private int mPreviousUserInteractions;
80 
81     // Translation members.
82     @NonNull
83     private String mTargetLanguage = "";
84     @NonNull
85     private String mFluentLanguages = "";
86 
87     // Whether the Related Searches functionality should also be activated.
88     private boolean mDoRelatedSearches;
89 
90     /** A {@link ContextualSearchContext} that ignores changes to the selection. */
91     static class ChangeIgnoringContext extends ContextualSearchContext {
92         @Override
onSelectionChanged()93         void onSelectionChanged() {}
94     }
95 
96     /**
97      * Constructs a context that tracks the selection and some amount of page content.
98      */
ContextualSearchContext()99     ContextualSearchContext() {
100         mNativePointer = ContextualSearchContextJni.get().init(this);
101         mHasSetResolveProperties = false;
102     }
103 
104     /**
105      * Updates a context to be able to resolve a search term and have a large amount of
106      * page content.
107      * @param homeCountry The country where the user usually resides, or an empty string if not
108      *        known.
109      * @param doSendBasePageUrl Whether the base-page URL should be sent to the server.
110      * @param previousEventId An EventID from the server to send along with the resolve request.
111      * @param previousUserInteractions Persisted interaction outcomes to send along with the resolve
112      *        request.
113      * @param targetLanguage The language to translate into, in case translation might be needed.
114      * @param fluentLanguages An ordered comma-separated list of ISO 639 language codes that
115      *        the user can read fluently, or an empty string.
116      */
setResolveProperties(@onNull String homeCountry, boolean doSendBasePageUrl, long previousEventId, int previousUserInteractions, @NonNull String targetLanguage, @NonNull String fluentLanguages)117     void setResolveProperties(@NonNull String homeCountry, boolean doSendBasePageUrl,
118             long previousEventId, int previousUserInteractions, @NonNull String targetLanguage,
119             @NonNull String fluentLanguages) {
120         // TODO(donnd): consider making this a constructor variation.
121         mHasSetResolveProperties = true;
122         mHomeCountry = homeCountry;
123         mPreviousEventId = previousEventId;
124         mPreviousUserInteractions = previousUserInteractions;
125         ContextualSearchContextJni.get().setResolveProperties(getNativePointer(), this, homeCountry,
126                 doSendBasePageUrl, previousEventId, previousUserInteractions);
127         mTargetLanguage = targetLanguage;
128         mFluentLanguages = fluentLanguages;
129     }
130 
131     /**
132      * This method should be called to clean up storage when an instance of this class is
133      * no longer in use.  The ContextualSearchContextJni.get().destroy will call the destructor on
134      * the native instance.
135      */
destroy()136     void destroy() {
137         assert mNativePointer != 0;
138         ContextualSearchContextJni.get().destroy(mNativePointer, this);
139         mNativePointer = 0;
140 
141         // Also zero out private data that may be sizable.
142         mSurroundingText = null;
143     }
144 
145     /**
146      * Sets the surrounding text and selection offsets.
147      * @param encoding The original encoding of the base page.
148      * @param surroundingText The text from the base page surrounding the selection.
149      * @param startOffset The offset of start the selection.
150      * @param endOffset The offset of the end of the selection
151      */
setSurroundingText( String encoding, String surroundingText, int startOffset, int endOffset)152     void setSurroundingText(
153             String encoding, String surroundingText, int startOffset, int endOffset) {
154         setSurroundingText(encoding, surroundingText, startOffset, endOffset, false);
155     }
156 
157     /**
158      * Sets the surrounding text and selection offsets assuming UTF-8 and no insertion-point
159      * support.
160      * @param surroundingText The text from the base page surrounding the selection.
161      * @param startOffset The offset of start the selection.
162      * @param endOffset The offset of the end of the selection
163      */
164     @VisibleForTesting
setSurroundingText(String surroundingText, int startOffset, int endOffset)165     void setSurroundingText(String surroundingText, int startOffset, int endOffset) {
166         setSurroundingText("UTF-8", surroundingText, startOffset, endOffset, false);
167     }
168 
169     /**
170      * Sets the surrounding text and selection offsets.
171      * @param encoding The original encoding of the base page.
172      * @param surroundingText The text from the base page surrounding the selection.
173      * @param startOffset The offset of start the selection.
174      * @param endOffset The offset of the end of the selection.
175      * @param setNative Whether to set the native context too by passing it through JNI.
176      */
177     @VisibleForTesting
setSurroundingText(String encoding, String surroundingText, int startOffset, int endOffset, boolean setNative)178     void setSurroundingText(String encoding, String surroundingText, int startOffset, int endOffset,
179             boolean setNative) {
180         assert startOffset <= endOffset;
181         mEncoding = encoding;
182         mSurroundingText = surroundingText;
183         mSelectionStartOffset = startOffset;
184         mSelectionEndOffset = endOffset;
185         if (startOffset == endOffset && startOffset <= surroundingText.length()
186                 && !hasAnalyzedTap()) {
187             analyzeTap(startOffset);
188         }
189         // Notify of an initial selection if it's not empty.
190         if (endOffset > startOffset) {
191             updateInitialSelectedWord();
192             onSelectionChanged();
193         }
194         if (setNative) {
195             ContextualSearchContextJni.get().setContent(getNativePointer(), this, mSurroundingText,
196                     mSelectionStartOffset, mSelectionEndOffset);
197         }
198         // Detect the language of the surroundings or the selection.
199         setTranslationLanguages(getDetectedLanguage(), mTargetLanguage, mFluentLanguages);
200     }
201 
202     /**
203      * @return The text that surrounds the selection, or {@code null} if none yet known.
204      */
205     @Nullable
getSurroundingText()206     String getSurroundingText() {
207         return mSurroundingText;
208     }
209 
210     /**
211      * @return The offset into the surrounding text of the start of the selection, or
212      *         {@link #INVALID_OFFSET} if not yet established.
213      */
getSelectionStartOffset()214     int getSelectionStartOffset() {
215         return mSelectionStartOffset;
216     }
217 
218     /**
219      * @return The offset into the surrounding text of the end of the selection, or
220      *         {@link #INVALID_OFFSET} if not yet established.
221      */
getSelectionEndOffset()222     int getSelectionEndOffset() {
223         return mSelectionEndOffset;
224     }
225 
226     /**
227      * @return The original encoding of the base page.
228      */
getEncoding()229     String getEncoding() {
230         return mEncoding;
231     }
232 
233     /**
234      * @return The home country, or an empty string if none set.
235      */
getHomeCountry()236     String getHomeCountry() {
237         return mHomeCountry;
238     }
239 
240     /**
241      * @return The initial word selected by a Tap.
242      */
getInitialSelectedWord()243     String getInitialSelectedWord() {
244         return mInitialSelectedWord;
245     }
246 
247     /**
248      * @param word The initial word selected.
249      */
setInitialSelectedWord(String word)250     private void setInitialSelectedWord(String word) {
251         mInitialSelectedWord = word;
252     }
253 
254     /**
255      * @return The text content that follows the selection (one side of the surrounding text).
256      */
getTextContentFollowingSelection()257     String getTextContentFollowingSelection() {
258         if (mSurroundingText != null && mSelectionEndOffset > 0
259                 && mSelectionEndOffset <= mSurroundingText.length()) {
260             return mSurroundingText.substring(mSelectionEndOffset);
261         } else {
262             return "";
263         }
264     }
265 
266     /**
267      * @return Whether this context can Resolve the Search Term.
268      */
canResolve()269     boolean canResolve() {
270         return mHasSetResolveProperties && hasValidSelection();
271     }
272 
273     /**
274      * Prepares the Context to be used in a Resolve request by supplying last minute parameters.
275      * If this call is not made before a Resolve then defaults are used (not exact and not a
276      * Related Search).
277      * @param isExactSearch Specifies whether this search must be exact -- meaning the resolve must
278      *        return a non-expanding result that matches the selection exactly.
279      * @param relatedSearchesStamp Information to be attached to the Resolve request that is needed
280      *        for Related Searches. If this string is empty then no Related Searches results will
281      *        be requested.
282      */
prepareToResolve(boolean isExactSearch, String relatedSearchesStamp)283     void prepareToResolve(boolean isExactSearch, String relatedSearchesStamp) {
284         ContextualSearchContextJni.get().prepareToResolve(
285                 mNativePointer, this, isExactSearch, relatedSearchesStamp);
286     }
287 
288     /**
289      * Notifies of an adjustment that has been applied to the start and end of the selection.
290      * @param startAdjust A signed value indicating the direction of the adjustment to the start of
291      *        the selection (typically a negative value when the selection expands).
292      * @param endAdjust A signed value indicating the direction of the adjustment to the end of
293      *        the selection (typically a positive value when the selection expands).
294      */
onSelectionAdjusted(int startAdjust, int endAdjust)295     void onSelectionAdjusted(int startAdjust, int endAdjust) {
296         // Fully track the selection as it changes.
297         mSelectionStartOffset += startAdjust;
298         mSelectionEndOffset += endAdjust;
299         updateInitialSelectedWord();
300         ContextualSearchContextJni.get().adjustSelection(
301                 getNativePointer(), this, startAdjust, endAdjust);
302         // Notify of changes.
303         onSelectionChanged();
304     }
305 
306     /** Updates the initial selected word if it has not yet been set. */
updateInitialSelectedWord()307     private void updateInitialSelectedWord() {
308         if (TextUtils.isEmpty(mInitialSelectedWord) && !TextUtils.isEmpty(mSurroundingText)) {
309             // TODO(donnd): investigate the root cause of crbug.com/725027 that requires this
310             // additional validation to prevent this substring call from crashing!
311             if (mSelectionEndOffset < mSelectionStartOffset || mSelectionStartOffset < 0
312                     || mSelectionEndOffset > mSurroundingText.length()) {
313                 return;
314             }
315             mInitialSelectedWord =
316                     mSurroundingText.substring(mSelectionStartOffset, mSelectionEndOffset);
317         }
318     }
319 
320     /** @return the current selection, or an empty string if data is invalid or nothing selected. */
getSelection()321     String getSelection() {
322         if (TextUtils.isEmpty(mSurroundingText) || mSelectionEndOffset < mSelectionStartOffset
323                 || mSelectionStartOffset < 0 || mSelectionEndOffset > mSurroundingText.length()) {
324             return "";
325         }
326         return mSurroundingText.substring(mSelectionStartOffset, mSelectionEndOffset);
327     }
328 
329     /**
330      * Notifies this instance that the selection has been changed.
331      */
onSelectionChanged()332     abstract void onSelectionChanged();
333 
334     /**
335      * Gets the language of the current context's content by calling the native CLD3 detector if
336      * needed.
337      * @return An ISO 639 language code string, or an empty string if the language cannot be
338      *         reliably determined.
339      */
340     @NonNull
getDetectedLanguage()341     String getDetectedLanguage() {
342         assert mSurroundingText != null;
343         if (mDetectedLanguage == null) {
344             mDetectedLanguage =
345                     ContextualSearchContextJni.get().detectLanguage(mNativePointer, this);
346         }
347         return mDetectedLanguage;
348     }
349 
350     /**
351      * Pushes the given language down to the native ContextualSearchContext.
352      * @param detectedLanguage An ISO 639 language code string for the language to translate from.
353      * @param targetLanguage An ISO 639 language code string to translation into.
354      * @param fluentLanguages An ordered comma-separated list of ISO 639 language codes that
355      *        the user can read fluently, or an empty string.
356      */
357     @VisibleForTesting
setTranslationLanguages(@onNull String detectedLanguage, @NonNull String targetLanguage, @NonNull String fluentLanguages)358     void setTranslationLanguages(@NonNull String detectedLanguage, @NonNull String targetLanguage,
359             @NonNull String fluentLanguages) {
360         // Set redundant languages to empty strings.
361         fluentLanguages = targetLanguage.equals(fluentLanguages) ? "" : fluentLanguages;
362         if (targetLanguage.equals(detectedLanguage)) {
363             detectedLanguage = "";
364             targetLanguage = "";
365         }
366         ContextualSearchContextJni.get().setTranslationLanguages(
367                 mNativePointer, this, detectedLanguage, targetLanguage, fluentLanguages);
368     }
369 
370     // ============================================================================================
371     // Content Analysis.
372     // ============================================================================================
373 
374     /**
375      * @return Whether this context has valid Surrounding text and initial Tap offset.
376      */
377     @VisibleForTesting
hasValidTappedText()378     boolean hasValidTappedText() {
379         return !TextUtils.isEmpty(mSurroundingText) && mTapOffset >= 0
380                 && mTapOffset <= mSurroundingText.length();
381     }
382 
383     /**
384      * @return Whether this context has a valid selection, which may be an insertion point.
385      */
386     @VisibleForTesting
hasValidSelection()387     boolean hasValidSelection() {
388         return !TextUtils.isEmpty(mSurroundingText) && mSelectionStartOffset != INVALID_OFFSET
389                 && mSelectionEndOffset != INVALID_OFFSET
390                 && mSelectionStartOffset < mSelectionEndOffset
391                 && mSelectionEndOffset < mSurroundingText.length();
392     }
393 
394     /**
395      * @return Whether a Tap gesture has occurred and been analyzed.
396      */
397     @VisibleForTesting
hasAnalyzedTap()398     boolean hasAnalyzedTap() {
399         return mTapOffset >= 0;
400     }
401 
402     /**
403      * @return The word tapped, or {@code null} if the word that was tapped cannot be identified by
404      *         the current limited parsing capability.
405      * @see #analyzeTap(int)
406      */
getWordTapped()407     String getWordTapped() {
408         return mWordTapped;
409     }
410 
411     /**
412      * @return The offset of the start of the tapped word, or {@code INVALID_OFFSET} if the tapped
413      *         word cannot be identified by the current parsing capability.
414      * @see #analyzeTap(int)
415      */
getWordTappedOffset()416     int getWordTappedOffset() {
417         return mWordTappedStartOffset;
418     }
419 
420     /**
421      * @return The offset of the tap within the tapped word, or {@code INVALID_OFFSET} if the tapped
422      *         word cannot be identified by the current parsing capability.
423      * @see #analyzeTap(int)
424      */
getTapOffsetWithinTappedWord()425     int getTapOffsetWithinTappedWord() {
426         return mTapWithinWordOffset;
427     }
428 
429     /**
430      * @return The word previous to the word that was tapped, or {@code null} if not available.
431      */
getWordPreviousToTap()432     String getWordPreviousToTap() {
433         return mWordPreviousToTap;
434     }
435 
436     /**
437      * @return The offset of the first character of the word previous to the word that was tapped,
438      *         or {@code INVALID_OFFSET} if not available.
439      */
getWordPreviousToTapOffset()440     int getWordPreviousToTapOffset() {
441         return mWordPreviousToTapOffset;
442     }
443 
444     /**
445      * @return The word following the word that was tapped, or {@code null} if not available.
446      */
getWordFollowingTap()447     String getWordFollowingTap() {
448         return mWordFollowingTap;
449     }
450 
451     /**
452      * @return The offset of the first character of the word following the word that was tapped,
453      *         or {@code INVALID_OFFSET} if not available.
454      */
getWordFollowingTapOffset()455     int getWordFollowingTapOffset() {
456         return mWordFollowingTapOffset;
457     }
458 
459     /**
460      * Finds the words around the initial Tap offset by expanding and looking for word-breaks.
461      * This mimics the Blink word-segmentation invoked by SelectWordAroundCaret and similar
462      * selection logic, but is only appropriate for limited use.  Does not work on ideographic
463      * languages and possibly many other cases.  Should only be used only for ML signal evaluation.
464      * @param tapOffset The offset of the Tap within the surrounding text.
465      */
analyzeTap(int tapOffset)466     private void analyzeTap(int tapOffset) {
467         mTapOffset = tapOffset;
468         mWordTapped = null;
469         mTapWithinWordOffset = INVALID_OFFSET;
470 
471         assert hasValidTappedText();
472 
473         int wordStartOffset = findWordStartOffset(mTapOffset);
474         int wordEndOffset = findWordEndOffset(mTapOffset);
475         if (wordStartOffset == INVALID_OFFSET || wordEndOffset == INVALID_OFFSET) return;
476 
477         mWordTappedStartOffset = wordStartOffset;
478         mWordTapped = mSurroundingText.substring(wordStartOffset, wordEndOffset);
479         mTapWithinWordOffset = mTapOffset - wordStartOffset;
480 
481         findPreviousWord();
482         findFollowingWord();
483     }
484 
485     /**
486      * Finds the word previous to the word tapped.
487      */
findPreviousWord()488     private void findPreviousWord() {
489         // Scan past word-break characters preceding the tapped word.
490         int previousWordEndOffset = mWordTappedStartOffset;
491         while (previousWordEndOffset >= 1 && isWordBreakAtIndex(previousWordEndOffset - 1)) {
492             --previousWordEndOffset;
493         }
494         if (previousWordEndOffset == 0) return;
495 
496         mWordPreviousToTapOffset = findWordStartOffset(previousWordEndOffset);
497         if (mWordPreviousToTapOffset == INVALID_OFFSET) return;
498 
499         mWordPreviousToTap =
500                 mSurroundingText.substring(mWordPreviousToTapOffset, previousWordEndOffset);
501     }
502 
503     /**
504      * Finds the word following the word tapped.
505      */
findFollowingWord()506     private void findFollowingWord() {
507         int tappedWordOffset = getWordTappedOffset();
508         int followingWordStartOffset = tappedWordOffset + mWordTapped.length() + 1;
509         while (followingWordStartOffset < mSurroundingText.length()
510                 && isWordBreakAtIndex(followingWordStartOffset)) {
511             ++followingWordStartOffset;
512         }
513         if (followingWordStartOffset == mSurroundingText.length()) return;
514 
515         int wordFollowingTapEndOffset = findWordEndOffset(followingWordStartOffset);
516         if (wordFollowingTapEndOffset == INVALID_OFFSET) return;
517 
518         mWordFollowingTapOffset = followingWordStartOffset;
519         mWordFollowingTap =
520                 mSurroundingText.substring(mWordFollowingTapOffset, wordFollowingTapEndOffset);
521     }
522 
523     /**
524      * @return The start of the word that contains the given initial offset, within the surrounding
525      *         text, or {@code INVALID_OFFSET} if not found.
526      */
findWordStartOffset(int initial)527     private int findWordStartOffset(int initial) {
528         // Scan before, aborting if we hit any ideographic letter.
529         for (int offset = initial - 1; offset >= 0; offset--) {
530             if (isWordBreakAtIndex(offset)) {
531                 // The start of the word is after this word break.
532                 return offset + 1;
533             }
534         }
535 
536         return INVALID_OFFSET;
537     }
538 
539     /**
540      * Finds the offset of the end of the word that includes the given initial offset.
541      * NOTE: this is the index of the character just past the last character of the word,
542      * so a 3 character word "who" has start index 0 and end index 3.
543      * The character at the initial offset is examined and each one after that too until a non-word
544      * character is encountered, and that offset will be returned.
545      * @param initial The initial offset to scan from.
546      * @return The end of the word that contains the given initial offset, within the surrounding
547      *         text.
548      */
findWordEndOffset(int initial)549     private int findWordEndOffset(int initial) {
550         // Scan after, aborting if we hit any CJKV letter.
551         for (int offset = initial; offset < mSurroundingText.length(); offset++) {
552             if (isWordBreakAtIndex(offset)) {
553                 // The end of the word is the offset of this word break.
554                 return offset;
555             }
556         }
557         return INVALID_OFFSET;
558     }
559 
560     /**
561      * @return Whether the character at the given index is a word-break.
562      */
isWordBreakAtIndex(int index)563     private boolean isWordBreakAtIndex(int index) {
564         return !Character.isLetterOrDigit(mSurroundingText.charAt(index))
565                 && mSurroundingText.charAt(index) != SOFT_HYPHEN_CHAR;
566     }
567 
568     // ============================================================================================
569     // Test support.
570     // ============================================================================================
571 
572     @VisibleForTesting
getPreviousUserInteractions()573     int getPreviousUserInteractions() {
574         return mPreviousUserInteractions;
575     }
576 
577     @VisibleForTesting
getPreviousEventId()578     long getPreviousEventId() {
579         return mPreviousEventId;
580     }
581 
582     // ============================================================================================
583     // Native callback support.
584     // ============================================================================================
585 
586     @CalledByNative
getNativePointer()587     private long getNativePointer() {
588         assert mNativePointer != 0;
589         return mNativePointer;
590     }
591 
592     @NativeMethods
593     interface Natives {
init(ContextualSearchContext caller)594         long init(ContextualSearchContext caller);
destroy(long nativeContextualSearchContext, ContextualSearchContext caller)595         void destroy(long nativeContextualSearchContext, ContextualSearchContext caller);
setResolveProperties(long nativeContextualSearchContext, ContextualSearchContext caller, String homeCountry, boolean doSendBasePageUrl, long previousEventId, int previousEventResults)596         void setResolveProperties(long nativeContextualSearchContext,
597                 ContextualSearchContext caller, String homeCountry, boolean doSendBasePageUrl,
598                 long previousEventId, int previousEventResults);
adjustSelection(long nativeContextualSearchContext, ContextualSearchContext caller, int startAdjust, int endAdjust)599         void adjustSelection(long nativeContextualSearchContext, ContextualSearchContext caller,
600                 int startAdjust, int endAdjust);
setContent(long nativeContextualSearchContext, ContextualSearchContext caller, String content, int selectionStart, int selectionEnd)601         void setContent(long nativeContextualSearchContext, ContextualSearchContext caller,
602                 String content, int selectionStart, int selectionEnd);
detectLanguage(long nativeContextualSearchContext, ContextualSearchContext caller)603         String detectLanguage(long nativeContextualSearchContext, ContextualSearchContext caller);
setTranslationLanguages(long nativeContextualSearchContext, ContextualSearchContext caller, String detectedLanguage, String targetLanguage, String fluentLanguages)604         void setTranslationLanguages(long nativeContextualSearchContext,
605                 ContextualSearchContext caller, String detectedLanguage, String targetLanguage,
606                 String fluentLanguages);
prepareToResolve(long nativeContextualSearchContext, ContextualSearchContext caller, boolean isExactSearch, String relatedSearchesStamp)607         void prepareToResolve(long nativeContextualSearchContext, ContextualSearchContext caller,
608                 boolean isExactSearch, String relatedSearchesStamp);
609     }
610 }
611