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 static android.theme.app.TestConfiguration.LAYOUTS; 20 import static android.theme.app.TestConfiguration.THEMES; 21 22 import android.animation.ValueAnimator; 23 import android.app.Activity; 24 import android.app.KeyguardManager; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.res.Configuration; 28 import android.os.Bundle; 29 import android.theme.app.modifiers.AbstractLayoutModifier; 30 import android.util.Log; 31 import android.view.LayoutInflater; 32 import android.view.View; 33 import android.view.WindowManager.LayoutParams; 34 import android.widget.DatePicker; 35 36 import java.io.File; 37 38 /** 39 * A activity which display various UI elements with non-modifiable themes. 40 */ 41 public class ThemeDeviceActivity extends Activity { 42 public static final String EXTRA_THEME = "theme"; 43 public static final String EXTRA_OUTPUT_DIR = "outputDir"; 44 45 private static final String TAG = "ThemeDeviceActivity"; 46 47 /** 48 * Delay that allows the Holo-style CalendarView to settle to its final 49 * position. 50 */ 51 private static final long HOLO_CALENDAR_VIEW_ADJUSTMENT_DURATION = 540; 52 53 private ThemeInfo mTheme; 54 private ReferenceViewGroup mViewGroup; 55 private File mOutputDir; 56 private int mLayoutIndex; 57 private boolean mIsRunning; 58 59 @Override onCreate(Bundle icicle)60 protected void onCreate(Bundle icicle) { 61 super.onCreate(icicle); 62 63 final Intent intent = getIntent(); 64 final int themeIndex = intent.getIntExtra(EXTRA_THEME, -1); 65 if (themeIndex < 0) { 66 Log.e(TAG, "No theme specified"); 67 finish(); 68 } 69 70 final String outputDir = intent.getStringExtra(EXTRA_OUTPUT_DIR); 71 if (outputDir == null) { 72 Log.e(TAG, "No output directory specified"); 73 finish(); 74 } 75 76 mOutputDir = new File(outputDir); 77 mTheme = THEMES[themeIndex]; 78 79 // Disable animations. 80 ValueAnimator.setDurationScale(0); 81 82 // Force text scaling to 1.0 regardless of system default. 83 Configuration config = new Configuration(); 84 config.fontScale = 1.0f; 85 86 Context inflationContext = createConfigurationContext(config); 87 inflationContext.setTheme(mTheme.id); 88 89 LayoutInflater layoutInflater = LayoutInflater.from(inflationContext); 90 setContentView(layoutInflater.inflate(R.layout.theme_test, null)); 91 92 mViewGroup = findViewById(R.id.reference_view_group); 93 94 getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); 95 96 // The activity has been created, but we don't want to start image generation until various 97 // asynchronous conditions are satisfied. 98 new ConditionCheck(this, () -> setNextLayout(), message -> finish(message, false)) 99 .addCondition("Device is unlocked", 100 () -> !getSystemService(KeyguardManager.class).isDeviceLocked()) 101 .addCondition("Window is focused", 102 () -> hasWindowFocus()) 103 .addCondition("Activity is resumed", 104 () -> mIsRunning) 105 .start(); 106 } 107 108 @Override onResume()109 protected void onResume() { 110 super.onResume(); 111 112 mIsRunning = true; 113 } 114 115 @Override onPause()116 protected void onPause() { 117 mIsRunning = false; 118 119 if (!isFinishing()) { 120 // The activity paused for some reason, likely a system crash 121 // dialog. Finish it so we can move to the next theme. 122 Log.e(TAG, "onPause() called without a call to finish(), check system dialogs"); 123 } 124 125 super.onPause(); 126 } 127 128 @Override onDestroy()129 protected void onDestroy() { 130 if (mLayoutIndex < LAYOUTS.length) { 131 finish("Only rendered " + mLayoutIndex + "/" + LAYOUTS.length + " layouts", false); 132 } 133 134 super.onDestroy(); 135 } 136 137 /** 138 * Sets the next layout in the UI. 139 */ setNextLayout()140 private void setNextLayout() { 141 if (mLayoutIndex >= LAYOUTS.length) { 142 finish("Rendered all layouts", true); 143 return; 144 } 145 146 mViewGroup.removeAllViews(); 147 148 final LayoutInfo layout = LAYOUTS[mLayoutIndex++]; 149 final AbstractLayoutModifier modifier = layout.modifier; 150 final String layoutName = String.format("%s_%s", mTheme.name, layout.name); 151 final LayoutInflater layoutInflater = LayoutInflater.from(mViewGroup.getContext()); 152 final View view = layoutInflater.inflate(layout.id, mViewGroup, false); 153 if (modifier != null) { 154 modifier.modifyViewBeforeAdd(view); 155 } 156 view.setFocusable(false); 157 158 mViewGroup.addView(view); 159 160 if (modifier != null) { 161 modifier.modifyViewAfterAdd(view); 162 } 163 164 Log.v(TAG, "Rendering layout " + layoutName 165 + " (" + mLayoutIndex + "/" + LAYOUTS.length + ")"); 166 167 final Runnable generateBitmapRunnable = () -> 168 new BitmapTask(view, layoutName).execute(); 169 170 if (view instanceof DatePicker && mTheme.spec == ThemeInfo.HOLO) { 171 // The Holo-styled DatePicker uses a CalendarView that has a 172 // non-configurable adjustment duration of 540ms. 173 view.postDelayed(generateBitmapRunnable, HOLO_CALENDAR_VIEW_ADJUSTMENT_DURATION); 174 } else { 175 view.post(generateBitmapRunnable); 176 } 177 } 178 finish(String reason, boolean success)179 private void finish(String reason, boolean success) { 180 final Intent data = new Intent(); 181 data.putExtra(GenerateImagesActivity.EXTRA_REASON, reason); 182 setResult(success ? RESULT_OK : RESULT_CANCELED, data); 183 184 if (!isFinishing()) { 185 finish(); 186 } 187 } 188 189 private class BitmapTask extends GenerateBitmapTask { 190 BitmapTask(View view, String name)191 public BitmapTask(View view, String name) { 192 super(view, mOutputDir, name); 193 } 194 195 @Override onPostExecute(Boolean success)196 protected void onPostExecute(Boolean success) { 197 if (success && mIsRunning) { 198 setNextLayout(); 199 } else { 200 finish("Failed to render view to bitmap: " + mName + " (activity running? " 201 + mIsRunning + ")", false); 202 } 203 } 204 } 205 } 206