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.car.Car; 20 import android.car.CarAppFocusManager; 21 import android.car.CarProjectionManager; 22 import android.car.hardware.CarSensorManager; 23 import android.car.hardware.hvac.CarHvacManager; 24 import android.car.hardware.power.CarPowerManager; 25 import android.car.hardware.property.CarPropertyManager; 26 import android.car.telemetry.CarTelemetryManager; 27 import android.car.watchdog.CarWatchdogManager; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.pm.PackageManager; 31 import android.os.AsyncTask; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.util.Log; 35 import android.view.LayoutInflater; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.widget.Button; 39 import android.widget.TextView; 40 41 import androidx.fragment.app.Fragment; 42 import androidx.fragment.app.FragmentActivity; 43 import androidx.recyclerview.widget.GridLayoutManager; 44 import androidx.recyclerview.widget.RecyclerView; 45 46 import com.google.android.car.kitchensink.activityresolver.ActivityResolverFragment; 47 import com.google.android.car.kitchensink.admin.DevicePolicyFragment; 48 import com.google.android.car.kitchensink.alertdialog.AlertDialogTestFragment; 49 import com.google.android.car.kitchensink.assistant.CarAssistantFragment; 50 import com.google.android.car.kitchensink.audio.AudioTestFragment; 51 import com.google.android.car.kitchensink.audio.CarAudioInputTestFragment; 52 import com.google.android.car.kitchensink.audiorecorder.AudioRecorderTestFragment; 53 import com.google.android.car.kitchensink.backup.BackupAndRestoreFragment; 54 import com.google.android.car.kitchensink.bluetooth.BluetoothHeadsetFragment; 55 import com.google.android.car.kitchensink.bluetooth.BluetoothUuidFragment; 56 import com.google.android.car.kitchensink.bluetooth.MapMceTestFragment; 57 import com.google.android.car.kitchensink.carboard.KeyboardTestFragment; 58 import com.google.android.car.kitchensink.cluster.InstrumentClusterFragment; 59 import com.google.android.car.kitchensink.connectivity.ConnectivityFragment; 60 import com.google.android.car.kitchensink.cube.CubesTestFragment; 61 import com.google.android.car.kitchensink.diagnostic.DiagnosticTestFragment; 62 import com.google.android.car.kitchensink.displayinfo.DisplayInfoFragment; 63 import com.google.android.car.kitchensink.experimental.ExperimentalFeatureTestFragment; 64 import com.google.android.car.kitchensink.hvac.HvacTestFragment; 65 import com.google.android.car.kitchensink.insets.WindowInsetsFullScreenFragment; 66 import com.google.android.car.kitchensink.mainline.CarMainlineFragment; 67 import com.google.android.car.kitchensink.notification.NotificationFragment; 68 import com.google.android.car.kitchensink.orientation.OrientationTestFragment; 69 import com.google.android.car.kitchensink.packageinfo.PackageInfoFragment; 70 import com.google.android.car.kitchensink.power.PowerTestFragment; 71 import com.google.android.car.kitchensink.projection.ProjectionFragment; 72 import com.google.android.car.kitchensink.property.PropertyTestFragment; 73 import com.google.android.car.kitchensink.qc.QCViewerFragment; 74 import com.google.android.car.kitchensink.rotary.RotaryFragment; 75 import com.google.android.car.kitchensink.sensor.SensorsTestFragment; 76 import com.google.android.car.kitchensink.storagelifetime.StorageLifetimeFragment; 77 import com.google.android.car.kitchensink.storagevolumes.StorageVolumesFragment; 78 import com.google.android.car.kitchensink.systembars.SystemBarsFragment; 79 import com.google.android.car.kitchensink.systemfeatures.SystemFeaturesFragment; 80 import com.google.android.car.kitchensink.telemetry.CarTelemetryTestFragment; 81 import com.google.android.car.kitchensink.touch.TouchTestFragment; 82 import com.google.android.car.kitchensink.users.ProfileUserFragment; 83 import com.google.android.car.kitchensink.users.UserFragment; 84 import com.google.android.car.kitchensink.users.UserRestrictionsFragment; 85 import com.google.android.car.kitchensink.vehiclectrl.VehicleCtrlFragment; 86 import com.google.android.car.kitchensink.volume.VolumeTestFragment; 87 import com.google.android.car.kitchensink.watchdog.CarWatchdogTestFragment; 88 import com.google.android.car.kitchensink.weblinks.WebLinksTestFragment; 89 90 import java.io.FileDescriptor; 91 import java.io.PrintWriter; 92 import java.util.Arrays; 93 import java.util.Comparator; 94 import java.util.List; 95 96 public class KitchenSinkActivity extends FragmentActivity { 97 private static final String TAG = "KitchenSinkActivity"; 98 private static final String LAST_FRAGMENT_TAG = "lastFragmentTag"; 99 private static final String DEFAULT_FRAGMENT_TAG = ""; 100 private RecyclerView mMenu; 101 private Button mMenuButton; 102 private View mKitchenContent; 103 private String mLastFragmentTag = DEFAULT_FRAGMENT_TAG; 104 105 private interface ClickHandler { onClick()106 void onClick(); 107 } 108 109 private static abstract class MenuEntry implements ClickHandler { getText()110 abstract String getText(); 111 } 112 113 private final class OnClickMenuEntry extends MenuEntry { 114 private final String mText; 115 private final ClickHandler mClickHandler; 116 OnClickMenuEntry(String text, ClickHandler clickHandler)117 OnClickMenuEntry(String text, ClickHandler clickHandler) { 118 mText = text; 119 mClickHandler = clickHandler; 120 } 121 122 @Override getText()123 String getText() { 124 return mText; 125 } 126 127 @Override onClick()128 public void onClick() { 129 toggleMenuVisibility(); 130 mClickHandler.onClick(); 131 } 132 } 133 134 private final class FragmentMenuEntry<T extends Fragment> extends MenuEntry { 135 private final class FragmentClassOrInstance<T extends Fragment> { 136 final Class<T> mClazz; 137 T mFragment = null; 138 FragmentClassOrInstance(Class<T> clazz)139 FragmentClassOrInstance(Class<T> clazz) { 140 mClazz = clazz; 141 } 142 getFragment()143 T getFragment() { 144 if (mFragment == null) { 145 try { 146 mFragment = mClazz.newInstance(); 147 } catch (InstantiationException | IllegalAccessException e) { 148 Log.e(TAG, "unable to create fragment", e); 149 } 150 } 151 return mFragment; 152 } 153 } 154 155 private final String mText; 156 private final FragmentClassOrInstance<T> mFragment; 157 FragmentMenuEntry(String text, Class<T> clazz)158 FragmentMenuEntry(String text, Class<T> clazz) { 159 mText = text; 160 mFragment = new FragmentClassOrInstance<>(clazz); 161 } 162 163 @Override getText()164 String getText() { 165 return mText; 166 } 167 168 @Override onClick()169 public void onClick() { 170 Fragment fragment = mFragment.getFragment(); 171 if (fragment != null) { 172 KitchenSinkActivity.this.showFragment(fragment); 173 toggleMenuVisibility(); 174 mLastFragmentTag = fragment.getTag(); 175 } else { 176 Log.e(TAG, "cannot show fragment for " + getText()); 177 } 178 } 179 } 180 181 private final List<MenuEntry> mMenuEntries = Arrays.asList( 182 new FragmentMenuEntry("activity resolver", ActivityResolverFragment.class), 183 new FragmentMenuEntry("alert window", AlertDialogTestFragment.class), 184 new FragmentMenuEntry("assistant", CarAssistantFragment.class), 185 new FragmentMenuEntry(AudioTestFragment.FRAGMENT_NAME, AudioTestFragment.class), 186 new FragmentMenuEntry(AudioRecorderTestFragment.FRAGMENT_NAME, 187 AudioRecorderTestFragment.class), 188 new FragmentMenuEntry(CarAudioInputTestFragment.FRAGMENT_NAME, 189 CarAudioInputTestFragment.class), 190 new FragmentMenuEntry("B&R", BackupAndRestoreFragment.class), 191 new FragmentMenuEntry("BT headset", BluetoothHeadsetFragment.class), 192 new FragmentMenuEntry("BT messaging", MapMceTestFragment.class), 193 new FragmentMenuEntry("BT Uuids", BluetoothUuidFragment.class), 194 new FragmentMenuEntry("carapi", CarApiTestFragment.class), 195 new FragmentMenuEntry("carboard", KeyboardTestFragment.class), 196 new FragmentMenuEntry("connectivity", ConnectivityFragment.class), 197 new FragmentMenuEntry("cubes test", CubesTestFragment.class), 198 new FragmentMenuEntry("device policy", DevicePolicyFragment.class), 199 new FragmentMenuEntry("diagnostic", DiagnosticTestFragment.class), 200 new FragmentMenuEntry("display info", DisplayInfoFragment.class), 201 new FragmentMenuEntry("experimental feature", ExperimentalFeatureTestFragment.class), 202 new FragmentMenuEntry("hvac", HvacTestFragment.class), 203 new FragmentMenuEntry("inst cluster", InstrumentClusterFragment.class), 204 new FragmentMenuEntry("mainline", CarMainlineFragment.class), 205 new FragmentMenuEntry("notification", NotificationFragment.class), 206 new FragmentMenuEntry("orientation test", OrientationTestFragment.class), 207 new FragmentMenuEntry("package info", PackageInfoFragment.class), 208 new FragmentMenuEntry("power test", PowerTestFragment.class), 209 new FragmentMenuEntry("profile_user", ProfileUserFragment.class), 210 new FragmentMenuEntry("projection", ProjectionFragment.class), 211 new FragmentMenuEntry("property test", PropertyTestFragment.class), 212 new FragmentMenuEntry("qc viewer", QCViewerFragment.class), 213 new FragmentMenuEntry("rotary", RotaryFragment.class), 214 new FragmentMenuEntry("sensors", SensorsTestFragment.class), 215 new FragmentMenuEntry("storage lifetime", StorageLifetimeFragment.class), 216 new FragmentMenuEntry("storage volumes", StorageVolumesFragment.class), 217 new FragmentMenuEntry("system bars", SystemBarsFragment.class), 218 new FragmentMenuEntry("system features", SystemFeaturesFragment.class), 219 new FragmentMenuEntry("telemetry", CarTelemetryTestFragment.class), 220 new FragmentMenuEntry("touch test", TouchTestFragment.class), 221 new FragmentMenuEntry("users", UserFragment.class), 222 new FragmentMenuEntry("user restrictions", UserRestrictionsFragment.class), 223 new FragmentMenuEntry("vehicle ctrl", VehicleCtrlFragment.class), 224 new FragmentMenuEntry("volume test", VolumeTestFragment.class), 225 new FragmentMenuEntry("watchdog", CarWatchdogTestFragment.class), 226 new FragmentMenuEntry("web links", WebLinksTestFragment.class), 227 new FragmentMenuEntry("window insets full screen", 228 WindowInsetsFullScreenFragment.class)); 229 230 private Car mCarApi; 231 private CarHvacManager mHvacManager; 232 private CarPowerManager mPowerManager; 233 private CarPropertyManager mPropertyManager; 234 private CarSensorManager mSensorManager; 235 private CarAppFocusManager mCarAppFocusManager; 236 private CarProjectionManager mCarProjectionManager; 237 private CarTelemetryManager mCarTelemetryManager; 238 private CarWatchdogManager mCarWatchdogManager; 239 private Object mPropertyManagerReady = new Object(); 240 KitchenSinkActivity()241 public KitchenSinkActivity() { 242 mMenuEntries.sort(Comparator.comparing(MenuEntry::getText)); 243 } 244 getHvacManager()245 public CarHvacManager getHvacManager() { 246 return mHvacManager; 247 } 248 getPowerManager()249 public CarPowerManager getPowerManager() { 250 return mPowerManager; 251 } 252 getPropertyManager()253 public CarPropertyManager getPropertyManager() { 254 return mPropertyManager; 255 } 256 getSensorManager()257 public CarSensorManager getSensorManager() { 258 return mSensorManager; 259 } 260 getProjectionManager()261 public CarProjectionManager getProjectionManager() { 262 return mCarProjectionManager; 263 } 264 getCarTelemetryManager()265 public CarTelemetryManager getCarTelemetryManager() { 266 return mCarTelemetryManager; 267 } 268 getCarWatchdogManager()269 public CarWatchdogManager getCarWatchdogManager() { 270 return mCarWatchdogManager; 271 } 272 273 /* Open any tab directly: 274 * adb shell am force-stop com.google.android.car.kitchensink 275 * adb shell am start -n com.google.android.car.kitchensink/.KitchenSinkActivity \ 276 * --es "select" "connectivity" 277 * 278 * Test car watchdog: 279 * adb shell am force-stop com.google.android.car.kitchensink 280 * adb shell am start -n com.google.android.car.kitchensink/.KitchenSinkActivity \ 281 * --es "watchdog" "[timeout] [not_respond_after] [inactive_main_after] [verbose]" 282 * - timeout: critical | moderate | normal 283 * - not_respond_after: after the given seconds, the client will not respond to car watchdog 284 * (-1 for making the client respond always) 285 * - inactive_main_after: after the given seconds, the main thread will not be responsive 286 * (-1 for making the main thread responsive always) 287 * - verbose: whether to output verbose logs (default: false) 288 */ 289 @Override onNewIntent(Intent intent)290 protected void onNewIntent(Intent intent) { 291 super.onNewIntent(intent); 292 Log.i(TAG, "onNewIntent"); 293 Bundle extras = intent.getExtras(); 294 if (extras == null) { 295 return; 296 } 297 String watchdog = extras.getString("watchdog"); 298 if (watchdog != null) { 299 CarWatchdogClient.start(getCar(), watchdog); 300 } 301 String select = extras.getString("select"); 302 if (select != null) { 303 mMenuEntries.stream().filter(me -> select.equals(me.getText())) 304 .findAny().ifPresent(me -> me.onClick()); 305 } 306 } 307 308 @Override onCreate(Bundle savedInstanceState)309 protected void onCreate(Bundle savedInstanceState) { 310 super.onCreate(savedInstanceState); 311 setContentView(R.layout.kitchen_activity); 312 313 // Connection to Car Service does not work for non-automotive yet. 314 if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { 315 initCarApi(); 316 } 317 318 mKitchenContent = findViewById(R.id.kitchen_content); 319 320 mMenu = findViewById(R.id.menu); 321 mMenu.setAdapter(new MenuAdapter(this)); 322 mMenu.setLayoutManager(new GridLayoutManager(this, 4)); 323 324 mMenuButton = findViewById(R.id.menu_button); 325 mMenuButton.setOnClickListener(view -> toggleMenuVisibility()); 326 Log.i(TAG, "onCreate"); 327 onNewIntent(getIntent()); 328 } 329 330 @Override onRestoreInstanceState(Bundle savedInstanceState)331 protected void onRestoreInstanceState(Bundle savedInstanceState) { 332 super.onRestoreInstanceState(savedInstanceState); 333 // The app is being started for the first time. 334 if (savedInstanceState == null) { 335 return; 336 } 337 338 // The app is being reloaded, restores the last fragment UI. 339 mLastFragmentTag = savedInstanceState.getString(LAST_FRAGMENT_TAG); 340 if (mLastFragmentTag != DEFAULT_FRAGMENT_TAG) { 341 toggleMenuVisibility(); 342 } 343 } 344 toggleMenuVisibility()345 private void toggleMenuVisibility() { 346 boolean menuVisible = mMenu.getVisibility() == View.VISIBLE; 347 mMenu.setVisibility(menuVisible ? View.GONE : View.VISIBLE); 348 mKitchenContent.setVisibility(menuVisible ? View.VISIBLE : View.GONE); 349 mMenuButton.setText(menuVisible ? "Show KitchenSink Menu" : "Hide KitchenSink Menu"); 350 } 351 initCarApi()352 private void initCarApi() { 353 if (mCarApi != null && mCarApi.isConnected()) { 354 mCarApi.disconnect(); 355 mCarApi = null; 356 } 357 mCarApi = Car.createCar(this, null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, 358 (Car car, boolean ready) -> { 359 if (ready) { 360 initManagers(car); 361 } 362 }); 363 } 364 365 @Override onStart()366 protected void onStart() { 367 super.onStart(); 368 Log.i(TAG, "onStart"); 369 } 370 371 @Override onRestart()372 protected void onRestart() { 373 super.onRestart(); 374 Log.i(TAG, "onRestart"); 375 } 376 377 @Override onResume()378 protected void onResume() { 379 super.onResume(); 380 Log.i(TAG, "onResume"); 381 } 382 383 @Override onSaveInstanceState(Bundle outState)384 protected void onSaveInstanceState(Bundle outState) { 385 outState.putString(LAST_FRAGMENT_TAG, mLastFragmentTag); 386 super.onSaveInstanceState(outState); 387 } 388 389 @Override onPause()390 protected void onPause() { 391 super.onPause(); 392 Log.i(TAG, "onPause"); 393 } 394 395 @Override onStop()396 protected void onStop() { 397 super.onStop(); 398 Log.i(TAG, "onStop"); 399 } 400 401 @Override onDestroy()402 protected void onDestroy() { 403 if (mCarApi != null) { 404 mCarApi.disconnect(); 405 } 406 Log.i(TAG, "onDestroy"); 407 super.onDestroy(); 408 } 409 410 @Override dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)411 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 412 if (args != null && args.length > 0 && args[0].equals("cmd")) { 413 String[] cmdArgs = new String[args.length - 1]; 414 System.arraycopy(args, 1, cmdArgs, 0, args.length - 1); 415 new KitchenSinkShellCommand(this, writer, cmdArgs).run(); 416 return; 417 } 418 super.dump(prefix, fd, writer, args); 419 } 420 showFragment(Fragment fragment)421 private void showFragment(Fragment fragment) { 422 getSupportFragmentManager().beginTransaction() 423 .replace(R.id.kitchen_content, fragment) 424 .commit(); 425 } 426 initManagers(Car car)427 private void initManagers(Car car) { 428 synchronized (mPropertyManagerReady) { 429 mHvacManager = (CarHvacManager) car.getCarManager( 430 android.car.Car.HVAC_SERVICE); 431 mPowerManager = (CarPowerManager) car.getCarManager( 432 android.car.Car.POWER_SERVICE); 433 mPropertyManager = (CarPropertyManager) car.getCarManager( 434 android.car.Car.PROPERTY_SERVICE); 435 mSensorManager = (CarSensorManager) car.getCarManager( 436 android.car.Car.SENSOR_SERVICE); 437 mCarAppFocusManager = 438 (CarAppFocusManager) car.getCarManager(Car.APP_FOCUS_SERVICE); 439 mCarProjectionManager = 440 (CarProjectionManager) car.getCarManager(Car.PROJECTION_SERVICE); 441 mCarTelemetryManager = 442 (CarTelemetryManager) car.getCarManager(Car.CAR_TELEMETRY_SERVICE); 443 mCarWatchdogManager = 444 (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE); 445 mPropertyManagerReady.notifyAll(); 446 } 447 } 448 getCar()449 public Car getCar() { 450 return mCarApi; 451 } 452 453 private final class MenuAdapter extends RecyclerView.Adapter<ItemViewHolder> { 454 455 private final LayoutInflater mLayoutInflator; 456 MenuAdapter(Context context)457 MenuAdapter(Context context) { 458 mLayoutInflator = LayoutInflater.from(context); 459 } 460 461 @Override onCreateViewHolder(ViewGroup parent, int viewType)462 public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 463 View view = mLayoutInflator.inflate(R.layout.menu_item, parent, false); 464 return new ItemViewHolder(view); 465 } 466 467 @Override onBindViewHolder(ItemViewHolder holder, int position)468 public void onBindViewHolder(ItemViewHolder holder, int position) { 469 holder.mTitle.setText(mMenuEntries.get(position).getText()); 470 holder.mTitle.setOnClickListener(v -> mMenuEntries.get(position).onClick()); 471 } 472 473 @Override getItemCount()474 public int getItemCount() { 475 return mMenuEntries.size(); 476 } 477 } 478 479 private final class ItemViewHolder extends RecyclerView.ViewHolder { 480 TextView mTitle; 481 ItemViewHolder(View itemView)482 ItemViewHolder(View itemView) { 483 super(itemView); 484 mTitle = itemView.findViewById(R.id.title); 485 } 486 } 487 488 // Use AsyncTask to refresh Car*Manager after car service connected requestRefreshManager(final Runnable r, final Handler h)489 public void requestRefreshManager(final Runnable r, final Handler h) { 490 final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { 491 @Override 492 protected Void doInBackground(Void... unused) { 493 synchronized (mPropertyManagerReady) { 494 while (!mCarApi.isConnected()) { 495 try { 496 mPropertyManagerReady.wait(); 497 } catch (InterruptedException e) { 498 return null; 499 } 500 } 501 } 502 return null; 503 } 504 505 @Override 506 protected void onPostExecute(Void unused) { 507 h.post(r); 508 } 509 }; 510 task.execute(); 511 } 512 } 513