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