Files
Lawnchair/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
T
Sebastian Franco 1e4e50149b Reorder code so we can use different displays on the preview render
The existing code almost have the avility to use different display
for the preview by providing a different display id, this change
just make sure the InvariantDeviceProfile is created after we
create a new context with the desired display and we need to use
the default display for the SurfaceControlViewHost

Test: Manual testing, changing the display id on FixedWidthDisplayRatioFrameLayout
Bug: 292152331
Change-Id: I450d94a9a3f1a6a7b0c2b0722a11cf335122de05
2023-08-14 12:56:13 -07:00

309 lines
12 KiB
Java

/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.graphics;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.app.WallpaperColors;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.database.Cursor;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.util.Size;
import android.util.SparseArray;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceControlViewHost.SurfacePackage;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import android.view.animation.AccelerateDecelerateInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.GridSizeMigrationUtil;
import com.android.launcher3.model.LauncherBinder;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Themes;
import com.android.launcher3.widget.LocalColorExtractor;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/** Render preview using surface view. */
@SuppressWarnings("NewApi")
public class PreviewSurfaceRenderer {
private static final String TAG = "PreviewSurfaceRenderer";
private static final int FADE_IN_ANIMATION_DURATION = 200;
private static final String KEY_HOST_TOKEN = "host_token";
private static final String KEY_VIEW_WIDTH = "width";
private static final String KEY_VIEW_HEIGHT = "height";
private static final String KEY_DISPLAY_ID = "display_id";
private static final String KEY_COLORS = "wallpaper_colors";
private Context mContext;
private final IBinder mHostToken;
private final int mWidth;
private final int mHeight;
private String mGridName;
private final Display mDisplay;
private final WallpaperColors mWallpaperColors;
private final RunnableList mOnDestroyCallbacks = new RunnableList();
private final SurfaceControlViewHost mSurfaceControlViewHost;
private boolean mDestroyed = false;
private LauncherPreviewRenderer mRenderer;
private boolean mHideQsb;
public PreviewSurfaceRenderer(Context context, Bundle bundle) throws Exception {
mContext = context;
mGridName = bundle.getString("name");
bundle.remove("name");
if (mGridName == null) {
mGridName = InvariantDeviceProfile.getCurrentGridName(context);
}
mWallpaperColors = bundle.getParcelable(KEY_COLORS);
mHideQsb = bundle.getBoolean(GridCustomizationsProvider.KEY_HIDE_BOTTOM_ROW);
mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
mWidth = bundle.getInt(KEY_VIEW_WIDTH);
mHeight = bundle.getInt(KEY_VIEW_HEIGHT);
mDisplay = context.getSystemService(DisplayManager.class)
.getDisplay(bundle.getInt(KEY_DISPLAY_ID));
mSurfaceControlViewHost = MAIN_EXECUTOR.submit(() -> new SurfaceControlViewHost(mContext,
context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY),
mHostToken)).get(5, TimeUnit.SECONDS);
mOnDestroyCallbacks.add(mSurfaceControlViewHost::release);
}
public IBinder getHostToken() {
return mHostToken;
}
public SurfacePackage getSurfacePackage() {
return mSurfaceControlViewHost.getSurfacePackage();
}
/**
* Destroys the preview and all associated data
*/
@UiThread
public void destroy() {
mDestroyed = true;
mOnDestroyCallbacks.executeAllAndDestroy();
}
/**
* A function that queries for the launcher app widget span info
*
* @param context The context to get the content resolver from, should be related to launcher
* @return A SparseArray with the app widget id being the key and the span info being the values
*/
@WorkerThread
@Nullable
public SparseArray<Size> getLoadedLauncherWidgetInfo(
@NonNull final Context context) {
final SparseArray<Size> widgetInfo = new SparseArray<>();
final String query = LauncherSettings.Favorites.ITEM_TYPE + " = "
+ LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
ModelDbController mainController =
LauncherAppState.getInstance(mContext).getModel().getModelDbController();
try (Cursor c = mainController.query(TABLE_NAME,
new String[] {
LauncherSettings.Favorites.APPWIDGET_ID,
LauncherSettings.Favorites.SPANX,
LauncherSettings.Favorites.SPANY
}, query, null, null)) {
final int appWidgetIdIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.APPWIDGET_ID);
final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
while (c.moveToNext()) {
final int appWidgetId = c.getInt(appWidgetIdIndex);
final int spanX = c.getInt(spanXIndex);
final int spanY = c.getInt(spanYIndex);
widgetInfo.append(appWidgetId, new Size(spanX, spanY));
}
} catch (Exception e) {
Log.e(TAG, "Error querying for launcher widget info", e);
return null;
}
return widgetInfo;
}
/**
* Generates the preview in background
*/
public void loadAsync() {
MODEL_EXECUTOR.execute(this::loadModelData);
}
/**
* Hides the components in the bottom row.
*
* @param hide True to hide and false to show.
*/
public void hideBottomRow(boolean hide) {
if (mRenderer != null) {
mRenderer.hideBottomRow(hide);
}
}
/***
* Generates a new context overriding the theme color and the display size without affecting the
* main application context
*/
private Context getPreviewContext() {
Context context = mContext.createDisplayContext(mDisplay);
if (mWallpaperColors == null) {
return new ContextThemeWrapper(context,
Themes.getActivityThemeRes(context));
}
if (Utilities.ATLEAST_R) {
context = context.createWindowContext(
LayoutParams.TYPE_APPLICATION_OVERLAY, null);
}
LocalColorExtractor.newInstance(context)
.applyColorsOverride(context, mWallpaperColors);
return new ContextThemeWrapper(context,
Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
}
@WorkerThread
private void loadModelData() {
final Context inflationContext = getPreviewContext();
final InvariantDeviceProfile idp = new InvariantDeviceProfile(inflationContext, mGridName);
if (GridSizeMigrationUtil.needsToMigrate(inflationContext, idp)) {
// Start the migration
PreviewContext previewContext = new PreviewContext(inflationContext, idp);
// Copy existing data to preview DB
LauncherDbUtils.copyTable(LauncherAppState.getInstance(mContext)
.getModel().getModelDbController().getDb(),
TABLE_NAME,
LauncherAppState.getInstance(previewContext)
.getModel().getModelDbController().getDb(),
TABLE_NAME,
mContext);
LauncherAppState.getInstance(previewContext)
.getModel().getModelDbController().clearEmptyDbFlag();
BgDataModel bgModel = new BgDataModel();
new LoaderTask(
LauncherAppState.getInstance(previewContext),
/* bgAllAppsList= */ null,
bgModel,
LauncherAppState.getInstance(previewContext).getModel().getModelDelegate(),
new LauncherBinder(LauncherAppState.getInstance(previewContext), bgModel,
/* bgAllAppsList= */ null, new Callbacks[0])) {
@Override
public void run() {
DeviceProfile deviceProfile = idp.getDeviceProfile(previewContext);
String query =
LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
+ " or " + LauncherSettings.Favorites.CONTAINER + " = "
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT;
if (deviceProfile.isTwoPanels) {
query += " or " + LauncherSettings.Favorites.SCREEN + " = "
+ Workspace.SECOND_SCREEN_ID;
}
loadWorkspace(new ArrayList<>(), query, null);
final SparseArray<Size> spanInfo =
getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
MAIN_EXECUTOR.execute(() -> {
renderView(previewContext, mBgDataModel, mWidgetProvidersMap, spanInfo,
idp);
mOnDestroyCallbacks.add(previewContext::onDestroy);
});
}
}.run();
} else {
LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> {
if (dataModel != null) {
MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null,
null, idp));
} else {
Log.e(TAG, "Model loading failed");
}
});
}
}
@UiThread
private void renderView(Context inflationContext, BgDataModel dataModel,
Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap,
@Nullable final SparseArray<Size> launcherWidgetSpanInfo, InvariantDeviceProfile idp) {
if (mDestroyed) {
return;
}
mRenderer = new LauncherPreviewRenderer(inflationContext, idp,
mWallpaperColors, launcherWidgetSpanInfo);
mRenderer.hideBottomRow(mHideQsb);
View view = mRenderer.getRenderedView(dataModel, widgetProviderInfoMap);
// This aspect scales the view to fit in the surface and centers it
final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
mHeight / (float) view.getMeasuredHeight());
view.setScaleX(scale);
view.setScaleY(scale);
view.setPivotX(0);
view.setPivotY(0);
view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
view.setAlpha(0);
view.animate().alpha(1)
.setInterpolator(new AccelerateDecelerateInterpolator())
.setDuration(FADE_IN_ANIMATION_DURATION)
.start();
mSurfaceControlViewHost.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight());
}
}