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 17 package com.google.android.car.kitchensink; 18 19 import android.annotation.Nullable; 20 import android.app.NotificationManager; 21 import android.car.Car; 22 import android.car.CarOccupantZoneManager; 23 import android.car.CarProjectionManager; 24 import android.car.hardware.CarSensorManager; 25 import android.car.hardware.hvac.CarHvacManager; 26 import android.car.hardware.power.CarPowerManager; 27 import android.car.hardware.property.CarPropertyManager; 28 import android.car.os.CarPerformanceManager; 29 import android.car.telemetry.CarTelemetryManager; 30 import android.car.watchdog.CarWatchdogManager; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.SystemProperties; 36 import android.text.TextUtils; 37 import android.util.Log; 38 import android.util.Pair; 39 import android.view.LayoutInflater; 40 import android.view.View; 41 import android.view.ViewGroup; 42 import android.widget.Button; 43 import android.widget.LinearLayout; 44 import android.widget.TextView; 45 46 import androidx.fragment.app.Fragment; 47 import androidx.fragment.app.FragmentActivity; 48 import androidx.recyclerview.widget.GridLayoutManager; 49 import androidx.recyclerview.widget.RecyclerView; 50 51 import com.google.android.car.kitchensink.activityresolver.ActivityResolverFragment; 52 import com.google.android.car.kitchensink.admin.DevicePolicyFragment; 53 import com.google.android.car.kitchensink.alertdialog.AlertDialogTestFragment; 54 import com.google.android.car.kitchensink.assistant.CarAssistantFragment; 55 import com.google.android.car.kitchensink.audio.AudioConfigurationTestFragment; 56 import com.google.android.car.kitchensink.audio.AudioMirrorTestFragment; 57 import com.google.android.car.kitchensink.audio.AudioTestFragment; 58 import com.google.android.car.kitchensink.audio.AudioUserAssignmentFragment; 59 import com.google.android.car.kitchensink.audio.CarAudioInputTestFragment; 60 import com.google.android.car.kitchensink.audio.OemCarServiceTestFragment; 61 import com.google.android.car.kitchensink.audiorecorder.AudioRecorderTestFragment; 62 import com.google.android.car.kitchensink.backup.BackupAndRestoreFragment; 63 import com.google.android.car.kitchensink.biometrics.BiometricPromptTestFragment; 64 import com.google.android.car.kitchensink.bluetooth.BluetoothHeadsetFragment; 65 import com.google.android.car.kitchensink.bluetooth.BluetoothUuidFragment; 66 import com.google.android.car.kitchensink.bluetooth.MapMceTestFragment; 67 import com.google.android.car.kitchensink.camera2.Camera2TestFragment; 68 import com.google.android.car.kitchensink.carboard.KeyboardTestFragment; 69 import com.google.android.car.kitchensink.cluster.InstrumentClusterFragment; 70 import com.google.android.car.kitchensink.connectivity.ConnectivityFragment; 71 import com.google.android.car.kitchensink.cube.CubesTestFragment; 72 import com.google.android.car.kitchensink.customizationtool.CustomizationToolFragment; 73 import com.google.android.car.kitchensink.diagnostic.DiagnosticTestFragment; 74 import com.google.android.car.kitchensink.display.DisplayInfoFragment; 75 import com.google.android.car.kitchensink.display.DisplayMirroringFragment; 76 import com.google.android.car.kitchensink.display.VirtualDisplayFragment; 77 import com.google.android.car.kitchensink.drivemode.DriveModeSwitchFragment; 78 import com.google.android.car.kitchensink.experimental.ExperimentalFeatureTestFragment; 79 import com.google.android.car.kitchensink.hotword.CarMultiConcurrentHotwordTestFragment; 80 import com.google.android.car.kitchensink.hvac.HvacTestFragment; 81 import com.google.android.car.kitchensink.input.DisplayInputLockTestFragment; 82 import com.google.android.car.kitchensink.insets.WindowInsetsFullScreenFragment; 83 import com.google.android.car.kitchensink.key.InjectKeyTestFragment; 84 import com.google.android.car.kitchensink.mainline.CarMainlineFragment; 85 import com.google.android.car.kitchensink.media.MultidisplayMediaFragment; 86 import com.google.android.car.kitchensink.notification.NotificationFragment; 87 import com.google.android.car.kitchensink.orientation.OrientationTestFragment; 88 import com.google.android.car.kitchensink.os.CarPerformanceTestFragment; 89 import com.google.android.car.kitchensink.packageinfo.PackageInfoFragment; 90 import com.google.android.car.kitchensink.power.PowerTestFragment; 91 import com.google.android.car.kitchensink.privacy.PrivacyIndicatorFragment; 92 import com.google.android.car.kitchensink.projection.ProjectionFragment; 93 import com.google.android.car.kitchensink.property.PropertyTestFragment; 94 import com.google.android.car.kitchensink.qc.QCViewerFragment; 95 import com.google.android.car.kitchensink.radio.RadioTestFragment; 96 import com.google.android.car.kitchensink.remoteaccess.RemoteAccessTestFragment; 97 import com.google.android.car.kitchensink.rotary.RotaryFragment; 98 import com.google.android.car.kitchensink.sensor.SensorsTestFragment; 99 import com.google.android.car.kitchensink.storagelifetime.StorageLifetimeFragment; 100 import com.google.android.car.kitchensink.storagevolumes.StorageVolumesFragment; 101 import com.google.android.car.kitchensink.systembars.SystemBarsFragment; 102 import com.google.android.car.kitchensink.systemfeatures.SystemFeaturesFragment; 103 import com.google.android.car.kitchensink.telemetry.CarTelemetryTestFragment; 104 import com.google.android.car.kitchensink.touch.InjectMotionTestFragment; 105 import com.google.android.car.kitchensink.touch.TouchTestFragment; 106 import com.google.android.car.kitchensink.users.ProfileUserFragment; 107 import com.google.android.car.kitchensink.users.UserFragment; 108 import com.google.android.car.kitchensink.users.UserRestrictionsFragment; 109 import com.google.android.car.kitchensink.vehiclectrl.VehicleCtrlFragment; 110 import com.google.android.car.kitchensink.volume.VolumeTestFragment; 111 import com.google.android.car.kitchensink.watchdog.CarWatchdogTestFragment; 112 import com.google.android.car.kitchensink.weblinks.WebLinksTestFragment; 113 114 import java.io.FileDescriptor; 115 import java.io.PrintWriter; 116 import java.util.ArrayList; 117 import java.util.Arrays; 118 import java.util.Comparator; 119 import java.util.List; 120 import java.util.Optional; 121 122 public class KitchenSinkActivity extends FragmentActivity implements KitchenSinkHelper { 123 124 public static final String TAG = "KitchenSinkActivity"; 125 private static final String LAST_FRAGMENT_TAG = "lastFragmentTag"; 126 private static final String DEFAULT_FRAGMENT_TAG = ""; 127 128 private static final String PROPERTY_SHOW_HEADER_INFO = 129 "com.android.car.kitchensink.SHOW_HEADER_INFO"; 130 131 private RecyclerView mMenu; 132 private LinearLayout mHeader; 133 private Button mMenuButton; 134 private TextView mUserIdView; 135 private TextView mDisplayIdView; 136 private View mKitchenContent; 137 private String mLastFragmentTag = DEFAULT_FRAGMENT_TAG; 138 @Nullable 139 private Fragment mLastFragment; 140 private int mNotificationId = 1000; 141 private boolean mShowHeaderInfo; 142 143 private final KitchenSinkHelperImpl mKsHelperImpl = new KitchenSinkHelperImpl(); 144 145 public static final String DUMP_ARG_CMD = "cmd"; 146 public static final String DUMP_ARG_FRAGMENT = "fragment"; 147 public static final String DUMP_ARG_QUIET = "quiet"; 148 public static final String DUMP_ARG_REFRESH = "refresh"; 149 150 @Override getCar()151 public Car getCar() { 152 return mKsHelperImpl.getCar(); 153 } 154 155 @Override requestRefreshManager(Runnable r, Handler h)156 public void requestRefreshManager(Runnable r, Handler h) { 157 mKsHelperImpl.requestRefreshManager(r, h); 158 } 159 160 @Override getPropertyManager()161 public CarPropertyManager getPropertyManager() { 162 return mKsHelperImpl.getPropertyManager(); 163 } 164 165 @Override getHvacManager()166 public CarHvacManager getHvacManager() { 167 return mKsHelperImpl.getHvacManager(); 168 } 169 170 @Override getOccupantZoneManager()171 public CarOccupantZoneManager getOccupantZoneManager() { 172 return mKsHelperImpl.getOccupantZoneManager(); 173 } 174 175 @Override getPowerManager()176 public CarPowerManager getPowerManager() { 177 return mKsHelperImpl.getPowerManager(); 178 } 179 180 @Override getSensorManager()181 public CarSensorManager getSensorManager() { 182 return mKsHelperImpl.getSensorManager(); 183 } 184 185 @Override getProjectionManager()186 public CarProjectionManager getProjectionManager() { 187 return mKsHelperImpl.getProjectionManager(); 188 } 189 190 @Override getCarTelemetryManager()191 public CarTelemetryManager getCarTelemetryManager() { 192 return mKsHelperImpl.getCarTelemetryManager(); 193 } 194 195 @Override getCarWatchdogManager()196 public CarWatchdogManager getCarWatchdogManager() { 197 return mKsHelperImpl.getCarWatchdogManager(); 198 } 199 200 @Override getPerformanceManager()201 public CarPerformanceManager getPerformanceManager() { 202 return mKsHelperImpl.getPerformanceManager(); 203 } 204 205 private interface ClickHandler { onClick()206 void onClick(); 207 } 208 209 private static abstract class MenuEntry implements ClickHandler { getText()210 abstract String getText(); 211 dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)212 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 213 writer.printf("%s doesn't implement dump()\n", this); 214 } 215 } 216 217 private final class OnClickMenuEntry extends MenuEntry { 218 private final String mText; 219 private final ClickHandler mClickHandler; 220 OnClickMenuEntry(String text, ClickHandler clickHandler)221 OnClickMenuEntry(String text, ClickHandler clickHandler) { 222 mText = text; 223 mClickHandler = clickHandler; 224 } 225 226 @Override getText()227 String getText() { 228 return mText; 229 } 230 231 @Override onClick()232 public void onClick() { 233 toggleMenuVisibility(); 234 mClickHandler.onClick(); 235 } 236 } 237 238 private final class FragmentMenuEntry<T extends Fragment> extends MenuEntry { 239 private final class FragmentClassOrInstance<T extends Fragment> { 240 final Class<T> mClazz; 241 T mFragment = null; 242 FragmentClassOrInstance(Class<T> clazz)243 FragmentClassOrInstance(Class<T> clazz) { 244 mClazz = clazz; 245 } 246 getFragment()247 T getFragment() { 248 if (mFragment == null) { 249 try { 250 mFragment = mClazz.newInstance(); 251 } catch (InstantiationException | IllegalAccessException e) { 252 Log.e(TAG, "unable to create fragment", e); 253 } 254 } 255 return mFragment; 256 } 257 } 258 259 private final String mText; 260 private final FragmentClassOrInstance<T> mFragment; 261 FragmentMenuEntry(String text, Class<T> clazz)262 FragmentMenuEntry(String text, Class<T> clazz) { 263 mText = text; 264 mFragment = new FragmentClassOrInstance<>(clazz); 265 } 266 267 @Override getText()268 String getText() { 269 return mText; 270 } 271 272 @Override onClick()273 public void onClick() { 274 Fragment fragment = mFragment.getFragment(); 275 if (fragment != null) { 276 KitchenSinkActivity.this.showFragment(fragment); 277 toggleMenuVisibility(); 278 mLastFragmentTag = fragment.getTag(); 279 } else { 280 Log.e(TAG, "cannot show fragment for " + getText()); 281 } 282 } 283 284 @Override dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)285 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 286 Fragment fragment = mFragment.getFragment(); 287 if (fragment != null) { 288 fragment.dump(prefix, fd, writer, args); 289 } else { 290 writer.printf("Cannot dump %s\n", getText()); 291 } 292 } 293 } 294 295 private final List<MenuEntry> mMenuEntries = new ArrayList<>(); 296 297 public static final List<Pair<String, Class>> MENU_ENTRIES = Arrays.asList( 298 new Pair<>("activity resolver", ActivityResolverFragment.class), 299 new Pair<>("alert window", AlertDialogTestFragment.class), 300 new Pair<>("assistant", CarAssistantFragment.class), 301 new Pair<>(AudioTestFragment.FRAGMENT_NAME, AudioTestFragment.class), 302 new Pair<>(AudioUserAssignmentFragment.FRAGMENT_NAME, 303 AudioUserAssignmentFragment.class), 304 new Pair<>(AudioConfigurationTestFragment.FRAGMENT_NAME, 305 AudioConfigurationTestFragment.class), 306 new Pair<>(AudioRecorderTestFragment.FRAGMENT_NAME, 307 AudioRecorderTestFragment.class), 308 new Pair<>(CarAudioInputTestFragment.FRAGMENT_NAME, 309 CarAudioInputTestFragment.class), 310 new Pair<>(AudioMirrorTestFragment.FRAGMENT_NAME, 311 AudioMirrorTestFragment.class), 312 new Pair<>("Hotword", CarMultiConcurrentHotwordTestFragment.class), 313 new Pair<>("B&R", BackupAndRestoreFragment.class), 314 new Pair<>("BT headset", BluetoothHeadsetFragment.class), 315 new Pair<>("BT messaging", MapMceTestFragment.class), 316 new Pair<>("BT Uuids", BluetoothUuidFragment.class), 317 new Pair<>(BiometricPromptTestFragment.FRAGMENT_NAME, 318 BiometricPromptTestFragment.class), 319 new Pair<>("carapi", CarApiTestFragment.class), 320 new Pair<>("carboard", KeyboardTestFragment.class), 321 new Pair<>("connectivity", ConnectivityFragment.class), 322 new Pair<>("cubes test", CubesTestFragment.class), 323 new Pair<>("customization tool", CustomizationToolFragment.class), 324 new Pair<>("device policy", DevicePolicyFragment.class), 325 new Pair<>("diagnostic", DiagnosticTestFragment.class), 326 new Pair<>("display info", DisplayInfoFragment.class), 327 new Pair<>("display input lock", DisplayInputLockTestFragment.class), 328 new Pair<>("display mirroring", DisplayMirroringFragment.class), 329 new Pair<>("drive mode switch", DriveModeSwitchFragment.class), 330 new Pair<>("experimental feature", ExperimentalFeatureTestFragment.class), 331 new Pair<>("hvac", HvacTestFragment.class), 332 new Pair<>("inst cluster", InstrumentClusterFragment.class), 333 new Pair<>("mainline", CarMainlineFragment.class), 334 new Pair<>("MD media", MultidisplayMediaFragment.class), 335 new Pair<>("notification", NotificationFragment.class), 336 new Pair<>("orientation test", OrientationTestFragment.class), 337 new Pair<>("package info", PackageInfoFragment.class), 338 new Pair<>("performance", CarPerformanceTestFragment.class), 339 new Pair<>("power test", PowerTestFragment.class), 340 new Pair<>(PrivacyIndicatorFragment.FRAGMENT_NAME, 341 PrivacyIndicatorFragment.class), 342 new Pair<>("profile_user", ProfileUserFragment.class), 343 new Pair<>("projection", ProjectionFragment.class), 344 new Pair<>("property test", PropertyTestFragment.class), 345 new Pair<>("qc viewer", QCViewerFragment.class), 346 new Pair<>("remote access", RemoteAccessTestFragment.class), 347 new Pair<>("rotary", RotaryFragment.class), 348 new Pair<>("sensors", SensorsTestFragment.class), 349 new Pair<>("storage lifetime", StorageLifetimeFragment.class), 350 new Pair<>("storage volumes", StorageVolumesFragment.class), 351 new Pair<>("system bars", SystemBarsFragment.class), 352 new Pair<>("system features", SystemFeaturesFragment.class), 353 new Pair<>("telemetry", CarTelemetryTestFragment.class), 354 new Pair<>("touch test", TouchTestFragment.class), 355 new Pair<>("users", UserFragment.class), 356 new Pair<>("user restrictions", UserRestrictionsFragment.class), 357 new Pair<>("vehicle ctrl", VehicleCtrlFragment.class), 358 new Pair<>(VirtualDisplayFragment.FRAGMENT_NAME, 359 VirtualDisplayFragment.class), 360 new Pair<>("volume test", VolumeTestFragment.class), 361 new Pair<>("watchdog", CarWatchdogTestFragment.class), 362 new Pair<>("web links", WebLinksTestFragment.class), 363 new Pair<>("inject motion", InjectMotionTestFragment.class), 364 new Pair<>("inject key", InjectKeyTestFragment.class), 365 new Pair<>("window insets full screen", 366 WindowInsetsFullScreenFragment.class), 367 new Pair<>("oem car service", OemCarServiceTestFragment.class), 368 new Pair<>("Camera2", Camera2TestFragment.class), 369 new Pair<>(RadioTestFragment.FRAGMENT_NAME, RadioTestFragment.class)); 370 KitchenSinkActivity()371 public KitchenSinkActivity() { 372 for (Pair<String, Class> entry : MENU_ENTRIES) { 373 mMenuEntries.add(new FragmentMenuEntry(entry.first, entry.second)); 374 } 375 mMenuEntries.sort(Comparator.comparing(MenuEntry::getText)); 376 } 377 378 379 /* Open any tab directly: 380 * adb shell am force-stop com.google.android.car.kitchensink 381 * adb shell am 'start -n com.google.android.car.kitchensink/.KitchenSinkActivity \ 382 * --es select "connectivity"' 383 * 384 * Test car watchdog: 385 * adb shell am force-stop com.google.android.car.kitchensink 386 * adb shell am start -n com.google.android.car.kitchensink/.KitchenSinkActivity \ 387 * --es "watchdog" "[timeout] [not_respond_after] [inactive_main_after] [verbose]" 388 * - timeout: critical | moderate | normal 389 * - not_respond_after: after the given seconds, the client will not respond to car watchdog 390 * (-1 for making the client respond always) 391 * - inactive_main_after: after the given seconds, the main thread will not be responsive 392 * (-1 for making the main thread responsive always) 393 * - verbose: whether to output verbose logs (default: false) 394 */ 395 @Override onNewIntent(Intent intent)396 protected void onNewIntent(Intent intent) { 397 super.onNewIntent(intent); 398 Log.i(TAG, "onNewIntent"); 399 if (intent.getCategories() != null 400 && intent.getCategories().contains( 401 NotificationFragment.INTENT_CATEGORY_SELF_DISMISS)) { 402 NotificationManager nm = this.getSystemService(NotificationManager.class); 403 nm.cancel(NotificationFragment.SELF_DISMISS_NOTIFICATION_ID); 404 } 405 Bundle extras = intent.getExtras(); 406 if (extras == null) { 407 return; 408 } 409 String watchdog = extras.getString("watchdog"); 410 if (watchdog != null) { 411 CarWatchdogClient.start(getCar(), watchdog); 412 } 413 String select = extras.getString("select"); 414 if (select != null) { 415 Log.d(TAG, "Trying to launch entry '" + select + "'"); 416 mMenuEntries.stream().filter(me -> select.equals(me.getText())) 417 .findAny().ifPresent(me -> me.onClick()); 418 } 419 } 420 421 @Override onCreate(Bundle savedInstanceState)422 protected void onCreate(Bundle savedInstanceState) { 423 super.onCreate(savedInstanceState); 424 setContentView(R.layout.kitchen_activity); 425 426 findViewById(R.id.root).setOnApplyWindowInsetsListener((v, insets) -> { 427 final android.graphics.Insets i = insets.getSystemWindowInsets(); 428 v.setPadding(i.left, i.top, i.right, i.bottom); 429 return insets.inset(i).consumeSystemWindowInsets(); 430 }); 431 432 mKsHelperImpl.initCarApiIfAutomotive(this); 433 434 mKitchenContent = findViewById(R.id.kitchen_content); 435 436 mMenu = findViewById(R.id.menu); 437 mMenu.setAdapter(new MenuAdapter(this)); 438 mMenu.setLayoutManager(new GridLayoutManager(this, 4)); 439 440 mMenuButton = findViewById(R.id.menu_button); 441 mMenuButton.setOnClickListener(view -> toggleMenuVisibility()); 442 443 ((Button) findViewById(R.id.new_version_button)).setOnClickListener( 444 view -> goToNewVersion()); 445 ((Button) findViewById(R.id.finish_button)).setOnClickListener(view -> finish()); 446 ((Button) findViewById(R.id.home_button)).setOnClickListener(view -> launchHome()); 447 448 mHeader = findViewById(R.id.header); 449 450 int userId = getUserId(); 451 int displayId = getDisplayId(); 452 453 mUserIdView = findViewById(R.id.user_id); 454 mDisplayIdView = findViewById(R.id.display_id); 455 mUserIdView.setText("U#" + userId); 456 mDisplayIdView.setText("D#" + displayId); 457 458 Log.i(TAG, "onCreate: userId=" + userId + ", displayId=" + displayId); 459 onNewIntent(getIntent()); 460 } 461 462 @Override onRestoreInstanceState(Bundle savedInstanceState)463 protected void onRestoreInstanceState(Bundle savedInstanceState) { 464 super.onRestoreInstanceState(savedInstanceState); 465 // The app is being started for the first time. 466 if (savedInstanceState == null) { 467 return; 468 } 469 470 // The app is being reloaded, restores the last fragment UI. 471 mLastFragmentTag = savedInstanceState.getString(LAST_FRAGMENT_TAG); 472 if (!DEFAULT_FRAGMENT_TAG.equals(mLastFragmentTag)) { 473 toggleMenuVisibility(); 474 } 475 } 476 toggleMenuVisibility()477 private void toggleMenuVisibility() { 478 boolean menuVisible = mMenu.getVisibility() == View.VISIBLE; 479 mMenu.setVisibility(menuVisible ? View.GONE : View.VISIBLE); 480 int contentVisibility = menuVisible ? View.VISIBLE : View.GONE; 481 mKitchenContent.setVisibility(contentVisibility); 482 mMenuButton.setText(menuVisible ? "Show KitchenSink Menu" : "Hide KitchenSink Menu"); 483 if (mLastFragment != null) { 484 mLastFragment.onHiddenChanged(!menuVisible); 485 } 486 } 487 488 /** 489 * Goes to the newer version of Kitchen Sink App. 490 */ goToNewVersion()491 private void goToNewVersion() { 492 Intent intent = new Intent(this, KitchenSink2Activity.class); 493 startActivity(intent); 494 } 495 496 /** 497 * Sets the visibility of the header that's shown on all fragments. 498 */ setHeaderVisibility(boolean visible)499 public void setHeaderVisibility(boolean visible) { 500 mHeader.setVisibility(visible ? View.VISIBLE : View.GONE); 501 } 502 503 /** 504 * Adds a view to the main header (which by default contains the "show/ hide KS menu" button). 505 */ addHeaderView(View view)506 public void addHeaderView(View view) { 507 Log.d(TAG, "Adding header view: " + view); 508 mHeader.addView(view, new ViewGroup.LayoutParams( 509 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); 510 } 511 launchHome()512 private void launchHome() { 513 Intent homeIntent = new Intent(Intent.ACTION_MAIN); 514 homeIntent.addCategory(Intent.CATEGORY_HOME); 515 startActivity(homeIntent); 516 } 517 518 @Override onStart()519 protected void onStart() { 520 super.onStart(); 521 Log.i(TAG, "onStart"); 522 } 523 524 @Override onRestart()525 protected void onRestart() { 526 super.onRestart(); 527 Log.i(TAG, "onRestart"); 528 } 529 530 @Override onResume()531 protected void onResume() { 532 super.onResume(); 533 Log.i(TAG, "onResume"); 534 535 updateHeaderInfoVisibility(); 536 } 537 538 @Override onSaveInstanceState(Bundle outState)539 protected void onSaveInstanceState(Bundle outState) { 540 outState.putString(LAST_FRAGMENT_TAG, mLastFragmentTag); 541 super.onSaveInstanceState(outState); 542 } 543 544 @Override onPause()545 protected void onPause() { 546 super.onPause(); 547 Log.i(TAG, "onPause"); 548 } 549 550 @Override onStop()551 protected void onStop() { 552 super.onStop(); 553 Log.i(TAG, "onStop"); 554 } 555 556 @Override onDestroy()557 protected void onDestroy() { 558 mKsHelperImpl.disconnect(); 559 Log.i(TAG, "onDestroy"); 560 super.onDestroy(); 561 } 562 563 @Override dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)564 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 565 boolean skipParentState = false; 566 if (args != null && args.length > 0) { 567 Log.v(TAG, "dump: args=" + Arrays.toString(args)); 568 String arg = args[0]; 569 switch (arg) { 570 case DUMP_ARG_CMD: 571 String[] cmdArgs = new String[args.length - 1]; 572 System.arraycopy(args, 1, cmdArgs, 0, args.length - 1); 573 new KitchenSinkShellCommand(this, writer, cmdArgs, mNotificationId++).run(); 574 return; 575 case DUMP_ARG_FRAGMENT: 576 if (args.length < 2) { 577 writer.println("Missing fragment name"); 578 return; 579 } 580 String select = args[1]; 581 Optional<MenuEntry> entry = mMenuEntries.stream() 582 .filter(me -> select.equals(me.getText())).findAny(); 583 if (entry.isPresent()) { 584 String[] strippedArgs = new String[args.length - 2]; 585 System.arraycopy(args, 2, strippedArgs, 0, strippedArgs.length); 586 entry.get().dump(prefix, fd, writer, strippedArgs); 587 } else { 588 writer.printf("No entry called '%s'\n", select); 589 } 590 return; 591 case DUMP_ARG_QUIET: 592 skipParentState = true; 593 break; 594 case DUMP_ARG_REFRESH: 595 updateHeaderInfoVisibility(writer); 596 return; 597 default: 598 Log.v(TAG, "dump(): unknown arg, calling super(): " + Arrays.toString(args)); 599 } 600 } 601 String innerPrefix = prefix; 602 if (!skipParentState) { 603 writer.printf("%sCustom state:\n", prefix); 604 innerPrefix = prefix + prefix; 605 } 606 writer.printf("%smLastFragmentTag: %s\n", innerPrefix, mLastFragmentTag); 607 writer.printf("%smLastFragment: %s\n", innerPrefix, mLastFragment); 608 writer.printf("%sHeader views: %d\n", innerPrefix, mHeader.getChildCount()); 609 writer.printf("%sNext Notification Id: %d\n", innerPrefix, mNotificationId); 610 writer.printf("%sShow header info: %b\n", innerPrefix, mShowHeaderInfo); 611 612 if (skipParentState) { 613 Log.v(TAG, "dump(): skipping parent state"); 614 return; 615 } 616 writer.println(); 617 618 super.dump(prefix, fd, writer, args); 619 } 620 showFragment(Fragment fragment)621 private void showFragment(Fragment fragment) { 622 if (mLastFragment != fragment) { 623 Log.v(TAG, "showFragment(): from " + mLastFragment + " to " + fragment); 624 } else { 625 Log.v(TAG, "showFragment(): showing " + fragment + " again"); 626 } 627 getSupportFragmentManager().beginTransaction() 628 .replace(R.id.kitchen_content, fragment) 629 .commit(); 630 mLastFragment = fragment; 631 } 632 updateHeaderInfoVisibility()633 private void updateHeaderInfoVisibility() { 634 mShowHeaderInfo = getBooleanProperty(PROPERTY_SHOW_HEADER_INFO, false); 635 Log.i(TAG, "updateHeaderInfoVisibility(): showHeaderInfo=" + mShowHeaderInfo); 636 int visibility = mShowHeaderInfo ? View.VISIBLE : View.GONE; 637 mUserIdView.setVisibility(visibility); 638 mDisplayIdView.setVisibility(visibility); 639 } 640 updateHeaderInfoVisibility(PrintWriter writer)641 private void updateHeaderInfoVisibility(PrintWriter writer) { 642 boolean before = mShowHeaderInfo; 643 updateHeaderInfoVisibility(); 644 boolean after = mShowHeaderInfo; 645 writer.printf("Updated header info visibility from %b to %b\n", before, after); 646 } 647 648 private final class MenuAdapter extends RecyclerView.Adapter<ItemViewHolder> { 649 650 private final LayoutInflater mLayoutInflator; 651 MenuAdapter(Context context)652 MenuAdapter(Context context) { 653 mLayoutInflator = LayoutInflater.from(context); 654 } 655 656 @Override onCreateViewHolder(ViewGroup parent, int viewType)657 public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 658 View view = mLayoutInflator.inflate(R.layout.menu_item, parent, false); 659 return new ItemViewHolder(view); 660 } 661 662 @Override onBindViewHolder(ItemViewHolder holder, int position)663 public void onBindViewHolder(ItemViewHolder holder, int position) { 664 holder.mTitle.setText(mMenuEntries.get(position).getText()); 665 holder.mTitle.setOnClickListener(v -> mMenuEntries.get(position).onClick()); 666 } 667 668 @Override getItemCount()669 public int getItemCount() { 670 return mMenuEntries.size(); 671 } 672 } 673 674 private final class ItemViewHolder extends RecyclerView.ViewHolder { 675 TextView mTitle; 676 ItemViewHolder(View itemView)677 ItemViewHolder(View itemView) { 678 super(itemView); 679 mTitle = itemView.findViewById(R.id.title); 680 } 681 } 682 getBooleanProperty(String prop, boolean defaultValue)683 private static boolean getBooleanProperty(String prop, boolean defaultValue) { 684 String value = SystemProperties.get(prop); 685 Log.v(TAG, "getBooleanProperty(" + prop + "): got '" + value + "'"); 686 if (!TextUtils.isEmpty(value)) { 687 boolean finalValue = Boolean.valueOf(value); 688 Log.v(TAG, "returning " + finalValue); 689 return finalValue; 690 } 691 String persistProp = "persist." + prop; 692 boolean finalValue = SystemProperties.getBoolean(persistProp, defaultValue); 693 Log.v(TAG, "getBooleanProperty(" + persistProp + "): returning " + finalValue); 694 return finalValue; 695 } 696 } 697