Placeholder wire format to use inner bundle

We don't want placeholders to break existing WFs which might use
the usually hidden inner a.s.w.c.ComplicationData. Previously
the wire format of a NoDataComplication data stored the
placeholder in the usual fields (problematic because old WFs
would render the placeholder string which isn't intended), instead
we now use an inner bundle to fully isolate.

Test: Manual testing
Change-Id: I3d3abd41578a9e84fed24b8ddfe5176b116d1196
diff --git a/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataTimeline.kt b/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataTimeline.kt
index 47e166d..868b87e 100644
--- a/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataTimeline.kt
+++ b/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataTimeline.kt
@@ -137,8 +137,8 @@
                     complicationData.type == defaultComplicationData.type
                 ) {
                     "TimelineEntry's complicationData must have the same type as the " +
-                        "defaultComplicationData. Found ${complicationData.type} expected " +
-                        "${defaultComplicationData.type}."
+                        "defaultComplicationData or be NoDataComplicationData. Found " +
+                        "${complicationData.type} expected ${defaultComplicationData.type}."
                 }
 
                 require(!complicationData.hasPlaceholderFields()) {
diff --git a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java
index 0c31e4b..0dad6db 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java
+++ b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java
@@ -262,6 +262,8 @@
     private static final String FIELD_TIMELINE_START_TIME = "TIMELINE_START_TIME";
     private static final String FIELD_TIMELINE_END_TIME = "TIMELINE_END_TIME";
     private static final String FIELD_TIMELINE_ENTRIES = "TIMELINE";
+    private static final String FIELD_TIMELINE_ENTRY_TYPE = "TIMELINE_ENTRY_TYPE";
+    private static final String FIELD_PLACEHOLDER_FIELDS = "PLACEHOLDER_FIELDS";
     private static final String FIELD_PLACEHOLDER_TYPE = "PLACEHOLDER_TYPE";
     private static final String FIELD_DATA_SOURCE = "FIELD_DATA_SOURCE";
 
@@ -351,6 +353,7 @@
                     FIELD_LONG_TITLE,
                     FIELD_MAX_VALUE,
                     FIELD_MIN_VALUE,
+                    FIELD_PLACEHOLDER_FIELDS,
                     FIELD_PLACEHOLDER_TYPE,
                     FIELD_SHORT_TEXT,
                     FIELD_SHORT_TITLE,
@@ -389,19 +392,9 @@
     }
 
     ComplicationData(int type, Bundle fields) {
+        mType = type;
         mFields = fields;
         mFields.setClassLoader(getClass().getClassLoader());
-        // If this is a placeholder, coerce to TYPE_NO_DATA.
-        // If this is defined within a timeline, we assume the type of the outer ComplicationData
-        // applies to all elements in the timeline and we can just use the passed in type. The only
-        // exception is if we get a NO_DATA ComplicationData. In that case, we can check whether
-        // the placeholder type is included the serialization to determine if NO_DATA was passed
-        // in and coerce the type to NO_DATA.
-        if (mFields.containsKey(FIELD_PLACEHOLDER_TYPE)) {
-            mType = TYPE_NO_DATA;
-        } else {
-            mType = type;
-        }
     }
 
     private ComplicationData(@NonNull Parcel in) {
@@ -411,7 +404,7 @@
 
     @RequiresApi(api = Build.VERSION_CODES.P)
     private static class SerializedForm implements Serializable {
-        private static final int VERSION_NUMBER = 5;
+        private static final int VERSION_NUMBER = 6;
 
         @NonNull
         ComplicationData mComplicationData;
@@ -480,9 +473,6 @@
             if (isFieldValidForType(FIELD_END_TIME, type)) {
                 oos.writeLong(mComplicationData.getEndDateTimeMillis());
             }
-            if (isFieldValidForType(FIELD_PLACEHOLDER_TYPE, type)) {
-                oos.writeInt(mComplicationData.getPlaceholderType());
-            }
             if (isFieldValidForType(FIELD_DATA_SOURCE, type)) {
                 ComponentName componentName = mComplicationData.getDataSource();
                 if (componentName == null) {
@@ -500,6 +490,16 @@
             long end = mComplicationData.mFields.getLong(FIELD_TIMELINE_END_TIME, -1);
             oos.writeLong(end);
 
+            if (isFieldValidForType(FIELD_PLACEHOLDER_FIELDS, type)) {
+                ComplicationData placeholder = mComplicationData.getPlaceholder();
+                if (placeholder == null) {
+                    oos.writeBoolean(false);
+                } else {
+                    oos.writeBoolean(true);
+                    new SerializedForm(placeholder).writeObject(oos);
+                }
+            }
+
             // This has to be last, since it's recursive.
             List<ComplicationData> timeline = mComplicationData.getTimelineEntries();
             int timelineLength = (timeline != null) ? timeline.size() : 0;
@@ -572,12 +572,6 @@
             if (isFieldValidForType(FIELD_END_TIME, type)) {
                 fields.putLong(FIELD_END_TIME, ois.readLong());
             }
-            if (isFieldValidForType(FIELD_PLACEHOLDER_TYPE, type)) {
-                int placeholderType = ois.readInt();
-                if (placeholderType != 0) {
-                    fields.putInt(FIELD_PLACEHOLDER_TYPE, placeholderType);
-                }
-            }
             if (isFieldValidForType(FIELD_DATA_SOURCE, type)) {
                 String componentName = ois.readUTF();
                 if (componentName.isEmpty()) {
@@ -598,6 +592,18 @@
             if (end != -1) {
                 fields.putLong(FIELD_TIMELINE_END_TIME, end);
             }
+
+            if (isFieldValidForType(FIELD_PLACEHOLDER_FIELDS, type)) {
+                if (ois.readBoolean()) {
+                    SerializedForm serializedPlaceholder = new SerializedForm();
+                    serializedPlaceholder.readObject(ois);
+                    fields.putInt(FIELD_PLACEHOLDER_TYPE,
+                            serializedPlaceholder.mComplicationData.mType);
+                    fields.putBundle(FIELD_PLACEHOLDER_FIELDS,
+                            serializedPlaceholder.mComplicationData.mFields);
+                }
+            }
+
             int timelineLength = ois.readInt();
             if (timelineLength != 0) {
                 Parcelable[] parcels = new Parcelable[timelineLength];
@@ -731,11 +737,12 @@
         }
         ArrayList<ComplicationData> entries = new ArrayList<>();
         for (Parcelable parcelable : bundles) {
-            // Pass is the type of the outer complication data to the timeline entries by default.
-            // The array should only contain elements of the same type. The only exception is the
-            // NO_DATA type, which is allowed, but the code in the constructor is going to coerce
-            // the type to NO_DATA if necessary.
-            entries.add(new ComplicationData(mType, (Bundle) parcelable));
+            Bundle bundle = (Bundle) parcelable;
+            // Use the serialized FIELD_TIMELINE_ENTRY_TYPE or the outer type if it's not there.
+            // Usually the timeline entry type will be the same as the outer type, unless an entry
+            // contains NoDataComplicationData.
+            int type = bundle.getInt(FIELD_TIMELINE_ENTRY_TYPE, mType);
+            entries.add(new ComplicationData(type, (Bundle) parcelable));
         }
         return entries;
     }
@@ -747,7 +754,13 @@
         } else {
             mFields.putParcelableArray(
                     FIELD_TIMELINE_ENTRIES,
-                    timelineEntries.stream().map(e -> e.mFields).toArray(Parcelable[]::new));
+                    timelineEntries.stream().map(
+                            e -> {
+                                // This supports timeline entry of NoDataComplicationData.
+                                e.mFields.putInt(FIELD_TIMELINE_ENTRY_TYPE, e.mType);
+                                return e.mFields;
+                            }
+                    ).toArray(Parcelable[]::new));
         }
     }
 
@@ -1215,28 +1228,18 @@
     }
 
     /**
-     * Returns true if the ComplicationData contains a placeholder type. I.e. if
-     * {@link #getPlaceholderType} can succeed.
+     * Returns the placeholder ComplicationData if there is one or `null`.
      */
-    public boolean hasPlaceholderType() {
-        try {
-            return isFieldValidForType(FIELD_PLACEHOLDER_TYPE, mType)
-                    && mFields.containsKey(FIELD_PLACEHOLDER_TYPE);
-        } catch (BadParcelableException e) {
-            return false;
-        }
-    }
-
-    /**
-     * Returns the type this complication is a placeholder for.
-     *
-     * <p>Valid only if the type of this complication data is {@link #TYPE_NO_DATA}.
-     * Otherwise returns zero.
-     */
-    @ComplicationType
-    public int getPlaceholderType() {
+    @Nullable
+    public ComplicationData getPlaceholder() {
+        checkFieldValidForType(FIELD_PLACEHOLDER_FIELDS, mType);
         checkFieldValidForType(FIELD_PLACEHOLDER_TYPE, mType);
-        return mFields.getInt(FIELD_PLACEHOLDER_TYPE);
+        if (!mFields.containsKey(FIELD_PLACEHOLDER_FIELDS)
+                || !mFields.containsKey(FIELD_PLACEHOLDER_TYPE)) {
+            return null;
+        }
+        return new ComplicationData(mFields.getInt(FIELD_PLACEHOLDER_TYPE),
+                mFields.getBundle(FIELD_PLACEHOLDER_FIELDS));
     }
 
     /**
@@ -1699,13 +1702,21 @@
         }
 
         /**
-         * Sets the type this complication is a placeholder for.
+         * Sets the placeholder.
          *
          * <p>Returns this Builder to allow chaining.
          */
+        @SuppressLint("SyntheticAccessor")
         @NonNull
-        public Builder setPlaceholderType(@ComplicationType int placeholderType) {
-            putIntField(FIELD_PLACEHOLDER_TYPE, placeholderType);
+        public Builder setPlaceholder(@Nullable ComplicationData placeholder) {
+            if (placeholder == null) {
+                mFields.remove(FIELD_PLACEHOLDER_FIELDS);
+                mFields.remove(FIELD_PLACEHOLDER_TYPE);
+            } else {
+                ComplicationData.checkFieldValidForType(FIELD_PLACEHOLDER_FIELDS, mType);
+                mFields.putBundle(FIELD_PLACEHOLDER_FIELDS, placeholder.mFields);
+                putIntField(FIELD_PLACEHOLDER_TYPE, placeholder.mType);
+            }
             return this;
         }
 
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
index 8520303..5141d8b 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
@@ -86,11 +86,6 @@
     internal open fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
     }
 
-    internal fun asPlaceholderWireComplicationData(): WireComplicationData =
-        WireComplicationDataBuilder(NoDataComplicationData.TYPE.toWireComplicationType()).apply {
-            fillWireComplicationDataBuilder(this)
-        }.build()
-
     /**
      * Returns `true` if any of the fields of this ComplicationData are placeholders. I.e. if any
      * fields are equal to: [ComplicationText.PLACEHOLDER], [SmallImage.PLACEHOLDER],
@@ -135,7 +130,7 @@
     TYPE,
     placeholder?.tapAction,
     cachedWireComplicationData,
-    dataSource = placeholder?.dataSource
+    dataSource = null
 ) {
 
     /** Constructs a NoDataComplicationData without a [placeholder]. */
@@ -170,12 +165,12 @@
         }
         return createWireComplicationDataBuilder().apply {
             if (placeholder == null) {
-                setPlaceholderType(TYPE.toWireComplicationType())
+                setPlaceholder(null)
             } else {
-                setPlaceholderType(placeholder.type.toWireComplicationType())
-                placeholder.fillWireComplicationDataBuilder(this)
+                val builder = placeholder.createWireComplicationDataBuilder()
+                placeholder.fillWireComplicationDataBuilder(builder)
+                setPlaceholder(builder.build())
             }
-            setDataSource(dataSource)
         }.build().also { cachedWireComplicationData = it }
     }
 
