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.annotation.Nullable;
20 import android.app.NotificationManager;
21 import android.car.Car;
22 import android.car.CarOccupantZoneManager;
23 import android.car.CarProjectionManager;
24 import android.car.hardware.CarSensorManager;
25 import android.car.hardware.hvac.CarHvacManager;
26 import android.car.hardware.power.CarPowerManager;
27 import android.car.hardware.property.CarPropertyManager;
28 import android.car.os.CarPerformanceManager;
29 import android.car.telemetry.CarTelemetryManager;
30 import android.car.watchdog.CarWatchdogManager;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.SystemProperties;
36 import android.text.TextUtils;
37 import android.util.Log;
38 import android.util.Pair;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.widget.Button;
43 import android.widget.LinearLayout;
44 import android.widget.TextView;
45 
46 import androidx.fragment.app.Fragment;
47 import androidx.fragment.app.FragmentActivity;
48 import androidx.recyclerview.widget.GridLayoutManager;
49 import androidx.recyclerview.widget.RecyclerView;
50 
51 import com.google.android.car.kitchensink.activityresolver.ActivityResolverFragment;
52 import com.google.android.car.kitchensink.admin.DevicePolicyFragment;
53 import com.google.android.car.kitchensink.alertdialog.AlertDialogTestFragment;
54 import com.google.android.car.kitchensink.assistant.CarAssistantFragment;
55 import com.google.android.car.kitchensink.audio.AudioConfigurationTestFragment;
56 import com.google.android.car.kitchensink.audio.AudioMirrorTestFragment;
57 import com.google.android.car.kitchensink.audio.AudioTestFragment;
58 import com.google.android.car.kitchensink.audio.AudioUserAssignmentFragment;
59 import com.google.android.car.kitchensink.audio.CarAudioInputTestFragment;
60 import com.google.android.car.kitchensink.audio.OemCarServiceTestFragment;
61 import com.google.android.car.kitchensink.audiorecorder.AudioRecorderTestFragment;
62 import com.google.android.car.kitchensink.backup.BackupAndRestoreFragment;
63 import com.google.android.car.kitchensink.biometrics.BiometricPromptTestFragment;
64 import com.google.android.car.kitchensink.bluetooth.BluetoothHeadsetFragment;
65 import com.google.android.car.kitchensink.bluetooth.BluetoothUuidFragment;
66 import com.google.android.car.kitchensink.bluetooth.MapMceTestFragment;
67 import com.google.android.car.kitchensink.camera2.Camera2TestFragment;
68 import com.google.android.car.kitchensink.carboard.KeyboardTestFragment;
69 import com.google.android.car.kitchensink.cluster.InstrumentClusterFragment;
70 import com.google.android.car.kitchensink.connectivity.ConnectivityFragment;
71 import com.google.android.car.kitchensink.cube.CubesTestFragment;
72 import com.google.android.car.kitchensink.customizationtool.CustomizationToolFragment;
73 import com.google.android.car.kitchensink.diagnostic.DiagnosticTestFragment;
74 import com.google.android.car.kitchensink.display.DisplayInfoFragment;
75 import com.google.android.car.kitchensink.display.DisplayMirroringFragment;
76 import com.google.android.car.kitchensink.display.VirtualDisplayFragment;
77 import com.google.android.car.kitchensink.drivemode.DriveModeSwitchFragment;
78 import com.google.android.car.kitchensink.experimental.ExperimentalFeatureTestFragment;
79 import com.google.android.car.kitchensink.hotword.CarMultiConcurrentHotwordTestFragment;
80 import com.google.android.car.kitchensink.hvac.HvacTestFragment;
81 import com.google.android.car.kitchensink.input.DisplayInputLockTestFragment;
82 import com.google.android.car.kitchensink.insets.WindowInsetsFullScreenFragment;
83 import com.google.android.car.kitchensink.key.InjectKeyTestFragment;
84 import com.google.android.car.kitchensink.mainline.CarMainlineFragment;
85 import com.google.android.car.kitchensink.media.MultidisplayMediaFragment;
86 import com.google.android.car.kitchensink.notification.NotificationFragment;
87 import com.google.android.car.kitchensink.orientation.OrientationTestFragment;
88 import com.google.android.car.kitchensink.os.CarPerformanceTestFragment;
89 import com.google.android.car.kitchensink.packageinfo.PackageInfoFragment;
90 import com.google.android.car.kitchensink.power.PowerTestFragment;
91 import com.google.android.car.kitchensink.privacy.PrivacyIndicatorFragment;
92 import com.google.android.car.kitchensink.projection.ProjectionFragment;
93 import com.google.android.car.kitchensink.property.PropertyTestFragment;
94 import com.google.android.car.kitchensink.qc.QCViewerFragment;
95 import com.google.android.car.kitchensink.radio.RadioTestFragment;
96 import com.google.android.car.kitchensink.remoteaccess.RemoteAccessTestFragment;
97 import com.google.android.car.kitchensink.rotary.RotaryFragment;
98 import com.google.android.car.kitchensink.sensor.SensorsTestFragment;
99 import com.google.android.car.kitchensink.storagelifetime.StorageLifetimeFragment;
100 import com.google.android.car.kitchensink.storagevolumes.StorageVolumesFragment;
101 import com.google.android.car.kitchensink.systembars.SystemBarsFragment;
102 import com.google.android.car.kitchensink.systemfeatures.SystemFeaturesFragment;
103 import com.google.android.car.kitchensink.telemetry.CarTelemetryTestFragment;
104 import com.google.android.car.kitchensink.touch.InjectMotionTestFragment;
105 import com.google.android.car.kitchensink.touch.TouchTestFragment;
106 import com.google.android.car.kitchensink.users.ProfileUserFragment;
107 import com.google.android.car.kitchensink.users.UserFragment;
108 import com.google.android.car.kitchensink.users.UserRestrictionsFragment;
109 import com.google.android.car.kitchensink.vehiclectrl.VehicleCtrlFragment;
110 import com.google.android.car.kitchensink.volume.VolumeTestFragment;
111 import com.google.android.car.kitchensink.watchdog.CarWatchdogTestFragment;
112 import com.google.android.car.kitchensink.weblinks.WebLinksTestFragment;
113 
114 import java.io.FileDescriptor;
115 import java.io.PrintWriter;
116 import java.util.ArrayList;
117 import java.util.Arrays;
118 import java.util.Comparator;
119 import java.util.List;
120 import java.util.Optional;
121 
122 public class KitchenSinkActivity extends FragmentActivity implements KitchenSinkHelper {
123 
124     public static final String TAG = "KitchenSinkActivity";
125     private static final String LAST_FRAGMENT_TAG = "lastFragmentTag";
126     private static final String DEFAULT_FRAGMENT_TAG = "";
127 
128     private static final String PROPERTY_SHOW_HEADER_INFO =
129             "com.android.car.kitchensink.SHOW_HEADER_INFO";
130 
131     private RecyclerView mMenu;
132     private LinearLayout mHeader;
133     private Button mMenuButton;
134     private TextView mUserIdView;
135     private TextView mDisplayIdView;
136     private View mKitchenContent;
137     private String mLastFragmentTag = DEFAULT_FRAGMENT_TAG;
138     @Nullable
139     private Fragment mLastFragment;
140     private int mNotificationId = 1000;
141     private boolean mShowHeaderInfo;
142 
143     private final KitchenSinkHelperImpl mKsHelperImpl = new KitchenSinkHelperImpl();
144 
145     public static final String DUMP_ARG_CMD = "cmd";
146     public static final String DUMP_ARG_FRAGMENT = "fragment";
147     public static final String DUMP_ARG_QUIET = "quiet";
148     public static final String DUMP_ARG_REFRESH = "refresh";
149 
150     @Override
getCar()151     public Car getCar() {
152         return mKsHelperImpl.getCar();
153     }
154 
155     @Override
requestRefreshManager(Runnable r, Handler h)156     public void requestRefreshManager(Runnable r, Handler h) {
157         mKsHelperImpl.requestRefreshManager(r, h);
158     }
159 
160     @Override
getPropertyManager()161     public CarPropertyManager getPropertyManager() {
162         return mKsHelperImpl.getPropertyManager();
163     }
164 
165     @Override
getHvacManager()166     public CarHvacManager getHvacManager() {
167         return mKsHelperImpl.getHvacManager();
168     }
169 
170     @Override
getOccupantZoneManager()171     public CarOccupantZoneManager getOccupantZoneManager() {
172         return mKsHelperImpl.getOccupantZoneManager();
173     }
174 
175     @Override
getPowerManager()176     public CarPowerManager getPowerManager() {
177         return mKsHelperImpl.getPowerManager();
178     }
179 
180     @Override
getSensorManager()181     public CarSensorManager getSensorManager() {
182         return mKsHelperImpl.getSensorManager();
183     }
184 
185     @Override
getProjectionManager()186     public CarProjectionManager getProjectionManager() {
187         return mKsHelperImpl.getProjectionManager();
188     }
189 
190     @Override
getCarTelemetryManager()191     public CarTelemetryManager getCarTelemetryManager() {
192         return mKsHelperImpl.getCarTelemetryManager();
193     }
194 
195     @Override
getCarWatchdogManager()196     public CarWatchdogManager getCarWatchdogManager() {
197         return mKsHelperImpl.getCarWatchdogManager();
198     }
199 
200     @Override
getPerformanceManager()201     public CarPerformanceManager getPerformanceManager() {
202         return mKsHelperImpl.getPerformanceManager();
203     }
204 
205     private interface ClickHandler {
onClick()206         void onClick();
207     }
208 
209     private static abstract class MenuEntry implements ClickHandler {
getText()210         abstract String getText();
211 
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)212         public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
213             writer.printf("%s doesn't implement dump()\n", this);
214         }
215     }
216 
217     private final class OnClickMenuEntry extends MenuEntry {
218         private final String mText;
219         private final ClickHandler mClickHandler;
220 
OnClickMenuEntry(String text, ClickHandler clickHandler)221         OnClickMenuEntry(String text, ClickHandler clickHandler) {
222             mText = text;
223             mClickHandler = clickHandler;
224         }
225 
226         @Override
getText()227         String getText() {
228             return mText;
229         }
230 
231         @Override
onClick()232         public void onClick() {
233             toggleMenuVisibility();
234             mClickHandler.onClick();
235         }
236     }
237 
238     private final class FragmentMenuEntry<T extends Fragment> extends MenuEntry {
239         private final class FragmentClassOrInstance<T extends Fragment> {
240             final Class<T> mClazz;
241             T mFragment = null;
242 
FragmentClassOrInstance(Class<T> clazz)243             FragmentClassOrInstance(Class<T> clazz) {
244                 mClazz = clazz;
245             }
246 
getFragment()247             T getFragment() {
248                 if (mFragment == null) {
249                     try {
250                         mFragment = mClazz.newInstance();
251                     } catch (InstantiationException | IllegalAccessException e) {
252                         Log.e(TAG, "unable to create fragment", e);
253                     }
254                 }
255                 return mFragment;
256             }
257         }
258 
259         private final String mText;
260         private final FragmentClassOrInstance<T> mFragment;
261 
FragmentMenuEntry(String text, Class<T> clazz)262         FragmentMenuEntry(String text, Class<T> clazz) {
263             mText = text;
264             mFragment = new FragmentClassOrInstance<>(clazz);
265         }
266 
267         @Override
getText()268         String getText() {
269             return mText;
270         }
271 
272         @Override
onClick()273         public void onClick() {
274             Fragment fragment = mFragment.getFragment();
275             if (fragment != null) {
276                 KitchenSinkActivity.this.showFragment(fragment);
277                 toggleMenuVisibility();
278                 mLastFragmentTag = fragment.getTag();
279             } else {
280                 Log.e(TAG, "cannot show fragment for " + getText());
281             }
282         }
283 
284         @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)285         public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
286             Fragment fragment = mFragment.getFragment();
287             if (fragment != null) {
288                 fragment.dump(prefix, fd, writer, args);
289             } else {
290                 writer.printf("Cannot dump %s\n", getText());
291             }
292         }
293     }
294 
295     private final List<MenuEntry> mMenuEntries = new ArrayList<>();
296 
297     public static final List<Pair<String, Class>> MENU_ENTRIES = Arrays.asList(
298             new Pair<>("activity resolver", ActivityResolverFragment.class),
299             new Pair<>("alert window", AlertDialogTestFragment.class),
300             new Pair<>("assistant", CarAssistantFragment.class),
301             new Pair<>(AudioTestFragment.FRAGMENT_NAME, AudioTestFragment.class),
302             new Pair<>(AudioUserAssignmentFragment.FRAGMENT_NAME,
303                     AudioUserAssignmentFragment.class),
304             new Pair<>(AudioConfigurationTestFragment.FRAGMENT_NAME,
305                     AudioConfigurationTestFragment.class),
306             new Pair<>(AudioRecorderTestFragment.FRAGMENT_NAME,
307                     AudioRecorderTestFragment.class),
308             new Pair<>(CarAudioInputTestFragment.FRAGMENT_NAME,
309                     CarAudioInputTestFragment.class),
310             new Pair<>(AudioMirrorTestFragment.FRAGMENT_NAME,
311                     AudioMirrorTestFragment.class),
312             new Pair<>("Hotword", CarMultiConcurrentHotwordTestFragment.class),
313             new Pair<>("B&R", BackupAndRestoreFragment.class),
314             new Pair<>("BT headset", BluetoothHeadsetFragment.class),
315             new Pair<>("BT messaging", MapMceTestFragment.class),
316             new Pair<>("BT Uuids", BluetoothUuidFragment.class),
317             new Pair<>(BiometricPromptTestFragment.FRAGMENT_NAME,
318                     BiometricPromptTestFragment.class),
319             new Pair<>("carapi", CarApiTestFragment.class),
320             new Pair<>("carboard", KeyboardTestFragment.class),
321             new Pair<>("connectivity", ConnectivityFragment.class),
322             new Pair<>("cubes test", CubesTestFragment.class),
323             new Pair<>("customization tool", CustomizationToolFragment.class),
324             new Pair<>("device policy", DevicePolicyFragment.class),
325             new Pair<>("diagnostic", DiagnosticTestFragment.class),
326             new Pair<>("display info", DisplayInfoFragment.class),
327             new Pair<>("display input lock", DisplayInputLockTestFragment.class),
328             new Pair<>("display mirroring", DisplayMirroringFragment.class),
329             new Pair<>("drive mode switch", DriveModeSwitchFragment.class),
330             new Pair<>("experimental feature", ExperimentalFeatureTestFragment.class),
331             new Pair<>("hvac", HvacTestFragment.class),
332             new Pair<>("inst cluster", InstrumentClusterFragment.class),
333             new Pair<>("mainline", CarMainlineFragment.class),
334             new Pair<>("MD media", MultidisplayMediaFragment.class),
335             new Pair<>("notification", NotificationFragment.class),
336             new Pair<>("orientation test", OrientationTestFragment.class),
337             new Pair<>("package info", PackageInfoFragment.class),
338             new Pair<>("performance", CarPerformanceTestFragment.class),
339             new Pair<>("power test", PowerTestFragment.class),
340             new Pair<>(PrivacyIndicatorFragment.FRAGMENT_NAME,
341                     PrivacyIndicatorFragment.class),
342             new Pair<>("profile_user", ProfileUserFragment.class),
343             new Pair<>("projection", ProjectionFragment.class),
344             new Pair<>("property test", PropertyTestFragment.class),
345             new Pair<>("qc viewer", QCViewerFragment.class),
346             new Pair<>("remote access", RemoteAccessTestFragment.class),
347             new Pair<>("rotary", RotaryFragment.class),
348             new Pair<>("sensors", SensorsTestFragment.class),
349             new Pair<>("storage lifetime", StorageLifetimeFragment.class),
350             new Pair<>("storage volumes", StorageVolumesFragment.class),
351             new Pair<>("system bars", SystemBarsFragment.class),
352             new Pair<>("system features", SystemFeaturesFragment.class),
353             new Pair<>("telemetry", CarTelemetryTestFragment.class),
354             new Pair<>("touch test", TouchTestFragment.class),
355             new Pair<>("users", UserFragment.class),
356             new Pair<>("user restrictions", UserRestrictionsFragment.class),
357             new Pair<>("vehicle ctrl", VehicleCtrlFragment.class),
358             new Pair<>(VirtualDisplayFragment.FRAGMENT_NAME,
359                     VirtualDisplayFragment.class),
360             new Pair<>("volume test", VolumeTestFragment.class),
361             new Pair<>("watchdog", CarWatchdogTestFragment.class),
362             new Pair<>("web links", WebLinksTestFragment.class),
363             new Pair<>("inject motion", InjectMotionTestFragment.class),
364             new Pair<>("inject key", InjectKeyTestFragment.class),
365             new Pair<>("window insets full screen",
366                     WindowInsetsFullScreenFragment.class),
367             new Pair<>("oem car service", OemCarServiceTestFragment.class),
368             new Pair<>("Camera2", Camera2TestFragment.class),
369             new Pair<>(RadioTestFragment.FRAGMENT_NAME, RadioTestFragment.class));
370 
KitchenSinkActivity()371     public KitchenSinkActivity() {
372         for (Pair<String, Class> entry : MENU_ENTRIES) {
373             mMenuEntries.add(new FragmentMenuEntry(entry.first, entry.second));
374         }
375         mMenuEntries.sort(Comparator.comparing(MenuEntry::getText));
376     }
377 
378 
379     /* Open any tab directly:
380      * adb shell am force-stop com.google.android.car.kitchensink
381      * adb shell am 'start -n com.google.android.car.kitchensink/.KitchenSinkActivity \
382      *     --es select "connectivity"'
383      *
384      * Test car watchdog:
385      * adb shell am force-stop com.google.android.car.kitchensink
386      * adb shell am start -n com.google.android.car.kitchensink/.KitchenSinkActivity \
387      *     --es "watchdog" "[timeout] [not_respond_after] [inactive_main_after] [verbose]"
388      * - timeout: critical | moderate | normal
389      * - not_respond_after: after the given seconds, the client will not respond to car watchdog
390      *                      (-1 for making the client respond always)
391      * - inactive_main_after: after the given seconds, the main thread will not be responsive
392      *                        (-1 for making the main thread responsive always)
393      * - verbose: whether to output verbose logs (default: false)
394      */
395     @Override
onNewIntent(Intent intent)396     protected void onNewIntent(Intent intent) {
397         super.onNewIntent(intent);
398         Log.i(TAG, "onNewIntent");
399         if (intent.getCategories() != null
400                 && intent.getCategories().contains(
401                         NotificationFragment.INTENT_CATEGORY_SELF_DISMISS)) {
402             NotificationManager nm = this.getSystemService(NotificationManager.class);
403             nm.cancel(NotificationFragment.SELF_DISMISS_NOTIFICATION_ID);
404         }
405         Bundle extras = intent.getExtras();
406         if (extras == null) {
407             return;
408         }
409         String watchdog = extras.getString("watchdog");
410         if (watchdog != null) {
411             CarWatchdogClient.start(getCar(), watchdog);
412         }
413         String select = extras.getString("select");
414         if (select != null) {
415             Log.d(TAG, "Trying to launch entry '" + select + "'");
416             mMenuEntries.stream().filter(me -> select.equals(me.getText()))
417                     .findAny().ifPresent(me -> me.onClick());
418         }
419     }
420 
421     @Override
onCreate(Bundle savedInstanceState)422     protected void onCreate(Bundle savedInstanceState) {
423         super.onCreate(savedInstanceState);
424         setContentView(R.layout.kitchen_activity);
425 
426         findViewById(R.id.root).setOnApplyWindowInsetsListener((v, insets) -> {
427             final android.graphics.Insets i = insets.getSystemWindowInsets();
428             v.setPadding(i.left, i.top, i.right, i.bottom);
429             return insets.inset(i).consumeSystemWindowInsets();
430         });
431 
432         mKsHelperImpl.initCarApiIfAutomotive(this);
433 
434         mKitchenContent = findViewById(R.id.kitchen_content);
435 
436         mMenu = findViewById(R.id.menu);
437         mMenu.setAdapter(new MenuAdapter(this));
438         mMenu.setLayoutManager(new GridLayoutManager(this, 4));
439 
440         mMenuButton = findViewById(R.id.menu_button);
441         mMenuButton.setOnClickListener(view -> toggleMenuVisibility());
442 
443         ((Button) findViewById(R.id.new_version_button)).setOnClickListener(
444                 view -> goToNewVersion());
445         ((Button) findViewById(R.id.finish_button)).setOnClickListener(view -> finish());
446         ((Button) findViewById(R.id.home_button)).setOnClickListener(view -> launchHome());
447 
448         mHeader = findViewById(R.id.header);
449 
450         int userId = getUserId();
451         int displayId = getDisplayId();
452 
453         mUserIdView = findViewById(R.id.user_id);
454         mDisplayIdView = findViewById(R.id.display_id);
455         mUserIdView.setText("U#" + userId);
456         mDisplayIdView.setText("D#" + displayId);
457 
458         Log.i(TAG, "onCreate: userId=" + userId + ", displayId=" + displayId);
459         onNewIntent(getIntent());
460     }
461 
462     @Override
onRestoreInstanceState(Bundle savedInstanceState)463     protected void onRestoreInstanceState(Bundle savedInstanceState) {
464         super.onRestoreInstanceState(savedInstanceState);
465         // The app is being started for the first time.
466         if (savedInstanceState == null) {
467             return;
468         }
469 
470         // The app is being reloaded, restores the last fragment UI.
471         mLastFragmentTag = savedInstanceState.getString(LAST_FRAGMENT_TAG);
472         if (!DEFAULT_FRAGMENT_TAG.equals(mLastFragmentTag)) {
473             toggleMenuVisibility();
474         }
475     }
476 
toggleMenuVisibility()477     private void toggleMenuVisibility() {
478         boolean menuVisible = mMenu.getVisibility() == View.VISIBLE;
479         mMenu.setVisibility(menuVisible ? View.GONE : View.VISIBLE);
480         int contentVisibility = menuVisible ? View.VISIBLE : View.GONE;
481         mKitchenContent.setVisibility(contentVisibility);
482         mMenuButton.setText(menuVisible ? "Show KitchenSink Menu" : "Hide KitchenSink Menu");
483         if (mLastFragment != null) {
484             mLastFragment.onHiddenChanged(!menuVisible);
485         }
486     }
487 
488     /**
489      * Goes to the newer version of Kitchen Sink App.
490      */
goToNewVersion()491     private void goToNewVersion() {
492         Intent intent = new Intent(this, KitchenSink2Activity.class);
493         startActivity(intent);
494     }
495 
496     /**
497      * Sets the visibility of the header that's shown on all fragments.
498      */
setHeaderVisibility(boolean visible)499     public void setHeaderVisibility(boolean visible) {
500         mHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
501     }
502 
503     /**
504      * Adds a view to the main header (which by default contains the "show/ hide KS menu" button).
505      */
addHeaderView(View view)506     public void addHeaderView(View view) {
507         Log.d(TAG, "Adding header view: " + view);
508         mHeader.addView(view, new ViewGroup.LayoutParams(
509                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
510     }
511 
launchHome()512     private void launchHome() {
513         Intent homeIntent = new Intent(Intent.ACTION_MAIN);
514         homeIntent.addCategory(Intent.CATEGORY_HOME);
515         startActivity(homeIntent);
516     }
517 
518     @Override
onStart()519     protected void onStart() {
520         super.onStart();
521         Log.i(TAG, "onStart");
522     }
523 
524     @Override
onRestart()525     protected void onRestart() {
526         super.onRestart();
527         Log.i(TAG, "onRestart");
528     }
529 
530     @Override
onResume()531     protected void onResume() {
532         super.onResume();
533         Log.i(TAG, "onResume");
534 
535         updateHeaderInfoVisibility();
536     }
537 
538     @Override
onSaveInstanceState(Bundle outState)539     protected void onSaveInstanceState(Bundle outState) {
540         outState.putString(LAST_FRAGMENT_TAG, mLastFragmentTag);
541         super.onSaveInstanceState(outState);
542     }
543 
544     @Override
onPause()545     protected void onPause() {
546         super.onPause();
547         Log.i(TAG, "onPause");
548     }
549 
550     @Override
onStop()551     protected void onStop() {
552         super.onStop();
553         Log.i(TAG, "onStop");
554     }
555 
556     @Override
onDestroy()557     protected void onDestroy() {
558         mKsHelperImpl.disconnect();
559         Log.i(TAG, "onDestroy");
560         super.onDestroy();
561     }
562 
563     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)564     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
565         boolean skipParentState = false;
566         if (args != null && args.length > 0) {
567             Log.v(TAG, "dump: args=" + Arrays.toString(args));
568             String arg = args[0];
569             switch (arg) {
570                 case DUMP_ARG_CMD:
571                     String[] cmdArgs = new String[args.length - 1];
572                     System.arraycopy(args, 1, cmdArgs, 0, args.length - 1);
573                     new KitchenSinkShellCommand(this, writer, cmdArgs, mNotificationId++).run();
574                     return;
575                 case DUMP_ARG_FRAGMENT:
576                     if (args.length < 2) {
577                         writer.println("Missing fragment name");
578                         return;
579                     }
580                     String select = args[1];
581                     Optional<MenuEntry> entry = mMenuEntries.stream()
582                             .filter(me -> select.equals(me.getText())).findAny();
583                     if (entry.isPresent()) {
584                         String[] strippedArgs = new String[args.length - 2];
585                         System.arraycopy(args, 2, strippedArgs, 0, strippedArgs.length);
586                         entry.get().dump(prefix, fd, writer, strippedArgs);
587                     } else {
588                         writer.printf("No entry called '%s'\n", select);
589                     }
590                     return;
591                 case DUMP_ARG_QUIET:
592                     skipParentState = true;
593                     break;
594                 case DUMP_ARG_REFRESH:
595                     updateHeaderInfoVisibility(writer);
596                     return;
597                 default:
598                     Log.v(TAG, "dump(): unknown arg, calling super(): " + Arrays.toString(args));
599             }
600         }
601         String innerPrefix = prefix;
602         if (!skipParentState) {
603             writer.printf("%sCustom state:\n", prefix);
604             innerPrefix = prefix + prefix;
605         }
606         writer.printf("%smLastFragmentTag: %s\n", innerPrefix, mLastFragmentTag);
607         writer.printf("%smLastFragment: %s\n", innerPrefix, mLastFragment);
608         writer.printf("%sHeader views: %d\n", innerPrefix, mHeader.getChildCount());
609         writer.printf("%sNext Notification Id: %d\n", innerPrefix, mNotificationId);
610         writer.printf("%sShow header info: %b\n", innerPrefix, mShowHeaderInfo);
611 
612         if (skipParentState) {
613             Log.v(TAG, "dump(): skipping parent state");
614             return;
615         }
616         writer.println();
617 
618         super.dump(prefix, fd, writer, args);
619     }
620 
showFragment(Fragment fragment)621     private void showFragment(Fragment fragment) {
622         if (mLastFragment != fragment) {
623             Log.v(TAG, "showFragment(): from " + mLastFragment + " to " + fragment);
624         } else {
625             Log.v(TAG, "showFragment(): showing " + fragment + " again");
626         }
627         getSupportFragmentManager().beginTransaction()
628                 .replace(R.id.kitchen_content, fragment)
629                 .commit();
630         mLastFragment = fragment;
631     }
632 
updateHeaderInfoVisibility()633     private void updateHeaderInfoVisibility() {
634         mShowHeaderInfo = getBooleanProperty(PROPERTY_SHOW_HEADER_INFO, false);
635         Log.i(TAG, "updateHeaderInfoVisibility(): showHeaderInfo=" + mShowHeaderInfo);
636         int visibility = mShowHeaderInfo ? View.VISIBLE : View.GONE;
637         mUserIdView.setVisibility(visibility);
638         mDisplayIdView.setVisibility(visibility);
639     }
640 
updateHeaderInfoVisibility(PrintWriter writer)641     private void updateHeaderInfoVisibility(PrintWriter writer) {
642         boolean before = mShowHeaderInfo;
643         updateHeaderInfoVisibility();
644         boolean after = mShowHeaderInfo;
645         writer.printf("Updated header info visibility from %b to %b\n", before, after);
646     }
647 
648     private final class MenuAdapter extends RecyclerView.Adapter<ItemViewHolder> {
649 
650         private final LayoutInflater mLayoutInflator;
651 
MenuAdapter(Context context)652         MenuAdapter(Context context) {
653             mLayoutInflator = LayoutInflater.from(context);
654         }
655 
656         @Override
onCreateViewHolder(ViewGroup parent, int viewType)657         public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
658             View view = mLayoutInflator.inflate(R.layout.menu_item, parent, false);
659             return new ItemViewHolder(view);
660         }
661 
662         @Override
onBindViewHolder(ItemViewHolder holder, int position)663         public void onBindViewHolder(ItemViewHolder holder, int position) {
664             holder.mTitle.setText(mMenuEntries.get(position).getText());
665             holder.mTitle.setOnClickListener(v -> mMenuEntries.get(position).onClick());
666         }
667 
668         @Override
getItemCount()669         public int getItemCount() {
670             return mMenuEntries.size();
671         }
672     }
673 
674     private final class ItemViewHolder extends RecyclerView.ViewHolder {
675         TextView mTitle;
676 
ItemViewHolder(View itemView)677         ItemViewHolder(View itemView) {
678             super(itemView);
679             mTitle = itemView.findViewById(R.id.title);
680         }
681     }
682 
getBooleanProperty(String prop, boolean defaultValue)683     private static boolean getBooleanProperty(String prop, boolean defaultValue) {
684         String value = SystemProperties.get(prop);
685         Log.v(TAG, "getBooleanProperty(" + prop + "): got '" + value + "'");
686         if (!TextUtils.isEmpty(value)) {
687             boolean finalValue = Boolean.valueOf(value);
688             Log.v(TAG, "returning " + finalValue);
689             return finalValue;
690         }
691         String persistProp = "persist." + prop;
692         boolean finalValue = SystemProperties.getBoolean(persistProp, defaultValue);
693         Log.v(TAG, "getBooleanProperty(" + persistProp + "): returning " + finalValue);
694         return finalValue;
695     }
696 }
697