Fixing widget size incorreectly updated during preview
Bug: 408934352 Bug: 228328759 Flag: EXEMPT bugfix Test: Manually verified Change-Id: Ie69e67cfaee6231da3971323ae98d2b3ab514c0d
This commit is contained in:
@@ -53,6 +53,7 @@ import com.android.launcher3.util.window.RefreshRateTracker;
|
||||
import com.android.launcher3.util.window.WindowManagerProxy;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder.WidgetHolderFactory;
|
||||
import com.android.launcher3.widget.custom.CustomWidgetManager;
|
||||
import com.android.launcher3.widget.util.WidgetSizeHandler;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
|
||||
@@ -101,8 +102,9 @@ public interface LauncherBaseAppComponent {
|
||||
InstantAppResolver getInstantAppResolver();
|
||||
DumpManager getDumpManager();
|
||||
StatsLogManager.StatsLogManagerFactory getStatsLogManagerFactory();
|
||||
ActivityContextComponent.Builder getActivityContextComponentBuilder();
|
||||
WidgetPickerComposeWrapper getWidgetPickerComposeWrapper();
|
||||
ActivityContextComponent.Builder getActivityContextComponentBuilder();
|
||||
WidgetPickerComposeWrapper getWidgetPickerComposeWrapper();
|
||||
WidgetSizeHandler getWidgetSizeHandler();
|
||||
|
||||
|
||||
/** Builder for LauncherBaseAppComponent. */
|
||||
|
||||
@@ -419,9 +419,6 @@ public class GridCustomizationsProxy implements ProxyProvider {
|
||||
this.lifeCycleTracker = lifeCycleTracker;
|
||||
this.renderer = renderer;
|
||||
lifeCycleTracker.add(() -> destroyed = true);
|
||||
// Preview grid change currently affects actual widget size. Revert grid changes
|
||||
// when preview is destroyed to make sure Launcher widgets display correctly.
|
||||
lifeCycleTracker.add(() -> renderer.updateGrid(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -116,7 +116,8 @@ public class LauncherPreviewRenderer extends BaseContext
|
||||
super(context, Themes.getActivityThemeRes(context));
|
||||
mUiHandler = new Handler(Looper.getMainLooper());
|
||||
mIdp = idp;
|
||||
mDp = getDeviceProfileForPreview(context).toBuilder(context).build();
|
||||
mDp = getDeviceProfileForPreview(context).toBuilder(context)
|
||||
.setViewScaleProvider(new PreviewScaleProvider(this)).build();
|
||||
mInsets = getInsets(context);
|
||||
mDp.updateInsets(mInsets);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.launcher3.preview
|
||||
|
||||
import android.appwidget.AppWidgetProviderInfo
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import com.android.launcher3.InvariantDeviceProfile
|
||||
@@ -29,7 +30,6 @@ import com.android.launcher3.dagger.AppModule
|
||||
import com.android.launcher3.dagger.LauncherAppComponent
|
||||
import com.android.launcher3.dagger.LauncherAppSingleton
|
||||
import com.android.launcher3.dagger.LauncherComponentProvider.appComponent
|
||||
import com.android.launcher3.dagger.LauncherComponentProvider.get
|
||||
import com.android.launcher3.dagger.LauncherConcurrencyModule
|
||||
import com.android.launcher3.dagger.PerDisplayModule
|
||||
import com.android.launcher3.dagger.PluginManagerWrapperModule
|
||||
@@ -47,6 +47,7 @@ import com.android.launcher3.util.SandboxContext
|
||||
import com.android.launcher3.util.dagger.LauncherExecutorsModule
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder.WidgetHolderFactory
|
||||
import com.android.launcher3.widget.util.WidgetSizeHandler
|
||||
import com.android.systemui.shared.Flags
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
@@ -90,19 +91,21 @@ constructor(
|
||||
else selectionForWorkspaceScreen(workspacePageId)
|
||||
|
||||
val builder = DaggerPreviewContext_PreviewAppComponent.builder().bindPrefs(prefs)
|
||||
builder.bindLoaderParams(
|
||||
LoaderParams(
|
||||
workspaceSelection = selectionQuery,
|
||||
sanitizeData = false,
|
||||
loadNonWorkspaceItems = false,
|
||||
builder
|
||||
.bindLoaderParams(
|
||||
LoaderParams(
|
||||
workspaceSelection = selectionQuery,
|
||||
sanitizeData = false,
|
||||
loadNonWorkspaceItems = false,
|
||||
)
|
||||
)
|
||||
)
|
||||
.bindWidgetSizeHandler(NoOpWidgetSizeHandler(this))
|
||||
|
||||
if (layoutXml.isNullOrEmpty() || !Flags.extendibleThemeManager()) {
|
||||
mDbDir = null
|
||||
builder
|
||||
.bindParserFactory(LayoutParserFactory(this))
|
||||
.bindWidgetsFactory(get(base).widgetHolderFactory)
|
||||
.bindWidgetsFactory(base.appComponent.widgetHolderFactory)
|
||||
} else {
|
||||
mDbDir = File(base.filesDir, randomUid)
|
||||
emptyDbDir()
|
||||
@@ -139,8 +142,19 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDatabasePath(name: String): File {
|
||||
return if (mDbDir != null) File(mDbDir, name) else super.getDatabasePath(name)
|
||||
override fun getDatabasePath(name: String): File =
|
||||
if (mDbDir != null) File(mDbDir, name) else super.getDatabasePath(name)
|
||||
|
||||
private class NoOpWidgetSizeHandler(context: Context) : WidgetSizeHandler(context) {
|
||||
|
||||
override fun updateSizeRangesAsync(
|
||||
widgetId: Int,
|
||||
info: AppWidgetProviderInfo,
|
||||
spanX: Int,
|
||||
spanY: Int,
|
||||
) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
@LauncherAppSingleton // Exclude widget module since we bind widget holder separately
|
||||
@@ -175,6 +189,8 @@ constructor(
|
||||
|
||||
@BindsInstance fun bindLoaderParams(params: LoaderParams): Builder
|
||||
|
||||
@BindsInstance fun bindWidgetSizeHandler(handler: WidgetSizeHandler): Builder
|
||||
|
||||
override fun build(): PreviewAppComponent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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.preview
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.graphics.PointF
|
||||
import android.util.SizeF
|
||||
import com.android.launcher3.DeviceProfile.DEFAULT_SCALE
|
||||
import com.android.launcher3.DeviceProfile.ViewScaleProvider
|
||||
import com.android.launcher3.model.data.ItemInfo
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo
|
||||
import com.android.launcher3.views.ActivityContext
|
||||
import com.android.launcher3.widget.util.WidgetSizeHandler.Companion.getWidgetSizeList
|
||||
import com.android.launcher3.widget.util.WidgetSizes
|
||||
import kotlin.math.pow
|
||||
|
||||
/** A [ViewScaleProvider] which scales widgets based on existing widget size */
|
||||
class PreviewScaleProvider(val target: ActivityContext) : ViewScaleProvider {
|
||||
|
||||
override fun getScaleFromItemInfo(info: ItemInfo?): PointF {
|
||||
if (info !is LauncherAppWidgetInfo) {
|
||||
return DEFAULT_SCALE
|
||||
}
|
||||
|
||||
val density = target.asContext().resources.displayMetrics.density
|
||||
if (density == 0f) return DEFAULT_SCALE
|
||||
|
||||
val existingSizes: List<SizeF>
|
||||
try {
|
||||
existingSizes =
|
||||
AppWidgetManager.getInstance(target.asContext())
|
||||
.getAppWidgetOptions(info.appWidgetId)
|
||||
.getWidgetSizeList() ?: return DEFAULT_SCALE
|
||||
} catch (e: Exception) {
|
||||
// Failed to get widget options, ignore
|
||||
return DEFAULT_SCALE
|
||||
}
|
||||
|
||||
val expectedSize =
|
||||
WidgetSizes.getWidgetSizePx(target.deviceProfile, info.spanX, info.spanY).let {
|
||||
SizeF(it.width / density, it.height / density)
|
||||
}
|
||||
if (expectedSize.height <= 0 || expectedSize.width <= 0) return DEFAULT_SCALE
|
||||
|
||||
// Find the size which is closest to the expected size
|
||||
val bestOriginalSize =
|
||||
existingSizes
|
||||
.asSequence()
|
||||
.filter { it.height > 0 && it.width > 0 }
|
||||
.minByOrNull {
|
||||
(it.width - expectedSize.width).pow(2) +
|
||||
(it.height - expectedSize.height).pow(2)
|
||||
} ?: return DEFAULT_SCALE
|
||||
|
||||
return PointF(
|
||||
expectedSize.width / bestOriginalSize.width,
|
||||
expectedSize.height / bestOriginalSize.height,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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.widget.util
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_SIZES
|
||||
import android.appwidget.AppWidgetProviderInfo
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.SizeF
|
||||
import com.android.launcher3.dagger.ApplicationContext
|
||||
import com.android.launcher3.util.Executors
|
||||
import javax.inject.Inject
|
||||
|
||||
/** Helper class for handling widget updates */
|
||||
open class WidgetSizeHandler @Inject constructor(@ApplicationContext private val context: Context) {
|
||||
|
||||
/**
|
||||
* Updates the widget size range if it is not currently the same. This makes two binder calls,
|
||||
* one for getting the existing options, [AppWidgetManager.getAppWidgetOptions] and if it
|
||||
* doesn't match the expected value, another call to update it,
|
||||
* [AppWidgetManager.updateAppWidgetOptions].
|
||||
*
|
||||
* Note that updating the options is a costly call as it wakes up the provider process and
|
||||
* causes a full widget update, hence two binder calls are preferable over unnecessarily
|
||||
* updating the widget options.
|
||||
*/
|
||||
open fun updateSizeRangesAsync(
|
||||
widgetId: Int,
|
||||
info: AppWidgetProviderInfo,
|
||||
spanX: Int,
|
||||
spanY: Int,
|
||||
) {
|
||||
Executors.UI_HELPER_EXECUTOR.execute {
|
||||
val widgetManager = AppWidgetManager.getInstance(context)
|
||||
val sizeOptions = WidgetSizes.getWidgetSizeOptions(context, info.provider, spanX, spanY)
|
||||
if (
|
||||
sizeOptions.getWidgetSizeList() !=
|
||||
widgetManager.getAppWidgetOptions(widgetId).getWidgetSizeList()
|
||||
)
|
||||
widgetManager.updateAppWidgetOptions(widgetId, sizeOptions)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun Bundle.getWidgetSizeList() = getParcelableArrayList<SizeF>(OPTION_APPWIDGET_SIZES)
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package com.android.launcher3.widget.util;
|
||||
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
|
||||
import android.appwidget.AppWidgetHostView;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
@@ -25,13 +23,13 @@ import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.Size;
|
||||
import android.util.SizeF;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.dagger.LauncherComponentProvider;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -110,17 +108,8 @@ public final class WidgetSizes {
|
||||
return;
|
||||
}
|
||||
|
||||
UI_HELPER_EXECUTOR.execute(() -> {
|
||||
AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
|
||||
Bundle sizeOptions = getWidgetSizeOptions(context, info.provider, spanX, spanY);
|
||||
if (sizeOptions.<SizeF>getParcelableArrayList(
|
||||
AppWidgetManager.OPTION_APPWIDGET_SIZES).equals(
|
||||
widgetManager.getAppWidgetOptions(widgetId).<SizeF>getParcelableArrayList(
|
||||
AppWidgetManager.OPTION_APPWIDGET_SIZES))) {
|
||||
return;
|
||||
}
|
||||
widgetManager.updateAppWidgetOptions(widgetId, sizeOptions);
|
||||
});
|
||||
LauncherComponentProvider.get(context).getWidgetSizeHandler()
|
||||
.updateSizeRangesAsync(widgetId, info, spanX, spanY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,8 +126,6 @@ public final class WidgetSizes {
|
||||
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, rect.right);
|
||||
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, rect.bottom);
|
||||
options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, paddedSizes);
|
||||
Log.d("b/267448330", "provider: " + provider + ", paddedSizes: " + paddedSizes
|
||||
+ ", getMinMaxSizes: " + rect);
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user