• 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.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.database.Cursor;
26 import android.hardware.display.DisplayManager;
27 import android.os.Bundle;
28 import android.os.IBinder;
29 import android.util.Log;
30 import android.util.Size;
31 import android.util.SparseArray;
32 import android.view.ContextThemeWrapper;
33 import android.view.Display;
34 import android.view.SurfaceControlViewHost;
35 import android.view.SurfaceControlViewHost.SurfacePackage;
36 import android.view.View;
37 import android.view.WindowManager.LayoutParams;
38 import android.view.animation.AccelerateDecelerateInterpolator;
39 
40 import androidx.annotation.NonNull;
41 import androidx.annotation.Nullable;
42 import androidx.annotation.UiThread;
43 import androidx.annotation.WorkerThread;
44 
45 import com.android.launcher3.DeviceProfile;
46 import com.android.launcher3.InvariantDeviceProfile;
47 import com.android.launcher3.LauncherAppState;
48 import com.android.launcher3.LauncherSettings;
49 import com.android.launcher3.Utilities;
50 import com.android.launcher3.Workspace;
51 import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
52 import com.android.launcher3.model.BgDataModel;
53 import com.android.launcher3.model.GridSizeMigrationUtil;
54 import com.android.launcher3.model.LoaderTask;
55 import com.android.launcher3.util.ComponentKey;
56 import com.android.launcher3.util.RunnableList;
57 import com.android.launcher3.util.Themes;
58 import com.android.launcher3.widget.LocalColorExtractor;
59 
60 import java.util.ArrayList;
61 import java.util.Map;
62 import java.util.concurrent.TimeUnit;
63 
64 /** Render preview using surface view. */
65 @SuppressWarnings("NewApi")
66 public class PreviewSurfaceRenderer {
67 
68     private static final String TAG = "PreviewSurfaceRenderer";
69 
70     private static final int FADE_IN_ANIMATION_DURATION = 200;
71 
72     private static final String KEY_HOST_TOKEN = "host_token";
73     private static final String KEY_VIEW_WIDTH = "width";
74     private static final String KEY_VIEW_HEIGHT = "height";
75     private static final String KEY_DISPLAY_ID = "display_id";
76     private static final String KEY_COLORS = "wallpaper_colors";
77 
78     private final Context mContext;
79     private final InvariantDeviceProfile mIdp;
80     private final IBinder mHostToken;
81     private final int mWidth;
82     private final int mHeight;
83     private final Display mDisplay;
84     private final WallpaperColors mWallpaperColors;
85     private final RunnableList mOnDestroyCallbacks = new RunnableList();
86 
87     private final SurfaceControlViewHost mSurfaceControlViewHost;
88 
89     private boolean mDestroyed = false;
90     private LauncherPreviewRenderer mRenderer;
91     private boolean mHideQsb;
92 
PreviewSurfaceRenderer(Context context, Bundle bundle)93     public PreviewSurfaceRenderer(Context context, Bundle bundle) throws Exception {
94         mContext = context;
95 
96         String gridName = bundle.getString("name");
97         bundle.remove("name");
98         if (gridName == null) {
99             gridName = InvariantDeviceProfile.getCurrentGridName(context);
100         }
101         mWallpaperColors = bundle.getParcelable(KEY_COLORS);
102         mHideQsb = bundle.getBoolean(GridCustomizationsProvider.KEY_HIDE_BOTTOM_ROW);
103         mIdp = new InvariantDeviceProfile(context, gridName);
104 
105         mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
106         mWidth = bundle.getInt(KEY_VIEW_WIDTH);
107         mHeight = bundle.getInt(KEY_VIEW_HEIGHT);
108         mDisplay = context.getSystemService(DisplayManager.class)
109                 .getDisplay(bundle.getInt(KEY_DISPLAY_ID));
110 
111         mSurfaceControlViewHost = MAIN_EXECUTOR
112                 .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))
113                 .get(5, TimeUnit.SECONDS);
114         mOnDestroyCallbacks.add(mSurfaceControlViewHost::release);
115     }
116 
getHostToken()117     public IBinder getHostToken() {
118         return mHostToken;
119     }
120 
getSurfacePackage()121     public SurfacePackage getSurfacePackage() {
122         return mSurfaceControlViewHost.getSurfacePackage();
123     }
124 
125     /**
126      * Destroys the preview and all associated data
127      */
128     @UiThread
destroy()129     public void destroy() {
130         mDestroyed = true;
131         mOnDestroyCallbacks.executeAllAndDestroy();
132     }
133 
134     /**
135      * A function that queries for the launcher app widget span info
136      *
137      * @param context The context to get the content resolver from, should be related to launcher
138      * @return A SparseArray with the app widget id being the key and the span info being the values
139      */
140     @WorkerThread
141     @Nullable
getLoadedLauncherWidgetInfo( @onNull final Context context)142     public SparseArray<Size> getLoadedLauncherWidgetInfo(
143             @NonNull final Context context) {
144         final SparseArray<Size> widgetInfo = new SparseArray<>();
145         final String query = LauncherSettings.Favorites.ITEM_TYPE + " = "
146                 + LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
147 
148         try (Cursor c = context.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
149                 new String[] {
150                         LauncherSettings.Favorites.APPWIDGET_ID,
151                         LauncherSettings.Favorites.SPANX,
152                         LauncherSettings.Favorites.SPANY
153                 }, query, null, null)) {
154             final int appWidgetIdIndex = c.getColumnIndexOrThrow(
155                     LauncherSettings.Favorites.APPWIDGET_ID);
156             final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
157             final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
158             while (c.moveToNext()) {
159                 final int appWidgetId = c.getInt(appWidgetIdIndex);
160                 final int spanX = c.getInt(spanXIndex);
161                 final int spanY = c.getInt(spanYIndex);
162 
163                 widgetInfo.append(appWidgetId, new Size(spanX, spanY));
164             }
165         } catch (Exception e) {
166             Log.e(TAG, "Error querying for launcher widget info", e);
167             return null;
168         }
169 
170         return widgetInfo;
171     }
172 
173     /**
174      * Generates the preview in background
175      */
loadAsync()176     public void loadAsync() {
177         MODEL_EXECUTOR.execute(this::loadModelData);
178     }
179 
180     /**
181      * Hides the components in the bottom row.
182      *
183      * @param hide True to hide and false to show.
184      */
hideBottomRow(boolean hide)185     public void hideBottomRow(boolean hide) {
186         if (mRenderer != null) {
187             mRenderer.hideBottomRow(hide);
188         }
189     }
190 
191     @WorkerThread
loadModelData()192     private void loadModelData() {
193         final boolean migrated = doGridMigrationIfNecessary();
194 
195         final Context inflationContext;
196         if (mWallpaperColors != null) {
197             // Create a themed context, without affecting the main application context
198             Context context = mContext.createDisplayContext(mDisplay);
199             if (Utilities.ATLEAST_R) {
200                 context = context.createWindowContext(
201                         LayoutParams.TYPE_APPLICATION_OVERLAY, null);
202             }
203             LocalColorExtractor.newInstance(mContext)
204                     .applyColorsOverride(context, mWallpaperColors);
205             inflationContext = new ContextThemeWrapper(context,
206                     Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
207         } else {
208             inflationContext = new ContextThemeWrapper(mContext,
209                     Themes.getActivityThemeRes(mContext));
210         }
211 
212         if (migrated) {
213             PreviewContext previewContext = new PreviewContext(inflationContext, mIdp);
214             new LoaderTask(
215                     LauncherAppState.getInstance(previewContext),
216                     /* bgAllAppsList= */ null,
217                     new BgDataModel(),
218                     LauncherAppState.getInstance(previewContext).getModel().getModelDelegate(),
219                     /* results= */ null) {
220 
221                 @Override
222                 public void run() {
223                     DeviceProfile deviceProfile = mIdp.getDeviceProfile(previewContext);
224                     String query =
225                             LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
226                                     + " or " + LauncherSettings.Favorites.CONTAINER + " = "
227                                     + LauncherSettings.Favorites.CONTAINER_HOTSEAT;
228                     if (deviceProfile.isTwoPanels) {
229                         query += " or " + LauncherSettings.Favorites.SCREEN + " = "
230                                 + Workspace.SECOND_SCREEN_ID;
231                     }
232                     loadWorkspace(new ArrayList<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
233                             query);
234 
235                     final SparseArray<Size> spanInfo =
236                             getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
237 
238                     MAIN_EXECUTOR.execute(() -> {
239                         renderView(previewContext, mBgDataModel, mWidgetProvidersMap, spanInfo);
240                         mOnDestroyCallbacks.add(previewContext::onDestroy);
241                     });
242                 }
243             }.run();
244         } else {
245             LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> {
246                 if (dataModel != null) {
247                     MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null,
248                             null));
249                 } else {
250                     Log.e(TAG, "Model loading failed");
251                 }
252             });
253         }
254     }
255 
256     @WorkerThread
doGridMigrationIfNecessary()257     private boolean doGridMigrationIfNecessary() {
258         if (!GridSizeMigrationUtil.needsToMigrate(mContext, mIdp)) {
259             return false;
260         }
261         return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, mIdp);
262     }
263 
264     @UiThread
renderView(Context inflationContext, BgDataModel dataModel, Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap, @Nullable final SparseArray<Size> launcherWidgetSpanInfo)265     private void renderView(Context inflationContext, BgDataModel dataModel,
266             Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap,
267             @Nullable final SparseArray<Size> launcherWidgetSpanInfo) {
268         if (mDestroyed) {
269             return;
270         }
271         mRenderer = new LauncherPreviewRenderer(inflationContext, mIdp,
272                 mWallpaperColors, launcherWidgetSpanInfo);
273         mRenderer.hideBottomRow(mHideQsb);
274         View view = mRenderer.getRenderedView(dataModel, widgetProviderInfoMap);
275         // This aspect scales the view to fit in the surface and centers it
276         final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
277                 mHeight / (float) view.getMeasuredHeight());
278         view.setScaleX(scale);
279         view.setScaleY(scale);
280         view.setPivotX(0);
281         view.setPivotY(0);
282         view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
283         view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
284         view.setAlpha(0);
285         view.animate().alpha(1)
286                 .setInterpolator(new AccelerateDecelerateInterpolator())
287                 .setDuration(FADE_IN_ANIMATION_DURATION)
288                 .start();
289         mSurfaceControlViewHost.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight());
290     }
291 }
292