| // Copyright 2018 The Feed Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.android.libraries.feed.common.testing; |
| |
| import com.google.protobuf.ByteString; |
| import com.google.search.now.ui.piet.PietProto.PietSharedState; |
| import com.google.search.now.ui.stream.StreamStructureProto.Content; |
| import com.google.search.now.ui.stream.StreamStructureProto.PietContent; |
| import com.google.search.now.wire.feed.ActionPropertiesProto.ActionProperties; |
| import com.google.search.now.wire.feed.ContentIdProto.ContentId; |
| import com.google.search.now.wire.feed.DataOperationProto.DataOperation; |
| import com.google.search.now.wire.feed.DataOperationProto.DataOperation.Operation; |
| import com.google.search.now.wire.feed.FeatureProto.Feature; |
| import com.google.search.now.wire.feed.FeatureProto.Feature.RenderableUnit; |
| import com.google.search.now.wire.feed.FeedResponseProto.FeedResponse; |
| import com.google.search.now.wire.feed.OpaqueActionDataProto.OpaqueActionData; |
| import com.google.search.now.wire.feed.PayloadMetadataProto.PayloadMetadata; |
| import com.google.search.now.wire.feed.ResponseProto.Response; |
| import com.google.search.now.wire.feed.SemanticPropertiesProto.SemanticProperties; |
| import com.google.search.now.wire.feed.TokenProto.Token; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** Builder making creation of wire protocol Response object easier. */ |
| public class ResponseBuilder { |
| // this needs to match the root content_id defined in the FeedProtocolAdapter. |
| public static final ContentId ROOT_CONTENT_ID = |
| ContentId.newBuilder().setContentDomain("stream_root").setId(0).setTable("feature").build(); |
| public static final ContentId PIET_SHARED_STATE = |
| ContentId.newBuilder() |
| .setContentDomain("piet-shared-state") |
| .setId(1) |
| .setTable("feature") |
| .build(); |
| private static final int CONTENT_ID_CARD_INCREMENT = 100; |
| private static final ContentId TOKEN_CONTENT_ID = |
| ContentId.newBuilder().setContentDomain("token").setId(0).setTable("token").build(); |
| |
| public static ContentId createFeatureContentId(int id) { |
| return ContentId.newBuilder().setContentDomain("feature").setId(id).setTable("feature").build(); |
| } |
| |
| private final WireProtocolInfo wireProtocolInfo = new WireProtocolInfo(); |
| private final FeedResponse.Builder feedResponseBuilder = FeedResponse.newBuilder(); |
| private final List<ContentId> pietSharedStateContentIds = new ArrayList<>(); |
| |
| private int tokenId = 0; |
| /*@Nullable*/ private ByteString token = null; |
| |
| /** Add a CLEAR_ALL data operation to the response */ |
| public ResponseBuilder addClearOperation() { |
| wireProtocolInfo.hasClearOperation = true; |
| feedResponseBuilder.addDataOperation( |
| DataOperation.newBuilder().setOperation(Operation.CLEAR_ALL).build()); |
| return this; |
| } |
| |
| public ResponseBuilder addStreamToken(int tokenId, ByteString token) { |
| this.token = token; |
| this.tokenId = tokenId; |
| wireProtocolInfo.hasToken = true; |
| return this; |
| } |
| |
| public ResponseBuilder addPietSharedState() { |
| return addPietSharedState(PIET_SHARED_STATE); |
| } |
| |
| public ResponseBuilder addPietSharedState(ContentId pietSharedStateContentId) { |
| pietSharedStateContentIds.add(pietSharedStateContentId); |
| PayloadMetadata payloadMetadata = |
| PayloadMetadata.newBuilder().setContentId(pietSharedStateContentId).build(); |
| feedResponseBuilder.addDataOperation( |
| DataOperation.newBuilder() |
| .setOperation(Operation.UPDATE_OR_APPEND) |
| .setPietSharedState(PietSharedState.getDefaultInstance()) |
| .setMetadata(payloadMetadata) |
| .build()); |
| return this; |
| } |
| |
| /** Add a data operation for a feature that represents the Root of the Stream. */ |
| public ResponseBuilder addRootFeature() { |
| return addRootFeature(ROOT_CONTENT_ID); |
| } |
| |
| public ResponseBuilder addRootFeature(ContentId contentId) { |
| PayloadMetadata payloadMetadata = PayloadMetadata.newBuilder().setContentId(contentId).build(); |
| Feature feature = Feature.newBuilder().setRenderableUnit(RenderableUnit.STREAM).build(); |
| |
| feedResponseBuilder.addDataOperation( |
| DataOperation.newBuilder() |
| .setOperation(Operation.UPDATE_OR_APPEND) |
| .setFeature(feature) |
| .setMetadata(payloadMetadata) |
| .build()); |
| wireProtocolInfo.featuresAdded.add(contentId); |
| return this; |
| } |
| |
| public ResponseBuilder addClusterFeature(ContentId contentId, ContentId parentId) { |
| PayloadMetadata payloadMetadata = PayloadMetadata.newBuilder().setContentId(contentId).build(); |
| Feature feature = |
| Feature.newBuilder() |
| .setRenderableUnit(RenderableUnit.CLUSTER) |
| .setParentId(parentId) |
| .build(); |
| feedResponseBuilder.addDataOperation( |
| DataOperation.newBuilder() |
| .setOperation(Operation.UPDATE_OR_APPEND) |
| .setFeature(feature) |
| .setMetadata(payloadMetadata) |
| .build()); |
| wireProtocolInfo.featuresAdded.add(contentId); |
| return this; |
| } |
| |
| public ResponseBuilder addCard(ContentId contentId, ContentId parentId) { |
| // Create a Card |
| PayloadMetadata payloadMetadata = PayloadMetadata.newBuilder().setContentId(contentId).build(); |
| Feature feature = |
| Feature.newBuilder().setRenderableUnit(RenderableUnit.CARD).setParentId(parentId).build(); |
| feedResponseBuilder.addDataOperation( |
| DataOperation.newBuilder() |
| .setOperation(Operation.UPDATE_OR_APPEND) |
| .setFeature(feature) |
| .setMetadata(payloadMetadata) |
| .build()); |
| wireProtocolInfo.featuresAdded.add(contentId); |
| |
| // Create content within the Card |
| payloadMetadata = |
| PayloadMetadata.newBuilder() |
| .setContentId(createNewContentId(contentId, CONTENT_ID_CARD_INCREMENT)) |
| .build(); |
| feature = |
| Feature.newBuilder() |
| .setRenderableUnit(RenderableUnit.CONTENT) |
| .setParentId(contentId) |
| .setExtension( |
| Content.contentExtension, |
| Content.newBuilder() |
| .setType(Content.Type.PIET) |
| .setExtension( |
| PietContent.pietContentExtension, |
| PietContent.newBuilder() |
| .addAllPietSharedStates(pietSharedStateContentIds) |
| .build()) |
| .build()) |
| .build(); |
| feedResponseBuilder.addDataOperation( |
| DataOperation.newBuilder() |
| .setOperation(Operation.UPDATE_OR_APPEND) |
| .setFeature(feature) |
| .setMetadata(payloadMetadata) |
| .build()); |
| wireProtocolInfo.featuresAdded.add(payloadMetadata.getContentId()); |
| return this; |
| } |
| |
| public ResponseBuilder addCardsToRoot(ContentId[] contentIds) { |
| for (ContentId contentId : contentIds) { |
| addCard(contentId, ROOT_CONTENT_ID); |
| } |
| return this; |
| } |
| |
| public ResponseBuilder addCardWithSemanticData(ContentId contentId, ByteString semanticData) { |
| feedResponseBuilder.addDataOperation( |
| DataOperation.newBuilder() |
| .setOperation(Operation.UPDATE_OR_APPEND) |
| .setFeature(Feature.newBuilder().setRenderableUnit(RenderableUnit.CARD)) |
| .setMetadata( |
| PayloadMetadata.newBuilder() |
| .setContentId(contentId) |
| .setSemanticProperties( |
| SemanticProperties.newBuilder().setSemanticPropertiesData(semanticData)))); |
| wireProtocolInfo.featuresAdded.add(contentId); |
| return this; |
| } |
| |
| public ResponseBuilder addCardWithActionData(ContentId contentId, OpaqueActionData actionData) { |
| feedResponseBuilder.addDataOperation( |
| DataOperation.newBuilder() |
| .setOperation(Operation.UPDATE_OR_APPEND) |
| .setFeature(Feature.newBuilder().setRenderableUnit(RenderableUnit.CARD)) |
| .setMetadata( |
| PayloadMetadata.newBuilder() |
| .setContentId(contentId) |
| .setActionProperties(ActionProperties.newBuilder().setActionData(actionData)))); |
| wireProtocolInfo.featuresAdded.add(contentId); |
| return this; |
| } |
| |
| public ResponseBuilder removeFeature(ContentId contentId, ContentId parentId) { |
| PayloadMetadata payloadMetadata = PayloadMetadata.newBuilder().setContentId(contentId).build(); |
| Feature feature = |
| Feature.newBuilder().setRenderableUnit(RenderableUnit.CARD).setParentId(parentId).build(); |
| feedResponseBuilder.addDataOperation( |
| DataOperation.newBuilder() |
| .setOperation(Operation.REMOVE) |
| .setFeature(feature) |
| .setMetadata(payloadMetadata) |
| .build()); |
| wireProtocolInfo.featuresAdded.add(contentId); |
| return this; |
| } |
| |
| public Response build() { |
| if (token != null) { |
| addToken(tokenId, token); |
| } |
| return Response.newBuilder() |
| .setExtension(FeedResponse.feedResponse, feedResponseBuilder.build()) |
| .build(); |
| } |
| |
| private void addToken(int tokenId, ByteString token) { |
| PayloadMetadata payloadMetadata = |
| PayloadMetadata.newBuilder() |
| .setContentId(createNewContentId(TOKEN_CONTENT_ID, tokenId)) |
| .build(); |
| Feature.Builder featureBuilder = |
| Feature.newBuilder().setRenderableUnit(RenderableUnit.TOKEN).setParentId(ROOT_CONTENT_ID); |
| Token wireToken = Token.newBuilder().setNextPageToken(token).build(); |
| featureBuilder.setExtension(Token.tokenExtension, wireToken); |
| feedResponseBuilder.addDataOperation( |
| DataOperation.newBuilder() |
| .setOperation(Operation.UPDATE_OR_APPEND) |
| .setFeature(featureBuilder.build()) |
| .setMetadata(payloadMetadata) |
| .build()); |
| } |
| |
| public WireProtocolInfo getWireProtocolInfo() { |
| return wireProtocolInfo; |
| } |
| |
| /** Returns a new {@link ResponseBuilder}. */ |
| public static ResponseBuilder builder() { |
| return new ResponseBuilder(); |
| } |
| |
| /** Returns a {@link ResponseBuilder} with a CLEAR_ALL, root, and cards. */ |
| public static ResponseBuilder forClearAllWithCards(ContentId[] cards) { |
| return builder().addClearOperation().addRootFeature().addCardsToRoot(cards); |
| } |
| |
| /** Captures information about the wire protocol that was created. */ |
| public static class WireProtocolInfo { |
| public final List<ContentId> featuresAdded = new ArrayList<>(); |
| public boolean hasClearOperation = false; |
| public boolean hasToken = false; |
| } |
| |
| private ContentId createNewContentId(ContentId contentId, int idIncrement) { |
| ContentId.Builder builder = contentId.toBuilder(); |
| builder.setId(contentId.getId() + idIncrement); |
| return builder.build(); |
| } |
| } |