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 android.theme.app;
18 
19 import android.app.Activity;
20 import android.content.Intent;
21 import android.os.Build;
22 import android.os.Bundle;
23 import android.theme.app.modifiers.DatePickerModifier;
24 import android.theme.app.modifiers.ProgressBarModifier;
25 import android.theme.app.modifiers.SearchViewModifier;
26 import android.theme.app.modifiers.TimePickerModifier;
27 import android.theme.app.modifiers.ViewCheckedModifier;
28 import android.theme.app.modifiers.ViewPressedModifier;
29 import android.util.Log;
30 import android.view.View;
31 import android.view.WindowManager.LayoutParams;
32 import android.widget.DatePicker;
33 
34 import java.io.File;
35 import java.lang.Override;
36 
37 /**
38  * A activity which display various UI elements with non-modifiable themes.
39  */
40 public class ThemeDeviceActivity extends Activity {
41     public static final String EXTRA_THEME = "theme";
42     public static final String EXTRA_OUTPUT_DIR = "outputDir";
43 
44     private static final String TAG = "ThemeDeviceActivity";
45 
46     /**
47      * The duration of the CalendarView adjustment to settle to its final
48      * position.
49      */
50     private static final long CALENDAR_VIEW_ADJUSTMENT_DURATION = 540;
51 
52     private Theme mTheme;
53     private ReferenceViewGroup mViewGroup;
54     private File mOutputDir;
55     private int mLayoutIndex;
56     private boolean mIsRunning;
57 
58     @Override
onCreate(Bundle icicle)59     protected void onCreate(Bundle icicle) {
60         super.onCreate(icicle);
61 
62         final Intent intent = getIntent();
63         final int themeIndex = intent.getIntExtra(EXTRA_THEME, -1);
64         if (themeIndex < 0) {
65             Log.e(TAG, "No theme specified");
66             finish();
67         }
68 
69         final String outputDir = intent.getStringExtra(EXTRA_OUTPUT_DIR);
70         if (outputDir == null) {
71             Log.e(TAG, "No output directory specified");
72             finish();
73         }
74 
75         mOutputDir = new File(outputDir);
76         mTheme = THEMES[themeIndex];
77 
78         setTheme(mTheme.id);
79         setContentView(R.layout.theme_test);
80 
81         mViewGroup = (ReferenceViewGroup) findViewById(R.id.reference_view_group);
82 
83         getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON
84                 | LayoutParams.FLAG_TURN_SCREEN_ON
85                 | LayoutParams.FLAG_DISMISS_KEYGUARD );
86     }
87 
88     @Override
onResume()89     protected void onResume() {
90         super.onResume();
91 
92         mIsRunning = true;
93 
94         setNextLayout();
95     }
96 
97     @Override
onPause()98     protected void onPause() {
99         mIsRunning = false;
100 
101         if (!isFinishing()) {
102             // The activity paused for some reason, likely a system crash
103             // dialog. Finish it so we can move to the next theme.
104             Log.w(TAG, "onPause() called without a call to finish()", new RuntimeException());
105             finish();
106         }
107 
108         super.onPause();
109     }
110 
111     @Override
onDestroy()112     protected void onDestroy() {
113         if (mLayoutIndex < LAYOUTS.length) {
114             Log.e(TAG, "Not all layouts got rendered: " + mLayoutIndex);
115             setResult(RESULT_CANCELED);
116         }
117 
118         super.onDestroy();
119     }
120 
121     /**
122      * Sets the next layout in the UI.
123      */
setNextLayout()124     private void setNextLayout() {
125         if (mLayoutIndex >= LAYOUTS.length) {
126             setResult(RESULT_OK);
127             finish();
128             return;
129         }
130 
131         mViewGroup.removeAllViews();
132 
133         final Layout layout = LAYOUTS[mLayoutIndex++];
134         final String layoutName = String.format("%s_%s", mTheme.name, layout.name);
135         final View view = getLayoutInflater().inflate(layout.id, mViewGroup, false);
136         if (layout.modifier != null) {
137             layout.modifier.modifyView(view);
138         }
139 
140         mViewGroup.addView(view);
141         view.setFocusable(false);
142 
143         Log.v(TAG, "Rendering layout " + layoutName
144                 + " (" + mLayoutIndex + "/" + LAYOUTS.length + ")");
145 
146         final Runnable generateBitmapRunnable = new Runnable() {
147             @Override
148             public void run() {
149                 new BitmapTask(view, layoutName).execute();
150             }
151         };
152 
153         if (view instanceof DatePicker) {
154             // The Holo-styled DatePicker uses a CalendarView that has a
155             // non-configurable adjustment duration of 540ms.
156             view.postDelayed(generateBitmapRunnable, CALENDAR_VIEW_ADJUSTMENT_DURATION);
157         } else {
158             view.post(generateBitmapRunnable);
159         }
160     }
161 
162     private class BitmapTask extends GenerateBitmapTask {
BitmapTask(View view, String name)163         public BitmapTask(View view, String name) {
164             super(view, mOutputDir, name);
165         }
166 
167         @Override
onPostExecute(Boolean success)168         protected void onPostExecute(Boolean success) {
169             if (success && mIsRunning) {
170                 setNextLayout();
171             } else {
172                 Log.e(TAG, "Failed to render view to bitmap: " + mName + " (activity running? "
173                         + mIsRunning + ")");
174                 finish();
175             }
176         }
177     }
178 
179     /**
180      * A class to encapsulate information about a theme.
181      */
182     static class Theme {
183         public final int id;
184         public final int apiLevel;
185         public final String name;
186 
Theme(int id, int apiLevel, String name)187         private Theme(int id, int apiLevel, String name) {
188             this.id = id;
189             this.apiLevel = apiLevel;
190             this.name = name;
191         }
192     }
193 
194     // List of themes to verify.
195     static final Theme[] THEMES = {
196             // Holo
197             new Theme(android.R.style.Theme_Holo,
198                     Build.VERSION_CODES.HONEYCOMB, "holo"),
199             new Theme(android.R.style.Theme_Holo_Dialog,
200                     Build.VERSION_CODES.HONEYCOMB, "holo_dialog"),
201             new Theme(android.R.style.Theme_Holo_Dialog_MinWidth,
202                     Build.VERSION_CODES.HONEYCOMB, "holo_dialog_minwidth"),
203             new Theme(android.R.style.Theme_Holo_Dialog_NoActionBar,
204                     Build.VERSION_CODES.HONEYCOMB, "holo_dialog_noactionbar"),
205             new Theme(android.R.style.Theme_Holo_Dialog_NoActionBar_MinWidth,
206                     Build.VERSION_CODES.HONEYCOMB, "holo_dialog_noactionbar_minwidth"),
207             new Theme(android.R.style.Theme_Holo_DialogWhenLarge,
208                     Build.VERSION_CODES.HONEYCOMB, "holo_dialogwhenlarge"),
209             new Theme(android.R.style.Theme_Holo_DialogWhenLarge_NoActionBar,
210                     Build.VERSION_CODES.HONEYCOMB, "holo_dialogwhenlarge_noactionbar"),
211             new Theme(android.R.style.Theme_Holo_InputMethod,
212                     Build.VERSION_CODES.HONEYCOMB, "holo_inputmethod"),
213             new Theme(android.R.style.Theme_Holo_NoActionBar,
214                     Build.VERSION_CODES.HONEYCOMB, "holo_noactionbar"),
215             new Theme(android.R.style.Theme_Holo_NoActionBar_Fullscreen,
216                     Build.VERSION_CODES.HONEYCOMB, "holo_noactionbar_fullscreen"),
217             new Theme(android.R.style.Theme_Holo_NoActionBar_Overscan,
218                     Build.VERSION_CODES.JELLY_BEAN_MR2, "holo_noactionbar_overscan"),
219             new Theme(android.R.style.Theme_Holo_NoActionBar_TranslucentDecor,
220                     Build.VERSION_CODES.KITKAT, "holo_noactionbar_translucentdecor"),
221             new Theme(android.R.style.Theme_Holo_Panel,
222                     Build.VERSION_CODES.HONEYCOMB, "holo_panel"),
223             new Theme(android.R.style.Theme_Holo_Wallpaper,
224                     Build.VERSION_CODES.HONEYCOMB, "holo_wallpaper"),
225             new Theme(android.R.style.Theme_Holo_Wallpaper_NoTitleBar,
226                     Build.VERSION_CODES.HONEYCOMB, "holo_wallpaper_notitlebar"),
227 
228             // Holo Light
229             new Theme(android.R.style.Theme_Holo_Light,
230                     Build.VERSION_CODES.HONEYCOMB, "holo_light"),
231             new Theme(android.R.style.Theme_Holo_Light_DarkActionBar,
232                     Build.VERSION_CODES.ICE_CREAM_SANDWICH, "holo_light_darkactionbar"),
233             new Theme(android.R.style.Theme_Holo_Light_Dialog,
234                     Build.VERSION_CODES.HONEYCOMB, "holo_light_dialog"),
235             new Theme(android.R.style.Theme_Holo_Light_Dialog_MinWidth,
236                     Build.VERSION_CODES.HONEYCOMB, "holo_light_dialog_minwidth"),
237             new Theme(android.R.style.Theme_Holo_Light_Dialog_NoActionBar,
238                     Build.VERSION_CODES.HONEYCOMB, "holo_light_dialog_noactionbar"),
239             new Theme(android.R.style.Theme_Holo_Light_Dialog_NoActionBar_MinWidth,
240                     Build.VERSION_CODES.HONEYCOMB, "holo_light_dialog_noactionbar_minwidth"),
241             new Theme(android.R.style.Theme_Holo_Light_DialogWhenLarge,
242                     Build.VERSION_CODES.HONEYCOMB, "holo_light_dialogwhenlarge"),
243             new Theme(android.R.style.Theme_Holo_Light_DialogWhenLarge_NoActionBar,
244                     Build.VERSION_CODES.HONEYCOMB, "holo_light_dialogwhenlarge_noactionbar"),
245             new Theme(android.R.style.Theme_Holo_Light_NoActionBar,
246                     Build.VERSION_CODES.HONEYCOMB_MR2, "holo_light_noactionbar"),
247             new Theme(android.R.style.Theme_Holo_Light_NoActionBar_Fullscreen,
248                     Build.VERSION_CODES.HONEYCOMB_MR2, "holo_light_noactionbar_fullscreen"),
249             new Theme(android.R.style.Theme_Holo_Light_NoActionBar_Overscan,
250                     Build.VERSION_CODES.JELLY_BEAN_MR2, "holo_light_noactionbar_overscan"),
251             new Theme(android.R.style.Theme_Holo_Light_NoActionBar_TranslucentDecor,
252                     Build.VERSION_CODES.KITKAT, "holo_light_noactionbar_translucentdecor"),
253             new Theme(android.R.style.Theme_Holo_Light_Panel,
254                     Build.VERSION_CODES.HONEYCOMB, "holo_light_panel"),
255 
256             // Material
257             new Theme(android.R.style.Theme_Material,
258                     Build.VERSION_CODES.LOLLIPOP, "material"),
259             new Theme(android.R.style.Theme_Material_Dialog,
260                     Build.VERSION_CODES.LOLLIPOP, "material_dialog"),
261             new Theme(android.R.style.Theme_Material_Dialog_Alert,
262                     Build.VERSION_CODES.LOLLIPOP, "material_dialog_alert"),
263             new Theme(android.R.style.Theme_Material_Dialog_MinWidth,
264                     Build.VERSION_CODES.LOLLIPOP, "material_dialog_minwidth"),
265             new Theme(android.R.style.Theme_Material_Dialog_NoActionBar,
266                     Build.VERSION_CODES.LOLLIPOP, "material_dialog_noactionbar"),
267             new Theme(android.R.style.Theme_Material_Dialog_NoActionBar_MinWidth,
268                     Build.VERSION_CODES.LOLLIPOP, "material_dialog_noactionbar_minwidth"),
269             new Theme(android.R.style.Theme_Material_Dialog_Presentation,
270                     Build.VERSION_CODES.LOLLIPOP, "material_dialog_presentation"),
271             new Theme(android.R.style.Theme_Material_DialogWhenLarge,
272                     Build.VERSION_CODES.LOLLIPOP, "material_dialogwhenlarge"),
273             new Theme(android.R.style.Theme_Material_DialogWhenLarge_NoActionBar,
274                     Build.VERSION_CODES.LOLLIPOP, "material_dialogwhenlarge_noactionbar"),
275             new Theme(android.R.style.Theme_Material_InputMethod,
276                     Build.VERSION_CODES.LOLLIPOP, "material_inputmethod"),
277             new Theme(android.R.style.Theme_Material_NoActionBar,
278                     Build.VERSION_CODES.LOLLIPOP, "material_noactionbar"),
279             new Theme(android.R.style.Theme_Material_NoActionBar_Fullscreen,
280                     Build.VERSION_CODES.LOLLIPOP, "material_noactionbar_fullscreen"),
281             new Theme(android.R.style.Theme_Material_NoActionBar_Overscan,
282                     Build.VERSION_CODES.LOLLIPOP, "material_noactionbar_overscan"),
283             new Theme(android.R.style.Theme_Material_NoActionBar_TranslucentDecor,
284                     Build.VERSION_CODES.LOLLIPOP, "material_noactionbar_translucentdecor"),
285             new Theme(android.R.style.Theme_Material_Panel,
286                     Build.VERSION_CODES.LOLLIPOP, "material_panel"),
287             new Theme(android.R.style.Theme_Material_Settings,
288                     Build.VERSION_CODES.LOLLIPOP, "material_settings"),
289             new Theme(android.R.style.Theme_Material_Voice,
290                     Build.VERSION_CODES.LOLLIPOP, "material_voice"),
291             new Theme(android.R.style.Theme_Material_Wallpaper,
292                     Build.VERSION_CODES.LOLLIPOP, "material_wallpaper"),
293             new Theme(android.R.style.Theme_Material_Wallpaper_NoTitleBar,
294                     Build.VERSION_CODES.LOLLIPOP, "material_wallpaper_notitlebar"),
295 
296             // Material Light
297             new Theme(android.R.style.Theme_Material_Light,
298                     Build.VERSION_CODES.LOLLIPOP, "material_light"),
299             new Theme(android.R.style.Theme_Material_Light_DarkActionBar,
300                     Build.VERSION_CODES.LOLLIPOP, "material_light_darkactionbar"),
301             new Theme(android.R.style.Theme_Material_Light_Dialog,
302                     Build.VERSION_CODES.LOLLIPOP, "material_light_dialog"),
303             new Theme(android.R.style.Theme_Material_Light_Dialog_Alert,
304                     Build.VERSION_CODES.LOLLIPOP, "material_light_dialog_alert"),
305             new Theme(android.R.style.Theme_Material_Light_Dialog_MinWidth,
306                     Build.VERSION_CODES.LOLLIPOP, "material_light_dialog_minwidth"),
307             new Theme(android.R.style.Theme_Material_Light_Dialog_NoActionBar,
308                     Build.VERSION_CODES.LOLLIPOP, "material_light_dialog_noactionbar"),
309             new Theme(android.R.style.Theme_Material_Light_Dialog_NoActionBar_MinWidth,
310                     Build.VERSION_CODES.LOLLIPOP, "material_light_dialog_noactionbar_minwidth"),
311             new Theme(android.R.style.Theme_Material_Light_Dialog_Presentation,
312                     Build.VERSION_CODES.LOLLIPOP, "material_light_dialog_presentation"),
313             new Theme(android.R.style.Theme_Material_Light_DialogWhenLarge,
314                     Build.VERSION_CODES.LOLLIPOP, "material_light_dialogwhenlarge"),
315             new Theme(android.R.style.Theme_Material_Light_DialogWhenLarge_NoActionBar,
316                     Build.VERSION_CODES.LOLLIPOP, "material_light_dialogwhenlarge_noactionbar"),
317             new Theme(android.R.style.Theme_Material_Light_LightStatusBar,
318                     Build.VERSION_CODES.M, "material_light_lightstatusbar"),
319             new Theme(android.R.style.Theme_Material_Light_NoActionBar,
320                     Build.VERSION_CODES.LOLLIPOP, "material_light_noactionbar"),
321             new Theme(android.R.style.Theme_Material_Light_NoActionBar_Fullscreen,
322                     Build.VERSION_CODES.LOLLIPOP, "material_light_noactionbar_fullscreen"),
323             new Theme(android.R.style.Theme_Material_Light_NoActionBar_Overscan,
324                     Build.VERSION_CODES.LOLLIPOP, "material_light_noactionbar_overscan"),
325             new Theme(android.R.style.Theme_Material_Light_NoActionBar_TranslucentDecor,
326                     Build.VERSION_CODES.LOLLIPOP, "material_light_noactionbar_translucentdecor"),
327             new Theme(android.R.style.Theme_Material_Light_Panel,
328                     Build.VERSION_CODES.LOLLIPOP, "material_light_panel"),
329             new Theme(android.R.style.Theme_Material_Light_Voice,
330                     Build.VERSION_CODES.LOLLIPOP, "material_light_voice")
331     };
332 
333     /**
334      * A class to encapsulate information about a layout.
335      */
336     private static class Layout {
337         public final int id;
338         public final String name;
339         public final LayoutModifier modifier;
340 
Layout(int id, String name)341         private Layout(int id, String name) {
342             this(id, name, null);
343         }
344 
Layout(int id, String name, LayoutModifier modifier)345         private Layout(int id, String name, LayoutModifier modifier) {
346             this.id = id;
347             this.name = name;
348             this.modifier = modifier;
349         }
350     }
351 
352     // List of layouts to verify for each theme.
353     private static final Layout[] LAYOUTS = {
354             new Layout(R.layout.button, "button"),
355             new Layout(R.layout.button, "button_pressed",
356                     new ViewPressedModifier()),
357             new Layout(R.layout.checkbox, "checkbox"),
358             new Layout(R.layout.checkbox, "checkbox_checked",
359                     new ViewCheckedModifier()),
360             new Layout(R.layout.chronometer, "chronometer"),
361             new Layout(R.layout.color_blue_bright, "color_blue_bright"),
362             new Layout(R.layout.color_blue_dark, "color_blue_dark"),
363             new Layout(R.layout.color_blue_light, "color_blue_light"),
364             new Layout(R.layout.color_green_dark, "color_green_dark"),
365             new Layout(R.layout.color_green_light, "color_green_light"),
366             new Layout(R.layout.color_orange_dark, "color_orange_dark"),
367             new Layout(R.layout.color_orange_light, "color_orange_light"),
368             new Layout(R.layout.color_purple, "color_purple"),
369             new Layout(R.layout.color_red_dark, "color_red_dark"),
370             new Layout(R.layout.color_red_light, "color_red_light"),
371             new Layout(R.layout.datepicker, "datepicker",
372                     new DatePickerModifier()),
373             new Layout(R.layout.display_info, "display_info"),
374             new Layout(R.layout.edittext, "edittext"),
375             new Layout(R.layout.progressbar_horizontal_0, "progressbar_horizontal_0"),
376             new Layout(R.layout.progressbar_horizontal_100, "progressbar_horizontal_100"),
377             new Layout(R.layout.progressbar_horizontal_50, "progressbar_horizontal_50"),
378             new Layout(R.layout.progressbar_large, "progressbar_large",
379                     new ProgressBarModifier()),
380             new Layout(R.layout.progressbar_small, "progressbar_small",
381                     new ProgressBarModifier()),
382             new Layout(R.layout.progressbar, "progressbar",
383                     new ProgressBarModifier()),
384             new Layout(R.layout.radiobutton_checked, "radiobutton_checked"),
385             new Layout(R.layout.radiobutton, "radiobutton"),
386             new Layout(R.layout.radiogroup_horizontal, "radiogroup_horizontal"),
387             new Layout(R.layout.radiogroup_vertical, "radiogroup_vertical"),
388             new Layout(R.layout.ratingbar_0, "ratingbar_0"),
389             new Layout(R.layout.ratingbar_2point5, "ratingbar_2point5"),
390             new Layout(R.layout.ratingbar_5, "ratingbar_5"),
391             new Layout(R.layout.ratingbar_0, "ratingbar_0_pressed",
392                     new ViewPressedModifier()),
393             new Layout(R.layout.ratingbar_2point5, "ratingbar_2point5_pressed",
394                     new ViewPressedModifier()),
395             new Layout(R.layout.ratingbar_5, "ratingbar_5_pressed",
396                     new ViewPressedModifier()),
397             new Layout(R.layout.searchview, "searchview_query",
398                     new SearchViewModifier(SearchViewModifier.QUERY)),
399             new Layout(R.layout.searchview, "searchview_query_hint",
400                     new SearchViewModifier(SearchViewModifier.QUERY_HINT)),
401             new Layout(R.layout.seekbar_0, "seekbar_0"),
402             new Layout(R.layout.seekbar_100, "seekbar_100"),
403             new Layout(R.layout.seekbar_50, "seekbar_50"),
404             new Layout(R.layout.spinner, "spinner"),
405             new Layout(R.layout.switch_button_checked, "switch_button_checked"),
406             new Layout(R.layout.switch_button, "switch_button"),
407             new Layout(R.layout.textview, "textview"),
408             new Layout(R.layout.timepicker, "timepicker",
409                     new TimePickerModifier()),
410             new Layout(R.layout.togglebutton_checked, "togglebutton_checked"),
411             new Layout(R.layout.togglebutton, "togglebutton"),
412             new Layout(R.layout.zoomcontrols, "zoomcontrols"),
413     };
414 }
415