@@ -1611,6 +1606,84 @@
     }
 }
 
+internal fun WireComplicationData.toPlaceholderComplicationData(): ComplicationData? = when (type) {
+    NoDataComplicationData.TYPE.toWireComplicationType() -> null
+
+    ShortTextComplicationData.TYPE.toWireComplicationType() -> {
+        ShortTextComplicationData.Builder(
+            shortText!!.toApiComplicationTextPlaceholderAware(),
+            contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
+        ).apply {
+            setTapAction(tapAction)
+            setValidTimeRange(parseTimeRange())
+            setMonochromaticImage(parseIconPlaceholderAware())
+            setTitle(shortTitle?.toApiComplicationTextPlaceholderAware())
+            setDataSource(dataSource)
+        }.build()
+    }
+
+    LongTextComplicationData.TYPE.toWireComplicationType() -> {
+        LongTextComplicationData.Builder(
+            longText!!.toApiComplicationTextPlaceholderAware(),
+            contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
+        ).apply {
+            setTapAction(tapAction)
+            setValidTimeRange(parseTimeRange())
+            setMonochromaticImage(parseIconPlaceholderAware())
+            setSmallImage(parseSmallImagePlaceholderAware())
+            setTitle(longTitle?.toApiComplicationTextPlaceholderAware())
+            setDataSource(dataSource)
+        }.build()
+    }
+
+    RangedValueComplicationData.TYPE.toWireComplicationType() ->
+        RangedValueComplicationData.Builder(
+            value = rangedValue,
+            min = rangedMinValue,
+            max = rangedMaxValue,
+            contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
+        ).apply {
+            setTapAction(tapAction)
+            setValidTimeRange(parseTimeRange())
+            setMonochromaticImage(parseIconPlaceholderAware())
+            setTitle(shortTitle?.toApiComplicationTextPlaceholderAware())
+            setText(shortText?.toApiComplicationTextPlaceholderAware())
+            setDataSource(dataSource)
+        }.build()
+
+    MonochromaticImageComplicationData.TYPE.toWireComplicationType() ->
+        MonochromaticImageComplicationData(
+            parseIconPlaceholderAware()!!,
+            contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY,
+            tapAction,
+            parseTimeRange(),
+            this,
+            dataSource
+        )
+
+    SmallImageComplicationData.TYPE.toWireComplicationType() ->
+        SmallImageComplicationData(
+            parseSmallImagePlaceholderAware()!!,
+            contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY,
+            tapAction,
+            parseTimeRange(),
+            this,
+            dataSource
+        )
+
+    PhotoImageComplicationData.TYPE.toWireComplicationType() ->
+        PhotoImageComplicationData(
+            parseLargeImagePlaceholderAware()!!,
+            contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY,
+            tapAction,
+            parseTimeRange(),
+            this,
+            dataSource
+        )
+
+    else -> null
+}
+
 /**
  * @hide
  */
