1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.android.setupcompat.logging; 18 19 import static com.google.android.setupcompat.internal.Validations.assertLengthInRange; 20 21 import android.annotation.TargetApi; 22 import android.os.Build; 23 import android.os.Build.VERSION_CODES; 24 import android.os.Bundle; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.os.PersistableBundle; 28 import androidx.annotation.NonNull; 29 import androidx.annotation.VisibleForTesting; 30 import com.google.android.setupcompat.internal.ClockProvider; 31 import com.google.android.setupcompat.internal.PersistableBundles; 32 import com.google.android.setupcompat.internal.Preconditions; 33 import com.google.android.setupcompat.util.ObjectUtils; 34 35 /** 36 * This class represents a interesting event at a particular point in time. The event is identified 37 * by {@link MetricKey} along with {@code timestamp}. It can include additional key-value pairs 38 * providing more attributes associated with the given event. Only primitive values are supported 39 * for now (int, long, double, float, String). 40 */ 41 @TargetApi(VERSION_CODES.Q) 42 public final class CustomEvent implements Parcelable { 43 private static final String BUNDLE_KEY_TIMESTAMP = "CustomEvent_timestamp"; 44 private static final String BUNDLE_KEY_METRICKEY = "CustomEvent_metricKey"; 45 private static final String BUNDLE_KEY_BUNDLE_VALUES = "CustomEvent_bundleValues"; 46 private static final String BUNDLE_KEY_BUNDLE_PII_VALUES = "CustomEvent_pii_bundleValues"; 47 private static final String BUNDLE_VERSION = "CustomEvent_version"; 48 private static final int VERSION = 1; 49 50 /** Creates a new instance of {@code CustomEvent}. Null arguments are not allowed. */ create( MetricKey metricKey, PersistableBundle bundle, PersistableBundle piiValues)51 public static CustomEvent create( 52 MetricKey metricKey, PersistableBundle bundle, PersistableBundle piiValues) { 53 Preconditions.checkArgument( 54 Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q, 55 "The constructor only support on sdk Q or higher"); 56 return new CustomEvent( 57 ClockProvider.timeInMillis(), 58 metricKey, 59 // Assert only in factory methods since these methods are directly used by API consumers 60 // while constructor is used directly only when data is de-serialized from bundle (which 61 // might have been sent by a client using a newer API) 62 PersistableBundles.assertIsValid(bundle), 63 PersistableBundles.assertIsValid(piiValues)); 64 } 65 66 /** Creates a new instance of {@code CustomEvent}. Null arguments are not allowed. */ create(MetricKey metricKey, PersistableBundle bundle)67 public static CustomEvent create(MetricKey metricKey, PersistableBundle bundle) { 68 Preconditions.checkArgument( 69 Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q, 70 "The constructor only support on sdk Q or higher."); 71 return create(metricKey, bundle, PersistableBundle.EMPTY); 72 } 73 74 /** Converts {@link Bundle} into {@link CustomEvent}. */ toCustomEvent(Bundle bundle)75 public static CustomEvent toCustomEvent(Bundle bundle) { 76 return new CustomEvent( 77 bundle.getLong(BUNDLE_KEY_TIMESTAMP, /* defaultValue= */ Long.MIN_VALUE), 78 MetricKey.toMetricKey(bundle.getBundle(BUNDLE_KEY_METRICKEY)), 79 PersistableBundles.fromBundle(bundle.getBundle(BUNDLE_KEY_BUNDLE_VALUES)), 80 PersistableBundles.fromBundle(bundle.getBundle(BUNDLE_KEY_BUNDLE_PII_VALUES))); 81 } 82 83 /** Converts {@link CustomEvent} into {@link Bundle}. */ toBundle(CustomEvent customEvent)84 public static Bundle toBundle(CustomEvent customEvent) { 85 Preconditions.checkNotNull(customEvent, "CustomEvent cannot be null"); 86 Bundle bundle = new Bundle(); 87 bundle.putInt(BUNDLE_VERSION, VERSION); 88 bundle.putLong(BUNDLE_KEY_TIMESTAMP, customEvent.timestampMillis()); 89 bundle.putBundle(BUNDLE_KEY_METRICKEY, MetricKey.fromMetricKey(customEvent.metricKey())); 90 bundle.putBundle(BUNDLE_KEY_BUNDLE_VALUES, PersistableBundles.toBundle(customEvent.values())); 91 bundle.putBundle( 92 BUNDLE_KEY_BUNDLE_PII_VALUES, PersistableBundles.toBundle(customEvent.piiValues())); 93 return bundle; 94 } 95 96 public static final Creator<CustomEvent> CREATOR = 97 new Creator<CustomEvent>() { 98 @Override 99 public CustomEvent createFromParcel(Parcel in) { 100 return new CustomEvent( 101 in.readLong(), 102 in.readParcelable(MetricKey.class.getClassLoader()), 103 in.readPersistableBundle(MetricKey.class.getClassLoader()), 104 in.readPersistableBundle(MetricKey.class.getClassLoader())); 105 } 106 107 @Override 108 public CustomEvent[] newArray(int size) { 109 return new CustomEvent[size]; 110 } 111 }; 112 113 /** Returns the timestamp of when the event occurred. */ timestampMillis()114 public long timestampMillis() { 115 return timestampMillis; 116 } 117 118 /** Returns the identifier of the event. */ metricKey()119 public MetricKey metricKey() { 120 return this.metricKey; 121 } 122 123 /** Returns the non PII values describing the event. Only primitive values are supported. */ values()124 public PersistableBundle values() { 125 return new PersistableBundle(this.persistableBundle); 126 } 127 128 /** 129 * Returns the PII(Personally identifiable information) values describing the event. These values 130 * will not be included in the aggregated logs. Only primitive values are supported. 131 */ piiValues()132 public PersistableBundle piiValues() { 133 return this.piiValues; 134 } 135 136 @Override describeContents()137 public int describeContents() { 138 return 0; 139 } 140 141 @Override writeToParcel(Parcel parcel, int i)142 public void writeToParcel(Parcel parcel, int i) { 143 parcel.writeLong(timestampMillis); 144 parcel.writeParcelable(metricKey, i); 145 parcel.writePersistableBundle(persistableBundle); 146 parcel.writePersistableBundle(piiValues); 147 } 148 149 @Override equals(Object o)150 public boolean equals(Object o) { 151 if (this == o) { 152 return true; 153 } 154 if (!(o instanceof CustomEvent)) { 155 return false; 156 } 157 CustomEvent that = (CustomEvent) o; 158 return timestampMillis == that.timestampMillis 159 && ObjectUtils.equals(metricKey, that.metricKey) 160 && PersistableBundles.equals(persistableBundle, that.persistableBundle) 161 && PersistableBundles.equals(piiValues, that.piiValues); 162 } 163 164 @Override hashCode()165 public int hashCode() { 166 return ObjectUtils.hashCode(timestampMillis, metricKey, persistableBundle, piiValues); 167 } 168 CustomEvent( long timestampMillis, MetricKey metricKey, PersistableBundle bundle, PersistableBundle piiValues)169 private CustomEvent( 170 long timestampMillis, 171 MetricKey metricKey, 172 PersistableBundle bundle, 173 PersistableBundle piiValues) { 174 Preconditions.checkArgument(timestampMillis >= 0, "Timestamp cannot be negative."); 175 Preconditions.checkNotNull(metricKey, "MetricKey cannot be null."); 176 Preconditions.checkNotNull(bundle, "Bundle cannot be null."); 177 Preconditions.checkArgument(!bundle.isEmpty(), "Bundle cannot be empty."); 178 Preconditions.checkNotNull(piiValues, "piiValues cannot be null."); 179 assertPersistableBundleIsValid(bundle); 180 this.timestampMillis = timestampMillis; 181 this.metricKey = metricKey; 182 this.persistableBundle = new PersistableBundle(bundle); 183 this.piiValues = new PersistableBundle(piiValues); 184 } 185 186 private final long timestampMillis; 187 private final MetricKey metricKey; 188 private final PersistableBundle persistableBundle; 189 private final PersistableBundle piiValues; 190 assertPersistableBundleIsValid(PersistableBundle bundle)191 private static void assertPersistableBundleIsValid(PersistableBundle bundle) { 192 for (String key : bundle.keySet()) { 193 assertLengthInRange(key, "bundle key", MIN_BUNDLE_KEY_LENGTH, MAX_STR_LENGTH); 194 Object value = bundle.get(key); 195 if (value instanceof String) { 196 Preconditions.checkArgument( 197 ((String) value).length() <= MAX_STR_LENGTH, 198 String.format( 199 "Maximum length of string value for key='%s' cannot exceed %s.", 200 key, MAX_STR_LENGTH)); 201 } 202 } 203 } 204 205 /** 206 * Trims the string longer than {@code MAX_STR_LENGTH} character, only keep the first {@code 207 * MAX_STR_LENGTH} - 1 characters and attached … in the end. 208 */ 209 @NonNull trimsStringOverMaxLength(@onNull String str)210 public static String trimsStringOverMaxLength(@NonNull String str) { 211 if (str.length() <= MAX_STR_LENGTH) { 212 return str; 213 } else { 214 return String.format("%s…", str.substring(0, MAX_STR_LENGTH - 1)); 215 } 216 } 217 218 @VisibleForTesting static final int MAX_STR_LENGTH = 50; 219 @VisibleForTesting static final int MIN_BUNDLE_KEY_LENGTH = 3; 220 } 221