summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-06-15 10:18:49 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-06-15 10:18:49 +0000
commitf4fe78cee3015fc8bee644a580dc935e28b6431d (patch)
tree5d5ab5b75e521a32cd3cebe20209d9af46887f5a /src
parent22278c22053e18c26f995917810c5b6a0543ff89 (diff)
parentc0b3a6241e521188758e361d21a4a693b49a1c66 (diff)
Snap for 10323517 from c0b3a6241e521188758e361d21a4a693b49a1c66 to t-keystone-qcom-release
Change-Id: I2631b96e4cc1b448e2d561f21c2f188e13f7f6f6
Diffstat (limited to 'src')
-rwxr-xr-xsrc/com/android/wallpaper/asset/LiveWallpaperThumbAsset.java29
-rw-r--r--src/com/android/wallpaper/config/BaseFlags.kt47
-rw-r--r--src/com/android/wallpaper/effects/EffectsController.java24
-rwxr-xr-xsrc/com/android/wallpaper/model/Category.java7
-rw-r--r--src/com/android/wallpaper/model/CustomizationSectionController.java73
-rw-r--r--src/com/android/wallpaper/model/CustomizationSectionController.kt89
-rwxr-xr-xsrc/com/android/wallpaper/model/LiveWallpaperInfo.java10
-rw-r--r--src/com/android/wallpaper/model/LiveWallpaperMetadata.java67
-rw-r--r--src/com/android/wallpaper/model/SetWallpaperViewModel.java2
-rw-r--r--src/com/android/wallpaper/model/WallpaperColorsViewModel.kt47
-rwxr-xr-xsrc/com/android/wallpaper/model/WallpaperInfo.java7
-rwxr-xr-xsrc/com/android/wallpaper/model/WallpaperMetadata.java4
-rw-r--r--src/com/android/wallpaper/model/WallpaperSectionController.java207
-rw-r--r--src/com/android/wallpaper/module/CustomizationSections.java13
-rwxr-xr-xsrc/com/android/wallpaper/module/DefaultCurrentWallpaperInfoFactory.java32
-rwxr-xr-xsrc/com/android/wallpaper/module/DefaultWallpaperPersister.java114
-rwxr-xr-xsrc/com/android/wallpaper/module/DefaultWallpaperPreferences.java54
-rwxr-xr-xsrc/com/android/wallpaper/module/DefaultWallpaperRefresher.java180
-rwxr-xr-xsrc/com/android/wallpaper/module/Injector.kt18
-rw-r--r--src/com/android/wallpaper/module/LargeScreenMultiPanesChecker.kt1
-rwxr-xr-xsrc/com/android/wallpaper/module/WallpaperPersister.java32
-rwxr-xr-xsrc/com/android/wallpaper/module/WallpaperPicker2Injector.kt60
-rw-r--r--src/com/android/wallpaper/module/WallpaperPickerSections.java37
-rwxr-xr-xsrc/com/android/wallpaper/module/WallpaperPreferenceKeys.java5
-rwxr-xr-xsrc/com/android/wallpaper/module/WallpaperPreferences.java69
-rw-r--r--src/com/android/wallpaper/module/WallpaperSetter.java42
-rw-r--r--src/com/android/wallpaper/picker/CategorySelectorFragment.java6
-rw-r--r--src/com/android/wallpaper/picker/CustomizationPickerActivity.java44
-rw-r--r--src/com/android/wallpaper/picker/CustomizationPickerFragment.java118
-rw-r--r--src/com/android/wallpaper/picker/DisplayAspectRatioLinearLayout.kt105
-rwxr-xr-xsrc/com/android/wallpaper/picker/FullPreviewActivity.java30
-rwxr-xr-xsrc/com/android/wallpaper/picker/ImagePreviewFragment.java9
-rwxr-xr-xsrc/com/android/wallpaper/picker/PreviewActivity.java14
-rwxr-xr-xsrc/com/android/wallpaper/picker/PreviewFragment.java2
-rwxr-xr-xsrc/com/android/wallpaper/picker/StandalonePreviewActivity.java75
-rw-r--r--src/com/android/wallpaper/picker/TouchForwardingLayout.java10
-rwxr-xr-xsrc/com/android/wallpaper/picker/ViewOnlyPreviewActivity.java9
-rw-r--r--src/com/android/wallpaper/picker/WallpaperOnlyFragment.java15
-rw-r--r--src/com/android/wallpaper/picker/WallpaperPickerDelegate.java2
-rw-r--r--src/com/android/wallpaper/picker/WorkspaceSurfaceHolderCallback.java32
-rw-r--r--src/com/android/wallpaper/picker/common/button/ui/viewbinder/ButtonViewBinder.kt59
-rw-r--r--src/com/android/wallpaper/picker/common/button/ui/viewmodel/ButtonStyle.kt (renamed from src/com/android/wallpaper/model/WorkspaceViewModel.kt)27
-rw-r--r--src/com/android/wallpaper/picker/common/button/ui/viewmodel/ButtonViewModel.kt26
-rw-r--r--src/com/android/wallpaper/picker/common/dialog/ui/viewbinder/DialogViewBinder.kt110
-rw-r--r--src/com/android/wallpaper/picker/common/dialog/ui/viewmodel/DialogViewModel.kt30
-rw-r--r--src/com/android/wallpaper/picker/common/icon/ui/viewbinder/ContentDescriptionViewBinder.kt34
-rw-r--r--src/com/android/wallpaper/picker/common/icon/ui/viewbinder/IconViewBinder.kt42
-rw-r--r--src/com/android/wallpaper/picker/common/icon/ui/viewmodel/Icon.kt42
-rw-r--r--src/com/android/wallpaper/picker/common/text/ui/viewbinder/TextViewBinder.kt33
-rw-r--r--src/com/android/wallpaper/picker/common/text/ui/viewmodel/Text.kt51
-rw-r--r--src/com/android/wallpaper/picker/customization/data/content/WallpaperClient.kt54
-rw-r--r--src/com/android/wallpaper/picker/customization/data/content/WallpaperClientImpl.kt194
-rw-r--r--src/com/android/wallpaper/picker/customization/data/repository/WallpaperRepository.kt101
-rw-r--r--src/com/android/wallpaper/picker/customization/domain/interactor/WallpaperInteractor.kt106
-rw-r--r--src/com/android/wallpaper/picker/customization/domain/interactor/WallpaperSnapshotRestorer.kt111
-rw-r--r--src/com/android/wallpaper/picker/customization/shared/model/WallpaperDestination.kt28
-rw-r--r--src/com/android/wallpaper/picker/customization/shared/model/WallpaperModel.kt (renamed from src/com/android/wallpaper/picker/undo/ui/viewmodel/UndoDialogViewModel.kt)12
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/binder/CustomizationPickerBinder.kt139
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/binder/ScreenPreviewBinder.kt116
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/binder/WallpaperQuickSwitchOptionBinder.kt176
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/binder/WallpaperQuickSwitchSectionBinder.kt147
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/section/ConnectedSectionController.kt135
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/section/ResponsiveLayoutSectionView.kt31
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/section/ScreenPreviewSectionController.kt74
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/section/ScreenPreviewView.kt59
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/section/WallpaperQuickSwitchSectionController.kt68
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/section/WallpaperQuickSwitchView.kt31
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/viewmodel/CustomizationPickerViewModel.kt17
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/viewmodel/ScreenPreviewViewModel.kt16
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/viewmodel/WallpaperQuickSwitchOptionViewModel.kt47
-rw-r--r--src/com/android/wallpaper/picker/customization/ui/viewmodel/WallpaperQuickSwitchViewModel.kt246
-rwxr-xr-xsrc/com/android/wallpaper/picker/individual/IndividualHolder.java4
-rw-r--r--src/com/android/wallpaper/picker/individual/IndividualPickerFragment2.kt103
-rw-r--r--src/com/android/wallpaper/picker/option/ui/adapter/OptionItemAdapter.kt125
-rw-r--r--src/com/android/wallpaper/picker/option/ui/binder/OptionItemBinder.kt303
-rw-r--r--src/com/android/wallpaper/picker/option/ui/viewmodel/OptionItemViewModel.kt65
-rw-r--r--src/com/android/wallpaper/picker/undo/domain/interactor/SnapshotRestorer.kt8
-rw-r--r--src/com/android/wallpaper/picker/undo/domain/interactor/SnapshotStore.kt34
-rw-r--r--src/com/android/wallpaper/picker/undo/domain/interactor/UndoInteractor.kt31
-rw-r--r--src/com/android/wallpaper/picker/undo/shared/model/RestorableSnapshot.kt31
-rw-r--r--src/com/android/wallpaper/picker/undo/ui/binder/RevertToolbarButtonBinder.kt20
-rw-r--r--src/com/android/wallpaper/picker/undo/ui/viewmodel/UndoViewModel.kt38
-rw-r--r--src/com/android/wallpaper/settings/data/repository/SecureSettingsRepository.kt103
-rwxr-xr-xsrc/com/android/wallpaper/util/ActivityUtils.java9
-rw-r--r--src/com/android/wallpaper/util/DisplayUtils.kt75
-rw-r--r--src/com/android/wallpaper/util/VideoWallpaperUtils.java40
-rwxr-xr-xsrc/com/android/wallpaper/util/WallpaperCropUtils.java39
-rw-r--r--src/com/android/wallpaper/widget/DuoTabs.java10
-rw-r--r--src/com/android/wallpaper/widget/FloatingSheet.kt4
-rw-r--r--src/com/android/wallpaper/widget/WallpaperControlButtonGroup.java26
-rw-r--r--src/com/android/wallpaper/widget/floatingsheetcontent/WallpaperInfoContent.kt4
91 files changed, 4486 insertions, 689 deletions
diff --git a/src/com/android/wallpaper/asset/LiveWallpaperThumbAsset.java b/src/com/android/wallpaper/asset/LiveWallpaperThumbAsset.java
index 1f4fb674..08486a12 100755
--- a/src/com/android/wallpaper/asset/LiveWallpaperThumbAsset.java
+++ b/src/com/android/wallpaper/asset/LiveWallpaperThumbAsset.java
@@ -21,16 +21,23 @@ import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
import android.util.Log;
import android.widget.ImageView;
import androidx.annotation.WorkerThread;
+import com.android.wallpaper.module.DrawableLayerResolver;
+import com.android.wallpaper.module.InjectorProvider;
+
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.MultiTransformation;
@@ -59,6 +66,7 @@ public class LiveWallpaperThumbAsset extends Asset {
protected final Context mContext;
protected final android.app.WallpaperInfo mInfo;
+ protected final DrawableLayerResolver mLayerResolver;
// The content Uri of thumbnail
protected Uri mUri;
private Drawable mThumbnailDrawable;
@@ -66,6 +74,7 @@ public class LiveWallpaperThumbAsset extends Asset {
public LiveWallpaperThumbAsset(Context context, android.app.WallpaperInfo info) {
mContext = context.getApplicationContext();
mInfo = info;
+ mLayerResolver = InjectorProvider.getInjector().getDrawableLayerResolver();
}
public LiveWallpaperThumbAsset(Context context, android.app.WallpaperInfo info, Uri uri) {
@@ -114,7 +123,25 @@ public class LiveWallpaperThumbAsset extends Asset {
@Override
public void decodeRawDimensions(Activity unused, DimensionsReceiver receiver) {
- receiver.onDimensionsDecoded(null);
+ // TODO(b/277166654): Reuse the logic for all thumb asset decoding
+ sExecutorService.execute(() -> {
+ Bitmap result = null;
+ Drawable thumb = mInfo.loadThumbnail(mContext.getPackageManager());
+ if (thumb instanceof BitmapDrawable) {
+ result = ((BitmapDrawable) thumb).getBitmap();
+ } else if (thumb instanceof LayerDrawable) {
+ Drawable layer = mLayerResolver.resolveLayer((LayerDrawable) thumb);
+ if (layer instanceof BitmapDrawable) {
+ result = ((BitmapDrawable) layer).getBitmap();
+ }
+ }
+ final Bitmap lr = result;
+ new Handler(Looper.getMainLooper()).post(
+ () ->
+ receiver.onDimensionsDecoded(
+ lr == null ? null : new Point(lr.getWidth(), lr.getHeight()))
+ );
+ });
}
@Override
diff --git a/src/com/android/wallpaper/config/BaseFlags.kt b/src/com/android/wallpaper/config/BaseFlags.kt
index 37f3d6e0..ffd76c69 100644
--- a/src/com/android/wallpaper/config/BaseFlags.kt
+++ b/src/com/android/wallpaper/config/BaseFlags.kt
@@ -16,24 +16,55 @@
package com.android.wallpaper.config
import android.content.Context
-import android.os.SystemProperties
+import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
abstract class BaseFlags {
+ var customizationProviderClient: CustomizationProviderClient? = null
open fun isStagingBackdropContentEnabled() = false
- open fun isEnableWallpaperEffect() = false
- fun isMonochromaticFlagEnabled() =
- SystemProperties.getBoolean("persist.sysui.monochromatic", false)
- open fun isEnableEffectOnMultiplePanel() = false
- open fun isFullscreenWallpaperPreview() = false
- fun isUseRevampedUi(context: Context): Boolean {
- return runBlocking { CustomizationProviderClientImpl(context, Dispatchers.IO).queryFlags() }
+ open fun isWallpaperEffectEnabled() = false
+ open fun isFullscreenWallpaperPreviewEnabled(context: Context): Boolean {
+ return runBlocking { getCustomizationProviderClient(context).queryFlags() }
+ .firstOrNull { flag ->
+ flag.name == Contract.FlagsTable.FLAG_NAME_WALLPAPER_FULLSCREEN_PREVIEW
+ }
+ ?.value == true
+ }
+ fun isUseRevampedUiEnabled(context: Context): Boolean {
+ return runBlocking { getCustomizationProviderClient(context).queryFlags() }
.firstOrNull { flag ->
flag.name == Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI
}
?.value == true
}
+ fun isCustomClocksEnabled(context: Context): Boolean {
+ return runBlocking { getCustomizationProviderClient(context).queryFlags() }
+ .firstOrNull { flag ->
+ flag.name == Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED
+ }
+ ?.value == true
+ }
+ fun isMonochromaticThemeEnabled(context: Context): Boolean {
+ return runBlocking { getCustomizationProviderClient(context).queryFlags() }
+ .firstOrNull { flag -> flag.name == Contract.FlagsTable.FLAG_NAME_MONOCHROMATIC_THEME }
+ ?.value == true
+ }
+
+ fun isAIWallpaperEnabled(context: Context): Boolean {
+ return runBlocking { getCustomizationProviderClient(context).queryFlags() }
+ .firstOrNull { flag ->
+ flag.name == Contract.FlagsTable.FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP
+ }
+ ?.value == true
+ }
+
+ private fun getCustomizationProviderClient(context: Context): CustomizationProviderClient {
+ return customizationProviderClient
+ ?: CustomizationProviderClientImpl(context, Dispatchers.IO).also {
+ customizationProviderClient = it
+ }
+ }
}
diff --git a/src/com/android/wallpaper/effects/EffectsController.java b/src/com/android/wallpaper/effects/EffectsController.java
index 8c745718..c1e9a145 100644
--- a/src/com/android/wallpaper/effects/EffectsController.java
+++ b/src/com/android/wallpaper/effects/EffectsController.java
@@ -15,6 +15,7 @@
*/
package com.android.wallpaper.effects;
+import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
@@ -22,6 +23,11 @@ import android.os.Bundle;
* Utility class to provide methods to generate effects for the wallpaper.
*/
public abstract class EffectsController {
+ public static final int ERROR_ORIGINAL_DESTROY_CONTROLLER = -16;
+ public static final int ERROR_ORIGINAL_FINISH_ONGOING_SERVICE = -8;
+ public static final int ERROR_ORIGINAL_SERVICE_DISCONNECT = -4;
+ public static final int ERROR_ORIGINAL_TIME_OUT = -2;
+
public static final int RESULT_ORIGINAL_UNKNOWN = -1;
public static final int RESULT_SUCCESS = 0;
public static final int RESULT_ERROR_TRY_ANOTHER_PHOTO = 1;
@@ -76,6 +82,14 @@ public abstract class EffectsController {
}
/**
+ * Triggers the effect.
+ *
+ * @param context the context
+ */
+ public void triggerEffect(Context context) {
+ }
+
+ /**
* Interface to listen to different key moments of the connection with the Effects Service.
*/
public interface EffectsServiceListener {
@@ -91,4 +105,14 @@ public abstract class EffectsController {
void onEffectFinished(EffectEnumInterface effect, Bundle bundle, int error,
int originalStatusCode, String errorMessage);
}
+
+ /**
+ * Gets whether the effect triggering is successful or not.
+ *
+ * @return whether the effect triggering is successful or not.
+ */
+ public boolean isEffectTriggered() {
+ return false;
+ }
+
}
diff --git a/src/com/android/wallpaper/model/Category.java b/src/com/android/wallpaper/model/Category.java
index a182ea4e..beb349fc 100755
--- a/src/com/android/wallpaper/model/Category.java
+++ b/src/com/android/wallpaper/model/Category.java
@@ -154,6 +154,13 @@ public abstract class Category {
return false;
}
+ /**
+ * Returns whether this category supports content that can be added or removed dynamically.
+ */
+ public boolean supportsWallpaperSetUpdates() {
+ return false;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Category)) return false;
diff --git a/src/com/android/wallpaper/model/CustomizationSectionController.java b/src/com/android/wallpaper/model/CustomizationSectionController.java
deleted file mode 100644
index 58ef178b..00000000
--- a/src/com/android/wallpaper/model/CustomizationSectionController.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2021 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.wallpaper.model;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-
-import com.android.wallpaper.picker.SectionView;
-
-/**
- * The interface for the behavior of section in the customization picker.
- *
- * @param <T> the {@link SectionView} to create for the section
- */
-public interface CustomizationSectionController<T extends SectionView> {
-
- /** Interface for customization section navigation. */
- interface CustomizationSectionNavigationController {
- /** Navigates to the given {@code fragment}. */
- void navigateTo(Fragment fragment);
-
- /** Navigates to a {@code fragment} that maps to the given destination ID. */
- void navigateTo(String destinationId);
- }
-
- /** Returns {@code true} if the customization section is available. */
- boolean isAvailable(@Nullable Context context);
-
- /**
- * Returns a newly created {@link SectionView} for the section.
- *
- * @param context The {@link Context} to inflate view.
- * @param isOnLockScreen Whether we are on the lock screen.
- */
- default T createView(Context context, boolean isOnLockScreen) {
- return createView(context);
- }
-
- /**
- * Returns a newly created {@link SectionView} for the section.
- *
- * @param context the {@link Context} to inflate view
- */
- T createView(Context context);
-
- /** Saves the view state for configuration changes. */
- default void onSaveInstanceState(Bundle savedInstanceState) {}
-
- /** Releases the controller. */
- default void release() {}
-
- /** Gets called when the section gets transitioned out. */
- default void onTransitionOut() {}
-
- /** Notifies when the screen was switched. */
- default void onScreenSwitched(boolean isOnLockScreen) {}
-}
diff --git a/src/com/android/wallpaper/model/CustomizationSectionController.kt b/src/com/android/wallpaper/model/CustomizationSectionController.kt
new file mode 100644
index 00000000..76c140e3
--- /dev/null
+++ b/src/com/android/wallpaper/model/CustomizationSectionController.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 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.wallpaper.model
+
+import android.content.Context
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import com.android.wallpaper.picker.SectionView
+
+/**
+ * The interface for the behavior of section in the customization picker.
+ *
+ * @param <T> the [SectionView] to create for the section </T>
+ */
+interface CustomizationSectionController<T : SectionView> {
+ /** Interface for customization section navigation. */
+ interface CustomizationSectionNavigationController {
+ /** Navigates to the given `fragment`. */
+ fun navigateTo(fragment: Fragment?)
+
+ /** Navigates to a `fragment` that maps to the given destination ID. */
+ fun navigateTo(destinationId: String?)
+ }
+
+ data class ViewCreationParams(
+ /** Whether the view is being created in the context of the lock screen tab of the UI. */
+ val isOnLockScreen: Boolean = false,
+ /**
+ * Whether the view is being created in the context of a bunch of "connected" sections that
+ * are laid out side-by-side in a horizontal layout.
+ */
+ val isConnectedHorizontallyToOtherSections: Boolean = false,
+ )
+
+ /**
+ * It means that the creation of the controller can be expensive and we should avoid recreation
+ * in conditions like the user switching between the home and lock screen.
+ */
+ @JvmDefault
+ fun shouldRetainInstanceWhenSwitchingTabs(): Boolean {
+ return false
+ }
+
+ /** Returns `true` if the customization section is available. */
+ fun isAvailable(context: Context): Boolean
+
+ /**
+ * Returns a newly created [SectionView] for the section.
+ *
+ * @param context The [Context] to inflate view.
+ * @param params Parameters for the creation of the view.
+ */
+ @JvmDefault
+ fun createView(context: Context, params: ViewCreationParams): T {
+ return createView(context)
+ }
+
+ /**
+ * Returns a newly created [SectionView] for the section.
+ *
+ * @param context the [Context] to inflate view
+ */
+ fun createView(context: Context): T
+
+ /** Saves the view state for configuration changes. */
+ @JvmDefault fun onSaveInstanceState(savedInstanceState: Bundle) = Unit
+
+ /** Releases the controller. */
+ @JvmDefault fun release() = Unit
+
+ /** Gets called when the section gets transitioned out. */
+ @JvmDefault fun onTransitionOut() = Unit
+
+ /** Notifies when the screen was switched. */
+ @JvmDefault fun onScreenSwitched(isOnLockScreen: Boolean) = Unit
+}
diff --git a/src/com/android/wallpaper/model/LiveWallpaperInfo.java b/src/com/android/wallpaper/model/LiveWallpaperInfo.java
index 47bf89af..bc1f3668 100755
--- a/src/com/android/wallpaper/model/LiveWallpaperInfo.java
+++ b/src/com/android/wallpaper/model/LiveWallpaperInfo.java
@@ -446,4 +446,14 @@ public class LiveWallpaperInfo extends WallpaperInfo {
public String getWallpaperId() {
return mInfo.getServiceName();
}
+
+ /**
+ * Returns true if this wallpaper is currently applied.
+ */
+ public boolean isApplied(android.app.WallpaperInfo currentWallpaper) {
+ return getWallpaperComponent() != null
+ && currentWallpaper != null
+ && TextUtils.equals(getWallpaperComponent().getServiceName(),
+ currentWallpaper.getServiceName());
+ }
}
diff --git a/src/com/android/wallpaper/model/LiveWallpaperMetadata.java b/src/com/android/wallpaper/model/LiveWallpaperMetadata.java
new file mode 100644
index 00000000..6b0dd3a8
--- /dev/null
+++ b/src/com/android/wallpaper/model/LiveWallpaperMetadata.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.model;
+
+import android.app.WallpaperInfo;
+
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Live wallpaper-specific wrapper for user-facing wallpaper metadata.
+ */
+public class LiveWallpaperMetadata extends WallpaperMetadata {
+ public LiveWallpaperMetadata(android.app.WallpaperInfo wallpaperComponent) {
+ super(null, null, 0, 0, null, null, wallpaperComponent);
+ }
+
+ @Override
+ public List<String> getAttributions() {
+ throw new UnsupportedOperationException("Not implemented for live wallpapers");
+ }
+
+ @Override
+ public String getActionUrl() {
+ throw new UnsupportedOperationException("Not implemented for live wallpapers");
+ }
+
+ @Override
+ public int getActionLabelRes() {
+ throw new UnsupportedOperationException("Not implemented for live wallpapers");
+ }
+
+ @Override
+ public int getActionIconRes() {
+ throw new UnsupportedOperationException("Not implemented for live wallpapers");
+ }
+
+ @Override
+ public String getCollectionId() {
+ throw new UnsupportedOperationException("Not implemented for live wallpapers");
+ }
+
+ @Nullable
+ @Override
+ public String getBackingFileName() {
+ throw new UnsupportedOperationException("Not implemented for live wallpapers");
+ }
+
+ @Override
+ public WallpaperInfo getWallpaperComponent() {
+ return mWallpaperComponent;
+ }
+}
diff --git a/src/com/android/wallpaper/model/SetWallpaperViewModel.java b/src/com/android/wallpaper/model/SetWallpaperViewModel.java
index efd55d39..fcba06ee 100644
--- a/src/com/android/wallpaper/model/SetWallpaperViewModel.java
+++ b/src/com/android/wallpaper/model/SetWallpaperViewModel.java
@@ -47,7 +47,7 @@ public class SetWallpaperViewModel extends ViewModel {
SetWallpaperViewModel viewModel = provider.get(SetWallpaperViewModel.class);
return new SetWallpaperCallback() {
@Override
- public void onSuccess(WallpaperInfo wallpaperInfo) {
+ public void onSuccess(WallpaperInfo wallpaperInfo, @Destination int destination) {
Log.d(TAG, "SetWallpaperCallback success");
viewModel.mStatus.setValue(SetWallpaperStatus.SUCCESS);
}
diff --git a/src/com/android/wallpaper/model/WallpaperColorsViewModel.kt b/src/com/android/wallpaper/model/WallpaperColorsViewModel.kt
index 3d5b7715..8fc2a919 100644
--- a/src/com/android/wallpaper/model/WallpaperColorsViewModel.kt
+++ b/src/com/android/wallpaper/model/WallpaperColorsViewModel.kt
@@ -16,19 +16,52 @@
package com.android.wallpaper.model
import android.app.WallpaperColors
+import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
-/** ViewModel class to keep track of WallpaperColors for the current wallpaper */
-class WallpaperColorsViewModel : ViewModel() {
+/**
+ * ViewModel class to keep track of WallpaperColors for the current wallpaper
+ *
+ * TODO (b/269451870): Rename to WallpaperColorsRepository
+ */
+class WallpaperColorsViewModel {
- /** WallpaperColors for the currently set home wallpaper */
- val homeWallpaperColors: MutableLiveData<WallpaperColors> by lazy {
+ /**
+ * WallpaperColors exposed as live data to allow Java integration
+ *
+ * TODO (b/262924584): Remove after ColorSectionController2 & ColorCustomizationManager refactor
+ */
+ private val _homeWallpaperColorsLiveData: MutableLiveData<WallpaperColors> by lazy {
MutableLiveData<WallpaperColors>()
}
+ val homeWallpaperColorsLiveData: LiveData<WallpaperColors> = _homeWallpaperColorsLiveData
+ private val _lockWallpaperColorsLiveData: MutableLiveData<WallpaperColors> by lazy {
+ MutableLiveData<WallpaperColors>()
+ }
+ val lockWallpaperColorsLiveData: LiveData<WallpaperColors> = _lockWallpaperColorsLiveData
+ private val _homeWallpaperColors = MutableStateFlow<WallpaperColors?>(null)
+ /** WallpaperColors for the currently set home wallpaper */
+ val homeWallpaperColors: StateFlow<WallpaperColors?> = _homeWallpaperColors.asStateFlow()
+
+ private val _lockWallpaperColors = MutableStateFlow<WallpaperColors?>(null)
/** WallpaperColors for the currently set lock wallpaper */
- val lockWallpaperColors: MutableLiveData<WallpaperColors> by lazy {
- MutableLiveData<WallpaperColors>()
+ val lockWallpaperColors: StateFlow<WallpaperColors?> = _lockWallpaperColors.asStateFlow()
+
+ fun setHomeWallpaperColors(colors: WallpaperColors?) {
+ _homeWallpaperColors.value = colors
+ if (colors != _homeWallpaperColorsLiveData.value) {
+ _homeWallpaperColorsLiveData.value = colors
+ }
+ }
+
+ fun setLockWallpaperColors(colors: WallpaperColors?) {
+ _lockWallpaperColors.value = colors
+ if (colors != _lockWallpaperColorsLiveData.value) {
+ _lockWallpaperColorsLiveData.value = colors
+ }
}
}
diff --git a/src/com/android/wallpaper/model/WallpaperInfo.java b/src/com/android/wallpaper/model/WallpaperInfo.java
index 0fc0d275..761e62f6 100755
--- a/src/com/android/wallpaper/model/WallpaperInfo.java
+++ b/src/com/android/wallpaper/model/WallpaperInfo.java
@@ -88,6 +88,13 @@ public abstract class WallpaperInfo implements Parcelable {
}
/**
+ * Returns the content description for this wallpaper, or null if none exists.
+ */
+ public String getContentDescription(Context context) {
+ return null;
+ }
+
+ /**
* @return The available attributions for this wallpaper, as a list of strings. These represent
* the author / website or any other attribution required to be displayed for this wallpaper
* regarding authorship, ownership, etc.
diff --git a/src/com/android/wallpaper/model/WallpaperMetadata.java b/src/com/android/wallpaper/model/WallpaperMetadata.java
index cd24796f..2e6b2734 100755
--- a/src/com/android/wallpaper/model/WallpaperMetadata.java
+++ b/src/com/android/wallpaper/model/WallpaperMetadata.java
@@ -32,7 +32,7 @@ public class WallpaperMetadata {
private final String mActionUrl;
private final String mCollectionId;
private final String mBackingFileName;
- private final android.app.WallpaperInfo mWallpaperComponent;
+ protected final android.app.WallpaperInfo mWallpaperComponent;
@StringRes private final int mActionLabelRes;
@DrawableRes private final int mActionIconRes;
@@ -101,6 +101,6 @@ public class WallpaperMetadata {
* describes an image wallpaper.
*/
public WallpaperInfo getWallpaperComponent() {
- return mWallpaperComponent;
+ throw new UnsupportedOperationException("Not implemented for static wallpapers");
}
}
diff --git a/src/com/android/wallpaper/model/WallpaperSectionController.java b/src/com/android/wallpaper/model/WallpaperSectionController.java
index cf07e76c..d61330e2 100644
--- a/src/com/android/wallpaper/model/WallpaperSectionController.java
+++ b/src/com/android/wallpaper/model/WallpaperSectionController.java
@@ -26,6 +26,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.graphics.RenderEffect;
+import android.graphics.Shader.TileMode;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
@@ -46,6 +48,7 @@ import androidx.core.widget.ContentLoadingProgressBar;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LiveData;
import androidx.lifecycle.OnLifecycleEvent;
import com.android.wallpaper.R;
@@ -63,6 +66,7 @@ import com.android.wallpaper.picker.WorkspaceSurfaceHolderCallback;
import com.android.wallpaper.util.DisplayUtils;
import com.android.wallpaper.util.PreviewUtils;
import com.android.wallpaper.util.ResourceUtils;
+import com.android.wallpaper.util.VideoWallpaperUtils;
import com.android.wallpaper.util.WallpaperConnection;
import com.android.wallpaper.util.WallpaperSurfaceCallback;
import com.android.wallpaper.widget.LockScreenPreviewer;
@@ -70,7 +74,9 @@ import com.android.wallpaper.widget.LockScreenPreviewer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
-/** The class to control the wallpaper section view. */
+/**
+ * The class to control the wallpaper section view.
+ */
public class WallpaperSectionController implements
CustomizationSectionController<WallpaperSectionView>,
LifecycleObserver {
@@ -85,12 +91,15 @@ public class WallpaperSectionController implements
private WorkspaceSurfaceHolderCallback mWorkspaceSurfaceCallback;
private SurfaceView mHomeWallpaperSurface;
private WallpaperSurfaceCallback mHomeWallpaperSurfaceCallback;
+ private ImageView mHomeFadeInScrim;
private SurfaceView mLockWallpaperSurface;
private WallpaperSurfaceCallback mLockWallpaperSurfaceCallback;
private CardView mLockscreenPreviewCard;
private ViewGroup mLockPreviewContainer;
+ private ImageView mLockFadeInScrim;
private ContentLoadingProgressBar mLockscreenPreviewProgress;
- private WallpaperConnection mWallpaperConnection;
+ private WallpaperConnection mHomeWallpaperConnection;
+ private WallpaperConnection mLockWallpaperConnection;
// The wallpaper information which is currently shown on the home preview.
private WallpaperInfo mHomePreviewWallpaperInfo;
@@ -104,7 +113,8 @@ public class WallpaperSectionController implements
private final LifecycleOwner mLifecycleOwner;
private final PermissionRequester mPermissionRequester;
private final WallpaperColorsViewModel mWallpaperColorsViewModel;
- private final WorkspaceViewModel mWorkspaceViewModel;
+ @Nullable
+ private final LiveData<Boolean> mOnThemingChanged;
private final CustomizationSectionNavigationController mSectionNavigationController;
private final WallpaperPreviewNavigator mWallpaperPreviewNavigator;
private final Bundle mSavedInstanceState;
@@ -112,7 +122,7 @@ public class WallpaperSectionController implements
public WallpaperSectionController(Activity activity, LifecycleOwner lifecycleOwner,
PermissionRequester permissionRequester, WallpaperColorsViewModel colorsViewModel,
- WorkspaceViewModel workspaceViewModel,
+ @Nullable LiveData<Boolean> onThemingChanged,
CustomizationSectionNavigationController sectionNavigationController,
WallpaperPreviewNavigator wallpaperPreviewNavigator,
Bundle savedInstanceState,
@@ -122,7 +132,7 @@ public class WallpaperSectionController implements
mPermissionRequester = permissionRequester;
mAppContext = mActivity.getApplicationContext();
mWallpaperColorsViewModel = colorsViewModel;
- mWorkspaceViewModel = workspaceViewModel;
+ mOnThemingChanged = onThemingChanged;
mSectionNavigationController = sectionNavigationController;
mWallpaperPreviewNavigator = wallpaperPreviewNavigator;
mSavedInstanceState = savedInstanceState;
@@ -132,27 +142,26 @@ public class WallpaperSectionController implements
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
@MainThread
public void onResume() {
- refreshCurrentWallpapers(/* forceRefresh= */ mSavedInstanceState == null);
- if (mWallpaperConnection != null) {
- mWallpaperConnection.setVisibility(true);
- }
+ refreshCurrentWallpapers(/* forceRefresh= */ true);
+ updateLivePreviewVisibility(true);
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
@MainThread
public void onPause() {
- if (mWallpaperConnection != null) {
- mWallpaperConnection.setVisibility(false);
- }
+ updateLivePreviewVisibility(false);
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
@MainThread
public void onStop() {
- if (mWallpaperConnection != null) {
- mWallpaperConnection.disconnect();
- mWallpaperConnection = null;
- }
+ disconnectHomeLiveWallpaper();
+ disconnectLockLiveWallpaper();
+ }
+
+ @Override
+ public boolean shouldRetainInstanceWhenSwitchingTabs() {
+ return true;
}
@Override
@@ -174,6 +183,7 @@ public class WallpaperSectionController implements
new PreviewUtils(
mAppContext, mAppContext.getString(R.string.grid_control_metadata_name)));
mHomeWallpaperSurface = mHomePreviewCard.findViewById(R.id.wallpaper_surface);
+ mHomeFadeInScrim = mHomePreviewCard.findViewById(R.id.wallpaper_fadein_scrim);
Future<ColorInfo> colorFuture = CompletableFuture.completedFuture(
new ColorInfo(/* wallpaperColors= */ null,
@@ -183,7 +193,7 @@ public class WallpaperSectionController implements
mHomeWallpaperSurface, colorFuture, () -> {
if (mHomePreviewWallpaperInfo != null) {
maybeLoadThumbnail(mHomePreviewWallpaperInfo, mHomeWallpaperSurfaceCallback,
- mDisplayUtils.isOnWallpaperDisplay(mActivity));
+ mDisplayUtils.isSingleDisplayOrUnfoldedHorizontalHinge(mActivity), true);
}
});
@@ -194,11 +204,12 @@ public class WallpaperSectionController implements
R.id.wallpaper_preview_spinner);
mLockscreenPreviewCard.findViewById(R.id.workspace_surface).setVisibility(View.GONE);
mLockWallpaperSurface = mLockscreenPreviewCard.findViewById(R.id.wallpaper_surface);
+ mLockFadeInScrim = mLockscreenPreviewCard.findViewById(R.id.wallpaper_fadein_scrim);
mLockWallpaperSurfaceCallback = new WallpaperSurfaceCallback(mActivity,
mLockscreenPreviewCard, mLockWallpaperSurface, colorFuture, () -> {
if (mLockPreviewWallpaperInfo != null) {
maybeLoadThumbnail(mLockPreviewWallpaperInfo, mLockWallpaperSurfaceCallback,
- mDisplayUtils.isOnWallpaperDisplay(mActivity));
+ mDisplayUtils.isSingleDisplayOrUnfoldedHorizontalHinge(mActivity), false);
}
});
mLockPreviewContainer = mLockscreenPreviewCard.findViewById(
@@ -218,14 +229,39 @@ public class WallpaperSectionController implements
wallpaperSectionView.findViewById(R.id.wallpaper_picker_entry).setOnClickListener(
v -> mSectionNavigationController.navigateTo(new CategorySelectorFragment()));
- mWorkspaceViewModel.getUpdateWorkspace().observe(mLifecycleOwner, update ->
- updateWorkspacePreview(mWorkspaceSurface, mWorkspaceSurfaceCallback,
- mWallpaperColorsViewModel.getHomeWallpaperColors().getValue())
- );
+ if (mOnThemingChanged != null) {
+ mOnThemingChanged.observe(mLifecycleOwner, update ->
+ updateWorkspacePreview(mWorkspaceSurface, mWorkspaceSurfaceCallback,
+ mWallpaperColorsViewModel.getHomeWallpaperColors().getValue())
+ );
+ }
return wallpaperSectionView;
}
+ private void updateLivePreviewVisibility(boolean visible) {
+ if (mHomeWallpaperConnection != null) {
+ mHomeWallpaperConnection.setVisibility(visible);
+ }
+ if (mLockWallpaperConnection != null) {
+ mLockWallpaperConnection.setVisibility(visible);
+ }
+ }
+
+ private void disconnectHomeLiveWallpaper() {
+ if (mHomeWallpaperConnection != null) {
+ mHomeWallpaperConnection.disconnect();
+ mHomeWallpaperConnection = null;
+ }
+ }
+
+ private void disconnectLockLiveWallpaper() {
+ if (mLockWallpaperConnection != null) {
+ mLockWallpaperConnection.disconnect();
+ mLockWallpaperConnection = null;
+ }
+ }
+
private void updateWorkspacePreview(SurfaceView workspaceSurface,
WorkspaceSurfaceHolderCallback callback, @Nullable WallpaperColors colors) {
// Reattach SurfaceView to trigger #surfaceCreated to update preview for different option.
@@ -234,7 +270,9 @@ public class WallpaperSectionController implements
parent.removeView(workspaceSurface);
if (callback != null) {
callback.resetLastSurface();
+ callback.setHideBottomRow(false);
callback.setWallpaperColors(colors);
+ callback.maybeRenderPreview();
}
parent.addView(workspaceSurface, viewIndex);
}
@@ -374,6 +412,18 @@ public class WallpaperSectionController implements
}
onLockWallpaperColorsChanged(lockColors);
+
+ // If we need to do the scrim fade, show the scrim first.
+ if (VideoWallpaperUtils.needsFadeIn(mHomePreviewWallpaperInfo)) {
+ mHomeFadeInScrim.animate().cancel();
+ mHomeFadeInScrim.setAlpha(1f);
+ mHomeFadeInScrim.setVisibility(View.VISIBLE);
+ }
+ if (VideoWallpaperUtils.needsFadeIn(mLockPreviewWallpaperInfo)) {
+ mLockFadeInScrim.animate().cancel();
+ mLockFadeInScrim.setAlpha(1f);
+ mLockFadeInScrim.setVisibility(View.VISIBLE);
+ }
}, forceRefresh);
}
@@ -394,15 +444,24 @@ public class WallpaperSectionController implements
// Load thumb regardless of live wallpaper to make sure we have a placeholder while
// the live wallpaper initializes in that case.
maybeLoadThumbnail(wallpaperInfo, surfaceCallback,
- mDisplayUtils.isOnWallpaperDisplay(mActivity));
-
- if (isHomeWallpaper) {
- if (mWallpaperConnection != null) {
- mWallpaperConnection.disconnect();
- mWallpaperConnection = null;
+ mDisplayUtils.isSingleDisplayOrUnfoldedHorizontalHinge(mActivity), isHomeWallpaper);
+
+ WallpaperManager wallpaperManager = WallpaperManager.getInstance(mActivity);
+ if (wallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+ if (isHomeWallpaper) {
+ disconnectHomeLiveWallpaper();
+ } else {
+ disconnectLockLiveWallpaper();
}
if (wallpaperInfo instanceof LiveWallpaperInfo) {
- setUpLiveWallpaperPreview(wallpaperInfo);
+ setUpLiveWallpaperPreview(wallpaperInfo, isHomeWallpaper);
+ }
+ } else {
+ if (isHomeWallpaper) {
+ disconnectHomeLiveWallpaper();
+ if (wallpaperInfo instanceof LiveWallpaperInfo) {
+ setUpLiveWallpaperPreviewLegacy(wallpaperInfo);
+ }
}
}
@@ -415,13 +474,19 @@ public class WallpaperSectionController implements
@NonNull
private Asset maybeLoadThumbnail(WallpaperInfo wallpaperInfo,
- WallpaperSurfaceCallback surfaceCallback, boolean offsetToStart) {
- ImageView imageView = surfaceCallback.getHomeImageWallpaper();
+ WallpaperSurfaceCallback surfaceCallback, boolean offsetToStart, boolean isHome) {
+ ImageView liveThumbnailView = isHome ? mHomeFadeInScrim : mLockFadeInScrim;
+ ImageView imageView = VideoWallpaperUtils.needsFadeIn(wallpaperInfo) ? liveThumbnailView
+ : surfaceCallback.getHomeImageWallpaper();
Asset thumbAsset = wallpaperInfo.getThumbAsset(mAppContext);
// Respect offsetToStart only for CurrentWallpaperAssetVN otherwise true.
offsetToStart = !(thumbAsset instanceof CurrentWallpaperAssetVN) || offsetToStart;
thumbAsset = new BitmapCachingAsset(mAppContext, thumbAsset);
if (imageView != null && imageView.getDrawable() == null) {
+ if (VideoWallpaperUtils.needsFadeIn(wallpaperInfo)) {
+ imageView.setRenderEffect(
+ RenderEffect.createBlurEffect(50f, 50f, TileMode.CLAMP));
+ }
thumbAsset.loadPreviewImage(mActivity, imageView,
ResourceUtils.getColorAttr(mActivity, android.R.attr.colorSecondary),
offsetToStart);
@@ -434,7 +499,7 @@ public class WallpaperSectionController implements
mWallpaperColorsViewModel.getHomeWallpaperColors().getValue())) {
return;
}
- mWallpaperColorsViewModel.getHomeWallpaperColors().setValue(wallpaperColors);
+ mWallpaperColorsViewModel.setHomeWallpaperColors(wallpaperColors);
}
private void onLockWallpaperColorsChanged(WallpaperColors wallpaperColors) {
@@ -442,20 +507,69 @@ public class WallpaperSectionController implements
mWallpaperColorsViewModel.getLockWallpaperColors().getValue())) {
return;
}
- mWallpaperColorsViewModel.getLockWallpaperColors().setValue(wallpaperColors);
+ mWallpaperColorsViewModel.setLockWallpaperColors(wallpaperColors);
if (mLockScreenPreviewer != null) {
mLockScreenPreviewer.setColor(wallpaperColors);
}
}
- private void setUpLiveWallpaperPreview(WallpaperInfo homeWallpaper) {
+ private void setUpLiveWallpaperPreview(WallpaperInfo wallpaper, boolean isHomeWallpaper) {
+ if (!isActivityAlive() || !WallpaperConnection.isPreviewAvailable()) {
+ return;
+ }
+
+ final boolean isHomeBoth = (mHomePreviewWallpaperInfo == mLockPreviewWallpaperInfo);
+ if (isHomeBoth && !isHomeWallpaper) {
+ // If home and lock are the same the preview is handled by mirroring the home preview,
+ // so the lock preview is a no-op.
+ return;
+ }
+
+ final SurfaceView mainSurface =
+ isHomeWallpaper ? mHomeWallpaperSurface : mLockWallpaperSurface;
+ final SurfaceView mirrorSurface = isHomeBoth ? mLockWallpaperSurface : null;
+ final WallpaperConnection connection = new WallpaperConnection(
+ getWallpaperIntent(wallpaper.getWallpaperComponent()), mActivity,
+ new WallpaperConnection.WallpaperConnectionListener() {
+ @Override
+ public void onWallpaperColorsChanged(WallpaperColors colors, int displayId) {
+ if (isHomeWallpaper) {
+ onHomeWallpaperColorsChanged(colors);
+ if (isHomeBoth && mLockScreenPreviewer != null) {
+ mLockScreenPreviewer.setColor(colors);
+ onLockWallpaperColorsChanged(colors);
+ }
+ } else {
+ onLockWallpaperColorsChanged(colors);
+ }
+ }
+ },
+ mainSurface, mirrorSurface);
+
+ connection.setVisibility(true);
+ if (isHomeWallpaper) {
+ mHomeWallpaperConnection = connection;
+ } else {
+ mLockWallpaperConnection = connection;
+ }
+ mainSurface.post(() -> {
+ if (mHomeWallpaperConnection != null && !mHomeWallpaperConnection.connect()) {
+ mHomeWallpaperConnection = null;
+ }
+ if (mLockWallpaperConnection != null && !mLockWallpaperConnection.connect()) {
+ mLockWallpaperConnection = null;
+ }
+ });
+ }
+
+ private void setUpLiveWallpaperPreviewLegacy(WallpaperInfo homeWallpaper) {
if (!isActivityAlive()) {
return;
}
if (WallpaperConnection.isPreviewAvailable()) {
final boolean isLockLive = mLockPreviewWallpaperInfo instanceof LiveWallpaperInfo;
- mWallpaperConnection = new WallpaperConnection(
+ mHomeWallpaperConnection = new WallpaperConnection(
getWallpaperIntent(homeWallpaper.getWallpaperComponent()), mActivity,
new WallpaperConnection.WallpaperConnectionListener() {
@Override
@@ -467,13 +581,29 @@ public class WallpaperSectionController implements
}
onHomeWallpaperColorsChanged(colors);
}
+
+ @Override
+ public void onEngineShown() {
+ if (VideoWallpaperUtils.needsFadeIn(homeWallpaper)) {
+ mHomeFadeInScrim.animate().alpha(0.0f)
+ .setDuration(VideoWallpaperUtils.TRANSITION_MILLIS)
+ .withEndAction(() -> mHomeFadeInScrim.setVisibility(
+ View.INVISIBLE));
+ if (isLockLive) {
+ mLockFadeInScrim.animate().alpha(0.0f)
+ .setDuration(VideoWallpaperUtils.TRANSITION_MILLIS)
+ .withEndAction(() -> mLockFadeInScrim.setVisibility(
+ View.INVISIBLE));
+ }
+ }
+ }
},
mHomeWallpaperSurface, isLockLive ? mLockWallpaperSurface : null);
- mWallpaperConnection.setVisibility(true);
+ mHomeWallpaperConnection.setVisibility(true);
mHomeWallpaperSurface.post(() -> {
- if (mWallpaperConnection != null && !mWallpaperConnection.connect()) {
- mWallpaperConnection = null;
+ if (mHomeWallpaperConnection != null && !mHomeWallpaperConnection.connect()) {
+ mHomeWallpaperConnection = null;
}
});
}
@@ -500,6 +630,7 @@ public class WallpaperSectionController implements
return !mActivity.isDestroyed() && !mActivity.isFinishing();
}
+ // TODO(b/276439056) Remove these animations as they have no effect
private void fadeWallpaperPreview(boolean isFadeIn, int duration) {
setupFade(mHomePreviewCard, mHomePreviewProgress, duration, isFadeIn);
setupFade(mLockscreenPreviewCard, mLockscreenPreviewProgress, duration, isFadeIn);
diff --git a/src/com/android/wallpaper/module/CustomizationSections.java b/src/com/android/wallpaper/module/CustomizationSections.java
index 66ae64d1..5fa62f76 100644
--- a/src/com/android/wallpaper/module/CustomizationSections.java
+++ b/src/com/android/wallpaper/module/CustomizationSections.java
@@ -11,7 +11,8 @@ import com.android.wallpaper.model.CustomizationSectionController.CustomizationS
import com.android.wallpaper.model.PermissionRequester;
import com.android.wallpaper.model.WallpaperColorsViewModel;
import com.android.wallpaper.model.WallpaperPreviewNavigator;
-import com.android.wallpaper.model.WorkspaceViewModel;
+import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor;
+import com.android.wallpaper.picker.customization.ui.viewmodel.WallpaperQuickSwitchViewModel;
import com.android.wallpaper.util.DisplayUtils;
import java.util.List;
@@ -26,24 +27,27 @@ public interface CustomizationSections {
}
/**
+ * Currently protected under BaseFlags.isUseRevampedUi() flag.
+ *
* Gets a new instance of the section controller list for the given {@link Screen}.
*
* Note that the section views will be displayed by the list ordering.
*
* <p>Don't keep the section controllers as singleton since they contain views.
*/
- List<CustomizationSectionController<?>> getSectionControllersForScreen(
+ List<CustomizationSectionController<?>> getRevampedUISectionControllersForScreen(
Screen screen,
FragmentActivity activity,
LifecycleOwner lifecycleOwner,
WallpaperColorsViewModel wallpaperColorsViewModel,
- WorkspaceViewModel workspaceViewModel,
PermissionRequester permissionRequester,
WallpaperPreviewNavigator wallpaperPreviewNavigator,
CustomizationSectionNavigationController sectionNavigationController,
@Nullable Bundle savedInstanceState,
CurrentWallpaperInfoFactory wallpaperInfoFactory,
- DisplayUtils displayUtils);
+ DisplayUtils displayUtils,
+ WallpaperQuickSwitchViewModel wallpaperQuickSwitchViewModel,
+ WallpaperInteractor wallpaperInteractor);
/**
* Gets a new instance of the section controller list.
@@ -56,7 +60,6 @@ public interface CustomizationSections {
FragmentActivity activity,
LifecycleOwner lifecycleOwner,
WallpaperColorsViewModel wallpaperColorsViewModel,
- WorkspaceViewModel workspaceViewModel,
PermissionRequester permissionRequester,
WallpaperPreviewNavigator wallpaperPreviewNavigator,
CustomizationSectionNavigationController sectionNavigationController,
diff --git a/src/com/android/wallpaper/module/DefaultCurrentWallpaperInfoFactory.java b/src/com/android/wallpaper/module/DefaultCurrentWallpaperInfoFactory.java
index 9e2787fd..c9f9b267 100755
--- a/src/com/android/wallpaper/module/DefaultCurrentWallpaperInfoFactory.java
+++ b/src/com/android/wallpaper/module/DefaultCurrentWallpaperInfoFactory.java
@@ -15,12 +15,14 @@
*/
package com.android.wallpaper.module;
+import android.app.WallpaperManager;
import android.content.Context;
import androidx.annotation.Nullable;
import com.android.wallpaper.compat.WallpaperManagerCompat;
import com.android.wallpaper.model.CurrentWallpaperInfoVN;
+import com.android.wallpaper.model.LiveWallpaperMetadata;
import com.android.wallpaper.model.WallpaperInfo;
import com.android.wallpaper.module.WallpaperPreferences.PresentationMode;
@@ -33,6 +35,7 @@ public class DefaultCurrentWallpaperInfoFactory implements CurrentWallpaperInfoF
private final Context mAppContext;
private final WallpaperRefresher mWallpaperRefresher;
private final LiveWallpaperInfoFactory mLiveWallpaperInfoFactory;
+ private final WallpaperManager mWallpaperManager;
// Cached copies of the currently-set WallpaperInfo(s) and presentation mode.
private WallpaperInfo mHomeWallpaper;
@@ -46,6 +49,7 @@ public class DefaultCurrentWallpaperInfoFactory implements CurrentWallpaperInfoF
Injector injector = InjectorProvider.getInjector();
mWallpaperRefresher = injector.getWallpaperRefresher(mAppContext);
mLiveWallpaperInfoFactory = injector.getLiveWallpaperInfoFactory(mAppContext);
+ mWallpaperManager = WallpaperManager.getInstance(context);
}
@Override
@@ -68,7 +72,10 @@ public class DefaultCurrentWallpaperInfoFactory implements CurrentWallpaperInfoF
mWallpaperRefresher.refresh(
(homeWallpaperMetadata, lockWallpaperMetadata, presentationMode) -> {
WallpaperInfo homeWallpaper;
- if (homeWallpaperMetadata.getWallpaperComponent() == null) {
+ if (homeWallpaperMetadata instanceof LiveWallpaperMetadata) {
+ homeWallpaper = mLiveWallpaperInfoFactory.getLiveWallpaperInfo(
+ homeWallpaperMetadata.getWallpaperComponent());
+ } else {
homeWallpaper = new CurrentWallpaperInfoVN(
homeWallpaperMetadata.getAttributions(),
homeWallpaperMetadata.getActionUrl(),
@@ -76,21 +83,24 @@ public class DefaultCurrentWallpaperInfoFactory implements CurrentWallpaperInfoF
homeWallpaperMetadata.getActionIconRes(),
homeWallpaperMetadata.getCollectionId(),
WallpaperManagerCompat.FLAG_SYSTEM);
- } else {
- homeWallpaper = mLiveWallpaperInfoFactory.getLiveWallpaperInfo(
- homeWallpaperMetadata.getWallpaperComponent());
}
WallpaperInfo lockWallpaper = null;
if (lockWallpaperMetadata != null) {
- lockWallpaper = new CurrentWallpaperInfoVN(
- lockWallpaperMetadata.getAttributions(),
- lockWallpaperMetadata.getActionUrl(),
- lockWallpaperMetadata.getActionLabelRes(),
- lockWallpaperMetadata.getActionIconRes(),
- lockWallpaperMetadata.getCollectionId(),
- WallpaperManagerCompat.FLAG_LOCK);
+
+ if (lockWallpaperMetadata instanceof LiveWallpaperMetadata) {
+ lockWallpaper = mLiveWallpaperInfoFactory.getLiveWallpaperInfo(
+ lockWallpaperMetadata.getWallpaperComponent());
+ } else {
+ lockWallpaper = new CurrentWallpaperInfoVN(
+ lockWallpaperMetadata.getAttributions(),
+ lockWallpaperMetadata.getActionUrl(),
+ lockWallpaperMetadata.getActionLabelRes(),
+ lockWallpaperMetadata.getActionIconRes(),
+ lockWallpaperMetadata.getCollectionId(),
+ WallpaperManagerCompat.FLAG_LOCK);
+ }
}
mHomeWallpaper = homeWallpaper;
diff --git a/src/com/android/wallpaper/module/DefaultWallpaperPersister.java b/src/com/android/wallpaper/module/DefaultWallpaperPersister.java
index 89cc09b3..fdfc9be7 100755
--- a/src/com/android/wallpaper/module/DefaultWallpaperPersister.java
+++ b/src/com/android/wallpaper/module/DefaultWallpaperPersister.java
@@ -15,6 +15,9 @@
*/
package com.android.wallpaper.module;
+import static android.app.WallpaperManager.FLAG_LOCK;
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.WallpaperColors;
@@ -448,7 +451,8 @@ public class DefaultWallpaperPersister implements WallpaperPersister {
int offsetY = Math.max(0, -(screenSize.y / 2 - scaledCenter.y));
Rect cropRect = WallpaperCropUtils.calculateCropRect(mAppContext, minWallpaperZoom,
- wallpaperSize, defaultCropSurfaceSize, screenSize, offsetX, offsetY);
+ wallpaperSize, defaultCropSurfaceSize, screenSize, offsetX,
+ offsetY, /* cropExtraWidth= */ true);
Rect scaledCropRect = new Rect(
(int) Math.floor((float) cropRect.left / minWallpaperZoom),
@@ -467,9 +471,9 @@ public class DefaultWallpaperPersister implements WallpaperPersister {
int wallpaperId = setBitmapToWallpaperManagerCompat(wallpaperBitmap,
/* allowBackup */ false, whichWallpaper);
if (wallpaperId > 0) {
- mWallpaperPreferences.storeLatestHomeWallpaper(String.valueOf(wallpaperId),
- attributions, actionUrl, collectionId, wallpaperBitmap,
- WallpaperColors.fromBitmap(wallpaperBitmap));
+ mWallpaperPreferences.storeLatestWallpaper(whichWallpaper,
+ String.valueOf(wallpaperId), attributions, actionUrl, collectionId,
+ wallpaperBitmap, WallpaperColors.fromBitmap(wallpaperBitmap));
}
return wallpaperId;
}
@@ -532,7 +536,7 @@ public class DefaultWallpaperPersister implements WallpaperPersister {
}
@Override
- public void onLiveWallpaperSet() {
+ public void onLiveWallpaperSet(@Destination int destination) {
android.app.WallpaperInfo currentWallpaperComponent = mWallpaperManager.getWallpaperInfo();
android.app.WallpaperInfo previewedWallpaperComponent = mWallpaperInfoInPreview != null
? mWallpaperInfoInPreview.getWallpaperComponent() : null;
@@ -541,13 +545,14 @@ public class DefaultWallpaperPersister implements WallpaperPersister {
// WallpaperInfo which was last previewed, then do nothing and nullify last previewed
// wallpaper.
if (currentWallpaperComponent == null || previewedWallpaperComponent == null
- || !currentWallpaperComponent.getPackageName()
- .equals(previewedWallpaperComponent.getPackageName())) {
+ || !currentWallpaperComponent.getServiceName()
+ .equals(previewedWallpaperComponent.getServiceName())) {
mWallpaperInfoInPreview = null;
return;
}
- setLiveWallpaperMetadata();
+ setLiveWallpaperMetadata(mWallpaperInfoInPreview, mWallpaperInfoInPreview.getEffectNames(),
+ destination);
}
/**
@@ -572,30 +577,29 @@ public class DefaultWallpaperPersister implements WallpaperPersister {
return isLockWallpaperSet;
}
- /**
- * Sets the live wallpaper's metadata on SharedPreferences.
- */
- private void setLiveWallpaperMetadata() {
- android.app.WallpaperInfo previewedWallpaperComponent =
- mWallpaperInfoInPreview.getWallpaperComponent();
+ @Override
+ public void setLiveWallpaperMetadata(WallpaperInfo wallpaperInfo, String effects,
+ @Destination int destination) {
+ android.app.WallpaperInfo component = wallpaperInfo.getWallpaperComponent();
+
+ if (destination == WallpaperPersister.DEST_HOME_SCREEN
+ || destination == WallpaperPersister.DEST_BOTH) {
+ mWallpaperPreferences.clearHomeWallpaperMetadata();
+ mWallpaperPreferences.setHomeWallpaperServiceName(component.getServiceName());
+ mWallpaperPreferences.setHomeWallpaperEffects(effects);
+
+ // Since rotation affects home screen only, disable it when setting home live wp
+ mWallpaperPreferences.setWallpaperPresentationMode(
+ WallpaperPreferences.PRESENTATION_MODE_STATIC);
+ mWallpaperPreferences.clearDailyRotations();
+ }
- mWallpaperPreferences.clearHomeWallpaperMetadata();
- // NOTE: We explicitly do not also clear the lock wallpaper metadata. Since the user may
- // have set the live wallpaper on the home screen only, we leave the lock wallpaper metadata
- // intact. If the user has set the live wallpaper for both home and lock screens, then the
- // WallpaperRefresher will pick up on that and update the preferences later.
- mWallpaperPreferences
- .setHomeWallpaperAttributions(mWallpaperInfoInPreview.getAttributions(mAppContext));
- mWallpaperPreferences.setHomeWallpaperPackageName(
- previewedWallpaperComponent.getPackageName());
- mWallpaperPreferences.setHomeWallpaperServiceName(
- previewedWallpaperComponent.getServiceName());
- mWallpaperPreferences.setHomeWallpaperCollectionId(
- mWallpaperInfoInPreview.getCollectionId(mAppContext));
- mWallpaperPreferences.setWallpaperPresentationMode(
- WallpaperPreferences.PRESENTATION_MODE_STATIC);
- mWallpaperPreferences.clearDailyRotations();
- mWallpaperPreferences.setWallpaperEffects(mWallpaperInfoInPreview.getEffectNames());
+ if (destination == WallpaperPersister.DEST_LOCK_SCREEN
+ || destination == WallpaperPersister.DEST_BOTH) {
+ mWallpaperPreferences.clearLockWallpaperMetadata();
+ mWallpaperPreferences.setLockWallpaperServiceName(component.getServiceName());
+ mWallpaperPreferences.setLockWallpaperEffects(effects);
+ }
}
private class SetWallpaperTask extends AsyncTask<Void, Void, Boolean> {
@@ -725,7 +729,7 @@ public class DefaultWallpaperPersister implements WallpaperPersister {
}
if (isSuccess) {
- mCallback.onSuccess(mWallpaper);
+ mCallback.onSuccess(mWallpaper, mDestination);
mWallpaperChangedNotifier.notifyWallpaperChanged();
} else {
mCallback.onError(null /* throwable */);
@@ -774,7 +778,7 @@ public class DefaultWallpaperPersister implements WallpaperPersister {
private void setImageWallpaperMetadata(@Destination int destination, int wallpaperId) {
if (destination == DEST_HOME_SCREEN || destination == DEST_BOTH) {
mWallpaperPreferences.clearHomeWallpaperMetadata();
- mWallpaperPreferences.setWallpaperEffects(null);
+ mWallpaperPreferences.setHomeWallpaperEffects(null);
setImageWallpaperHomeMetadata(wallpaperId);
// Reset presentation mode to STATIC if an individual wallpaper is set to the
@@ -820,7 +824,7 @@ public class DefaultWallpaperPersister implements WallpaperPersister {
mWallpaperPreferences.setHomeWallpaperCollectionId(
mWallpaper.getCollectionId(mAppContext));
mWallpaperPreferences.setHomeWallpaperRemoteId(mWallpaper.getWallpaperId());
- mWallpaperPreferences.storeLatestHomeWallpaper(
+ mWallpaperPreferences.storeLatestWallpaper(FLAG_SYSTEM,
TextUtils.isEmpty(mWallpaper.getWallpaperId()) ? String.valueOf(bitmapHash)
: mWallpaper.getWallpaperId(),
mWallpaper, mBitmap, colors);
@@ -843,42 +847,46 @@ public class DefaultWallpaperPersister implements WallpaperPersister {
// because WallpaperManager-generated IDs are specific to a physical device and
// cannot be used to identify a wallpaper image on another device after restore is
// complete.
- saveLockWallpaperHashCode();
+ Bitmap lockBitmap = getLockWallpaperBitmap();
+ if (lockBitmap != null) {
+ saveLockWallpaperHashCode(lockBitmap);
+ mWallpaperPreferences.storeLatestWallpaper(FLAG_LOCK,
+ TextUtils.isEmpty(mWallpaper.getWallpaperId())
+ ? String.valueOf(mWallpaperPreferences.getLockWallpaperHashCode())
+ : mWallpaper.getWallpaperId(),
+ mWallpaper, lockBitmap, WallpaperColors.fromBitmap(lockBitmap));
+ }
}
- private void saveLockWallpaperHashCode() {
- Bitmap lockBitmap = null;
-
+ private Bitmap getLockWallpaperBitmap() {
ParcelFileDescriptor parcelFd = mWallpaperManagerCompat.getWallpaperFile(
WallpaperManagerCompat.FLAG_LOCK);
if (parcelFd == null) {
- return;
+ return null;
}
- InputStream fileStream = null;
- try {
- fileStream = new FileInputStream(parcelFd.getFileDescriptor());
- lockBitmap = BitmapFactory.decodeStream(fileStream);
- parcelFd.close();
+ try (InputStream fileStream = new FileInputStream(parcelFd.getFileDescriptor())) {
+ return BitmapFactory.decodeStream(fileStream);
} catch (IOException e) {
- Log.e(TAG, "IO exception when closing the file descriptor.");
+ Log.e(TAG, "IO exception when closing the file stream.", e);
+ return null;
} finally {
- if (fileStream != null) {
- try {
- fileStream.close();
- } catch (IOException e) {
- Log.e(TAG,
- "IO exception when closing the input stream for the lock screen "
- + "WP.");
- }
+ try {
+ parcelFd.close();
+ } catch (IOException e) {
+ Log.e(TAG, "IO exception when closing the file descriptor.", e);
}
}
+ }
+ private long saveLockWallpaperHashCode(Bitmap lockBitmap) {
if (lockBitmap != null) {
long bitmapHash = BitmapUtils.generateHashCode(lockBitmap);
mWallpaperPreferences.setLockWallpaperHashCode(bitmapHash);
+ return bitmapHash;
}
+ return 0;
}
}
}
diff --git a/src/com/android/wallpaper/module/DefaultWallpaperPreferences.java b/src/com/android/wallpaper/module/DefaultWallpaperPreferences.java
index c2cd07b4..6f945a90 100755
--- a/src/com/android/wallpaper/module/DefaultWallpaperPreferences.java
+++ b/src/com/android/wallpaper/module/DefaultWallpaperPreferences.java
@@ -47,6 +47,7 @@ import java.util.TimeZone;
* Default implementation that writes to and reads from SharedPreferences.
*/
public class DefaultWallpaperPreferences implements WallpaperPreferences {
+
public static final String PREFS_NAME = "wallpaper";
public static final String NO_BACKUP_PREFS_NAME = "wallpaper-nobackup";
@@ -154,10 +155,6 @@ public class DefaultWallpaperPreferences implements WallpaperPreferences {
editor.putInt(NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED,
mSharedPrefs.getInt(NoBackupKeys.KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED, 0));
}
- if (mSharedPrefs.contains(NoBackupKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME)) {
- editor.putString(NoBackupKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME,
- mSharedPrefs.getString(NoBackupKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME, null));
- }
if (mSharedPrefs.contains(NoBackupKeys.KEY_HOME_WALLPAPER_SERVICE_NAME)) {
editor.putString(NoBackupKeys.KEY_HOME_WALLPAPER_SERVICE_NAME,
mSharedPrefs.getString(NoBackupKeys.KEY_HOME_WALLPAPER_SERVICE_NAME, null));
@@ -325,7 +322,7 @@ public class DefaultWallpaperPreferences implements WallpaperPreferences {
.apply();
mNoBackupPrefs.edit()
- .remove(NoBackupKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME)
+ .remove(NoBackupKeys.KEY_HOME_WALLPAPER_SERVICE_NAME)
.remove(NoBackupKeys.KEY_HOME_WALLPAPER_MANAGER_ID)
.remove(NoBackupKeys.KEY_HOME_WALLPAPER_REMOTE_ID)
.remove(NoBackupKeys.KEY_HOME_WALLPAPER_SERVICE_NAME)
@@ -335,19 +332,6 @@ public class DefaultWallpaperPreferences implements WallpaperPreferences {
}
@Override
- public String getHomeWallpaperPackageName() {
- return mNoBackupPrefs.getString(
- NoBackupKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME, null);
- }
-
- @Override
- public void setHomeWallpaperPackageName(String packageName) {
- mNoBackupPrefs.edit().putString(
- NoBackupKeys.KEY_HOME_WALLPAPER_PACKAGE_NAME, packageName)
- .apply();
- }
-
- @Override
public String getHomeWallpaperServiceName() {
return mNoBackupPrefs.getString(
NoBackupKeys.KEY_HOME_WALLPAPER_SERVICE_NAME, null);
@@ -542,6 +526,17 @@ public class DefaultWallpaperPreferences implements WallpaperPreferences {
}
@Override
+ public String getLockWallpaperServiceName() {
+ return mNoBackupPrefs.getString(NoBackupKeys.KEY_LOCK_WALLPAPER_SERVICE_NAME, null);
+ }
+
+ @Override
+ public void setLockWallpaperServiceName(String serviceName) {
+ mNoBackupPrefs.edit().putString(NoBackupKeys.KEY_LOCK_WALLPAPER_SERVICE_NAME, serviceName)
+ .apply();
+ }
+
+ @Override
public void addDailyRotation(long timestamp) {
String jsonString = mNoBackupPrefs.getString(
NoBackupKeys.KEY_DAILY_ROTATION_TIMESTAMPS, "[]");
@@ -942,19 +937,32 @@ public class DefaultWallpaperPreferences implements WallpaperPreferences {
setLockWallpaperCollectionId(collectionId);
setLockWallpaperRemoteId(wallpaperId);
}
- setWallpaperEffects(null);
+ setHomeWallpaperEffects(null);
+ }
+
+ @Override
+ public String getHomeWallpaperEffects() {
+ return mNoBackupPrefs.getString(
+ NoBackupKeys.KEY_HOME_WALLPAPER_EFFECTS, null);
+ }
+
+ @Override
+ public void setHomeWallpaperEffects(String effects) {
+ mNoBackupPrefs.edit().putString(
+ NoBackupKeys.KEY_HOME_WALLPAPER_EFFECTS, effects)
+ .apply();
}
@Override
- public String getWallpaperEffects() {
+ public String getLockWallpaperEffects() {
return mNoBackupPrefs.getString(
- NoBackupKeys.KEY_WALLPAPER_EFFECTS, null);
+ NoBackupKeys.KEY_LOCK_WALLPAPER_EFFECTS, null);
}
@Override
- public void setWallpaperEffects(String effects) {
+ public void setLockWallpaperEffects(String effects) {
mNoBackupPrefs.edit().putString(
- NoBackupKeys.KEY_WALLPAPER_EFFECTS, effects)
+ NoBackupKeys.KEY_LOCK_WALLPAPER_EFFECTS, effects)
.apply();
}
diff --git a/src/com/android/wallpaper/module/DefaultWallpaperRefresher.java b/src/com/android/wallpaper/module/DefaultWallpaperRefresher.java
index 3363d8ea..3c870280 100755
--- a/src/com/android/wallpaper/module/DefaultWallpaperRefresher.java
+++ b/src/com/android/wallpaper/module/DefaultWallpaperRefresher.java
@@ -15,6 +15,9 @@
*/
package com.android.wallpaper.module;
+import static com.android.wallpaper.compat.WallpaperManagerCompat.FLAG_LOCK;
+import static com.android.wallpaper.compat.WallpaperManagerCompat.FLAG_SYSTEM;
+
import android.annotation.SuppressLint;
import android.app.WallpaperManager;
import android.content.Context;
@@ -29,6 +32,7 @@ import android.util.Log;
import com.android.wallpaper.R;
import com.android.wallpaper.asset.BitmapUtils;
import com.android.wallpaper.compat.WallpaperManagerCompat;
+import com.android.wallpaper.model.LiveWallpaperMetadata;
import com.android.wallpaper.model.WallpaperMetadata;
import java.io.FileInputStream;
@@ -44,6 +48,7 @@ import java.util.List;
*/
@SuppressLint("ServiceCast")
public class DefaultWallpaperRefresher implements WallpaperRefresher {
+
private static final String TAG = "DefaultWPRefresher";
private final Context mAppContext;
@@ -77,12 +82,13 @@ public class DefaultWallpaperRefresher implements WallpaperRefresher {
*/
private class GetWallpaperMetadataAsyncTask extends
AsyncTask<Void, Void, List<WallpaperMetadata>> {
+
private final RefreshListener mListener;
private final WallpaperManagerCompat mWallpaperManagerCompat;
private long mCurrentHomeWallpaperHashCode;
private long mCurrentLockWallpaperHashCode;
- private String mSystemWallpaperPackageName;
+ private String mSystemWallpaperServiceName;
@SuppressLint("ServiceCast")
public GetWallpaperMetadataAsyncTask(RefreshListener listener) {
@@ -95,18 +101,17 @@ public class DefaultWallpaperRefresher implements WallpaperRefresher {
protected List<WallpaperMetadata> doInBackground(Void... unused) {
List<WallpaperMetadata> wallpaperMetadatas = new ArrayList<>();
- if (!isHomeScreenMetadataCurrent() || isHomeScreenAttributionsEmpty()) {
+ boolean isHomeScreenStatic = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) == null;
+ if (!isHomeScreenMetadataCurrent() || (isHomeScreenStatic
+ && isHomeScreenAttributionsEmpty())) {
mWallpaperPreferences.clearHomeWallpaperMetadata();
setFallbackHomeScreenWallpaperMetadata();
}
- boolean isLockScreenWallpaperCurrentlySet = mWallpaperStatusChecker.isLockWallpaperSet(
- mAppContext);
+ boolean isLockScreenWallpaperCurrentlySet =
+ mWallpaperStatusChecker.isLockWallpaperSet(mAppContext);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N
- || !isLockScreenWallpaperCurrentlySet) {
-
- // Return only home metadata if pre-N device or lock screen wallpaper is not explicitly set.
+ if (mWallpaperManager.getWallpaperInfo() == null) {
wallpaperMetadatas.add(new WallpaperMetadata(
mWallpaperPreferences.getHomeWallpaperAttributions(),
mWallpaperPreferences.getHomeWallpaperActionUrl(),
@@ -114,32 +119,40 @@ public class DefaultWallpaperRefresher implements WallpaperRefresher {
mWallpaperPreferences.getHomeWallpaperActionIconRes(),
mWallpaperPreferences.getHomeWallpaperCollectionId(),
mWallpaperPreferences.getHomeWallpaperBackingFileName(),
- mWallpaperManager.getWallpaperInfo()));
+ null));
+ } else {
+ wallpaperMetadatas.add(
+ new LiveWallpaperMetadata(mWallpaperManager.getWallpaperInfo()));
+ }
+
+ // Return only home metadata if pre-N device or lock screen wallpaper is not explicitly
+ // set.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N
+ || !isLockScreenWallpaperCurrentlySet) {
return wallpaperMetadatas;
}
- if (!isLockScreenMetadataCurrent() || isLockScreenAttributionsEmpty()) {
+ boolean isLockScreenStatic = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) == null;
+ if (!isLockScreenMetadataCurrent() || (isLockScreenStatic
+ && isLockScreenAttributionsEmpty())) {
mWallpaperPreferences.clearLockWallpaperMetadata();
setFallbackLockScreenWallpaperMetadata();
}
- wallpaperMetadatas.add(new WallpaperMetadata(
- mWallpaperPreferences.getHomeWallpaperAttributions(),
- mWallpaperPreferences.getHomeWallpaperActionUrl(),
- mWallpaperPreferences.getHomeWallpaperActionLabelRes(),
- mWallpaperPreferences.getHomeWallpaperActionIconRes(),
- mWallpaperPreferences.getHomeWallpaperCollectionId(),
- mWallpaperPreferences.getHomeWallpaperBackingFileName(),
- mWallpaperManager.getWallpaperInfo()));
-
- wallpaperMetadatas.add(new WallpaperMetadata(
- mWallpaperPreferences.getLockWallpaperAttributions(),
- mWallpaperPreferences.getLockWallpaperActionUrl(),
- mWallpaperPreferences.getLockWallpaperActionLabelRes(),
- mWallpaperPreferences.getLockWallpaperActionIconRes(),
- mWallpaperPreferences.getLockWallpaperCollectionId(),
- mWallpaperPreferences.getLockWallpaperBackingFileName(),
- null /* wallpaperComponent */));
+ if (mWallpaperManager.getWallpaperInfo(FLAG_LOCK) == null
+ || !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+ wallpaperMetadatas.add(new WallpaperMetadata(
+ mWallpaperPreferences.getLockWallpaperAttributions(),
+ mWallpaperPreferences.getLockWallpaperActionUrl(),
+ mWallpaperPreferences.getLockWallpaperActionLabelRes(),
+ mWallpaperPreferences.getLockWallpaperActionIconRes(),
+ mWallpaperPreferences.getLockWallpaperCollectionId(),
+ mWallpaperPreferences.getLockWallpaperBackingFileName(),
+ null));
+ } else {
+ wallpaperMetadatas.add(new LiveWallpaperMetadata(
+ mWallpaperManager.getWallpaperInfo(FLAG_LOCK)));
+ }
return wallpaperMetadatas;
}
@@ -147,8 +160,9 @@ public class DefaultWallpaperRefresher implements WallpaperRefresher {
@Override
protected void onPostExecute(List<WallpaperMetadata> metadatas) {
if (metadatas.size() > 2) {
- Log.e(TAG, "Got more than 2 WallpaperMetadata objects - only home and (optionally) lock "
- + "are permitted.");
+ Log.e(TAG,
+ "Got more than 2 WallpaperMetadata objects - only home and (optionally) "
+ + "lock are permitted.");
return;
}
@@ -157,27 +171,30 @@ public class DefaultWallpaperRefresher implements WallpaperRefresher {
}
/**
- * Sets fallback wallpaper attributions to WallpaperPreferences when the saved metadata did not
- * match the system wallpaper. For live wallpapers, loads the label (title) but for image
- * wallpapers loads a generic title string.
+ * Sets fallback wallpaper attributions to WallpaperPreferences when the saved metadata did
+ * not match the system wallpaper. For live wallpapers, loads the label (title) but for
+ * image wallpapers loads a generic title string.
*/
private void setFallbackHomeScreenWallpaperMetadata() {
android.app.WallpaperInfo wallpaperComponent = mWallpaperManager.getWallpaperInfo();
if (wallpaperComponent == null) { // Image wallpaper
mWallpaperPreferences.setHomeWallpaperAttributions(
- Arrays.asList(mAppContext.getResources().getString(R.string.fallback_wallpaper_title)));
+ Arrays.asList(mAppContext.getResources()
+ .getString(R.string.fallback_wallpaper_title)));
- // Set wallpaper ID if at least N or set a hash code if an earlier version of Android.
+ // Set wallpaper ID if at least N or set a hash code if an earlier version of
+ // Android.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- mWallpaperPreferences.setHomeWallpaperManagerId(mWallpaperManagerCompat.getWallpaperId(
- WallpaperManagerCompat.FLAG_SYSTEM));
+ mWallpaperPreferences.setHomeWallpaperManagerId(
+ mWallpaperManagerCompat.getWallpaperId(FLAG_SYSTEM));
} else {
- mWallpaperPreferences.setHomeWallpaperHashCode(getCurrentHomeWallpaperHashCode());
+ mWallpaperPreferences.setHomeWallpaperHashCode(
+ getCurrentHomeWallpaperHashCode());
}
} else { // Live wallpaper
mWallpaperPreferences.setHomeWallpaperAttributions(Arrays.asList(
wallpaperComponent.loadLabel(mAppContext.getPackageManager()).toString()));
- mWallpaperPreferences.setHomeWallpaperPackageName(mSystemWallpaperPackageName);
+ mWallpaperPreferences.setHomeWallpaperServiceName(mSystemWallpaperServiceName);
}
mWallpaperPreferences.setWallpaperPresentationMode(
WallpaperPreferences.PRESENTATION_MODE_STATIC);
@@ -185,14 +202,15 @@ public class DefaultWallpaperRefresher implements WallpaperRefresher {
/**
* Sets fallback lock screen wallpaper attributions to WallpaperPreferences. This should be
- * called when the saved lock screen wallpaper metadata does not match the currently set lock
- * screen wallpaper.
+ * called when the saved lock screen wallpaper metadata does not match the currently set
+ * lock screen wallpaper.
*/
private void setFallbackLockScreenWallpaperMetadata() {
mWallpaperPreferences.setLockWallpaperAttributions(
- Arrays.asList(mAppContext.getResources().getString(R.string.fallback_wallpaper_title)));
+ Arrays.asList(mAppContext.getResources()
+ .getString(R.string.fallback_wallpaper_title)));
mWallpaperPreferences.setLockWallpaperId(mWallpaperManagerCompat.getWallpaperId(
- WallpaperManagerCompat.FLAG_LOCK));
+ FLAG_LOCK));
}
/**
@@ -209,7 +227,8 @@ public class DefaultWallpaperRefresher implements WallpaperRefresher {
* Returns whether the home screen attributions saved in WallpaperPreferences is empty.
*/
private boolean isHomeScreenAttributionsEmpty() {
- List<String> homeScreenAttributions = mWallpaperPreferences.getHomeWallpaperAttributions();
+ List<String> homeScreenAttributions =
+ mWallpaperPreferences.getHomeWallpaperAttributions();
return homeScreenAttributions.get(0) == null
&& homeScreenAttributions.get(1) == null
&& homeScreenAttributions.get(2) == null;
@@ -217,13 +236,15 @@ public class DefaultWallpaperRefresher implements WallpaperRefresher {
private long getCurrentHomeWallpaperHashCode() {
if (mCurrentHomeWallpaperHashCode == 0) {
- BitmapDrawable wallpaperDrawable = (BitmapDrawable) mWallpaperManagerCompat.getDrawable();
- Bitmap wallpaperBitmap = wallpaperDrawable.getBitmap();
- mCurrentHomeWallpaperHashCode = BitmapUtils.generateHashCode(wallpaperBitmap);
-
- // Manually request that WallpaperManager loses its reference to the current wallpaper
- // bitmap, which can occupy a large memory allocation for the lifetime of the app.
- mWallpaperManager.forgetLoadedWallpaper();
+ BitmapDrawable wallpaperDrawable = (BitmapDrawable)
+ mWallpaperManagerCompat.getDrawable();
+ Bitmap wallpaperBitmap = wallpaperDrawable.getBitmap();
+ mCurrentHomeWallpaperHashCode = BitmapUtils.generateHashCode(wallpaperBitmap);
+
+ // Manually request that WallpaperManager loses its reference to the current
+ // wallpaper bitmap, which can occupy a large memory allocation for the lifetime of
+ // the app.
+ mWallpaperManager.forgetLoadedWallpaper();
}
return mCurrentHomeWallpaperHashCode;
}
@@ -245,7 +266,7 @@ public class DefaultWallpaperRefresher implements WallpaperRefresher {
Bitmap lockBitmap = null;
ParcelFileDescriptor pfd = mWallpaperManagerCompat.getWallpaperFile(
- WallpaperManagerCompat.FLAG_LOCK);
+ FLAG_LOCK);
// getWallpaperFile returns null if the lock screen isn't explicitly set, so need this
// check.
if (pfd != null) {
@@ -262,7 +283,8 @@ public class DefaultWallpaperRefresher implements WallpaperRefresher {
try {
fileStream.close();
} catch (IOException e) {
- Log.e(TAG, "IO exception when closing input stream for lock screen WP.");
+ Log.e(TAG,
+ "IO exception when closing input stream for lock screen WP.");
}
}
}
@@ -278,25 +300,25 @@ public class DefaultWallpaperRefresher implements WallpaperRefresher {
private boolean isHomeScreenImageWallpaperCurrent() {
long savedBitmapHash = mWallpaperPreferences.getHomeWallpaperHashCode();
- // Use WallpaperManager IDs to check same-ness of image wallpaper on N+ versions of Android
- // only when there is no saved bitmap hash code (which could be leftover from a previous build
- // of the app that did not use wallpaper IDs).
+ // Use WallpaperManager IDs to check same-ness of image wallpaper on N+ versions of
+ // Android only when there is no saved bitmap hash code (which could be leftover from a
+ // previous build of the app that did not use wallpaper IDs).
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && savedBitmapHash == 0) {
return mWallpaperPreferences.getHomeWallpaperManagerId()
- == mWallpaperManagerCompat.getWallpaperId(WallpaperManagerCompat.FLAG_SYSTEM);
+ == mWallpaperManagerCompat.getWallpaperId(FLAG_SYSTEM);
}
return savedBitmapHash == getCurrentHomeWallpaperHashCode();
}
/**
- * Returns whether the live wallpaper set to the system's home screen matches the metadata in
- * WallpaperPreferences.
+ * Returns whether the live wallpaper set to the system's home screen matches the metadata
+ * in WallpaperPreferences.
*/
private boolean isHomeScreenLiveWallpaperCurrent() {
- mSystemWallpaperPackageName = mWallpaperManager.getWallpaperInfo().getPackageName();
- String homeWallpaperPackageName = mWallpaperPreferences.getHomeWallpaperPackageName();
- return mSystemWallpaperPackageName.equals(homeWallpaperPackageName);
+ mSystemWallpaperServiceName = mWallpaperManager.getWallpaperInfo().getServiceName();
+ String homeWallpaperServiceName = mWallpaperPreferences.getHomeWallpaperServiceName();
+ return mSystemWallpaperServiceName.equals(homeWallpaperServiceName);
}
/**
@@ -304,18 +326,42 @@ public class DefaultWallpaperRefresher implements WallpaperRefresher {
* current lock screen wallpaper.
*/
private boolean isLockScreenMetadataCurrent() {
- // Check for lock wallpaper image same-ness only when there is no stored lock wallpaper hash
- // code. Otherwise if there is a lock wallpaper hash code stored in
+ return (mWallpaperManager.getWallpaperInfo(FLAG_LOCK) == null)
+ ? isLockScreenImageWallpaperCurrent()
+ : isLockScreenLiveWallpaperCurrent();
+ }
+
+ /**
+ * Returns whether the image wallpaper set for the lock screen matches the metadata in
+ * WallpaperPreferences.
+ */
+ private boolean isLockScreenImageWallpaperCurrent() {
+ // Check for lock wallpaper image same-ness only when there is no stored lock wallpaper
+ // hash code. Otherwise if there is a lock wallpaper hash code stored in
// {@link WallpaperPreferences}, then check hash codes.
long savedLockWallpaperHash = mWallpaperPreferences.getLockWallpaperHashCode();
- return (savedLockWallpaperHash == 0)
- ? mWallpaperPreferences.getLockWallpaperId()
- == mWallpaperManagerCompat.getWallpaperId(WallpaperManagerCompat.FLAG_LOCK)
- : savedLockWallpaperHash == getCurrentLockWallpaperHashCode();
+ if (savedLockWallpaperHash == 0) {
+ return mWallpaperPreferences.getLockWallpaperId()
+ == mWallpaperManagerCompat.getWallpaperId(FLAG_LOCK);
+ } else {
+ return savedLockWallpaperHash == getCurrentLockWallpaperHashCode();
+ }
}
/**
+ * Returns whether the live wallpaper for the home screen matches the metadata in
+ * WallpaperPreferences.
+ */
+ private boolean isLockScreenLiveWallpaperCurrent() {
+ String currentServiceName = mWallpaperManager.getWallpaperInfo(FLAG_LOCK)
+ .getServiceName();
+ String storedServiceName = mWallpaperPreferences.getLockWallpaperServiceName();
+ return currentServiceName.equals(storedServiceName);
+ }
+
+
+ /**
* Returns whether the lock screen attributions saved in WallpaperPreferences are empty.
*/
private boolean isLockScreenAttributionsEmpty() {
diff --git a/src/com/android/wallpaper/module/Injector.kt b/src/com/android/wallpaper/module/Injector.kt
index bcbf0e59..37b2c20a 100755
--- a/src/com/android/wallpaper/module/Injector.kt
+++ b/src/com/android/wallpaper/module/Injector.kt
@@ -15,20 +15,22 @@
*/
package com.android.wallpaper.module
-import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
+import androidx.activity.ComponentActivity
import androidx.fragment.app.Fragment
import com.android.wallpaper.compat.WallpaperManagerCompat
import com.android.wallpaper.config.BaseFlags
import com.android.wallpaper.effects.EffectsController
-import com.android.wallpaper.effects.EffectsController.EffectsServiceListener
import com.android.wallpaper.model.CategoryProvider
+import com.android.wallpaper.model.WallpaperColorsViewModel
import com.android.wallpaper.model.WallpaperInfo
import com.android.wallpaper.monitor.PerformanceMonitor
import com.android.wallpaper.network.Requester
import com.android.wallpaper.picker.PreviewFragment
+import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor
+import com.android.wallpaper.picker.customization.domain.interactor.WallpaperSnapshotRestorer
import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
import com.android.wallpaper.picker.undo.domain.interactor.UndoInteractor
import com.android.wallpaper.util.DisplayUtils
@@ -46,7 +48,7 @@ interface Injector {
fun getCurrentWallpaperInfoFactory(context: Context): CurrentWallpaperInfoFactory
- fun getCustomizationSections(activity: Activity): CustomizationSections
+ fun getCustomizationSections(activity: ComponentActivity): CustomizationSections
fun getDeepLinkRedirectIntent(context: Context, uri: Uri): Intent
@@ -56,11 +58,11 @@ interface Injector {
fun getDrawableLayerResolver(): DrawableLayerResolver
- fun getEffectsController(context: Context, listener: EffectsServiceListener): EffectsController?
+ fun getEffectsController(context: Context): EffectsController?
fun getExploreIntentChecker(context: Context): ExploreIntentChecker
- fun getIndividualPickerFragment(collectionId: String): Fragment
+ fun getIndividualPickerFragment(context: Context, collectionId: String): Fragment
fun getLiveWallpaperInfoFactory(context: Context): LiveWallpaperInfoFactory
@@ -114,4 +116,10 @@ interface Injector {
// Empty because we don't support undoing in WallpaperPicker2.
return HashMap()
}
+
+ fun getWallpaperInteractor(context: Context): WallpaperInteractor
+
+ fun getWallpaperSnapshotRestorer(context: Context): WallpaperSnapshotRestorer
+
+ fun getWallpaperColorsViewModel(): WallpaperColorsViewModel
}
diff --git a/src/com/android/wallpaper/module/LargeScreenMultiPanesChecker.kt b/src/com/android/wallpaper/module/LargeScreenMultiPanesChecker.kt
index 7e42d2da..5b4e5ec6 100644
--- a/src/com/android/wallpaper/module/LargeScreenMultiPanesChecker.kt
+++ b/src/com/android/wallpaper/module/LargeScreenMultiPanesChecker.kt
@@ -39,6 +39,7 @@ class LargeScreenMultiPanesChecker : MultiPanesChecker {
override fun getMultiPanesIntent(intent: Intent): Intent {
return Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY).apply {
+ intent.extras?.let { putExtras(it) }
putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, VALUE_HIGHLIGHT_MENU)
putExtra(
EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI,
diff --git a/src/com/android/wallpaper/module/WallpaperPersister.java b/src/com/android/wallpaper/module/WallpaperPersister.java
index cfb3cab9..9b5cae82 100755
--- a/src/com/android/wallpaper/module/WallpaperPersister.java
+++ b/src/com/android/wallpaper/module/WallpaperPersister.java
@@ -167,14 +167,26 @@ public interface WallpaperPersister {
* Saves attributions to WallpaperPreferences for the last previewed wallpaper if it has an
* {@link android.app.WallpaperInfo} component matching the one currently set to the
* {@link android.app.WallpaperManager}.
+ *
+ * @param destination Live wallpaper destination (home/lock/both)
+ */
+ void onLiveWallpaperSet(@Destination int destination);
+
+ /**
+ * Updates lie wallpaper metadata by persisting them to SharedPreferences.
+ *
+ * @param wallpaperInfo Wallpaper model for the live wallpaper
+ * @param effects Comma-separate list of effect (see {@link WallpaperInfo#getEffectNames})
+ * @param destination Live wallpaper destination (home/lock/both)
*/
- void onLiveWallpaperSet();
+ void setLiveWallpaperMetadata(WallpaperInfo wallpaperInfo, String effects,
+ @Destination int destination);
/**
* Interface for tracking success or failure of set wallpaper operations.
*/
interface SetWallpaperCallback {
- void onSuccess(WallpaperInfo wallpaperInfo);
+ void onSuccess(WallpaperInfo wallpaperInfo, @Destination int destination);
void onError(@Nullable Throwable throwable);
}
@@ -215,4 +227,20 @@ public interface WallpaperPersister {
throw new AssertionError("Unknown @Destination");
}
}
+
+ /**
+ * Converts a set of {@link SetWallpaperFlags} to the corresponding {@link Destination}.
+ */
+ @Destination
+ static int flagsToDestination(@SetWallpaperFlags int flags) {
+ if (flags == (FLAG_SYSTEM | FLAG_LOCK)) {
+ return DEST_BOTH;
+ } else if (flags == FLAG_SYSTEM) {
+ return DEST_HOME_SCREEN;
+ } else if (flags == FLAG_LOCK) {
+ return DEST_LOCK_SCREEN;
+ } else {
+ throw new AssertionError("Unknown @SetWallpaperFlags value");
+ }
+ }
}
diff --git a/src/com/android/wallpaper/module/WallpaperPicker2Injector.kt b/src/com/android/wallpaper/module/WallpaperPicker2Injector.kt
index d97e4bea..db560c4d 100755
--- a/src/com/android/wallpaper/module/WallpaperPicker2Injector.kt
+++ b/src/com/android/wallpaper/module/WallpaperPicker2Injector.kt
@@ -15,18 +15,18 @@
*/
package com.android.wallpaper.module
-import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
+import androidx.activity.ComponentActivity
import androidx.fragment.app.Fragment
import com.android.wallpaper.compat.WallpaperManagerCompat
import com.android.wallpaper.config.BaseFlags
import com.android.wallpaper.effects.EffectsController
-import com.android.wallpaper.effects.EffectsController.EffectsServiceListener
import com.android.wallpaper.model.CategoryProvider
import com.android.wallpaper.model.LiveWallpaperInfo
+import com.android.wallpaper.model.WallpaperColorsViewModel
import com.android.wallpaper.model.WallpaperInfo
import com.android.wallpaper.monitor.PerformanceMonitor
import com.android.wallpaper.network.Requester
@@ -35,13 +35,20 @@ import com.android.wallpaper.picker.CustomizationPickerActivity
import com.android.wallpaper.picker.ImagePreviewFragment
import com.android.wallpaper.picker.LivePreviewFragment
import com.android.wallpaper.picker.PreviewFragment
+import com.android.wallpaper.picker.customization.data.content.WallpaperClientImpl
+import com.android.wallpaper.picker.customization.data.repository.WallpaperRepository
+import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor
+import com.android.wallpaper.picker.customization.domain.interactor.WallpaperSnapshotRestorer
import com.android.wallpaper.picker.individual.IndividualPickerFragment
import com.android.wallpaper.picker.undo.data.repository.UndoRepository
import com.android.wallpaper.picker.undo.domain.interactor.UndoInteractor
+import com.android.wallpaper.settings.data.repository.SecureSettingsRepository
+import com.android.wallpaper.settings.data.repository.SecureSettingsRepositoryImpl
import com.android.wallpaper.util.DisplayUtils
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
-open class WallpaperPicker2Injector : Injector {
+open class WallpaperPicker2Injector() : Injector {
private var alarmManagerWrapper: AlarmManagerWrapper? = null
private var bitmapCropper: BitmapCropper? = null
private var categoryProvider: CategoryProvider? = null
@@ -67,6 +74,10 @@ open class WallpaperPicker2Injector : Injector {
private var wallpaperStatusChecker: WallpaperStatusChecker? = null
private var flags: BaseFlags? = null
private var undoInteractor: UndoInteractor? = null
+ private var wallpaperInteractor: WallpaperInteractor? = null
+ private var wallpaperSnapshotRestorer: WallpaperSnapshotRestorer? = null
+ private var secureSettingsRepository: SecureSettingsRepository? = null
+ private var wallpaperColorsViewModel: WallpaperColorsViewModel? = null
@Synchronized
override fun getAlarmManagerWrapper(context: Context): AlarmManagerWrapper {
@@ -94,8 +105,7 @@ open class WallpaperPicker2Injector : Injector {
}
}
- override fun getCustomizationSections(activity: Activity): CustomizationSections {
-
+ override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections {
return customizationSections
?: WallpaperPickerSections().also { customizationSections = it }
}
@@ -123,7 +133,6 @@ open class WallpaperPicker2Injector : Injector {
override fun getEffectsController(
context: Context,
- listener: EffectsServiceListener
): EffectsController? {
return null
}
@@ -136,8 +145,7 @@ open class WallpaperPicker2Injector : Injector {
}
}
- @Synchronized
- override fun getIndividualPickerFragment(collectionId: String): Fragment {
+ override fun getIndividualPickerFragment(context: Context, collectionId: String): Fragment {
return IndividualPickerFragment.newInstance(collectionId)
}
@@ -275,6 +283,42 @@ open class WallpaperPicker2Injector : Injector {
}
}
+ override fun getWallpaperInteractor(context: Context): WallpaperInteractor {
+ return wallpaperInteractor
+ ?: WallpaperInteractor(
+ repository =
+ WallpaperRepository(
+ scope = GlobalScope,
+ client = WallpaperClientImpl(context = context),
+ backgroundDispatcher = Dispatchers.IO,
+ ),
+ )
+ .also { wallpaperInteractor = it }
+ }
+
+ override fun getWallpaperSnapshotRestorer(context: Context): WallpaperSnapshotRestorer {
+ return wallpaperSnapshotRestorer
+ ?: WallpaperSnapshotRestorer(
+ scope = GlobalScope,
+ interactor = getWallpaperInteractor(context),
+ )
+ .also { wallpaperSnapshotRestorer = it }
+ }
+
+ protected fun getSecureSettingsRepository(context: Context): SecureSettingsRepository {
+ return secureSettingsRepository
+ ?: SecureSettingsRepositoryImpl(
+ contentResolver = context.contentResolver,
+ backgroundDispatcher = Dispatchers.IO,
+ )
+ .also { secureSettingsRepository = it }
+ }
+
+ override fun getWallpaperColorsViewModel(): WallpaperColorsViewModel {
+ return wallpaperColorsViewModel
+ ?: WallpaperColorsViewModel().also { wallpaperColorsViewModel = it }
+ }
+
companion object {
/**
* When this injector is overridden, this is the minimal value that should be used by
diff --git a/src/com/android/wallpaper/module/WallpaperPickerSections.java b/src/com/android/wallpaper/module/WallpaperPickerSections.java
index 16782ce3..a77bf2e0 100644
--- a/src/com/android/wallpaper/module/WallpaperPickerSections.java
+++ b/src/com/android/wallpaper/module/WallpaperPickerSections.java
@@ -12,8 +12,10 @@ import com.android.wallpaper.model.PermissionRequester;
import com.android.wallpaper.model.WallpaperColorsViewModel;
import com.android.wallpaper.model.WallpaperPreviewNavigator;
import com.android.wallpaper.model.WallpaperSectionController;
-import com.android.wallpaper.model.WorkspaceViewModel;
+import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor;
import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewSectionController;
+import com.android.wallpaper.picker.customization.ui.section.WallpaperQuickSwitchSectionController;
+import com.android.wallpaper.picker.customization.ui.viewmodel.WallpaperQuickSwitchViewModel;
import com.android.wallpaper.util.DisplayUtils;
import java.util.ArrayList;
@@ -23,18 +25,19 @@ import java.util.List;
public final class WallpaperPickerSections implements CustomizationSections {
@Override
- public List<CustomizationSectionController<?>> getSectionControllersForScreen(
+ public List<CustomizationSectionController<?>> getRevampedUISectionControllersForScreen(
Screen screen,
FragmentActivity activity,
LifecycleOwner lifecycleOwner,
WallpaperColorsViewModel wallpaperColorsViewModel,
- WorkspaceViewModel workspaceViewModel,
PermissionRequester permissionRequester,
WallpaperPreviewNavigator wallpaperPreviewNavigator,
CustomizationSectionNavigationController sectionNavigationController,
@Nullable Bundle savedInstanceState,
CurrentWallpaperInfoFactory wallpaperInfoFactory,
- DisplayUtils displayUtils) {
+ DisplayUtils displayUtils,
+ WallpaperQuickSwitchViewModel wallpaperQuickSwitchViewModel,
+ WallpaperInteractor wallpaperInteractor) {
List<CustomizationSectionController<?>> sectionControllers = new ArrayList<>();
sectionControllers.add(
@@ -44,7 +47,15 @@ public final class WallpaperPickerSections implements CustomizationSections {
screen,
wallpaperInfoFactory,
wallpaperColorsViewModel,
- displayUtils));
+ displayUtils,
+ sectionNavigationController,
+ wallpaperInteractor));
+ sectionControllers.add(
+ new WallpaperQuickSwitchSectionController(
+ screen,
+ wallpaperQuickSwitchViewModel,
+ lifecycleOwner,
+ sectionNavigationController));
return sectionControllers;
}
@@ -54,7 +65,6 @@ public final class WallpaperPickerSections implements CustomizationSections {
FragmentActivity activity,
LifecycleOwner lifecycleOwner,
WallpaperColorsViewModel wallpaperColorsViewModel,
- WorkspaceViewModel workspaceViewModel,
PermissionRequester permissionRequester,
WallpaperPreviewNavigator wallpaperPreviewNavigator,
CustomizationSectionNavigationController sectionNavigationController,
@@ -62,10 +72,17 @@ public final class WallpaperPickerSections implements CustomizationSections {
DisplayUtils displayUtils) {
List<CustomizationSectionController<?>> sectionControllers = new ArrayList<>();
- sectionControllers.add(new WallpaperSectionController(
- activity, lifecycleOwner, permissionRequester, wallpaperColorsViewModel,
- workspaceViewModel, sectionNavigationController, wallpaperPreviewNavigator,
- savedInstanceState, displayUtils));
+ sectionControllers.add(
+ new WallpaperSectionController(
+ activity,
+ lifecycleOwner,
+ permissionRequester,
+ wallpaperColorsViewModel,
+ null,
+ sectionNavigationController,
+ wallpaperPreviewNavigator,
+ savedInstanceState,
+ displayUtils));
return sectionControllers;
}
diff --git a/src/com/android/wallpaper/module/WallpaperPreferenceKeys.java b/src/com/android/wallpaper/module/WallpaperPreferenceKeys.java
index 4b0e5da2..12929bcc 100755
--- a/src/com/android/wallpaper/module/WallpaperPreferenceKeys.java
+++ b/src/com/android/wallpaper/module/WallpaperPreferenceKeys.java
@@ -73,9 +73,10 @@ public class WallpaperPreferenceKeys {
"num_days_daily_rotation_failed";
String KEY_NUM_DAYS_DAILY_ROTATION_NOT_ATTEMPTED =
"num_days_daily_rotation_not_attempted";
- String KEY_HOME_WALLPAPER_PACKAGE_NAME = "home_wallpaper_package_name";
String KEY_HOME_WALLPAPER_SERVICE_NAME = "home_wallpaper_service_name";
+ String KEY_LOCK_WALLPAPER_SERVICE_NAME = "lock_wallpaper_service_name";
String KEY_PREVIEW_WALLPAPER_COLOR_ID = "preview_wallpaper_color_id";
- String KEY_WALLPAPER_EFFECTS = "wallpaper_effects";
+ String KEY_HOME_WALLPAPER_EFFECTS = "home_wallpaper_effects";
+ String KEY_LOCK_WALLPAPER_EFFECTS = "lock_wallpaper_effects";
}
}
diff --git a/src/com/android/wallpaper/module/WallpaperPreferences.java b/src/com/android/wallpaper/module/WallpaperPreferences.java
index 184972b1..30bcd72b 100755
--- a/src/com/android/wallpaper/module/WallpaperPreferences.java
+++ b/src/com/android/wallpaper/module/WallpaperPreferences.java
@@ -17,6 +17,7 @@ package com.android.wallpaper.module;
import android.annotation.TargetApi;
import android.app.WallpaperColors;
+import android.app.WallpaperManager.SetWallpaperFlags;
import android.graphics.Bitmap;
import android.os.Build;
@@ -140,16 +141,6 @@ public interface WallpaperPreferences {
void setHomeWallpaperHashCode(long hashCode);
/**
- * Gets the home wallpaper's package name, which is present for live wallpapers.
- */
- String getHomeWallpaperPackageName();
-
- /**
- * Sets the home wallpaper's package name, which is present for live wallpapers.
- */
- void setHomeWallpaperPackageName(String packageName);
-
- /**
* Gets the home wallpaper's service name, which is present for live wallpapers.
*/
String getHomeWallpaperServiceName();
@@ -184,6 +175,18 @@ public interface WallpaperPreferences {
void setHomeWallpaperRemoteId(String wallpaperRemoteId);
/**
+ * Gets the home wallpaper's effects.
+ */
+ String getHomeWallpaperEffects();
+
+ /**
+ * Sets the home wallpaper's effects to SharedPreferences.
+ *
+ * @param wallpaperEffects The wallpaper effects.
+ */
+ void setHomeWallpaperEffects(String wallpaperEffects);
+
+ /**
* Returns the lock wallpaper's action URL or null if there is none.
*/
String getLockWallpaperActionUrl();
@@ -262,6 +265,16 @@ public interface WallpaperPreferences {
void setLockWallpaperHashCode(long hashCode);
/**
+ * Gets the lock wallpaper's service name, which is present for live wallpapers.
+ */
+ String getLockWallpaperServiceName();
+
+ /**
+ * Sets the lock wallpaper's service name, which is present for live wallpapers.
+ */
+ void setLockWallpaperServiceName(String serviceName);
+
+ /**
* Gets the lock wallpaper's ID, which is provided by WallpaperManager for static wallpapers.
*/
@TargetApi(Build.VERSION_CODES.N)
@@ -286,6 +299,18 @@ public interface WallpaperPreferences {
void setLockWallpaperRemoteId(String wallpaperRemoteId);
/**
+ * Gets the lock wallpaper's effects.
+ */
+ String getLockWallpaperEffects();
+
+ /**
+ * Sets the lock wallpaper's effects to SharedPreferences.
+ *
+ * @param wallpaperEffects The wallpaper effects.
+ */
+ void setLockWallpaperEffects(String wallpaperEffects);
+
+ /**
* Persists the timestamp of a daily wallpaper rotation that just occurred.
*/
void addDailyRotation(long timestamp);
@@ -505,18 +530,6 @@ public interface WallpaperPreferences {
String wallpaperId);
/**
- * Gets the wallpaper's effects.
- */
- String getWallpaperEffects();
-
- /**
- * Sets the wallpaper's effects to SharedPreferences.
- *
- * @param wallpaperEffects The wallpaper effects.
- */
- void setWallpaperEffects(String wallpaperEffects);
-
- /**
* The possible wallpaper presentation modes, i.e., either "static" or "rotating".
*/
@IntDef({
@@ -545,29 +558,33 @@ public interface WallpaperPreferences {
/**
* Stores the given live wallpaper in the recent wallpapers list
+ * @param which flag indicating the wallpaper destination
* @param wallpaperId unique identifier for this wallpaper
* @param wallpaper {@link LiveWallpaperInfo} for the applied wallpaper
* @param colors WallpaperColors to be used as placeholder for quickswitching
*/
- default void storeLatestHomeWallpaper(String wallpaperId,
+ default void storeLatestWallpaper(@SetWallpaperFlags int which, String wallpaperId,
@NonNull LiveWallpaperInfo wallpaper, WallpaperColors colors) {
// Do nothing in the default case.
}
/**
* Stores the given static wallpaper data in the recent wallpapers list.
+ * @param which flag indicating the wallpaper destination
* @param wallpaperId unique identifier for this wallpaper
* @param wallpaper {@link WallpaperInfo} for the applied wallpaper
* @param croppedWallpaperBitmap wallpaper bitmap exactly as applied to WallaperManager
* @param colors WallpaperColors to be used as placeholder for quickswitching
*/
- default void storeLatestHomeWallpaper(String wallpaperId, @NonNull WallpaperInfo wallpaper,
+ default void storeLatestWallpaper(@SetWallpaperFlags int which, String wallpaperId,
+ @NonNull WallpaperInfo wallpaper,
@NonNull Bitmap croppedWallpaperBitmap, WallpaperColors colors) {
// Do nothing in the default case.
}
/**
* Stores the given static wallpaper data in the recent wallpapers list.
+ * @param which flag indicating the wallpaper destination
* @param wallpaperId unique identifier for this wallpaper
* @param attributions List of attribution items.
* @param actionUrl The action or "explore" URL for the wallpaper.
@@ -575,7 +592,9 @@ public interface WallpaperPreferences {
* @param croppedWallpaperBitmap wallpaper bitmap exactly as applied to WallaperManager
* @param colors {@link WallpaperColors} to be used as placeholder for quickswitching
*/
- default void storeLatestHomeWallpaper(String wallpaperId, List<String> attributions,
+ default void storeLatestWallpaper(
+ @SetWallpaperFlags int which,
+ String wallpaperId, List<String> attributions,
String actionUrl, String collectionId,
@NonNull Bitmap croppedWallpaperBitmap, WallpaperColors colors) {
// Do nothing in the default case.
diff --git a/src/com/android/wallpaper/module/WallpaperSetter.java b/src/com/android/wallpaper/module/WallpaperSetter.java
index 6924a078..b52cfd90 100644
--- a/src/com/android/wallpaper/module/WallpaperSetter.java
+++ b/src/com/android/wallpaper/module/WallpaperSetter.java
@@ -172,10 +172,11 @@ public class WallpaperSetter {
wallpaper, wallpaperAsset, cropRect,
wallpaperScale, destination, new SetWallpaperCallback() {
@Override
- public void onSuccess(WallpaperInfo wallpaperInfo) {
+ public void onSuccess(WallpaperInfo wallpaperInfo,
+ @Destination int destination) {
onWallpaperApplied(wallpaper, containerActivity);
if (callback != null) {
- callback.onSuccess(wallpaper);
+ callback.onSuccess(wallpaper, destination);
}
}
@@ -197,21 +198,25 @@ public class WallpaperSetter {
// wallpaper and restore after setting the wallpaper finishes.
saveAndLockScreenOrientationIfNeeded(activity);
- if (destination == WallpaperPersister.DEST_LOCK_SCREEN) {
+ WallpaperManager wallpaperManager = WallpaperManager.getInstance(activity);
+ if (destination == WallpaperPersister.DEST_LOCK_SCREEN
+ && !wallpaperManager.isLockscreenLiveWallpaperEnabled()) {
throw new IllegalArgumentException(
- "Live wallpaper cannot be applied on lock screen only");
+ "Live wallpaper cannot be applied on lock screen only");
}
- WallpaperManager wallpaperManager = WallpaperManager.getInstance(activity);
+
setWallpaperComponent(wallpaperManager, wallpaper, destination);
wallpaperManager.setWallpaperOffsetSteps(0.5f /* xStep */, 0.0f /* yStep */);
wallpaperManager.setWallpaperOffsets(
activity.getWindow().getDecorView().getRootView().getWindowToken(),
0.5f /* xOffset */, 0.0f /* yOffset */);
- mPreferences.storeLatestHomeWallpaper(wallpaper.getWallpaperId(), wallpaper, colors);
+ mPreferences.storeLatestWallpaper(WallpaperPersister.destinationToFlags(destination),
+ wallpaper.getWallpaperId(), wallpaper, colors);
onWallpaperApplied(wallpaper, activity);
if (callback != null) {
- callback.onSuccess(wallpaper);
+ callback.onSuccess(wallpaper, destination);
}
+ mWallpaperPersister.onLiveWallpaperSet(destination);
} catch (RuntimeException | IOException e) {
onWallpaperApplyError(e, activity);
if (callback != null) {
@@ -233,7 +238,8 @@ public class WallpaperSetter {
wallpaperManager.setWallpaperComponent(
wallpaper.getWallpaperComponent().getComponent());
}
- if (destination == WallpaperPersister.DEST_BOTH) {
+ if (!wallpaperManager.isLockscreenLiveWallpaperEnabled()
+ && destination == WallpaperPersister.DEST_BOTH) {
wallpaperManager.clear(FLAG_LOCK);
}
}
@@ -257,14 +263,16 @@ public class WallpaperSetter {
}
WallpaperManager wallpaperManager = WallpaperManager.getInstance(context);
setWallpaperComponent(wallpaperManager, wallpaper, destination);
- mPreferences.storeLatestHomeWallpaper(wallpaper.getWallpaperId(), wallpaper,
- colors != null ? colors :
+ mPreferences.storeLatestWallpaper(WallpaperPersister.destinationToFlags(destination),
+ wallpaper.getWallpaperId(),
+ wallpaper, colors != null ? colors :
WallpaperColors.fromBitmap(wallpaper.getThumbAsset(context)
.getLowResBitmap(context)));
// Not call onWallpaperApplied() as no UI is presented.
if (callback != null) {
- callback.onSuccess(wallpaper);
+ callback.onSuccess(wallpaper, destination);
}
+ mWallpaperPersister.onLiveWallpaperSet(destination);
} catch (RuntimeException | IOException e) {
// Not call onWallpaperApplyError() as no UI is presented.
if (callback != null) {
@@ -353,6 +361,15 @@ public class WallpaperSetter {
}
};
+ WallpaperManager wallpaperManager = WallpaperManager.getInstance(activity);
+ SetWallpaperDialogFragment setWallpaperDialog = new SetWallpaperDialogFragment();
+ setWallpaperDialog.setTitleResId(titleResId);
+ setWallpaperDialog.setListener(listenerWrapper);
+ if (wallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+ setWallpaperDialog.show(fragmentManager, TAG_SET_WALLPAPER_DIALOG_FRAGMENT);
+ return;
+ }
+
WallpaperStatusChecker wallpaperStatusChecker =
InjectorProvider.getInjector().getWallpaperStatusChecker();
boolean isLiveWallpaperSet =
@@ -361,9 +378,6 @@ public class WallpaperSetter {
boolean isBuiltIn = !isLiveWallpaperSet
&& !wallpaperStatusChecker.isHomeStaticWallpaperSet(activity);
- SetWallpaperDialogFragment setWallpaperDialog = new SetWallpaperDialogFragment();
- setWallpaperDialog.setTitleResId(titleResId);
- setWallpaperDialog.setListener(listenerWrapper);
if ((isLiveWallpaperSet || isBuiltIn)
&& !wallpaperStatusChecker.isLockWallpaperSet(activity)) {
if (isLiveWallpaper) {
diff --git a/src/com/android/wallpaper/picker/CategorySelectorFragment.java b/src/com/android/wallpaper/picker/CategorySelectorFragment.java
index 2b6c71dc..fe36592e 100644
--- a/src/com/android/wallpaper/picker/CategorySelectorFragment.java
+++ b/src/com/android/wallpaper/picker/CategorySelectorFragment.java
@@ -47,6 +47,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.wallpaper.R;
import com.android.wallpaper.asset.Asset;
+import com.android.wallpaper.effects.EffectsController;
import com.android.wallpaper.model.Category;
import com.android.wallpaper.model.CategoryProvider;
import com.android.wallpaper.model.LiveWallpaperInfo;
@@ -299,6 +300,11 @@ public class CategorySelectorFragment extends AppbarFragment {
eventLogger.logCategorySelected(mCategory.getCollectionId());
if (mCategory.supportsCustomPhotos()) {
+ EffectsController effectsController =
+ InjectorProvider.getInjector().getEffectsController(getContext());
+ if (!effectsController.isEffectTriggered()) {
+ effectsController.triggerEffect(getContext());
+ }
getCategorySelectorFragmentHost().requestCustomPhotoPicker(
new MyPhotosStarter.PermissionChangedListener() {
@Override
diff --git a/src/com/android/wallpaper/picker/CustomizationPickerActivity.java b/src/com/android/wallpaper/picker/CustomizationPickerActivity.java
index 2351c72e..3f8c2d03 100644
--- a/src/com/android/wallpaper/picker/CustomizationPickerActivity.java
+++ b/src/com/android/wallpaper/picker/CustomizationPickerActivity.java
@@ -52,7 +52,6 @@ import com.android.wallpaper.picker.AppbarFragment.AppbarFragmentHost;
import com.android.wallpaper.picker.CategorySelectorFragment.CategorySelectorFragmentHost;
import com.android.wallpaper.picker.MyPhotosStarter.PermissionChangedListener;
import com.android.wallpaper.picker.individual.IndividualPickerFragment.IndividualPickerFragmentHost;
-import com.android.wallpaper.picker.undo.domain.interactor.UndoInteractor;
import com.android.wallpaper.util.ActivityUtils;
import com.android.wallpaper.util.DeepLinkUtils;
import com.android.wallpaper.util.LaunchUtils;
@@ -79,7 +78,7 @@ public class CustomizationPickerActivity extends FragmentActivity implements App
private BottomActionBar mBottomActionBar;
private boolean mIsSafeToCommitFragmentTransaction;
- @Nullable private UndoInteractor mUndoInteractor;
+ private boolean mIsUseRevampedUi;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -109,7 +108,9 @@ public class CustomizationPickerActivity extends FragmentActivity implements App
// See go/pdr-edge-to-edge-guide.
WindowCompat.setDecorFitsSystemWindows(getWindow(), isSUWMode(this));
- final boolean isUseRevampedUi = injector.getFlags().isUseRevampedUi(this);
+ mIsUseRevampedUi = injector.getFlags().isUseRevampedUiEnabled(this);
+ final boolean startFromLockScreen = getIntent() == null
+ || !ActivityUtils.isLaunchedFromLauncher(getIntent());
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment == null) {
@@ -122,17 +123,30 @@ public class CustomizationPickerActivity extends FragmentActivity implements App
// Switch to the target fragment.
switchFragment(isWallpaperOnlyMode(getIntent())
- ? new WallpaperOnlyFragment()
- : CustomizationPickerFragment.newInstance(isUseRevampedUi));
+ ? WallpaperOnlyFragment.newInstance(mIsUseRevampedUi)
+ : CustomizationPickerFragment.newInstance(
+ mIsUseRevampedUi, startFromLockScreen));
+
+ // Cache the categories, but only if we're not restoring state (b/276767415).
+ mDelegate.prefetchCategories();
}
- if (isUseRevampedUi) {
- mUndoInteractor = injector.getUndoInteractor(this);
- mUndoInteractor.startSession();
+ if (savedInstanceState == null) {
+ // We only want to start a new undo session if this activity is brand-new. A non-new
+ // activity will have a non-null savedInstanceState.
+ if (mIsUseRevampedUi) {
+ injector.getUndoInteractor(this).startSession();
+ }
}
final Intent intent = getIntent();
final String navigationDestination = intent.getStringExtra(EXTRA_DESTINATION);
+ // Consume the destination and commit the intent back so the OS doesn't revert to the same
+ // destination when we change color or wallpaper (which causes the activity to be
+ // recreated).
+ intent.removeExtra(EXTRA_DESTINATION);
+ setIntent(intent);
+
final String deepLinkCollectionId = DeepLinkUtils.getCollectionId(intent);
if (!TextUtils.isEmpty(navigationDestination)) {
@@ -147,10 +161,9 @@ public class CustomizationPickerActivity extends FragmentActivity implements App
// Wallpaper Collection deep link case
switchFragmentWithBackStack(new CategorySelectorFragment());
switchFragmentWithBackStack(InjectorProvider.getInjector().getIndividualPickerFragment(
- deepLinkCollectionId));
+ this, deepLinkCollectionId));
intent.setData(null);
}
- mDelegate.prefetchCategories();
}
@Override
@@ -254,7 +267,7 @@ public class CustomizationPickerActivity extends FragmentActivity implements App
return;
}
switchFragmentWithBackStack(InjectorProvider.getInjector().getIndividualPickerFragment(
- category.getCollectionId()));
+ this, category.getCollectionId()));
}
@Override
@@ -331,6 +344,15 @@ public class CustomizationPickerActivity extends FragmentActivity implements App
if (mDelegate.handleActivityResult(requestCode, resultCode, data)) {
if (isSUWMode(this)) {
finishActivityForSUW();
+ } else if (mIsUseRevampedUi) {
+ // We don't finish in the revamped UI to let the user have a chance to reset the
+ // change they made, should they want to. We do, however, remove all the fragments
+ // from our back stack to reveal the root fragment, revealing the main screen of the
+ // app.
+ final FragmentManager fragmentManager = getSupportFragmentManager();
+ while (fragmentManager.getBackStackEntryCount() > 0) {
+ fragmentManager.popBackStackImmediate();
+ }
} else {
finishActivityWithResultOk();
}
diff --git a/src/com/android/wallpaper/picker/CustomizationPickerFragment.java b/src/com/android/wallpaper/picker/CustomizationPickerFragment.java
index 380fe13c..cb0820d0 100644
--- a/src/com/android/wallpaper/picker/CustomizationPickerFragment.java
+++ b/src/com/android/wallpaper/picker/CustomizationPickerFragment.java
@@ -33,34 +33,40 @@ import com.android.wallpaper.R;
import com.android.wallpaper.model.CustomizationSectionController;
import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController;
import com.android.wallpaper.model.PermissionRequester;
-import com.android.wallpaper.model.WallpaperColorsViewModel;
import com.android.wallpaper.model.WallpaperPreviewNavigator;
-import com.android.wallpaper.model.WorkspaceViewModel;
import com.android.wallpaper.module.CustomizationSections;
import com.android.wallpaper.module.FragmentFactory;
import com.android.wallpaper.module.Injector;
import com.android.wallpaper.module.InjectorProvider;
import com.android.wallpaper.picker.customization.ui.binder.CustomizationPickerBinder;
import com.android.wallpaper.picker.customization.ui.viewmodel.CustomizationPickerViewModel;
+import com.android.wallpaper.picker.customization.ui.viewmodel.WallpaperQuickSwitchViewModel;
import com.android.wallpaper.util.ActivityUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
+import kotlinx.coroutines.DisposableHandle;
+
/** The Fragment UI for customization sections. */
public class CustomizationPickerFragment extends AppbarFragment implements
CustomizationSectionNavigationController {
private static final String TAG = "CustomizationPickerFragment";
private static final String SCROLL_POSITION_Y = "SCROLL_POSITION_Y";
- private static final String KEY_IS_USE_REVAMPED_UI = "is_use_revamped_ui";
+ protected static final String KEY_IS_USE_REVAMPED_UI = "is_use_revamped_ui";
+ private static final String KEY_START_FROM_LOCK_SCREEN = "start_from_lock_screen";
+ private DisposableHandle mBinding;
/** Returns a new instance of {@link CustomizationPickerFragment}. */
- public static CustomizationPickerFragment newInstance(boolean isUseRevampedUi) {
+ public static CustomizationPickerFragment newInstance(
+ boolean isUseRevampedUi,
+ boolean startFromLockScreen) {
final CustomizationPickerFragment fragment = new CustomizationPickerFragment();
final Bundle args = new Bundle();
args.putBoolean(KEY_IS_USE_REVAMPED_UI, isUseRevampedUi);
+ args.putBoolean(KEY_START_FROM_LOCK_SCREEN, startFromLockScreen);
fragment.setArguments(args);
return fragment;
}
@@ -68,9 +74,11 @@ public class CustomizationPickerFragment extends AppbarFragment implements
// Note that the section views will be displayed by the list ordering.
private final List<CustomizationSectionController<?>> mSectionControllers = new ArrayList<>();
private NestedScrollView mNestedScrollView;
- @Nullable private Bundle mBackStackSavedInstanceState;
+ @Nullable
+ private Bundle mBackStackSavedInstanceState;
private final FragmentFactory mFragmentFactory;
- @Nullable private CustomizationPickerViewModel mViewModel;
+ @Nullable
+ private CustomizationPickerViewModel mViewModel;
public CustomizationPickerFragment() {
mFragmentFactory = InjectorProvider.getInjector().getFragmentFactory();
@@ -79,9 +87,11 @@ public class CustomizationPickerFragment extends AppbarFragment implements
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@Nullable Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.collapsing_toolbar_container_layout,
- container, /* attachToRoot= */ false);
-
+ final boolean shouldUseRevampedUi = shouldUseRevampedUi();
+ final int layoutId = shouldUseRevampedUi
+ ? R.layout.toolbar_container_layout
+ : R.layout.collapsing_toolbar_container_layout;
+ final View view = inflater.inflate(layoutId, container, false);
if (ActivityUtils.isLaunchedFromSettingsRelated(getActivity().getIntent())) {
setUpToolbar(view, !ActivityEmbeddingUtils.shouldHideNavigateUpButton(
getActivity(), /* isSecondLayerPage= */ true));
@@ -90,16 +100,7 @@ public class CustomizationPickerFragment extends AppbarFragment implements
}
final Injector injector = InjectorProvider.getInjector();
- final Bundle args = getArguments();
- final boolean isUseRevampedUi;
- if (args != null && args.containsKey(KEY_IS_USE_REVAMPED_UI)) {
- isUseRevampedUi = args.getBoolean(KEY_IS_USE_REVAMPED_UI);
- } else {
- throw new IllegalStateException(
- "Must contain KEY_IS_USE_REVAMPED_UI argument, did you instantiate directly"
- + " instead of using the newInstance function?");
- }
- if (isUseRevampedUi) {
+ if (shouldUseRevampedUi) {
setContentView(view, R.layout.fragment_tabbed_customization_picker);
mViewModel = new ViewModelProvider(
this,
@@ -108,19 +109,26 @@ public class CustomizationPickerFragment extends AppbarFragment implements
savedInstanceState,
injector.getUndoInteractor(requireContext()))
).get(CustomizationPickerViewModel.class);
+ final Bundle arguments = getArguments();
+ mViewModel.setInitialScreen(
+ arguments != null && arguments.getBoolean(KEY_START_FROM_LOCK_SCREEN));
setUpToolbarMenu(R.menu.undoable_customization_menu);
final Bundle finalSavedInstanceState = savedInstanceState;
- CustomizationPickerBinder.bind(
+ if (mBinding != null) {
+ mBinding.dispose();
+ }
+ mBinding = CustomizationPickerBinder.bind(
view,
getToolbarId(),
mViewModel,
this,
- isOnLockScreen -> getSectionControllers(
- isOnLockScreen
- ? CustomizationSections.Screen.LOCK_SCREEN
- : CustomizationSections.Screen.HOME_SCREEN,
- finalSavedInstanceState));
+ isOnLockScreen -> filterAvailableSections(
+ getSectionControllers(
+ isOnLockScreen
+ ? CustomizationSections.Screen.LOCK_SCREEN
+ : CustomizationSections.Screen.HOME_SCREEN,
+ finalSavedInstanceState)));
} else {
setContentView(view, R.layout.fragment_customization_picker);
}
@@ -132,7 +140,7 @@ public class CustomizationPickerFragment extends AppbarFragment implements
mNestedScrollView = view.findViewById(R.id.scroll_container);
- if (!isUseRevampedUi) {
+ if (!shouldUseRevampedUi) {
ViewGroup sectionContainer = view.findViewById(R.id.section_container);
sectionContainer.setOnApplyWindowInsetsListener((v, windowInsets) -> {
v.setPadding(
@@ -187,12 +195,12 @@ public class CustomizationPickerFragment extends AppbarFragment implements
@Override
protected int getToolbarId() {
- return R.id.action_bar;
+ return shouldUseRevampedUi() ? R.id.toolbar : R.id.action_bar;
}
@Override
protected int getToolbarColorId() {
- return android.R.color.transparent;
+ return shouldUseRevampedUi() ? R.color.toolbar_color : android.R.color.transparent;
}
@Override
@@ -257,14 +265,10 @@ public class CustomizationPickerFragment extends AppbarFragment implements
mSectionControllers.clear();
mSectionControllers.addAll(
- getAvailableSections(getAvailableSectionControllers(savedInstanceState)));
- }
-
- private List<CustomizationSectionController<?>> getAvailableSectionControllers(
- @Nullable Bundle savedInstanceState) {
- return getSectionControllers(
- null,
- savedInstanceState);
+ filterAvailableSections(
+ getSectionControllers(
+ null,
+ savedInstanceState)));
}
private List<CustomizationSectionController<?>> getSectionControllers(
@@ -272,50 +276,55 @@ public class CustomizationPickerFragment extends AppbarFragment implements
@Nullable Bundle savedInstanceState) {
final Injector injector = InjectorProvider.getInjector();
- WallpaperColorsViewModel wcViewModel = new ViewModelProvider(getActivity())
- .get(WallpaperColorsViewModel.class);
- WorkspaceViewModel workspaceViewModel = new ViewModelProvider(getActivity())
- .get(WorkspaceViewModel.class);
+ WallpaperQuickSwitchViewModel wallpaperQuickSwitchViewModel = new ViewModelProvider(
+ getActivity(),
+ WallpaperQuickSwitchViewModel.newFactory(
+ this,
+ savedInstanceState,
+ injector.getWallpaperInteractor(requireContext())))
+ .get(WallpaperQuickSwitchViewModel.class);
CustomizationSections sections = injector.getCustomizationSections(getActivity());
if (screen == null) {
return sections.getAllSectionControllers(
getActivity(),
getViewLifecycleOwner(),
- wcViewModel,
- workspaceViewModel,
+ injector.getWallpaperColorsViewModel(),
getPermissionRequester(),
getWallpaperPreviewNavigator(),
this,
savedInstanceState,
injector.getDisplayUtils(getActivity()));
} else {
- return sections.getSectionControllersForScreen(
+ return sections.getRevampedUISectionControllersForScreen(
screen,
getActivity(),
getViewLifecycleOwner(),
- wcViewModel,
- workspaceViewModel,
+ injector.getWallpaperColorsViewModel(),
getPermissionRequester(),
getWallpaperPreviewNavigator(),
this,
savedInstanceState,
injector.getCurrentWallpaperInfoFactory(requireContext()),
- injector.getDisplayUtils(getActivity()));
+ injector.getDisplayUtils(getActivity()),
+ wallpaperQuickSwitchViewModel,
+ injector.getWallpaperInteractor(requireContext()));
}
}
- protected List<CustomizationSectionController<?>> getAvailableSections(
+ /** Returns a filtered list containing only the available section controllers. */
+ protected List<CustomizationSectionController<?>> filterAvailableSections(
List<CustomizationSectionController<?>> controllers) {
return controllers.stream()
.filter(controller -> {
- if(controller.isAvailable(getContext())) {
+ if (controller.isAvailable(getContext())) {
return true;
} else {
controller.release();
Log.d(TAG, "Section is not available: " + controller);
return false;
- }})
+ }
+ })
.collect(Collectors.toList());
}
@@ -326,4 +335,15 @@ public class CustomizationPickerFragment extends AppbarFragment implements
private WallpaperPreviewNavigator getWallpaperPreviewNavigator() {
return (WallpaperPreviewNavigator) getActivity();
}
+
+ private boolean shouldUseRevampedUi() {
+ final Bundle args = getArguments();
+ if (args != null && args.containsKey(KEY_IS_USE_REVAMPED_UI)) {
+ return args.getBoolean(KEY_IS_USE_REVAMPED_UI);
+ } else {
+ throw new IllegalStateException(
+ "Must contain KEY_IS_USE_REVAMPED_UI argument, did you instantiate directly"
+ + " instead of using the newInstance function?");
+ }
+ }
}
diff --git a/src/com/android/wallpaper/picker/DisplayAspectRatioLinearLayout.kt b/src/com/android/wallpaper/picker/DisplayAspectRatioLinearLayout.kt
new file mode 100644
index 00000000..f970a9b9
--- /dev/null
+++ b/src/com/android/wallpaper/picker/DisplayAspectRatioLinearLayout.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import androidx.core.view.children
+import androidx.core.view.updateLayoutParams
+import com.android.wallpaper.util.ScreenSizeCalculator
+
+/**
+ * [LinearLayout] that sizes its children using a fixed aspect ratio that is the same as that of the
+ * display, and can lay out multiple children horizontally with margin
+ */
+class DisplayAspectRatioLinearLayout(
+ context: Context,
+ attrs: AttributeSet?,
+) : LinearLayout(context, attrs) {
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+ val screenAspectRatio = ScreenSizeCalculator.getInstance().getScreenAspectRatio(context)
+ val parentWidth = this.measuredWidth
+ val parentHeight = this.measuredHeight
+ val itemSpacingPx = ITEM_SPACING_DP.toPx(context.resources.displayMetrics.density)
+ val (childWidth, childHeight) =
+ if (orientation == HORIZONTAL) {
+ val availableWidth =
+ parentWidth - paddingStart - paddingEnd - (childCount - 1) * itemSpacingPx
+ val availableHeight = parentHeight - paddingTop - paddingBottom
+ var width = availableWidth / childCount
+ var height = (width * screenAspectRatio).toInt()
+ if (height > availableHeight) {
+ height = availableHeight
+ width = (height / screenAspectRatio).toInt()
+ }
+ width to height
+ } else {
+ val availableWidth = parentWidth - paddingStart - paddingEnd
+ val availableHeight =
+ parentHeight - paddingTop - paddingBottom - (childCount - 1) * itemSpacingPx
+ var height = availableHeight / childCount
+ var width = (height / screenAspectRatio).toInt()
+ if (width > availableWidth) {
+ width = availableWidth
+ height = (width * screenAspectRatio).toInt()
+ }
+ width to height
+ }
+
+ val itemSpacingHalfPx = ITEM_SPACING_DP_HALF.toPx(context.resources.displayMetrics.density)
+ children.forEachIndexed { index, child ->
+ val addSpacingToStart = index > 0
+ val addSpacingToEnd = index < (childCount - 1)
+ if (orientation == HORIZONTAL) {
+ child.updateLayoutParams<MarginLayoutParams> {
+ if (addSpacingToStart) this.marginStart = itemSpacingHalfPx
+ if (addSpacingToEnd) this.marginEnd = itemSpacingHalfPx
+ }
+ } else {
+ child.updateLayoutParams<MarginLayoutParams> {
+ if (addSpacingToStart) this.topMargin = itemSpacingHalfPx
+ if (addSpacingToEnd) this.bottomMargin = itemSpacingHalfPx
+ }
+ }
+
+ child.measure(
+ MeasureSpec.makeMeasureSpec(
+ childWidth,
+ MeasureSpec.EXACTLY,
+ ),
+ MeasureSpec.makeMeasureSpec(
+ childHeight,
+ MeasureSpec.EXACTLY,
+ ),
+ )
+ }
+ }
+
+ private fun Int.toPx(density: Float): Int {
+ return (this * density).toInt()
+ }
+
+ companion object {
+ private const val ITEM_SPACING_DP = 12
+ private const val ITEM_SPACING_DP_HALF = ITEM_SPACING_DP / 2
+ }
+}
diff --git a/src/com/android/wallpaper/picker/FullPreviewActivity.java b/src/com/android/wallpaper/picker/FullPreviewActivity.java
index ce780644..5a22b62c 100755
--- a/src/com/android/wallpaper/picker/FullPreviewActivity.java
+++ b/src/com/android/wallpaper/picker/FullPreviewActivity.java
@@ -17,20 +17,21 @@ package com.android.wallpaper.picker;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.transition.Slide;
-import android.view.View;
import android.view.Window;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.android.wallpaper.R;
-import com.android.wallpaper.model.InlinePreviewIntentFactory;
+import com.android.wallpaper.config.BaseFlags;
import com.android.wallpaper.model.WallpaperInfo;
import com.android.wallpaper.module.InjectorProvider;
import com.android.wallpaper.picker.AppbarFragment.AppbarFragmentHost;
import com.android.wallpaper.util.ActivityUtils;
+import com.android.wallpaper.util.DisplayUtils;
/**
* Activity that displays a full preview of a specific wallpaper and provides the ability to set the
@@ -43,7 +44,7 @@ public class FullPreviewActivity extends BasePreviewActivity implements AppbarFr
*/
public static Intent newIntent(Context packageContext, WallpaperInfo wallpaperInfo) {
Intent intent = new Intent(packageContext, FullPreviewActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra(EXTRA_WALLPAPER_INFO, wallpaperInfo);
return intent;
}
@@ -74,7 +75,9 @@ public class FullPreviewActivity extends BasePreviewActivity implements AppbarFr
if (fragment == null) {
Intent intent = getIntent();
WallpaperInfo wallpaper = intent.getParcelableExtra(EXTRA_WALLPAPER_INFO);
- boolean viewAsHome = intent.getBooleanExtra(EXTRA_VIEW_AS_HOME, true);
+ BaseFlags flags = InjectorProvider.getInjector().getFlags();
+ boolean viewAsHome = intent.getBooleanExtra(EXTRA_VIEW_AS_HOME, !flags
+ .isFullscreenWallpaperPreviewEnabled(this));
boolean testingModeEnabled = intent.getBooleanExtra(EXTRA_TESTING_MODE_ENABLED, false);
fragment = InjectorProvider.getInjector().getPreviewFragment(
/* context= */ this,
@@ -99,26 +102,15 @@ public class FullPreviewActivity extends BasePreviewActivity implements AppbarFr
return !ActivityUtils.isSUWMode(getBaseContext());
}
- /**
- * Implementation that provides an intent to start a PreviewActivity.
- */
- public static class PreviewActivityIntentFactory implements InlinePreviewIntentFactory {
- @Override
- public Intent newIntent(Context context, WallpaperInfo wallpaper) {
- return FullPreviewActivity.newIntent(context, wallpaper);
- }
- }
-
@Override
protected void onResume() {
super.onResume();
+ DisplayUtils displayUtils = InjectorProvider.getInjector().getDisplayUtils(this);
+ int orientation = displayUtils.isOnWallpaperDisplay(this)
+ ? ActivityInfo.SCREEN_ORIENTATION_USER : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ setRequestedOrientation(orientation);
if (isInMultiWindowMode()) {
onBackPressed();
}
- // Hide the navigation bar
- View decorView = getWindow().getDecorView();
- int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_FULLSCREEN;
- decorView.setSystemUiVisibility(uiOptions);
}
}
diff --git a/src/com/android/wallpaper/picker/ImagePreviewFragment.java b/src/com/android/wallpaper/picker/ImagePreviewFragment.java
index 6d75c87b..ba477b7d 100755
--- a/src/com/android/wallpaper/picker/ImagePreviewFragment.java
+++ b/src/com/android/wallpaper/picker/ImagePreviewFragment.java
@@ -424,7 +424,7 @@ public class ImagePreviewFragment extends PreviewFragment {
BitmapCropper bitmapCropper = mInjector.getBitmapCropper();
bitmapCropper.cropAndScaleBitmap(mWallpaperAsset, mFullResImageView.getScale(),
- calculateCropRect(context), /* adjustForRtl= */ false,
+ calculateCropRect(context, /* cropExtraWidth= */ true), /* adjustForRtl= */ false,
new BitmapCropper.Callback() {
@Override
public void onBitmapCropped(Bitmap croppedBitmap) {
@@ -546,7 +546,7 @@ public class ImagePreviewFragment extends PreviewFragment {
mFullResImageView.setScaleAndCenter(minWallpaperZoom, centerPosition);
}
- private Rect calculateCropRect(Context context) {
+ private Rect calculateCropRect(Context context, boolean cropExtraWidth) {
float wallpaperZoom = mFullResImageView.getScale();
Context appContext = context.getApplicationContext();
@@ -563,12 +563,13 @@ public class ImagePreviewFragment extends PreviewFragment {
Point cropSurfaceSize = WallpaperCropUtils.calculateCropSurfaceSize(res, maxCrop, minCrop,
cropWidth, cropHeight);
return WallpaperCropUtils.calculateCropRect(appContext, hostViewSize,
- cropSurfaceSize, mRawWallpaperSize, visibleFileRect, wallpaperZoom);
+ cropSurfaceSize, mRawWallpaperSize, visibleFileRect, wallpaperZoom, cropExtraWidth);
}
@Override
protected void setCurrentWallpaper(@Destination int destination) {
- Rect cropRect = calculateCropRect(getContext());
+ // Only crop extra wallpaper width for single display devices.
+ Rect cropRect = calculateCropRect(getContext(), !mDisplayUtils.hasMultiInternalDisplays());
float screenScale = WallpaperCropUtils.getScaleOfScreenResolution(
mFullResImageView.getScale(), cropRect, mWallpaperScreenSize.x,
mWallpaperScreenSize.y);
diff --git a/src/com/android/wallpaper/picker/PreviewActivity.java b/src/com/android/wallpaper/picker/PreviewActivity.java
index ba39ba4e..e7646250 100755
--- a/src/com/android/wallpaper/picker/PreviewActivity.java
+++ b/src/com/android/wallpaper/picker/PreviewActivity.java
@@ -25,10 +25,12 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.android.wallpaper.R;
+import com.android.wallpaper.config.BaseFlags;
import com.android.wallpaper.model.ImageWallpaperInfo;
import com.android.wallpaper.model.InlinePreviewIntentFactory;
import com.android.wallpaper.model.WallpaperInfo;
import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.module.LargeScreenMultiPanesChecker;
import com.android.wallpaper.picker.AppbarFragment.AppbarFragmentHost;
import com.android.wallpaper.util.ActivityUtils;
@@ -61,7 +63,9 @@ public class PreviewActivity extends BasePreviewActivity implements AppbarFragme
if (fragment == null) {
Intent intent = getIntent();
WallpaperInfo wallpaper = intent.getParcelableExtra(EXTRA_WALLPAPER_INFO);
- boolean viewAsHome = intent.getBooleanExtra(EXTRA_VIEW_AS_HOME, true);
+ BaseFlags flags = InjectorProvider.getInjector().getFlags();
+ boolean viewAsHome = intent.getBooleanExtra(EXTRA_VIEW_AS_HOME, !flags
+ .isFullscreenWallpaperPreviewEnabled(this));
boolean testingModeEnabled = intent.getBooleanExtra(EXTRA_TESTING_MODE_ENABLED, false);
fragment = InjectorProvider.getInjector().getPreviewFragment(
/* context */ this,
@@ -115,6 +119,14 @@ public class PreviewActivity extends BasePreviewActivity implements AppbarFragme
public static class PreviewActivityIntentFactory implements InlinePreviewIntentFactory {
@Override
public Intent newIntent(Context context, WallpaperInfo wallpaper) {
+ LargeScreenMultiPanesChecker multiPanesChecker = new LargeScreenMultiPanesChecker();
+ // Launch a full preview activity for devices supporting multipanel mode
+ if (multiPanesChecker.isMultiPanesEnabled(context)
+ && InjectorProvider.getInjector().getFlags()
+ .isFullscreenWallpaperPreviewEnabled(context)) {
+ return FullPreviewActivity.newIntent(context, wallpaper);
+ }
+
return PreviewActivity.newIntent(context, wallpaper);
}
}
diff --git a/src/com/android/wallpaper/picker/PreviewFragment.java b/src/com/android/wallpaper/picker/PreviewFragment.java
index e9e78c8a..6671b208 100755
--- a/src/com/android/wallpaper/picker/PreviewFragment.java
+++ b/src/com/android/wallpaper/picker/PreviewFragment.java
@@ -203,7 +203,7 @@ public abstract class PreviewFragment extends AppbarFragment implements
mFullScreenAnimation.getStatusBarHeight(),
previewHeader.getPaddingRight(), previewHeader.getPaddingBottom());
- return windowInsets.consumeSystemWindowInsets();
+ return windowInsets.CONSUMED;
}
);
diff --git a/src/com/android/wallpaper/picker/StandalonePreviewActivity.java b/src/com/android/wallpaper/picker/StandalonePreviewActivity.java
index f4938059..3b65306c 100755
--- a/src/com/android/wallpaper/picker/StandalonePreviewActivity.java
+++ b/src/com/android/wallpaper/picker/StandalonePreviewActivity.java
@@ -19,12 +19,13 @@ import static com.android.wallpaper.util.ActivityUtils.startActivityForResultSaf
import android.Manifest.permission;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.util.Log;
-import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
@@ -62,7 +63,8 @@ public class StandalonePreviewActivity extends BasePreviewActivity implements Ap
enableFullScreen();
- mUserEventLogger = InjectorProvider.getInjector().getUserEventLogger(getApplicationContext());
+ mUserEventLogger = InjectorProvider.getInjector().getUserEventLogger(
+ getApplicationContext());
mUserEventLogger.logStandalonePreviewLaunched();
Intent cropAndSetWallpaperIntent = getIntent();
@@ -80,10 +82,11 @@ public class StandalonePreviewActivity extends BasePreviewActivity implements Ap
mUserEventLogger.logStandalonePreviewImageUriHasReadPermission(
isReadPermissionGrantedForImageUri);
- // Request storage permission if necessary (i.e., on Android M and later if storage permission
- // has not already been granted) and delay loading the PreviewFragment until the permission is
- // granted.
- if (!isReadPermissionGrantedForImageUri && !isReadExternalStoragePermissionGrantedForApp()) {
+ // Request storage permission if necessary (i.e., on Android M and later if storage
+ // permission has not already been granted) and delay loading the PreviewFragment until the
+ // permission is granted.
+ if (!isReadPermissionGrantedForImageUri
+ && !isReadExternalStoragePermissionGrantedForApp()) {
requestPermissions(
new String[]{permission.READ_MEDIA_IMAGES},
READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
@@ -103,8 +106,20 @@ public class StandalonePreviewActivity extends BasePreviewActivity implements Ap
}
@Override
+ protected void onResume() {
+ super.onResume();
+ Resources res = getResources();
+ boolean isDeviceFoldableOrTablet = res.getBoolean(R.bool.is_large_screen);
+
+ if (!isDeviceFoldableOrTablet) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+ }
+
+ @SuppressWarnings("MissingSuperCall") // TODO: Fix me
+ @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
- @NonNull int[] grantResults) {
+ @NonNull int[] grantResults) {
// Load the preview fragment if the storage permission was granted.
if (requestCode == READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
boolean isGranted = permissions.length > 0
@@ -137,14 +152,6 @@ public class StandalonePreviewActivity extends BasePreviewActivity implements Ap
return getIntent().getBooleanExtra(KEY_UP_ARROW, false);
}
- @Override
- protected void enableFullScreen() {
- super.enableFullScreen();
- getWindow().setFlags(
- WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
- WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
- }
-
/**
* Launches multi-pane when it is enabled, for non-Settings' trampoline launch case will
* retrieve EXTRA_STREAM's image URI and assign back its intent by calling setData().
@@ -155,22 +162,10 @@ public class StandalonePreviewActivity extends BasePreviewActivity implements Ap
Intent intent = getIntent();
if (!ActivityUtils.isLaunchedFromSettingsTrampoline(intent)
&& !ActivityUtils.isLaunchedFromSettingsRelated(intent)) {
- Uri uri = intent.getData();
- if (uri != null) {
- // Grant URI permission for next launching activity.
- grantUriPermission(getPackageName(), uri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ if (!InjectorProvider.getInjector().getFlags().isFullscreenWallpaperPreviewEnabled(
+ this)) {
+ launchMultiPanes(checker);
}
-
- Intent previewLaunch = checker.getMultiPanesIntent(intent);
- previewLaunch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- // Put image URI and back arrow condition to separate extras.
- .putExtra(Intent.EXTRA_STREAM, intent.getData())
- .putExtra(KEY_UP_ARROW, true);
-
- startActivityForResultSafely(/* activity= */ this, previewLaunch, /* requestCode= */
- 0);
- finish();
} else {
Uri uri = intent.hasExtra(Intent.EXTRA_STREAM) ? intent.getParcelableExtra(
Intent.EXTRA_STREAM) : null;
@@ -181,6 +176,26 @@ public class StandalonePreviewActivity extends BasePreviewActivity implements Ap
}
}
+ private void launchMultiPanes(MultiPanesChecker checker) {
+ Intent intent = getIntent();
+ Uri uri = intent.getData();
+ if (uri != null) {
+ // Grant URI permission for next launching activity.
+ grantUriPermission(getPackageName(), uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+
+ Intent previewLaunch = checker.getMultiPanesIntent(intent);
+ previewLaunch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ // Put image URI and back arrow condition to separate extras.
+ .putExtra(Intent.EXTRA_STREAM, intent.getData())
+ .putExtra(KEY_UP_ARROW, true);
+
+ startActivityForResultSafely(/* activity= */ this, previewLaunch, /* requestCode= */
+ 0);
+ finish();
+ }
+
/**
* Creates a new instance of {@link PreviewFragment} and loads the fragment into this activity's
* fragment container so that it's shown to the user.
diff --git a/src/com/android/wallpaper/picker/TouchForwardingLayout.java b/src/com/android/wallpaper/picker/TouchForwardingLayout.java
index 2cb692de..ced59689 100644
--- a/src/com/android/wallpaper/picker/TouchForwardingLayout.java
+++ b/src/com/android/wallpaper/picker/TouchForwardingLayout.java
@@ -17,6 +17,7 @@ package com.android.wallpaper.picker;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
@@ -26,13 +27,22 @@ public class TouchForwardingLayout extends FrameLayout {
private View mView;
private boolean mForwardingEnabled;
+ private GestureDetector mGestureDetector;
public TouchForwardingLayout(Context context, AttributeSet attrs) {
super(context, attrs);
+ mGestureDetector = new GestureDetector(context,
+ new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ return performClick();
+ }
+ });
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
+ mGestureDetector.onTouchEvent(ev);
if (mView != null && mForwardingEnabled) {
mView.dispatchTouchEvent(ev);
}
diff --git a/src/com/android/wallpaper/picker/ViewOnlyPreviewActivity.java b/src/com/android/wallpaper/picker/ViewOnlyPreviewActivity.java
index 4ff3c2ea..7377103f 100755
--- a/src/com/android/wallpaper/picker/ViewOnlyPreviewActivity.java
+++ b/src/com/android/wallpaper/picker/ViewOnlyPreviewActivity.java
@@ -26,6 +26,7 @@ import com.android.wallpaper.R;
import com.android.wallpaper.model.InlinePreviewIntentFactory;
import com.android.wallpaper.model.WallpaperInfo;
import com.android.wallpaper.module.InjectorProvider;
+import com.android.wallpaper.module.LargeScreenMultiPanesChecker;
import com.android.wallpaper.picker.AppbarFragment.AppbarFragmentHost;
import com.android.wallpaper.util.ActivityUtils;
@@ -94,6 +95,14 @@ public class ViewOnlyPreviewActivity extends BasePreviewActivity implements Appb
@Override
public Intent newIntent(Context context, WallpaperInfo wallpaper) {
+ LargeScreenMultiPanesChecker multiPanesChecker = new LargeScreenMultiPanesChecker();
+ // Launch a full preview activity for devices supporting multipanel mode
+ if (multiPanesChecker.isMultiPanesEnabled(context)
+ && InjectorProvider.getInjector().getFlags()
+ .isFullscreenWallpaperPreviewEnabled(context)) {
+ return FullPreviewActivity.newIntent(context, wallpaper, mIsViewAsHome);
+ }
+
if (mIsHomeAndLockPreviews) {
return ViewOnlyPreviewActivity.newIntent(context, wallpaper, mIsViewAsHome);
}
diff --git a/src/com/android/wallpaper/picker/WallpaperOnlyFragment.java b/src/com/android/wallpaper/picker/WallpaperOnlyFragment.java
index 7673df2f..6d99bed9 100644
--- a/src/com/android/wallpaper/picker/WallpaperOnlyFragment.java
+++ b/src/com/android/wallpaper/picker/WallpaperOnlyFragment.java
@@ -15,6 +15,8 @@
*/
package com.android.wallpaper.picker;
+import android.os.Bundle;
+
import com.android.wallpaper.R;
import com.android.wallpaper.model.CustomizationSectionController;
import com.android.wallpaper.model.WallpaperSectionController;
@@ -25,17 +27,26 @@ import java.util.stream.Collectors;
/** The Fragment UI for wallpaper only section. */
public class WallpaperOnlyFragment extends CustomizationPickerFragment {
+ /** Returns a new instance of {@link WallpaperOnlyFragment}. */
+ public static WallpaperOnlyFragment newInstance(boolean isUseRevampedUi) {
+ final WallpaperOnlyFragment fragment = new WallpaperOnlyFragment();
+ final Bundle args = new Bundle();
+ args.putBoolean(KEY_IS_USE_REVAMPED_UI, isUseRevampedUi);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
@Override
public CharSequence getDefaultTitle() {
return getString(R.string.wallpaper_app_name);
}
@Override
- protected List<CustomizationSectionController<?>> getAvailableSections(
+ protected List<CustomizationSectionController<?>> filterAvailableSections(
List<CustomizationSectionController<?>> controllers) {
List<CustomizationSectionController<?>> wallpaperOnlySections = controllers.stream()
.filter(controller -> controller instanceof WallpaperSectionController)
.collect(Collectors.toList());
- return super.getAvailableSections(wallpaperOnlySections);
+ return super.filterAvailableSections(wallpaperOnlySections);
}
}
diff --git a/src/com/android/wallpaper/picker/WallpaperPickerDelegate.java b/src/com/android/wallpaper/picker/WallpaperPickerDelegate.java
index 5b60c064..3c788ef4 100644
--- a/src/com/android/wallpaper/picker/WallpaperPickerDelegate.java
+++ b/src/com/android/wallpaper/picker/WallpaperPickerDelegate.java
@@ -467,7 +467,6 @@ public class WallpaperPickerDelegate implements MyPhotosStarter {
PREVIEW_WALLPAPER_REQUEST_CODE);
return false;
case PREVIEW_LIVE_WALLPAPER_REQUEST_CODE:
- mWallpaperPersister.onLiveWallpaperSet();
populateCategories(/* forceRefresh= */ true);
return true;
case VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE:
@@ -475,7 +474,6 @@ public class WallpaperPickerDelegate implements MyPhotosStarter {
case PREVIEW_WALLPAPER_REQUEST_CODE:
// User previewed and selected a wallpaper, so finish this activity.
if (data != null && data.getBooleanExtra(IS_LIVE_WALLPAPER, false)) {
- mWallpaperPersister.onLiveWallpaperSet();
populateCategories(/* forceRefresh= */ true);
}
return true;
diff --git a/src/com/android/wallpaper/picker/WorkspaceSurfaceHolderCallback.java b/src/com/android/wallpaper/picker/WorkspaceSurfaceHolderCallback.java
index bb4c0d36..9c26cc3d 100644
--- a/src/com/android/wallpaper/picker/WorkspaceSurfaceHolderCallback.java
+++ b/src/com/android/wallpaper/picker/WorkspaceSurfaceHolderCallback.java
@@ -46,12 +46,15 @@ public class WorkspaceSurfaceHolderCallback implements SurfaceHolder.Callback {
private static final String TAG = "WsSurfaceHolderCallback";
private static final String KEY_WALLPAPER_COLORS = "wallpaper_colors";
+ public static final int MESSAGE_ID_UPDATE_PREVIEW = 1337;
+ public static final String KEY_HIDE_BOTTOM_ROW = "hide_bottom_row";
private final SurfaceView mWorkspaceSurface;
private final PreviewUtils mPreviewUtils;
private final boolean mShouldUseWallpaperColors;
private final AtomicBoolean mRequestPending = new AtomicBoolean(false);
private WallpaperColors mWallpaperColors;
+ private boolean mHideBottomRow;
private boolean mIsWallpaperColorsReady;
private Surface mLastSurface;
private Message mCallback;
@@ -126,27 +129,45 @@ public class WorkspaceSurfaceHolderCallback implements SurfaceHolder.Callback {
}
mWallpaperColors = colors;
mIsWallpaperColorsReady = true;
- maybeRenderPreview();
+ }
+
+ /**
+ * Set the current flag if we should hide the workspace bottom row.
+ */
+ public void setHideBottomRow(boolean hideBottomRow) {
+ mHideBottomRow = hideBottomRow;
+ }
+
+ /**
+ * Hides the components in the bottom row.
+ *
+ * @param hide True to hide and false to show.
+ */
+ public void hideBottomRow(boolean hide) {
+ Bundle data = new Bundle();
+ data.putBoolean(KEY_HIDE_BOTTOM_ROW, hide);
+ send(MESSAGE_ID_UPDATE_PREVIEW, data);
}
public void setListener(WorkspaceRenderListener listener) {
mListener = listener;
}
- private void maybeRenderPreview() {
+ /**
+ * Render the preview with the current selected {@link #mWallpaperColors} and
+ * {@link #mHideBottomRow}.
+ */
+ public void maybeRenderPreview() {
if ((mShouldUseWallpaperColors && !mIsWallpaperColorsReady) || mLastSurface == null) {
return;
}
-
mRequestPending.set(true);
requestPreview(mWorkspaceSurface, (result) -> {
mRequestPending.set(false);
if (result != null && mLastSurface != null) {
mWorkspaceSurface.setChildSurfacePackage(
SurfaceViewUtils.getSurfacePackage(result));
-
mCallback = SurfaceViewUtils.getCallback(result);
-
if (mNeedsToCleanUp) {
cleanUp();
} else if (mListener != null) {
@@ -216,6 +237,7 @@ public class WorkspaceSurfaceHolderCallback implements SurfaceHolder.Callback {
Bundle request = SurfaceViewUtils.createSurfaceViewRequest(workspaceSurface, mExtras);
if (mWallpaperColors != null) {
request.putParcelable(KEY_WALLPAPER_COLORS, mWallpaperColors);
+ request.putBoolean(KEY_HIDE_BOTTOM_ROW, mHideBottomRow);
}
mPreviewUtils.renderPreview(request, callback);
}
diff --git a/src/com/android/wallpaper/picker/common/button/ui/viewbinder/ButtonViewBinder.kt b/src/com/android/wallpaper/picker/common/button/ui/viewbinder/ButtonViewBinder.kt
new file mode 100644
index 00000000..6e63835b
--- /dev/null
+++ b/src/com/android/wallpaper/picker/common/button/ui/viewbinder/ButtonViewBinder.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.common.button.ui.viewbinder
+
+import android.view.ContextThemeWrapper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.annotation.LayoutRes
+import com.android.wallpaper.R
+import com.android.wallpaper.picker.common.button.ui.viewmodel.ButtonViewModel
+import com.android.wallpaper.picker.common.text.ui.viewbinder.TextViewBinder
+
+object ButtonViewBinder {
+ /** Returns a newly-created [View] that's already bound to the given [ButtonViewModel]. */
+ fun create(
+ parent: ViewGroup,
+ viewModel: ButtonViewModel,
+ @LayoutRes buttonLayoutResourceId: Int = R.layout.dialog_button,
+ ): View {
+ val view =
+ LayoutInflater.from(
+ ContextThemeWrapper(
+ parent.context,
+ viewModel.style.styleResourceId,
+ )
+ )
+ .inflate(
+ buttonLayoutResourceId,
+ parent,
+ false,
+ )
+ val text: TextView = view.requireViewById(R.id.text)
+ view.setOnClickListener { viewModel.onClicked?.invoke() }
+
+ TextViewBinder.bind(
+ view = text,
+ viewModel = viewModel.text,
+ )
+
+ return view
+ }
+}
diff --git a/src/com/android/wallpaper/model/WorkspaceViewModel.kt b/src/com/android/wallpaper/picker/common/button/ui/viewmodel/ButtonStyle.kt
index 627406ee..a83a8ab4 100644
--- a/src/com/android/wallpaper/model/WorkspaceViewModel.kt
+++ b/src/com/android/wallpaper/picker/common/button/ui/viewmodel/ButtonStyle.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -12,18 +12,23 @@
* 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.wallpaper.model
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
+package com.android.wallpaper.picker.common.button.ui.viewmodel
-/** ViewModel class to keep track of workspace updates. */
-class WorkspaceViewModel : ViewModel() {
+import androidx.annotation.StyleRes
+import com.android.wallpaper.R
- /**
- * Triggers workspace updates through flipping the value from {@code false} to {@code true}, or
- * from {@code true} to {@code false}.
- */
- val updateWorkspace: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
+sealed class ButtonStyle(
+ @StyleRes open val styleResourceId: Int,
+) {
+ object Primary :
+ ButtonStyle(
+ styleResourceId = R.style.DialogButton_Primary,
+ )
+ object Secondary :
+ ButtonStyle(
+ styleResourceId = R.style.DialogButton_Secondary,
+ )
}
diff --git a/src/com/android/wallpaper/picker/common/button/ui/viewmodel/ButtonViewModel.kt b/src/com/android/wallpaper/picker/common/button/ui/viewmodel/ButtonViewModel.kt
new file mode 100644
index 00000000..62f3e81d
--- /dev/null
+++ b/src/com/android/wallpaper/picker/common/button/ui/viewmodel/ButtonViewModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.common.button.ui.viewmodel
+
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+
+data class ButtonViewModel(
+ val text: Text,
+ val style: ButtonStyle,
+ val onClicked: (() -> Unit)? = null,
+)
diff --git a/src/com/android/wallpaper/picker/common/dialog/ui/viewbinder/DialogViewBinder.kt b/src/com/android/wallpaper/picker/common/dialog/ui/viewbinder/DialogViewBinder.kt
new file mode 100644
index 00000000..7130f21a
--- /dev/null
+++ b/src/com/android/wallpaper/picker/common/dialog/ui/viewbinder/DialogViewBinder.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.common.dialog.ui.viewbinder
+
+import android.app.Dialog
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.annotation.LayoutRes
+import androidx.appcompat.app.AlertDialog
+import androidx.core.view.isVisible
+import com.android.wallpaper.R
+import com.android.wallpaper.picker.common.button.ui.viewbinder.ButtonViewBinder
+import com.android.wallpaper.picker.common.dialog.ui.viewmodel.DialogViewModel
+import com.android.wallpaper.picker.common.icon.ui.viewbinder.IconViewBinder
+import com.android.wallpaper.picker.common.text.ui.viewbinder.TextViewBinder
+
+object DialogViewBinder {
+ /** Returns a shown dialog that's bound to the given [DialogViewModel]. */
+ fun show(
+ context: Context,
+ viewModel: DialogViewModel,
+ onDismissed: (() -> Unit)? = null,
+ @LayoutRes dialogLayoutResourceId: Int = R.layout.dialog_view,
+ @LayoutRes buttonLayoutResourceId: Int = R.layout.dialog_button,
+ ): Dialog {
+ val view = LayoutInflater.from(context).inflate(dialogLayoutResourceId, null)
+ val icon: ImageView = view.requireViewById(R.id.icon)
+ val title: TextView = view.requireViewById(R.id.title)
+ val message: TextView = view.requireViewById(R.id.message)
+ val buttonContainer: ViewGroup = view.requireViewById(R.id.button_container)
+
+ viewModel.icon?.let {
+ IconViewBinder.bind(
+ view = icon,
+ viewModel = it,
+ )
+ icon.isVisible = true
+ }
+ ?: run { icon.isVisible = false }
+
+ viewModel.title?.let {
+ TextViewBinder.bind(
+ view = title,
+ viewModel = it,
+ )
+ title.isVisible = true
+ }
+ ?: run { title.isVisible = false }
+
+ viewModel.message?.let {
+ TextViewBinder.bind(
+ view = message,
+ viewModel = it,
+ )
+ message.isVisible = true
+ }
+ ?: run { message.isVisible = false }
+
+ val dialog =
+ AlertDialog.Builder(context, R.style.LightDialogTheme)
+ .setView(view)
+ .apply {
+ if (viewModel.onDismissed != null || onDismissed != null) {
+ setOnDismissListener {
+ onDismissed?.invoke()
+ viewModel.onDismissed?.invoke()
+ }
+ }
+ }
+ .create()
+
+ buttonContainer.removeAllViews()
+ viewModel.buttons.forEach { buttonViewModel ->
+ buttonContainer.addView(
+ ButtonViewBinder.create(
+ parent = buttonContainer,
+ viewModel =
+ buttonViewModel.copy(
+ onClicked = {
+ buttonViewModel.onClicked?.invoke()
+ dialog.dismiss()
+ },
+ ),
+ buttonLayoutResourceId = buttonLayoutResourceId,
+ )
+ )
+ }
+
+ dialog.show()
+ return dialog
+ }
+}
diff --git a/src/com/android/wallpaper/picker/common/dialog/ui/viewmodel/DialogViewModel.kt b/src/com/android/wallpaper/picker/common/dialog/ui/viewmodel/DialogViewModel.kt
new file mode 100644
index 00000000..a34bf33d
--- /dev/null
+++ b/src/com/android/wallpaper/picker/common/dialog/ui/viewmodel/DialogViewModel.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.common.dialog.ui.viewmodel
+
+import com.android.wallpaper.picker.common.button.ui.viewmodel.ButtonViewModel
+import com.android.wallpaper.picker.common.icon.ui.viewmodel.Icon
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+
+data class DialogViewModel(
+ val icon: Icon? = null,
+ val title: Text? = null,
+ val message: Text? = null,
+ val buttons: List<ButtonViewModel> = emptyList(),
+ val onDismissed: (() -> Unit)? = null,
+)
diff --git a/src/com/android/wallpaper/picker/common/icon/ui/viewbinder/ContentDescriptionViewBinder.kt b/src/com/android/wallpaper/picker/common/icon/ui/viewbinder/ContentDescriptionViewBinder.kt
new file mode 100644
index 00000000..e31852b5
--- /dev/null
+++ b/src/com/android/wallpaper/picker/common/icon/ui/viewbinder/ContentDescriptionViewBinder.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.common.icon.ui.viewbinder
+
+import android.view.View
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+
+object ContentDescriptionViewBinder {
+ fun bind(
+ view: View,
+ viewModel: Text,
+ ) {
+ view.contentDescription =
+ when (viewModel) {
+ is Text.Resource -> view.context.getString(viewModel.res)
+ is Text.Loaded -> viewModel.text
+ }
+ }
+}
diff --git a/src/com/android/wallpaper/picker/common/icon/ui/viewbinder/IconViewBinder.kt b/src/com/android/wallpaper/picker/common/icon/ui/viewbinder/IconViewBinder.kt
new file mode 100644
index 00000000..79ec5682
--- /dev/null
+++ b/src/com/android/wallpaper/picker/common/icon/ui/viewbinder/IconViewBinder.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.common.icon.ui.viewbinder
+
+import android.widget.ImageView
+import com.android.wallpaper.picker.common.icon.ui.viewmodel.Icon
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+
+object IconViewBinder {
+ fun bind(
+ view: ImageView,
+ viewModel: Icon,
+ ) {
+ when (viewModel) {
+ is Icon.Resource -> view.setImageResource(viewModel.res)
+ is Icon.Loaded -> view.setImageDrawable(viewModel.drawable)
+ }
+
+ view.contentDescription =
+ when (viewModel.contentDescription) {
+ is Text.Resource ->
+ view.context.getString((viewModel.contentDescription as Text.Resource).res)
+ is Text.Loaded -> (viewModel.contentDescription as Text.Loaded).text
+ null -> null
+ }
+ }
+}
diff --git a/src/com/android/wallpaper/picker/common/icon/ui/viewmodel/Icon.kt b/src/com/android/wallpaper/picker/common/icon/ui/viewmodel/Icon.kt
new file mode 100644
index 00000000..aa42e46a
--- /dev/null
+++ b/src/com/android/wallpaper/picker/common/icon/ui/viewmodel/Icon.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.common.icon.ui.viewmodel
+
+import android.graphics.drawable.Drawable
+import androidx.annotation.DrawableRes
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+
+sealed class Icon(
+ open val contentDescription: Text?,
+) {
+ data class Resource(
+ @DrawableRes val res: Int,
+ override val contentDescription: Text?,
+ ) :
+ Icon(
+ contentDescription = contentDescription,
+ )
+
+ data class Loaded(
+ val drawable: Drawable,
+ override val contentDescription: Text?,
+ ) :
+ Icon(
+ contentDescription = contentDescription,
+ )
+}
diff --git a/src/com/android/wallpaper/picker/common/text/ui/viewbinder/TextViewBinder.kt b/src/com/android/wallpaper/picker/common/text/ui/viewbinder/TextViewBinder.kt
new file mode 100644
index 00000000..2b58bbef
--- /dev/null
+++ b/src/com/android/wallpaper/picker/common/text/ui/viewbinder/TextViewBinder.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.common.text.ui.viewbinder
+
+import android.widget.TextView
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+
+object TextViewBinder {
+ fun bind(
+ view: TextView,
+ viewModel: Text,
+ ) {
+ when (viewModel) {
+ is Text.Resource -> view.setText(viewModel.res)
+ is Text.Loaded -> view.text = viewModel.text
+ }
+ }
+}
diff --git a/src/com/android/wallpaper/picker/common/text/ui/viewmodel/Text.kt b/src/com/android/wallpaper/picker/common/text/ui/viewmodel/Text.kt
new file mode 100644
index 00000000..fc423e3e
--- /dev/null
+++ b/src/com/android/wallpaper/picker/common/text/ui/viewmodel/Text.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.common.text.ui.viewmodel
+
+import android.content.Context
+import androidx.annotation.StringRes
+
+sealed class Text {
+ data class Resource(
+ @StringRes val res: Int,
+ ) : Text()
+
+ data class Loaded(
+ val text: String,
+ ) : Text()
+
+ fun asString(context: Context): String {
+ return when (this) {
+ is Resource -> context.getString(res)
+ is Loaded -> text
+ }
+ }
+
+ companion object {
+ /**
+ * Returns `true` if the given [Text] instances evaluate to the values; `false` otherwise.
+ */
+ fun evaluationEquals(
+ context: Context,
+ first: Text?,
+ second: Text?,
+ ): Boolean {
+ return first?.asString(context) == second?.asString(context)
+ }
+ }
+}
diff --git a/src/com/android/wallpaper/picker/customization/data/content/WallpaperClient.kt b/src/com/android/wallpaper/picker/customization/data/content/WallpaperClient.kt
new file mode 100644
index 00000000..e849426b
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/data/content/WallpaperClient.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.data.content
+
+import android.graphics.Bitmap
+import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination
+import com.android.wallpaper.picker.customization.shared.model.WallpaperModel
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface for classes that can interact with the Wallpaper API. */
+interface WallpaperClient {
+
+ /** Lists the most recent wallpapers. The first one is the most recent (current) wallpaper. */
+ fun recentWallpapers(
+ destination: WallpaperDestination,
+ limit: Int,
+ ): Flow<List<WallpaperModel>>
+
+ /** Returns the selected wallpaper. */
+ suspend fun getCurrentWallpaper(
+ destination: WallpaperDestination,
+ ): WallpaperModel
+
+ /**
+ * Asynchronously sets the wallpaper to the one with the given ID.
+ *
+ * @param destination The screen to set the wallpaper on.
+ * @param wallpaperId The ID of the wallpaper to set.
+ * @param onDone A callback to invoke when setting is done.
+ */
+ suspend fun setWallpaper(
+ destination: WallpaperDestination,
+ wallpaperId: String,
+ onDone: () -> Unit
+ )
+
+ /** Returns a thumbnail for the wallpaper with the given ID. */
+ suspend fun loadThumbnail(wallpaperId: String): Bitmap?
+}
diff --git a/src/com/android/wallpaper/picker/customization/data/content/WallpaperClientImpl.kt b/src/com/android/wallpaper/picker/customization/data/content/WallpaperClientImpl.kt
new file mode 100644
index 00000000..e85cdfd8
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/data/content/WallpaperClientImpl.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.data.content
+
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.net.Uri
+import android.util.Log
+import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination
+import com.android.wallpaper.picker.customization.shared.model.WallpaperModel
+import java.io.IOException
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+class WallpaperClientImpl(
+ private val context: Context,
+) : WallpaperClient {
+
+ override fun recentWallpapers(
+ destination: WallpaperDestination,
+ limit: Int,
+ ): Flow<List<WallpaperModel>> {
+ return callbackFlow {
+ suspend fun queryAndSend(limit: Int) {
+ send(queryRecentWallpapers(destination = destination, limit = limit))
+ }
+
+ val contentObserver =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ launch { queryAndSend(limit = limit) }
+ }
+ }
+
+ context.contentResolver.registerContentObserver(
+ LIST_RECENTS_URI,
+ /* notifyForDescendants= */ true,
+ contentObserver,
+ )
+ queryAndSend(limit = limit)
+
+ awaitClose { context.contentResolver.unregisterContentObserver(contentObserver) }
+ }
+ }
+
+ override suspend fun getCurrentWallpaper(
+ destination: WallpaperDestination,
+ ): WallpaperModel {
+ return queryRecentWallpapers(destination = destination, limit = 1).first()
+ }
+
+ override suspend fun setWallpaper(
+ destination: WallpaperDestination,
+ wallpaperId: String,
+ onDone: () -> Unit
+ ) {
+ val updateValues = ContentValues()
+ updateValues.put(KEY_ID, wallpaperId)
+ updateValues.put(KEY_SCREEN, destination.asString())
+ val updatedRowCount = context.contentResolver.update(SET_WALLPAPER_URI, updateValues, null)
+ if (updatedRowCount == 0) {
+ Log.e(TAG, "Error setting wallpaper: $wallpaperId")
+ }
+ onDone.invoke()
+ }
+
+ private suspend fun queryRecentWallpapers(
+ destination: WallpaperDestination,
+ limit: Int,
+ ): List<WallpaperModel> {
+ context.contentResolver
+ .query(
+ LIST_RECENTS_URI.buildUpon().appendPath(destination.asString()).build(),
+ arrayOf(
+ KEY_ID,
+ KEY_PLACEHOLDER_COLOR,
+ ),
+ null,
+ null,
+ )
+ .use { cursor ->
+ if (cursor == null || cursor.count == 0) {
+ return emptyList()
+ }
+
+ return buildList {
+ val idColumnIndex = cursor.getColumnIndex(KEY_ID)
+ val placeholderColorColumnIndex = cursor.getColumnIndex(KEY_PLACEHOLDER_COLOR)
+ while (cursor.moveToNext() && size < limit) {
+ val wallpaperId = cursor.getString(idColumnIndex)
+ val placeholderColor = cursor.getInt(placeholderColorColumnIndex)
+ add(
+ WallpaperModel(
+ wallpaperId = wallpaperId,
+ placeholderColor = placeholderColor,
+ )
+ )
+ }
+ }
+ }
+ }
+
+ override suspend fun loadThumbnail(
+ wallpaperId: String,
+ ): Bitmap? {
+ try {
+ // We're already using this in a suspend function, so we're okay.
+ @Suppress("BlockingMethodInNonBlockingContext")
+ context.contentResolver
+ .openFile(
+ GET_THUMBNAIL_BASE_URI.buildUpon().appendPath(wallpaperId).build(),
+ "r",
+ null,
+ )
+ .use { file ->
+ if (file == null) {
+ Log.e(TAG, "Error getting wallpaper preview: $wallpaperId")
+ } else {
+ return BitmapFactory.decodeFileDescriptor(file.fileDescriptor)
+ }
+ }
+ } catch (e: IOException) {
+ Log.e(TAG, "Error getting wallpaper preview: $wallpaperId", e)
+ }
+
+ return null
+ }
+
+ private fun WallpaperDestination.asString(): String {
+ return when (this) {
+ WallpaperDestination.BOTH -> SCREEN_ALL
+ WallpaperDestination.HOME -> SCREEN_HOME
+ WallpaperDestination.LOCK -> SCREEN_LOCK
+ }
+ }
+
+ companion object {
+ private const val TAG = "WallpaperClientImpl"
+ private const val AUTHORITY = "com.google.android.apps.wallpaper.recents"
+
+ /** Path for making a content provider request to set the wallpaper. */
+ private const val PATH_SET_WALLPAPER = "set_recent_wallpaper"
+ /** Path for making a content provider request to query for the recent wallpapers. */
+ private const val PATH_LIST_RECENTS = "list_recent"
+ /** Path for making a content provider request to query for the thumbnail of a wallpaper. */
+ private const val PATH_GET_THUMBNAIL = "thumb"
+
+ private val BASE_URI =
+ Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build()
+ /** [Uri] for making a content provider request to set the wallpaper. */
+ private val SET_WALLPAPER_URI = BASE_URI.buildUpon().appendPath(PATH_SET_WALLPAPER).build()
+ /** [Uri] for making a content provider request to query for the recent wallpapers. */
+ private val LIST_RECENTS_URI = BASE_URI.buildUpon().appendPath(PATH_LIST_RECENTS).build()
+ /**
+ * [Uri] for making a content provider request to query for the thumbnail of a wallpaper.
+ */
+ private val GET_THUMBNAIL_BASE_URI =
+ BASE_URI.buildUpon().appendPath(PATH_GET_THUMBNAIL).build()
+
+ /** Key for a parameter used to pass the wallpaper ID to/from the content provider. */
+ private const val KEY_ID = "id"
+ /** Key for a parameter used to pass the screen to/from the content provider. */
+ private const val KEY_SCREEN = "screen"
+ private const val SCREEN_ALL = "all_screens"
+ private const val SCREEN_HOME = "home_screen"
+ private const val SCREEN_LOCK = "lock_screen"
+ /**
+ * Key for a parameter used to get the placeholder color for a wallpaper from the content
+ * provider.
+ */
+ private const val KEY_PLACEHOLDER_COLOR = "placeholder_color"
+ }
+}
diff --git a/src/com/android/wallpaper/picker/customization/data/repository/WallpaperRepository.kt b/src/com/android/wallpaper/picker/customization/data/repository/WallpaperRepository.kt
new file mode 100644
index 00000000..6234fa53
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/data/repository/WallpaperRepository.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.data.repository
+
+import android.graphics.Bitmap
+import com.android.wallpaper.picker.customization.data.content.WallpaperClient
+import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination
+import com.android.wallpaper.picker.customization.shared.model.WallpaperModel
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+
+/** Encapsulates access to wallpaper-related data. */
+class WallpaperRepository(
+ private val scope: CoroutineScope,
+ private val client: WallpaperClient,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) {
+ /** The ID of the currently-selected wallpaper. */
+ fun selectedWallpaperId(
+ destination: WallpaperDestination,
+ ): StateFlow<String> {
+ return client
+ .recentWallpapers(destination = destination, limit = 1)
+ .map { previews -> previews.first().wallpaperId }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue =
+ runBlocking {
+ client.getCurrentWallpaper(destination = destination).wallpaperId
+ },
+ )
+ }
+
+ private val _selectingWallpaperId =
+ MutableStateFlow<Map<WallpaperDestination, String?>>(emptyMap())
+ /**
+ * The ID of the wallpaper that is in the process of becoming the selected wallpaper or `null`
+ * if no such transaction is currently taking place.
+ */
+ val selectingWallpaperId: StateFlow<Map<WallpaperDestination, String?>> =
+ _selectingWallpaperId.asStateFlow()
+
+ /** Lists the most recent wallpapers. The first one is the most recent (current) wallpaper. */
+ fun recentWallpapers(
+ destination: WallpaperDestination,
+ limit: Int,
+ ): Flow<List<WallpaperModel>> {
+ return client
+ .recentWallpapers(destination = destination, limit = limit)
+ .flowOn(backgroundDispatcher)
+ }
+
+ /** Returns a thumbnail for the wallpaper with the given ID. */
+ suspend fun loadThumbnail(wallpaperId: String): Bitmap? {
+ return withContext(backgroundDispatcher) { client.loadThumbnail(wallpaperId) }
+ }
+
+ /** Sets the wallpaper to the one with the given ID. */
+ suspend fun setWallpaper(
+ destination: WallpaperDestination,
+ wallpaperId: String,
+ ) {
+ _selectingWallpaperId.value =
+ _selectingWallpaperId.value.toMutableMap().apply { this[destination] = wallpaperId }
+ withContext(backgroundDispatcher) {
+ client.setWallpaper(
+ destination = destination,
+ wallpaperId = wallpaperId,
+ ) {
+ _selectingWallpaperId.value =
+ _selectingWallpaperId.value.toMutableMap().apply { this[destination] = null }
+ }
+ }
+ }
+}
diff --git a/src/com/android/wallpaper/picker/customization/domain/interactor/WallpaperInteractor.kt b/src/com/android/wallpaper/picker/customization/domain/interactor/WallpaperInteractor.kt
new file mode 100644
index 00000000..d9e2ef64
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/domain/interactor/WallpaperInteractor.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.domain.interactor
+
+import android.graphics.Bitmap
+import com.android.wallpaper.module.CustomizationSections
+import com.android.wallpaper.picker.customization.data.repository.WallpaperRepository
+import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination
+import com.android.wallpaper.picker.customization.shared.model.WallpaperModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+
+/** Handles business logic for wallpaper-related use-cases. */
+class WallpaperInteractor(
+ private val repository: WallpaperRepository,
+ /** Returns whether wallpaper picker should handle reload */
+ val shouldHandleReload: () -> Boolean = { true },
+) {
+ /** Returns a flow that is updated whenever the wallpaper has been updated */
+ fun wallpaperUpdateEvents(screen: CustomizationSections.Screen): Flow<WallpaperModel> {
+ return when (screen) {
+ CustomizationSections.Screen.LOCK_SCREEN ->
+ previews(WallpaperDestination.LOCK, 1).map { recentWallpapers ->
+ recentWallpapers[0]
+ }
+ CustomizationSections.Screen.HOME_SCREEN ->
+ previews(WallpaperDestination.HOME, 1).map { recentWallpapers ->
+ recentWallpapers[0]
+ }
+ }
+ }
+
+ /** Returns the ID of the currently-selected wallpaper. */
+ fun selectedWallpaperId(
+ destination: WallpaperDestination,
+ ): StateFlow<String> {
+ return repository.selectedWallpaperId(destination = destination)
+ }
+
+ /**
+ * Returns the ID of the wallpaper that is in the process of becoming the selected wallpaper or
+ * `null` if no such transaction is currently taking place.
+ */
+ fun selectingWallpaperId(
+ destination: WallpaperDestination,
+ ): Flow<String?> {
+ return repository.selectingWallpaperId.map { it[destination] }
+ }
+
+ /**
+ * Lists the [maxResults] most recent wallpapers.
+ *
+ * The first one is the most recent (current) wallpaper.
+ */
+ fun previews(
+ destination: WallpaperDestination,
+ maxResults: Int,
+ ): Flow<List<WallpaperModel>> {
+ return repository
+ .recentWallpapers(
+ destination = destination,
+ limit = maxResults,
+ )
+ .map { previews ->
+ if (previews.size > maxResults) {
+ previews.subList(0, maxResults)
+ } else {
+ previews
+ }
+ }
+ }
+
+ /** Sets the wallpaper to the one with the given ID. */
+ suspend fun setWallpaper(
+ destination: WallpaperDestination,
+ wallpaperId: String,
+ ) {
+ repository.setWallpaper(
+ destination = destination,
+ wallpaperId = wallpaperId,
+ )
+ }
+
+ /** Returns a thumbnail for the wallpaper with the given ID. */
+ suspend fun loadThumbnail(wallpaperId: String): Bitmap? {
+ return repository.loadThumbnail(
+ wallpaperId = wallpaperId,
+ )
+ }
+}
diff --git a/src/com/android/wallpaper/picker/customization/domain/interactor/WallpaperSnapshotRestorer.kt b/src/com/android/wallpaper/picker/customization/domain/interactor/WallpaperSnapshotRestorer.kt
new file mode 100644
index 00000000..7c23cfed
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/domain/interactor/WallpaperSnapshotRestorer.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.domain.interactor
+
+import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination
+import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
+import com.android.wallpaper.picker.undo.domain.interactor.SnapshotStore
+import com.android.wallpaper.picker.undo.shared.model.RestorableSnapshot
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.launch
+
+/** Stores and restores undo snapshots for wallpaper state. */
+class WallpaperSnapshotRestorer(
+ private val scope: CoroutineScope,
+ private val interactor: WallpaperInteractor,
+) : SnapshotRestorer {
+
+ private var store: SnapshotStore = SnapshotStore.NOOP
+
+ override suspend fun setUpSnapshotRestorer(
+ store: SnapshotStore,
+ ): RestorableSnapshot {
+ this.store = store
+ startObserving()
+ return snapshot()
+ }
+
+ override suspend fun restoreToSnapshot(
+ snapshot: RestorableSnapshot,
+ ) {
+ val homeWallpaperId = snapshot.args[SELECTED_HOME_SCREEN_WALLPAPER_ID]
+ if (!homeWallpaperId.isNullOrEmpty()) {
+ interactor.setWallpaper(
+ destination = WallpaperDestination.HOME,
+ wallpaperId = homeWallpaperId
+ )
+ }
+
+ val lockWallpaperId = snapshot.args[SELECTED_LOCK_SCREEN_WALLPAPER_ID]
+ if (!lockWallpaperId.isNullOrEmpty()) {
+ interactor.setWallpaper(
+ destination = WallpaperDestination.LOCK,
+ wallpaperId = lockWallpaperId
+ )
+ }
+ }
+
+ private fun startObserving() {
+ scope.launch {
+ combine(
+ interactor.selectedWallpaperId(destination = WallpaperDestination.HOME),
+ interactor.selectedWallpaperId(destination = WallpaperDestination.LOCK),
+ ::Pair,
+ )
+ .drop(1) // We skip the first value because it's the same as the initial.
+ .collect { (homeWallpaperId, lockWallpaperId) ->
+ store.store(
+ snapshot(
+ homeWallpaperId,
+ lockWallpaperId,
+ )
+ )
+ }
+ }
+ }
+
+ private fun snapshot(
+ homeWallpaperId: String = querySelectedWallpaperId(destination = WallpaperDestination.HOME),
+ lockWallpaperId: String = querySelectedWallpaperId(destination = WallpaperDestination.LOCK),
+ ): RestorableSnapshot {
+ return RestorableSnapshot(
+ args =
+ buildMap {
+ put(
+ SELECTED_HOME_SCREEN_WALLPAPER_ID,
+ homeWallpaperId,
+ )
+ put(
+ SELECTED_LOCK_SCREEN_WALLPAPER_ID,
+ lockWallpaperId,
+ )
+ }
+ )
+ }
+
+ private fun querySelectedWallpaperId(destination: WallpaperDestination): String {
+ return interactor.selectedWallpaperId(destination = destination).value
+ }
+
+ companion object {
+ private const val SELECTED_HOME_SCREEN_WALLPAPER_ID = "selected_home_screen_wallpaper_id"
+ private const val SELECTED_LOCK_SCREEN_WALLPAPER_ID = "selected_lock_screen_wallpaper_id"
+ }
+}
diff --git a/src/com/android/wallpaper/picker/customization/shared/model/WallpaperDestination.kt b/src/com/android/wallpaper/picker/customization/shared/model/WallpaperDestination.kt
new file mode 100644
index 00000000..89d5af4e
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/shared/model/WallpaperDestination.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.shared.model
+
+/** Enumerates all known wallpaper destinations. */
+enum class WallpaperDestination {
+ /** Both [HOME] and [LOCK] destinations. */
+ BOTH,
+ /** The home screen wallpaper. */
+ HOME,
+ /** The lock screen wallpaper. */
+ LOCK,
+}
diff --git a/src/com/android/wallpaper/picker/undo/ui/viewmodel/UndoDialogViewModel.kt b/src/com/android/wallpaper/picker/customization/shared/model/WallpaperModel.kt
index b7f754b5..a0d8b38d 100644
--- a/src/com/android/wallpaper/picker/undo/ui/viewmodel/UndoDialogViewModel.kt
+++ b/src/com/android/wallpaper/picker/customization/shared/model/WallpaperModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -15,10 +15,10 @@
*
*/
-package com.android.wallpaper.picker.undo.ui.viewmodel
+package com.android.wallpaper.picker.customization.shared.model
-/** Models the UI state for an undo confirmation dialog. */
-data class UndoDialogViewModel(
- val onConfirmed: () -> Unit,
- val onDismissed: () -> Unit,
+/** Models a single wallpaper preview. */
+data class WallpaperModel(
+ val wallpaperId: String,
+ val placeholderColor: Int,
)
diff --git a/src/com/android/wallpaper/picker/customization/ui/binder/CustomizationPickerBinder.kt b/src/com/android/wallpaper/picker/customization/ui/binder/CustomizationPickerBinder.kt
index 53c8375b..be036bb4 100644
--- a/src/com/android/wallpaper/picker/customization/ui/binder/CustomizationPickerBinder.kt
+++ b/src/com/android/wallpaper/picker/customization/ui/binder/CustomizationPickerBinder.kt
@@ -30,17 +30,30 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.wallpaper.R
import com.android.wallpaper.model.CustomizationSectionController
-import com.android.wallpaper.model.WallpaperSectionController
import com.android.wallpaper.picker.SectionView
-import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewSectionController
import com.android.wallpaper.picker.customization.ui.viewmodel.CustomizationPickerViewModel
import com.android.wallpaper.picker.undo.ui.binder.RevertToolbarButtonBinder
+import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.launch
typealias SectionController = CustomizationSectionController<*>
/** Binds view to view-model for the customization picker. */
object CustomizationPickerBinder {
+
+ /**
+ * Binds the given view and view-model, keeping the UI up-to-date and listening to user input.
+ *
+ * @param view The root of the UI to keep up-to-date and observe for user input.
+ * @param toolbarViewId The view ID of the toolbar view.
+ * @param viewModel The view-model to observe UI state from and report user input to.
+ * @param lifecycleOwner An owner of the lifecycle, so we can stop doing work when the lifecycle
+ * cleans up.
+ * @param sectionControllerProvider A function that can provide the list of [SectionController]
+ * instances to show, based on the given passed-in value of "isOnLockScreen".
+ * @return A [DisposableHandle] to use to dispose of the binding before another binding is about
+ * to be created by a subsequent call to this function.
+ */
@JvmStatic
fun bind(
view: View,
@@ -48,7 +61,7 @@ object CustomizationPickerBinder {
viewModel: CustomizationPickerViewModel,
lifecycleOwner: LifecycleOwner,
sectionControllerProvider: (isOnLockScreen: Boolean) -> List<SectionController>,
- ) {
+ ): DisposableHandle {
RevertToolbarButtonBinder.bind(
view = view.requireViewById(toolbarViewId),
viewModel = viewModel.undo,
@@ -77,68 +90,82 @@ object CustomizationPickerBinder {
topMargin = 0
}
- lifecycleOwner.lifecycleScope.launch {
- lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.isOnLockScreen.collect { isOnLockScreen ->
- // These are the available section controllers we should use now.
- val newSectionControllers =
- sectionControllerProvider.invoke(isOnLockScreen).filter {
- it.isAvailable(view.context)
- }
+ val job =
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.isOnLockScreen.collect { isOnLockScreen ->
+ // These are the available section controllers we should use now.
+ val newSectionControllers =
+ sectionControllerProvider.invoke(isOnLockScreen).filter {
+ it.isAvailable(view.context)
+ }
- check(
- newSectionControllers[0] is WallpaperSectionController ||
- newSectionControllers[0] is ScreenPreviewSectionController
- ) {
- "The first section must always be the preview or the assumption below" +
- " must be updated."
- }
+ check(
+ newSectionControllers[0].shouldRetainInstanceWhenSwitchingTabs()
+ ) {
+ "We are not recreating the first section when the users switching" +
+ " between the home screen and lock screen tab. The first" +
+ " section should always retain."
+ }
- val firstTime = sectionContainer.childCount == 0
- if (!firstTime) {
- // Remove all views, except the very first one, which we assume is for
- // the wallpaper preview section.
- sectionContainer.removeViews(1, sectionContainer.childCount - 1)
+ val firstTime = sectionContainer.childCount == 0
+ if (!firstTime) {
+ // Remove all views, except the very first one, which we assume is
+ // for
+ // the wallpaper preview section.
+ sectionContainer.removeViews(1, sectionContainer.childCount - 1)
- // The old controllers for the removed views should be released, except
- // for the very first one, which is for the wallpaper preview section;
- // that one we keep but just tell it that we switched screens.
- sectionContainer.children
- .mapNotNull { it.tag as? SectionController }
- .forEachIndexed { index, oldController ->
- if (index == 0) {
- // We assume that index 0 is the wallpaper preview section.
- // We keep it because it's an expensive section (as it needs
- // to maintain a wallpaper connection that seems to be
- // making assumptions about its SurfaceView always remaining
- // attached to the window).
- oldController.onScreenSwitched(isOnLockScreen)
- } else {
- // All other old controllers will be thrown out so let's
- // release them.
- oldController.release()
+ // The old controllers for the removed views should be released,
+ // except
+ // for the very first one, which is for the wallpaper preview
+ // section;
+ // that one we keep but just tell it that we switched screens.
+ sectionContainer.children
+ .mapNotNull { it.tag as? SectionController }
+ .forEachIndexed { index, oldController ->
+ if (index == 0) {
+ // We assume that index 0 is the wallpaper preview
+ // section.
+ // We keep it because it's an expensive section (as it
+ // needs
+ // to maintain a wallpaper connection that seems to be
+ // making assumptions about its SurfaceView always
+ // remaining
+ // attached to the window).
+ oldController.onScreenSwitched(isOnLockScreen)
+ } else {
+ // All other old controllers will be thrown out so let's
+ // release them.
+ oldController.release()
+ }
}
- }
- }
+ }
- // Let's add the new controllers and views.
- newSectionControllers.forEachIndexed { index, controller ->
- if (firstTime || index > 0) {
- val addedView = controller.createView(view.context, isOnLockScreen)
- addedView.tag = controller
- sectionContainer.addView(addedView)
+ // Let's add the new controllers and views.
+ newSectionControllers.forEachIndexed { index, controller ->
+ if (firstTime || index > 0) {
+ val addedView =
+ controller.createView(
+ view.context,
+ CustomizationSectionController.ViewCreationParams(
+ isOnLockScreen = isOnLockScreen,
+ )
+ )
+ addedView?.tag = controller
+ sectionContainer.addView(addedView)
+ }
}
}
}
}
- }
- // This happens when the lifecycle is stopped.
- sectionContainer.children
- .mapNotNull { it.tag as? CustomizationSectionController<out SectionView> }
- .forEach { controller -> controller.release() }
- sectionContainer.removeAllViews()
- }
+ // This happens when the lifecycle is stopped.
+ sectionContainer.children
+ .mapNotNull { it.tag as? CustomizationSectionController<out SectionView> }
+ .forEach { controller -> controller.release() }
+ sectionContainer.removeAllViews()
+ }
+ return DisposableHandle { job.cancel() }
}
}
diff --git a/src/com/android/wallpaper/picker/customization/ui/binder/ScreenPreviewBinder.kt b/src/com/android/wallpaper/picker/customization/ui/binder/ScreenPreviewBinder.kt
index 9df609eb..a703b9a0 100644
--- a/src/com/android/wallpaper/picker/customization/ui/binder/ScreenPreviewBinder.kt
+++ b/src/com/android/wallpaper/picker/customization/ui/binder/ScreenPreviewBinder.kt
@@ -23,18 +23,22 @@ import android.content.Intent
import android.os.Bundle
import android.service.wallpaper.WallpaperService
import android.view.SurfaceView
+import android.view.View
+import android.view.ViewGroup
import androidx.cardview.widget.CardView
import androidx.core.view.isVisible
+import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import com.android.wallpaper.R
import com.android.wallpaper.asset.Asset
import com.android.wallpaper.asset.BitmapCachingAsset
import com.android.wallpaper.asset.CurrentWallpaperAssetVN
import com.android.wallpaper.model.LiveWallpaperInfo
import com.android.wallpaper.model.WallpaperInfo
+import com.android.wallpaper.module.CustomizationSections
import com.android.wallpaper.picker.WorkspaceSurfaceHolderCallback
import com.android.wallpaper.picker.customization.ui.viewmodel.ScreenPreviewViewModel
import com.android.wallpaper.util.ResourceUtils
@@ -57,8 +61,16 @@ object ScreenPreviewBinder {
id: Int,
args: Bundle = Bundle.EMPTY,
)
+ fun destroy()
}
+ /**
+ * Binds the view to the given [viewModel].
+ *
+ * Note that if [dimWallpaper] is `true`, the wallpaper will be dimmed (to help highlight
+ * something that is changing on top of the wallpaper, for example, the lock screen shortcuts or
+ * the clock).
+ */
@JvmStatic
fun bind(
activity: Activity,
@@ -66,9 +78,19 @@ object ScreenPreviewBinder {
viewModel: ScreenPreviewViewModel,
lifecycleOwner: LifecycleOwner,
offsetToStart: Boolean,
+ dimWallpaper: Boolean = false,
+ // TODO (b/270193793): add below fields to all usages of this class & remove default values
+ screen: CustomizationSections.Screen = CustomizationSections.Screen.LOCK_SCREEN,
+ onPreviewDirty: () -> Unit = {},
): Binding {
val workspaceSurface: SurfaceView = previewView.requireViewById(R.id.workspace_surface)
val wallpaperSurface: SurfaceView = previewView.requireViewById(R.id.wallpaper_surface)
+ wallpaperSurface.setZOrderOnTop(false)
+
+ if (dimWallpaper) {
+ previewView.requireViewById<View>(R.id.wallpaper_dimming_scrim).isVisible = true
+ workspaceSurface.setZOrderOnTop(true)
+ }
previewView.radius =
previewView.resources.getDimension(R.dimen.wallpaper_picker_entry_card_corner_radius)
@@ -78,10 +100,23 @@ object ScreenPreviewBinder {
var wallpaperConnection: WallpaperConnection? = null
var wallpaperInfo: WallpaperInfo? = null
- lifecycleOwner.lifecycle.addObserver(
- LifecycleEventObserver { _, event ->
- when (event) {
- Lifecycle.Event.ON_CREATE -> {
+ val job =
+ lifecycleOwner.lifecycleScope.launch {
+ launch {
+ val lifecycleObserver =
+ object : DefaultLifecycleObserver {
+ override fun onStop(owner: LifecycleOwner) {
+ super.onStop(owner)
+ wallpaperConnection?.disconnect()
+ }
+
+ override fun onPause(owner: LifecycleOwner) {
+ super.onPause(owner)
+ wallpaperConnection?.setVisibility(false)
+ }
+ }
+
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
previewSurfaceCallback =
WorkspaceSurfaceHolderCallback(
workspaceSurface,
@@ -89,7 +124,9 @@ object ScreenPreviewBinder {
viewModel.getInitialExtras(),
)
workspaceSurface.holder.addCallback(previewSurfaceCallback)
- workspaceSurface.setZOrderMediaOverlay(true)
+ if (!dimWallpaper) {
+ workspaceSurface.setZOrderMediaOverlay(true)
+ }
wallpaperSurfaceCallback =
WallpaperSurfaceCallback(
@@ -114,15 +151,42 @@ object ScreenPreviewBinder {
)
}
wallpaperSurface.holder.addCallback(wallpaperSurfaceCallback)
- wallpaperSurface.setZOrderMediaOverlay(true)
+ if (!dimWallpaper) {
+ wallpaperSurface.setZOrderMediaOverlay(true)
+ }
+
+ lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
}
- Lifecycle.Event.ON_DESTROY -> {
- workspaceSurface.holder.removeCallback(previewSurfaceCallback)
- previewSurfaceCallback?.cleanUp()
- wallpaperSurface.holder.removeCallback(wallpaperSurfaceCallback)
- wallpaperSurfaceCallback?.cleanUp()
+
+ // Here when destroyed.
+ lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
+ workspaceSurface.holder.removeCallback(previewSurfaceCallback)
+ previewSurfaceCallback?.cleanUp()
+ wallpaperSurface.holder.removeCallback(wallpaperSurfaceCallback)
+ wallpaperSurfaceCallback?.cleanUp()
+ }
+
+ launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ var initialWallpaperUpdate = true
+ viewModel.wallpaperUpdateEvents(screen)?.collect {
+ // Do not update screen preview on initial update,since the initial
+ // update results from starting or resuming the activity.
+ //
+ // In addition, update screen preview only if system color is a preset
+ // color. Otherwise, setting wallpaper will cause a change in wallpaper
+ // color and trigger a reset from system ui
+ if (initialWallpaperUpdate) {
+ initialWallpaperUpdate = false
+ } else if (viewModel.shouldHandleReload()) {
+ onPreviewDirty()
+ }
+ }
}
- Lifecycle.Event.ON_RESUME -> {
+ }
+
+ launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
lifecycleOwner.lifecycleScope.launch {
wallpaperInfo = viewModel.getWallpaperInfo()
(wallpaperInfo as? LiveWallpaperInfo)?.let { liveWallpaperInfo ->
@@ -162,16 +226,8 @@ object ScreenPreviewBinder {
)
}
}
- Lifecycle.Event.ON_PAUSE -> {
- wallpaperConnection?.setVisibility(false)
- }
- Lifecycle.Event.ON_STOP -> {
- wallpaperConnection?.disconnect()
- }
- else -> Unit
}
}
- )
return object : Binding {
override fun show() {
@@ -189,6 +245,24 @@ object ScreenPreviewBinder {
override fun sendMessage(id: Int, args: Bundle) {
previewSurfaceCallback?.send(id, args)
}
+
+ override fun destroy() {
+ job.cancel()
+ // We want to remove the SurfaceView from its parent and add it back. This causes
+ // the hierarchy to treat the SurfaceView as "dirty" which will cause it to render
+ // itself anew the next time the bind function is invoked.
+ removeAndReadd(workspaceSurface)
+ }
+ }
+ }
+
+ private fun removeAndReadd(view: View) {
+ (view.parent as? ViewGroup)?.let { parent ->
+ val indexInParent = parent.indexOfChild(view)
+ if (indexInParent >= 0) {
+ parent.removeView(view)
+ parent.addView(view, indexInParent)
+ }
}
}
diff --git a/src/com/android/wallpaper/picker/customization/ui/binder/WallpaperQuickSwitchOptionBinder.kt b/src/com/android/wallpaper/picker/customization/ui/binder/WallpaperQuickSwitchOptionBinder.kt
new file mode 100644
index 00000000..11f2ede3
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/ui/binder/WallpaperQuickSwitchOptionBinder.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.ui.binder
+
+import android.animation.ValueAnimator
+import android.view.View
+import android.widget.ImageView
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.wallpaper.R
+import com.android.wallpaper.picker.customization.ui.viewmodel.WallpaperQuickSwitchOptionViewModel
+import kotlinx.coroutines.launch
+
+/**
+ * Binds between the view and view-model for a single wallpaper quick switch option.
+ *
+ * The options are presented to the user in some sort of collection and clicking on one of the
+ * options selects that wallpaper.
+ */
+object WallpaperQuickSwitchOptionBinder {
+
+ /** Binds the given view to the given view-model. */
+ fun bind(
+ view: View,
+ viewModel: WallpaperQuickSwitchOptionViewModel,
+ lifecycleOwner: LifecycleOwner,
+ smallOptionWidthPx: Int,
+ largeOptionWidthPx: Int,
+ ) {
+ val selectionBorder: View = view.requireViewById(R.id.selection_border)
+ val selectionIcon: View = view.requireViewById(R.id.selection_icon)
+ val progressIndicator: View = view.requireViewById(R.id.progress_indicator)
+ val thumbnailView: ImageView = view.requireViewById(R.id.thumbnail)
+ val placeholder: ImageView = view.requireViewById(R.id.placeholder)
+
+ placeholder.setBackgroundColor(viewModel.placeholderColor)
+
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.onSelected.collect { onSelectedOrNull ->
+ view.setOnClickListener(
+ if (onSelectedOrNull != null) {
+ { onSelectedOrNull.invoke() }
+ } else {
+ null
+ }
+ )
+ }
+ }
+
+ launch {
+ // We want to skip animating the first width update.
+ var isFirstValue = true
+ viewModel.isLarge.collect { isLarge ->
+ updateWidth(
+ view = view,
+ targetWidthPx = if (isLarge) largeOptionWidthPx else smallOptionWidthPx,
+ animate = !isFirstValue,
+ )
+ isFirstValue = false
+ }
+ }
+
+ launch {
+ viewModel.isSelectionBorderVisible.collect {
+ selectionBorder.animatedVisibility(isVisible = it)
+ }
+ }
+
+ launch {
+ viewModel.isSelectionIconVisible.collect {
+ selectionIcon.animatedVisibility(isVisible = it)
+ }
+ }
+
+ launch {
+ viewModel.isProgressIndicatorVisible.collect {
+ progressIndicator.animatedVisibility(isVisible = it)
+ }
+ }
+
+ launch {
+ val thumbnail = viewModel.thumbnail()
+ if (thumbnailView.tag != thumbnail) {
+ thumbnailView.tag = thumbnail
+ if (thumbnail != null) {
+ thumbnailView.setImageBitmap(thumbnail)
+ thumbnailView.fadeIn()
+ } else {
+ thumbnailView.fadeOut()
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates the view width.
+ *
+ * @param view The [View] to update.
+ * @param targetWidthPx The width we want the view to have.
+ * @param animate Whether the update should be animated.
+ */
+ private fun updateWidth(
+ view: View,
+ targetWidthPx: Int,
+ animate: Boolean,
+ ) {
+ fun setWidth(widthPx: Int) {
+ view.updateLayoutParams { width = widthPx }
+ }
+
+ if (!animate) {
+ setWidth(widthPx = targetWidthPx)
+ return
+ }
+
+ ValueAnimator.ofInt(
+ view.width,
+ targetWidthPx,
+ )
+ .apply {
+ addUpdateListener { setWidth(it.animatedValue as Int) }
+ start()
+ }
+ }
+
+ private fun View.animatedVisibility(
+ isVisible: Boolean,
+ ) {
+ if (isVisible) {
+ fadeIn()
+ } else {
+ fadeOut()
+ }
+ }
+
+ private fun View.fadeIn() {
+ if (isVisible) {
+ return
+ }
+
+ alpha = 0f
+ isVisible = true
+ animate().alpha(1f).start()
+ }
+
+ private fun View.fadeOut() {
+ if (!isVisible) {
+ return
+ }
+
+ animate().alpha(0f).withEndAction { isVisible = false }.start()
+ }
+}
diff --git a/src/com/android/wallpaper/picker/customization/ui/binder/WallpaperQuickSwitchSectionBinder.kt b/src/com/android/wallpaper/picker/customization/ui/binder/WallpaperQuickSwitchSectionBinder.kt
new file mode 100644
index 00000000..95f233c8
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/ui/binder/WallpaperQuickSwitchSectionBinder.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.ui.binder
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.DimenRes
+import androidx.core.view.doOnLayout
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.wallpaper.R
+import com.android.wallpaper.picker.customization.ui.viewmodel.WallpaperQuickSwitchViewModel
+import kotlinx.coroutines.launch
+
+/** Binds between the view and view-model for the wallpaper quick switch section. */
+object WallpaperQuickSwitchSectionBinder {
+ fun bind(
+ view: View,
+ viewModel: WallpaperQuickSwitchViewModel,
+ lifecycleOwner: LifecycleOwner,
+ onNavigateToFullWallpaperSelector: () -> Unit,
+ ) {
+ view.requireViewById<View>(R.id.more_wallpapers).setOnClickListener {
+ onNavigateToFullWallpaperSelector()
+ }
+
+ val optionContainer: ViewGroup = view.requireViewById(R.id.options)
+ // We have to wait for the container to be laid out before we can bind it because we need
+ // its size to calculate the sizes of the option items.
+ optionContainer.doOnLayout {
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ bindOptions(
+ parent = optionContainer,
+ viewModel = viewModel,
+ lifecycleOwner = lifecycleOwner,
+ )
+ }
+ }
+ }
+ }
+ }
+
+ /** Binds the option items to the given parent. */
+ private suspend fun bindOptions(
+ parent: ViewGroup,
+ viewModel: WallpaperQuickSwitchViewModel,
+ lifecycleOwner: LifecycleOwner,
+ ) {
+ viewModel.options.collect { options ->
+ // Remove all views from a previous update.
+ parent.removeAllViews()
+
+ // Calculate the sizes that views should have.
+ val (largeOptionWidth, smallOptionWidth) = calculateSizes(parent, options.size)
+
+ // Create, add, and bind a view for each option.
+ options.forEach { option ->
+ val optionView =
+ createOptionView(
+ parent = parent,
+ )
+ parent.addView(optionView)
+ WallpaperQuickSwitchOptionBinder.bind(
+ view = optionView,
+ viewModel = option,
+ lifecycleOwner = lifecycleOwner,
+ smallOptionWidthPx = smallOptionWidth,
+ largeOptionWidthPx = largeOptionWidth,
+ )
+ }
+ }
+ }
+
+ /**
+ * Returns a pair where the first value is the width that we should use for the large/selected
+ * option and the second value is the width for the small/non-selected options.
+ */
+ private fun calculateSizes(
+ parent: View,
+ optionCount: Int,
+ ): Pair<Int, Int> {
+ // The large/selected option is a square. Its size should be equal to the height of its
+ // container (with padding removed).
+ val largeOptionWidth = parent.height - parent.paddingTop - parent.paddingBottom
+ // We'll use the total (non-padded) width of the container to figure out the widths of the
+ // small/non-selected options.
+ val optionContainerWidthWithoutPadding =
+ parent.width - parent.paddingStart - parent.paddingEnd
+ // First, we will need the total of the widths of all the spacings between the options.
+ val spacingWidth = parent.dimensionResource(R.dimen.spacing_8dp)
+ val totalMarginWidths = (optionCount - 1) * spacingWidth
+
+ val remainingSpaceForSmallOptions =
+ optionContainerWidthWithoutPadding - largeOptionWidth - totalMarginWidths
+ // One option is always large, the rest are small.
+ val numberOfSmallOptions = optionCount - 1
+ val smallOptionWidth =
+ if (numberOfSmallOptions != 0) {
+ (remainingSpaceForSmallOptions / numberOfSmallOptions).coerceAtMost(
+ parent.dimensionResource(R.dimen.wallpaper_quick_switch_max_option_width)
+ )
+ } else {
+ 0
+ }
+
+ return Pair(largeOptionWidth, smallOptionWidth)
+ }
+
+ /** Returns a new [View] for an option, without attaching it to the view-tree. */
+ private fun createOptionView(
+ parent: ViewGroup,
+ ): View {
+ return LayoutInflater.from(parent.context)
+ .inflate(
+ R.layout.wallpaper_quick_switch_option,
+ parent,
+ false,
+ )
+ }
+
+ /** Compose-inspired cnvenience alias for getting a dimension in pixels. */
+ private fun View.dimensionResource(
+ @DimenRes res: Int,
+ ): Int {
+ return context.resources.getDimensionPixelSize(res)
+ }
+}
diff --git a/src/com/android/wallpaper/picker/customization/ui/section/ConnectedSectionController.kt b/src/com/android/wallpaper/picker/customization/ui/section/ConnectedSectionController.kt
new file mode 100644
index 00000000..aae5edfe
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/ui/section/ConnectedSectionController.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.ui.section
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
+import com.android.wallpaper.R
+import com.android.wallpaper.model.CustomizationSectionController
+import com.android.wallpaper.picker.SectionView
+import java.util.*
+
+/**
+ * A section controller that renders two sections that are connected.
+ *
+ * In portrait mode, they are rendered vertically; in landscape mode, side-by-side.
+ */
+class ConnectedSectionController(
+ /** First section. */
+ private val firstSectionController: CustomizationSectionController<out SectionView>,
+ /** Second section. */
+ private val secondSectionController: CustomizationSectionController<out SectionView>,
+ /** Whether to flip the order of the child sections when laid out horizontally. */
+ private val reverseOrderWhenHorizontal: Boolean = false,
+) : CustomizationSectionController<ResponsiveLayoutSectionView> {
+ override fun isAvailable(context: Context): Boolean {
+ return firstSectionController.isAvailable(context) ||
+ secondSectionController.isAvailable(context)
+ }
+
+ @SuppressLint("InflateParams") // It's okay that we're inflating without a parent view.
+ override fun createView(context: Context): ResponsiveLayoutSectionView {
+ val view =
+ LayoutInflater.from(context)
+ .inflate(
+ R.layout.responsive_section,
+ null,
+ ) as ResponsiveLayoutSectionView
+
+ val isHorizontal = view.orientation == LinearLayout.HORIZONTAL
+ val flipViewOrder = reverseOrderWhenHorizontal && isHorizontal
+
+ add(
+ parentView = view,
+ childController =
+ if (flipViewOrder) {
+ secondSectionController
+ } else {
+ firstSectionController
+ },
+ isHorizontal = isHorizontal,
+ isFirst = true,
+ )
+ add(
+ parentView = view,
+ childController =
+ if (flipViewOrder) {
+ firstSectionController
+ } else {
+ secondSectionController
+ },
+ isHorizontal = isHorizontal,
+ isFirst = false,
+ )
+
+ return view
+ }
+
+ private fun add(
+ parentView: LinearLayout,
+ childController: CustomizationSectionController<out SectionView>,
+ isHorizontal: Boolean,
+ isFirst: Boolean,
+ ) {
+ val childView =
+ childController.createView(
+ context = parentView.context,
+ params =
+ CustomizationSectionController.ViewCreationParams(
+ isConnectedHorizontallyToOtherSections = isHorizontal,
+ ),
+ )
+
+ if (isHorizontal) {
+ // We want each child to stretch to fill an equal amount as the other children.
+ childView.layoutParams =
+ LinearLayout.LayoutParams(
+ /* width= */ 0,
+ /* height= */ LinearLayout.LayoutParams.WRAP_CONTENT,
+ /* weight= */ 1f,
+ )
+
+ val isLeftToRight =
+ TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
+ View.LAYOUT_DIRECTION_LTR
+ childView.setBackgroundResource(
+ if (isLeftToRight) {
+ // In left-to-right layouts, the first item is on the left.
+ if (isFirst) {
+ R.drawable.leftmost_connected_section_background
+ } else {
+ R.drawable.rightmost_connected_section_background
+ }
+ } else {
+ // In right-to-left layouts, the first item is on the right.
+ if (isFirst) {
+ R.drawable.rightmost_connected_section_background
+ } else {
+ R.drawable.leftmost_connected_section_background
+ }
+ }
+ )
+ }
+
+ parentView.addView(childView)
+ }
+}
diff --git a/src/com/android/wallpaper/picker/customization/ui/section/ResponsiveLayoutSectionView.kt b/src/com/android/wallpaper/picker/customization/ui/section/ResponsiveLayoutSectionView.kt
new file mode 100644
index 00000000..52469468
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/ui/section/ResponsiveLayoutSectionView.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.ui.section
+
+import android.content.Context
+import android.util.AttributeSet
+import com.android.wallpaper.picker.SectionView
+
+class ResponsiveLayoutSectionView(
+ context: Context,
+ attrs: AttributeSet?,
+) :
+ SectionView(
+ context,
+ attrs,
+ )
diff --git a/src/com/android/wallpaper/picker/customization/ui/section/ScreenPreviewSectionController.kt b/src/com/android/wallpaper/picker/customization/ui/section/ScreenPreviewSectionController.kt
index 6cb7d068..821c5bd8 100644
--- a/src/com/android/wallpaper/picker/customization/ui/section/ScreenPreviewSectionController.kt
+++ b/src/com/android/wallpaper/picker/customization/ui/section/ScreenPreviewSectionController.kt
@@ -19,19 +19,23 @@ package com.android.wallpaper.picker.customization.ui.section
import android.annotation.SuppressLint
import android.app.Activity
-import android.app.WallpaperColors
import android.content.Context
+import android.os.Bundle
import android.view.LayoutInflater
+import android.view.View
import androidx.cardview.widget.CardView
+import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
+import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
import com.android.wallpaper.R
import com.android.wallpaper.model.CustomizationSectionController
import com.android.wallpaper.model.WallpaperColorsViewModel
import com.android.wallpaper.model.WallpaperInfo
import com.android.wallpaper.module.CurrentWallpaperInfoFactory
import com.android.wallpaper.module.CustomizationSections
+import com.android.wallpaper.picker.CategorySelectorFragment
+import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor
import com.android.wallpaper.picker.customization.ui.binder.ScreenPreviewBinder
import com.android.wallpaper.picker.customization.ui.viewmodel.ScreenPreviewViewModel
import com.android.wallpaper.util.DisplayUtils
@@ -44,19 +48,28 @@ import kotlinx.coroutines.withContext
/** Controls the screen preview section. */
@OptIn(ExperimentalCoroutinesApi::class)
-class ScreenPreviewSectionController(
+open class ScreenPreviewSectionController(
private val activity: Activity,
private val lifecycleOwner: LifecycleOwner,
private val initialScreen: CustomizationSections.Screen,
private val wallpaperInfoFactory: CurrentWallpaperInfoFactory,
private val colorViewModel: WallpaperColorsViewModel,
private val displayUtils: DisplayUtils,
+ private val navigator: CustomizationSectionController.CustomizationSectionNavigationController,
+ private val wallpaperInteractor: WallpaperInteractor,
) : CustomizationSectionController<ScreenPreviewView> {
private lateinit var lockScreenBinding: ScreenPreviewBinder.Binding
private lateinit var homeScreenBinding: ScreenPreviewBinder.Binding
- override fun isAvailable(context: Context?): Boolean {
+ /** Override to hide the lock screen clock preview. */
+ open val hideLockScreenClockPreview = false
+
+ override fun shouldRetainInstanceWhenSwitchingTabs(): Boolean {
+ return true
+ }
+
+ override fun isAvailable(context: Context): Boolean {
// Assumption is that, if this section controller is included, we are using the revamped UI
// so it should always be shown.
return true
@@ -70,6 +83,9 @@ class ScreenPreviewSectionController(
R.layout.screen_preview_section,
/* parent= */ null,
) as ScreenPreviewView
+ val onClickListener =
+ View.OnClickListener { navigator.navigateTo(CategorySelectorFragment()) }
+ view.setOnClickListener(onClickListener)
val lockScreenView: CardView = view.requireViewById(R.id.lock_preview)
val homeScreenView: CardView = view.requireViewById(R.id.home_preview)
@@ -95,7 +111,7 @@ class ScreenPreviewSectionController(
loadInitialColors(
context = context,
wallpaper = wallpaper,
- liveData = colorViewModel.lockWallpaperColors,
+ screen = CustomizationSections.Screen.LOCK_SCREEN,
)
continuation.resume(wallpaper, null)
},
@@ -104,11 +120,29 @@ class ScreenPreviewSectionController(
}
},
onWallpaperColorChanged = { colors ->
- colorViewModel.lockWallpaperColors.value = colors
+ colorViewModel.setLockWallpaperColors(colors)
+ },
+ initialExtrasProvider = {
+ Bundle().apply {
+ // Hide the clock from the system UI rendered preview so we can
+ // place the carousel on top of it.
+ putBoolean(
+ ClockPreviewConstants.KEY_HIDE_CLOCK,
+ hideLockScreenClockPreview,
+ )
+ }
},
+ wallpaperInteractor = wallpaperInteractor,
),
lifecycleOwner = lifecycleOwner,
- offsetToStart = displayUtils.isOnWallpaperDisplay(activity),
+ offsetToStart = displayUtils.isSingleDisplayOrUnfoldedHorizontalHinge(activity),
+ screen = CustomizationSections.Screen.LOCK_SCREEN,
+ onPreviewDirty = {
+ // only the visible binding should recreate the activity so it's not done twice
+ if (lockScreenView.isVisible) {
+ activity.recreate()
+ }
+ },
)
homeScreenBinding =
ScreenPreviewBinder.bind(
@@ -132,7 +166,7 @@ class ScreenPreviewSectionController(
loadInitialColors(
context = context,
wallpaper = wallpaper,
- liveData = colorViewModel.homeWallpaperColors,
+ screen = CustomizationSections.Screen.HOME_SCREEN
)
continuation.resume(wallpaper, null)
},
@@ -141,11 +175,19 @@ class ScreenPreviewSectionController(
}
},
onWallpaperColorChanged = { colors ->
- colorViewModel.lockWallpaperColors.value = colors
+ colorViewModel.setHomeWallpaperColors(colors)
},
+ wallpaperInteractor = wallpaperInteractor,
),
lifecycleOwner = lifecycleOwner,
- offsetToStart = displayUtils.isOnWallpaperDisplay(activity),
+ offsetToStart = displayUtils.isSingleDisplayOrUnfoldedHorizontalHinge(activity),
+ screen = CustomizationSections.Screen.HOME_SCREEN,
+ onPreviewDirty = {
+ // only the visible binding should recreate the activity so it's not done twice
+ if (homeScreenView.isVisible) {
+ activity.recreate()
+ }
+ },
)
onScreenSwitched(isOnLockScreen = initialScreen == CustomizationSections.Screen.LOCK_SCREEN)
@@ -166,11 +208,19 @@ class ScreenPreviewSectionController(
private fun loadInitialColors(
context: Context,
wallpaper: WallpaperInfo?,
- liveData: MutableLiveData<WallpaperColors>,
+ screen: CustomizationSections.Screen,
) {
lifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
val colors = wallpaper?.computeColorInfo(context)?.get()?.wallpaperColors
- withContext(Dispatchers.Main) { liveData.value = colors }
+ withContext(Dispatchers.Main) {
+ if (colors != null) {
+ if (screen == CustomizationSections.Screen.LOCK_SCREEN) {
+ colorViewModel.setLockWallpaperColors(colors)
+ } else {
+ colorViewModel.setHomeWallpaperColors(colors)
+ }
+ }
+ }
}
}
}
diff --git a/src/com/android/wallpaper/picker/customization/ui/section/ScreenPreviewView.kt b/src/com/android/wallpaper/picker/customization/ui/section/ScreenPreviewView.kt
index ee55de41..c9e8022b 100644
--- a/src/com/android/wallpaper/picker/customization/ui/section/ScreenPreviewView.kt
+++ b/src/com/android/wallpaper/picker/customization/ui/section/ScreenPreviewView.kt
@@ -19,7 +19,11 @@ package com.android.wallpaper.picker.customization.ui.section
import android.content.Context
import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.ViewConfiguration
import com.android.wallpaper.picker.SectionView
+import kotlin.math.pow
+import kotlin.math.sqrt
class ScreenPreviewView(
context: Context,
@@ -28,4 +32,57 @@ class ScreenPreviewView(
SectionView(
context,
attrs,
- )
+ ) {
+
+ private var downX = 0f
+ private var downY = 0f
+
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ downX = event.x
+ downY = event.y
+ }
+
+ // We want to intercept clicks so the Carousel MotionLayout child doesn't prevent users from
+ // clicking on the screen preview.
+ if (isClick(event, downX, downY)) {
+ return performClick()
+ }
+
+ return super.onInterceptTouchEvent(event)
+ }
+
+ companion object {
+ private fun isClick(event: MotionEvent, downX: Float, downY: Float): Boolean {
+ return when {
+ // It's not a click if the event is not an UP action (though it may become one
+ // later, when/if an UP is received).
+ event.actionMasked != MotionEvent.ACTION_UP -> false
+ // It's not a click if too much time has passed between the down and the current
+ // event.
+ gestureElapsedTime(event) > ViewConfiguration.getTapTimeout() -> false
+ // It's not a click if the touch traveled too far.
+ distanceMoved(event, downX, downY) > ViewConfiguration.getTouchSlop() -> false
+ // Otherwise, this is a click!
+ else -> true
+ }
+ }
+
+ /**
+ * Returns the distance that the pointer traveled in the touch gesture the given event is
+ * part of.
+ */
+ private fun distanceMoved(event: MotionEvent, downX: Float, downY: Float): Float {
+ val deltaX = event.x - downX
+ val deltaY = event.y - downY
+ return sqrt(deltaX.pow(2) + deltaY.pow(2))
+ }
+
+ /**
+ * Returns the elapsed time since the touch gesture the given event is part of has begun.
+ */
+ private fun gestureElapsedTime(event: MotionEvent): Long {
+ return event.eventTime - event.downTime
+ }
+ }
+}
diff --git a/src/com/android/wallpaper/picker/customization/ui/section/WallpaperQuickSwitchSectionController.kt b/src/com/android/wallpaper/picker/customization/ui/section/WallpaperQuickSwitchSectionController.kt
new file mode 100644
index 00000000..d54e9c0e
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/ui/section/WallpaperQuickSwitchSectionController.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.ui.section
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.LayoutInflater
+import androidx.lifecycle.LifecycleOwner
+import com.android.wallpaper.R
+import com.android.wallpaper.model.CustomizationSectionController
+import com.android.wallpaper.module.CustomizationSections
+import com.android.wallpaper.picker.CategorySelectorFragment
+import com.android.wallpaper.picker.customization.ui.binder.WallpaperQuickSwitchSectionBinder
+import com.android.wallpaper.picker.customization.ui.viewmodel.WallpaperQuickSwitchViewModel
+
+/** Controls a section that lets the user switch wallpapers quickly. */
+class WallpaperQuickSwitchSectionController(
+ private val screen: CustomizationSections.Screen,
+ private val viewModel: WallpaperQuickSwitchViewModel,
+ private val lifecycleOwner: LifecycleOwner,
+ private val navigator: CustomizationSectionController.CustomizationSectionNavigationController,
+) : CustomizationSectionController<WallpaperQuickSwitchView> {
+
+ override fun isAvailable(context: Context): Boolean {
+ return true
+ }
+
+ @SuppressLint("InflateParams") // We don't care that the parent is null.
+ override fun createView(context: Context): WallpaperQuickSwitchView {
+ val view =
+ LayoutInflater.from(context)
+ .inflate(
+ R.layout.wallpaper_quick_switch_section,
+ /* parent= */ null,
+ ) as WallpaperQuickSwitchView
+ viewModel.setOnLockScreen(
+ isLockScreenSelected = screen == CustomizationSections.Screen.LOCK_SCREEN,
+ )
+ WallpaperQuickSwitchSectionBinder.bind(
+ view = view,
+ viewModel = viewModel,
+ lifecycleOwner = lifecycleOwner,
+ onNavigateToFullWallpaperSelector = {
+ navigator.navigateTo(CategorySelectorFragment())
+ },
+ )
+ return view
+ }
+
+ override fun onScreenSwitched(isOnLockScreen: Boolean) {
+ viewModel.setOnLockScreen(isLockScreenSelected = isOnLockScreen)
+ }
+}
diff --git a/src/com/android/wallpaper/picker/customization/ui/section/WallpaperQuickSwitchView.kt b/src/com/android/wallpaper/picker/customization/ui/section/WallpaperQuickSwitchView.kt
new file mode 100644
index 00000000..5a23f09e
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/ui/section/WallpaperQuickSwitchView.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.ui.section
+
+import android.content.Context
+import android.util.AttributeSet
+import com.android.wallpaper.picker.SectionView
+
+class WallpaperQuickSwitchView(
+ context: Context,
+ attrs: AttributeSet?,
+) :
+ SectionView(
+ context,
+ attrs,
+ )
diff --git a/src/com/android/wallpaper/picker/customization/ui/viewmodel/CustomizationPickerViewModel.kt b/src/com/android/wallpaper/picker/customization/ui/viewmodel/CustomizationPickerViewModel.kt
index 7fd622cb..14c0715d 100644
--- a/src/com/android/wallpaper/picker/customization/ui/viewmodel/CustomizationPickerViewModel.kt
+++ b/src/com/android/wallpaper/picker/customization/ui/viewmodel/CustomizationPickerViewModel.kt
@@ -81,7 +81,22 @@ constructor(
}
init {
- _isOnLockScreen.value = savedStateHandle[KEY_SAVED_STATE_IS_ON_LOCK_SCREEN] ?: true
+ savedStateHandle.get<Boolean>(KEY_SAVED_STATE_IS_ON_LOCK_SCREEN)?.let {
+ _isOnLockScreen.value = it
+ }
+ }
+
+ /**
+ * Sets the initial screen we should be on, unless there's already a selected screen from a
+ * previous saved state, in which case we ignore the passed-in one.
+ */
+ fun setInitialScreen(onLockScreen: Boolean) {
+ _isOnLockScreen.value =
+ savedStateHandle[KEY_SAVED_STATE_IS_ON_LOCK_SCREEN]
+ ?: run {
+ savedStateHandle[KEY_SAVED_STATE_IS_ON_LOCK_SCREEN] = onLockScreen
+ onLockScreen
+ }
}
companion object {
diff --git a/src/com/android/wallpaper/picker/customization/ui/viewmodel/ScreenPreviewViewModel.kt b/src/com/android/wallpaper/picker/customization/ui/viewmodel/ScreenPreviewViewModel.kt
index 79e18a38..1ceaa80b 100644
--- a/src/com/android/wallpaper/picker/customization/ui/viewmodel/ScreenPreviewViewModel.kt
+++ b/src/com/android/wallpaper/picker/customization/ui/viewmodel/ScreenPreviewViewModel.kt
@@ -20,7 +20,11 @@ package com.android.wallpaper.picker.customization.ui.viewmodel
import android.app.WallpaperColors
import android.os.Bundle
import com.android.wallpaper.model.WallpaperInfo
+import com.android.wallpaper.module.CustomizationSections
+import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor
+import com.android.wallpaper.picker.customization.shared.model.WallpaperModel
import com.android.wallpaper.util.PreviewUtils
+import kotlinx.coroutines.flow.Flow
/** Models the UI state for a preview of the home screen or lock screen. */
class ScreenPreviewViewModel(
@@ -28,7 +32,19 @@ class ScreenPreviewViewModel(
private val initialExtrasProvider: () -> Bundle? = { null },
private val wallpaperInfoProvider: suspend () -> WallpaperInfo?,
private val onWallpaperColorChanged: (WallpaperColors?) -> Unit = {},
+ // TODO (b/270193793): add below field to all usages, remove default value & make non-nullable
+ private val wallpaperInteractor: WallpaperInteractor? = null,
) {
+ /** Returns whether wallpaper picker should handle reload */
+ fun shouldHandleReload(): Boolean {
+ return wallpaperInteractor?.let { it.shouldHandleReload() } ?: true
+ }
+
+ /** Returns a flow that is updated whenever the wallpaper has been updated */
+ fun wallpaperUpdateEvents(screen: CustomizationSections.Screen): Flow<WallpaperModel>? {
+ return wallpaperInteractor?.wallpaperUpdateEvents(screen)
+ }
+
fun getInitialExtras(): Bundle? {
return initialExtrasProvider.invoke()
}
diff --git a/src/com/android/wallpaper/picker/customization/ui/viewmodel/WallpaperQuickSwitchOptionViewModel.kt b/src/com/android/wallpaper/picker/customization/ui/viewmodel/WallpaperQuickSwitchOptionViewModel.kt
new file mode 100644
index 00000000..c8cb2fde
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/ui/viewmodel/WallpaperQuickSwitchOptionViewModel.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.ui.viewmodel
+
+import android.graphics.Bitmap
+import kotlinx.coroutines.flow.Flow
+
+/** Models the UI state for an option in the wallpaper quick switcher. */
+data class WallpaperQuickSwitchOptionViewModel(
+ /** The ID of the wallpaper this option is associated with. */
+ val wallpaperId: String,
+ /** A placeholder color to show in the option while we load the preview thumbnail. */
+ val placeholderColor: Int,
+ /** A function to invoke to get the preview thumbnail for the option. */
+ val thumbnail: suspend () -> Bitmap?,
+ /**
+ * Whether the option should be rendered as large. If `false`, the option should be rendered
+ * smaller.
+ */
+ val isLarge: Flow<Boolean>,
+ /** Whether the progress indicator should be visible. */
+ val isProgressIndicatorVisible: Flow<Boolean>,
+ /** Whether the selection border should be visible. */
+ val isSelectionBorderVisible: Flow<Boolean>,
+ /** Whether the selection icon should be visible. */
+ val isSelectionIconVisible: Flow<Boolean>,
+ /**
+ * A function to invoke when the option is clicked by the user. If `null`, the option is not
+ * clickable.
+ */
+ val onSelected: Flow<(() -> Unit)?>,
+)
diff --git a/src/com/android/wallpaper/picker/customization/ui/viewmodel/WallpaperQuickSwitchViewModel.kt b/src/com/android/wallpaper/picker/customization/ui/viewmodel/WallpaperQuickSwitchViewModel.kt
new file mode 100644
index 00000000..105dcff0
--- /dev/null
+++ b/src/com/android/wallpaper/picker/customization/ui/viewmodel/WallpaperQuickSwitchViewModel.kt
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.customization.ui.viewmodel
+
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.AbstractSavedStateViewModelFactory
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.savedstate.SavedStateRegistryOwner
+import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor
+import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+
+/** Models UI state for views that can render wallpaper quick switching. */
+@OptIn(ExperimentalCoroutinesApi::class)
+class WallpaperQuickSwitchViewModel
+@VisibleForTesting
+constructor(
+ private val interactor: WallpaperInteractor,
+ maxOptions: Int,
+) : ViewModel() {
+ private val isLockScreenSelected = MutableStateFlow(false)
+
+ private val selectedWallpaperId: Flow<String> =
+ isLockScreenSelected
+ .flatMapLatest { isOnLockScreen ->
+ interactor.selectedWallpaperId(
+ destination =
+ if (isOnLockScreen) {
+ WallpaperDestination.LOCK
+ } else {
+ WallpaperDestination.HOME
+ },
+ )
+ }
+ .shareIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
+ private val selectingWallpaperId: Flow<String?> =
+ isLockScreenSelected
+ .flatMapLatest { isOnLockScreen ->
+ interactor.selectingWallpaperId(
+ destination =
+ if (isOnLockScreen) {
+ WallpaperDestination.LOCK
+ } else {
+ WallpaperDestination.HOME
+ },
+ )
+ }
+ .shareIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
+
+ val options: Flow<List<WallpaperQuickSwitchOptionViewModel>> =
+ isLockScreenSelected
+ .flatMapLatest { isOnLockScreen ->
+ interactor
+ .previews(
+ destination =
+ if (isOnLockScreen) {
+ WallpaperDestination.LOCK
+ } else {
+ WallpaperDestination.HOME
+ },
+ maxResults = maxOptions,
+ )
+ .distinctUntilChangedBy { previews ->
+ // Produce a key that's the same if the same set of wallpapers is available,
+ // even if in a different order. This is so that the view can keep from
+ // moving the wallpaper options around when the sort order changes as the
+ // user selects different wallpapers.
+ previews.map { preview -> preview.wallpaperId }.sorted().joinToString(",")
+ }
+ .map { previews ->
+ // True if any option is becoming selected following user click.
+ val isSomethingBecomingSelectedFlow: Flow<Boolean> =
+ selectingWallpaperId.distinctUntilChanged().map { it != null }
+
+ previews.map { preview ->
+ // True if this option is currently selected.
+ val isSelectedFlow: Flow<Boolean> =
+ selectedWallpaperId.distinctUntilChanged().map {
+ it == preview.wallpaperId
+ }
+ // True if this option is becoming the selected one following user
+ // click.
+ val isBecomingSelectedFlow: Flow<Boolean> =
+ selectingWallpaperId.distinctUntilChanged().map {
+ it == preview.wallpaperId
+ }
+
+ WallpaperQuickSwitchOptionViewModel(
+ wallpaperId = preview.wallpaperId,
+ placeholderColor = preview.placeholderColor,
+ thumbnail = {
+ interactor.loadThumbnail(
+ wallpaperId = preview.wallpaperId,
+ )
+ },
+ isLarge =
+ combine(
+ isSelectedFlow,
+ isBecomingSelectedFlow,
+ isSomethingBecomingSelectedFlow,
+ ) { isSelected, isBecomingSelected, isSomethingBecomingSelected
+ ->
+ // The large option is the one that's currently selected or
+ // the one that is becoming the selected one following user
+ // click.
+ (isSelected && !isSomethingBecomingSelected) ||
+ isBecomingSelected
+ },
+ // We show the progress indicator if the option is in the process of
+ // becoming the selected one following user click.
+ isProgressIndicatorVisible = isBecomingSelectedFlow,
+ isSelectionBorderVisible =
+ combine(
+ isSelectedFlow,
+ isBecomingSelectedFlow,
+ isSomethingBecomingSelectedFlow,
+ ) { isSelected, isBeingSelected, isSomethingBecomingSelected ->
+ // The selection border is shown for the option that is the
+ // one that's currently selected or the one that is becoming
+ // the selected one following user click.
+ (isSelected && !isSomethingBecomingSelected) ||
+ isBeingSelected
+ },
+ isSelectionIconVisible =
+ combine(
+ isSelectedFlow,
+ isSomethingBecomingSelectedFlow,
+ ) { isSelected, isSomethingBecomingSelected ->
+ // The selection icon is shown for the option that is
+ // currently selected but only if nothing else is becoming
+ // selected. If anything is being selected following user
+ // click, the selection icon is not shown on any option.
+ isSelected && !isSomethingBecomingSelected
+ },
+ onSelected =
+ combine(
+ isSelectedFlow,
+ isBecomingSelectedFlow,
+ isSomethingBecomingSelectedFlow,
+ ) { isSelected, isBeingSelected, isSomethingBecomingSelected
+ ->
+ // An option is selectable if it is not itself becoming
+ // selected following user click or if nothing else is
+ // becoming selected but this option is not the selected
+ // one.
+ (isSomethingBecomingSelected && !isBeingSelected) ||
+ (!isSomethingBecomingSelected && !isSelected)
+ }
+ .distinctUntilChanged()
+ .map { isSelectable ->
+ if (isSelectable) {
+ {
+ // A selectable option can become selected.
+ viewModelScope.launch {
+ interactor.setWallpaper(
+ destination =
+ if (isOnLockScreen) {
+ WallpaperDestination.LOCK
+ } else {
+ WallpaperDestination.HOME
+ },
+ wallpaperId = preview.wallpaperId,
+ )
+ }
+ }
+ } else {
+ // A non-selectable option cannot become selected.
+ null
+ }
+ }
+ )
+ }
+ }
+ }
+ .shareIn(
+ scope = viewModelScope,
+ started = SharingStarted.Lazily,
+ replay = 1,
+ )
+
+ fun setOnLockScreen(isLockScreenSelected: Boolean) {
+ this.isLockScreenSelected.value = isLockScreenSelected
+ }
+
+ companion object {
+ @JvmStatic
+ fun newFactory(
+ owner: SavedStateRegistryOwner,
+ defaultArgs: Bundle? = null,
+ interactor: WallpaperInteractor,
+ ): AbstractSavedStateViewModelFactory =
+ object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : ViewModel> create(
+ key: String,
+ modelClass: Class<T>,
+ handle: SavedStateHandle,
+ ): T {
+ return WallpaperQuickSwitchViewModel(
+ interactor = interactor,
+ maxOptions = MAX_OPTIONS,
+ )
+ as T
+ }
+ }
+
+ /** The maximum number of options to show, including the currently-selected one. */
+ private const val MAX_OPTIONS = 5
+ }
+}
diff --git a/src/com/android/wallpaper/picker/individual/IndividualHolder.java b/src/com/android/wallpaper/picker/individual/IndividualHolder.java
index c780db1e..feff2b8d 100755
--- a/src/com/android/wallpaper/picker/individual/IndividualHolder.java
+++ b/src/com/android/wallpaper/picker/individual/IndividualHolder.java
@@ -70,7 +70,9 @@ abstract class IndividualHolder extends ViewHolder {
mTitleView.setVisibility(View.VISIBLE);
mTileLayout.setContentDescription(title);
} else if (firstAttribution != null) {
- mTileLayout.setContentDescription(firstAttribution);
+ String contentDescription = wallpaper.getContentDescription(mActivity);
+ mTileLayout.setContentDescription(
+ contentDescription != null ? contentDescription : firstAttribution);
}
Drawable overlayIcon = wallpaper.getOverlayIcon(mActivity);
diff --git a/src/com/android/wallpaper/picker/individual/IndividualPickerFragment2.kt b/src/com/android/wallpaper/picker/individual/IndividualPickerFragment2.kt
index 4a21dbc2..3186eb5f 100644
--- a/src/com/android/wallpaper/picker/individual/IndividualPickerFragment2.kt
+++ b/src/com/android/wallpaper/picker/individual/IndividualPickerFragment2.kt
@@ -43,12 +43,14 @@ import androidx.annotation.DrawableRes
import androidx.cardview.widget.CardView
import androidx.core.widget.ContentLoadingProgressBar
import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.wallpaper.R
import com.android.wallpaper.model.Category
import com.android.wallpaper.model.CategoryProvider
import com.android.wallpaper.model.CategoryReceiver
+import com.android.wallpaper.model.LiveWallpaperInfo
import com.android.wallpaper.model.WallpaperCategory
import com.android.wallpaper.model.WallpaperInfo
import com.android.wallpaper.model.WallpaperRotationInitializer
@@ -71,6 +73,8 @@ import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDele
import com.bumptech.glide.Glide
import com.bumptech.glide.MemoryCategory
import java.util.Date
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
/** Displays the Main UI for picking an individual wallpaper image. */
class IndividualPickerFragment2 :
@@ -109,18 +113,20 @@ class IndividualPickerFragment2 :
private lateinit var imageGrid: RecyclerView
private var adapter: IndividualAdapter? = null
- private lateinit var category: WallpaperCategory
+ private var category: WallpaperCategory? = null
private var wallpaperRotationInitializer: WallpaperRotationInitializer? = null
private lateinit var items: MutableList<PickerItem>
private var packageStatusNotifier: PackageStatusNotifier? = null
-
private var isWallpapersReceived = false
- private var appStatusListener: PackageStatusNotifier.Listener? = null
+ private var appStatusListener: PackageStatusNotifier.Listener? = null
private var progressDialog: ProgressDialog? = null
+
private var testingMode = false
private var loading: ContentLoadingProgressBar? = null
+ private var shouldReloadWallpapers = false
private lateinit var categoryProvider: CategoryProvider
+ private var appliedWallpaperIds: Set<String> = setOf()
/**
* Staged error dialog fragments that were unable to be shown when the activity didn't allow
@@ -177,14 +183,14 @@ class IndividualPickerFragment2 :
return
}
category = fetchedCategory as WallpaperCategory
- onCategoryLoaded()
+ category?.let { onCategoryLoaded(it) }
}
},
false
)
}
- fun onCategoryLoaded() {
+ fun onCategoryLoaded(category: Category) {
val fragmentHost = getIndividualPickerFragmentHost()
if (fragmentHost.isHostToolbarShown) {
fragmentHost.setToolbarTitle(category.title)
@@ -195,22 +201,12 @@ class IndividualPickerFragment2 :
if (mToolbar != null && isRotationEnabled()) {
setUpToolbarMenu(R.menu.individual_picker_menu)
}
- fetchWallpapers(false)
+ var shouldForceReload = false
if (category.supportsThirdParty()) {
- appStatusListener =
- PackageStatusNotifier.Listener { pkgName: String?, status: Int ->
- if (
- status != PackageStatusNotifier.PackageStatus.REMOVED ||
- category.containsThirdParty(pkgName)
- ) {
- fetchWallpapers(true)
- }
- }
- packageStatusNotifier?.addListener(
- appStatusListener,
- WallpaperService.SERVICE_INTERFACE
- )
+ shouldForceReload = true
}
+ fetchWallpapers(shouldForceReload)
+ registerPackageListener(category)
}
private fun fetchWallpapers(forceReload: Boolean) {
@@ -218,13 +214,13 @@ class IndividualPickerFragment2 :
isWallpapersReceived = false
updateLoading()
val context = requireContext()
- category.fetchWallpapers(
+ category?.fetchWallpapers(
context.applicationContext,
{ fetchedWallpapers ->
isWallpapersReceived = true
updateLoading()
val byGroup = fetchedWallpapers.groupBy { it.getGroupName(context) }
- val appliedWallpaperIds = getAppliedWallpaperIds()
+ appliedWallpaperIds = getAppliedWallpaperIds()
byGroup.forEach { (groupName, wallpapers) ->
if (!TextUtils.isEmpty(groupName)) {
items.add(
@@ -235,12 +231,16 @@ class IndividualPickerFragment2 :
}
)
}
+ val currentWallpaper = WallpaperManager.getInstance(context).wallpaperInfo
items.addAll(
wallpapers.map {
- PickerItem.WallpaperItem(
- it,
- appliedWallpaperIds.contains(it.wallpaperId)
- )
+ val isApplied =
+ if (it is LiveWallpaperInfo) {
+ it.isApplied(currentWallpaper)
+ } else {
+ appliedWallpaperIds.contains(it.wallpaperId)
+ }
+ PickerItem.WallpaperItem(it, isApplied)
}
)
}
@@ -260,6 +260,24 @@ class IndividualPickerFragment2 :
)
}
+ private fun registerPackageListener(category: Category) {
+ if (category.supportsThirdParty()) {
+ appStatusListener =
+ PackageStatusNotifier.Listener { pkgName: String?, status: Int ->
+ if (
+ status != PackageStatusNotifier.PackageStatus.REMOVED ||
+ category.containsThirdParty(pkgName)
+ ) {
+ fetchWallpapers(true)
+ }
+ }
+ packageStatusNotifier?.addListener(
+ appStatusListener,
+ WallpaperService.SERVICE_INTERFACE
+ )
+ }
+ }
+
private fun updateLoading() {
if (isWallpapersReceived) {
loading?.hide()
@@ -293,7 +311,7 @@ class IndividualPickerFragment2 :
if (isRotationEnabled()) {
setUpToolbarMenu(R.menu.individual_picker_menu)
}
- setTitle(category.title)
+ setTitle(category?.title)
}
imageGrid = view.findViewById<View>(R.id.wallpaper_grid) as RecyclerView
loading = view.findViewById(R.id.loading_indicator)
@@ -328,7 +346,7 @@ class IndividualPickerFragment2 :
return
}
// Skip if category hasn't loaded yet
- if (!this::category.isInitialized) {
+ if (category == null) {
return
}
if (context == null) {
@@ -365,7 +383,7 @@ class IndividualPickerFragment2 :
} else {
SizeCalculator.getIndividualTileSize(activity!!)
}
- setUpImageGrid(tileSizePx)
+ setUpImageGrid(tileSizePx, checkNotNull(category))
imageGrid.setAccessibilityDelegateCompat(
WallpaperPickerRecyclerViewAccessibilityDelegate(
imageGrid,
@@ -408,7 +426,7 @@ class IndividualPickerFragment2 :
* Create the adapter and assign it to mImageGrid. Both mImageGrid and mCategory are guaranteed
* to not be null when this method is called.
*/
- private fun setUpImageGrid(tileSizePx: Point) {
+ private fun setUpImageGrid(tileSizePx: Point, category: Category) {
adapter =
IndividualAdapter(
items,
@@ -435,6 +453,14 @@ class IndividualPickerFragment2 :
imageGrid.layoutManager = gridLayoutManager
}
+ private suspend fun fetchWallpapersIfNeeded() {
+ coroutineScope {
+ if (isWallpapersReceived && (shouldReloadWallpapers || isAppliedWallpaperChanged())) {
+ fetchWallpapers(true)
+ }
+ }
+ }
+
override fun onResume() {
super.onResume()
val preferences = InjectorProvider.getInjector().getPreferences(requireActivity())
@@ -452,10 +478,16 @@ class IndividualPickerFragment2 :
parentFragmentManager,
TAG_START_ROTATION_ERROR_DIALOG
)
+ lifecycleScope.launch { fetchWallpapersIfNeeded() }
}
stagedStartRotationErrorDialogFragment = null
}
+ override fun onPause() {
+ shouldReloadWallpapers = category?.supportsWallpaperSetUpdates() ?: false
+ super.onPause()
+ }
+
override fun onDestroyView() {
super.onDestroyView()
getIndividualPickerFragmentHost().removeToolbarMenu()
@@ -493,7 +525,7 @@ class IndividualPickerFragment2 :
override fun startRotation(@NetworkPreference networkPreference: Int) {
if (!isRotationEnabled()) {
- Log.e(TAG, "Rotation is not enabled for this category " + category.title)
+ Log.e(TAG, "Rotation is not enabled for this category " + category?.title)
return
}
@@ -628,6 +660,17 @@ class IndividualPickerFragment2 :
return appliedWallpaperIds
}
+ // TODO(b/277180178): Extract the check to another class for unit testing
+ private fun isAppliedWallpaperChanged(): Boolean {
+ // Reload wallpapers if the current wallpapers have changed
+ getAppliedWallpaperIds().let {
+ if (appliedWallpaperIds.isEmpty() || appliedWallpaperIds != it) {
+ return true
+ }
+ }
+ return false
+ }
+
sealed class PickerItem(val title: CharSequence = "") {
class WallpaperItem(val wallpaperInfo: WallpaperInfo, val isApplied: Boolean) :
PickerItem()
diff --git a/src/com/android/wallpaper/picker/option/ui/adapter/OptionItemAdapter.kt b/src/com/android/wallpaper/picker/option/ui/adapter/OptionItemAdapter.kt
new file mode 100644
index 00000000..150dc42c
--- /dev/null
+++ b/src/com/android/wallpaper/picker/option/ui/adapter/OptionItemAdapter.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.option.ui.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.RecyclerView
+import com.android.wallpaper.R
+import com.android.wallpaper.picker.option.ui.binder.OptionItemBinder
+import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/** Adapts between option items and their views. */
+class OptionItemAdapter<T>(
+ @LayoutRes private val layoutResourceId: Int,
+ private val lifecycleOwner: LifecycleOwner,
+ private val backgroundDispatcher: CoroutineDispatcher = Dispatchers.IO,
+ private val foregroundTintSpec: OptionItemBinder.TintSpec? = null,
+ private val bindIcon: (View, T) -> Unit,
+) : RecyclerView.Adapter<OptionItemAdapter.ViewHolder>() {
+
+ private val items = mutableListOf<OptionItemViewModel<T>>()
+
+ fun setItems(items: List<OptionItemViewModel<T>>) {
+ lifecycleOwner.lifecycleScope.launch {
+ val oldItems = this@OptionItemAdapter.items
+ val newItems = items
+ val diffResult =
+ withContext(backgroundDispatcher) {
+ DiffUtil.calculateDiff(
+ object : DiffUtil.Callback() {
+ override fun getOldListSize(): Int {
+ return oldItems.size
+ }
+
+ override fun getNewListSize(): Int {
+ return newItems.size
+ }
+
+ override fun areItemsTheSame(
+ oldItemPosition: Int,
+ newItemPosition: Int
+ ): Boolean {
+ val oldItem = oldItems[oldItemPosition]
+ val newItem = newItems[newItemPosition]
+ return oldItem.key.value == newItem.key.value
+ }
+
+ override fun areContentsTheSame(
+ oldItemPosition: Int,
+ newItemPosition: Int
+ ): Boolean {
+ val oldItem = oldItems[oldItemPosition]
+ val newItem = newItems[newItemPosition]
+ return oldItem == newItem
+ }
+ },
+ /* detectMoves= */ false,
+ )
+ }
+
+ oldItems.clear()
+ oldItems.addAll(items)
+ diffResult.dispatchUpdatesTo(this@OptionItemAdapter)
+ }
+ }
+
+ class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ var disposableHandle: DisposableHandle? = null
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(parent.context)
+ .inflate(
+ layoutResourceId,
+ parent,
+ false,
+ )
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.disposableHandle?.dispose()
+ val item = items[position]
+ item.payload?.let {
+ bindIcon(holder.itemView.requireViewById(R.id.foreground), item.payload)
+ }
+ holder.disposableHandle =
+ OptionItemBinder.bind(
+ view = holder.itemView,
+ viewModel = item,
+ lifecycleOwner = lifecycleOwner,
+ foregroundTintSpec = foregroundTintSpec,
+ )
+ }
+}
diff --git a/src/com/android/wallpaper/picker/option/ui/binder/OptionItemBinder.kt b/src/com/android/wallpaper/picker/option/ui/binder/OptionItemBinder.kt
new file mode 100644
index 00000000..802fc62c
--- /dev/null
+++ b/src/com/android/wallpaper/picker/option/ui/binder/OptionItemBinder.kt
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2023 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.
+ *
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.wallpaper.picker.option.ui.binder
+
+import android.view.View
+import android.view.ViewPropertyAnimator
+import android.view.animation.LinearInterpolator
+import android.view.animation.PathInterpolator
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.annotation.ColorInt
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.wallpaper.R
+import com.android.wallpaper.picker.common.icon.ui.viewbinder.ContentDescriptionViewBinder
+import com.android.wallpaper.picker.common.text.ui.viewbinder.TextViewBinder
+import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.launch
+
+object OptionItemBinder {
+ /**
+ * Binds the given [View] to the given [OptionItemViewModel].
+ *
+ * The child views of [view] must be named and arranged in the following manner, from top of the
+ * z-axis to the bottom:
+ * - [R.id.foreground] is the foreground drawable ([ImageView]).
+ * - [R.id.background] is the view in the background ([View]).
+ * - [R.id.selection_border] is a view rendering a border. It must have the same exact size as
+ * [R.id.background] ([View]) and must be placed below it on the z-axis (you read that right).
+ *
+ * The animation logic in this binder takes care of scaling up the border at the right time to
+ * help it peek out around the background. In order to allow for this, you may need to disable
+ * the clipping of child views across the view-tree using:
+ * ```
+ * android:clipChildren="false"
+ * ```
+ *
+ * Optionally, there may be an [R.id.text] [TextView] to show the text from the view-model. If
+ * one is not supplied, the text will be used as the content description of the icon.
+ *
+ * @param view The view; it must contain the child views described above.
+ * @param viewModel The view-model.
+ * @param lifecycleOwner The [LifecycleOwner].
+ * @param animationSpec The specification for the animation.
+ * @param foregroundTintSpec The specification of how to tint the foreground icons.
+ * @return A [DisposableHandle] that must be invoked when the view is recycled.
+ */
+ fun bind(
+ view: View,
+ viewModel: OptionItemViewModel<*>,
+ lifecycleOwner: LifecycleOwner,
+ animationSpec: AnimationSpec = AnimationSpec(),
+ foregroundTintSpec: TintSpec? = null,
+ ): DisposableHandle {
+ val borderView: View = view.requireViewById(R.id.selection_border)
+ val backgroundView: View = view.requireViewById(R.id.background)
+ val foregroundView: View = view.requireViewById(R.id.foreground)
+ val textView: TextView? = view.findViewById(R.id.text)
+
+ if (textView != null) {
+ TextViewBinder.bind(
+ view = textView,
+ viewModel = viewModel.text,
+ )
+ } else {
+ // Use the text as the content description of the foreground if we don't have a TextView
+ // dedicated to for the text.
+ ContentDescriptionViewBinder.bind(
+ view = foregroundView,
+ viewModel = viewModel.text,
+ )
+ }
+ view.alpha =
+ if (viewModel.isEnabled) {
+ animationSpec.enabledAlpha
+ } else {
+ animationSpec.disabledAlpha
+ }
+ view.onLongClickListener =
+ if (viewModel.onLongClicked != null) {
+ View.OnLongClickListener {
+ viewModel.onLongClicked.invoke()
+ true
+ }
+ } else {
+ null
+ }
+
+ val job =
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ // We only want to animate if the view-model is updating in response to a
+ // selection or deselection of the same exact option. For that, we save the
+ // last
+ // value of isSelected.
+ var lastSelected: Boolean? = null
+
+ viewModel.key
+ .flatMapLatest {
+ // If the key changed, then it means that this binding is no longer
+ // rendering the UI for the same option as before, we nullify the
+ // last
+ // selected value to "forget" that we've ever seen a value for
+ // isSelected,
+ // effectively starting anew so the first update doesn't animate.
+ lastSelected = null
+ viewModel.isSelected
+ }
+ .collect { isSelected ->
+ if (foregroundTintSpec != null && foregroundView is ImageView) {
+ if (isSelected) {
+ foregroundView.setColorFilter(
+ foregroundTintSpec.selectedColor
+ )
+ } else {
+ foregroundView.setColorFilter(
+ foregroundTintSpec.unselectedColor
+ )
+ }
+ }
+
+ animatedSelection(
+ animationSpec = animationSpec,
+ borderView = borderView,
+ contentView = backgroundView,
+ isSelected = isSelected,
+ animate = lastSelected != null && lastSelected != isSelected,
+ )
+ lastSelected = isSelected
+ }
+ }
+
+ launch {
+ viewModel.onClicked.collect { onClicked ->
+ view.setOnClickListener(
+ if (onClicked != null) {
+ View.OnClickListener { onClicked.invoke() }
+ } else {
+ null
+ }
+ )
+ }
+ }
+ }
+ }
+
+ return DisposableHandle { job.cancel() }
+ }
+
+ /**
+ * Uses a "bouncy" animation to animate the selecting or un-selecting of a view with a
+ * background and a border.
+ *
+ * Note that it is expected that the [borderView] is below the [contentView] on the z axis so
+ * the latter obscures the former at rest.
+ *
+ * @param borderView A view for the selection border that should be shown when the view is
+ *
+ * ```
+ * selected.
+ * @param contentView
+ * ```
+ *
+ * The view containing the opaque part of the view.
+ *
+ * @param isSelected Whether the view is selected or not.
+ * @param animationSpec The specification for the animation.
+ * @param animate Whether to animate; if `false`, will jump directly to the final state without
+ *
+ * ```
+ * animating.
+ * ```
+ */
+ private fun animatedSelection(
+ borderView: View,
+ contentView: View,
+ isSelected: Boolean,
+ animationSpec: AnimationSpec,
+ animate: Boolean = true,
+ ) {
+ if (isSelected) {
+ if (!animate) {
+ borderView.alpha = 1f
+ borderView.scale(1f)
+ contentView.scale(0.86f)
+ return
+ }
+
+ // Border scale.
+ borderView
+ .animate()
+ .scale(1.099f)
+ .setDuration(animationSpec.durationMs / 2)
+ .setInterpolator(PathInterpolator(0.29f, 0f, 0.67f, 1f))
+ .withStartAction {
+ borderView.scaleX = 0.98f
+ borderView.scaleY = 0.98f
+ borderView.alpha = 1f
+ }
+ .withEndAction {
+ borderView
+ .animate()
+ .scale(1f)
+ .setDuration(animationSpec.durationMs / 2)
+ .setInterpolator(PathInterpolator(0.33f, 0f, 0.15f, 1f))
+ .start()
+ }
+ .start()
+
+ // Background scale.
+ contentView
+ .animate()
+ .scale(0.9321f)
+ .setDuration(animationSpec.durationMs / 2)
+ .setInterpolator(PathInterpolator(0.29f, 0f, 0.67f, 1f))
+ .withEndAction {
+ contentView
+ .animate()
+ .scale(0.86f)
+ .setDuration(animationSpec.durationMs / 2)
+ .setInterpolator(PathInterpolator(0.33f, 0f, 0.15f, 1f))
+ .start()
+ }
+ .start()
+ } else {
+ if (!animate) {
+ borderView.alpha = 0f
+ contentView.scale(1f)
+ return
+ }
+
+ // Border opacity.
+ borderView
+ .animate()
+ .alpha(0f)
+ .setDuration(animationSpec.durationMs / 2)
+ .setInterpolator(LinearInterpolator())
+ .start()
+
+ // Border scale.
+ borderView
+ .animate()
+ .scale(1f)
+ .setDuration(animationSpec.durationMs)
+ .setInterpolator(PathInterpolator(0.2f, 0f, 0f, 1f))
+ .start()
+
+ // Background scale.
+ contentView
+ .animate()
+ .scale(1f)
+ .setDuration(animationSpec.durationMs)
+ .setInterpolator(PathInterpolator(0.2f, 0f, 0f, 1f))
+ .start()
+ }
+ }
+
+ data class AnimationSpec(
+ /** Opacity of the option when it's enabled. */
+ val enabledAlpha: Float = 1f,
+ /** Opacity of the option when it's disabled. */
+ val disabledAlpha: Float = 0.3f,
+ /** Duration of the animation, in milliseconds. */
+ val durationMs: Long = 333L,
+ )
+
+ data class TintSpec(
+ @ColorInt val selectedColor: Int,
+ @ColorInt val unselectedColor: Int,
+ )
+
+ private fun View.scale(scale: Float) {
+ scaleX = scale
+ scaleY = scale
+ }
+
+ private fun ViewPropertyAnimator.scale(scale: Float): ViewPropertyAnimator {
+ return scaleX(scale).scaleY(scale)
+ }
+}
diff --git a/src/com/android/wallpaper/picker/option/ui/viewmodel/OptionItemViewModel.kt b/src/com/android/wallpaper/picker/option/ui/viewmodel/OptionItemViewModel.kt
new file mode 100644
index 00000000..61068867
--- /dev/null
+++ b/src/com/android/wallpaper/picker/option/ui/viewmodel/OptionItemViewModel.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.option.ui.viewmodel
+
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Models UI state for an item in a list of selectable options. */
+data class OptionItemViewModel<Payload>(
+ /**
+ * A stable key that uniquely identifies this option amongst all other options in the same list
+ * of options.
+ */
+ val key: StateFlow<String>,
+
+ /**
+ * The view model representing additional details needed for binding the icon of an option item
+ */
+ val payload: Payload? = null,
+
+ /**
+ * A text to show to the user (or attach as content description on the icon, if there's no
+ * dedicated view for it).
+ */
+ val text: Text,
+
+ /** Whether this option is selected. */
+ val isSelected: StateFlow<Boolean>,
+
+ /** Whether this option is enabled. */
+ val isEnabled: Boolean = true,
+
+ /** Notifies that the option has been clicked by the user. */
+ val onClicked: Flow<(() -> Unit)?>,
+
+ /** Notifies that the option has been long-clicked by the user. */
+ val onLongClicked: (() -> Unit)? = null,
+) {
+ override fun equals(other: Any?): Boolean {
+ val otherItem = other as? OptionItemViewModel<*> ?: return false
+ // skipping comparison of onClicked because it is correlated with
+ // changes on isSelected
+ return this.payload == otherItem.payload &&
+ this.text == otherItem.text &&
+ this.isSelected.value == otherItem.isSelected.value &&
+ this.isEnabled == otherItem.isEnabled &&
+ this.onLongClicked == otherItem.onLongClicked
+ }
+}
diff --git a/src/com/android/wallpaper/picker/undo/domain/interactor/SnapshotRestorer.kt b/src/com/android/wallpaper/picker/undo/domain/interactor/SnapshotRestorer.kt
index 30478341..27ae6095 100644
--- a/src/com/android/wallpaper/picker/undo/domain/interactor/SnapshotRestorer.kt
+++ b/src/com/android/wallpaper/picker/undo/domain/interactor/SnapshotRestorer.kt
@@ -25,12 +25,12 @@ interface SnapshotRestorer {
/**
* Sets up the restorer.
*
- * @param updater An updater the can be used when a new snapshot should be stored; invoke this
- * in response to state changes that you wish could be restored when the user asks to reset the
- * changes.
+ * @param store An object the can be used when a new snapshot should be stored; use this in
+ * response to state changes that you wish could be restored when the user asks to reset the
+ * changes.
* @return A snapshot of the initial state as it was at the moment that this method was invoked.
*/
- suspend fun setUpSnapshotRestorer(updater: (RestorableSnapshot) -> Unit): RestorableSnapshot
+ suspend fun setUpSnapshotRestorer(store: SnapshotStore): RestorableSnapshot
/** Restores the state to what is described in the given snapshot. */
suspend fun restoreToSnapshot(snapshot: RestorableSnapshot)
diff --git a/src/com/android/wallpaper/picker/undo/domain/interactor/SnapshotStore.kt b/src/com/android/wallpaper/picker/undo/domain/interactor/SnapshotStore.kt
new file mode 100644
index 00000000..e2906a84
--- /dev/null
+++ b/src/com/android/wallpaper/picker/undo/domain/interactor/SnapshotStore.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.picker.undo.domain.interactor
+
+import com.android.wallpaper.picker.undo.shared.model.RestorableSnapshot
+
+interface SnapshotStore {
+ fun retrieve(): RestorableSnapshot
+ fun store(snapshot: RestorableSnapshot)
+
+ /** A "no op" implementation of [SnapshotStore] that's safe to call. */
+ object NOOP : SnapshotStore {
+ override fun retrieve(): RestorableSnapshot {
+ return RestorableSnapshot(emptyMap())
+ }
+
+ override fun store(snapshot: RestorableSnapshot) = Unit
+ }
+}
diff --git a/src/com/android/wallpaper/picker/undo/domain/interactor/UndoInteractor.kt b/src/com/android/wallpaper/picker/undo/domain/interactor/UndoInteractor.kt
index 3aa80ae1..53119fd2 100644
--- a/src/com/android/wallpaper/picker/undo/domain/interactor/UndoInteractor.kt
+++ b/src/com/android/wallpaper/picker/undo/domain/interactor/UndoInteractor.kt
@@ -18,6 +18,7 @@
package com.android.wallpaper.picker.undo.domain.interactor
import com.android.wallpaper.picker.undo.data.repository.UndoRepository
+import com.android.wallpaper.picker.undo.shared.model.RestorableSnapshot
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
@@ -27,14 +28,17 @@ import kotlinx.coroutines.launch
*
* ## Usage
* 1. Instantiate, injecting the supported [SnapshotRestorer] into it, one for each feature that
+ *
* ```
* should support undo functionality.
* ```
* 2. Call [startSession] which will bootstrap all passed-in [SnapshotRestorer] instances and
+ *
* ```
* hydrate our model with the latest snapshots from each one.
* ```
* 3. Observe [isUndoable] to know whether the UI for triggering an "undo" action should be made
+ *
* ```
* visible to the user.
* ```
@@ -51,18 +55,29 @@ class UndoInteractor(
/** Bootstraps the undo system, querying each undo-supporting area for the initial snapshot. */
fun startSession() {
- // TODO(b/262924056): take in a saved instance state and reuse it instead.
repository.clearAllDirty()
restorerByOwnerId.forEach { (ownerId, restorer) ->
scope.launch {
val initialSnapshot =
- restorer.setUpSnapshotRestorer { subsequentSnapshot ->
- val initialSnapshot = repository.getSnapshot(ownerId)
- repository.putDirty(
- ownerId = ownerId,
- isDirty = initialSnapshot != subsequentSnapshot
- )
- }
+ restorer.setUpSnapshotRestorer(
+ object : SnapshotStore {
+ override fun retrieve(): RestorableSnapshot {
+ return repository.getSnapshot(ownerId)
+ ?: error(
+ "No snapshot for this owner ID! Did you call this before" +
+ " storing a snapshot?"
+ )
+ }
+
+ override fun store(snapshot: RestorableSnapshot) {
+ val initialSnapshot = repository.getSnapshot(ownerId)
+ repository.putDirty(
+ ownerId = ownerId,
+ isDirty = initialSnapshot != snapshot
+ )
+ }
+ }
+ )
repository.putSnapshot(
ownerId = ownerId,
diff --git a/src/com/android/wallpaper/picker/undo/shared/model/RestorableSnapshot.kt b/src/com/android/wallpaper/picker/undo/shared/model/RestorableSnapshot.kt
index aac0e22d..4161a145 100644
--- a/src/com/android/wallpaper/picker/undo/shared/model/RestorableSnapshot.kt
+++ b/src/com/android/wallpaper/picker/undo/shared/model/RestorableSnapshot.kt
@@ -20,4 +20,33 @@ package com.android.wallpaper.picker.undo.shared.model
/** Models a snapshot of the state of an undo-supporting feature at a given time. */
data class RestorableSnapshot(
val args: Map<String, String>,
-)
+) {
+ /**
+ * Returns a copy of the [RestorableSnapshot] but with the [block] applied to its arguments.
+ *
+ * Sample usage:
+ * ```
+ * val previousSnapshot: RestorableSnapshot = ...
+ * val nextSnapshot = previousSnapshot { args ->
+ * args.put("one", "true")
+ * args.remove("two")
+ * }
+ *
+ * // Now, nextSnapshot is exactly like previousSnapshot but with its args having "one" mapped
+ * // to "true" and without "two", since it was removed.
+ * ```
+ *
+ * @param block A function that receives the original [args] from the current
+ * [RestorableSnapshot] and can edit them for inclusion into the returned
+ * [RestorableSnapshot].
+ */
+ fun copy(
+ block: (MutableMap<String, String>) -> Unit,
+ ): RestorableSnapshot {
+ val mutableArgs = args.toMutableMap()
+ block(mutableArgs)
+ return RestorableSnapshot(
+ args = mutableArgs.toMap(),
+ )
+ }
+}
diff --git a/src/com/android/wallpaper/picker/undo/ui/binder/RevertToolbarButtonBinder.kt b/src/com/android/wallpaper/picker/undo/ui/binder/RevertToolbarButtonBinder.kt
index a1eb0185..a2e85ee6 100644
--- a/src/com/android/wallpaper/picker/undo/ui/binder/RevertToolbarButtonBinder.kt
+++ b/src/com/android/wallpaper/picker/undo/ui/binder/RevertToolbarButtonBinder.kt
@@ -17,7 +17,6 @@
package com.android.wallpaper.picker.undo.ui.binder
-import android.app.AlertDialog
import android.app.Dialog
import android.widget.Toolbar
import androidx.lifecycle.Lifecycle
@@ -25,7 +24,8 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.wallpaper.R
-import com.android.wallpaper.picker.undo.ui.viewmodel.UndoDialogViewModel
+import com.android.wallpaper.picker.common.dialog.ui.viewbinder.DialogViewBinder
+import com.android.wallpaper.picker.common.dialog.ui.viewmodel.DialogViewModel
import com.android.wallpaper.picker.undo.ui.viewmodel.UndoViewModel
import kotlinx.coroutines.launch
@@ -44,18 +44,12 @@ object RevertToolbarButtonBinder {
var dialog: Dialog? = null
- fun showDialog(viewModel: UndoDialogViewModel) {
+ fun showDialog(viewModel: DialogViewModel) {
dialog =
- AlertDialog.Builder(view.context, R.style.LightDialogTheme)
- .setTitle(R.string.reset_confirmation_dialog_title)
- .setMessage(R.string.reset_confirmation_dialog_message)
- .setPositiveButton(R.string.reset) { _, _ -> viewModel.onConfirmed() }
- .setNegativeButton(R.string.cancel, null)
- .setOnDismissListener {
- dialog = null
- viewModel.onDismissed()
- }
- .create()
+ DialogViewBinder.show(
+ context = view.context,
+ viewModel = viewModel,
+ )
dialog?.show()
}
diff --git a/src/com/android/wallpaper/picker/undo/ui/viewmodel/UndoViewModel.kt b/src/com/android/wallpaper/picker/undo/ui/viewmodel/UndoViewModel.kt
index 5e0701bd..37b3e832 100644
--- a/src/com/android/wallpaper/picker/undo/ui/viewmodel/UndoViewModel.kt
+++ b/src/com/android/wallpaper/picker/undo/ui/viewmodel/UndoViewModel.kt
@@ -17,6 +17,12 @@
package com.android.wallpaper.picker.undo.ui.viewmodel
+import com.android.wallpaper.R
+import com.android.wallpaper.picker.common.button.ui.viewmodel.ButtonStyle
+import com.android.wallpaper.picker.common.button.ui.viewmodel.ButtonViewModel
+import com.android.wallpaper.picker.common.dialog.ui.viewmodel.DialogViewModel
+import com.android.wallpaper.picker.common.icon.ui.viewmodel.Icon
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
import com.android.wallpaper.picker.undo.domain.interactor.UndoInteractor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -28,21 +34,39 @@ class UndoViewModel(
) {
/** Whether the "revert" button should be visible. */
val isRevertButtonVisible: Flow<Boolean> = interactor.isUndoable
- private val _dialog = MutableStateFlow<UndoDialogViewModel?>(null)
+ private val _dialog = MutableStateFlow<DialogViewModel?>(null)
/**
* A view-model of the undo confirmation dialog that should be shown, or `null` when no dialog
* should be shown.
*/
- val dialog: Flow<UndoDialogViewModel?> = _dialog.asStateFlow()
+ val dialog: Flow<DialogViewModel?> = _dialog.asStateFlow()
/** Notifies that the "revert" button has been clicked by the user. */
fun onRevertButtonClicked() {
_dialog.value =
- UndoDialogViewModel(
- onConfirmed = {
- interactor.revertAll()
- _dialog.value = null
- },
+ DialogViewModel(
+ icon =
+ Icon.Resource(
+ res = R.drawable.ic_device_reset,
+ contentDescription = null,
+ ),
+ title = Text.Resource(R.string.reset_confirmation_dialog_title),
+ message = Text.Resource(R.string.reset_confirmation_dialog_message),
+ buttons =
+ listOf(
+ ButtonViewModel(
+ text = Text.Resource(R.string.cancel),
+ style = ButtonStyle.Secondary,
+ ),
+ ButtonViewModel(
+ text = Text.Resource(R.string.reset),
+ style = ButtonStyle.Primary,
+ onClicked = {
+ interactor.revertAll()
+ _dialog.value = null
+ },
+ ),
+ ),
onDismissed = { _dialog.value = null },
)
}
diff --git a/src/com/android/wallpaper/settings/data/repository/SecureSettingsRepository.kt b/src/com/android/wallpaper/settings/data/repository/SecureSettingsRepository.kt
new file mode 100644
index 00000000..db56b678
--- /dev/null
+++ b/src/com/android/wallpaper/settings/data/repository/SecureSettingsRepository.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.settings.data.repository
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/** Defines interface for classes that can provide access to data from [Settings.Secure]. */
+interface SecureSettingsRepository {
+
+ /** Returns a [Flow] tracking the value of a setting as an [Int]. */
+ fun intSetting(
+ name: String,
+ defaultValue: Int = 0,
+ ): Flow<Int>
+
+ /** Updates the value of the setting with the given name. */
+ suspend fun set(
+ name: String,
+ value: Int,
+ )
+
+ suspend fun get(
+ name: String,
+ defaultValue: Int = 0,
+ ): Int
+}
+
+class SecureSettingsRepositoryImpl(
+ private val contentResolver: ContentResolver,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) : SecureSettingsRepository {
+
+ override fun intSetting(
+ name: String,
+ defaultValue: Int,
+ ): Flow<Int> {
+ return callbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(name),
+ /* notifyForDescendants= */ false,
+ observer,
+ )
+ send(Unit)
+
+ awaitClose { contentResolver.unregisterContentObserver(observer) }
+ }
+ .map { Settings.Secure.getInt(contentResolver, name, defaultValue) }
+ // The above work is done on the background thread (which is important for accessing
+ // settings through the content resolver).
+ .flowOn(backgroundDispatcher)
+ }
+
+ override suspend fun set(name: String, value: Int) {
+ withContext(backgroundDispatcher) {
+ Settings.Secure.putInt(
+ contentResolver,
+ name,
+ value,
+ )
+ }
+ }
+
+ override suspend fun get(name: String, defaultValue: Int): Int {
+ return withContext(backgroundDispatcher) {
+ Settings.Secure.getInt(
+ contentResolver,
+ name,
+ defaultValue,
+ )
+ }
+ }
+}
diff --git a/src/com/android/wallpaper/util/ActivityUtils.java b/src/com/android/wallpaper/util/ActivityUtils.java
index 7456edd7..c6ffc61e 100755
--- a/src/com/android/wallpaper/util/ActivityUtils.java
+++ b/src/com/android/wallpaper/util/ActivityUtils.java
@@ -122,4 +122,13 @@ public final class ActivityUtils {
return "wallpaper_only".equals(
intent.getStringExtra("com.android.launcher3.WALLPAPER_FLAVOR"));
}
+
+ /**
+ * Returns {@code true} if the activity was launched from the home screen (launcher);
+ * {@code false} otherwise.
+ */
+ public static boolean isLaunchedFromLauncher(Intent intent) {
+ return LaunchSourceUtils.LAUNCH_SOURCE_LAUNCHER.equals(
+ intent.getStringExtra(WALLPAPER_LAUNCH_SOURCE));
+ }
} \ No newline at end of file
diff --git a/src/com/android/wallpaper/util/DisplayUtils.kt b/src/com/android/wallpaper/util/DisplayUtils.kt
index ce6980ed..cd364dbe 100644
--- a/src/com/android/wallpaper/util/DisplayUtils.kt
+++ b/src/com/android/wallpaper/util/DisplayUtils.kt
@@ -21,49 +21,92 @@ import android.graphics.Point
import android.hardware.display.DisplayManager
import android.util.Log
import android.view.Display
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
/**
* Utility class to provide methods to find and obtain information about displays via {@link
* DisplayManager}
*/
-class DisplayUtils(context: Context) {
+class DisplayUtils(private val context: Context) {
companion object {
private const val TAG = "DisplayUtils"
+ private val ROTATION_HORIZONTAL_HINGE = setOf(ROTATION_90, ROTATION_270)
}
- private val internalDisplays: List<Display>
+ private val displayManager: DisplayManager by lazy {
+ context.applicationContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+ }
- init {
- val appContext = context.applicationContext
- val dm = appContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
- val allDisplays: Array<out Display> =
- dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
- if (allDisplays.isEmpty()) {
- Log.e(TAG, "No displays found on context $appContext")
- throw RuntimeException("No displays found!")
- }
- internalDisplays = allDisplays.filter { it.type == Display.TYPE_INTERNAL }
+ fun hasMultiInternalDisplays(): Boolean {
+ return getInternalDisplays().size > 1
}
- /** Returns the {@link Display} to be used to calculate wallpaper size and cropping. */
+ /**
+ * Returns the internal {@link Display} with tthe largest area to be used to calculate wallpaper
+ * size and cropping.
+ */
fun getWallpaperDisplay(): Display {
- return internalDisplays.maxWithOrNull { a, b -> getRealSize(a) - getRealSize(b) }
+ val internalDisplays = getInternalDisplays()
+ return internalDisplays.maxWithOrNull { a, b -> getRealArea(a) - getRealArea(b) }
?: internalDisplays[0]
}
/**
+ * Checks if the device only has one display or unfolded screen in horizontal hinge orientation.
+ */
+ fun isSingleDisplayOrUnfoldedHorizontalHinge(activity: Activity): Boolean {
+ return !hasMultiInternalDisplays() || isUnfoldedHorizontalHinge(activity)
+ }
+
+ /**
+ * Checks if the device is a foldable and it's unfolded and in horizontal hinge orientation
+ * (portrait).
+ */
+ fun isUnfoldedHorizontalHinge(activity: Activity): Boolean {
+ return activity.display.rotation in ROTATION_HORIZONTAL_HINGE &&
+ isOnWallpaperDisplay(activity) &&
+ hasMultiInternalDisplays()
+ }
+
+ fun getMaxDisplaysDimension(): Point {
+ val dimen = Point()
+ getInternalDisplays().let { displays ->
+ dimen.x = displays.maxOf { getRealSize(it).x }
+ dimen.y = displays.maxOf { getRealSize(it).y }
+ }
+ return dimen
+ }
+
+ /**
* Returns `true` if the current display is the wallpaper display on a multi-display device.
*
* On a multi-display device the wallpaper display is the largest display while on a single
* display device the only display is both the wallpaper display and the current display.
*/
fun isOnWallpaperDisplay(activity: Activity): Boolean {
- return activity.display.displayId == getWallpaperDisplay().displayId
+ return activity.display.uniqueId == getWallpaperDisplay().uniqueId
}
- private fun getRealSize(display: Display): Int {
+ private fun getRealArea(display: Display): Int {
val p = Point()
display.getRealSize(p)
return p.x * p.y
}
+
+ private fun getRealSize(display: Display): Point {
+ val p = Point()
+ display.getRealSize(p)
+ return p
+ }
+
+ private fun getInternalDisplays(): List<Display> {
+ val allDisplays: Array<out Display> =
+ displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
+ if (allDisplays.isEmpty()) {
+ Log.e(TAG, "No displays found on context ${context.applicationContext}")
+ throw RuntimeException("No displays found!")
+ }
+ return allDisplays.filter { it.type == Display.TYPE_INTERNAL }
+ }
}
diff --git a/src/com/android/wallpaper/util/VideoWallpaperUtils.java b/src/com/android/wallpaper/util/VideoWallpaperUtils.java
new file mode 100644
index 00000000..2093fe83
--- /dev/null
+++ b/src/com/android/wallpaper/util/VideoWallpaperUtils.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.wallpaper.util;
+
+import com.android.wallpaper.model.LiveWallpaperInfo;
+import com.android.wallpaper.model.WallpaperInfo;
+
+/**
+ * Workarounds for dealing with issues displaying video wallpaper previews until better solutions
+ * are found.
+ *
+ * See b/268066031.
+ */
+public class VideoWallpaperUtils {
+
+ /**
+ * Transition time for fade-in animation.
+ */
+ public static final int TRANSITION_MILLIS = 250;
+
+ /**
+ * Returns true if the is a video wallpaper that requires the fade-in workaround.
+ */
+ public static boolean needsFadeIn(WallpaperInfo info) {
+ return info instanceof LiveWallpaperInfo;
+ }
+}
diff --git a/src/com/android/wallpaper/util/WallpaperCropUtils.java b/src/com/android/wallpaper/util/WallpaperCropUtils.java
index 380a6b61..4aed3c2d 100755
--- a/src/com/android/wallpaper/util/WallpaperCropUtils.java
+++ b/src/com/android/wallpaper/util/WallpaperCropUtils.java
@@ -190,7 +190,8 @@ public final class WallpaperCropUtils {
* @return a Rect representing the area of the wallpaper to crop.
*/
public static Rect calculateCropRect(Context context, float wallpaperZoom, Point wallpaperSize,
- Point defaultCropSurfaceSize, Point targetHostSize, int scrollX, int scrollY) {
+ Point defaultCropSurfaceSize, Point targetHostSize, int scrollX, int scrollY,
+ boolean cropExtraWidth) {
// Calculate Rect of wallpaper in physical pixel terms (i.e., scaled to current zoom).
int scaledWallpaperWidth = Math.round(wallpaperSize.x * wallpaperZoom);
int scaledWallpaperHeight = Math.round(wallpaperSize.y * wallpaperZoom);
@@ -205,12 +206,14 @@ public final class WallpaperCropUtils {
int extraWidth = defaultCropSurfaceSize.x - targetHostSize.x;
int extraHeightTopAndBottom = (int) ((defaultCropSurfaceSize.y - targetHostSize.y) / 2f);
- // Try to increase size of screenRect to include extra width depending on the layout
- // direction.
- if (isRtl(context)) {
- cropRect.left = Math.max(cropRect.left - extraWidth, rect.left);
- } else {
- cropRect.right = Math.min(cropRect.right + extraWidth, rect.right);
+ if (cropExtraWidth) {
+ // Try to increase size of screenRect to include extra width depending on the layout
+ // direction.
+ if (isRtl(context)) {
+ cropRect.left = Math.max(cropRect.left - extraWidth, rect.left);
+ } else {
+ cropRect.right = Math.min(cropRect.right + extraWidth, rect.right);
+ }
}
// Try to increase the size of the cropRect to to include extra height.
@@ -301,14 +304,32 @@ public final class WallpaperCropUtils {
* @param rawWallpaperSize the size of the raw wallpaper as a Point (x,y).
* @param visibleRawWallpaperRect the area of the raw wallpaper which is expected to see.
* @param wallpaperZoom the factor which is used to scale the raw wallpaper.
+ * @param cropExtraWidth true to crop extra wallpaper width for panel sliding.
*/
public static Rect calculateCropRect(Context context, Point hostViewSize, Point cropSize,
- Point rawWallpaperSize, Rect visibleRawWallpaperRect, float wallpaperZoom) {
+ Point rawWallpaperSize, Rect visibleRawWallpaperRect, float wallpaperZoom,
+ boolean cropExtraWidth) {
int scrollX = (int) (visibleRawWallpaperRect.left * wallpaperZoom);
int scrollY = (int) (visibleRawWallpaperRect.top * wallpaperZoom);
return calculateCropRect(context, wallpaperZoom, rawWallpaperSize, cropSize, hostViewSize,
- scrollX, scrollY);
+ scrollX, scrollY, cropExtraWidth);
+ }
+
+ /**
+ * Calculates {@link Rect} of the wallpaper which we want to crop to in physical pixel terms
+ * (i.e., scaled to current zoom).
+ *
+ * @param hostViewSize the size of the view hosting the wallpaper as a Point (x,y).
+ * @param cropSize the default size of the crop as a Point (x,y).
+ * @param rawWallpaperSize the size of the raw wallpaper as a Point (x,y).
+ * @param visibleRawWallpaperRect the area of the raw wallpaper which is expected to see.
+ * @param wallpaperZoom the factor which is used to scale the raw wallpaper.
+ */
+ public static Rect calculateCropRect(Context context, Point hostViewSize, Point cropSize,
+ Point rawWallpaperSize, Rect visibleRawWallpaperRect, float wallpaperZoom) {
+ return calculateCropRect(context, hostViewSize, cropSize, rawWallpaperSize,
+ visibleRawWallpaperRect, wallpaperZoom, /* cropExtraWidth= */ true);
}
/**
diff --git a/src/com/android/wallpaper/widget/DuoTabs.java b/src/com/android/wallpaper/widget/DuoTabs.java
index a8f30240..b0fb0b14 100644
--- a/src/com/android/wallpaper/widget/DuoTabs.java
+++ b/src/com/android/wallpaper/widget/DuoTabs.java
@@ -18,8 +18,8 @@ package com.android.wallpaper.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
-import android.widget.Button;
import android.widget.FrameLayout;
+import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -54,10 +54,10 @@ public final class DuoTabs extends FrameLayout {
void onTabSelected(@Tab int tab);
}
- OnTabSelectedListener mOnTabSelectedListener;
- Button mPrimaryTab;
- Button mSecondaryTab;
- @Tab int mCurrentOverlayTab;
+ private OnTabSelectedListener mOnTabSelectedListener;
+ private final TextView mPrimaryTab;
+ private final TextView mSecondaryTab;
+ @Tab private int mCurrentOverlayTab;
/**
* Constructor
diff --git a/src/com/android/wallpaper/widget/FloatingSheet.kt b/src/com/android/wallpaper/widget/FloatingSheet.kt
index 7c8bbd76..26f068a4 100644
--- a/src/com/android/wallpaper/widget/FloatingSheet.kt
+++ b/src/com/android/wallpaper/widget/FloatingSheet.kt
@@ -154,8 +154,8 @@ class FloatingSheet(context: Context, attrs: AttributeSet?) : FrameLayout(contex
* Adds Floating Sheet Callback to connected [BottomSheetBehavior].
*
* @param callback the callback for floating sheet state changes, has to be in the type of
- * [BottomSheetBehavior.BottomSheetCallback] since the floating sheet behavior is currently
- * based on [BottomSheetBehavior]
+ * [BottomSheetBehavior.BottomSheetCallback] since the floating sheet behavior is currently
+ * based on [BottomSheetBehavior]
*/
fun addFloatingSheetCallback(callback: BottomSheetCallback?) {
floatingSheetBehavior.addBottomSheetCallback(callback!!)
diff --git a/src/com/android/wallpaper/widget/WallpaperControlButtonGroup.java b/src/com/android/wallpaper/widget/WallpaperControlButtonGroup.java
index 00a681ae..c5c8f7ea 100644
--- a/src/com/android/wallpaper/widget/WallpaperControlButtonGroup.java
+++ b/src/com/android/wallpaper/widget/WallpaperControlButtonGroup.java
@@ -35,22 +35,26 @@ import com.android.wallpaper.R;
public final class WallpaperControlButtonGroup extends FrameLayout {
public static final int DELETE = 0;
- public static final int CUSTOMIZE = 1;
- public static final int EFFECTS = 2;
- public static final int INFORMATION = 3;
+ public static final int EDIT = 1;
+ public static final int CUSTOMIZE = 2;
+ public static final int EFFECTS = 3;
+ public static final int INFORMATION = 4;
+ public static final int SHARE = 5;
/**
* Overlay tab
*/
- @IntDef({DELETE, CUSTOMIZE, EFFECTS, INFORMATION})
+ @IntDef({DELETE, EDIT, CUSTOMIZE, EFFECTS, SHARE, INFORMATION})
public @interface WallpaperControlType {
}
- final int[] mFloatingSheetControlButtonTypes = { CUSTOMIZE, EFFECTS, INFORMATION };
+ final int[] mFloatingSheetControlButtonTypes = { CUSTOMIZE, EFFECTS, SHARE, INFORMATION };
ToggleButton mDeleteButton;
+ ToggleButton mEditButton;
ToggleButton mCustomizeButton;
ToggleButton mEffectsButton;
+ ToggleButton mShareButton;
ToggleButton mInformationButton;
/**
@@ -60,8 +64,10 @@ public final class WallpaperControlButtonGroup extends FrameLayout {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.wallpaper_control_button_group, this, true);
mDeleteButton = findViewById(R.id.delete_button);
+ mEditButton = findViewById(R.id.edit_button);
mCustomizeButton = findViewById(R.id.customize_button);
mEffectsButton = findViewById(R.id.effects_button);
+ mShareButton = findViewById(R.id.share_button);
mInformationButton = findViewById(R.id.information_button);
}
@@ -81,10 +87,14 @@ public final class WallpaperControlButtonGroup extends FrameLayout {
switch (type) {
case DELETE:
return mDeleteButton;
+ case EDIT:
+ return mEditButton;
case CUSTOMIZE:
return mCustomizeButton;
case EFFECTS:
return mEffectsButton;
+ case SHARE:
+ return mShareButton;
case INFORMATION:
return mInformationButton;
default:
@@ -115,15 +125,21 @@ public final class WallpaperControlButtonGroup extends FrameLayout {
return;
}
mDeleteButton.setForeground(null);
+ mEditButton.setForeground(null);
mCustomizeButton.setForeground(null);
mEffectsButton.setForeground(null);
+ mShareButton.setForeground(null);
mInformationButton.setForeground(null);
mDeleteButton.setForeground(AppCompatResources.getDrawable(context,
R.drawable.wallpaper_control_button_delete));
+ mEditButton.setForeground(
+ AppCompatResources.getDrawable(context, R.drawable.wallpaper_control_button_edit));
mCustomizeButton.setForeground(AppCompatResources.getDrawable(context,
R.drawable.wallpaper_control_button_customize));
mEffectsButton.setForeground(AppCompatResources.getDrawable(context,
R.drawable.wallpaper_control_button_effect));
+ mShareButton.setForeground(AppCompatResources.getDrawable(context,
+ R.drawable.wallpaper_control_button_share));
mInformationButton.setForeground(
AppCompatResources.getDrawable(context, R.drawable.wallpaper_control_button_info));
}
diff --git a/src/com/android/wallpaper/widget/floatingsheetcontent/WallpaperInfoContent.kt b/src/com/android/wallpaper/widget/floatingsheetcontent/WallpaperInfoContent.kt
index 2bc75a7a..611df546 100644
--- a/src/com/android/wallpaper/widget/floatingsheetcontent/WallpaperInfoContent.kt
+++ b/src/com/android/wallpaper/widget/floatingsheetcontent/WallpaperInfoContent.kt
@@ -75,6 +75,8 @@ class WallpaperInfoContent(private var context: Context, private val wallpaper:
wallpaper!!,
actionLabel,
WallpaperInfoHelper.shouldShowExploreButton(context, exploreIntent)
- ) { onExploreClicked() }
+ ) {
+ onExploreClicked()
+ }
}
}