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