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