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.Manifest.permission;
20 import android.app.Activity;
21 import android.app.KeyguardManager;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.os.Build.VERSION;
26 import android.os.Bundle;
27 import android.os.Environment;
28 import android.os.Handler;
29 import android.util.Log;
30 import android.view.WindowManager.LayoutParams;
31 
32 import java.io.File;
33 import java.io.IOException;
34 import java.util.concurrent.CountDownLatch;
35 import java.util.concurrent.TimeUnit;
36 
37 /**
38  * Generates images by iterating through all themes and launching instances of
39  * {@link ThemeDeviceActivity}.
40  */
41 public class GenerateImagesActivity extends Activity {
42     private static final String TAG = "GenerateImagesActivity";
43 
44     private static final String OUT_DIR = "cts-theme-assets";
45     private static final int REQUEST_CODE = 1;
46 
47     public static final String EXTRA_REASON = "reason";
48 
49     private final CountDownLatch mLatch = new CountDownLatch(1);
50 
51     private File mOutputDir;
52     private File mOutputZip;
53 
54     private int mCurrentTheme;
55     private String mFinishReason;
56     private boolean mFinishSuccess;
57 
58     @Override
onCreate(Bundle savedInstanceState)59     protected void onCreate(Bundle savedInstanceState) {
60         super.onCreate(savedInstanceState);
61 
62         getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON
63                 | LayoutParams.FLAG_TURN_SCREEN_ON
64                 | LayoutParams.FLAG_DISMISS_KEYGUARD);
65 
66         mOutputDir = new File(Environment.getExternalStorageDirectory(), OUT_DIR);
67         ThemeTestUtils.deleteDirectory(mOutputDir);
68         mOutputDir.mkdirs();
69 
70         if (!mOutputDir.exists()) {
71             finish("Failed to create output directory " + mOutputDir.getAbsolutePath(), false);
72             return;
73         }
74 
75         final boolean canDisableKeyguard = checkCallingOrSelfPermission(
76                 permission.DISABLE_KEYGUARD) == PackageManager.PERMISSION_GRANTED;
77         if (!canDisableKeyguard) {
78             finish("Not granted permission to disable keyguard", false);
79             return;
80         }
81 
82         new KeyguardCheck(this) {
83             @Override
84             public void onSuccess() {
85                 generateNextImage();
86             }
87 
88             @Override
89             public void onFailure() {
90                 finish("Device is locked", false);
91             }
92         }.start();
93     }
94 
isFinishSuccess()95     public boolean isFinishSuccess() {
96         return mFinishSuccess;
97     }
98 
getFinishReason()99     public String getFinishReason() {
100         return mFinishReason;
101     }
102 
103     static abstract class KeyguardCheck implements Runnable {
104         private static final int MAX_RETRIES = 3;
105         private static final int RETRY_DELAY = 500;
106 
107         private final Handler mHandler;
108         private final KeyguardManager mKeyguard;
109 
110         private int mRetries;
111 
KeyguardCheck(Context context)112         public KeyguardCheck(Context context) {
113             mHandler = new Handler(context.getMainLooper());
114             mKeyguard = (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE);
115         }
116 
start()117         public void start() {
118             mRetries = 0;
119 
120             mHandler.removeCallbacks(this);
121             mHandler.post(this);
122         }
123 
cancel()124         public void cancel() {
125             mHandler.removeCallbacks(this);
126         }
127 
128         @Override
run()129         public void run() {
130             if (!mKeyguard.isKeyguardLocked()) {
131                 onSuccess();
132             } else if (mRetries < MAX_RETRIES) {
133                 mRetries++;
134                 mHandler.postDelayed(this, RETRY_DELAY);
135             } else {
136                 onFailure();
137             }
138 
139         }
140 
onSuccess()141         public abstract void onSuccess();
onFailure()142         public abstract void onFailure();
143     }
144 
145     /**
146      * Starts the activity to generate the next image.
147      */
generateNextImage()148     private boolean generateNextImage() {
149         final ThemeDeviceActivity.Theme theme = ThemeDeviceActivity.THEMES[mCurrentTheme];
150         if (theme.apiLevel > VERSION.SDK_INT) {
151             Log.v(TAG, "Skipping theme \"" + theme.name
152                     + "\" (requires API " + theme.apiLevel + ")");
153             return false;
154         }
155 
156         Log.v(TAG, "Generating images for theme \"" + theme.name + "\"...");
157 
158         final Intent intent = new Intent(this, ThemeDeviceActivity.class);
159         intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
160         intent.putExtra(ThemeDeviceActivity.EXTRA_THEME, mCurrentTheme);
161         intent.putExtra(ThemeDeviceActivity.EXTRA_OUTPUT_DIR, mOutputDir.getAbsolutePath());
162         startActivityForResult(intent, REQUEST_CODE);
163         return true;
164     }
165 
166     @Override
onActivityResult(int requestCode, int resultCode, Intent data)167     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
168         if (resultCode != RESULT_OK) {
169             finish("Failed to generate images for theme " + mCurrentTheme + " ("
170                     + data.getStringExtra(EXTRA_REASON) + ")", false);
171             return;
172         }
173 
174         // Keep trying themes until one works.
175         boolean success = false;
176         while (++mCurrentTheme < ThemeDeviceActivity.THEMES.length && !success) {
177             success = generateNextImage();
178         }
179 
180         // If we ran out of themes, we're done.
181         if (!success) {
182             compressOutput();
183 
184             finish("Image generation complete!", true);
185         }
186     }
187 
compressOutput()188     private void compressOutput() {
189         mOutputZip = new File(mOutputDir.getParentFile(), mOutputDir.getName() + ".zip");
190 
191         if (mOutputZip.exists()) {
192             // Remove any old test results.
193             mOutputZip.delete();
194         }
195 
196         try {
197             ThemeTestUtils.compressDirectory(mOutputDir, mOutputZip);
198             ThemeTestUtils.deleteDirectory(mOutputDir);
199         } catch (IOException e) {
200             e.printStackTrace();
201         }
202     }
203 
finish(String reason, boolean success)204     private void finish(String reason, boolean success) {
205         mFinishSuccess = success;
206         mFinishReason = reason;
207 
208         finish();
209     }
210 
211     @Override
finish()212     public void finish() {
213         mLatch.countDown();
214 
215         super.finish();
216     }
217 
getOutputZip()218     public File getOutputZip() {
219         return mOutputZip;
220     }
221 
waitForCompletion(long timeoutMillis)222     public boolean waitForCompletion(long timeoutMillis) throws InterruptedException {
223         return mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
224     }
225 }
226