1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 package org.mozilla.search.autocomplete; 6 7 import org.mozilla.gecko.R; 8 import org.mozilla.gecko.Telemetry; 9 import org.mozilla.gecko.TelemetryContract; 10 import org.mozilla.gecko.gfx.BitmapUtils; 11 import org.mozilla.gecko.search.SearchEngine; 12 13 import android.content.Context; 14 import android.graphics.Bitmap; 15 import android.graphics.Color; 16 import android.graphics.PorterDuff; 17 import android.graphics.PorterDuffColorFilter; 18 import android.graphics.drawable.BitmapDrawable; 19 import android.graphics.drawable.Drawable; 20 import android.text.Editable; 21 import android.text.TextWatcher; 22 import android.util.AttributeSet; 23 import android.view.KeyEvent; 24 import android.view.LayoutInflater; 25 import android.view.MotionEvent; 26 import android.view.View; 27 import android.view.inputmethod.EditorInfo; 28 import android.view.inputmethod.InputMethodManager; 29 import android.widget.EditText; 30 import android.widget.FrameLayout; 31 import android.widget.ImageButton; 32 import android.widget.ImageView; 33 import android.widget.TextView; 34 35 public class SearchBar extends FrameLayout { 36 37 private final EditText editText; 38 private final ImageButton clearButton; 39 private final ImageView engineIcon; 40 41 private final Drawable focusedBackground; 42 private final Drawable defaultBackground; 43 44 private final InputMethodManager inputMethodManager; 45 46 private TextListener listener; 47 48 private boolean active; 49 50 public interface TextListener { onChange(String text)51 public void onChange(String text); onSubmit(String text)52 public void onSubmit(String text); onFocusChange(boolean hasFocus)53 public void onFocusChange(boolean hasFocus); 54 } 55 56 // Deprecation warnings suppressed to allow building with API level 22 57 @SuppressWarnings("deprecation") SearchBar(Context context, AttributeSet attrs)58 public SearchBar(Context context, AttributeSet attrs) { 59 super(context, attrs); 60 61 LayoutInflater.from(context).inflate(R.layout.search_bar, this); 62 63 editText = (EditText) findViewById(R.id.edit_text); 64 editText.addTextChangedListener(new TextWatcher() { 65 @Override 66 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 67 } 68 69 @Override 70 public void onTextChanged(CharSequence s, int start, int before, int count) { 71 } 72 73 @Override 74 public void afterTextChanged(Editable s) { 75 if (listener != null) { 76 listener.onChange(s.toString()); 77 } 78 79 updateClearButtonVisibility(); 80 } 81 }); 82 83 // Attach a listener for the "search" key on the keyboard. 84 editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { 85 @Override 86 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 87 if (listener != null && 88 (actionId == EditorInfo.IME_ACTION_UNSPECIFIED || actionId == EditorInfo.IME_ACTION_SEARCH)) { 89 // The user searched without using search engine suggestions. 90 Telemetry.sendUIEvent(TelemetryContract.Event.SEARCH, TelemetryContract.Method.ACTIONBAR, "text"); 91 listener.onSubmit(v.getText().toString()); 92 return true; 93 } 94 return false; 95 } 96 }); 97 98 editText.setOnFocusChangeListener(new View.OnFocusChangeListener() { 99 @Override 100 public void onFocusChange(View v, boolean hasFocus) { 101 if (listener != null) { 102 listener.onFocusChange(hasFocus); 103 } 104 } 105 }); 106 107 clearButton = (ImageButton) findViewById(R.id.clear_button); 108 clearButton.setOnClickListener(new View.OnClickListener() { 109 @Override 110 public void onClick(View v) { 111 editText.setText(""); 112 } 113 }); 114 engineIcon = (ImageView) findViewById(R.id.engine_icon); 115 116 focusedBackground = getResources().getDrawable(R.drawable.edit_text_focused); 117 defaultBackground = getResources().getDrawable(R.drawable.edit_text_default); 118 119 inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 120 } 121 setText(String text)122 public void setText(String text) { 123 editText.setText(text); 124 125 // Move cursor to end of search input. 126 editText.setSelection(text.length()); 127 } 128 getText()129 public String getText() { 130 return editText.getText().toString(); 131 } 132 setEngine(SearchEngine engine)133 public void setEngine(SearchEngine engine) { 134 final String iconURL = engine.getIconURL(); 135 final Bitmap bitmap = BitmapUtils.getBitmapFromDataURI(iconURL); 136 final BitmapDrawable d = new BitmapDrawable(getResources(), bitmap); 137 engineIcon.setImageDrawable(d); 138 engineIcon.setContentDescription(engine.getName()); 139 140 // Update the focused background color. 141 int color = BitmapUtils.getDominantColor(bitmap); 142 143 // BitmapUtils#getDominantColor ignores black and white pixels, but it will 144 // return white if no dominant color was found. We don't want to create a 145 // white underline for the search bar, so we default to black instead. 146 if (color == Color.WHITE) { 147 color = Color.BLACK; 148 } 149 focusedBackground.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); 150 151 editText.setHint(getResources().getString(R.string.search_bar_hint, engine.getName())); 152 } 153 154 @SuppressWarnings("deprecation") setActive(boolean active)155 public void setActive(boolean active) { 156 if (this.active == active) { 157 return; 158 } 159 this.active = active; 160 161 updateClearButtonVisibility(); 162 163 editText.setFocusable(active); 164 editText.setFocusableInTouchMode(active); 165 166 final int leftDrawable = active ? R.drawable.search_icon_active : R.drawable.search_icon_inactive; 167 editText.setCompoundDrawablesWithIntrinsicBounds(leftDrawable, 0, 0, 0); 168 169 // We can't use a selector drawable because we apply a color filter to the focused 170 // background at run time. 171 // TODO: setBackgroundDrawable is deprecated in API level 16 172 editText.setBackgroundDrawable(active ? focusedBackground : defaultBackground); 173 174 if (active) { 175 editText.requestFocus(); 176 inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT); 177 } else { 178 editText.clearFocus(); 179 inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), 0); 180 } 181 } 182 updateClearButtonVisibility()183 private void updateClearButtonVisibility() { 184 // Only show the clear button when there is text in the input. 185 final boolean visible = active && (editText.getText().length() > 0); 186 clearButton.setVisibility(visible ? View.VISIBLE : View.GONE); 187 engineIcon.setVisibility(visible ? View.GONE : View.VISIBLE); 188 } 189 setTextListener(TextListener listener)190 public void setTextListener(TextListener listener) { 191 this.listener = listener; 192 } 193 194 @Override onInterceptTouchEvent(MotionEvent e)195 public boolean onInterceptTouchEvent(MotionEvent e) { 196 // When the view is active, pass touch events to child views. 197 // Otherwise, intercept touch events to allow click listeners on the view to 198 // fire no matter where the user clicks. 199 return !active; 200 } 201 } 202