@@ -1619,95 +1692,9 @@
     val wireComplicationData = this
     return when (type) {
         NoDataComplicationData.TYPE.toWireComplicationType() -> {
-            if (hasPlaceholderType()) {
-                val placeholder = when (placeholderType) {
-                    NoDataComplicationData.TYPE.toWireComplicationType() -> null
-
-                    ShortTextComplicationData.TYPE.toWireComplicationType() -> {
-                        ShortTextComplicationData.Builder(
-                            shortText!!.toApiComplicationTextPlaceholderAware(),
-                            contentDescription?.toApiComplicationText()
-                                ?: ComplicationText.EMPTY
-                        ).apply {
-                            setMonochromaticImage(parseIconPlaceholderAware())
-                            setTitle(shortTitle?.toApiComplicationTextPlaceholderAware())
-                            setDataSource(dataSource)
-                        }.build()
-                    }
-
-                    LongTextComplicationData.TYPE.toWireComplicationType() -> {
-                        LongTextComplicationData.Builder(
-                            longText!!.toApiComplicationTextPlaceholderAware(),
-                            contentDescription?.toApiComplicationText()
-                                ?: ComplicationText.EMPTY
-                        ).apply {
-                            setMonochromaticImage(parseIconPlaceholderAware())
-                            setSmallImage(parseSmallImagePlaceholderAware())
-                            setTitle(longTitle?.toApiComplicationTextPlaceholderAware())
-                            setDataSource(dataSource)
-                        }.build()
-                    }
-
-                    RangedValueComplicationData.TYPE.toWireComplicationType() ->
-                        RangedValueComplicationData.Builder(
-                            value = rangedValue,
-                            min = rangedMinValue,
-                            max = rangedMaxValue,
-                            contentDescription?.toApiComplicationText()
-                                ?: ComplicationText.EMPTY
-                        ).apply {
-                            setMonochromaticImage(parseIconPlaceholderAware())
-                            setTitle(shortTitle?.toApiComplicationTextPlaceholderAware())
-                            setText(shortText?.toApiComplicationTextPlaceholderAware())
-                            setDataSource(dataSource)
-                        }.build()
-
-                    MonochromaticImageComplicationData.TYPE.toWireComplicationType() ->
-                        MonochromaticImageComplicationData(
-                            parseIconPlaceholderAware()!!,
-                            contentDescription?.toApiComplicationText()
-                                ?: ComplicationText.EMPTY,
-                            tapAction,
-                            parseTimeRange(),
-                            wireComplicationData,
-                            dataSource
-                        )
-
-                    SmallImageComplicationData.TYPE.toWireComplicationType() ->
-                        SmallImageComplicationData(
-                            parseSmallImagePlaceholderAware()!!,
-                            contentDescription?.toApiComplicationText()
-                                ?: ComplicationText.EMPTY,
-                            tapAction,
-                            parseTimeRange(),
-                            wireComplicationData,
-                            dataSource
-                        )
-
-                    PhotoImageComplicationData.TYPE.toWireComplicationType() ->
-                        PhotoImageComplicationData(
-                            parseLargeImagePlaceholderAware()!!,
-                            contentDescription?.toApiComplicationText()
-                                ?: ComplicationText.EMPTY,
-                            tapAction,
-                            parseTimeRange(),
-                            wireComplicationData,
-                            dataSource
-                        )
-
-                    else -> throw IllegalStateException(
-                        "Unrecognized placeholderType $placeholderType"
-                    )
-                }
-
-                if (placeholder != null) {
-                    NoDataComplicationData(placeholder)
-                } else {
-                    NoDataComplicationData()
-                }
-            } else {
-                NoDataComplicationData()
-            }
+            placeholder?.toPlaceholderComplicationData() ?.let {
+                NoDataComplicationData(it)
+            } ?: NoDataComplicationData()
         }
 
         EmptyComplicationData.TYPE.toWireComplicationType() -> EmptyComplicationData()
diff --git a/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataTest.kt b/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataTest.kt
index e583843..2b79589 100644
--- a/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataTest.kt
+++ b/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataTest.kt
@@ -1095,16 +1095,19 @@
 
     @Test
     public fun timelineEntryCollectionWithPlaceholder() {
+        val placeholderString =
+            androidx.wear.watchface.complications.data.ComplicationText.PLACEHOLDER_STRING
         val data =
             ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
-                .setLongText(ComplicationText.plainText(
-                    androidx.wear.watchface.complications.data.ComplicationText.PLACEHOLDER_STRING))
+                .setLongText(ComplicationText.plainText(placeholderString))
                 .build()
         val timelineEntry =
             ComplicationData.Builder(ComplicationData.TYPE_NO_DATA)
-                .setPlaceholderType(ComplicationData.TYPE_LONG_TEXT)
-                .setLongText(ComplicationText.plainText(
-                    androidx.wear.watchface.complications.data.ComplicationText.PLACEHOLDER_STRING))
+                .setPlaceholder(
+                    ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT)
+                        .setLongText(ComplicationText.plainText(placeholderString))
+                            .build()
+                )
                 .build()
         timelineEntry.timelineStartEpochSecond = 100
         timelineEntry.timelineEndEpochSecond = 1000
@@ -1112,7 +1115,7 @@
 
         val entry = data.timelineEntries!!.first()
         Truth.assertThat(entry.type).isEqualTo(ComplicationData.TYPE_NO_DATA)
-        Truth.assertThat(entry.placeholderType).isEqualTo(ComplicationData.TYPE_LONG_TEXT)
+        Truth.assertThat(entry.placeholder!!.type).isEqualTo(ComplicationData.TYPE_LONG_TEXT)
     }
 
     private companion object {
diff --git a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
index 19a5e93..f1945a4 100644
--- a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
+++ b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
@@ -45,7 +45,7 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_NO_DATA)
+                    .setPlaceholder(null)
                     .build()
             )
         testRoundTripConversions(data)
