1 /*
2  * Copyright (C) 2022 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.android.settings.fuelgauge.batteryusage.db;
18 
19 import android.content.ContentValues;
20 
21 import androidx.room.Entity;
22 import androidx.room.PrimaryKey;
23 
24 import com.android.settings.fuelgauge.BatteryUtils;
25 import com.android.settings.fuelgauge.batteryusage.BatteryInformation;
26 import com.android.settings.fuelgauge.batteryusage.ConvertUtils;
27 
28 import com.google.errorprone.annotations.CanIgnoreReturnValue;
29 
30 import java.util.Locale;
31 
32 /** A {@link Entity} class to save battery states snapshot into database. */
33 @Entity
34 public class BatteryState {
35     @PrimaryKey(autoGenerate = true)
36     private long mId;
37 
38     // Records the app relative information.
39     public final long uid;
40     public final long userId;
41     public final String packageName;
42     public final long timestamp;
43     public final int consumerType;
44     public final boolean isFullChargeCycleStart;
45     public final String batteryInformation;
46 
47     /**
48      * This field is filled only when build type is "userdebug".
49      *
50      * <p>For now, Java Proto Lite is recommended by the Android team as the more lightweight
51      * solution designed specifically for mobile apps to process protobuf. However, converting
52      * protobuf to string through Java Proto Lite needs to parse it into a bytes field first, which
53      * leads to the strings saved in our database are encoded and hard to understand.
54      *
55      * <p>To make it easier to debug in our daily development, this field is added. It will not be
56      * filled for the real users.
57      */
58     public final String batteryInformationDebug;
59 
BatteryState( long uid, long userId, String packageName, long timestamp, int consumerType, boolean isFullChargeCycleStart, String batteryInformation, String batteryInformationDebug)60     public BatteryState(
61             long uid,
62             long userId,
63             String packageName,
64             long timestamp,
65             int consumerType,
66             boolean isFullChargeCycleStart,
67             String batteryInformation,
68             String batteryInformationDebug) {
69         // Records the app relative information.
70         this.uid = uid;
71         this.userId = userId;
72         this.packageName = packageName;
73         this.timestamp = timestamp;
74         this.consumerType = consumerType;
75         this.isFullChargeCycleStart = isFullChargeCycleStart;
76         this.batteryInformation = batteryInformation;
77         this.batteryInformationDebug = batteryInformationDebug;
78     }
79 
80     /** Sets the auto-generated content ID. */
setId(long id)81     public void setId(long id) {
82         this.mId = id;
83     }
84 
85     /** Gets the auto-generated content ID. */
getId()86     public long getId() {
87         return mId;
88     }
89 
90     @Override
toString()91     public String toString() {
92         final String recordAtDateTime = ConvertUtils.utcToLocalTimeForLogging(timestamp);
93         final BatteryInformation batteryInformationInstance =
94                 BatteryUtils.parseProtoFromString(
95                         batteryInformation, BatteryInformation.getDefaultInstance());
96         final StringBuilder builder =
97                 new StringBuilder()
98                         .append("\nBatteryState{")
99                         .append(
100                                 String.format(
101                                         Locale.US,
102                                         "\n\tpackage=%s|uid=%d|userId=%d",
103                                         packageName,
104                                         uid,
105                                         userId))
106                         .append(
107                                 String.format(
108                                         Locale.US,
109                                         "\n\ttimestamp=%s|consumer=%d|isStart=%b",
110                                         recordAtDateTime,
111                                         consumerType,
112                                         isFullChargeCycleStart))
113                         .append(String.format(Locale.US, "\n\tbatteryInfo="))
114                         .append(batteryInformationInstance.toString());
115         return builder.toString();
116     }
117 
118     /** Creates new {@link BatteryState} from {@link ContentValues}. */
create(ContentValues contentValues)119     public static BatteryState create(ContentValues contentValues) {
120         Builder builder = BatteryState.newBuilder();
121         if (contentValues.containsKey("uid")) {
122             builder.setUid(contentValues.getAsLong("uid"));
123         }
124         if (contentValues.containsKey("userId")) {
125             builder.setUserId(contentValues.getAsLong("userId"));
126         }
127         if (contentValues.containsKey("packageName")) {
128             builder.setPackageName(contentValues.getAsString("packageName"));
129         }
130         if (contentValues.containsKey("timestamp")) {
131             builder.setTimestamp(contentValues.getAsLong("timestamp"));
132         }
133         if (contentValues.containsKey("consumerType")) {
134             builder.setConsumerType(contentValues.getAsInteger("consumerType"));
135         }
136         if (contentValues.containsKey("isFullChargeCycleStart")) {
137             builder.setIsFullChargeCycleStart(contentValues.getAsBoolean("isFullChargeCycleStart"));
138         }
139         if (contentValues.containsKey("batteryInformation")) {
140             builder.setBatteryInformation(contentValues.getAsString("batteryInformation"));
141         }
142         if (contentValues.containsKey("batteryInformationDebug")) {
143             builder.setBatteryInformationDebug(
144                     contentValues.getAsString("batteryInformationDebug"));
145         }
146         return builder.build();
147     }
148 
149     /** Creates a new {@link Builder} instance. */
newBuilder()150     public static Builder newBuilder() {
151         return new Builder();
152     }
153 
154     /** A convenience builder class to improve readability. */
155     public static class Builder {
156         private long mUid;
157         private long mUserId;
158         private String mPackageName;
159         private long mTimestamp;
160         private int mConsumerType;
161         private boolean mIsFullChargeCycleStart;
162         private String mBatteryInformation;
163         private String mBatteryInformationDebug;
164 
165         /** Sets the uid. */
166         @CanIgnoreReturnValue
setUid(long uid)167         public Builder setUid(long uid) {
168             this.mUid = uid;
169             return this;
170         }
171 
172         /** Sets the user ID. */
173         @CanIgnoreReturnValue
setUserId(long userId)174         public Builder setUserId(long userId) {
175             this.mUserId = userId;
176             return this;
177         }
178 
179         /** Sets the package name. */
180         @CanIgnoreReturnValue
setPackageName(String packageName)181         public Builder setPackageName(String packageName) {
182             this.mPackageName = packageName;
183             return this;
184         }
185 
186         /** Sets the timestamp. */
187         @CanIgnoreReturnValue
setTimestamp(long timestamp)188         public Builder setTimestamp(long timestamp) {
189             this.mTimestamp = timestamp;
190             return this;
191         }
192 
193         /** Sets the consumer type. */
194         @CanIgnoreReturnValue
setConsumerType(int consumerType)195         public Builder setConsumerType(int consumerType) {
196             this.mConsumerType = consumerType;
197             return this;
198         }
199 
200         /** Sets whether is the full charge cycle start. */
201         @CanIgnoreReturnValue
setIsFullChargeCycleStart(boolean isFullChargeCycleStart)202         public Builder setIsFullChargeCycleStart(boolean isFullChargeCycleStart) {
203             this.mIsFullChargeCycleStart = isFullChargeCycleStart;
204             return this;
205         }
206 
207         /** Sets the battery information. */
208         @CanIgnoreReturnValue
setBatteryInformation(String batteryInformation)209         public Builder setBatteryInformation(String batteryInformation) {
210             this.mBatteryInformation = batteryInformation;
211             return this;
212         }
213 
214         /** Sets the battery information debug string. */
215         @CanIgnoreReturnValue
setBatteryInformationDebug(String batteryInformationDebug)216         public Builder setBatteryInformationDebug(String batteryInformationDebug) {
217             this.mBatteryInformationDebug = batteryInformationDebug;
218             return this;
219         }
220 
221         /** Builds the BatteryState. */
build()222         public BatteryState build() {
223             return new BatteryState(
224                     mUid,
225                     mUserId,
226                     mPackageName,
227                     mTimestamp,
228                     mConsumerType,
229                     mIsFullChargeCycleStart,
230                     mBatteryInformation,
231                     mBatteryInformationDebug);
232         }
233 
Builder()234         private Builder() {}
235     }
236 }
237