1 /* 2 * Copyright (c) 2017 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you 5 * may not use this file except in compliance with the License. You may 6 * 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 13 * implied. See the License for the specific language governing 14 * permissions and limitations under the License. 15 */ 16 17 package com.android.vts.entity; 18 19 import com.android.vts.proto.VtsReportMessage.AndroidDeviceInfoMessage; 20 import com.google.appengine.api.datastore.Entity; 21 import com.google.appengine.api.datastore.Key; 22 import com.google.appengine.api.memcache.MemcacheService; 23 import com.google.appengine.api.memcache.MemcacheServiceFactory; 24 import com.google.apphosting.api.ApiProxy; 25 import com.googlecode.objectify.annotation.Cache; 26 import com.googlecode.objectify.annotation.Id; 27 import com.googlecode.objectify.annotation.Ignore; 28 import com.googlecode.objectify.annotation.Index; 29 import com.googlecode.objectify.annotation.Parent; 30 import java.util.List; 31 import java.util.Objects; 32 import java.util.logging.Level; 33 import java.util.logging.Logger; 34 import java.util.stream.Collectors; 35 36 import lombok.Data; 37 import lombok.NoArgsConstructor; 38 39 import static com.googlecode.objectify.ObjectifyService.ofy; 40 41 @com.googlecode.objectify.annotation.Entity(name = "DeviceInfo") 42 @Cache 43 @Data 44 @NoArgsConstructor 45 /** Class describing a device used for a test run. */ 46 public class DeviceInfoEntity implements DashboardEntity { 47 protected static final Logger logger = Logger.getLogger(DeviceInfoEntity.class.getName()); 48 49 /** This is the instance of App Engine memcache service java library */ 50 private static MemcacheService syncCache = MemcacheServiceFactory.getMemcacheService(); 51 52 public static final String KIND = "DeviceInfo"; 53 54 // Property keys 55 public static final String BRANCH = "branch"; 56 public static final String PRODUCT = "product"; 57 public static final String BUILD_FLAVOR = "buildFlavor"; 58 public static final String BUILD_ID = "buildId"; 59 public static final String ABI_BITNESS = "abiBitness"; 60 public static final String ABI_NAME = "abiName"; 61 62 @Ignore 63 private Key parentKey; 64 65 /** ID field using start timestamp */ 66 @Id private Long id; 67 68 /** parent field based on Test and TestRun key */ 69 @Parent 70 private com.googlecode.objectify.Key<?> parent; 71 72 @Index 73 private String branch; 74 75 @Index 76 private String product; 77 78 @Index 79 private String buildFlavor; 80 81 @Index 82 private String buildId; 83 84 private String abiBitness; 85 86 private String abiName; 87 88 /** 89 * Create a DeviceInfoEntity object. 90 * 91 * @param parentKey The key for the parent entity in the database. 92 * @param branch The build branch. 93 * @param product The device product. 94 * @param buildFlavor The device build flavor. 95 * @param buildID The device build ID. 96 * @param abiBitness The abi bitness of the device. 97 * @param abiName The name of the abi. 98 */ DeviceInfoEntity( Key parentKey, String branch, String product, String buildFlavor, String buildID, String abiBitness, String abiName)99 public DeviceInfoEntity( 100 Key parentKey, 101 String branch, 102 String product, 103 String buildFlavor, 104 String buildID, 105 String abiBitness, 106 String abiName) { 107 this.parentKey = parentKey; 108 this.branch = branch; 109 this.product = product; 110 this.buildFlavor = buildFlavor; 111 this.buildId = buildID; 112 this.abiBitness = abiBitness; 113 this.abiName = abiName; 114 } 115 116 /** 117 * Create a DeviceInfoEntity object with objectify Key 118 * 119 * @param parent The objectify key for the parent entity in the database. 120 * @param branch The build branch. 121 * @param product The device product. 122 * @param buildFlavor The device build flavor. 123 * @param buildID The device build ID. 124 * @param abiBitness The abi bitness of the device. 125 * @param abiName The name of the abi. 126 */ DeviceInfoEntity( com.googlecode.objectify.Key parent, String branch, String product, String buildFlavor, String buildID, String abiBitness, String abiName)127 public DeviceInfoEntity( 128 com.googlecode.objectify.Key parent, 129 String branch, 130 String product, 131 String buildFlavor, 132 String buildID, 133 String abiBitness, 134 String abiName) { 135 this.parent = parent; 136 this.branch = branch; 137 this.product = product; 138 this.buildFlavor = buildFlavor; 139 this.buildId = buildID; 140 this.abiBitness = abiBitness; 141 this.abiName = abiName; 142 } 143 144 /** 145 * Get All Branch List from DeviceInfoEntity 146 */ getAllBranches()147 public static List<String> getAllBranches() { 148 try { 149 List<String> branchList = (List<String>) syncCache.get("branchList"); 150 if (Objects.isNull(branchList)) { 151 branchList = 152 ofy().load() 153 .type(DeviceInfoEntity.class) 154 .project("branch") 155 .distinct(true) 156 .list() 157 .stream() 158 .map(device -> device.branch) 159 .collect(Collectors.toList()); 160 syncCache.put("branchList", branchList); 161 } 162 return branchList; 163 } catch (ApiProxy.CallNotFoundException e) { 164 return ofy().load() 165 .type(DeviceInfoEntity.class) 166 .project("branch") 167 .distinct(true) 168 .list() 169 .stream() 170 .map(device -> device.branch) 171 .collect(Collectors.toList()); 172 } 173 } 174 175 /** 176 * Get All BuildFlavors List from DeviceInfoEntity 177 */ getAllBuildFlavors()178 public static List<String> getAllBuildFlavors() { 179 try { 180 List<String> buildFlavorList = (List<String>) syncCache.get("buildFlavorList"); 181 if (Objects.isNull(buildFlavorList)) { 182 buildFlavorList = 183 ofy().load() 184 .type(DeviceInfoEntity.class) 185 .project("buildFlavor") 186 .distinct(true) 187 .list() 188 .stream() 189 .map(device -> device.buildFlavor) 190 .collect(Collectors.toList()); 191 syncCache.put("buildFlavorList", buildFlavorList); 192 } 193 return buildFlavorList; 194 } catch (ApiProxy.CallNotFoundException e) { 195 return ofy().load() 196 .type(DeviceInfoEntity.class) 197 .project("buildFlavor") 198 .distinct(true) 199 .list() 200 .stream() 201 .map(device -> device.buildFlavor) 202 .collect(Collectors.toList()); 203 } 204 } 205 206 /** Saving function for the instance of this class */ save()207 public com.googlecode.objectify.Key<DeviceInfoEntity> save() { 208 return ofy().save().entity(this).now(); 209 } 210 toEntity()211 public Entity toEntity() { 212 Entity deviceEntity = new Entity(KIND, this.parentKey); 213 deviceEntity.setProperty(BRANCH, this.branch.toLowerCase()); 214 deviceEntity.setProperty(PRODUCT, this.product.toLowerCase()); 215 deviceEntity.setProperty(BUILD_FLAVOR, this.buildFlavor.toLowerCase()); 216 deviceEntity.setProperty(BUILD_ID, this.buildId.toLowerCase()); 217 if (this.abiBitness != null && this.abiName != null) { 218 deviceEntity.setUnindexedProperty(ABI_BITNESS, this.abiBitness.toLowerCase()); 219 deviceEntity.setUnindexedProperty(ABI_NAME, this.abiName.toLowerCase()); 220 } 221 222 return deviceEntity; 223 } 224 225 /** 226 * Convert an Entity object to a DeviceInfoEntity. 227 * 228 * @param e The entity to process. 229 * @return DeviceInfoEntity object with the properties from e, or null if incompatible. 230 */ fromEntity(Entity e)231 public static DeviceInfoEntity fromEntity(Entity e) { 232 if (!e.getKind().equals(KIND) || !e.hasProperty(BRANCH) || !e.hasProperty(PRODUCT) 233 || !e.hasProperty(BUILD_FLAVOR) || !e.hasProperty(BUILD_ID) 234 || !e.hasProperty(ABI_BITNESS) || !e.hasProperty(ABI_NAME)) { 235 logger.log(Level.WARNING, "Missing device info attributes in entity: " + e.toString()); 236 return null; 237 } 238 try { 239 Key parentKey = e.getKey().getParent(); 240 String branch = (String) e.getProperty(BRANCH); 241 String product = (String) e.getProperty(PRODUCT); 242 String buildFlavor = (String) e.getProperty(BUILD_FLAVOR); 243 String buildId = (String) e.getProperty(BUILD_ID); 244 String abiBitness = null; 245 String abiName = null; 246 if (e.hasProperty(ABI_BITNESS) && e.hasProperty(ABI_NAME)) { 247 abiBitness = (String) e.getProperty(ABI_BITNESS); 248 abiName = (String) e.getProperty(ABI_NAME); 249 } 250 return new DeviceInfoEntity( 251 parentKey, branch, product, buildFlavor, buildId, abiBitness, abiName); 252 } catch (ClassCastException exception) { 253 // Invalid cast 254 logger.log(Level.WARNING, "Error parsing device info entity.", exception); 255 } 256 return null; 257 } 258 259 /** 260 * Convert a device info message to a DeviceInfoEntity. 261 * 262 * @param parent The ancestor key for the device entity. 263 * @param device The device info report describing the target Android device. 264 * @return The DeviceInfoEntity for the target device, or null if incompatible 265 */ fromDeviceInfoMessage( com.googlecode.objectify.Key parent, AndroidDeviceInfoMessage device)266 public static DeviceInfoEntity fromDeviceInfoMessage( 267 com.googlecode.objectify.Key parent, AndroidDeviceInfoMessage device) { 268 if (!device.hasBuildAlias() || !device.hasBuildFlavor() || !device.hasProductVariant() 269 || !device.hasBuildId()) { 270 return null; 271 } 272 String branch = device.getBuildAlias().toStringUtf8(); 273 String buildFlavor = device.getBuildFlavor().toStringUtf8(); 274 String product = device.getProductVariant().toStringUtf8(); 275 String buildId = device.getBuildId().toStringUtf8(); 276 String abiBitness = device.getAbiBitness().toStringUtf8(); 277 String abiName = device.getAbiName().toStringUtf8(); 278 return new DeviceInfoEntity( 279 parent, branch, product, buildFlavor, buildId, abiBitness, abiName); 280 } 281 282 @Override equals(Object obj)283 public boolean equals(Object obj) { 284 if (!(obj instanceof DeviceInfoEntity)) { 285 return false; 286 } 287 DeviceInfoEntity device2 = (DeviceInfoEntity) obj; 288 if (!this.branch.equals(device2.branch) || !this.product.equals(device2.product) 289 || !this.buildFlavor.equals(device2.buildFlavor) 290 || !this.buildId.equals(device2.buildId)) { 291 return false; 292 } 293 return true; 294 } 295 296 @Override hashCode()297 public int hashCode() { 298 String deviceId = this.branch + this.product + this.buildFlavor + this.buildId; 299 return deviceId.hashCode(); 300 } 301 302 /** 303 * Create a copy of the device info under a near parent. 304 * 305 * @param parentKey The new parent key. 306 * @return A copy of the DeviceInfoEntity with the specified parent. 307 */ copyWithParent(com.googlecode.objectify.Key parentKey)308 public DeviceInfoEntity copyWithParent(com.googlecode.objectify.Key parentKey) { 309 return new DeviceInfoEntity(parentKey, this.branch, this.product, this.buildFlavor, 310 this.buildId, this.abiBitness, this.abiName); 311 } 312 313 /** 314 * Create a string representation of the device build information. 315 * @return A String fingerprint of the format: branch/buildFlavor (build ID) 316 */ getFingerprint()317 public String getFingerprint() { 318 return this.branch + "/" + this.buildFlavor + " (" + this.buildId + ")"; 319 } 320 } 321