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