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