@@ -470,12 +470,17 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_SHORT_TEXT)
-                    .setShortText(ComplicationText.PLACEHOLDER.toWireComplicationText())
-                    .setShortTitle(ComplicationText.PLACEHOLDER.toWireComplicationText())
-                    .setIcon(createPlaceholderIcon())
-                    .setContentDescription(WireComplicationText.plainText("content description"))
-                    .setDataSource(dataSourceA)
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_SHORT_TEXT)
+                            .setShortText(ComplicationText.PLACEHOLDER.toWireComplicationText())
+                            .setShortTitle(ComplicationText.PLACEHOLDER.toWireComplicationText())
+                            .setIcon(createPlaceholderIcon())
+                            .setContentDescription(
+                                WireComplicationText.plainText("content description")
+                            )
+                            .setDataSource(dataSourceA)
+                            .build()
+                    )
                     .build()
             )
         testRoundTripConversions(data)
@@ -533,10 +538,15 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_LONG_TEXT)
-                    .setLongText(WireComplicationText.plainText("text"))
-                    .setContentDescription(WireComplicationText.plainText("content description"))
-                    .setDataSource(dataSourceA)
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_LONG_TEXT)
+                            .setLongText(WireComplicationText.plainText("text"))
+                            .setContentDescription(
+                                WireComplicationText.plainText("content description")
+                            )
+                            .setDataSource(dataSourceA)
+                            .build()
+                    )
                     .build()
             )
         testRoundTripConversions(data)
