dec4305d0c
The majority of the logic in grid size migration should be implemented as static functions in the first place since (1) they only runs once after instantiation and (2) they are executed immediately after they are instantiated. This CL removes most of the instance variables from grid size migration in favor of static methods since the later is more efficient, more testable and therefore considered better programming practice in general. Bug: 256859723 Test: atest GridSizeMigrationUtilTest Change-Id: Ief4654d79ad5cfd636e6145fdcc9dfe5a39cdf62
276 lines
11 KiB
Java
276 lines
11 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 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.GridSizeMigrationUtil;
|
|
import com.android.launcher3.model.LoaderTask;
|
|
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 final Context mContext;
|
|
private final InvariantDeviceProfile mIdp;
|
|
private final IBinder mHostToken;
|
|
private final int mWidth;
|
|
private final int mHeight;
|
|
private final Display mDisplay;
|
|
private final WallpaperColors mWallpaperColors;
|
|
private final RunnableList mOnDestroyCallbacks = new RunnableList();
|
|
|
|
private final SurfaceControlViewHost mSurfaceControlViewHost;
|
|
|
|
private boolean mDestroyed = false;
|
|
|
|
public PreviewSurfaceRenderer(Context context, Bundle bundle) throws Exception {
|
|
mContext = context;
|
|
|
|
String gridName = bundle.getString("name");
|
|
bundle.remove("name");
|
|
if (gridName == null) {
|
|
gridName = InvariantDeviceProfile.getCurrentGridName(context);
|
|
}
|
|
mWallpaperColors = bundle.getParcelable(KEY_COLORS);
|
|
mIdp = new InvariantDeviceProfile(context, gridName);
|
|
|
|
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, mDisplay, 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;
|
|
|
|
try (Cursor c = context.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
|
|
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);
|
|
}
|
|
|
|
@WorkerThread
|
|
private void loadModelData() {
|
|
final boolean migrated = doGridMigrationIfNecessary();
|
|
|
|
final Context inflationContext;
|
|
if (mWallpaperColors != null) {
|
|
// Create a themed context, without affecting the main application context
|
|
Context context = mContext.createDisplayContext(mDisplay);
|
|
if (Utilities.ATLEAST_R) {
|
|
context = context.createWindowContext(
|
|
LayoutParams.TYPE_APPLICATION_OVERLAY, null);
|
|
}
|
|
LocalColorExtractor.newInstance(mContext)
|
|
.applyColorsOverride(context, mWallpaperColors);
|
|
inflationContext = new ContextThemeWrapper(context,
|
|
Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
|
|
} else {
|
|
inflationContext = new ContextThemeWrapper(mContext,
|
|
Themes.getActivityThemeRes(mContext));
|
|
}
|
|
|
|
if (migrated) {
|
|
PreviewContext previewContext = new PreviewContext(inflationContext, mIdp);
|
|
new LoaderTask(
|
|
LauncherAppState.getInstance(previewContext),
|
|
/* bgAllAppsList= */ null,
|
|
new BgDataModel(),
|
|
LauncherAppState.getInstance(previewContext).getModel().getModelDelegate(),
|
|
/* results= */ null) {
|
|
|
|
@Override
|
|
public void run() {
|
|
DeviceProfile deviceProfile = mIdp.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<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
|
|
query);
|
|
|
|
final SparseArray<Size> spanInfo =
|
|
getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
|
|
|
|
MAIN_EXECUTOR.execute(() -> {
|
|
renderView(previewContext, mBgDataModel, mWidgetProvidersMap, spanInfo);
|
|
mOnDestroyCallbacks.add(previewContext::onDestroy);
|
|
});
|
|
}
|
|
}.run();
|
|
} else {
|
|
LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> {
|
|
if (dataModel != null) {
|
|
MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null,
|
|
null));
|
|
} else {
|
|
Log.e(TAG, "Model loading failed");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
@WorkerThread
|
|
private boolean doGridMigrationIfNecessary() {
|
|
if (!GridSizeMigrationUtil.needsToMigrate(mContext, mIdp)) {
|
|
return false;
|
|
}
|
|
return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, mIdp);
|
|
}
|
|
|
|
@UiThread
|
|
private void renderView(Context inflationContext, BgDataModel dataModel,
|
|
Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap,
|
|
@Nullable final SparseArray<Size> launcherWidgetSpanInfo) {
|
|
if (mDestroyed) {
|
|
return;
|
|
}
|
|
View view = new LauncherPreviewRenderer(inflationContext, mIdp, mWallpaperColors,
|
|
launcherWidgetSpanInfo).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());
|
|
}
|
|
}
|