diff --git a/res/values/config.xml b/res/values/config.xml index 89415b89a3..8184c98d3d 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -90,6 +90,7 @@ + @@ -188,4 +189,8 @@ + + + + diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 87fb6fb7a1..0cc965c6c6 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -186,7 +186,6 @@ public class Workspace extends PagedView @Thunk final Launcher mLauncher; @Thunk DragController mDragController; - private final Rect mTempRect = new Rect(); private final int[] mTempXY = new int[2]; private final float[] mTempFXY = new float[2]; @Thunk float[] mDragViewVisualCenter = new float[2]; @@ -367,10 +366,19 @@ public class Workspace extends PagedView } public float getWallpaperOffsetForCenterPage() { - int pageScroll = getScrollForPage(getPageNearestToCenterOfScreen()); + return getWallpaperOffsetForPage(getPageNearestToCenterOfScreen()); + } + + private float getWallpaperOffsetForPage(int page) { + int pageScroll = getScrollForPage(page); return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll); } + /** Returns the number of pages used for the wallpaper parallax. */ + public int getNumPagesForWallpaperParallax() { + return mWallpaperOffset.getNumPagesForWallpaperParallax(); + } + public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) { Rect r = new Rect(); cl.cellToRect(hCell, vCell, hSpan, vSpan, r); diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java index c9fb956617..8d676c9fa3 100644 --- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java +++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java @@ -88,7 +88,6 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat { private PreferenceCategory mPluginsCategory; private FlagTogglerPrefUi mFlagTogglerPrefUi; - @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java index 2ad80cf665..b8554e418c 100644 --- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java +++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java @@ -145,14 +145,17 @@ public class WallpaperOffsetInterpolator extends BroadcastReceiver { msg.sendToTarget(); } - private void updateOffset() { - int numPagesForWallpaperParallax; + /** Returns the number of pages used for the wallpaper parallax. */ + public int getNumPagesForWallpaperParallax() { if (mWallpaperIsLiveWallpaper) { - numPagesForWallpaperParallax = mNumScreens; + return mNumScreens; } else { - numPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens); + return Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens); } - Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, numPagesForWallpaperParallax, 0, + } + + private void updateOffset() { + Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, getNumPagesForWallpaperParallax(), 0, mWindowToken).sendToTarget(); } diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index 3285c187c0..df012959fa 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -16,12 +16,17 @@ package com.android.launcher3.widget; +import android.app.WallpaperManager; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Rect; +import android.graphics.RectF; import android.os.Handler; import android.os.SystemClock; +import android.util.Log; import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -32,10 +37,13 @@ import android.widget.AdapterView; import android.widget.Advanceable; import android.widget.RemoteViews; +import androidx.annotation.Nullable; + import com.android.launcher3.CheckLongPressHelper; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.Workspace; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; @@ -43,11 +51,16 @@ import com.android.launcher3.util.Executors; import com.android.launcher3.util.Themes; import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener; +import java.util.List; + /** * {@inheritDoc} */ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView - implements TouchCompleteListener, View.OnLongClickListener { + implements TouchCompleteListener, View.OnLongClickListener, + LocalColorExtractor.Listener { + + private static final String LOG_TAG = "LauncherAppWidgetHostView"; // Related to the auto-advancing of widgets private static final long ADVANCE_INTERVAL = 20000; @@ -60,18 +73,29 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView private final CheckLongPressHelper mLongPressHelper; protected final Launcher mLauncher; + private final Workspace mWorkspace; + private final WallpaperManager mWallpaperManager; @ViewDebug.ExportedProperty(category = "launcher") private boolean mReinflateOnConfigChange; + // Maintain the color manager. + private final LocalColorExtractor mColorExtractor; + private boolean mIsScrollable; private boolean mIsAttachedToWindow; private boolean mIsAutoAdvanceRegistered; private Runnable mAutoAdvanceRunnable; + private RectF mLastLocationRegistered = null; + // Used to store the widget size during onLayout. + private final Rect mCurrentWidgetSize = new Rect(); + private final RectF mTempRectF = new RectF(); + private final boolean mIsRtl; public LauncherAppWidgetHostView(Context context) { super(context); mLauncher = Launcher.getLauncher(context); + mWorkspace = mLauncher.getWorkspace(); mLongPressHelper = new CheckLongPressHelper(this, this); mInflater = LayoutInflater.from(context); setAccessibilityDelegate(mLauncher.getAccessibilityDelegate()); @@ -81,6 +105,19 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) { setOnLightBackground(true); } + mIsRtl = Utilities.isRtl(context.getResources()); + mWallpaperManager = WallpaperManager.getInstance(getContext()); + mColorExtractor = LocalColorExtractor.newInstance(getContext()); + mColorExtractor.setListener(this); + } + + @Override + public void setColorResources(@Nullable SparseIntArray colors) { + if (colors == null) { + resetColorResources(); + } else { + super.setColorResources(colors); + } } @Override @@ -167,6 +204,7 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView // state is updated. So isAttachedToWindow() will return true until next frame. mIsAttachedToWindow = false; checkIfAutoAdvance(); + mColorExtractor.removeLocations(); } @Override @@ -213,6 +251,78 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView } mIsScrollable = checkScrollableRecursively(this); + + mCurrentWidgetSize.left = left; + mCurrentWidgetSize.top = top; + mCurrentWidgetSize.right = right; + mCurrentWidgetSize.bottom = bottom; + updateColorExtraction(mCurrentWidgetSize); + } + + private void updateColorExtraction(Rect widgetLocation) { + // If the widget hasn't been measured and laid out, we cannot do this. + if (widgetLocation.isEmpty()) { + return; + } + LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag(); + if (info != null) { + int screenWidth = mLauncher.getDeviceProfile().widthPx; + int screenHeight = mLauncher.getDeviceProfile().heightPx; + int numScreens = mWorkspace.getNumPagesForWallpaperParallax(); + int screenId = mIsRtl ? numScreens - info.screenId : info.screenId; + float relativeScreenWidth = 1f / numScreens; + float absoluteTop = widgetLocation.top; + float absoluteBottom = widgetLocation.bottom; + for (View v = (View) getParent(); + v != null && v.getId() != R.id.launcher; + v = (View) v.getParent()) { + absoluteBottom += v.getTop(); + absoluteTop += v.getTop(); + } + float xOffset = 0; + View parentView = (View) getParent(); + // The layout depends on the orientation. + if (getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE) { + xOffset = screenHeight - mWorkspace.getPaddingRight() + - parentView.getWidth(); + } else { + xOffset = mWorkspace.getPaddingLeft() + parentView.getPaddingLeft(); + } + // This is the position of the widget relative to the wallpaper, as expected by the + // local color extraction of the WallpaperManager. + // The coordinate system is such that, on the horizontal axis, each screen has a + // distinct range on the [0,1] segment. So if there are 3 screens, they will have the + // ranges [0, 1/3], [1/3, 2/3] and [2/3, 1]. The position on the subrange should be + // the position of the widget relative to the screen. For the vertical axis, this is + // simply the location of the widget relative to the screen. + mTempRectF.left = ((widgetLocation.left + xOffset) / screenWidth + screenId) + * relativeScreenWidth; + mTempRectF.right = ((widgetLocation.right + xOffset) / screenWidth + screenId) + * relativeScreenWidth; + mTempRectF.top = absoluteTop / screenHeight; + mTempRectF.bottom = absoluteBottom / screenHeight; + if (mTempRectF.left < 0 || mTempRectF.right > 1 || mTempRectF.top < 0 + || mTempRectF.bottom > 1) { + Log.e(LOG_TAG, " Error, invalid relative position"); + return; + } + if (!mTempRectF.equals(mLastLocationRegistered)) { + if (mLastLocationRegistered != null) { + mColorExtractor.removeLocations(); + } + mLastLocationRegistered = new RectF(mTempRectF); + mColorExtractor.addLocation(List.of(mLastLocationRegistered)); + } + } else { + mColorExtractor.removeLocations(); + } + } + + @Override + public void onColorsChanged(RectF rectF, SparseIntArray colors) { + // setColorResources will reapply the view, which must happen in the UI thread. + post(() -> setColorResources(colors)); } @Override @@ -225,6 +335,14 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView protected void onWindowVisibilityChanged(int visibility) { super.onWindowVisibilityChanged(visibility); maybeRegisterAutoAdvance(); + + if (visibility == View.VISIBLE) { + if (mLastLocationRegistered != null) { + mColorExtractor.addLocation(List.of(mLastLocationRegistered)); + } + } else { + mColorExtractor.removeLocations(); + } } private void checkIfAutoAdvance() { diff --git a/src/com/android/launcher3/widget/LocalColorExtractor.java b/src/com/android/launcher3/widget/LocalColorExtractor.java new file mode 100644 index 0000000000..097158b072 --- /dev/null +++ b/src/com/android/launcher3/widget/LocalColorExtractor.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009 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; + +import android.appwidget.AppWidgetHostView; +import android.content.Context; +import android.graphics.RectF; +import android.util.SparseIntArray; + +import androidx.annotation.Nullable; + +import com.android.launcher3.R; +import com.android.launcher3.util.ResourceBasedOverride; + +import java.util.List; + +/** Extracts the colors we need from the wallpaper at given locations. */ +public class LocalColorExtractor implements ResourceBasedOverride { + + /** Listener for color changes on a screen location. */ + public interface Listener { + /** + * Method called when the colors on a registered location has changed. + * + * {@code extractedColors} maps the color resources {@code android.R.colors.system_*} to + * their value, in a format that can be passed directly to + * {@link AppWidgetHostView#setColorResources(SparseIntArray)}. + */ + void onColorsChanged(RectF rect, SparseIntArray extractedColors); + } + + static LocalColorExtractor newInstance(Context context) { + return Overrides.getObject(LocalColorExtractor.class, context.getApplicationContext(), + R.string.local_colors_extraction_class); + } + + /** Sets the object that will receive the color changes. */ + public void setListener(@Nullable Listener listener) { + // no-op + } + + /** Adds a list of locations to track with this listener. */ + public void addLocation(List locations) { + // no-op + } + + /** Stops tracking any locations. */ + public void removeLocations() { + // no-op + } +} diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml index 744dee0607..7f6c8f8a87 100644 --- a/tests/AndroidManifest-common.xml +++ b/tests/AndroidManifest-common.xml @@ -60,6 +60,17 @@ android:resource="@xml/appwidget_with_config"/> + + + + + + + diff --git a/tests/res/layout/test_layout_appwidget_dynamic_colors.xml b/tests/res/layout/test_layout_appwidget_dynamic_colors.xml new file mode 100644 index 0000000000..c5ab030d11 --- /dev/null +++ b/tests/res/layout/test_layout_appwidget_dynamic_colors.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/res/xml/appwidget_dynamic_colors.xml b/tests/res/xml/appwidget_dynamic_colors.xml new file mode 100644 index 0000000000..f6b9a04343 --- /dev/null +++ b/tests/res/xml/appwidget_dynamic_colors.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java b/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java new file mode 100644 index 0000000000..5fb34540ed --- /dev/null +++ b/tests/src/com/android/launcher3/testcomponent/AppWidgetDynamicColors.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2013 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.testcomponent; + +import android.appwidget.AppWidgetProvider; + +/** + * A simple app widget showing a primary, secondary and neutral color. + */ +public class AppWidgetDynamicColors extends AppWidgetProvider { +}