@@ -592,13 +602,18 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_RANGED_VALUE)
-                    .setRangedValue(RangedValueComplicationData.PLACEHOLDER)
-                    .setRangedMinValue(0f)
-                    .setRangedMaxValue(100f)
-                    .setShortText(ComplicationText.PLACEHOLDER.toWireComplicationText())
-                    .setContentDescription(WireComplicationText.plainText("content description"))
-                    .setDataSource(dataSourceA)
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
+                            .setRangedValue(RangedValueComplicationData.PLACEHOLDER)
+                            .setRangedMinValue(0f)
+                            .setRangedMaxValue(100f)
+                            .setShortText(ComplicationText.PLACEHOLDER.toWireComplicationText())
+                            .setContentDescription(
+                                WireComplicationText.plainText("content description")
+                            )
+                            .setDataSource(dataSourceA)
+                            .build()
+                    )
                     .build()
             )
         testRoundTripConversions(data)
@@ -656,10 +671,15 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_ICON)
-                    .setIcon(createPlaceholderIcon())
-                    .setContentDescription(WireComplicationText.plainText("content description"))
-                    .setDataSource(dataSourceA)
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_ICON)
+                            .setIcon(createPlaceholderIcon())
+                            .setContentDescription(
+                                WireComplicationText.plainText("content description")
+                            )
+                            .setDataSource(dataSourceA)
+                            .build()
+                    )
                     .build()
             )
         testRoundTripConversions(data)
@@ -713,11 +733,16 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_SMALL_IMAGE)
-                    .setSmallImage(createPlaceholderIcon())
-                    .setSmallImageStyle(WireComplicationData.IMAGE_STYLE_ICON)
-                    .setContentDescription(WireComplicationText.plainText("content description"))
-                    .setDataSource(dataSourceA)
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_SMALL_IMAGE)
+                            .setSmallImage(createPlaceholderIcon())
+                            .setSmallImageStyle(WireComplicationData.IMAGE_STYLE_ICON)
+                            .setContentDescription(
+                                WireComplicationText.plainText("content description")
+                            )
+                            .setDataSource(dataSourceA)
+                            .build()
+                    )
                     .build()
             )
         testRoundTripConversions(data)
@@ -771,10 +796,15 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_LARGE_IMAGE)
-                    .setLargeImage(createPlaceholderIcon())
-                    .setContentDescription(WireComplicationText.plainText("content description"))
-                    .setDataSource(dataSourceA)
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_LARGE_IMAGE)
+                            .setLargeImage(createPlaceholderIcon())
+                            .setContentDescription(
+                                WireComplicationText.plainText("content description")
+                            )
+                            .setDataSource(dataSourceA)
+                            .build()
+                    )
                     .build()
             )
         testRoundTripConversions(data)
