summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt41
-rw-r--r--core/java/android/app/wallpaper.aconfig8
-rw-r--r--core/java/android/app/wallpaper/WallpaperDescription.aidl20
-rw-r--r--core/java/android/app/wallpaper/WallpaperDescription.java318
-rw-r--r--core/java/android/app/wallpaper/WallpaperInstance.aidl20
-rw-r--r--core/java/android/app/wallpaper/WallpaperInstance.java162
-rw-r--r--core/tests/coretests/AndroidManifest.xml20
-rw-r--r--core/tests/coretests/res/xml/livewallpaper.xml20
-rw-r--r--core/tests/coretests/src/android/app/wallpaper/OWNERS1
-rw-r--r--core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java162
-rw-r--r--core/tests/coretests/src/android/app/wallpaper/WallpaperInstanceTest.java189
11 files changed, 961 insertions, 0 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 46a864e19865..a1a734fe188d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9664,6 +9664,47 @@ package android.app.usage {
}
+package android.app.wallpaper {
+
+ @FlaggedApi("android.app.live_wallpaper_content_handling") public final class WallpaperDescription implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.content.ComponentName getComponent();
+ method @NonNull public android.os.PersistableBundle getContent();
+ method @Nullable public CharSequence getContextDescription();
+ method @Nullable public android.net.Uri getContextUri();
+ method @NonNull public java.util.List<java.lang.CharSequence> getDescription();
+ method @Nullable public String getId();
+ method @Nullable public android.net.Uri getThumbnail();
+ method @Nullable public CharSequence getTitle();
+ method @NonNull public android.app.wallpaper.WallpaperDescription.Builder toBuilder();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @Nullable public static final android.os.Parcelable.Creator<android.app.wallpaper.WallpaperDescription> CREATOR;
+ }
+
+ public static final class WallpaperDescription.Builder {
+ ctor public WallpaperDescription.Builder();
+ method @NonNull public android.app.wallpaper.WallpaperDescription build();
+ method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setContent(@NonNull android.os.PersistableBundle);
+ method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setContextDescription(@Nullable CharSequence);
+ method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setContextUri(@Nullable android.net.Uri);
+ method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setDescription(@NonNull java.util.List<java.lang.CharSequence>);
+ method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setId(@Nullable String);
+ method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setThumbnail(@Nullable android.net.Uri);
+ method @NonNull public android.app.wallpaper.WallpaperDescription.Builder setTitle(@Nullable CharSequence);
+ }
+
+ @FlaggedApi("android.app.live_wallpaper_content_handling") public final class WallpaperInstance implements android.os.Parcelable {
+ ctor public WallpaperInstance(@Nullable android.app.WallpaperInfo, @NonNull android.app.wallpaper.WallpaperDescription);
+ method public int describeContents();
+ method @NonNull public android.app.wallpaper.WallpaperDescription getDescription();
+ method @NonNull public String getId();
+ method @Nullable public android.app.WallpaperInfo getInfo();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpaper.WallpaperInstance> CREATOR;
+ }
+
+}
+
package android.appwidget {
public class AppWidgetHost {
diff --git a/core/java/android/app/wallpaper.aconfig b/core/java/android/app/wallpaper.aconfig
index c5bd56ff67aa..4b880d030413 100644
--- a/core/java/android/app/wallpaper.aconfig
+++ b/core/java/android/app/wallpaper.aconfig
@@ -14,3 +14,11 @@ flag {
description: "Fixes timing of wallpaper changed notification and adds extra information. Only effective after rebooting."
bug: "369814294"
}
+
+flag {
+ name: "live_wallpaper_content_handling"
+ namespace: "systemui"
+ description: "Support for user-generated content in live wallpapers. Only effective after rebooting."
+ bug: "347235611"
+ is_exported: true
+}
diff --git a/core/java/android/app/wallpaper/WallpaperDescription.aidl b/core/java/android/app/wallpaper/WallpaperDescription.aidl
new file mode 100644
index 000000000000..8c959b8d0172
--- /dev/null
+++ b/core/java/android/app/wallpaper/WallpaperDescription.aidl
@@ -0,0 +1,20 @@
+
+/*
+** Copyright 2024, 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 android.app.wallpaper;
+
+parcelable WallpaperDescription;
diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java
new file mode 100644
index 000000000000..e5b2cd3d76a6
--- /dev/null
+++ b/core/java/android/app/wallpaper/WallpaperDescription.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2024 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 android.app.wallpaper;
+
+import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
+
+import android.annotation.FlaggedApi;
+import android.app.WallpaperInfo;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Describes a wallpaper, including associated metadata and optional content to be used by its
+ * {@link android.service.wallpaper.WallpaperService.Engine}, the {@link ComponentName} to be used
+ * by {@link android.app.WallpaperManager}, and an optional id to differentiate between different
+ * distinct wallpapers rendered by the same wallpaper service.
+ *
+ * <p>This class is used to communicate among a wallpaper rendering service, a wallpaper chooser UI,
+ * and {@link android.app.WallpaperManager}. This class describes a specific instance of a live
+ * wallpaper, unlike {@link WallpaperInfo} which is common to all instances of a wallpaper
+ * component. Each {@link WallpaperDescription} can have distinct metadata.
+ * </p>
+ */
+@FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+public final class WallpaperDescription implements Parcelable {
+ @Nullable private final ComponentName mComponent;
+ @Nullable private final String mId;
+ @Nullable private final Uri mThumbnail;
+ @Nullable private final CharSequence mTitle;
+ @NonNull private final List<CharSequence> mDescription;
+ @Nullable private final Uri mContextUri;
+ @Nullable private final CharSequence mContextDescription;
+ @NonNull private final PersistableBundle mContent;
+
+ private WallpaperDescription(@Nullable ComponentName component,
+ @Nullable String id, @Nullable Uri thumbnail, @Nullable CharSequence title,
+ @Nullable List<CharSequence> description, @Nullable Uri contextUri,
+ @Nullable CharSequence contextDescription,
+ @Nullable PersistableBundle content) {
+ this.mComponent = component;
+ this.mId = id;
+ this.mThumbnail = thumbnail;
+ this.mTitle = title;
+ this.mDescription = (description != null) ? description : new ArrayList<>();
+ this.mContextUri = contextUri;
+ this.mContextDescription = contextDescription;
+ this.mContent = (content != null) ? content : new PersistableBundle();
+ }
+
+ /** @return the component for this wallpaper, or {@code null} for a static wallpaper */
+ @Nullable public ComponentName getComponent() {
+ return mComponent;
+ }
+
+ /** @return the id for this wallpaper, or {@code null} if not provided */
+ @Nullable public String getId() {
+ return mId;
+ }
+
+ /** @return the thumbnail for this wallpaper, or {@code null} if not provided */
+ @Nullable public Uri getThumbnail() {
+ return mThumbnail;
+ }
+
+ /**
+ * @return the title for this wallpaper, with each list element intended to be a separate
+ * line, or {@code null} if not provided
+ */
+ @Nullable public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /** @return the description for this wallpaper */
+ @NonNull
+ public List<CharSequence> getDescription() {
+ return new ArrayList<>();
+ }
+
+ /** @return the {@link Uri} for the action associated with the wallpaper, or {@code null} if not
+ * provided */
+ @Nullable public Uri getContextUri() {
+ return mContextUri;
+ }
+
+ /** @return the description for the action associated with the wallpaper, or {@code null} if not
+ * provided */
+ @Nullable public CharSequence getContextDescription() {
+ return mContextDescription;
+ }
+
+ /** @return any additional content required to render this wallpaper */
+ @NonNull
+ public PersistableBundle getContent() {
+ return mContent;
+ }
+
+ ////// Comparison overrides
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof WallpaperDescription that)) return false;
+ return Objects.equals(mComponent, that.mComponent) && Objects.equals(mId,
+ that.mId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mComponent, mId);
+ }
+
+ ////// Parcelable implementation
+
+ WallpaperDescription(@NonNull Parcel in) {
+ mComponent = ComponentName.readFromParcel(in);
+ mId = in.readString8();
+ mThumbnail = Uri.CREATOR.createFromParcel(in);
+ mTitle = in.readCharSequence();
+ mDescription = Arrays.stream(in.readCharSequenceArray()).toList();
+ mContextUri = Uri.CREATOR.createFromParcel(in);
+ mContextDescription = in.readCharSequence();
+ mContent = PersistableBundle.CREATOR.createFromParcel(in);
+ }
+
+ @Nullable
+ public static final Creator<WallpaperDescription> CREATOR = new Creator<>() {
+ @Override
+ public WallpaperDescription createFromParcel(Parcel source) {
+ return new WallpaperDescription(source);
+ }
+
+ @Override
+ public WallpaperDescription[] newArray(int size) {
+ return new WallpaperDescription[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ ComponentName.writeToParcel(mComponent, dest);
+ dest.writeString8(mId);
+ Uri.writeToParcel(dest, mThumbnail);
+ dest.writeCharSequence(mTitle);
+ dest.writeCharSequenceArray(mDescription.toArray(new CharSequence[0]));
+ Uri.writeToParcel(dest, mContextUri);
+ dest.writeCharSequence(mContextDescription);
+ dest.writePersistableBundle(mContent);
+ }
+
+ ////// Builder
+
+ /**
+ * Convert the current description to a {@link Builder}.
+ * @return the Builder representing this description
+ */
+ @NonNull
+ public Builder toBuilder() {
+ return new Builder().setComponent(mComponent).setId(mId).setThumbnail(mThumbnail).setTitle(
+ mTitle).setDescription(mDescription).setContextUri(
+ mContextUri).setContextDescription(mContextDescription).setContent(mContent);
+ }
+
+ /** Builder for the immutable {@link WallpaperDescription} class */
+ public static final class Builder {
+ @Nullable private ComponentName mComponent;
+ @Nullable private String mId;
+ @Nullable private Uri mThumbnail;
+ @Nullable private CharSequence mTitle;
+ @NonNull private List<CharSequence> mDescription = new ArrayList<>();
+ @Nullable private Uri mContextUri;
+ @Nullable private CharSequence mContextDescription;
+ @NonNull private PersistableBundle mContent = new PersistableBundle();
+
+ /** Creates a new, empty {@link Builder}. */
+ public Builder() {}
+
+ /**
+ * Specify the component for this wallpaper.
+ *
+ * <p>This method is hidden because only trusted apps should be able to specify the
+ * component, which names a wallpaper service to be started by the system.
+ * </p>
+ *
+ * @param component component name, or {@code null} for static wallpaper
+ * @hide
+ */
+ @NonNull
+ public Builder setComponent(@Nullable ComponentName component) {
+ mComponent = component;
+ return this;
+ }
+
+ /**
+ * Set the id for this wallpaper.
+ *
+ * <p>IDs are used to distinguish among different instances of wallpapers rendered by the
+ * same component, and should be unique among all wallpapers for that component.
+ * </p>
+ *
+ * @param id the id, or {@code null} for none
+ */
+ @NonNull
+ public Builder setId(@Nullable String id) {
+ mId = id;
+ return this;
+ }
+
+ /**
+ * Set the thumbnail Uri for this wallpaper.
+ *
+ * @param thumbnail the thumbnail Uri, or {@code null} for none
+ */
+ @NonNull
+ public Builder setThumbnail(@Nullable Uri thumbnail) {
+ mThumbnail = thumbnail;
+ return this;
+ }
+
+ /**
+ * Set the title for this wallpaper.
+ *
+ * @param title the title, or {@code null} for none
+ */
+ @NonNull
+ public Builder setTitle(@Nullable CharSequence title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Set the description for this wallpaper. Each array element should be shown on a
+ * different line.
+ *
+ * @param description the description, or an empty list for none
+ */
+ @NonNull
+ public Builder setDescription(@NonNull List<CharSequence> description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Set the Uri for the action associated with this wallpaper, to be shown as a link with the
+ * wallpaper information.
+ *
+ * @param contextUri the action Uri, or {@code null} for no action
+ */
+ @NonNull
+ public Builder setContextUri(@Nullable Uri contextUri) {
+ mContextUri = contextUri;
+ return this;
+ }
+
+ /**
+ * Set the link text for the action associated with this wallpaper.
+ *
+ * @param contextDescription the link text, or {@code null} for default text
+ */
+ @NonNull
+ public Builder setContextDescription(@Nullable CharSequence contextDescription) {
+ mContextDescription = contextDescription;
+ return this;
+ }
+
+ /**
+ * Set the additional content required to render this wallpaper.
+ *
+ * <p>When setting additional content (asset id, etc.), best practice is to set an ID as
+ * well. This allows WallpaperManager and other code to distinguish between different
+ * wallpapers handled by this component.
+ * </p>
+ *
+ * @param content additional content, or an empty bundle for none
+ */
+ @NonNull
+ public Builder setContent(@NonNull PersistableBundle content) {
+ mContent = content;
+ return this;
+ }
+
+ /** Creates and returns the {@link WallpaperDescription} represented by this builder. */
+ @NonNull
+ public WallpaperDescription build() {
+ return new WallpaperDescription(mComponent, mId, mThumbnail, mTitle, mDescription,
+ mContextUri, mContextDescription, mContent);
+ }
+ }
+}
diff --git a/core/java/android/app/wallpaper/WallpaperInstance.aidl b/core/java/android/app/wallpaper/WallpaperInstance.aidl
new file mode 100644
index 000000000000..15a15bef0d24
--- /dev/null
+++ b/core/java/android/app/wallpaper/WallpaperInstance.aidl
@@ -0,0 +1,20 @@
+
+/*
+** Copyright 2024, 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 android.app.wallpaper;
+
+parcelable WallpaperInstance;
diff --git a/core/java/android/app/wallpaper/WallpaperInstance.java b/core/java/android/app/wallpaper/WallpaperInstance.java
new file mode 100644
index 000000000000..48a649b87bfb
--- /dev/null
+++ b/core/java/android/app/wallpaper/WallpaperInstance.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 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 android.app.wallpaper;
+
+import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
+
+import android.annotation.FlaggedApi;
+import android.app.WallpaperInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Describes a wallpaper that has been set as a current wallpaper.
+ *
+ * <p>This class is used by {@link android.app.WallpaperManager} to store information about a
+ * wallpaper that is currently in use. Because it has been set as an active wallpaper it offers
+ * some guarantees that {@link WallpaperDescription} does not:
+ * <ul>
+ * <li>It contains the {@link WallpaperInfo} corresponding to the
+ * {@link android.content.ComponentName}</li> specified in the description
+ * <li>{@link #getId()} is guaranteed to be non-null</li>
+ * </ul>
+ * </p>
+ */
+@FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+public final class WallpaperInstance implements Parcelable {
+ private static final String DEFAULT_ID = "default_id";
+ @Nullable private final WallpaperInfo mInfo;
+ @NonNull private final WallpaperDescription mDescription;
+ @Nullable private final String mIdOverride;
+
+ /**
+ * Create a WallpaperInstance for the wallpaper given by {@link WallpaperDescription}.
+ *
+ * @param info the live wallpaper info for this wallpaper, or null if static
+ * @param description description of the wallpaper for this instance
+ */
+ public WallpaperInstance(@Nullable WallpaperInfo info,
+ @NonNull WallpaperDescription description) {
+ this(info, description, null);
+ }
+
+ /**
+ * Create a WallpaperInstance for the wallpaper given by {@link WallpaperDescription}.
+ *
+ * This is provided as an escape hatch to provide an explicit id for cases where the
+ * description id and {@link WallpaperInfo} are both {@code null}.
+ *
+ * @param info the live wallpaper info for this wallpaper, or null if static
+ * @param description description of the wallpaper for this instance
+ * @param idOverride optional id to override the value given in the description
+ *
+ * @hide
+ */
+ public WallpaperInstance(@Nullable WallpaperInfo info,
+ @NonNull WallpaperDescription description, @Nullable String idOverride) {
+ mInfo = info;
+ mDescription = description;
+ mIdOverride = idOverride;
+ }
+
+ /** @return the live wallpaper info, or {@code null} if static */
+ @Nullable public WallpaperInfo getInfo() {
+ return mInfo;
+ }
+
+ /**
+ * See {@link WallpaperDescription.Builder#getId()} for rules about id uniqueness.
+ *
+ * @return the ID of the wallpaper instance if given by the wallpaper description, otherwise a
+ * default value
+ */
+ @NonNull public String getId() {
+ if (mIdOverride != null) {
+ return mIdOverride;
+ } else if (mDescription.getId() != null) {
+ return mDescription.getId();
+ } else if (mInfo != null) {
+ return mInfo.getComponent().flattenToString();
+ } else {
+ return DEFAULT_ID;
+ }
+ }
+
+ /** @return the description for this wallpaper */
+ @NonNull public WallpaperDescription getDescription() {
+ return mDescription;
+ }
+
+ ////// Comparison overrides
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof WallpaperInstance that)) return false;
+ if (mInfo == null) {
+ return that.mInfo == null && Objects.equals(getId(), that.getId());
+ } else {
+ return that.mInfo != null
+ && Objects.equals(mInfo.getComponent(), that.mInfo.getComponent())
+ && Objects.equals(getId(), that.getId());
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return (mInfo != null) ? Objects.hash(mInfo.getComponent(), getId()) : Objects.hash(
+ getId());
+ }
+
+ ////// Parcelable implementation
+
+ WallpaperInstance(@NonNull Parcel in) {
+ mInfo = in.readTypedObject(WallpaperInfo.CREATOR);
+ mDescription = WallpaperDescription.CREATOR.createFromParcel(in);
+ mIdOverride = in.readString8();
+ }
+
+ @NonNull
+ public static final Creator<WallpaperInstance> CREATOR = new Creator<>() {
+ @Override
+ public WallpaperInstance createFromParcel(Parcel in) {
+ return new WallpaperInstance(in);
+ }
+
+ @Override
+ public WallpaperInstance[] newArray(int size) {
+ return new WallpaperInstance[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(mInfo, flags);
+ mDescription.writeToParcel(dest, flags);
+ dest.writeString8(mIdOverride);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index da7da7ddc3c5..9675d6bb1aba 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -46,6 +46,7 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.BIND_WALLPAPER"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
@@ -1770,6 +1771,25 @@
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
</intent-filter>
</activity>
+
+ <!-- Used by WallpaperInstanceTest -->
+ <service
+ android:name="stub.StubWallpaperService"
+ android:directBootAware="true"
+ android:enabled="true"
+ android:exported="true"
+ android:label="Stub wallpaper"
+ android:permission="android.permission.BIND_WALLPAPER">
+
+ <intent-filter>
+ <action android:name="android.service.wallpaper.WallpaperService" />
+ </intent-filter>
+
+ <!-- Link to XML that defines the wallpaper info. -->
+ <meta-data
+ android:name="android.service.wallpaper"
+ android:resource="@xml/livewallpaper" />
+ </service>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/core/tests/coretests/res/xml/livewallpaper.xml b/core/tests/coretests/res/xml/livewallpaper.xml
new file mode 100644
index 000000000000..3b3f4a793c63
--- /dev/null
+++ b/core/tests/coretests/res/xml/livewallpaper.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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
+ -->
+<wallpaper
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsSliceUri="content://com.android.frameworks.coretests/slice"
+ android:supportsAmbientMode="true"/>
diff --git a/core/tests/coretests/src/android/app/wallpaper/OWNERS b/core/tests/coretests/src/android/app/wallpaper/OWNERS
new file mode 100644
index 000000000000..93b068dabb4f
--- /dev/null
+++ b/core/tests/coretests/src/android/app/wallpaper/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/core/java/android/service/wallpaper/OWNERS
diff --git a/core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java b/core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java
new file mode 100644
index 000000000000..7c79ba709895
--- /dev/null
+++ b/core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 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 android.app.wallpaper;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class WallpaperDescriptionTest {
+ private final ComponentName mTestComponent = new ComponentName("fakePackage", "fakeClass");
+
+ @Test
+ public void equals_ignoresIrrelevantFields() {
+ String id = "fakeId";
+ WallpaperDescription desc1 = new WallpaperDescription.Builder().setComponent(
+ mTestComponent).setId(id).setTitle("fake one").build();
+ WallpaperDescription desc2 = new WallpaperDescription.Builder().setComponent(
+ mTestComponent).setId(id).setTitle("fake different").build();
+
+ assertThat(desc1).isEqualTo(desc2);
+ }
+
+ @Test
+ public void hash_ignoresIrrelevantFields() {
+ String id = "fakeId";
+ WallpaperDescription desc1 = new WallpaperDescription.Builder().setComponent(
+ mTestComponent).setId(id).setTitle("fake one").build();
+ WallpaperDescription desc2 = new WallpaperDescription.Builder().setComponent(
+ mTestComponent).setId(id).setTitle("fake different").build();
+
+ assertThat(desc1.hashCode()).isEqualTo(desc2.hashCode());
+ }
+
+ @Test
+ public void parcel_roundTripSucceeds() {
+ final Uri thumbnail = Uri.parse("http://www.bogus.com/thumbnail");
+ final List<CharSequence> description = List.of("line1", "line2");
+ final Uri contextUri = Uri.parse("http://www.bogus.com/contextUri");
+ final PersistableBundle content = new PersistableBundle();
+ content.putString("ckey", "cvalue");
+ WallpaperDescription source = new WallpaperDescription.Builder().setComponent(
+ mTestComponent).setId("fakeId").setThumbnail(thumbnail).setTitle(
+ "Fake title").setDescription(description).setContextUri(
+ contextUri).setContextDescription("Context description").setContent(
+ content).build();
+
+ Parcel parcel = Parcel.obtain();
+ source.writeToParcel(parcel, 0);
+ // Reset parcel for reading
+ parcel.setDataPosition(0);
+ WallpaperDescription destination = WallpaperDescription.CREATOR.createFromParcel(parcel);
+
+ assertThat(destination.getComponent()).isEqualTo(source.getComponent());
+ assertThat(destination.getId()).isEqualTo(source.getId());
+ assertThat(destination.getThumbnail()).isEqualTo(source.getThumbnail());
+ assertWithMessage("title mismatch").that(
+ CharSequence.compare(destination.getTitle(), source.getTitle())).isEqualTo(0);
+ assertThat(destination.getDescription()).hasSize(source.getDescription().size());
+ for (int i = 0; i < destination.getDescription().size(); i++) {
+ CharSequence strDest = destination.getDescription().get(i);
+ CharSequence strSrc = source.getDescription().get(i);
+ assertWithMessage("description string mismatch")
+ .that(CharSequence.compare(strDest, strSrc)).isEqualTo(0);
+ }
+ assertThat(destination.getContextUri()).isEqualTo(source.getContextUri());
+ assertWithMessage("context description mismatch").that(
+ CharSequence.compare(destination.getContextDescription(),
+ source.getContextDescription())).isEqualTo(0);
+ assertThat(destination.getContent()).isNotNull();
+ assertThat(destination.getContent().getString("ckey")).isEqualTo(
+ source.getContent().getString("ckey"));
+ }
+
+ @Test
+ public void parcel_roundTripSucceeds_withNulls() {
+ WallpaperDescription source = new WallpaperDescription.Builder().build();
+
+ Parcel parcel = Parcel.obtain();
+ source.writeToParcel(parcel, 0);
+ // Reset parcel for reading
+ parcel.setDataPosition(0);
+ WallpaperDescription destination = WallpaperDescription.CREATOR.createFromParcel(parcel);
+
+ assertThat(destination.getComponent()).isEqualTo(source.getComponent());
+ assertThat(destination.getId()).isEqualTo(source.getId());
+ assertThat(destination.getThumbnail()).isEqualTo(source.getThumbnail());
+ assertThat(destination.getTitle()).isNull();
+ assertThat(destination.getDescription()).hasSize(source.getDescription().size());
+ for (int i = 0; i < destination.getDescription().size(); i++) {
+ CharSequence strDest = destination.getDescription().get(i);
+ CharSequence strSrc = source.getDescription().get(i);
+ assertWithMessage("description string mismatch")
+ .that(CharSequence.compare(strDest, strSrc)).isEqualTo(0);
+ }
+ assertThat(destination.getContextUri()).isEqualTo(source.getContextUri());
+ assertThat(destination.getContextDescription()).isNull();
+ assertThat(destination.getContent()).isNotNull();
+ assertThat(destination.getContent().keySet()).isEmpty();
+ }
+
+ @Test
+ public void toBuilder_succeeds() {
+ final String sourceId = "sourceId";
+ final Uri thumbnail = Uri.parse("http://www.bogus.com/thumbnail");
+ final List<CharSequence> description = List.of("line1", "line2");
+ final Uri contextUri = Uri.parse("http://www.bogus.com/contextUri");
+ final PersistableBundle content = new PersistableBundle();
+ content.putString("ckey", "cvalue");
+ final String destinationId = "destinationId";
+ WallpaperDescription source = new WallpaperDescription.Builder().setComponent(
+ mTestComponent).setId(sourceId).setThumbnail(thumbnail).setTitle(
+ "Fake title").setDescription(description).setContextUri(
+ contextUri).setContextDescription("Context description").setContent(
+ content).build();
+
+ WallpaperDescription destination = source.toBuilder().setId(destinationId).build();
+
+ assertThat(destination.getComponent()).isEqualTo(source.getComponent());
+ assertThat(destination.getId()).isEqualTo(destinationId);
+ assertThat(destination.getThumbnail()).isEqualTo(source.getThumbnail());
+ assertWithMessage("title mismatch").that(
+ CharSequence.compare(destination.getTitle(), source.getTitle())).isEqualTo(0);
+ assertThat(destination.getDescription()).hasSize(source.getDescription().size());
+ for (int i = 0; i < destination.getDescription().size(); i++) {
+ CharSequence strDest = destination.getDescription().get(i);
+ CharSequence strSrc = source.getDescription().get(i);
+ assertWithMessage("description string mismatch")
+ .that(CharSequence.compare(strDest, strSrc)).isEqualTo(0);
+ }
+ assertThat(destination.getContextUri()).isEqualTo(source.getContextUri());
+ assertWithMessage("context description mismatch").that(
+ CharSequence.compare(destination.getContextDescription(),
+ source.getContextDescription())).isEqualTo(0);
+ assertThat(destination.getContent()).isNotNull();
+ assertThat(destination.getContent().getString("ckey")).isEqualTo(
+ source.getContent().getString("ckey"));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/wallpaper/WallpaperInstanceTest.java b/core/tests/coretests/src/android/app/wallpaper/WallpaperInstanceTest.java
new file mode 100644
index 000000000000..d5a893717ad1
--- /dev/null
+++ b/core/tests/coretests/src/android/app/wallpaper/WallpaperInstanceTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 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 android.app.wallpaper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.WallpaperInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Parcel;
+import android.service.wallpaper.WallpaperService;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class WallpaperInstanceTest {
+ @Test
+ public void equals_bothNullInfo_sameId_isTrue() {
+ WallpaperDescription description = new WallpaperDescription.Builder().setId("123").build();
+ WallpaperInstance instance1 = new WallpaperInstance(null, description);
+ WallpaperInstance instance2 = new WallpaperInstance(null, description);
+
+ assertThat(instance1).isEqualTo(instance2);
+ }
+
+ @Test
+ public void equals_bothNullInfo_differentIds_isFalse() {
+ WallpaperDescription description1 = new WallpaperDescription.Builder().setId("123").build();
+ WallpaperDescription description2 = new WallpaperDescription.Builder().setId("456").build();
+ WallpaperInstance instance1 = new WallpaperInstance(null, description1);
+ WallpaperInstance instance2 = new WallpaperInstance(null, description2);
+
+ assertThat(instance1).isNotEqualTo(instance2);
+ }
+
+ @Test
+ public void equals_singleNullInfo_isFalse() throws Exception {
+ WallpaperDescription description = new WallpaperDescription.Builder().build();
+ WallpaperInstance instance1 = new WallpaperInstance(null, description);
+ WallpaperInstance instance2 = new WallpaperInstance(makeWallpaperInfo(), description);
+
+ assertThat(instance1).isNotEqualTo(instance2);
+ }
+
+ @Test
+ public void equals_sameInfoAndId_isTrue() throws Exception {
+ WallpaperDescription description = new WallpaperDescription.Builder().setId("123").build();
+ WallpaperInstance instance1 = new WallpaperInstance(makeWallpaperInfo(), description);
+ WallpaperInstance instance2 = new WallpaperInstance(makeWallpaperInfo(), description);
+
+ assertThat(instance1).isEqualTo(instance2);
+ }
+
+ @Test
+ public void equals_sameInfo_differentIds_isFalse() throws Exception {
+ WallpaperDescription description1 = new WallpaperDescription.Builder().setId("123").build();
+ WallpaperDescription description2 = new WallpaperDescription.Builder().setId("456").build();
+ WallpaperInstance instance1 = new WallpaperInstance(makeWallpaperInfo(), description1);
+ WallpaperInstance instance2 = new WallpaperInstance(makeWallpaperInfo(), description2);
+
+ assertThat(instance1).isNotEqualTo(instance2);
+ }
+
+ @Test
+ public void hash_nullInfo_works() {
+ WallpaperDescription description1 = new WallpaperDescription.Builder().setId("123").build();
+ WallpaperDescription description2 = new WallpaperDescription.Builder().setId("456").build();
+ WallpaperInstance base = new WallpaperInstance(null, description1);
+ WallpaperInstance sameId = new WallpaperInstance(null, description1);
+ WallpaperInstance differentId = new WallpaperInstance(null, description2);
+
+ assertThat(base.hashCode()).isEqualTo(sameId.hashCode());
+ assertThat(base.hashCode()).isNotEqualTo(differentId.hashCode());
+ }
+
+ @Test
+ public void hash_withInfo_works() throws Exception {
+ WallpaperDescription description1 = new WallpaperDescription.Builder().setId("123").build();
+ WallpaperDescription description2 = new WallpaperDescription.Builder().setId("456").build();
+ WallpaperInstance base = new WallpaperInstance(makeWallpaperInfo(), description1);
+ WallpaperInstance sameId = new WallpaperInstance(makeWallpaperInfo(), description1);
+ WallpaperInstance differentId = new WallpaperInstance(makeWallpaperInfo(), description2);
+
+ assertThat(base.hashCode()).isEqualTo(sameId.hashCode());
+ assertThat(base.hashCode()).isNotEqualTo(differentId.hashCode());
+ }
+
+ @Test
+ public void id_fromOverride() throws Exception {
+ final String id = "override";
+ WallpaperInstance instance = new WallpaperInstance(makeWallpaperInfo(),
+ new WallpaperDescription.Builder().setId("abc123").build(), id);
+
+ assertThat(instance.getId()).isEqualTo(id);
+ }
+
+ @Test
+ public void id_fromDescription() throws Exception {
+ final String id = "abc123";
+ WallpaperInstance instance = new WallpaperInstance(makeWallpaperInfo(),
+ new WallpaperDescription.Builder().setId(id).build());
+
+ assertThat(instance.getId()).isEqualTo(id);
+ }
+
+ @Test
+ public void id_fromComponent() throws Exception {
+ WallpaperInfo info = makeWallpaperInfo();
+ WallpaperInstance instance = new WallpaperInstance(info,
+ new WallpaperDescription.Builder().build());
+
+ assertThat(instance.getId()).isEqualTo(info.getComponent().flattenToString());
+ }
+
+ @Test
+ public void id_default() {
+ WallpaperInstance instance = new WallpaperInstance(null,
+ new WallpaperDescription.Builder().build());
+
+ assertThat(instance.getId()).isNotNull();
+ }
+
+ @Test
+ public void parcel_roundTripSucceeds() throws Exception {
+ WallpaperInstance source = new WallpaperInstance(makeWallpaperInfo(),
+ new WallpaperDescription.Builder().build());
+
+ Parcel parcel = Parcel.obtain();
+ source.writeToParcel(parcel, 0);
+ // Reset parcel for reading
+ parcel.setDataPosition(0);
+
+ WallpaperInstance destination = WallpaperInstance.CREATOR.createFromParcel(parcel);
+
+ assertThat(destination.getInfo()).isNotNull();
+ assertThat(destination.getInfo().getComponent()).isEqualTo(source.getInfo().getComponent());
+ assertThat(destination.getId()).isEqualTo(source.getId());
+ assertThat(destination.getDescription()).isEqualTo(source.getDescription());
+ }
+
+ @Test
+ public void parcel_roundTripSucceeds_withNulls() {
+ WallpaperInstance source = new WallpaperInstance(null,
+ new WallpaperDescription.Builder().build());
+
+ Parcel parcel = Parcel.obtain();
+ source.writeToParcel(parcel, 0);
+ // Reset parcel for reading
+ parcel.setDataPosition(0);
+
+ WallpaperInstance destination = WallpaperInstance.CREATOR.createFromParcel(parcel);
+
+ assertThat(destination.getInfo()).isEqualTo(source.getInfo());
+ assertThat(destination.getId()).isEqualTo(source.getId());
+ assertThat(destination.getDescription()).isEqualTo(source.getDescription());
+ }
+
+ private WallpaperInfo makeWallpaperInfo() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
+ intent.setPackage("com.android.frameworks.coretests");
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> result = pm.queryIntentServices(intent, PackageManager.GET_META_DATA);
+ assertThat(result).hasSize(1);
+ ResolveInfo info = result.getFirst();
+ return new WallpaperInfo(context, info);
+ }
+}