/* * Copyright 2011 Google Inc. * * 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.ipc.invalidation.ticl.android2; import com.google.common.base.Joiner; import com.google.ipc.invalidation.common.CommonProtoStrings2; import com.google.ipc.invalidation.util.TextBuilder; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protos.ipc.invalidation.AndroidService.AndroidNetworkSendRequest; import com.google.protos.ipc.invalidation.AndroidService.AndroidSchedulerEvent; import com.google.protos.ipc.invalidation.AndroidService.ClientDowncall; import com.google.protos.ipc.invalidation.AndroidService.ClientDowncall.AckDowncall; import com.google.protos.ipc.invalidation.AndroidService.ClientDowncall.RegistrationDowncall; import com.google.protos.ipc.invalidation.AndroidService.InternalDowncall; import com.google.protos.ipc.invalidation.AndroidService.InternalDowncall.CreateClient; import com.google.protos.ipc.invalidation.AndroidService.InternalDowncall.NetworkStatus; import com.google.protos.ipc.invalidation.AndroidService.InternalDowncall.ServerMessage; import com.google.protos.ipc.invalidation.AndroidService.ListenerUpcall; import com.google.protos.ipc.invalidation.AndroidService.ListenerUpcall.ErrorUpcall; import com.google.protos.ipc.invalidation.AndroidService.ListenerUpcall.InvalidateUpcall; import com.google.protos.ipc.invalidation.AndroidService.ListenerUpcall.RegistrationFailureUpcall; import com.google.protos.ipc.invalidation.AndroidService.ListenerUpcall.RegistrationStatusUpcall; import com.google.protos.ipc.invalidation.AndroidService.ListenerUpcall.ReissueRegistrationsUpcall; import com.google.protos.ipc.invalidation.Client.AckHandleP; import com.google.protos.ipc.invalidation.ClientProtocol.ObjectIdP; import android.content.Intent; import java.util.List; /** * Utilities to format Android protocol buffers and intents as compact strings suitable for logging. * By convention, methods take a {@link TextBuilder} and the object to format and return the * builder. Null object arguments are permitted. * * <p>{@link #toCompactString} methods immediately append a description of the object to the given * {@link TextBuilder}s. {@link #toLazyCompactString} methods return an object that defers the work * of formatting the provided argument until {@link Object#toString} is called. * */ public class AndroidStrings { /** * String to return when the argument is unknown (suggests a new protocol field or invalid * proto). */ static final String UNKNOWN_MESSAGE = "UNKNOWN@AndroidStrings"; /** * String to return when there is an error formatting an argument. */ static final String ERROR_MESSAGE = "ERROR@AndroidStrings"; /** * Returns an object that lazily evaluates {@link #toCompactString} when {@link Object#toString} * is called. */ public static Object toLazyCompactString(final Intent intent) { return new Object() { @Override public String toString() { TextBuilder builder = new TextBuilder(); AndroidStrings.toCompactString(builder, intent); return builder.toString(); } }; } /** Appends a description of the given client {@code downcall} to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, ClientDowncall downcall) { if (downcall == null) { return builder; } builder.append(ProtocolIntents.CLIENT_DOWNCALL_KEY).append("::"); if (downcall.hasStart()) { builder.append("start()"); } else if (downcall.hasStop()) { builder.append("stop()"); } else if (downcall.hasAck()) { toCompactString(builder, downcall.getAck()); } else if (downcall.hasRegistrations()) { toCompactString(builder, downcall.getRegistrations()); } else { builder.append(UNKNOWN_MESSAGE); } return builder; } /** Appends a description of the given {@code ack} downcall to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, AckDowncall ack) { if (ack == null) { return builder; } builder.append("ack("); serializedAckHandleToCompactString(builder, ack.getAckHandle()); return builder.append(")"); } /** * Appends a description of the given {@code registration} downcall to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, RegistrationDowncall registration) { if (registration == null) { return builder; } List<ObjectIdP> objects; if (registration.getRegistrationsCount() > 0) { builder.append("register("); objects = registration.getRegistrationsList(); } else { builder.append("unregister("); objects = registration.getUnregistrationsList(); } return CommonProtoStrings2.toCompactStringForObjectIds(builder, objects).append(")"); } /** Appends a description of the given internal {@code downcall} to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, InternalDowncall downcall) { if (downcall == null) { return builder; } builder.append(ProtocolIntents.INTERNAL_DOWNCALL_KEY).append("::"); if (downcall.hasServerMessage()) { toCompactString(builder, downcall.getServerMessage()); } else if (downcall.hasNetworkStatus()) { toCompactString(builder, downcall.getNetworkStatus()); } else if (downcall.hasNetworkAddrChange()) { builder.append("newtworkAddrChange()"); } else if (downcall.hasCreateClient()) { toCompactString(builder, downcall.getCreateClient()); } else { builder.append(UNKNOWN_MESSAGE); } return builder; } /** Appends a description of the given {@code serverMessage} to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, ServerMessage serverMessage) { if (serverMessage == null) { return builder; } return builder.append("serverMessage(").append(serverMessage.getData()).append(")"); } /** Appends a description of the given {@code networkStatus} to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, NetworkStatus networkStatus) { if (networkStatus == null) { return builder; } return builder.append("networkStatus(isOnline = ").append(networkStatus.getIsOnline()) .append(")"); } /** * Appends a description of the given {@code createClient} command to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, CreateClient createClient) { if (createClient == null) { return builder; } return builder.append("createClient(type = ").append(createClient.getClientType()) .append(", name = ").append(createClient.getClientName()).append(", skipStartForTest = ") .append(createClient.getSkipStartForTest()).append(")"); } /** Appends a description of the given listener {@code upcall} to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, ListenerUpcall upcall) { if (upcall == null) { return builder; } builder.append(ProtocolIntents.LISTENER_UPCALL_KEY).append("::"); if (upcall.hasReady()) { builder.append(".ready()"); } else if (upcall.hasInvalidate()) { toCompactString(builder, upcall.getInvalidate()); } else if (upcall.hasRegistrationStatus()) { toCompactString(builder, upcall.getRegistrationStatus()); } else if (upcall.hasRegistrationFailure()) { toCompactString(builder, upcall.getRegistrationFailure()); } else if (upcall.hasReissueRegistrations()) { toCompactString(builder, upcall.getReissueRegistrations()); } else if (upcall.hasError()) { toCompactString(builder, upcall.getError()); } else { builder.append(UNKNOWN_MESSAGE); } return builder; } /** Appends a description of the given {@code invalidate} command to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, InvalidateUpcall invalidate) { if (invalidate == null) { return builder; } builder.append("invalidate(ackHandle = "); serializedAckHandleToCompactString(builder, invalidate.getAckHandle()); builder.append(", "); if (invalidate.hasInvalidation()) { CommonProtoStrings2.toCompactString(builder, invalidate.getInvalidation()); } else if (invalidate.getInvalidateAll()) { builder.append("ALL"); } else if (invalidate.hasInvalidateUnknown()) { builder.append("UNKNOWN: "); CommonProtoStrings2.toCompactString(builder, invalidate.getInvalidateUnknown()); } return builder.append(")"); } /** Appends a description of the given {@code status} upcall to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, RegistrationStatusUpcall status) { if (status == null) { return builder; } builder.append("registrationStatus(objectId = "); CommonProtoStrings2.toCompactString(builder, status.getObjectId()); return builder.append(", isRegistered = ").append(status.getIsRegistered()).append(")"); } /** Appends a description of the given {@code failure} upcall to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, RegistrationFailureUpcall failure) { if (failure == null) { return builder; } builder.append("registrationFailure(objectId = "); CommonProtoStrings2.toCompactString(builder, failure.getObjectId()); return builder.append(", isTransient = ").append(failure.getTransient()).append(")"); } /** * Appends a description of the given {@code reissue} registrations upcall to the given * {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, ReissueRegistrationsUpcall reissue) { if (reissue == null) { return builder; } builder.append("reissueRegistrations(prefix = "); return builder.append(reissue.getPrefix()).append(", length = ").append(reissue.getLength()) .append(")"); } /** Appends a description of the given {@code error} upcall to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, ErrorUpcall error) { if (error == null) { return builder; } return builder.append("error(code = ").append(error.getErrorCode()).append(", message = ") .append(error.getErrorMessage()).append(", isTransient = ").append(error.getIsTransient()) .append(")"); } /** Appends a description of the given {@code request} to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, AndroidNetworkSendRequest request) { if (request == null) { return builder; } return builder.append(ProtocolIntents.OUTBOUND_MESSAGE_KEY).append("(") .append(request.getMessage()).append(")"); } /** Appends a description of the given (@code event} to the given {@code builder}. */ public static TextBuilder toCompactString(TextBuilder builder, AndroidSchedulerEvent event) { if (event == null) { return builder; } return builder.append(ProtocolIntents.SCHEDULER_KEY).append("(eventName = ") .append(event.getEventName()).append(", ticlId = ").append(event.getTiclId()).append(")"); } /** See spec in implementation notes. */ public static TextBuilder toCompactString(TextBuilder builder, AckHandleP ackHandle) { if (ackHandle == null) { return builder; } return CommonProtoStrings2.toCompactString(builder.appendFormat("AckHandle: "), ackHandle.getInvalidation()); } /** * Appends a description of the given {@code intent} to the given {@code builder}. If the intent * includes some recognized extras, formats the extra context as well. */ public static TextBuilder toCompactString(TextBuilder builder, Intent intent) { if (intent == null) { return builder; } builder.append("intent("); try { if (!tryParseExtra(builder, intent)) { builder.append(UNKNOWN_MESSAGE).append(", extras = ") .append(Joiner.on(", ").join(intent.getExtras().keySet())); } } catch (InvalidProtocolBufferException exception) { builder.append(ERROR_MESSAGE).append(" : ").append(exception); } return builder.append(")"); } /** Appends a description of any known extra or appends 'UNKNOWN' if none are recognized. */ private static boolean tryParseExtra(TextBuilder builder, Intent intent) throws InvalidProtocolBufferException { byte[] data; data = intent.getByteArrayExtra(ProtocolIntents.SCHEDULER_KEY); if (data != null) { AndroidSchedulerEvent schedulerEvent = AndroidSchedulerEvent.parseFrom(data); toCompactString(builder, schedulerEvent); return true; } data = intent.getByteArrayExtra(ProtocolIntents.OUTBOUND_MESSAGE_KEY); if (data != null) { AndroidNetworkSendRequest outboundMessage = AndroidNetworkSendRequest.parseFrom(data); toCompactString(builder, outboundMessage); return true; } data = intent.getByteArrayExtra(ProtocolIntents.LISTENER_UPCALL_KEY); if (data != null) { ListenerUpcall upcall = ListenerUpcall.parseFrom(data); toCompactString(builder, upcall); return true; } data = intent.getByteArrayExtra(ProtocolIntents.INTERNAL_DOWNCALL_KEY); if (data != null) { InternalDowncall internalDowncall = InternalDowncall.parseFrom(data); toCompactString(builder, internalDowncall); return true; } data = intent.getByteArrayExtra(ProtocolIntents.CLIENT_DOWNCALL_KEY); if (data != null) { ClientDowncall clientDowncall = ClientDowncall.parseFrom(data); toCompactString(builder, clientDowncall); return true; } // Didn't recognize any intents. return false; } /** Given serialized form of an ack handle, appends description to {@code builder}. */ private static TextBuilder serializedAckHandleToCompactString( TextBuilder builder, ByteString serialized) { if (serialized == null) { return builder; } // The ack handle is supposed by an AckHandleP! try { AckHandleP ackHandle = AckHandleP.parseFrom(serialized); return toCompactString(builder, ackHandle); } catch (InvalidProtocolBufferException exception) { // But it wasn't... Just log the raw bytes. return builder.append(serialized); } } private AndroidStrings() { // Avoid instantiation. } }