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