diff options
| -rw-r--r-- | core/api/current.txt | 41 | ||||
| -rw-r--r-- | core/java/android/app/wallpaper.aconfig | 8 | ||||
| -rw-r--r-- | core/java/android/app/wallpaper/WallpaperDescription.aidl | 20 | ||||
| -rw-r--r-- | core/java/android/app/wallpaper/WallpaperDescription.java | 318 | ||||
| -rw-r--r-- | core/java/android/app/wallpaper/WallpaperInstance.aidl | 20 | ||||
| -rw-r--r-- | core/java/android/app/wallpaper/WallpaperInstance.java | 162 | ||||
| -rw-r--r-- | core/tests/coretests/AndroidManifest.xml | 20 | ||||
| -rw-r--r-- | core/tests/coretests/res/xml/livewallpaper.xml | 20 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/app/wallpaper/OWNERS | 1 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/app/wallpaper/WallpaperDescriptionTest.java | 162 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/app/wallpaper/WallpaperInstanceTest.java | 189 |
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); + } +} |