/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; import static android.widget.TwsSuggestionsAdapter.getColumnString; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.WeakHashMap; import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.app.PendingIntent; import android.app.SearchManager; import android.app.SearchableInfo; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.Cursor; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; import android.speech.RecognizerIntent; import android.text.Editable; import android.text.InputType; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.CollapsibleActionView; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.animation.AccelerateInterpolator; import android.view.animation.LinearInterpolator; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.AutoCompleteTextView; import android.widget.CursorAdapter; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.SuggestionsAdapter; import android.widget.TextView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.TextView.OnEditorActionListener; import com.tencent.tws.assistant.drawable.TwsRippleDrawable; import com.tencent.tws.assistant.utils.TwsRippleEffectInterface; import com.tencent.tws.assistant.utils.TwsRippleUtils; import com.tencent.tws.assistant.utils.ReflectUtils; import com.tencent.tws.assistant.utils.ThemeUtils; import com.tencent.tws.sharelib.R; /** * A widget that provides a user interface for the user to enter a search query and submit a request * to a search provider. Shows a list of query suggestions or results, if available, and allows the * user to pick a suggestion or result to launch into. * * <p> * When the SearchView is used in an ActionBar as an action view for a collapsible menu item, it * needs to be set to iconified by default using {@link #setIconifiedByDefault(boolean) * setIconifiedByDefault(true)}. This is the default, so nothing needs to be done. * </p> * <p> * If you want the search field to always be visible, then call setIconifiedByDefault(false). * </p> * * <p> * For more information, see the <a href="{@docRoot}guide/topics/search/index.html">Search</a> * documentation. * <p> * * @see android.view.MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW * @attr ref android.R.styleable#SearchView_iconifiedByDefault * @attr ref android.R.styleable#SearchView_imeOptions * @attr ref android.R.styleable#SearchView_inputType * @attr ref android.R.styleable#SearchView_maxWidth * @attr ref android.R.styleable#SearchView_queryHint */ public class TwsSearchView extends LinearLayout implements CollapsibleActionView, TwsStretchAnimation.AnimationListener{ private static final boolean DBG = false; private static final String LOG_TAG = "TwsSearchView"; private Context mContext; /** * Private constant for removing the microphone in the keyboard. */ private static final String IME_OPTION_NO_MICROPHONE = "nm"; private OnQueryTextListener mOnQueryChangeListener; private OnCloseListener mOnCloseListener; private OnFocusChangeListener mOnQueryTextFocusChangeListener; private OnSuggestionListener mOnSuggestionListener; private OnClickListener mOnSearchClickListener; private boolean mIconifiedByDefault; private boolean mIconified; private CursorAdapter mSuggestionsAdapter; private TextView mSearchCancelButton; private LinearLayout mSearchPlate; private ImageView mCloseButton; private LinearLayout mSearchEditFrame; private TwsSearchAutoComplete mQueryTextView; private View mDropDownAnchor; private boolean mSubmitButtonEnabled; private CharSequence mQueryHint; private boolean mQueryRefinement; private boolean mClearingFocus; private int mMaxWidth; private boolean mVoiceButtonEnabled; private CharSequence mOldQueryText; private CharSequence mUserQuery; private boolean mExpandedInActionView; private int mCollapsedImeOptions; private SearchableInfo mSearchable; private Bundle mAppSearchData; private onCancelClickListener mOnCancelClickListener = null; private LinearLayout mSearchBar; // tws-start::add cancel click listener 20141223 // screent width and height private int screentWidth = 0; private int screentHeight = 0; private float screenDensity = 0; private int maxSizeOffset = 24; private int minSizeOffset = 72; // view be able to flexible maxsize private int maxSize; // view be able to flexible minsize private int minSize; // cuurenr click view private View currentView; // show longest view private View preView; // main layout viewgroup private LinearLayout mainContain; private TwsStretchAnimation stretchanimation; boolean isCancelClick = false; boolean isSearchClick = true; boolean isSearchMode = false; boolean isEnable = true; boolean isReturnEvent = false; // tws-start::add cancel click listener 20142116 static final AutoCompleteTextViewReflector HIDDEN_METHOD_INVOKER = new AutoCompleteTextViewReflector(); private boolean isChange = true; /* * SearchView can be set expanded before the IME is ready to be shown during * initial UI setup. The show operation is asynchronous to account for this. */ private Runnable mShowImeRunnable = new Runnable() { public void run() { InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { HIDDEN_METHOD_INVOKER.showSoftInputUnchecked(imm, TwsSearchView.this, 0); } } }; private Runnable mUpdateDrawableStateRunnable = new Runnable() { public void run() { updateFocusedState(); } }; private Runnable mReleaseCursorRunnable = new Runnable() { public void run() { if (mSuggestionsAdapter != null && mSuggestionsAdapter instanceof SuggestionsAdapter) { mSuggestionsAdapter.changeCursor(null); } } }; // For voice searching private final Intent mVoiceWebSearchIntent; private final Intent mVoiceAppSearchIntent; // A weak map of drawables we've gotten from other packages, so we don't load them // more than once. private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache = new WeakHashMap<String, Drawable.ConstantState>(); /** * Callbacks for changes to the query text. */ public interface OnQueryTextListener { /** * Called when the user submits the query. This could be due to a key press on the * keyboard or due to pressing a submit button. * The listener can override the standard behavior by returning true * to indicate that it has handled the submit request. Otherwise return false to * let the SearchView handle the submission by launching any associated intent. * * @param query the query text that is to be submitted * * @return true if the query has been handled by the listener, false to let the * SearchView perform the default action. */ boolean onQueryTextSubmit(String query); /** * Called when the query text is changed by the user. * * @param newText the new content of the query text field. * * @return false if the SearchView should perform the default action of showing any * suggestions if available, true if the action was handled by the listener. */ boolean onQueryTextChange(String newText); } public interface OnCloseListener { /** * The user is attempting to close the SearchView. * * @return true if the listener wants to override the default behavior of clearing the * text field and dismissing it, false otherwise. */ boolean onClose(); } // tws-start::add cancel click listener 20141023 public interface onCancelClickListener{ // user listener cancel click event public void onClick(boolean isOnCancelClick); } private OnSearchListener mOnSearchListener; public interface OnSearchListener { public void onSearch(); } public void setOnSearchListener(OnSearchListener listener) { mOnSearchListener = listener; } public void setOnCancelClickListener (final onCancelClickListener listener) { mOnCancelClickListener = listener; } public void setOpenFoucusable(boolean isFouces){ // reconsitution code not need setting edittext focus } public void setSearchViewBackgroundResource(int resId) { mSearchBar.setBackgroundResource(resId); // mSearchEditFrame.setBackgroundResource(resId); } public void setSearchViewBackground(Drawable drawable) { if (android.os.Build.VERSION.SDK_INT > 15) { mSearchBar.setBackground(drawable); } else { mSearchBar.setBackgroundDrawable(drawable); } // mSearchEditFrame.setBackground(drawable); } public void setSearchViewBackgroundColor(int color) { // mSearchEditFrame.setBackgroundColor(color); mSearchBar.setBackgroundColor(color); } public void setSearchEditTextHint(String str) { // reconsitution code not need setting edittext hint } public void setSearchEditTextHint(int resid) { } public void setSearchEditHintResource(int resid) { mQueryTextView.setHintTextColor(mContext.getResources().getColorStateList(resid)); } public void setSearchIconResource(int resid) { mQueryTextView.setCompoundDrawablesWithIntrinsicBounds(mContext.getResources().getDrawable(resid), null, null, null); } public void setSearchCancelButton(String str) { mSearchCancelButton.setText(str); } public void setSearchEditBackgroundResource(int resid) { mSearchPlate.setBackgroundResource(resid); } public void setSearchEditBackgroundColor(int color) { mSearchPlate.setBackgroundColor(color); } public void setSearchViewDefaultStatus() { if(mSearchCancelButton.getVisibility() == VISIBLE) { onCancelClicked(); } } public void setShowSearchMode() { isSearchMode = true; onSearchClicked(); } public void setSearchCancleBtnByDefault() { if(mSearchCancelButton.getVisibility() != VISIBLE) { mSearchCancelButton.setVisibility(VISIBLE); } } public void setSearchViewIsChange(boolean change) { isChange = change; } public void setSearchViewThemeDarkToLight() { // drawable mSearchBar.setBackgroundResource(R.drawable.search_view_light_background); mSearchPlate.setBackgroundColor(mContext.getResources().getColor(R.color.search_view_editor_light_background)); // textcolor mQueryTextView.setTextColor(mContext.getResources().getColor(R.color.tws_light_body)); mQueryTextView.setHintTextColor(mContext.getResources().getColor(R.color.search_view_text_color_hint_light)); mSearchCancelButton.setTextColor(mContext.getResources().getColor(R.color.tws_text_blue)); // padding drawable mCloseButton.setImageResource(R.drawable.second_ic_clear_holo_light); mQueryTextView.setCompoundDrawablesWithIntrinsicBounds(mContext.getResources().getDrawable(R.drawable.ic_search), null, null, null); mQueryTextView.twsSetCursorDrawableRes(R.drawable.search_view_cursor_drawable_light); } public void setSearchViewHeightAndMargin(int height,int topMargin, int bottomMargin){ LinearLayout.LayoutParams para = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, height); mSearchBar.setLayoutParams(para); mSearchBar.setPadding(0, topMargin, 0, bottomMargin); requestLayout(); } public void setSearchViewEnable(boolean enable) { isEnable = enable; } public void clearSearchViewFocus() { clearFocus(); isReturnEvent = true; } public void requestSearchViewFocus() { requestFocus(); } // tws-end::add cancel click listener 20141023 /** * Callback interface for selection events on suggestions. These callbacks * are only relevant when a SearchableInfo has been specified by {@link #setSearchableInfo}. */ public interface OnSuggestionListener { /** * Called when a suggestion was selected by navigating to it. * @param position the absolute position in the list of suggestions. * * @return true if the listener handles the event and wants to override the default * behavior of possibly rewriting the query based on the selected item, false otherwise. */ boolean onSuggestionSelect(int position); /** * Called when a suggestion was clicked. * @param position the absolute position of the clicked item in the list of suggestions. * * @return true if the listener handles the event and wants to override the default * behavior of launching any intent or submitting a search query specified on that item. * Return false otherwise. */ boolean onSuggestionClick(int position); } public TwsSearchView(Context context) { this(context, null); } public TwsSearchView(Context context, AttributeSet attrs) { this(context, attrs, R.attr.searchViewStyle); } public TwsSearchView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public TwsSearchView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { // super(context, attrs); super(context, attrs, defStyleAttr); mContext = context; LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.search_view, this, true); mQueryTextView = (TwsSearchAutoComplete) findViewById(R.id.search_src_text); mQueryTextView.setFocusable(false); mQueryTextView.setFocusableInTouchMode(false); mQueryTextView.clearFocus(); mSearchCancelButton = (TextView) findViewById(R.id.cancel_search_btn); boolean bRipple = ThemeUtils.isShowRipple(context); if(bRipple){ TwsRippleDrawable mCancelBtnRippleDrawable = TwsRippleUtils.getDefaultDarkDrawable(mContext); // mCancelBtnRippleDrawable.twsSetHotspotBounds(0, 0, 300, 300); if (android.os.Build.VERSION.SDK_INT > 15) { mSearchCancelButton.setBackground(mCancelBtnRippleDrawable); } else { mSearchCancelButton.setBackgroundDrawable(mCancelBtnRippleDrawable); } } // TwsRippleEffectInterface.setupEffectionOverlay(mSearchCancelButton, mContext); /*tws-start::add 2012-10-19*/ //mQueryTextView.setTextSize(context.getResources().getInteger(R.integer.tws_searchview_text_size)); /*tws-end::add 2012-10-19*/ mQueryTextView.setSearchView(this); mSearchBar = (LinearLayout) findViewById(R.id.search_bar); mSearchEditFrame = (LinearLayout) findViewById(R.id.search_edit_frame); mSearchPlate = (LinearLayout) findViewById(R.id.search_plate); mCloseButton = (ImageView) findViewById(R.id.search_close_btn); mCloseButton.setOnClickListener(mOnClickListener); mQueryTextView.setOnClickListener(mOnClickListener); mSearchCancelButton.setOnClickListener(mOnClickListener); mQueryTextView.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View arg0) { Log.i(LOG_TAG, "OnLongClickListener mQueryTextView 11111"); if(mSearchCancelButton.getVisibility() == VISIBLE) { Log.i(LOG_TAG, "OnLongClickListener mQueryTextView 22222"); mQueryTextView.setCursorVisible(true); return false; }else { Log.i(LOG_TAG, "OnLongClickListener mQueryTextView 33333"); mQueryTextView.setCursorVisible(false); return true; } } }); mQueryTextView.addTextChangedListener(mTextWatcher); mQueryTextView.setOnEditorActionListener(mOnEditorActionListener); mQueryTextView.setOnItemClickListener(mOnItemClickListener); mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener); mQueryTextView.setOnKeyListener(mTextKeyListener); // Inform any listener of focus changes mQueryTextView.setOnFocusChangeListener(new OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { if (mOnQueryTextFocusChangeListener != null) { mOnQueryTextFocusChangeListener.onFocusChange(TwsSearchView.this, hasFocus); } } }); // bruce mainContain = mSearchEditFrame; initCommonData(); initViewData(0); // bruce Log.i(LOG_TAG, "obtainStyledAttributes 11111"); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SearchView, defStyleAttr, 0); Log.i(LOG_TAG, "obtainStyledAttributes 22222 a = " + a.toString()); // tws-start::by bruce::init searchview whether collapse one icon::2014-12-14 setIconifiedByDefault(a.getBoolean(R.styleable.SearchView_iconifiedByDefault, false)); // tws-end::by bruce::init searchview whether collapse one icon::2014-12-14 int maxWidth = a.getDimensionPixelSize(R.styleable.SearchView_maxWidth, -1); Log.i(LOG_TAG, "obtainStyledAttributes 33333 maxWidth = " + maxWidth); if (maxWidth != -1) { setMaxWidth(maxWidth); } CharSequence queryHint = a.getText(R.styleable.SearchView_queryHint); Log.i(LOG_TAG, "obtainStyledAttributes 44444 queryHint = " + queryHint); if (!TextUtils.isEmpty(queryHint)) { setQueryHint(queryHint); } int imeOptions = a.getInt(R.styleable.SearchView_android_imeOptions, -1); Log.i(LOG_TAG, "obtainStyledAttributes 55555 imeOptions = " + imeOptions); if (imeOptions != -1) { setImeOptions(imeOptions); } int inputType = a.getInt(R.styleable.SearchView_android_inputType, -1); Log.i(LOG_TAG, "obtainStyledAttributes 66666 inputType = " + inputType); if (inputType != -1) { setInputType(inputType); } boolean focusable = true; focusable = a.getBoolean(R.styleable.SearchView_focusable, focusable); Log.i(LOG_TAG, "obtainStyledAttributes 77777 focusable = " + focusable); setFocusable(focusable); a.recycle(); // boolean focusable = true; // // a = context.obtainStyledAttributes(attrs, android.R.styleable.View, 0, 0); // focusable = a.getBoolean(android.R.styleable.View_focusable, focusable); // a.recycle(); // setFocusable(focusable); // Save voice intent for later queries/launching mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH); mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mDropDownAnchor = findViewById(mQueryTextView.getDropDownAnchor()); if (mDropDownAnchor != null) { mDropDownAnchor.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { adjustDropDownSizeAndPosition(); } }); } updateViewsVisibility(mIconifiedByDefault); updateQueryHint(); } /** * Sets the SearchableInfo for this SearchView. Properties in the SearchableInfo are used * to display labels, hints, suggestions, create intents for launching search results screens * and controlling other affordances such as a voice button. * * @param searchable a SearchableInfo can be retrieved from the SearchManager, for a specific * activity or a global search provider. */ public void setSearchableInfo(SearchableInfo searchable) { mSearchable = searchable; if (mSearchable != null) { updateSearchAutoComplete(); updateQueryHint(); } // Cache the voice search capability mVoiceButtonEnabled = hasVoiceSearch(); if (mVoiceButtonEnabled) { // Disable the microphone on the keyboard, as a mic is displayed near the text box // TODO: use imeOptions to disable voice input when the new API will be available mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE); } updateViewsVisibility(isIconified()); } /** * Sets the APP_DATA for legacy SearchDialog use. * @param appSearchData bundle provided by the app when launching the search dialog * @hide */ public void setAppSearchData(Bundle appSearchData) { mAppSearchData = appSearchData; } /** * Sets the IME options on the query text field. * * @see TextView#setImeOptions(int) * @param imeOptions the options to set on the query text field * * @attr ref android.R.styleable#SearchView_imeOptions */ public void setImeOptions(int imeOptions) { mQueryTextView.setImeOptions(imeOptions); } /** * Sets the input type on the query text field. * * @see TextView#setInputType(int) * @param inputType the input type to set on the query text field * * @attr ref android.R.styleable#SearchView_inputType */ public void setInputType(int inputType) { mQueryTextView.setInputType(inputType); } /** @hide */ @Override public boolean requestFocus(int direction, Rect previouslyFocusedRect) { // Don't accept focus if in the middle of clearing focus if (mClearingFocus) return false; // Check if SearchView is focusable. if (!isFocusable()) return false; // If it is not iconified, then give the focus to the text field if (!isIconified()) { boolean result = mQueryTextView.requestFocus(direction, previouslyFocusedRect); if (result) { // resolve requese fouces problem because mms // updateViewsVisibility(false); } return result; } else { return super.requestFocus(direction, previouslyFocusedRect); } } /** @hide */ @Override public void clearFocus() { mClearingFocus = true; setImeVisibility(false); super.clearFocus(); mQueryTextView.clearFocus(); // tws-start::by bruce::init twssearchview status::2014-12-18 mQueryTextView.setFocusable(false); mQueryTextView.setFocusableInTouchMode(false); // tws-start::by bruce::init twssearchview status::2014-12-18 mClearingFocus = false; } /** * Sets a listener for user actions within the SearchView. * * @param listener the listener object that receives callbacks when the user performs * actions in the SearchView such as clicking on buttons or typing a query. */ public void setOnQueryTextListener(OnQueryTextListener listener) { mOnQueryChangeListener = listener; } /** * Sets a listener to inform when the user closes the SearchView. * * @param listener the listener to call when the user closes the SearchView. */ public void setOnCloseListener(OnCloseListener listener) { mOnCloseListener = listener; } /** * Sets a listener to inform when the focus of the query text field changes. * * @param listener the listener to inform of focus changes. */ public void setOnQueryTextFocusChangeListener(OnFocusChangeListener listener) { mOnQueryTextFocusChangeListener = listener; } /** * Sets a listener to inform when a suggestion is focused or clicked. * * @param listener the listener to inform of suggestion selection events. */ public void setOnSuggestionListener(OnSuggestionListener listener) { mOnSuggestionListener = listener; } /** * Sets a listener to inform when the search button is pressed. This is only * relevant when the text field is not visible by default. Calling {@link #setIconified * setIconified(false)} can also cause this listener to be informed. * * @param listener the listener to inform when the search button is clicked or * the text field is programmatically de-iconified. */ public void setOnSearchClickListener(OnClickListener listener) { mOnSearchClickListener = listener; } /** * Returns the query string currently in the text field. * * @return the query string */ public CharSequence getQuery() { return mQueryTextView.getText(); } /** * Sets a query string in the text field and optionally submits the query as well. * * @param query the query string. This replaces any query text already present in the * text field. * @param submit whether to submit the query right now or only update the contents of * text field. */ public void setQuery(CharSequence query, boolean submit) { mQueryTextView.setText(query); if (query != null) { mQueryTextView.setSelection(query.length()); mUserQuery = query; } // If the query is not empty and submit is requested, submit the query if (submit && !TextUtils.isEmpty(query)) { onSubmitQuery(); } } /** * Sets the hint text to display in the query text field. This overrides any hint specified * in the SearchableInfo. * * @param hint the hint text to display * * @attr ref android.R.styleable#SearchView_queryHint */ public void setQueryHint(CharSequence hint) { mQueryHint = hint; updateQueryHint(); } /** * Sets the default or resting state of the search field. If true, a single search icon is * shown by default and expands to show the text field and other buttons when pressed. Also, * if the default state is iconified, then it collapses to that state when the close button * is pressed. Changes to this property will take effect immediately. * * <p>The default value is true.</p> * * @param iconified whether the search field should be iconified by default * * @attr ref android.R.styleable#SearchView_iconifiedByDefault */ public void setIconifiedByDefault(boolean iconified) { if (mIconifiedByDefault == iconified) return; mIconifiedByDefault = iconified; updateViewsVisibility(iconified); updateQueryHint(); } /** * Returns the default iconified state of the search field. * @return */ public boolean isIconfiedByDefault() { return mIconifiedByDefault; } /** * Iconifies or expands the SearchView. Any query text is cleared when iconified. This is * a temporary state and does not override the default iconified state set by * {@link #setIconifiedByDefault(boolean)}. If the default state is iconified, then * a false here will only be valid until the user closes the field. And if the default * state is expanded, then a true here will only clear the text field and not close it. * * @param iconify a true value will collapse the SearchView to an icon, while a false will * expand it. */ public void setIconified(boolean iconify) { if (iconify) { onCloseClicked(); } else { onSearchClicked(); } } /** * Returns the current iconified state of the SearchView. * * @return true if the SearchView is currently iconified, false if the search field is * fully visible. */ public boolean isIconified() { return mIconified; } /** * Enables showing a submit button when the query is non-empty. In cases where the SearchView * is being used to filter the contents of the current activity and doesn't launch a separate * results activity, then the submit button should be disabled. * * @param enabled true to show a submit button for submitting queries, false if a submit * button is not required. */ public void setSubmitButtonEnabled(boolean enabled) { mSubmitButtonEnabled = enabled; updateViewsVisibility(isIconified()); } /** * Returns whether the submit button is enabled when necessary or never displayed. * * @return whether the submit button is enabled automatically when necessary */ public boolean isSubmitButtonEnabled() { return mSubmitButtonEnabled; } /** * Specifies if a query refinement button should be displayed alongside each suggestion * or if it should depend on the flags set in the individual items retrieved from the * suggestions provider. Clicking on the query refinement button will replace the text * in the query text field with the text from the suggestion. This flag only takes effect * if a SearchableInfo has been specified with {@link #setSearchableInfo(SearchableInfo)} * and not when using a custom adapter. * * @param enable true if all items should have a query refinement button, false if only * those items that have a query refinement flag set should have the button. * * @see SearchManager#SUGGEST_COLUMN_FLAGS * @see SearchManager#FLAG_QUERY_REFINEMENT */ public void setQueryRefinementEnabled(boolean enable) { mQueryRefinement = enable; if (mSuggestionsAdapter instanceof TwsSuggestionsAdapter) { ((TwsSuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement( enable ? TwsSuggestionsAdapter.REFINE_ALL : TwsSuggestionsAdapter.REFINE_BY_ENTRY); } } /** * Returns whether query refinement is enabled for all items or only specific ones. * @return true if enabled for all items, false otherwise. */ public boolean isQueryRefinementEnabled() { return mQueryRefinement; } /** * You can set a custom adapter if you wish. Otherwise the default adapter is used to * display the suggestions from the suggestions provider associated with the SearchableInfo. * * @see #setSearchableInfo(SearchableInfo) */ public void setSuggestionsAdapter(CursorAdapter adapter) { mSuggestionsAdapter = adapter; mQueryTextView.setAdapter(mSuggestionsAdapter); } /** * Returns the adapter used for suggestions, if any. * @return the suggestions adapter */ public CursorAdapter getSuggestionsAdapter() { return mSuggestionsAdapter; } /** * Makes the view at most this many pixels wide * * @attr ref android.R.styleable#SearchView_maxWidth */ public void setMaxWidth(int maxpixels) { mMaxWidth = maxpixels; requestLayout(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Let the standard measurements take effect in iconified state. if (isIconified()) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); return; } int widthMode = MeasureSpec.getMode(widthMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); switch (widthMode) { case MeasureSpec.AT_MOST: // If there is an upper limit, don't exceed maximum width (explicit or implicit) if (mMaxWidth > 0) { width = Math.min(mMaxWidth, width); } else { width = Math.min(getPreferredWidth(), width); } break; case MeasureSpec.EXACTLY: // If an exact width is specified, still don't exceed any specified maximum width if (mMaxWidth > 0) { width = Math.min(mMaxWidth, width); } break; case MeasureSpec.UNSPECIFIED: // Use maximum width, if specified, else preferred width width = mMaxWidth > 0 ? mMaxWidth : getPreferredWidth(); break; } widthMode = MeasureSpec.EXACTLY; super.onMeasure(MeasureSpec.makeMeasureSpec(width, widthMode), heightMeasureSpec); } private int getPreferredWidth() { return getContext().getResources() .getDimensionPixelSize(R.dimen.search_view_preferred_width); } private void updateViewsVisibility(final boolean collapsed) { mIconified = collapsed; // Visibility of views that are visible when collapsed final int visCollapsed = collapsed ? VISIBLE : GONE; // Is there text in the query final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText()); updateSubmitButton(hasText); if((mSearchCancelButton.getVisibility() == VISIBLE) && (collapsed == false)) { onCancelClicked(); } // modify frame whether show? mSearchEditFrame.setVisibility(collapsed ? GONE : VISIBLE); mSearchPlate.setVisibility(collapsed ? GONE : VISIBLE); mSearchCancelButton.setVisibility(collapsed ? VISIBLE : GONE); updateCloseButton(); updateVoiceButton(!hasText); updateSubmitArea(); } private boolean hasVoiceSearch() { if (mSearchable != null && mSearchable.getVoiceSearchEnabled()) { Intent testIntent = null; if (mSearchable.getVoiceSearchLaunchWebSearch()) { testIntent = mVoiceWebSearchIntent; } else if (mSearchable.getVoiceSearchLaunchRecognizer()) { testIntent = mVoiceAppSearchIntent; } if (testIntent != null) { ResolveInfo ri = getContext().getPackageManager().resolveActivity(testIntent, PackageManager.MATCH_DEFAULT_ONLY); return ri != null; } } return false; } private boolean isSubmitAreaEnabled() { return (mSubmitButtonEnabled || mVoiceButtonEnabled) && !isIconified(); } private void updateSubmitButton(boolean hasText) { int visibility = GONE; if (mSubmitButtonEnabled && isSubmitAreaEnabled() && hasFocus() && (hasText || !mVoiceButtonEnabled)) { visibility = VISIBLE; } // mSubmitButtonEnabled: by setSubmitButtonEnabled() method setting // ever is false,In our the ROM not use this para // mSubmitButton.setVisibility(visibility); } private void updateSubmitArea() { int visibility = GONE; if (isSubmitAreaEnabled() /*&& (mSubmitButton.getVisibility() == VISIBLE || mVoiceButton.getVisibility() == VISIBLE)*/) { visibility = VISIBLE; } // mSubmitArea.setVisibility(visibility); } private void updateCloseButton() { final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText()); // Should we show the close button? It is not shown if there's no focus, // field is not iconified by default and there is no text in it. final boolean showClose = hasText || (mIconifiedByDefault && !mExpandedInActionView); mCloseButton.setVisibility(showClose ? VISIBLE : GONE); final Drawable closeButtonImg = mCloseButton.getDrawable(); if (closeButtonImg != null){ closeButtonImg.setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET); } } private void postUpdateFocusedState() { post(mUpdateDrawableStateRunnable); } private void updateFocusedState() { boolean focused = mQueryTextView.hasFocus(); // mSearchPlate.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET); // mSubmitArea.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET); invalidate(); } @Override protected void onDetachedFromWindow() { removeCallbacks(mUpdateDrawableStateRunnable); post(mReleaseCursorRunnable); super.onDetachedFromWindow(); } private void setImeVisibility(final boolean visible) { if (visible) { post(mShowImeRunnable); } else { removeCallbacks(mShowImeRunnable); InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { imm.hideSoftInputFromWindow(getWindowToken(), 0); } } } /** * Called by the SuggestionsAdapter * @hide */ /* package */void onQueryRefine(CharSequence queryText) { setQuery(queryText); } private final OnClickListener mOnClickListener = new OnClickListener() { public void onClick(View v) { if(v == mCloseButton) { onCloseClicked(); } else if(v == mQueryTextView) { Log.i(LOG_TAG, "isEnable = " + isEnable + "; isSearchClick = " + isSearchClick + "; isReturnEvent = " + isReturnEvent); mQueryTextView.setCursorVisible(true); if(isEnable && isSearchClick) { onSearchClicked(); forceSuggestionQuery(); }else if(isReturnEvent && isEnable) { // bruce mQueryTextView.setFocusable(true); mQueryTextView.setFocusableInTouchMode(true); mQueryTextView.requestFocus(); setImeVisibility(true); // if (mOnSearchClickListener != null) { // mOnSearchClickListener.onClick(this); // } } } else if(v == mSearchCancelButton) { Log.i(LOG_TAG, "isCancelClick = " + isCancelClick); v.postDelayed(new Runnable() { @Override public void run() { if (mOnSearchListener != null) { mOnSearchListener.onSearch(); } else { if(isCancelClick) { onCancelClicked(); } } } }, 120); } } }; /** * Handles the key down event for dealing with action keys. * * @param keyCode This is the keycode of the typed key, and is the same value as * found in the KeyEvent parameter. * @param event The complete event record for the typed key * * @return true if the event was handled here, or false if not. */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (mSearchable == null) { return false; } // if it's an action specified by the searchable activity, launch the // entered query with the action key SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode); if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) { launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView.getText() .toString()); return true; } return super.onKeyDown(keyCode, event); } /** * React to the user typing "enter" or other hardwired keys while typing in * the search box. This handles these special keys while the edit box has * focus. */ View.OnKeyListener mTextKeyListener = new View.OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { // guard against possible race conditions if (mSearchable == null) { return false; } if (DBG) { Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event + "), selection: " + mQueryTextView.getListSelection()); } // If a suggestion is selected, handle enter, search key, and action keys // as presses on the selected suggestion if (mQueryTextView.isPopupShowing() && mQueryTextView.getListSelection() != ListView.INVALID_POSITION) { return onSuggestionsKey(v, keyCode, event); } // If there is text in the query box, handle enter, and action keys // The search key is handled by the dialog's onKeyDown(). if (!mQueryTextView.isEmpty() && event.hasNoModifiers()) { if (event.getAction() == KeyEvent.ACTION_UP) { if (keyCode == KeyEvent.KEYCODE_ENTER) { v.cancelLongPress(); // Launch as a regular search. launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mQueryTextView.getText() .toString()); return true; } } if (event.getAction() == KeyEvent.ACTION_DOWN) { SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode); if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) { launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView .getText().toString()); return true; } } } return false; } }; /** * React to the user typing while in the suggestions list. First, check for * action keys. If not handled, try refocusing regular characters into the * EditText. */ private boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) { // guard against possible race conditions (late arrival after dismiss) if (mSearchable == null) { return false; } if (mSuggestionsAdapter == null) { return false; } if (event.getAction() == KeyEvent.ACTION_DOWN && event.hasNoModifiers()) { // First, check for enter or search (both of which we'll treat as a // "click") if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH || keyCode == KeyEvent.KEYCODE_TAB) { int position = mQueryTextView.getListSelection(); return onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null); } // Next, check for left/right moves, which we use to "return" the // user to the edit view if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { // give "focus" to text editor, with cursor at the beginning if // left key, at end if right key // TODO: Reverse left/right for right-to-left languages, e.g. // Arabic int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mQueryTextView .length(); mQueryTextView.setSelection(selPoint); mQueryTextView.setListSelection(0); mQueryTextView.clearListSelection(); HIDDEN_METHOD_INVOKER.ensureImeVisible(mQueryTextView, true); return true; } // Next, check for an "up and out" move if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mQueryTextView.getListSelection()) { // TODO: restoreUserQuery(); // let ACTV complete the move return false; } // Next, check for an "action key" SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode); if ((actionKey != null) && ((actionKey.getSuggestActionMsg() != null) || (actionKey .getSuggestActionMsgColumn() != null))) { // launch suggestion using action key column int position = mQueryTextView.getListSelection(); if (position != ListView.INVALID_POSITION) { Cursor c = mSuggestionsAdapter.getCursor(); if (c.moveToPosition(position)) { final String actionMsg = getActionKeyMessage(c, actionKey); if (actionMsg != null && (actionMsg.length() > 0)) { return onItemClicked(position, keyCode, actionMsg); } } } } } return false; } /** * For a given suggestion and a given cursor row, get the action message. If * not provided by the specific row/column, also check for a single * definition (for the action key). * * @param c The cursor providing suggestions * @param actionKey The actionkey record being examined * * @return Returns a string, or null if no action key message for this * suggestion */ private static String getActionKeyMessage(Cursor c, SearchableInfo.ActionKeyInfo actionKey) { String result = null; // check first in the cursor data, for a suggestion-specific message final String column = actionKey.getSuggestActionMsgColumn(); if (column != null) { result = TwsSuggestionsAdapter.getColumnString(c, column); } // If the cursor didn't give us a message, see if there's a single // message defined // for the actionkey (for all suggestions) if (result == null) { result = actionKey.getSuggestActionMsg(); } return result; } private int getSearchIconId() { TypedValue outValue = new TypedValue(); getContext().getTheme().resolveAttribute(R.attr.searchViewSearchIcon, outValue, true); return outValue.resourceId; } private CharSequence getDecoratedHint(CharSequence hintText) { // If the field is always expanded, then don't add the search icon to the hint if (!mIconifiedByDefault) return hintText; SpannableStringBuilder ssb = new SpannableStringBuilder(" "); // for the icon ssb.append(hintText); // Drawable searchIcon = getContext().getResources().getDrawable(getSearchIconId()); // //geofffeng ImageSpan align bottom, the icon is not larger than font, so not need to *1.25 // int textSize = (int) mQueryTextView.getTextSize(); //* 1.25); // searchIcon.setBounds(0, 0, textSize, textSize); // ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return ssb; } private void updateQueryHint() { if (mQueryHint != null) { mQueryTextView.setHint(getDecoratedHint(mQueryHint)); } else if (mSearchable != null) { CharSequence hint = null; int hintId = mSearchable.getHintId(); if (hintId != 0) { hint = getContext().getString(hintId); } if (hint != null) { mQueryTextView.setHint(getDecoratedHint(hint)); } } else { mQueryTextView.setHint(getDecoratedHint("")); } } /** * Updates the auto-complete text view. */ private void updateSearchAutoComplete() { mQueryTextView.setDropDownAnimationStyle(0); // no animation mQueryTextView.setThreshold(mSearchable.getSuggestThreshold()); mQueryTextView.setImeOptions(mSearchable.getImeOptions()); int inputType = mSearchable.getInputType(); // We only touch this if the input type is set up for text (which it almost certainly // should be, in the case of search!) if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) { // The existence of a suggestions authority is the proxy for "suggestions // are available here" inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE; if (mSearchable.getSuggestAuthority() != null) { inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE; } } mQueryTextView.setInputType(inputType); if (mSuggestionsAdapter != null) { mSuggestionsAdapter.changeCursor(null); } // attach the suggestions adapter, if suggestions are available // The existence of a suggestions authority is the proxy for "suggestions available here" if (mSearchable.getSuggestAuthority() != null) { mSuggestionsAdapter = new TwsSuggestionsAdapter(getContext(), this, mSearchable, mOutsideDrawablesCache); mQueryTextView.setAdapter(mSuggestionsAdapter); ((TwsSuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement( mQueryRefinement ? TwsSuggestionsAdapter.REFINE_ALL : TwsSuggestionsAdapter.REFINE_BY_ENTRY); } } /** * Update the visibility of the voice button. There are actually two voice search modes, * either of which will activate the button. * @param empty whether the search query text field is empty. If it is, then the other * criteria apply to make the voice button visible. */ private void updateVoiceButton(boolean empty) { int visibility = GONE; if (mVoiceButtonEnabled && !isIconified() && empty) { visibility = VISIBLE; // mSubmitButton.setVisibility(GONE); } // mVoiceButton.setVisibility(visibility); } private final OnEditorActionListener mOnEditorActionListener = new OnEditorActionListener() { /** * Called when the input method default action key is pressed. */ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { onSubmitQuery(); return true; } }; private void onTextChanged(CharSequence newText) { CharSequence text = mQueryTextView.getText(); mUserQuery = text; boolean hasText = !TextUtils.isEmpty(text); updateSubmitButton(hasText); updateVoiceButton(!hasText); updateCloseButton(); updateSubmitArea(); // tws-start::by bruce::control mSearchCancelButton status::2014-12-14 // if(mSearchCancelButton.getVisibility() != VISIBLE) { // mSearchCancelButton.setVisibility(VISIBLE); // } // tws-end::by bruce::control mSearchCancelButton status::2014-12-14 if (mOnQueryChangeListener != null && !TextUtils.equals(newText, mOldQueryText)) { mOnQueryChangeListener.onQueryTextChange(newText.toString()); } mOldQueryText = newText.toString(); } private void onSubmitQuery() { CharSequence query = mQueryTextView.getText(); if (query != null && TextUtils.getTrimmedLength(query) > 0) { if (mOnQueryChangeListener == null || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) { if (mSearchable != null) { launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString()); setImeVisibility(false); } dismissSuggestions(); } } } private void dismissSuggestions() { mQueryTextView.dismissDropDown(); } // tws-start add search cancel fun::20141001 private void onCancelClicked() { // bruce // isSearchClick = true; isCancelClick = false; if(!isSearchMode) { btnDisappearAnimator(); }else { // tws-start::by bruce::setShowSearchMode mSearchCancelButton status::2015-01-14 if(mSearchCancelButton.getVisibility() == VISIBLE) { mSearchCancelButton.setVisibility(GONE); isSearchClick = true; } // tws-end::by bruce::setShowSearchMode mSearchCancelButton status::2015-01-14 } // bruce CharSequence text = mQueryTextView.getText(); if(!TextUtils.isEmpty(text)) { mQueryTextView.setText(""); } clearFocus(); // updateViewsVisibility(false); if(mOnCancelClickListener != null) { mOnCancelClickListener.onClick(true); } } // tws-end add search cancel fun::20141001 private void onCloseClicked() { CharSequence text = mQueryTextView.getText(); if (TextUtils.isEmpty(text)) { if (mIconifiedByDefault) { // If the app doesn't override the close behavior if (mOnCloseListener == null || !mOnCloseListener.onClose()) { // hide the keyboard and remove focus clearFocus(); // collapse the search field updateViewsVisibility(true); } } } else { mQueryTextView.setText(""); mQueryTextView.setFocusable(true); mQueryTextView.setFocusableInTouchMode(true); mQueryTextView.requestFocus(); mCloseButton.setVisibility(View.GONE); setImeVisibility(true); } } private void onSearchClicked() { // updateViewsVisibility(false); // bruce if(isSearchClick && !isSearchMode) { isSearchClick = false; onAnimatorByClick(); } if(isSearchMode) { // tws-start::by bruce::setShowSearchMode mSearchCancelButton status::2015-01-14 if(mSearchCancelButton.getVisibility() != VISIBLE) { mSearchCancelButton.setVisibility(VISIBLE); isCancelClick = true; } // tws-end::by bruce::setShowSearchMode mSearchCancelButton status::2015-01-14 } // bruce mQueryTextView.setFocusable(true); mQueryTextView.setFocusableInTouchMode(true); mQueryTextView.requestFocus(); setImeVisibility(true); if (mOnSearchClickListener != null) { mOnSearchClickListener.onClick(this); } } public void onAnimatorByClick() { // isCancelClick = true; currentView = mainContain.getChildAt(0); clickEvent(currentView); // onOffClickable(false); stretchanimation.startAnimation(currentView, 1); } private void onSearchviewAnimatorByClick() { } private void onSearchviewAnimatorByCancelClick() { } private void onVoiceClicked() { // guard against possible race conditions if (mSearchable == null) { return; } SearchableInfo searchable = mSearchable; try { if (searchable.getVoiceSearchLaunchWebSearch()) { Intent webSearchIntent = createVoiceWebSearchIntent(mVoiceWebSearchIntent, searchable); getContext().startActivity(webSearchIntent); } else if (searchable.getVoiceSearchLaunchRecognizer()) { Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent, searchable); getContext().startActivity(appSearchIntent); } } catch (ActivityNotFoundException e) { // Should not happen, since we check the availability of // voice search before showing the button. But just in case... Log.w(LOG_TAG, "Could not find voice search activity"); } } void onTextFocusChanged() { // updateViewsVisibility(isIconified()); // Delayed update to make sure that the focus has settled down and window focus changes // don't affect it. A synchronous update was not working. postUpdateFocusedState(); if (mQueryTextView.hasFocus()) { forceSuggestionQuery(); } } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); postUpdateFocusedState(); } /** * {@inheritDoc} */ @Override public void onActionViewCollapsed() { clearFocus(); updateViewsVisibility(true); mQueryTextView.setImeOptions(mCollapsedImeOptions); mExpandedInActionView = false; } /** * {@inheritDoc} */ @Override public void onActionViewExpanded() { if (mExpandedInActionView) return; mExpandedInActionView = true; mCollapsedImeOptions = mQueryTextView.getImeOptions(); mQueryTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN); mQueryTextView.setText(""); setIconified(false); } private void adjustDropDownSizeAndPosition() { if (mDropDownAnchor.getWidth() > 1) { Resources res = getContext().getResources(); int anchorPadding = mSearchPlate.getPaddingLeft(); Rect dropDownPadding = new Rect(); int iconOffset = mIconifiedByDefault ? res.getDimensionPixelSize(R.dimen.dropdownitem_icon_width) + res.getDimensionPixelSize(R.dimen.dropdownitem_text_padding_left) : 0; mQueryTextView.getDropDownBackground().getPadding(dropDownPadding); mQueryTextView.setDropDownHorizontalOffset(-(dropDownPadding.left + iconOffset) + anchorPadding); mQueryTextView.setDropDownWidth(mDropDownAnchor.getWidth() + dropDownPadding.left + dropDownPadding.right + iconOffset - (anchorPadding)); } } private boolean onItemClicked(int position, int actionKey, String actionMsg) { if (mOnSuggestionListener == null || !mOnSuggestionListener.onSuggestionClick(position)) { launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null); setImeVisibility(false); dismissSuggestions(); return true; } return false; } private boolean onItemSelected(int position) { if (mOnSuggestionListener == null || !mOnSuggestionListener.onSuggestionSelect(position)) { rewriteQueryFromSuggestion(position); return true; } return false; } private final OnItemClickListener mOnItemClickListener = new OnItemClickListener() { /** * Implements OnItemClickListener */ public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (DBG) Log.d(LOG_TAG, "onItemClick() position " + position); onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null); } }; private final OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() { /** * Implements OnItemSelectedListener */ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if (DBG) Log.d(LOG_TAG, "onItemSelected() position " + position); TwsSearchView.this.onItemSelected(position); } /** * Implements OnItemSelectedListener */ public void onNothingSelected(AdapterView<?> parent) { if (DBG) Log.d(LOG_TAG, "onNothingSelected()"); } }; /** * Query rewriting. */ private void rewriteQueryFromSuggestion(int position) { CharSequence oldQuery = mQueryTextView.getText(); Cursor c = mSuggestionsAdapter.getCursor(); if (c == null) { return; } if (c.moveToPosition(position)) { // Get the new query from the suggestion. CharSequence newQuery = mSuggestionsAdapter.convertToString(c); if (newQuery != null) { // The suggestion rewrites the query. // Update the text field, without getting new suggestions. setQuery(newQuery); } else { // The suggestion does not rewrite the query, restore the user's query. setQuery(oldQuery); } } else { // We got a bad position, restore the user's query. setQuery(oldQuery); } } /** * Launches an intent based on a suggestion. * * @param position The index of the suggestion to create the intent from. * @param actionKey The key code of the action key that was pressed, * or {@link KeyEvent#KEYCODE_UNKNOWN} if none. * @param actionMsg The message for the action key that was pressed, * or <code>null</code> if none. * @return true if a successful launch, false if could not (e.g. bad position). */ private boolean launchSuggestion(int position, int actionKey, String actionMsg) { Cursor c = mSuggestionsAdapter.getCursor(); if ((c != null) && c.moveToPosition(position)) { Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg); // launch the intent launchIntent(intent); return true; } return false; } /** * Launches an intent, including any special intent handling. */ private void launchIntent(Intent intent) { if (intent == null) { return; } try { // If the intent was created from a suggestion, it will always have an explicit // component here. getContext().startActivity(intent); } catch (RuntimeException ex) { Log.e(LOG_TAG, "Failed launch activity: " + intent, ex); } } /** * Sets the text in the query box, without updating the suggestions. */ private void setQuery(CharSequence query) { mQueryTextView.setText(query, true); // Move the cursor to the end mQueryTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length()); } private void launchQuerySearch(int actionKey, String actionMsg, String query) { String action = Intent.ACTION_SEARCH; Intent intent = createIntent(action, null, null, query, actionKey, actionMsg); getContext().startActivity(intent); } /** * Constructs an intent from the given information and the search dialog state. * * @param action Intent action. * @param data Intent data, or <code>null</code>. * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>. * @param query Intent query, or <code>null</code>. * @param actionKey The key code of the action key that was pressed, * or {@link KeyEvent#KEYCODE_UNKNOWN} if none. * @param actionMsg The message for the action key that was pressed, * or <code>null</code> if none. * @param mode The search mode, one of the acceptable values for * {@link SearchManager#SEARCH_MODE}, or {@code null}. * @return The intent. */ private Intent createIntent(String action, Uri data, String extraData, String query, int actionKey, String actionMsg) { // Now build the Intent Intent intent = new Intent(action); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // We need CLEAR_TOP to avoid reusing an old task that has other activities // on top of the one we want. We don't want to do this in in-app search though, // as it can be destructive to the activity stack. if (data != null) { intent.setData(data); } intent.putExtra(SearchManager.USER_QUERY, mUserQuery); if (query != null) { intent.putExtra(SearchManager.QUERY, query); } if (extraData != null) { intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData); } if (mAppSearchData != null) { intent.putExtra(SearchManager.APP_DATA, mAppSearchData); } if (actionKey != KeyEvent.KEYCODE_UNKNOWN) { intent.putExtra(SearchManager.ACTION_KEY, actionKey); intent.putExtra(SearchManager.ACTION_MSG, actionMsg); } intent.setComponent(mSearchable.getSearchActivity()); return intent; } /** * Create and return an Intent that can launch the voice search activity for web search. */ private Intent createVoiceWebSearchIntent(Intent baseIntent, SearchableInfo searchable) { Intent voiceIntent = new Intent(baseIntent); ComponentName searchActivity = searchable.getSearchActivity(); voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null : searchActivity.flattenToShortString()); return voiceIntent; } /** * Create and return an Intent that can launch the voice search activity, perform a specific * voice transcription, and forward the results to the searchable activity. * * @param baseIntent The voice app search intent to start from * @return A completely-configured intent ready to send to the voice search activity */ private Intent createVoiceAppSearchIntent(Intent baseIntent, SearchableInfo searchable) { ComponentName searchActivity = searchable.getSearchActivity(); // create the necessary intent to set up a search-and-forward operation // in the voice search system. We have to keep the bundle separate, // because it becomes immutable once it enters the PendingIntent Intent queryIntent = new Intent(Intent.ACTION_SEARCH); queryIntent.setComponent(searchActivity); PendingIntent pending = PendingIntent.getActivity(getContext(), 0, queryIntent, PendingIntent.FLAG_ONE_SHOT); // Now set up the bundle that will be inserted into the pending intent // when it's time to do the search. We always build it here (even if empty) // because the voice search activity will always need to insert "QUERY" into // it anyway. Bundle queryExtras = new Bundle(); // Now build the intent to launch the voice search. Add all necessary // extras to launch the voice recognizer, and then all the necessary extras // to forward the results to the searchable activity Intent voiceIntent = new Intent(baseIntent); // Add all of the configuration options supplied by the searchable's metadata String languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM; String prompt = null; String language = null; int maxResults = 1; Resources resources = getResources(); if (searchable.getVoiceLanguageModeId() != 0) { languageModel = resources.getString(searchable.getVoiceLanguageModeId()); } if (searchable.getVoicePromptTextId() != 0) { prompt = resources.getString(searchable.getVoicePromptTextId()); } if (searchable.getVoiceLanguageId() != 0) { language = resources.getString(searchable.getVoiceLanguageId()); } if (searchable.getVoiceMaxResults() != 0) { maxResults = searchable.getVoiceMaxResults(); } voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel); voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt); voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language); voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults); voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null : searchActivity.flattenToShortString()); // Add the values that configure forwarding the results voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending); voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras); return voiceIntent; } /** * When a particular suggestion has been selected, perform the various lookups required * to use the suggestion. This includes checking the cursor for suggestion-specific data, * and/or falling back to the XML for defaults; It also creates REST style Uri data when * the suggestion includes a data id. * * @param c The suggestions cursor, moved to the row of the user's selection * @param actionKey The key code of the action key that was pressed, * or {@link KeyEvent#KEYCODE_UNKNOWN} if none. * @param actionMsg The message for the action key that was pressed, * or <code>null</code> if none. * @return An intent for the suggestion at the cursor's position. */ private Intent createIntentFromSuggestion(Cursor c, int actionKey, String actionMsg) { try { // use specific action if supplied, or default action if supplied, or fixed default String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION); if (action == null) { action = mSearchable.getSuggestIntentAction(); } if (action == null) { action = Intent.ACTION_SEARCH; } // use specific data if supplied, or default data if supplied String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA); if (data == null) { data = mSearchable.getSuggestIntentData(); } // then, if an ID was provided, append it. if (data != null) { String id = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); if (id != null) { data = data + "/" + Uri.encode(id); } } Uri dataUri = (data == null) ? null : Uri.parse(data); String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY); String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA); return createIntent(action, dataUri, extraData, query, actionKey, actionMsg); } catch (RuntimeException e ) { int rowNum; try { // be really paranoid now rowNum = c.getPosition(); } catch (RuntimeException e2 ) { rowNum = -1; } Log.w(LOG_TAG, "Search Suggestions cursor at row " + rowNum + " returned exception" + e.toString()); return null; } } private void forceSuggestionQuery() { HIDDEN_METHOD_INVOKER.doBeforeTextChanged(mQueryTextView); HIDDEN_METHOD_INVOKER.doAfterTextChanged(mQueryTextView); } static boolean isLandscapeMode(Context context) { return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; } /** * Callback to watch the text field for empty/non-empty */ private TextWatcher mTextWatcher = new TextWatcher() { public void beforeTextChanged(CharSequence s, int start, int before, int after) { } public void onTextChanged(CharSequence s, int start, int before, int after) { TwsSearchView.this.onTextChanged(s); } public void afterTextChanged(Editable s) { } }; /** * Local subclass for AutoCompleteTextView. * @hide */ public static class TwsSearchAutoComplete extends AutoCompleteTextView { private int mThreshold; private TwsSearchView mSearchView; public TwsSearchAutoComplete(Context context) { super(context); mThreshold = getThreshold(); } public TwsSearchAutoComplete(Context context, AttributeSet attrs) { super(context, attrs); mThreshold = getThreshold(); } public TwsSearchAutoComplete(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mThreshold = getThreshold(); } void setSearchView(TwsSearchView searchView) { mSearchView = searchView; } @Override public void setThreshold(int threshold) { super.setThreshold(threshold); mThreshold = threshold; } /** * Returns true if the text field is empty, or contains only whitespace. */ private boolean isEmpty() { return TextUtils.getTrimmedLength(getText()) == 0; } /** * We override this method to avoid replacing the query box text when a * suggestion is clicked. */ @Override protected void replaceText(CharSequence text) { } /** * We override this method to avoid an extra onItemClick being called on * the drop-down's OnItemClickListener by * {@link AutoCompleteTextView#onKeyUp(int, KeyEvent)} when an item is * clicked with the trackball. */ @Override public void performCompletion() { } public void twsSetCursorDrawableRes(int resId){ try { Class c = ReflectUtils.forClassName("android.widget.TextView"); Field mCursorDrawableRes = ReflectUtils.getDeclaredField(TextView.class, "mCursorDrawableRes"); ReflectUtils.setFieldValue((TextView)this, mCursorDrawableRes, resId); } catch (Exception e) { e.printStackTrace(); } } /** * We override this method to be sure and show the soft keyboard if * appropriate when the TextView has focus. */ @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (hasWindowFocus && mSearchView.hasFocus() && getVisibility() == VISIBLE) { InputMethodManager inputManager = (InputMethodManager) getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); inputManager.showSoftInput(this, 0); // If in landscape mode, then make sure that // the ime is in front of the dropdown. if (isLandscapeMode(getContext())) { HIDDEN_METHOD_INVOKER.ensureImeVisible(this, true); } } } @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(focused, direction, previouslyFocusedRect); mSearchView.onTextFocusChanged(); } /** * We override this method so that we can allow a threshold of zero, * which ACTV does not. */ @Override public boolean enoughToFilter() { return mThreshold <= 0 || super.enoughToFilter(); } boolean isShow = false; @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); if ((imm != null) && (event.getAction() == 0)) { isShow = imm.hideSoftInputFromWindow(getWindowToken(), 0); Log.i("TwsSearchView", "onKeyPreIme isShow = " + isShow + "; event = " + event.getAction()); } if (keyCode == KeyEvent.KEYCODE_BACK && isShow) { // special case for the back key, we do not even try to send it // to the drop down list but instead, consume it immediately if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null) { state.startTracking(event, this); } // mSearchView.isSearchClick = true; mSearchView.isReturnEvent = true; if(mSearchView.isChange && (mSearchView.mSearchCancelButton.getVisibility() == VISIBLE)) { mSearchView.onCancelClicked(); } return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null) { state.handleUpEvent(event); } if (event.isTracking() && !event.isCanceled()) { mSearchView.clearFocus(); mSearchView.setImeVisibility(false); return true; } } } return super.onKeyPreIme(keyCode, event); } } private static class AutoCompleteTextViewReflector { private Method doBeforeTextChanged, doAfterTextChanged; private Method ensureImeVisible; private Method showSoftInputUnchecked; AutoCompleteTextViewReflector() { doBeforeTextChanged = ReflectUtils.getDeclaredMethod( AutoCompleteTextView.class, "doBeforeTextChanged"); doAfterTextChanged = ReflectUtils.getDeclaredMethod( AutoCompleteTextView.class, "doAfterTextChanged"); ensureImeVisible = ReflectUtils.getDeclaredMethod( AutoCompleteTextView.class, "ensureImeVisible", boolean.class); showSoftInputUnchecked = ReflectUtils.getDeclaredMethod( InputMethodManager.class, "showSoftInputUnchecked", int.class, ResultReceiver.class); } void doBeforeTextChanged(AutoCompleteTextView view) { if (doBeforeTextChanged != null) { try { doBeforeTextChanged.invoke(view); } catch (Exception e) { } } } void doAfterTextChanged(AutoCompleteTextView view) { if (doAfterTextChanged != null) { try { doAfterTextChanged.invoke(view); } catch (Exception e) { } } } void ensureImeVisible(AutoCompleteTextView view, boolean visible) { if (ensureImeVisible != null) { try { ensureImeVisible.invoke(view, visible); } catch (Exception e) { } } } void showSoftInputUnchecked(InputMethodManager imm, View view, int flags) { if (showSoftInputUnchecked != null) { try { showSoftInputUnchecked.invoke(imm, flags, null); return; } catch (Exception e) { } } // Hidden method failed, call public version instead imm.showSoftInput(view, flags); } } // bruce private void initCommonData() { DisplayMetrics metric = new DisplayMetrics(); android.view.WindowManager manager = (android.view.WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); manager.getDefaultDisplay().getMetrics(metric); screentWidth = metric.widthPixels; screentHeight = metric.heightPixels; screenDensity = metric.density; // horizatal measureSize(screentWidth, screenDensity); stretchanimation = new TwsStretchAnimation(maxSize, minSize, TwsStretchAnimation.TYPE.horizontal, 100); stretchanimation.setInterpolator(new AccelerateInterpolator()); stretchanimation.setDuration(100); stretchanimation.setOnAnimationListener(this); } /** * @param index * init */ private void initViewData(int index) { View child; int sizeValue = 0; android.view.ViewGroup.LayoutParams params = null; int childCount = mainContain.getChildCount(); if (index < 0 || index >= childCount) { throw new RuntimeException("index outofbounds"); } for (int i = 0; i < childCount - 1; i++) { child = mainContain.getChildAt(i); // child.setOnClickListener(this); params = child.getLayoutParams(); if (i == index) { preView = child; sizeValue = maxSize; } else { sizeValue = minSize; } if (stretchanimation.getmType() == TwsStretchAnimation.TYPE.horizontal) { params.width = sizeValue; } else if (stretchanimation.getmType() == TwsStretchAnimation.TYPE.vertical) { params.height = sizeValue; } child.setLayoutParams(params); } } private void clickEvent(View view) { View child; int childCount = mainContain.getChildCount(); LinearLayout.LayoutParams params; for (int i = 0; i < childCount; i++) { child = mainContain.getChildAt(i); if (preView == child) { params = (android.widget.LinearLayout.LayoutParams) child .getLayoutParams(); if (preView != view) { params.weight = 1.0f; } child.setLayoutParams(params); } else { params = (android.widget.LinearLayout.LayoutParams) child .getLayoutParams(); params.weight = 0.0f; if (stretchanimation.getmType() == TwsStretchAnimation.TYPE.horizontal) { params.width = minSize; } else if (stretchanimation.getmType() == TwsStretchAnimation.TYPE.vertical) { params.height = minSize; } child.setLayoutParams(params); } } preView = view; } /** * measure View * @param screenSize * @param index */ private void measureSize(int layoutSize, float density) { Log.i(LOG_TAG, "density = " + density); int halfWidth = layoutSize / 2; maxSize = layoutSize - maxSizeOffset * (int)density; String country = mContext.getResources().getConfiguration().locale.getCountry(); boolean isUKorUS = country.equals("UK") || country.equals("US"); minSize = layoutSize - (isUKorUS?minSizeOffset + 16:minSizeOffset) * (int)density; Log.i(LOG_TAG, "country="+country + " maxWidth=" + maxSize + " minWidth = " + minSize); } // LinearLayout all childView is click private void onOffClickable(boolean isClickable) { View child; int childCount = mainContain.getChildCount(); for (int i = 0; i < childCount - 1; i++) { child = mainContain.getChildAt(i); child.setClickable(isClickable); } } @Override public void animationEnd(View v, int type) { // TODO Auto-generated method stub Log.e(LOG_TAG, "animationEnd tpye = " + type); if(type == 1) { btnAppearAnimator(); }else{ isSearchClick = true; } } public void btnAppearAnimator(){ // mSearchCancelButton.setVisibility(View.VISIBLE); PropertyValuesHolder pvalphaCancel = PropertyValuesHolder.ofFloat("alpha", 0.0f,1.0f); float getx = mSearchCancelButton.getX(); float getWidth = mSearchCancelButton.getWidth(); PropertyValuesHolder pvxCancel = PropertyValuesHolder.ofFloat("translationX", mSearchCancelButton.getX() + mSearchCancelButton.getWidth(), mSearchCancelButton.getX()); ObjectAnimator mObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(mSearchCancelButton, pvalphaCancel/*, pvxCancel*/); mObjectAnimator.addListener(new AnimatorListener(){ @Override public void onAnimationCancel(Animator arg0) { // TODO Auto-generated method stub } @Override public void onAnimationEnd(Animator arg0) { // TODO Auto-generated method stub isCancelClick = true; } @Override public void onAnimationRepeat(Animator arg0) { // TODO Auto-generated method stub } @Override public void onAnimationStart(Animator arg0) { // TODO Auto-generated method stub // mSearchCancelButton.setVisibility(View.VISIBLE); // mSearchCancelButton.setText("aaaaaaa"); // tws-start::by bruce::control mSearchCancelButton status::2014-12-14 if(mSearchCancelButton.getVisibility() != VISIBLE) { mSearchCancelButton.setVisibility(VISIBLE); } // tws-end::by bruce::control mSearchCancelButton status::2014-12-14 } }); mObjectAnimator.setInterpolator(new LinearInterpolator()); mObjectAnimator.setDuration(200); mObjectAnimator.start(); } public void btnDisappearAnimator(){ PropertyValuesHolder pvalphaCancel = PropertyValuesHolder.ofFloat("alpha", 1.0f,0.0f); PropertyValuesHolder pvxCancel = PropertyValuesHolder.ofFloat("translationX", mSearchCancelButton.getX(), mSearchCancelButton.getX()+ mSearchCancelButton.getWidth()); ObjectAnimator mObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(mSearchCancelButton, pvalphaCancel/*, pvxCancel*/); mObjectAnimator.addListener(new AnimatorListener(){ @Override public void onAnimationCancel(Animator arg0) { // TODO Auto-generated method stub } @Override public void onAnimationEnd(Animator arg0) { // TODO Auto-generated method stub // mSearchCancelButton.setVisibility(View.GONE); // tws-start::by bruce::control mSearchCancelButton status::2014-12-14 if(mSearchCancelButton.getVisibility() == VISIBLE) { mSearchCancelButton.setVisibility(GONE); } stretchanimation.startAnimation(currentView, 2); // tws-end::by bruce::control mSearchCancelButton status::2014-12-14 } @Override public void onAnimationRepeat(Animator arg0) { // TODO Auto-generated method stub } @Override public void onAnimationStart(Animator arg0) { // TODO Auto-generated method stub } }); mObjectAnimator.setInterpolator(new LinearInterpolator()); mObjectAnimator.setDuration(100); mObjectAnimator.start(); } @Override protected void onConfigurationChanged(Configuration newConfig) { // TODO Auto-generated method stub super.onConfigurationChanged(newConfig); } }