1 /*
2  * Copyright (C) 2019 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 package com.android.keyguard.clock;
17 
18 import android.annotation.Nullable;
19 import android.graphics.Bitmap;
20 import android.graphics.Canvas;
21 import android.graphics.Color;
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.util.Log;
25 import android.view.View;
26 import android.view.ViewGroup;
27 
28 import java.util.concurrent.Callable;
29 import java.util.concurrent.FutureTask;
30 
31 /**
32  * Creates a preview image ({@link Bitmap}) of a {@link View} for a custom clock face.
33  */
34 final class ViewPreviewer {
35 
36     private static final String TAG = "ViewPreviewer";
37 
38     /**
39      * Handler used to run {@link View#draw(Canvas)} on the main thread.
40      */
41     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
42 
43     /**
44      * Generate a realistic preview of a clock face.
45      *
46      * @param view view is used to generate preview image.
47      * @param width width of the preview image, should be the same as device width in pixels.
48      * @param height height of the preview image, should be the same as device height in pixels.
49      * @return bitmap of view.
50      */
51     @Nullable
createPreview(View view, int width, int height)52     Bitmap createPreview(View view, int width, int height) {
53         if (view == null) {
54             return null;
55         }
56         FutureTask<Bitmap> task = new FutureTask<>(new Callable<Bitmap>() {
57             @Override
58             public Bitmap call() {
59                 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
60 
61                 // Draw clock view hierarchy to canvas.
62                 Canvas canvas = new Canvas(bitmap);
63                 canvas.drawColor(Color.BLACK);
64                 dispatchVisibilityAggregated(view, true);
65                 view.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
66                         View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
67                 view.layout(0, 0, width, height);
68                 view.draw(canvas);
69 
70                 return bitmap;
71             }
72         });
73 
74         if (Looper.myLooper() == Looper.getMainLooper()) {
75             task.run();
76         } else {
77             mMainHandler.post(task);
78         }
79 
80         try {
81             return task.get();
82         } catch (Exception e) {
83             Log.e(TAG, "Error completing task", e);
84             return null;
85         }
86     }
87 
dispatchVisibilityAggregated(View view, boolean isVisible)88     private void dispatchVisibilityAggregated(View view, boolean isVisible) {
89         // Similar to View.dispatchVisibilityAggregated implementation.
90         final boolean thisVisible = view.getVisibility() == View.VISIBLE;
91         if (thisVisible || !isVisible) {
92             view.onVisibilityAggregated(isVisible);
93         }
94 
95         if (view instanceof ViewGroup) {
96             isVisible = thisVisible && isVisible;
97             ViewGroup vg = (ViewGroup) view;
98             int count = vg.getChildCount();
99 
100             for (int i = 0; i < count; i++) {
101                 dispatchVisibilityAggregated(vg.getChildAt(i), isVisible);
102             }
103         }
104     }
105 }
106