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