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.THEMES;
20 
21 import android.app.Activity;
22 import android.content.Intent;
23 import android.os.Build.VERSION;
24 import android.os.Bundle;
25 import android.os.Environment;
26 import android.util.Log;
27 import android.view.WindowManager.LayoutParams;
28 
29 import java.io.File;
30 import java.io.IOException;
31 import java.util.concurrent.CountDownLatch;
32 import java.util.concurrent.TimeUnit;
33 
34 /**
35  * Generates images by iterating through all themes and launching instances of
36  * {@link ThemeDeviceActivity}.
37  */
38 public class GenerateImagesActivity extends Activity {
39     private static final String TAG = "GenerateImagesActivity";
40 
41     private static final String OUT_DIR = "cts-theme-assets";
42     private static final int REQUEST_CODE = 1;
43 
44     public static final String EXTRA_REASON = "reason";
45 
46     private final CountDownLatch mLatch = new CountDownLatch(1);
47 
48     private File mOutputDir;
49     private File mOutputZip;
50 
51     private int mCurrentTheme;
52     private String mFinishReason;
53     private boolean mFinishSuccess;
54 
55     class CompressOutputThread extends Thread {
run()56         public void run() {
57             compressOutput();
58         }
59     }
60 
61     @Override
onCreate(Bundle savedInstanceState)62     protected void onCreate(Bundle savedInstanceState) {
63         super.onCreate(savedInstanceState);
64 
65         // Useful for local testing. Not required for CTS harness.
66         getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
67 
68         // Make sure the device has reasonable assets.
69         String assetDensityFailureMsg = checkAssetDensity();
70         if (assetDensityFailureMsg != null) {
71             finish("Failed to verify device assets: "+ assetDensityFailureMsg, false);
72         } else {
73             mOutputDir = setupOutputDirectory();
74             if (mOutputDir == null) {
75                 finish("Failed to create output directory: " + OUT_DIR, false);
76             } else {
77                 generateNextImage();
78             }
79         }
80     }
81 
checkAssetDensity()82     private String checkAssetDensity() {
83         AssetBucketVerifier.Result result = AssetBucketVerifier.verifyAssetBucket(this);
84 
85         String message;
86         if (result.foundAtDensity.contains(result.expectedAtDensity)) {
87             message = null;
88         } else if (result.foundAtDensity.isEmpty()) {
89             message = "Failed to find expected device assets at any density";
90         } else {
91             StringBuilder foundAtDensityStr = new StringBuilder(result.foundAtDensity.get(0));
92             for (int i = 1; i < result.foundAtDensity.size(); i++) {
93                 foundAtDensityStr.append(", ");
94                 foundAtDensityStr.append(result.foundAtDensity.get(i));
95             }
96             message = "Failed to find device assets at expected density ("
97                     + result.expectedAtDensity + "), but found at " + foundAtDensityStr;
98         }
99 
100         return message;
101     }
102 
setupOutputDirectory()103     private File setupOutputDirectory() {
104         mOutputDir = new File(Environment.getExternalStorageDirectory(), OUT_DIR);
105         ThemeTestUtils.deleteDirectory(mOutputDir);
106         mOutputDir.mkdirs();
107 
108         if (mOutputDir.exists()) {
109             return mOutputDir;
110         }
111         return null;
112     }
113 
114     /**
115      * @return whether the test finished successfully
116      */
isFinishSuccess()117     public boolean isFinishSuccess() {
118         return mFinishSuccess;
119     }
120 
121     /**
122      * @return user-visible string explaining why the test finished, may be {@code null} if the test
123      *         finished unexpectedly
124      */
getFinishReason()125     public String getFinishReason() {
126         return mFinishReason;
127     }
128 
129     /**
130      * Starts the activity to generate the next image.
131      */
generateNextImage()132     private void generateNextImage() {
133         // Keep trying themes until one works.
134         boolean success = false;
135         while (mCurrentTheme < THEMES.length && !success) {
136             success = launchThemeDeviceActivity();
137             mCurrentTheme++;
138         }
139 
140         // If we ran out of themes, we're done.
141         if (!success) {
142             CompressOutputThread compressOutputThread = new CompressOutputThread();
143             compressOutputThread.start();
144         }
145     }
146 
launchThemeDeviceActivity()147     private boolean launchThemeDeviceActivity() {
148         final ThemeInfo theme = THEMES[mCurrentTheme];
149         if (theme.apiLevel > VERSION.SDK_INT) {
150             Log.v(TAG, "Skipping theme \"" + theme.name
151                     + "\" (requires API " + theme.apiLevel + ")");
152             return false;
153         }
154 
155         Log.v(TAG, "Generating images for theme \"" + theme.name + "\"...");
156 
157         final Intent intent = new Intent(this, ThemeDeviceActivity.class);
158         intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
159         intent.putExtra(ThemeDeviceActivity.EXTRA_THEME, mCurrentTheme);
160         intent.putExtra(ThemeDeviceActivity.EXTRA_OUTPUT_DIR, mOutputDir.getAbsolutePath());
161         startActivityForResult(intent, REQUEST_CODE);
162         return true;
163     }
164 
165     @Override
onActivityResult(int requestCode, int resultCode, Intent data)166     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
167         if (resultCode != RESULT_OK) {
168             finish("Failed to generate images for theme " + mCurrentTheme + " ("
169                     + data.getStringExtra(EXTRA_REASON) + ")", false);
170             return;
171         }
172 
173         generateNextImage();
174     }
175 
compressOutput()176     private void compressOutput() {
177         mOutputZip = new File(mOutputDir.getParentFile(), mOutputDir.getName() + ".zip");
178 
179         if (mOutputZip.exists()) {
180             // Remove any old test results.
181             mOutputZip.delete();
182         }
183 
184         try {
185             ThemeTestUtils.compressDirectory(mOutputDir, mOutputZip);
186             ThemeTestUtils.deleteDirectory(mOutputDir);
187         } catch (IOException e) {
188             e.printStackTrace();
189         }
190         runOnUiThread(() -> {
191             finish("Image generation complete!", true);
192         });
193     }
194 
finish(String reason, boolean success)195     private void finish(String reason, boolean success) {
196         mFinishSuccess = success;
197         mFinishReason = reason;
198 
199         finish();
200     }
201 
202     @Override
finish()203     public void finish() {
204         mLatch.countDown();
205 
206         super.finish();
207     }
208 
getOutputZip()209     public File getOutputZip() {
210         return mOutputZip;
211     }
212 
waitForCompletion(long timeoutMillis)213     public boolean waitForCompletion(long timeoutMillis) throws InterruptedException {
214         return mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
215     }
216 }
217