1 /*
2  * Copyright (C) 2020 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 com.android.launcher3.graphics;
18 
19 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
20 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
21 
22 import android.app.WallpaperColors;
23 import android.appwidget.AppWidgetProviderInfo;
24 import android.content.Context;
25 import android.hardware.display.DisplayManager;
26 import android.os.Bundle;
27 import android.os.IBinder;
28 import android.util.Log;
29 import android.view.ContextThemeWrapper;
30 import android.view.Display;
31 import android.view.SurfaceControlViewHost;
32 import android.view.SurfaceControlViewHost.SurfacePackage;
33 import android.view.View;
34 import android.view.WindowManager.LayoutParams;
35 import android.view.animation.AccelerateDecelerateInterpolator;
36 
37 import androidx.annotation.UiThread;
38 import androidx.annotation.WorkerThread;
39 
40 import com.android.launcher3.DeviceProfile;
41 import com.android.launcher3.InvariantDeviceProfile;
42 import com.android.launcher3.LauncherAppState;
43 import com.android.launcher3.LauncherSettings;
44 import com.android.launcher3.Utilities;
45 import com.android.launcher3.Workspace;
46 import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
47 import com.android.launcher3.model.BgDataModel;
48 import com.android.launcher3.model.GridSizeMigrationTaskV2;
49 import com.android.launcher3.model.LoaderTask;
50 import com.android.launcher3.model.ModelDelegate;
51 import com.android.launcher3.util.ComponentKey;
52 import com.android.launcher3.util.RunnableList;
53 import com.android.launcher3.util.Themes;
54 import com.android.launcher3.widget.LocalColorExtractor;
55 
56 import java.util.ArrayList;
57 import java.util.Map;
58 import java.util.concurrent.TimeUnit;
59 
60 /** Render preview using surface view. */
61 @SuppressWarnings("NewApi")
62 public class PreviewSurfaceRenderer {
63 
64     private static final String TAG = "PreviewSurfaceRenderer";
65 
66     private static final int FADE_IN_ANIMATION_DURATION = 200;
67 
68     private static final String KEY_HOST_TOKEN = "host_token";
69     private static final String KEY_VIEW_WIDTH = "width";
70     private static final String KEY_VIEW_HEIGHT = "height";
71     private static final String KEY_DISPLAY_ID = "display_id";
72     private static final String KEY_COLORS = "wallpaper_colors";
73 
74     private final Context mContext;
75     private final InvariantDeviceProfile mIdp;
76     private final IBinder mHostToken;
77     private final int mWidth;
78     private final int mHeight;
79     private final Display mDisplay;
80     private final WallpaperColors mWallpaperColors;
81     private final RunnableList mOnDestroyCallbacks = new RunnableList();
82 
83     private final SurfaceControlViewHost mSurfaceControlViewHost;
84 
85     private boolean mDestroyed = false;
86 
PreviewSurfaceRenderer(Context context, Bundle bundle)87     public PreviewSurfaceRenderer(Context context, Bundle bundle) throws Exception {
88         mContext = context;
89 
90         String gridName = bundle.getString("name");
91         bundle.remove("name");
92         if (gridName == null) {
93             gridName = InvariantDeviceProfile.getCurrentGridName(context);
94         }
95         mWallpaperColors = bundle.getParcelable(KEY_COLORS);
96         mIdp = new InvariantDeviceProfile(context, gridName);
97 
98         mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
99         mWidth = bundle.getInt(KEY_VIEW_WIDTH);
100         mHeight = bundle.getInt(KEY_VIEW_HEIGHT);
101         mDisplay = context.getSystemService(DisplayManager.class)
102                 .getDisplay(bundle.getInt(KEY_DISPLAY_ID));
103 
104         mSurfaceControlViewHost = MAIN_EXECUTOR
105                 .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))
106                 .get(5, TimeUnit.SECONDS);
107         mOnDestroyCallbacks.add(mSurfaceControlViewHost::release);
108     }
109 
getHostToken()110     public IBinder getHostToken() {
111         return mHostToken;
112     }
113 
getSurfacePackage()114     public SurfacePackage getSurfacePackage() {
115         return mSurfaceControlViewHost.getSurfacePackage();
116     }
117 
118     /**
119      * Destroys the preview and all associated data
120      */
121     @UiThread
destroy()122     public void destroy() {
123         mDestroyed = true;
124         mOnDestroyCallbacks.executeAllAndDestroy();
125     }
126 
127     /**
128      * Generates the preview in background
129      */
loadAsync()130     public void loadAsync() {
131         MODEL_EXECUTOR.execute(this::loadModelData);
132     }
133 
134     @WorkerThread
loadModelData()135     private void loadModelData() {
136         final boolean migrated = doGridMigrationIfNecessary();
137 
138         final Context inflationContext;
139         if (mWallpaperColors != null) {
140             // Create a themed context, without affecting the main application context
141             Context context = mContext.createDisplayContext(mDisplay);
142             if (Utilities.ATLEAST_R) {
143                 context = context.createWindowContext(
144                         LayoutParams.TYPE_APPLICATION_OVERLAY, null);
145             }
146             LocalColorExtractor.newInstance(mContext)
147                     .applyColorsOverride(context, mWallpaperColors);
148             inflationContext = new ContextThemeWrapper(context,
149                     Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
150         } else {
151             inflationContext = new ContextThemeWrapper(mContext,
152                     Themes.getActivityThemeRes(mContext));
153         }
154 
155         if (migrated) {
156             PreviewContext previewContext = new PreviewContext(inflationContext, mIdp);
157             new LoaderTask(
158                     LauncherAppState.getInstance(previewContext),
159                     null,
160                     new BgDataModel(),
161                     new ModelDelegate(), null) {
162 
163                 @Override
164                 public void run() {
165                     DeviceProfile deviceProfile = mIdp.getDeviceProfile(previewContext);
166                     String query =
167                             LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
168                             + " or " + LauncherSettings.Favorites.CONTAINER + " = "
169                             + LauncherSettings.Favorites.CONTAINER_HOTSEAT;
170                     if (deviceProfile.isTwoPanels) {
171                         query += " or " + LauncherSettings.Favorites.SCREEN + " = "
172                                 + Workspace.SECOND_SCREEN_ID;
173                     }
174                     loadWorkspace(new ArrayList<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
175                             query);
176 
177                     MAIN_EXECUTOR.execute(() -> {
178                         renderView(previewContext, mBgDataModel, mWidgetProvidersMap);
179                         mOnDestroyCallbacks.add(previewContext::onDestroy);
180                     });
181                 }
182             }.run();
183         } else {
184             LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> {
185                 if (dataModel != null) {
186                     MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null));
187                 } else {
188                     Log.e(TAG, "Model loading failed");
189                 }
190             });
191         }
192     }
193 
194     @WorkerThread
doGridMigrationIfNecessary()195     private boolean doGridMigrationIfNecessary() {
196         if (!GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp)) {
197             return false;
198         }
199         return GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp);
200     }
201 
202     @UiThread
renderView(Context inflationContext, BgDataModel dataModel, Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap)203     private void renderView(Context inflationContext, BgDataModel dataModel,
204             Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
205         if (mDestroyed) {
206             return;
207         }
208         View view = new LauncherPreviewRenderer(inflationContext, mIdp, mWallpaperColors)
209                 .getRenderedView(dataModel, widgetProviderInfoMap);
210         // This aspect scales the view to fit in the surface and centers it
211         final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
212                 mHeight / (float) view.getMeasuredHeight());
213         view.setScaleX(scale);
214         view.setScaleY(scale);
215         view.setPivotX(0);
216         view.setPivotY(0);
217         view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
218         view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
219         view.setAlpha(0);
220         view.animate().alpha(1)
221                 .setInterpolator(new AccelerateDecelerateInterpolator())
222                 .setDuration(FADE_IN_ANIMATION_DURATION)
223                 .start();
224         mSurfaceControlViewHost.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight());
225     }
226 }
227