1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.car.ui.provider; 17 18 import android.car.app.menu.CarMenuCallbacks; 19 import android.car.app.menu.RootMenu; 20 import android.car.app.menu.SearchBoxEditListener; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.graphics.Bitmap; 24 import android.graphics.PorterDuff; 25 import android.graphics.PorterDuffColorFilter; 26 import android.graphics.drawable.BitmapDrawable; 27 import android.os.Bundle; 28 import android.support.car.input.CarRestrictedEditText; 29 import android.support.car.ui.DrawerArrowDrawable; 30 import android.support.car.ui.PagedListView; 31 import android.support.v7.widget.CardView; 32 import android.text.Editable; 33 import android.text.TextWatcher; 34 import android.util.Log; 35 import android.view.Gravity; 36 import android.view.KeyEvent; 37 import android.widget.EditText; 38 import android.widget.FrameLayout; 39 import android.widget.ImageView; 40 import android.widget.LinearLayout; 41 import android.widget.TextView; 42 import android.view.View; 43 import android.view.LayoutInflater; 44 45 import android.support.car.ui.R; 46 47 public class CarUiEntry extends android.car.app.menu.CarUiEntry { 48 private static final String TAG = "Embedded_CarUiEntry"; 49 50 // These values and setSearchBoxMode exist rather than separate methods to make sure exactly the 51 // same set of things get set for each mode, just to different values. 52 /** The search box is not visible. */ 53 private static final int SEARCH_BOX_MODE_NONE = 0; 54 /** The small search box is shown in the header beneath the microphone button. */ 55 private static final int SEARCH_BOX_MODE_SMALL = 1; 56 /** The whole header between the menu button and the microphone button is taken up by the 57 * search box. */ 58 private static final int SEARCH_BOX_MODE_LARGE = 2; 59 60 private View mContentView; 61 private ImageView mMenuButton; 62 private TextView mTitleView; 63 private CardView mTruncatedListCardView; 64 private CarDrawerLayout mDrawerLayout; 65 private DrawerController mDrawerController; 66 private PagedListView mListView; 67 private DrawerArrowDrawable mDrawerArrowDrawable; 68 private CarRestrictedEditText mCarRestrictedEditText; 69 private SearchBoxClickListener mSearchBoxClickListener; 70 71 private View mSearchBox; 72 private View mSearchBoxContents; 73 private View mSearchBoxSearchLogoContainer; 74 private ImageView mSearchBoxSearchLogo; 75 private ImageView mSearchBoxSuperSearchLogo; 76 private FrameLayout mSearchBoxEndView; 77 private View mTitleContainer; 78 private SearchBoxEditListener mSearchBoxEditListener; 79 80 public interface SearchBoxClickListener { 81 /** 82 * The user clicked the search box while it was in small mode. 83 */ onClick()84 void onClick(); 85 } 86 CarUiEntry(Context providerContext, Context appContext)87 public CarUiEntry(Context providerContext, Context appContext) { 88 super(providerContext, appContext); 89 } 90 91 @Override getContentView()92 public View getContentView() { 93 LayoutInflater inflater = LayoutInflater.from(mUiLibContext); 94 mContentView = inflater.inflate(R.layout.car_activity, null); 95 mDrawerLayout = (CarDrawerLayout) mContentView.findViewById(R.id.drawer_container); 96 adjustDrawer(); 97 mMenuButton = (ImageView) mContentView.findViewById(R.id.car_drawer_button); 98 mTitleView = (TextView) mContentView.findViewById(R.id.car_drawer_title); 99 mTruncatedListCardView = (CardView) mContentView.findViewById(R.id.truncated_list_card); 100 mDrawerArrowDrawable = new DrawerArrowDrawable(mUiLibContext); 101 restoreMenuDrawable(); 102 mListView = (PagedListView) mContentView.findViewById(R.id.list_view); 103 mListView.setOnScrollBarListener(mOnScrollBarListener); 104 mMenuButton.setOnClickListener(mMenuListener); 105 mDrawerController = new DrawerController(this, mMenuButton, 106 mDrawerLayout, mListView, mTruncatedListCardView); 107 mTitleContainer = mContentView.findViewById(R.id.car_drawer_title_container); 108 109 mSearchBoxEndView = (FrameLayout) mContentView.findViewById(R.id.car_search_box_end_view); 110 mSearchBox = mContentView.findViewById(R.id.car_search_box); 111 mSearchBoxContents = mContentView.findViewById(R.id.car_search_box_contents); 112 mSearchBoxSearchLogoContainer = mContentView.findViewById( 113 R.id.car_search_box_search_logo_container); 114 mSearchBoxSearchLogoContainer.setOnClickListener(new View.OnClickListener() { 115 @Override 116 public void onClick(View view) { 117 if (mSearchBoxClickListener != null) { 118 mSearchBoxClickListener.onClick(); 119 } 120 } 121 }); 122 mSearchBoxSearchLogo = (ImageView) mContentView.findViewById( 123 R.id.car_search_box_search_logo); 124 mSearchBoxSearchLogo.setImageDrawable(mUiLibContext.getResources() 125 .getDrawable(R.drawable.ic_google)); 126 mSearchBoxSuperSearchLogo = (ImageView) mContentView.findViewById( 127 R.id.car_search_box_super_logo); 128 mSearchBoxSuperSearchLogo.setImageDrawable(mUiLibContext.getResources() 129 .getDrawable(R.drawable.ic_googleg)); 130 131 mCarRestrictedEditText = (CarRestrictedEditText) mContentView.findViewById( 132 R.id.car_search_box_edit_text); 133 mCarRestrictedEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { 134 @Override 135 public boolean onEditorAction(TextView view, int actionId, KeyEvent event) { 136 if (mSearchBoxEditListener != null) { 137 mSearchBoxEditListener.onSearch(mCarRestrictedEditText.getText().toString()); 138 } 139 return false; 140 } 141 }); 142 mCarRestrictedEditText.addTextChangedListener(new TextWatcher() { 143 @Override 144 public void beforeTextChanged(CharSequence text, int start, int count, int after) { 145 } 146 147 @Override 148 public void onTextChanged(CharSequence text, int start, int before, int count) { 149 } 150 151 @Override 152 public void afterTextChanged(Editable text) { 153 if (mSearchBoxEditListener != null) { 154 mSearchBoxEditListener.onEdit(text.toString()); 155 } 156 } 157 }); 158 setSearchBoxMode(SEARCH_BOX_MODE_NONE); 159 return mContentView; 160 } 161 162 private final View.OnClickListener mMenuListener = new View.OnClickListener() { 163 @Override 164 public void onClick(View v) { 165 CarUiEntry.this.mDrawerController.openDrawer(); 166 } 167 }; 168 169 @Override setCarMenuCallbacks(CarMenuCallbacks callbacks)170 public void setCarMenuCallbacks(CarMenuCallbacks callbacks){ 171 RootMenu rootMenu = callbacks.getRootMenu(null); 172 if (rootMenu != null) { 173 mDrawerController.setRootAndCallbacks( 174 rootMenu.getId(), callbacks); 175 mDrawerController.setDrawerEnabled(true); 176 } else { 177 hideMenuButton(); 178 } 179 } 180 181 @Override getFragmentContainerId()182 public int getFragmentContainerId() { 183 return R.id.container; 184 } 185 186 @Override setBackground(Bitmap bitmap)187 public void setBackground(Bitmap bitmap) { 188 BitmapDrawable bd = new BitmapDrawable(mUiLibContext.getResources(), bitmap); 189 ImageView bg = (ImageView) mContentView.findViewById(R.id.background); 190 bg.setBackground(bd); 191 } 192 193 @Override hideMenuButton()194 public void hideMenuButton() { 195 mMenuButton.setVisibility(View.GONE); 196 } 197 198 @Override restoreMenuDrawable()199 public void restoreMenuDrawable() { 200 mMenuButton.setImageDrawable(mDrawerArrowDrawable); 201 } 202 setMenuButtonBitmap(Bitmap bitmap)203 public void setMenuButtonBitmap(Bitmap bitmap) { 204 mMenuButton.setImageDrawable(new BitmapDrawable(mUiLibContext.getResources(), bitmap)); 205 } 206 207 @Override setScrimColor(int color)208 public void setScrimColor(int color) { 209 mDrawerLayout.setScrimColor(color); 210 } 211 212 @Override setTitle(CharSequence title)213 public void setTitle(CharSequence title) { 214 mDrawerController.setTitle(title); 215 } 216 217 @Override closeDrawer()218 public void closeDrawer() { 219 mDrawerController.closeDrawer(); 220 } 221 222 @Override openDrawer()223 public void openDrawer() { 224 mDrawerController.openDrawer(); 225 } 226 227 @Override showMenu(String id, String title)228 public void showMenu(String id, String title) { 229 mDrawerController.showMenu(id, title); 230 } 231 232 233 @Override setMenuButtonColor(int color)234 public void setMenuButtonColor(int color) { 235 setViewColor(mMenuButton, color); 236 setViewColor(mTitleView, color); 237 } 238 239 @Override showTitle()240 public void showTitle() { 241 mTitleView.setVisibility(View.VISIBLE); 242 } 243 244 @Override hideTitle()245 public void hideTitle() { 246 mTitleView.setVisibility(View.GONE); 247 } 248 249 @Override setLightMode()250 public void setLightMode() { 251 mDrawerController.setLightMode(); 252 } 253 254 @Override setDarkMode()255 public void setDarkMode() { 256 mDrawerController.setDarkMode(); 257 } 258 259 @Override setAutoLightDarkMode()260 public void setAutoLightDarkMode() { 261 mDrawerController.setAutoLightDarkMode(); 262 } 263 264 @Override showToast(String msg, long duration)265 public void showToast(String msg, long duration) { 266 // TODO: add toast support 267 } 268 269 @Override getSearchBoxText()270 public CharSequence getSearchBoxText() { 271 return mCarRestrictedEditText.getText(); 272 } 273 274 @Override startInput(String hint, View.OnClickListener searchBoxClickListener)275 public EditText startInput(String hint, 276 View.OnClickListener searchBoxClickListener) { 277 mSearchBoxClickListener = wrapSearchBoxClickListener(searchBoxClickListener); 278 setSearchBoxModeLarge(hint); 279 return mCarRestrictedEditText; 280 } 281 282 283 @Override onRestoreInstanceState(Bundle savedInstanceState)284 public void onRestoreInstanceState(Bundle savedInstanceState) { 285 if (mDrawerController != null) { 286 mDrawerController.restoreState(savedInstanceState); 287 } 288 } 289 290 @Override onSaveInstanceState(Bundle outState)291 public void onSaveInstanceState(Bundle outState) { 292 if (mDrawerController != null) { 293 mDrawerController.saveState(outState); 294 } 295 } 296 297 @Override onStart()298 public void onStart() { 299 300 } 301 302 @Override onResume()303 public void onResume() { 304 305 } 306 307 @Override onPause()308 public void onPause() { 309 310 } 311 312 @Override onStop()313 public void onStop() { 314 315 } 316 317 /** 318 * Sets the colors of all the parts of the search box (regardless of whether it is currently 319 * showing). 320 */ 321 @Override setSearchBoxColors(int backgroundColor, int searchLogoColor, int textColor, int hintTextColor)322 public void setSearchBoxColors(int backgroundColor, int searchLogoColor, int textColor, 323 int hintTextColor) { 324 // set background color of mSearchBox to get rid of the animation artifact in b/23767062 325 mSearchBox.setBackgroundColor(backgroundColor); 326 mSearchBoxContents.setBackgroundColor(backgroundColor); 327 mSearchBoxSearchLogo.setColorFilter(searchLogoColor, PorterDuff.Mode.SRC_IN); 328 mCarRestrictedEditText.setTextColor(textColor); 329 mCarRestrictedEditText.setHintTextColor(hintTextColor); 330 } 331 332 /** 333 * Sets the view to be displayed at the end of the search box, or null to clear any existing 334 * views. 335 */ 336 @Override setSearchBoxEndView(View endView)337 public void setSearchBoxEndView(View endView) { 338 if (endView == null) { 339 mSearchBoxEndView.removeAllViews(); 340 } else if (mSearchBoxEndView.getChildCount() == 0) { 341 mSearchBoxEndView.addView(endView); 342 } else if (mSearchBoxEndView.getChildAt(0) != endView) { 343 mSearchBoxEndView.removeViewAt(0); 344 mSearchBoxEndView.addView(endView); 345 } 346 } 347 348 @Override showSearchBox(final View.OnClickListener listener)349 public void showSearchBox(final View.OnClickListener listener) { 350 setSearchBoxMode(SEARCH_BOX_MODE_SMALL); 351 mSearchBoxClickListener = wrapSearchBoxClickListener(listener); 352 } 353 354 @Override stopInput()355 public void stopInput() { 356 setSearchBoxMode(SEARCH_BOX_MODE_NONE); 357 } 358 359 @Override setSearchBoxEditListener(SearchBoxEditListener listener)360 public void setSearchBoxEditListener(SearchBoxEditListener listener) { 361 mSearchBoxEditListener = listener; 362 } 363 364 365 /** 366 * Set the progress of the animated {@link DrawerArrowDrawable}. 367 * @param progress 0f displays a menu button 368 * 1f displays a back button 369 * anything in between will be an interpolation of the drawable between 370 * back and menu 371 */ setMenuProgress(float progress)372 public void setMenuProgress(float progress) { 373 mDrawerArrowDrawable.setProgress(progress); 374 } 375 setSearchBoxModeLarge(String hint)376 private void setSearchBoxModeLarge(String hint) { 377 mCarRestrictedEditText.setHint(hint); 378 setSearchBoxMode(SEARCH_BOX_MODE_LARGE); 379 } 380 setTitleText(CharSequence title)381 public void setTitleText(CharSequence title) { 382 mTitleView.setText(title); 383 } 384 385 /** 386 * Sets all the view visibilities and layout params for a search box mode. 387 */ setSearchBoxMode(int searchBoxMode)388 private void setSearchBoxMode(int searchBoxMode) { 389 // Set the visibility and width of the search box, and whether the rest of the header sits 390 // beside or beneath the microphone button. 391 LinearLayout.LayoutParams searchBoxLayoutParams = 392 (LinearLayout.LayoutParams) mSearchBox.getLayoutParams(); 393 if (searchBoxMode == SEARCH_BOX_MODE_LARGE) { 394 int screenWidth = mAppContext.getResources().getDisplayMetrics().widthPixels; 395 int searchBoxMargin = mUiLibContext.getResources() 396 .getDimensionPixelSize(R.dimen.car_drawer_header_menu_button_size); 397 int maxSearchBoxWidth = mUiLibContext.getResources().getDimensionPixelSize( 398 R.dimen.car_card_max_width); 399 int searchBoxMarginStart = 0; 400 int searchBoxMarginEnd = searchBoxMargin; 401 // If the width of search bar is larger than max card width, we adjust margin to fix it. 402 if (screenWidth - searchBoxMargin * 2 > maxSearchBoxWidth) { 403 searchBoxMarginEnd = (screenWidth - maxSearchBoxWidth) / 2; 404 searchBoxMarginStart = searchBoxMarginEnd - searchBoxMargin; 405 } 406 searchBoxLayoutParams.width = 0; 407 searchBoxLayoutParams.weight = 1.0f; 408 searchBoxLayoutParams.setMarginStart(searchBoxMarginStart); 409 searchBoxLayoutParams.setMarginEnd(searchBoxMarginEnd); 410 } else if (searchBoxMode == SEARCH_BOX_MODE_SMALL) { 411 searchBoxLayoutParams.width = mUiLibContext.getResources().getDimensionPixelSize( 412 R.dimen.car_app_layout_search_box_small_width); 413 searchBoxLayoutParams.weight = 0.0f; 414 searchBoxLayoutParams.setMarginStart(mUiLibContext.getResources() 415 .getDimensionPixelOffset(R.dimen.car_app_layout_search_box_small_margin)); 416 searchBoxLayoutParams.setMarginEnd(mUiLibContext.getResources().getDimensionPixelOffset( 417 R.dimen.car_app_layout_search_box_small_margin)); 418 } else { 419 searchBoxLayoutParams.width = mUiLibContext.getResources().getDimensionPixelSize( 420 R.dimen.car_app_layout_search_box_small_width); 421 searchBoxLayoutParams.weight = 0.0f; 422 searchBoxLayoutParams.setMarginStart(mUiLibContext.getResources().getDimensionPixelSize( 423 R.dimen.car_drawer_header_menu_button_size)); 424 searchBoxLayoutParams.setMarginEnd(-searchBoxLayoutParams.width); 425 } 426 mSearchBox.setLayoutParams(searchBoxLayoutParams); 427 428 // Animate the visibility of the contents of the search box - either the Search logo or the 429 // edit text is visible (the super logo also is visible when the edit text is visible). 430 View searchBoxEditTextContainer = (View) mCarRestrictedEditText.getParent(); 431 if (searchBoxMode == SEARCH_BOX_MODE_SMALL) { 432 if (mSearchBoxSearchLogoContainer.getVisibility() != View.VISIBLE) { 433 mSearchBoxSearchLogoContainer.setAlpha(0f); 434 mSearchBoxSearchLogoContainer.setVisibility(View.VISIBLE); 435 } 436 // 300ms delay to stagger the fade in behind the fade out animation. 437 mSearchBoxSearchLogoContainer.animate().alpha(1f).setStartDelay(300); 438 // Animate the container so it includes the super G logo. 439 if (searchBoxEditTextContainer.getVisibility() == View.VISIBLE) { 440 searchBoxEditTextContainer.animate().alpha(0f).setStartDelay(0) 441 .withEndAction(mSetEditTextGoneRunnable); 442 } 443 } else if (searchBoxMode == SEARCH_BOX_MODE_LARGE) { 444 if (searchBoxEditTextContainer.getVisibility() != View.VISIBLE) { 445 searchBoxEditTextContainer.setAlpha(0f); 446 searchBoxEditTextContainer.setVisibility(View.VISIBLE); 447 } 448 searchBoxEditTextContainer.animate().alpha(1f).setStartDelay(300); 449 if (mSearchBoxSearchLogoContainer.getVisibility() == View.VISIBLE) { 450 mSearchBoxSearchLogoContainer.animate().alpha(0f).setStartDelay(0) 451 .withEndAction(mSetSearchBoxLogoGoneRunnable); 452 } 453 } else { 454 searchBoxEditTextContainer.setVisibility(View.GONE); 455 } 456 457 // Set the visibility of the title and status containers. 458 if (searchBoxMode == SEARCH_BOX_MODE_LARGE) { 459 mTitleContainer.setVisibility(View.GONE); 460 } else { 461 mTitleContainer.setVisibility(View.VISIBLE); 462 } 463 } 464 465 466 private final Runnable mSetEditTextGoneRunnable = new Runnable() { 467 @Override 468 public void run() { 469 ((View) mCarRestrictedEditText.getParent()).setVisibility(View.GONE); 470 } 471 }; 472 473 private final Runnable mSetSearchBoxLogoGoneRunnable = new Runnable() { 474 @Override 475 public void run() { 476 mSearchBoxSearchLogoContainer.setVisibility(View.GONE); 477 } 478 }; 479 480 wrapSearchBoxClickListener(final View.OnClickListener listener)481 private SearchBoxClickListener wrapSearchBoxClickListener(final View.OnClickListener listener) { 482 return new SearchBoxClickListener() { 483 @Override 484 public void onClick() { 485 listener.onClick(null); 486 } 487 }; 488 } 489 490 491 private static void setViewColor(View view, int color) { 492 if (view instanceof TextView) { 493 ((TextView) view).setTextColor(color); 494 } else if (view instanceof ImageView) { 495 ImageView imageView = (ImageView) view; 496 PorterDuffColorFilter filter = 497 new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN); 498 imageView.setColorFilter(filter); 499 } else { 500 if (Log.isLoggable(TAG, Log.WARN)) { 501 Log.w(TAG, "Setting color is only supported for TextView and ImageView."); 502 } 503 } 504 } 505 506 private void adjustDrawer() { 507 Resources resources = mUiLibContext.getResources(); 508 float width = resources.getDisplayMetrics().widthPixels; 509 CarDrawerLayout.LayoutParams layoutParams = new CarDrawerLayout.LayoutParams( 510 CarDrawerLayout.LayoutParams.MATCH_PARENT, 511 CarDrawerLayout.LayoutParams.MATCH_PARENT); 512 layoutParams.gravity = Gravity.LEFT; 513 // 1. If the screen width is larger than 800dp, the drawer width is kept as 704dp; 514 // 2. Else the drawer width is adjusted to keep the margin end of drawer as 96dp. 515 if (width > resources.getDimension(R.dimen.car_standard_width)) { 516 layoutParams.setMarginEnd( 517 (int) (width - resources.getDimension(R.dimen.car_drawer_standard_width))); 518 } else { 519 layoutParams.setMarginEnd( 520 (int) resources.getDimension(R.dimen.car_card_margin)); 521 } 522 mContentView.findViewById(R.id.drawer).setLayoutParams(layoutParams); 523 } 524 525 private final PagedListView.OnScrollBarListener mOnScrollBarListener = 526 new PagedListView.OnScrollBarListener() { 527 528 @Override 529 public void onReachBottom() { 530 if (mDrawerController.isTruncatedList()) { 531 mTruncatedListCardView.setVisibility(View.VISIBLE); 532 } 533 } 534 535 @Override 536 public void onLeaveBottom() { 537 mTruncatedListCardView.setVisibility(View.GONE); 538 } 539 }; 540 } 541