@@ -842,7 +872,7 @@
     public fun noDataComplicationData() {
         assertRoundtrip(
             WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                .setPlaceholderType(WireComplicationData.TYPE_NO_DATA).build(),
+                .setPlaceholder(null).build(),
             ComplicationType.NO_DATA
         )
     }
@@ -953,11 +983,16 @@
         val icon = Icon.createWithContentUri("someuri")
         assertRoundtrip(
             WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                .setPlaceholderType(WireComplicationData.TYPE_SHORT_TEXT)
-                .setContentDescription(WireComplicationText.plainText("content description"))
-                .setShortText(WireComplicationText.plainText("text"))
-                .setShortTitle(WireComplicationText.plainText("title"))
-                .setIcon(icon)
+                .setPlaceholder(
+                    WireComplicationDataBuilder(WireComplicationData.TYPE_SHORT_TEXT)
+                        .setContentDescription(
+                            WireComplicationText.plainText("content description")
+                        )
+                        .setShortText(WireComplicationText.plainText("text"))
+                        .setShortTitle(WireComplicationText.plainText("title"))
+                        .setIcon(icon)
+                        .build()
+                )
                 .build(),
             ComplicationType.NO_DATA
         )
@@ -968,11 +1003,16 @@
         val icon = Icon.createWithContentUri("someuri")
         assertRoundtrip(
             WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                .setPlaceholderType(WireComplicationData.TYPE_LONG_TEXT)
-                .setContentDescription(WireComplicationText.plainText("content description"))
-                .setLongText(WireComplicationText.plainText("text"))
-                .setLongTitle(WireComplicationText.plainText("title"))
-                .setIcon(icon)
+                .setPlaceholder(
+                    WireComplicationDataBuilder(WireComplicationData.TYPE_LONG_TEXT)
+                        .setContentDescription(
+                            WireComplicationText.plainText("content description")
+                        )
+                        .setLongText(WireComplicationText.plainText("text"))
+                        .setLongTitle(WireComplicationText.plainText("title"))
+                        .setIcon(icon)
+                        .build()
+                )
                 .build(),
             ComplicationType.NO_DATA
         )
@@ -983,13 +1023,18 @@
         val icon = Icon.createWithContentUri("someuri")
         assertRoundtrip(
             WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                .setPlaceholderType(WireComplicationData.TYPE_RANGED_VALUE)
-                .setContentDescription(WireComplicationText.plainText("content description"))
-                .setRangedValue(75f)
-                .setRangedMinValue(0f)
-                .setRangedMaxValue(100f)
-                .setShortTitle(WireComplicationText.plainText("battery"))
-                .setIcon(icon)
+                .setPlaceholder(
+                    WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
+                        .setContentDescription(
+                            WireComplicationText.plainText("content description")
+                        )
+                        .setRangedValue(75f)
+                        .setRangedMinValue(0f)
+                        .setRangedMaxValue(100f)
+                        .setShortTitle(WireComplicationText.plainText("battery"))
+                        .setIcon(icon)
+                        .build()
+                )
                 .build(),
             ComplicationType.NO_DATA
         )
@@ -1000,10 +1045,15 @@
         val icon = Icon.createWithContentUri("someuri")
         assertRoundtrip(
             WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                .setPlaceholderType(WireComplicationData.TYPE_SMALL_IMAGE)
-                .setSmallImage(icon)
-                .setSmallImageStyle(WireComplicationData.IMAGE_STYLE_PHOTO)
-                .setContentDescription(WireComplicationText.plainText("content description"))
+                .setPlaceholder(
+                    WireComplicationDataBuilder(WireComplicationData.TYPE_SMALL_IMAGE)
+                        .setSmallImage(icon)
+                        .setSmallImageStyle(WireComplicationData.IMAGE_STYLE_PHOTO)
+                        .setContentDescription(
+                            WireComplicationText.plainText("content description")
+                        )
+                        .build()
+                )
                 .build(),
             ComplicationType.NO_DATA
         )
@@ -1014,9 +1064,14 @@
         val icon = Icon.createWithContentUri("someuri")
         assertRoundtrip(
             WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                .setPlaceholderType(WireComplicationData.TYPE_ICON)
-                .setIcon(icon)
-                .setContentDescription(WireComplicationText.plainText("content description"))
+                .setPlaceholder(
+                    WireComplicationDataBuilder(WireComplicationData.TYPE_ICON)
+                        .setIcon(icon)
+                        .setContentDescription(
+                            WireComplicationText.plainText("content description")
+                        )
+                        .build()
+                )
                 .build(),
             ComplicationType.NO_DATA
         )
@@ -1105,14 +1160,14 @@
     }
 
     @Test
-    public fun NoDataComplicationData() {
+    public fun noDataComplicationData() {
         assertThat(
             NoDataComplicationData(
                 ShortTextComplicationData.Builder(
                     ComplicationText.PLACEHOLDER,
                     ComplicationText.EMPTY
                 ).setTapAction(mPendingIntent).build()
-            ).asWireComplicationData().tapAction
+            ).asWireComplicationData().placeholder?.tapAction
         ).isEqualTo(mPendingIntent)
     }
 }
