1 /* 2 * Copyright (C) 2023 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 com.google.android.car.kitchensink; 17 18 import static com.google.android.car.kitchensink.KitchenSinkActivity.MENU_ENTRIES; 19 20 import android.annotation.Nullable; 21 import android.car.Car; 22 import android.car.CarOccupantZoneManager; 23 import android.car.CarProjectionManager; 24 import android.car.drivingstate.CarUxRestrictions; 25 import android.car.hardware.CarSensorManager; 26 import android.car.hardware.hvac.CarHvacManager; 27 import android.car.hardware.power.CarPowerManager; 28 import android.car.hardware.property.CarPropertyManager; 29 import android.car.os.CarPerformanceManager; 30 import android.car.telemetry.CarTelemetryManager; 31 import android.car.watchdog.CarWatchdogManager; 32 import android.content.Intent; 33 import android.content.SharedPreferences; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.util.Log; 37 import android.util.Pair; 38 import android.view.View; 39 import android.widget.EditText; 40 import android.widget.Toast; 41 42 import androidx.annotation.NonNull; 43 import androidx.fragment.app.Fragment; 44 import androidx.fragment.app.FragmentActivity; 45 46 import com.android.car.ui.core.CarUi; 47 import com.android.car.ui.recyclerview.CarUiContentListItem; 48 import com.android.car.ui.recyclerview.CarUiRecyclerView; 49 import com.android.car.ui.toolbar.MenuItem; 50 import com.android.car.ui.toolbar.NavButtonMode; 51 import com.android.car.ui.toolbar.SearchMode; 52 import com.android.car.ui.toolbar.ToolbarController; 53 54 import java.io.FileDescriptor; 55 import java.io.PrintWriter; 56 import java.util.ArrayList; 57 import java.util.Arrays; 58 import java.util.List; 59 import java.util.Locale; 60 import java.util.Optional; 61 62 public class KitchenSink2Activity extends FragmentActivity implements KitchenSinkHelper { 63 static final String TAG = KitchenSink2Activity.class.getName(); 64 private static final String LAST_FRAGMENT_TAG = "lastFragmentTag"; 65 private static final String PREFERENCES_NAME = "fragment_item_prefs"; 66 private static final String KEY_PINNED_ITEMS_LIST = "key_pinned_items_list"; 67 private static final String KEY_COLLAPSE_STATE = "key_collapse_state"; 68 private static final String DELIMITER = "::"; // A unique delimiter 69 @Nullable 70 private Fragment mLastFragment; 71 private int mNotificationId = 1000; 72 private static final int NO_INDEX = -1; 73 private static final String EMPTY_STRING = ""; 74 private HighlightableAdapter mAdapter; 75 private final FragmentItemClickHandler mItemClickHandler = new FragmentItemClickHandler(); 76 77 /** 78 * List of all the original menu items. 79 */ 80 private List<FragmentListItem> mData; 81 82 /** 83 * Dynamic list of menu items that needs to be given to the adapter. Useful while searching. 84 */ 85 private List<FragmentListItem> mFilteredData; 86 private boolean mIsSinglePane = false; 87 private ToolbarController mGlobalToolbar, mMiniToolbar; 88 private View mWrapper; 89 private CarUiRecyclerView mRV; 90 private CharSequence mLastFragmentTitle; 91 private MenuItem mFavButton; 92 private MenuItem mCollapsibleButton; 93 private boolean mIsSearching; 94 private int mPinnedItemsCount; 95 private SharedPreferences mSharedPreferences; 96 public static final String DUMP_ARG_CMD = "cmd"; 97 public static final String DUMP_ARG_FRAGMENT = "fragment"; 98 public static final String DUMP_ARG_QUIET = "quiet"; 99 100 private final KitchenSinkHelperImpl mKsHelperImpl = new KitchenSinkHelperImpl(); 101 102 @Override getCar()103 public Car getCar() { 104 return mKsHelperImpl.getCar(); 105 } 106 107 @Override requestRefreshManager(Runnable r, Handler h)108 public void requestRefreshManager(Runnable r, Handler h) { 109 mKsHelperImpl.requestRefreshManager(r, h); 110 } 111 112 @Override getPropertyManager()113 public CarPropertyManager getPropertyManager() { 114 return mKsHelperImpl.getPropertyManager(); 115 } 116 117 @Override getHvacManager()118 public CarHvacManager getHvacManager() { 119 return mKsHelperImpl.getHvacManager(); 120 } 121 122 @Override getOccupantZoneManager()123 public CarOccupantZoneManager getOccupantZoneManager() { 124 return mKsHelperImpl.getOccupantZoneManager(); 125 } 126 127 @Override getPowerManager()128 public CarPowerManager getPowerManager() { 129 return mKsHelperImpl.getPowerManager(); 130 } 131 132 @Override getSensorManager()133 public CarSensorManager getSensorManager() { 134 return mKsHelperImpl.getSensorManager(); 135 } 136 137 @Override getProjectionManager()138 public CarProjectionManager getProjectionManager() { 139 return mKsHelperImpl.getProjectionManager(); 140 } 141 142 @Override getCarTelemetryManager()143 public CarTelemetryManager getCarTelemetryManager() { 144 return mKsHelperImpl.getCarTelemetryManager(); 145 } 146 147 @Override getCarWatchdogManager()148 public CarWatchdogManager getCarWatchdogManager() { 149 return mKsHelperImpl.getCarWatchdogManager(); 150 } 151 152 @Override getPerformanceManager()153 public CarPerformanceManager getPerformanceManager() { 154 return mKsHelperImpl.getPerformanceManager(); 155 } 156 157 @Override onPointerCaptureChanged(boolean hasCapture)158 public void onPointerCaptureChanged(boolean hasCapture) { 159 super.onPointerCaptureChanged(hasCapture); 160 } 161 showDefaultFragment()162 private void showDefaultFragment() { 163 onFragmentItemClick(0); 164 } 165 166 /** 167 * Searches in dynamic list (Not the whole list) 168 * 169 * @param title - Searches the list whose fragment title matches 170 * @return index of the item 171 */ getFragmentIndexFromTitle(String title)172 private int getFragmentIndexFromTitle(String title) { 173 for (int i = 0; i < mFilteredData.size(); i++) { 174 String targetText = mFilteredData.get(i).getTitle().getPreferredText().toString(); 175 if (targetText.equalsIgnoreCase(title)) { 176 return i; 177 } 178 } 179 return NO_INDEX; 180 } 181 onFragmentItemClick(int fragIndex)182 public void onFragmentItemClick(int fragIndex) { 183 if (fragIndex < 0 || fragIndex >= mFilteredData.size()) return; 184 FragmentListItem fragmentListItem = mFilteredData.get(fragIndex); 185 Fragment fragment = fragmentListItem.getFragment(); 186 if (mLastFragment != fragment) { 187 Log.v(TAG, "onFragmentItemClick(): from " + mLastFragment + " to " + fragment); 188 } else { 189 Log.v(TAG, "onFragmentItemClick(): showing " + fragment + " again"); 190 } 191 getSupportFragmentManager() 192 .beginTransaction() 193 .replace(R.id.fragment_container, fragment) 194 .commit(); 195 mLastFragment = fragment; 196 mLastFragmentTitle = fragmentListItem.getTitle().getPreferredText(); 197 mMiniToolbar.setTitle(mLastFragmentTitle); 198 mAdapter.requestHighlight(mLastFragmentTitle.toString(), fragIndex); 199 mFavButton.setIcon( 200 fragmentListItem.isFavourite() 201 ? getDrawable(R.drawable.ic_item_unpin) 202 : getDrawable(R.drawable.ic_item_pin)); 203 } 204 205 @Override onCreate(Bundle savedInstanceState)206 public void onCreate(Bundle savedInstanceState) { 207 super.onCreate(savedInstanceState); 208 mSharedPreferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE); 209 210 setContentView(R.layout.activity_2pane); 211 212 mKsHelperImpl.initCarApiIfAutomotive(this); 213 214 mRV = findViewById(R.id.list_pane); 215 mWrapper = findViewById(R.id.wrapper); 216 mWrapper.setVisibility(mSharedPreferences.getInt(KEY_COLLAPSE_STATE, View.VISIBLE)); 217 218 setUpToolbars(); 219 220 mData = getProcessedData(); 221 mFilteredData = new ArrayList<>(mData); 222 mAdapter = new HighlightableAdapter(this, mFilteredData, mRV); 223 mRV.setAdapter(mAdapter); 224 225 // showing intent or default 226 showDefaultFragment(); 227 onNewIntent(getIntent()); 228 } 229 setUpToolbars()230 private void setUpToolbars() { 231 View toolBarView = requireViewById(R.id.top_level_menu_container); 232 233 mGlobalToolbar = CarUi.installBaseLayoutAround( 234 toolBarView, 235 insets -> findViewById(R.id.top_level_menu_container).setPadding( 236 insets.getLeft(), insets.getTop(), insets.getRight(), 237 insets.getBottom()), /* hasToolbar= */ true); 238 239 MenuItem searchButton = new MenuItem.Builder(this) 240 .setToSearch() 241 .setOnClickListener(menuItem -> onSearchButtonClicked()) 242 .setUxRestrictions(CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD) 243 .setId(R.id.toolbar_menu_item_search) 244 .build(); 245 246 mGlobalToolbar.setMenuItems(List.of(searchButton)); 247 mGlobalToolbar.registerBackListener(() -> { 248 mIsSearching = false; 249 mGlobalToolbar.setSearchMode(SearchMode.DISABLED); 250 mGlobalToolbar.setNavButtonMode(NavButtonMode.DISABLED); 251 mGlobalToolbar.unregisterOnSearchListener(this::onQueryChanged); 252 EditText mSearchText = findViewById(R.id.car_ui_toolbar_search_bar); 253 mSearchText.getText().clear(); 254 onQueryChanged(EMPTY_STRING); 255 mAdapter.onSearchEnded(); 256 return true; 257 }); 258 259 mGlobalToolbar.setTitle(getString(R.string.app_title)); 260 mGlobalToolbar.setNavButtonMode(NavButtonMode.DISABLED); 261 mGlobalToolbar.setLogo(R.drawable.ic_launcher); 262 // if (mIsSinglePane) { 263 // mGlobalToolbar.setNavButtonMode(NavButtonMode.BACK); 264 // findViewById(R.id.top_level_menu_container).setVisibility(View.GONE); 265 // findViewById(R.id.top_level_divider).setVisibility(View.GONE); 266 // return; 267 // } 268 mMiniToolbar = CarUi.installBaseLayoutAround( 269 requireViewById(R.id.fragment_container_wrapper), 270 insets -> findViewById(R.id.fragment_container_wrapper).setPadding( 271 insets.getLeft(), insets.getTop(), insets.getRight(), 272 insets.getBottom()), /* hasToolbar= */ true); 273 274 mFavButton = new MenuItem.Builder(this) 275 .setOnClickListener(i -> onFavClicked()) 276 .setUxRestrictions(CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD) 277 .build(); 278 279 mCollapsibleButton = new MenuItem.Builder(this) 280 .setOnClickListener(i -> toggleMenuWrapper()) 281 .setUxRestrictions(CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD) 282 .setIcon(mWrapper.getVisibility() == View.GONE 283 ? R.drawable.ic_left_panel_open 284 : R.drawable.ic_left_panel_close) 285 .build(); 286 287 mMiniToolbar.setMenuItems(List.of(mFavButton, mCollapsibleButton)); 288 mMiniToolbar.setNavButtonMode(NavButtonMode.BACK); 289 } 290 onSearchButtonClicked()291 private void onSearchButtonClicked() { 292 mIsSearching = true; 293 mGlobalToolbar.setSearchMode(SearchMode.SEARCH); 294 mGlobalToolbar.setNavButtonMode(NavButtonMode.BACK); 295 mGlobalToolbar.registerOnSearchListener(this::onQueryChanged); 296 } 297 onQueryChanged(String query)298 private void onQueryChanged(String query) { 299 mFilteredData.clear(); 300 if (query.isEmpty()) { 301 mFilteredData.addAll(mData); 302 } else { 303 for (FragmentListItem item : mData) { 304 if (item.getTitle().getPreferredText().toString().toLowerCase().contains( 305 query.toLowerCase())) { 306 mFilteredData.add(item); 307 } 308 } 309 } 310 mAdapter.afterTextChanged(); 311 } 312 onFavClicked()313 private void onFavClicked() { 314 int fromIndex = getOriginalIndexFromTitle(mLastFragmentTitle, 0, false); 315 int toIndex; 316 String text; 317 318 FragmentListItem fragmentListItem = mData.get(fromIndex); 319 if (fragmentListItem.isFavourite()) { 320 // Un-pinning: Moving the item to its lexicographic position 321 toIndex = getOriginalIndexFromTitle( 322 fragmentListItem.getTitle().getPreferredText(), mPinnedItemsCount, 323 true); 324 text = getString(R.string.toast_item_unpinned_message, mLastFragmentTitle); 325 mPinnedItemsCount--; 326 } else { 327 // Pinning: Moving the item to the top most position. 328 toIndex = 0; 329 text = getString(R.string.toast_item_pinned_message, mLastFragmentTitle); 330 mPinnedItemsCount++; 331 } 332 333 moveFragmentItem(fromIndex, toIndex); 334 fragmentListItem.toggleFavourite(); 335 336 Toast.makeText(this, text, Toast.LENGTH_SHORT).show(); 337 mFavButton.setIcon( 338 fragmentListItem.isFavourite() 339 ? getDrawable(R.drawable.ic_item_unpin) 340 : getDrawable(R.drawable.ic_item_pin)); 341 342 } 343 344 /** 345 * Finds index from the original data (Not from the dynamic list) 346 * 347 * @param fragmentTitle - Finds by comparing the title 348 * @param startFrom - the starting index it should search from 349 * @param isLexicographic - If set to true, finds lexicographical position by comparing 350 * strings. If false, finds the 351 * index with exact string match or -1. 352 */ getOriginalIndexFromTitle(CharSequence fragmentTitle, int startFrom, boolean isLexicographic)353 private int getOriginalIndexFromTitle(CharSequence fragmentTitle, int startFrom, 354 boolean isLexicographic) { 355 if (fragmentTitle.toString().equalsIgnoreCase(EMPTY_STRING)) return NO_INDEX; 356 for (int i = startFrom; i < mData.size(); i++) { 357 String targetText = mData.get(i).getTitle().getPreferredText().toString(); 358 if (isLexicographic && targetText.compareToIgnoreCase(fragmentTitle.toString()) > 0) { 359 return i - 1; 360 } 361 if (!isLexicographic && targetText.equalsIgnoreCase(fragmentTitle.toString())) { 362 return i; 363 } 364 } 365 return isLexicographic ? mData.size() - 1 : NO_INDEX; 366 } 367 368 /** 369 * Moves the fragmentItem from @param "from" to @param "to" 370 * Used for both pinning and unpinning an item. 371 * 372 * @param from - the current index of the item 373 * @param to - the target index to move the item 374 */ moveFragmentItem(int from, int to)375 private void moveFragmentItem(int from, int to) { 376 if (from < 0 || from >= mData.size() || to < 0 || to >= mData.size()) return; 377 mData.add(to, mData.remove(from)); 378 if (!mIsSearching) { 379 mFilteredData.add(to, mFilteredData.remove(from)); 380 mAdapter.afterFavClicked(from, to); 381 } 382 } 383 toggleMenuWrapper()384 private void toggleMenuWrapper() { 385 if (mWrapper.getVisibility() == View.VISIBLE) { 386 mWrapper.setVisibility(View.GONE); 387 mCollapsibleButton.setIcon(R.drawable.ic_left_panel_open); 388 } else { 389 mWrapper.setVisibility(View.VISIBLE); 390 mCollapsibleButton.setIcon(R.drawable.ic_left_panel_close); 391 } 392 } 393 saveVisibilityState()394 private void saveVisibilityState() { 395 if (mSharedPreferences == null) { 396 mSharedPreferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE); 397 } 398 mSharedPreferences.edit() 399 .putInt(KEY_COLLAPSE_STATE, mWrapper.getVisibility()) 400 .apply(); 401 } 402 getProcessedData()403 List<FragmentListItem> getProcessedData() { 404 405 List<String> pinnedTitles = getPinnedTitlesFromPrefs(); 406 407 List<FragmentListItem> allItems = new ArrayList<>(); 408 ArrayList<FragmentListItem> pinnedItems = new ArrayList<>(); 409 mPinnedItemsCount = pinnedTitles.size(); 410 for (int i = 0; i < mPinnedItemsCount; i++) { 411 pinnedItems.add(null); 412 } 413 414 for (Pair<String, Class> entry : MENU_ENTRIES) { 415 // Retrieves the pinned position and preserves it in the same order 416 int pinnedPosition = pinnedTitles.indexOf(entry.first.toLowerCase()); 417 if (pinnedPosition >= 0) { 418 pinnedItems.set(pinnedPosition, 419 new FragmentListItem(entry.first, true, entry.second, mItemClickHandler)); 420 } else { 421 allItems.add( 422 new FragmentListItem(entry.first, false, entry.second, mItemClickHandler)); 423 } 424 } 425 426 allItems.sort((o1, o2) -> { 427 String s1 = o1.getTitle().getPreferredText().toString(); 428 String s2 = o2.getTitle().getPreferredText().toString(); 429 return s1.compareToIgnoreCase(s2); 430 }); 431 432 allItems.addAll(0, pinnedItems); 433 return allItems; 434 } 435 436 @NonNull getPinnedTitlesFromPrefs()437 List<String> getPinnedTitlesFromPrefs() { 438 if (mSharedPreferences == null) return new ArrayList<>(); 439 440 String pinnedTitles = mSharedPreferences.getString(KEY_PINNED_ITEMS_LIST, ""); 441 if (pinnedTitles.isEmpty()) { 442 return new ArrayList<>(); 443 } else { 444 return Arrays.asList(pinnedTitles.split(DELIMITER)); 445 } 446 } 447 448 @Override onPause()449 protected void onPause() { 450 super.onPause(); 451 savePinnedItemsToPreferences(); 452 saveVisibilityState(); 453 } 454 455 @Override onSaveInstanceState(Bundle outState)456 protected void onSaveInstanceState(Bundle outState) { 457 outState.putString(LAST_FRAGMENT_TAG, mLastFragmentTitle.toString()); 458 super.onSaveInstanceState(outState); 459 } 460 461 @Override onRestoreInstanceState(Bundle savedInstanceState)462 protected void onRestoreInstanceState(Bundle savedInstanceState) { 463 super.onRestoreInstanceState(savedInstanceState); 464 // The app is being started for the first time. 465 if (savedInstanceState == null) { 466 return; 467 } 468 469 // The app is being reloaded, restores the last fragment UI. 470 mLastFragmentTitle = savedInstanceState.getString(LAST_FRAGMENT_TAG, ""); 471 if (mLastFragmentTitle.isEmpty()) { 472 showDefaultFragment(); 473 } else { 474 onFragmentItemClick(getFragmentIndexFromTitle(mLastFragmentTitle.toString())); 475 } 476 } 477 savePinnedItemsToPreferences()478 private void savePinnedItemsToPreferences() { 479 if (mSharedPreferences == null) { 480 mSharedPreferences = getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE); 481 } 482 SharedPreferences.Editor editor = mSharedPreferences.edit(); 483 484 StringBuilder sb = new StringBuilder(); 485 for (int i = 0; i < mPinnedItemsCount; i++) { 486 sb.append(mData.get(i).getTitle().getPreferredText()).append(DELIMITER); 487 } 488 489 // Remove the last delimiter 490 if (sb.length() > 0) { 491 sb.setLength(sb.length() - DELIMITER.length()); 492 } 493 494 editor.putString(KEY_PINNED_ITEMS_LIST, sb.toString().toLowerCase(Locale.US)); 495 editor.apply(); 496 } 497 498 /* Open any tab directly: 499 * adb shell am force-stop com.google.android.car.kitchensink 500 * adb shell am 'start -n com.google.android.car.kitchensink/.KitchenSink2Activity --es select 501 * "connectivity"' 502 *-ee 503 * Test car watchdog: 504 * adb shell am force-stop com.google.android.car.kitchensink 505 * adb shell am start -n com.google.android.car.kitchensink/.KitchenSink2Activity \ 506 * --es "watchdog" "[timeout] [not_respond_after] [inactive_main_after] [verbose]" 507 * - timeout: critical | moderate | normal 508 * - not_respond_after: after the given seconds, the client will not respond to car watchdog 509 * (-1 for making the client respond always) 510 * - inactive_main_after: after the given seconds, the main thread will not be responsive 511 * (-1 for making the main thread responsive always) 512 * - verbose: whether to output verbose logs (default: false) 513 */ 514 @Override onNewIntent(Intent intent)515 protected void onNewIntent(Intent intent) { 516 super.onNewIntent(intent); 517 Log.d(TAG, "onNewIntent"); 518 Bundle extras = intent.getExtras(); 519 if (extras == null) { 520 return; 521 } 522 String watchdog = extras.getString("watchdog"); 523 if (watchdog != null) { 524 CarWatchdogClient.start(getCar(), watchdog); 525 } 526 String select = extras.getString("select", ""); 527 Log.d(TAG, "Trying to launch entry: " + select); 528 int fragmentItemIndex = getOriginalIndexFromTitle(select, 0, false); 529 onFragmentItemClick(fragmentItemIndex); 530 } 531 532 @Override dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)533 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 534 boolean skipParentState = false; 535 if (args != null && args.length > 0) { 536 Log.v(TAG, "dump: args=" + Arrays.toString(args)); 537 String arg = args[0]; 538 switch (arg) { 539 case DUMP_ARG_CMD: 540 String[] cmdArgs = new String[args.length - 1]; 541 System.arraycopy(args, 1, cmdArgs, 0, args.length - 1); 542 new KitchenSinkShellCommand(this, writer, cmdArgs, mNotificationId++).run(); 543 return; 544 case DUMP_ARG_FRAGMENT: 545 if (args.length < 2) { 546 writer.println("Missing fragment name"); 547 return; 548 } 549 String select = args[1]; 550 Optional<FragmentListItem> entry = mData.stream() 551 .filter(me -> select.equals( 552 me.getTitle().getPreferredText().toString())).findAny(); 553 if (entry.isPresent()) { 554 String[] strippedArgs = new String[args.length - 2]; 555 System.arraycopy(args, 2, strippedArgs, 0, strippedArgs.length); 556 entry.get().dump(prefix, fd, writer, strippedArgs); 557 } else { 558 writer.printf("No entry called '%s'\n", select); 559 } 560 return; 561 case DUMP_ARG_QUIET: 562 skipParentState = true; 563 break; 564 default: 565 Log.v(TAG, "dump(): unknown arg, calling super(): " + Arrays.toString(args)); 566 } 567 } 568 String innerPrefix = prefix; 569 if (!skipParentState) { 570 writer.printf("%sCustom state:\n", prefix); 571 innerPrefix = prefix + prefix; 572 } 573 writer.printf("%smLastFragmentTag: %s\n", innerPrefix, mLastFragmentTitle); 574 writer.printf("%smLastFragment: %s\n", innerPrefix, mLastFragment); 575 writer.printf("%sNext Notification Id: %d\n", innerPrefix, mNotificationId); 576 577 if (skipParentState) { 578 Log.v(TAG, "dump(): skipping parent state"); 579 return; 580 } 581 writer.println(); 582 583 super.dump(prefix, fd, writer, args); 584 } 585 586 private class FragmentItemClickHandler implements CarUiContentListItem.OnClickListener { 587 @Override onClick(@onNull CarUiContentListItem carUiContentListItem)588 public void onClick(@NonNull CarUiContentListItem carUiContentListItem) { 589 int fragmentItemIndex = getFragmentIndexFromTitle( 590 carUiContentListItem.getTitle().getPreferredText().toString()); 591 onFragmentItemClick(fragmentItemIndex); 592 } 593 } 594 595 } 596