• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.android.car.ui.paintbooth;
18 
19 import static com.android.car.ui.paintbooth.PaintBoothApplication.SHARED_PREFERENCES_FILE;
20 import static com.android.car.ui.paintbooth.PaintBoothApplication.SHARED_PREFERENCES_SHARED_LIB_DENYLIST;
21 
22 import android.app.Activity;
23 import android.app.ActivityManager;
24 import android.app.Service;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.os.Bundle;
28 import android.util.Log;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.Button;
33 import android.widget.CompoundButton;
34 import android.widget.CompoundButton.OnCheckedChangeListener;
35 import android.widget.Switch;
36 import android.widget.Toast;
37 
38 import androidx.annotation.NonNull;
39 import androidx.core.util.Supplier;
40 import androidx.recyclerview.widget.RecyclerView;
41 
42 import com.android.car.ui.FocusArea;
43 import com.android.car.ui.baselayout.Insets;
44 import com.android.car.ui.baselayout.InsetsChangedListener;
45 import com.android.car.ui.core.CarUi;
46 import com.android.car.ui.paintbooth.appstyledview.AppStyledViewSampleActivity;
47 import com.android.car.ui.paintbooth.caruirecyclerview.CarUiListItemActivity;
48 import com.android.car.ui.paintbooth.caruirecyclerview.CarUiRecyclerViewActivity;
49 import com.android.car.ui.paintbooth.caruirecyclerview.GridCarUiRecyclerViewActivity;
50 import com.android.car.ui.paintbooth.currentactivity.CurrentActivityService;
51 import com.android.car.ui.paintbooth.dialogs.DialogsActivity;
52 import com.android.car.ui.paintbooth.overlays.OverlayActivity;
53 import com.android.car.ui.paintbooth.preferences.PreferenceActivity;
54 import com.android.car.ui.paintbooth.preferences.SplitPreferenceActivity;
55 import com.android.car.ui.paintbooth.toolbar.NoCarUiToolbarActivity;
56 import com.android.car.ui.paintbooth.toolbar.ToolbarActivity;
57 import com.android.car.ui.paintbooth.widescreenime.WideScreenImeActivity;
58 import com.android.car.ui.paintbooth.widescreenime.WideScreenTestView;
59 import com.android.car.ui.paintbooth.widgets.WidgetActivity;
60 import com.android.car.ui.recyclerview.CarUiRecyclerView;
61 import com.android.car.ui.toolbar.ToolbarController;
62 
63 import java.lang.reflect.Method;
64 import java.util.Arrays;
65 import java.util.Collections;
66 import java.util.List;
67 
68 /**
69  * Paint booth app
70  */
71 public class MainActivity extends Activity implements InsetsChangedListener {
72 
73     public static final String STOP_SERVICE = "com.android.car.ui.paintbooth.StopService";
74 
75     /**
76      * List of all sample activities.
77      */
78     private final List<ListElement> mActivities = Arrays.asList(
79             new ServiceElement("Show foreground activities", CurrentActivityService.class),
80             new ServiceElement("Simulate Screen Bounds", VisibleBoundsSimulator.class),
81             new SwitchElement("Enable shared library", this::isSharedLibEnabled,
82                     this::onSharedLibSwitchChanged),
83             new ActivityElement("Dialogs sample", DialogsActivity.class),
84             new ActivityElement("App Styled View Modal", AppStyledViewSampleActivity.class),
85             new ActivityElement("List sample", CarUiRecyclerViewActivity.class),
86             new ActivityElement("Grid sample", GridCarUiRecyclerViewActivity.class),
87             new ActivityElement("Preferences sample", PreferenceActivity.class),
88             new ActivityElement("Split preferences sample", SplitPreferenceActivity.class),
89             new ActivityElement("Overlays", OverlayActivity.class),
90             new ActivityElement("Toolbar sample", ToolbarActivity.class),
91             new ActivityElement("No CarUiToolbar sample", NoCarUiToolbarActivity.class),
92             new ActivityElement("Widget sample", WidgetActivity.class),
93             new ActivityElement("Wide Screen IME", WideScreenImeActivity.class),
94             new ActivityElement("Wide Screen View IME", WideScreenTestView.class),
95             new ActivityElement("ListItem sample", CarUiListItemActivity.class));
96 
97     private abstract static class ViewHolder extends RecyclerView.ViewHolder {
98 
ViewHolder(@onNull View itemView)99         ViewHolder(@NonNull View itemView) {
100             super(itemView);
101         }
102 
bind(ListElement element)103         public abstract void bind(ListElement element);
104     }
105 
106     private class ActivityViewHolder extends ViewHolder {
107         private final Button mButton;
108 
ActivityViewHolder(@onNull View itemView)109         ActivityViewHolder(@NonNull View itemView) {
110             super(itemView);
111             mButton = itemView.requireViewById(R.id.button);
112         }
113 
114         @Override
bind(ListElement e)115         public void bind(ListElement e) {
116             if (!(e instanceof ActivityElement)) {
117                 throw new IllegalArgumentException("Expected an ActivityElement");
118             }
119             ActivityElement element = (ActivityElement) e;
120             mButton.setText(element.getText());
121             mButton.setOnClickListener(v ->
122                     startActivity(new Intent(itemView.getContext(), element.getActivity())));
123         }
124     }
125 
126     private static class SwitchViewHolder extends ViewHolder {
127         private final Switch mSwitch;
128 
SwitchViewHolder(@onNull View itemView)129         SwitchViewHolder(@NonNull View itemView) {
130             super(itemView);
131             mSwitch = itemView.requireViewById(R.id.button);
132         }
133 
134         @Override
bind(ListElement e)135         public void bind(ListElement e) {
136             if (!(e instanceof SwitchElement)) {
137                 throw new IllegalArgumentException("Expected an ActivityElement");
138             }
139             SwitchElement element = (SwitchElement) e;
140             mSwitch.setChecked(element.isChecked());
141             mSwitch.setText(element.getText());
142             mSwitch.setOnCheckedChangeListener(element.getOnCheckedChangedListener());
143         }
144     }
145 
146     private final RecyclerView.Adapter<ViewHolder> mAdapter =
147             new RecyclerView.Adapter<ViewHolder>() {
148                 @NonNull
149                 @Override
150                 public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
151                     LayoutInflater inflater = LayoutInflater.from(MainActivity.this);
152                     if (viewType == ListElement.TYPE_ACTIVITY) {
153                         return new ActivityViewHolder(
154                                 inflater.inflate(R.layout.list_item, parent, false));
155                     } else if (viewType == ListElement.TYPE_SWITCH) {
156                         return new SwitchViewHolder(
157                                 inflater.inflate(R.layout.list_item_switch, parent, false));
158                     } else {
159                         throw new IllegalArgumentException("Unknown viewType: " + viewType);
160                     }
161                 }
162 
163                 @Override
164                 public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
165                     holder.bind(mActivities.get(position));
166                 }
167 
168                 @Override
169                 public int getItemCount() {
170                     return mActivities.size();
171                 }
172 
173                 @Override
174                 public int getItemViewType(int position) {
175                     return mActivities.get(position).getType();
176                 }
177             };
178 
179     @Override
onCreate(Bundle savedInstanceState)180     protected void onCreate(Bundle savedInstanceState) {
181         super.onCreate(savedInstanceState);
182         setContentView(R.layout.car_ui_recycler_view_activity);
183 
184         ToolbarController toolbar = CarUi.requireToolbar(this);
185         toolbar.setLogo(R.drawable.ic_launcher);
186         toolbar.setTitle(getTitle());
187 
188         CarUiRecyclerView prv = findViewById(R.id.list);
189         prv.setAdapter(mAdapter);
190 
191         initLeakCanary();
192     }
193 
initLeakCanary()194     private void initLeakCanary() {
195         // This sets LeakCanary to report errors after a single leak instead of 5, and to ask for
196         // permission to use storage, which it needs to work.
197         //
198         // Equivalent to this non-reflection code:
199         //
200         // Config config = LeakCanary.INSTANCE.getConfig();
201         // LeakCanary.INSTANCE.setConfig(config.copy(config.getDumpHeap(),
202         //     config.getDumpHeapWhenDebugging(),
203         //     1,
204         //     config.getReferenceMatchers(),
205         //     config.getObjectInspectors(),
206         //     config.getOnHeapAnalyzedListener(),
207         //     config.getMetatadaExtractor(),
208         //     config.getComputeRetainedHeapSize(),
209         //     config.getMaxStoredHeapDumps(),
210         //     true,
211         //     config.getUseExperimentalLeakFinders()));
212         try {
213             Class<?> canaryClass = Class.forName("leakcanary.LeakCanary");
214             try {
215                 Class<?> onHeapAnalyzedListenerClass =
216                         Class.forName("leakcanary.OnHeapAnalyzedListener");
217                 Class<?> metadataExtractorClass = Class.forName("shark.MetadataExtractor");
218                 Method getConfig = canaryClass.getMethod("getConfig");
219                 Class<?> configClass = getConfig.getReturnType();
220                 Method setConfig = canaryClass.getMethod("setConfig", configClass);
221                 Method copy = configClass.getMethod("copy", boolean.class, boolean.class,
222                         int.class, List.class, List.class, onHeapAnalyzedListenerClass,
223                         metadataExtractorClass, boolean.class, int.class, boolean.class,
224                         boolean.class);
225 
226                 Object canary = canaryClass.getField("INSTANCE").get(null);
227                 Object currentConfig = getConfig.invoke(canary);
228 
229                 Boolean dumpHeap = (Boolean) configClass
230                         .getMethod("getDumpHeap").invoke(currentConfig);
231                 Boolean dumpHeapWhenDebugging = (Boolean) configClass
232                         .getMethod("getDumpHeapWhenDebugging").invoke(currentConfig);
233                 List<?> referenceMatchers = (List<?>) configClass
234                         .getMethod("getReferenceMatchers").invoke(currentConfig);
235                 List<?> objectInspectors = (List<?>) configClass
236                         .getMethod("getObjectInspectors").invoke(currentConfig);
237                 Object onHeapAnalyzedListener = configClass
238                         .getMethod("getOnHeapAnalyzedListener").invoke(currentConfig);
239                 // Yes, LeakCanary misspelled metadata
240                 Object metadataExtractor = configClass
241                         .getMethod("getMetatadaExtractor").invoke(currentConfig);
242                 Boolean computeRetainedHeapSize = (Boolean) configClass
243                         .getMethod("getComputeRetainedHeapSize").invoke(currentConfig);
244                 Integer maxStoredHeapDumps = (Integer) configClass
245                         .getMethod("getMaxStoredHeapDumps").invoke(currentConfig);
246                 Boolean useExperimentalLeakFinders = (Boolean) configClass
247                         .getMethod("getUseExperimentalLeakFinders").invoke(currentConfig);
248 
249                 setConfig.invoke(canary, copy.invoke(currentConfig,
250                         dumpHeap,
251                         dumpHeapWhenDebugging,
252                         1,
253                         referenceMatchers,
254                         objectInspectors,
255                         onHeapAnalyzedListener,
256                         metadataExtractor,
257                         computeRetainedHeapSize,
258                         maxStoredHeapDumps,
259                         true,
260                         useExperimentalLeakFinders));
261 
262             } catch (ReflectiveOperationException e) {
263                 Log.e("paintbooth", "Error initializing LeakCanary", e);
264                 Toast.makeText(this, "Error initializing LeakCanary", Toast.LENGTH_LONG).show();
265             }
266         } catch (ClassNotFoundException e) {
267             // LeakCanary is not used in this build, do nothing.
268         }
269     }
270 
isServiceRunning(Class<? extends Service> serviceClazz)271     private boolean isServiceRunning(Class<? extends Service> serviceClazz) {
272         ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
273         for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(
274                 Integer.MAX_VALUE)) {
275             if (serviceClazz.getName().equals(service.service.getClassName())) {
276                 return true;
277             }
278         }
279         return false;
280     }
281 
setServiceRunning(Class<? extends Service> serviceClass, boolean running)282     private void setServiceRunning(Class<? extends Service> serviceClass, boolean running) {
283         Intent intent = new Intent(this, serviceClass);
284         if (!running) {
285             intent.setAction(STOP_SERVICE);
286         }
287         startForegroundService(intent);
288     }
289 
isSharedLibEnabled()290     private boolean isSharedLibEnabled() {
291         return getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE)
292                 .getStringSet(SHARED_PREFERENCES_SHARED_LIB_DENYLIST, null) == null;
293     }
294 
onSharedLibSwitchChanged(CompoundButton unused, boolean checked)295     private void onSharedLibSwitchChanged(CompoundButton unused, boolean checked) {
296         getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE)
297                 .edit()
298                 .putStringSet(SHARED_PREFERENCES_SHARED_LIB_DENYLIST,
299                         checked ? null : Collections.singleton("com.chassis.car.ui.sharedlibrary"))
300                 .apply();
301         Toast.makeText(this, "Relaunch PaintBooth to see effects", Toast.LENGTH_SHORT).show();
302     }
303 
304     @Override
onCarUiInsetsChanged(@onNull Insets insets)305     public void onCarUiInsetsChanged(@NonNull Insets insets) {
306         FocusArea focusArea = requireViewById(R.id.focus_area);
307         focusArea.setBoundsOffset(0, insets.getTop(), 0, insets.getBottom());
308         focusArea.setHighlightPadding(0, insets.getTop(), 0, insets.getBottom());
309         requireViewById(R.id.list)
310                 .setPadding(0, insets.getTop(), 0, insets.getBottom());
311         requireViewById(android.R.id.content)
312                 .setPadding(insets.getLeft(), 0, insets.getRight(), 0);
313     }
314 
315     private abstract static class ListElement {
316         static final int TYPE_ACTIVITY = 0;
317         static final int TYPE_SWITCH = 1;
318 
319         private final String mText;
320 
ListElement(String text)321         ListElement(String text) {
322             mText = text;
323         }
324 
getText()325         String getText() {
326             return mText;
327         }
328 
getType()329         abstract int getType();
330     }
331 
332     private static class ActivityElement extends ListElement {
333         private final Class<? extends Activity> mActivityClass;
334 
ActivityElement(String text, Class<? extends Activity> activityClass)335         ActivityElement(String text, Class<? extends Activity> activityClass) {
336             super(text);
337             mActivityClass = activityClass;
338         }
339 
getActivity()340         Class<? extends Activity> getActivity() {
341             return mActivityClass;
342         }
343 
344         @Override
getType()345         int getType() {
346             return TYPE_ACTIVITY;
347         }
348     }
349 
350     private static class SwitchElement extends ListElement {
351         private final Supplier<Boolean> mIsCheckedSupplier;
352         private final OnCheckedChangeListener mOnCheckedChanged;
353 
SwitchElement(String text, Supplier<Boolean> isCheckedSupplier, OnCheckedChangeListener onCheckedChanged)354         private SwitchElement(String text, Supplier<Boolean> isCheckedSupplier,
355                 OnCheckedChangeListener onCheckedChanged) {
356             super(text);
357             mIsCheckedSupplier = isCheckedSupplier;
358             mOnCheckedChanged = onCheckedChanged;
359         }
360 
isChecked()361         public boolean isChecked() {
362             return mIsCheckedSupplier.get();
363         }
364 
getOnCheckedChangedListener()365         public OnCheckedChangeListener getOnCheckedChangedListener() {
366             return mOnCheckedChanged;
367         }
368 
369         @Override
getType()370         int getType() {
371             return TYPE_SWITCH;
372         }
373     }
374 
375     private class ServiceElement extends SwitchElement {
ServiceElement(String text, Class<? extends Service> serviceClass)376         ServiceElement(String text, Class<? extends Service> serviceClass) {
377             super(text,
378                     () -> isServiceRunning(serviceClass),
379                     (v, checked) -> setServiceRunning(serviceClass, checked));
380         }
381     }
382 }
383