@@ -1325,8 +1380,11 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_SHORT_TEXT)
-                    .setShortText(WireComplicationText.plainText("text"))
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_SHORT_TEXT)
+                            .setShortText(WireComplicationText.plainText("text"))
+                            .build()
+                    )
                     .build()
             )
     }
@@ -1340,8 +1398,11 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_LONG_TEXT)
-                    .setLongText(WireComplicationText.plainText("text"))
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_LONG_TEXT)
+                            .setLongText(WireComplicationText.plainText("text"))
+                            .build()
+                    )
                     .build()
             )
     }
@@ -1359,10 +1420,13 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_RANGED_VALUE)
-                    .setRangedValue(95f)
-                    .setRangedMinValue(0f)
-                    .setRangedMaxValue(100f)
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
+                            .setRangedValue(95f)
+                            .setRangedMinValue(0f)
+                            .setRangedMaxValue(100f)
+                            .build()
+                    )
                     .build()
             )
     }
@@ -1377,8 +1441,11 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_ICON)
-                    .setIcon(icon)
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_ICON)
+                            .setIcon(icon)
+                            .build()
+                    )
                     .build()
             )
     }
@@ -1393,9 +1460,12 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_SMALL_IMAGE)
-                    .setSmallImage(icon)
-                    .setSmallImageStyle(WireComplicationData.IMAGE_STYLE_PHOTO)
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_SMALL_IMAGE)
+                            .setSmallImage(icon)
+                            .setSmallImageStyle(WireComplicationData.IMAGE_STYLE_PHOTO)
+                            .build()
+                    )
                     .build()
             )
     }
@@ -1409,8 +1479,11 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholderType(WireComplicationData.TYPE_LARGE_IMAGE)
-                    .setLargeImage(icon)
+                    .setPlaceholder(
+                        WireComplicationDataBuilder(WireComplicationData.TYPE_LARGE_IMAGE)
+                            .setLargeImage(icon)
+                            .build()
+                    )
                     .build()
             )
     }
diff --git a/wear/watchface/watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationRenderer.java b/wear/watchface/watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationRenderer.java
index 8ede3a8..2f790f8 100644
--- a/wear/watchface/watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationRenderer.java
+++ b/wear/watchface/watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/ComplicationRenderer.java
@@ -170,6 +170,7 @@
     boolean mIsPlaceholderTitle;
     @VisibleForTesting
     boolean mIsPlaceholderText;
+    boolean mIsPlaceholder;
 
     // Drawables for rendering rounded images
     private RoundedDrawable mRoundedBackgroundDrawable = null;
@@ -284,9 +285,12 @@
         mIsPlaceholderRangedValue = false;
         mIsPlaceholderTitle = false;
         mIsPlaceholderText = false;
