blob: dc9196e8c95195ba1fa312d698c50d995058e71d [file] [log] [blame]
Jason Monk8f8784c2018-01-22 21:29:42 -05001/*
2 * Copyright 2018 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
17package androidx.app.slice.render;
18
19import static android.view.View.MeasureSpec.makeMeasureSpec;
20
21import android.app.Activity;
22import android.app.ProgressDialog;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.os.AsyncTask;
Jason Monk8f8784c2018-01-22 21:29:42 -050026import android.os.Handler;
27import android.support.v7.widget.RecyclerView;
28import android.util.Log;
29import android.util.TypedValue;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.ViewGroup;
33
34import java.io.File;
35import java.io.FileNotFoundException;
36import java.io.FileOutputStream;
37import java.util.concurrent.CountDownLatch;
38
39import androidx.app.slice.Slice;
40import androidx.app.slice.SliceProvider;
41import androidx.app.slice.view.test.R;
42import androidx.app.slice.widget.SliceLiveData;
43import androidx.app.slice.widget.SliceView;
44
45public class SliceRenderer {
46
47 private static final String TAG = "SliceRenderer";
Jason Monk6d235262018-02-20 20:10:20 -050048 public static final String SCREENSHOT_DIR = "slice-screenshots";
Jason Monk8f8784c2018-01-22 21:29:42 -050049 private static File sScreenshotDirectory;
50
51 private final Activity mContext;
52 private final View mLayout;
53 private final SliceView mSV1;
54 private final SliceView mSV2;
55 private final SliceView mSV3;
56 private final ViewGroup mParent;
57 private final Handler mHandler;
58 private final SliceCreator mSliceCreator;
59 private CountDownLatch mDoneLatch;
60
61 public SliceRenderer(Activity context) {
62 mContext = context;
63 mParent = new ViewGroup(mContext) {
64 @Override
65 protected void onLayout(boolean changed, int l, int t, int r, int b) {
66 int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 900,
67 mContext.getResources().getDisplayMetrics());
Mady Mellor8a2763f2018-02-16 13:39:25 -080068 int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300,
Jason Monk8f8784c2018-01-22 21:29:42 -050069 mContext.getResources().getDisplayMetrics());
70 mLayout.measure(makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
71 makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
72 mLayout.layout(0, 0, width, height);
73 }
74
75 @Override
76 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
77 return false;
78 }
79 };
80 mLayout = LayoutInflater.from(context).inflate(R.layout.render_layout, null);
81 mSV1 = mLayout.findViewById(R.id.sv1);
82 mSV1.setMode(SliceView.MODE_SHORTCUT);
83 mSV2 = mLayout.findViewById(R.id.sv2);
84 mSV2.setMode(SliceView.MODE_SMALL);
85 mSV3 = mLayout.findViewById(R.id.sv3);
86 mSV3.setMode(SliceView.MODE_LARGE);
87 disableAnims(mLayout);
88 mHandler = new Handler();
89 ((ViewGroup) mContext.getWindow().getDecorView()).addView(mParent);
90 mParent.addView(mLayout);
91 SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
92 mSliceCreator = new SliceCreator(mContext);
93 }
94
95 private void disableAnims(View view) {
96 if (view instanceof RecyclerView) {
97 ((RecyclerView) view).setItemAnimator(null);
98 }
99 if (view instanceof ViewGroup) {
100 ViewGroup viewGroup = (ViewGroup) view;
101 for (int i = 0; i < viewGroup.getChildCount(); i++) {
102 disableAnims(viewGroup.getChildAt(i));
103 }
104 }
105 }
106
107
Jason Monk6d235262018-02-20 20:10:20 -0500108 private File getScreenshotDirectory() {
Jason Monk8f8784c2018-01-22 21:29:42 -0500109 if (sScreenshotDirectory == null) {
Jason Monk6d235262018-02-20 20:10:20 -0500110 File storage = mContext.getDataDir();
Jason Monk8f8784c2018-01-22 21:29:42 -0500111 sScreenshotDirectory = new File(storage, SCREENSHOT_DIR);
112 if (!sScreenshotDirectory.exists()) {
113 if (!sScreenshotDirectory.mkdirs()) {
114 throw new RuntimeException(
115 "Failed to create a screenshot directory.");
116 }
117 }
118 }
119 return sScreenshotDirectory;
120 }
121
122
123 private void doRender() {
124 File output = getScreenshotDirectory();
125 if (!output.exists()) {
126 output.mkdir();
127 }
128 mDoneLatch = new CountDownLatch(SliceCreator.URI_PATHS.length);
129 for (String slice : SliceCreator.URI_PATHS) {
130 doRender(slice, new File(output, String.format("%s.png", slice)));
131 }
132 Log.d(TAG, "Wrote render to " + output.getAbsolutePath());
133 mContext.runOnUiThread(new Runnable() {
134 @Override
135 public void run() {
136 ((ViewGroup) mParent.getParent()).removeView(mParent);
137 }
138 });
139 try {
140 mDoneLatch.await();
141 } catch (InterruptedException e) {
142 }
143 }
144
145 private void doRender(final String slice, final File file) {
146 Log.d(TAG, "Rendering " + slice + " to " + file.getAbsolutePath());
147
148 final Slice s = mSliceCreator.onBindSlice(SliceCreator.getUri(slice, mContext));
149
150 final CountDownLatch l = new CountDownLatch(1);
151 mContext.runOnUiThread(new Runnable() {
152 @Override
153 public void run() {
154 mSV1.setSlice(s);
155 mSV2.setSlice(s);
156 mSV3.setSlice(s);
157 mSV1.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
158 @Override
159 public void onLayoutChange(View v, int left, int top, int right, int bottom,
160 int oldLeft, int oldTop, int oldRight, int oldBottom) {
161 mSV1.removeOnLayoutChangeListener(this);
162 mSV1.postDelayed(new Runnable() {
163 @Override
164 public void run() {
165 Log.d(TAG, "Drawing " + slice);
166 Bitmap b = Bitmap.createBitmap(mLayout.getMeasuredWidth(),
167 mLayout.getMeasuredHeight(),
168 Bitmap.Config.ARGB_8888);
169
170 mLayout.draw(new Canvas(b));
171 try {
172 doCompress(slice, b, new FileOutputStream(file));
173 } catch (FileNotFoundException e) {
174 throw new RuntimeException(e);
175 }
176 l.countDown();
177 }
178 }, 10);
179 }
180 });
181 }
182 });
183 try {
184 l.await();
185 } catch (InterruptedException e) {
186 }
187 }
188
189 private void doCompress(final String slice, final Bitmap b, final FileOutputStream s) {
190 AsyncTask.execute(new Runnable() {
191 @Override
192 public void run() {
193 Log.d(TAG, "Compressing " + slice);
194 if (!b.compress(Bitmap.CompressFormat.PNG, 100, s)) {
195 throw new RuntimeException("Unable to compress");
196 }
197
198 b.recycle();
199 Log.d(TAG, "Done " + slice);
200 mDoneLatch.countDown();
201 }
202 });
203 }
204
205 public void renderAll(final Runnable runnable) {
206 final ProgressDialog dialog = ProgressDialog.show(mContext, null, "Rendering...");
207 new Thread(new Runnable() {
208 @Override
209 public void run() {
210 doRender();
211 mContext.runOnUiThread(new Runnable() {
212 @Override
213 public void run() {
214 dialog.dismiss();
215 runnable.run();
216 }
217 });
218 }
219 }).start();
220 }
221}