blob: eb45be133eef935a904cacb5264730c4843c17a8 [file] [log] [blame]
Sunny Goyalef92b822018-11-21 14:12:00 -08001/*
2 * Copyright (C) 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 */
16package com.android.launcher3.graphics;
17
18import static android.view.View.MeasureSpec.EXACTLY;
19import static android.view.View.MeasureSpec.makeMeasureSpec;
20import static android.view.View.VISIBLE;
21
22import android.annotation.TargetApi;
23import android.app.Fragment;
Santiago Etchebehere3fc41e72019-01-17 13:25:59 -080024import android.app.WallpaperManager;
Sunny Goyalef92b822018-11-21 14:12:00 -080025import android.content.Context;
26import android.content.Intent;
27import android.content.res.TypedArray;
28import android.graphics.Bitmap;
29import android.graphics.Canvas;
30import android.graphics.Color;
31import android.graphics.Rect;
32import android.graphics.drawable.AdaptiveIconDrawable;
Santiago Etchebehere3fc41e72019-01-17 13:25:59 -080033import android.graphics.drawable.BitmapDrawable;
Sunny Goyalef92b822018-11-21 14:12:00 -080034import android.graphics.drawable.ColorDrawable;
35import android.os.Build;
36import android.os.Handler;
37import android.os.Looper;
38import android.os.Process;
39import android.util.AttributeSet;
40import android.util.Log;
41import android.view.ContextThemeWrapper;
42import android.view.LayoutInflater;
43import android.view.View;
44import android.view.ViewGroup;
45import android.widget.TextClock;
46
47import com.android.launcher3.BubbleTextView;
48import com.android.launcher3.CellLayout;
49import com.android.launcher3.DeviceProfile;
50import com.android.launcher3.Hotseat;
51import com.android.launcher3.InsettableFrameLayout;
52import com.android.launcher3.InvariantDeviceProfile;
53import com.android.launcher3.LauncherSettings.Favorites;
54import com.android.launcher3.R;
55import com.android.launcher3.ShortcutInfo;
56import com.android.launcher3.Utilities;
57import com.android.launcher3.WorkspaceLayoutManager;
58import com.android.launcher3.allapps.SearchUiManager;
59import com.android.launcher3.config.FeatureFlags;
60import com.android.launcher3.icons.BaseIconFactory;
61import com.android.launcher3.icons.BitmapInfo;
62import com.android.launcher3.icons.BitmapRenderer;
63import com.android.launcher3.views.ActivityContext;
64import com.android.launcher3.views.BaseDragLayer;
65
Sunny Goyaleff44f32019-01-09 17:29:49 -080066import java.util.concurrent.Callable;
Sunny Goyalef92b822018-11-21 14:12:00 -080067import java.util.concurrent.CountDownLatch;
68
69/**
70 * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
71 * Steps:
72 * 1) Create a dummy icon info with just white icon
73 * 2) Inflate a strip down layout definition for Launcher
74 * 3) Place appropriate elements like icons and first-page qsb
75 * 4) Measure and draw the view on a canvas
76 */
77@TargetApi(Build.VERSION_CODES.O)
Sunny Goyaleff44f32019-01-09 17:29:49 -080078public class LauncherPreviewRenderer implements Callable<Bitmap> {
Sunny Goyalef92b822018-11-21 14:12:00 -080079
80 private static final String TAG = "LauncherPreviewRenderer";
81
82 private final Handler mUiHandler;
83 private final Context mContext;
84 private final InvariantDeviceProfile mIdp;
85 private final DeviceProfile mDp;
86 private final Rect mInsets;
87
88 private final ShortcutInfo mShortcutInfo;
89
90 public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp) {
91 mUiHandler = new Handler(Looper.getMainLooper());
92 mContext = context;
93 mIdp = idp;
94 mDp = idp.portraitProfile.copy(context);
95
96 // TODO: get correct insets once display cutout API is available.
97 mInsets = new Rect();
98 mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2;
99 mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2;
100 mDp.updateInsets(mInsets);
101
102 BaseIconFactory iconFactory =
103 new BaseIconFactory(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize) { };
104 BitmapInfo iconInfo = iconFactory.createBadgedIconBitmap(new AdaptiveIconDrawable(
105 new ColorDrawable(Color.WHITE), new ColorDrawable(Color.WHITE)),
106 Process.myUserHandle(),
107 Build.VERSION.SDK_INT);
108
109 mShortcutInfo = new ShortcutInfo();
110 mShortcutInfo.applyFrom(iconInfo);
111 mShortcutInfo.intent = new Intent();
112 mShortcutInfo.contentDescription = mShortcutInfo.title =
113 context.getString(R.string.label_application);
114 }
115
Sunny Goyaleff44f32019-01-09 17:29:49 -0800116 @Override
117 public Bitmap call() {
Sunny Goyalef92b822018-11-21 14:12:00 -0800118 return BitmapRenderer.createHardwareBitmap(mDp.widthPx, mDp.heightPx, c -> {
119
120 if (Looper.myLooper() == Looper.getMainLooper()) {
121 new MainThreadRenderer(mContext).renderScreenShot(c);
122 } else {
123 CountDownLatch latch = new CountDownLatch(1);
124 Utilities.postAsyncCallback(mUiHandler, () -> {
125 new MainThreadRenderer(mContext).renderScreenShot(c);
126 latch.countDown();
127 });
128
129 try {
130 latch.await();
131 } catch (Exception e) {
132 Log.e(TAG, "Error drawing on main thread", e);
133 }
134 }
135 });
136 }
137
138 private class MainThreadRenderer extends ContextThemeWrapper
139 implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
140
141 private final LayoutInflater mHomeElementInflater;
142 private final InsettableFrameLayout mRootView;
143
144 private final Hotseat mHotseat;
145 private final CellLayout mWorkspace;
146
147 MainThreadRenderer(Context context) {
148 super(context, R.style.AppTheme);
149
150 mHomeElementInflater = LayoutInflater.from(
151 new ContextThemeWrapper(this, R.style.HomeScreenElementTheme));
152 mHomeElementInflater.setFactory2(this);
153
154 mRootView = (InsettableFrameLayout) mHomeElementInflater.inflate(
155 R.layout.launcher_preview_layout, null, false);
156 mRootView.setInsets(mInsets);
157 measureView(mRootView, mDp.widthPx, mDp.heightPx);
158
159 mHotseat = mRootView.findViewById(R.id.hotseat);
160 mHotseat.resetLayout(false);
161
162 mWorkspace = mRootView.findViewById(R.id.workspace);
163 mWorkspace.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
164 mDp.workspacePadding.top,
165 mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
166 mDp.workspacePadding.bottom);
167 }
168
169 @Override
170 public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
171 if ("TextClock".equals(name)) {
172 // Workaround for TextClock accessing handler for unregistering ticker.
173 return new TextClock(context, attrs) {
174
175 @Override
176 public Handler getHandler() {
177 return mUiHandler;
178 }
179 };
180 } else if (!"fragment".equals(name)) {
181 return null;
182 }
183
184 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PreviewFragment);
185 FragmentWithPreview f = (FragmentWithPreview) Fragment.instantiate(
186 context, ta.getString(R.styleable.PreviewFragment_android_name));
187 f.enterPreviewMode(context);
188 f.onInit(null);
189
190 View view = f.onCreateView(LayoutInflater.from(context), (ViewGroup) parent, null);
191 view.setId(ta.getInt(R.styleable.PreviewFragment_android_id, View.NO_ID));
192 return view;
193 }
194
195 @Override
196 public View onCreateView(String name, Context context, AttributeSet attrs) {
197 return onCreateView(null, name, context, attrs);
198 }
199
200 @Override
201 public BaseDragLayer getDragLayer() {
202 throw new UnsupportedOperationException();
203 }
204
205 @Override
206 public DeviceProfile getDeviceProfile() {
207 return mDp;
208 }
209
210 @Override
211 public Hotseat getHotseat() {
212 return mHotseat;
213 }
214
215 @Override
216 public CellLayout getScreenWithId(int screenId) {
217 return mWorkspace;
218 }
219
220 private void inflateAndAddIcon(ShortcutInfo info) {
221 BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate(
222 R.layout.app_icon, mWorkspace, false);
223 icon.applyFromShortcutInfo(info);
224 addInScreenFromBind(icon, info);
225 }
226
227 private void dispatchVisibilityAggregated(View view, boolean isVisible) {
228 // Similar to View.dispatchVisibilityAggregated implementation.
229 final boolean thisVisible = view.getVisibility() == VISIBLE;
230 if (thisVisible || !isVisible) {
231 view.onVisibilityAggregated(isVisible);
232 }
233
234 if (view instanceof ViewGroup) {
235 isVisible = thisVisible && isVisible;
236 ViewGroup vg = (ViewGroup) view;
237 int count = vg.getChildCount();
238
239 for (int i = 0; i < count; i++) {
240 dispatchVisibilityAggregated(vg.getChildAt(i), isVisible);
241 }
242 }
243 }
244
245 private void renderScreenShot(Canvas canvas) {
246 // Add hotseat icons
247 for (int i = 0; i < mIdp.numHotseatIcons; i++) {
248 ShortcutInfo info = new ShortcutInfo(mShortcutInfo);
249 info.container = Favorites.CONTAINER_HOTSEAT;
250 info.screenId = i;
251 inflateAndAddIcon(info);
252 }
253
254 // Add workspace icons
255 for (int i = 0; i < mIdp.numColumns; i++) {
256 ShortcutInfo info = new ShortcutInfo(mShortcutInfo);
257 info.container = Favorites.CONTAINER_DESKTOP;
258 info.screenId = 0;
259 info.cellX = i;
260 info.cellY = mIdp.numRows - 1;
261 inflateAndAddIcon(info);
262 }
263
264 // Add first page QSB
Jon Miranda7143ba62019-03-15 09:00:05 -0700265 if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
Sunny Goyalef92b822018-11-21 14:12:00 -0800266 View qsb = mHomeElementInflater.inflate(
267 R.layout.search_container_workspace, mWorkspace, false);
268 CellLayout.LayoutParams lp =
269 new CellLayout.LayoutParams(0, 0, mWorkspace.getCountX(), 1);
270 lp.canReorder = false;
271 mWorkspace.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
272 }
273
274 // Setup search view
275 SearchUiManager searchUiManager =
276 mRootView.findViewById(R.id.search_container_all_apps);
277 mRootView.findViewById(R.id.apps_view).setTranslationY(
278 mDp.heightPx - searchUiManager.getScrollRangeDelta(mInsets));
279
280 measureView(mRootView, mDp.widthPx, mDp.heightPx);
281 dispatchVisibilityAggregated(mRootView, true);
282 measureView(mRootView, mDp.widthPx, mDp.heightPx);
283 // Additional measure for views which use auto text size API
284 measureView(mRootView, mDp.widthPx, mDp.heightPx);
285
Sunny Goyalef92b822018-11-21 14:12:00 -0800286 mRootView.draw(canvas);
287 dispatchVisibilityAggregated(mRootView, false);
288 }
289 }
290
291 private static void measureView(View view, int width, int height) {
292 view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
293 view.layout(0, 0, width, height);
294 }
295}