+        mIsPlaceholder = false;
 
         if (data.getType() == ComplicationData.TYPE_NO_DATA) {
-            if (data.hasPlaceholderType()) {
+            ComplicationData placeholder = data.getPlaceholder();
+            if (placeholder != null) {
+                data = placeholder;
                 mIsPlaceholderIcon = data.hasIcon() && ImageKt.isPlaceholder(data.getIcon());
                 mIsPlaceholderSmallImage =
                         data.hasSmallImage() && ImageKt.isPlaceholder(data.getSmallImage());
@@ -295,7 +299,7 @@
                 mIsPlaceholderRangedValue = data.hasRangedValue()
                         && data.getRangedValue()
                         == RangedValueComplicationData.PLACEHOLDER;
-                if (data.getPlaceholderType() == ComplicationData.TYPE_LONG_TEXT) {
+                if (data.getType() == ComplicationData.TYPE_LONG_TEXT) {
                     mIsPlaceholderTitle =
                             data.hasLongTitle() && data.getLongTitle().isPlaceholder();
                     mIsPlaceholderText =
@@ -308,6 +312,7 @@
                 }
                 mComplicationData = data;
                 mHasNoData = false;
+                mIsPlaceholder = true;
             } else {
                 if (!mHasNoData) {
                     // Render TYPE_NO_DATA as a short text complication with a predefined string
@@ -547,8 +552,8 @@
             float height;
             // Avoid drawing two placeholder text fields of the same length.
             if (!mSubTextBounds.isEmpty()
-                    && (mComplicationData.getPlaceholderType() == ComplicationData.TYPE_SHORT_TEXT
-                    || mComplicationData.getPlaceholderType() == ComplicationData.TYPE_LONG_TEXT)) {
+                    && (mComplicationData.getType() == ComplicationData.TYPE_SHORT_TEXT
+                    || mComplicationData.getType() == ComplicationData.TYPE_LONG_TEXT)) {
                 width = mMainTextBounds.width() * 0.4f;
                 height = mMainTextBounds.height() * 0.9f;
             } else {
@@ -659,7 +664,7 @@
             if (paintSet.isInBurnInProtectionMode() && mBurnInProtectionIcon != null) {
                 icon = mBurnInProtectionIcon;
             }
-            icon.setColorFilter(mComplicationData.hasPlaceholderType() ? PLACEHOLDER_COLOR_FILTER :
+            icon.setColorFilter(mIsPlaceholder ? PLACEHOLDER_COLOR_FILTER :
                     paintSet.mIconColorFilter);
             drawIconOnCanvas(canvas, mIconBounds, icon);
         } else if (isPlaceholder) {
@@ -768,9 +773,6 @@
         mBackgroundBoundsF.set(0, 0, mBounds.width(), mBounds.height());
         LayoutHelper currentLayoutHelper;
         int type = mComplicationData.getType();
-        if (type == ComplicationData.TYPE_NO_DATA && mComplicationData.hasPlaceholderType()) {
-            type = mComplicationData.getPlaceholderType();
-        }
         switch (type) {
             case ComplicationData.TYPE_ICON:
                 currentLayoutHelper = new IconLayoutHelper();
diff --git a/wear/watchface/watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java b/wear/watchface/watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java
index d3dd469..f3b4ae2 100644
--- a/wear/watchface/watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java
+++ b/wear/watchface/watchface-complications-rendering/src/test/java/androidx/wear/watchface/complications/rendering/ComplicationRendererTest.java
@@ -608,8 +608,10 @@
     public void placeholderLongTextIsDrawnAsPlaceholder() {
         mComplicationRenderer.setComplicationData(
                 new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA)
-                        .setPlaceholderType(ComplicationData.TYPE_LONG_TEXT)
-                        .setLongText(PLACEHOLDER_COMPLICATION_TEXT)
+                        .setPlaceholder(new ComplicationData.Builder(
+                                ComplicationData.TYPE_LONG_TEXT)
+                                .setLongText(PLACEHOLDER_COMPLICATION_TEXT)
+                                .build())
                         .build(),
                 true);
 
@@ -623,8 +625,10 @@
     public void placeholderShortTextIsDrawnAsPlaceholder() {
         mComplicationRenderer.setComplicationData(
                 new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA)
-                        .setPlaceholderType(ComplicationData.TYPE_SHORT_TEXT)
-                        .setShortText(PLACEHOLDER_COMPLICATION_TEXT)
+                        .setPlaceholder(new ComplicationData.Builder(
+                                ComplicationData.TYPE_SHORT_TEXT)
+                                .setShortText(PLACEHOLDER_COMPLICATION_TEXT)
+                                .build())
                         .build(),
                 true);
 
@@ -638,9 +642,11 @@
     public void placeholderLongTitleIsDrawnAsPlaceholder() {
         mComplicationRenderer.setComplicationData(
                 new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA)
-                        .setPlaceholderType(ComplicationData.TYPE_LONG_TEXT)
-                        .setLongText(ComplicationText.plainText("Hi"))
-                        .setLongTitle(PLACEHOLDER_COMPLICATION_TEXT)
+                        .setPlaceholder(new ComplicationData.Builder(
+                                ComplicationData.TYPE_LONG_TEXT)
+                                .setLongText(ComplicationText.plainText("Hi"))
+                                .setLongTitle(PLACEHOLDER_COMPLICATION_TEXT)
+                                .build())
                         .build(),
                 true);
 
@@ -654,9 +660,11 @@
     public void placeholderShortTitleIsDrawnAsPlaceholder() {
         mComplicationRenderer.setComplicationData(
                 new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA)
-                        .setPlaceholderType(ComplicationData.TYPE_SHORT_TEXT)
-                        .setShortText(ComplicationText.plainText("Hi"))
-                        .setShortTitle(PLACEHOLDER_COMPLICATION_TEXT)
+                        .setPlaceholder(new ComplicationData.Builder(
+                                ComplicationData.TYPE_SHORT_TEXT)
+                                .setShortText(ComplicationText.plainText("Hi"))
+                                .setShortTitle(PLACEHOLDER_COMPLICATION_TEXT)
+                                .build())
                         .build(),
                 true);
 
@@ -670,11 +678,13 @@
     public void iconIsTintedWithPlaceholderTintForPlaceholderComplication() {
         mComplicationRenderer.setComplicationData(
                 new ComplicationData.Builder(ComplicationData.TYPE_NO_DATA)
-                        .setPlaceholderType(ComplicationData.TYPE_SHORT_TEXT)
-                        .setShortText(ComplicationText.plainText("Hi"))
-                        .setIcon(Icon.createWithBitmap(
-                                Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565)))
-                        .setShortTitle(PLACEHOLDER_COMPLICATION_TEXT)
+                        .setPlaceholder(new ComplicationData.Builder(
+                                ComplicationData.TYPE_SHORT_TEXT)
+                                .setShortText(ComplicationText.plainText("Hi"))
+                                .setIcon(Icon.createWithBitmap(
+                                        Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565)))
+                                .setShortTitle(PLACEHOLDER_COMPLICATION_TEXT)
+                                .build())
                         .build(),
                 false);