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