func main() {
	flag.Parse()

	c, err := ioutil.ReadFile(*inputFile)
	if err != nil {
		log.Fatalf("Cannot open the file %s: %v", *inputFile, err)
	}

	br, fname, err := bugreportutils.ExtractBugReport(*inputFile, c)
	if err != nil {
		log.Fatalf("Error getting file contents: %v", err)
	}
	fmt.Printf("Parsing %s\n", fname)
	bs := bugreportutils.ExtractBatterystatsCheckin(br)
	if strings.Contains(bs, "Exception occurred while dumping") {
		log.Fatalf("Exception found in battery dump.")
	}
	m, err := bugreportutils.ParseMetaInfo(br)
	if err != nil {
		log.Fatalf("Unable to get meta info: %v", err)
	}
	s := &sessionpb.Checkin{
		Checkin:          proto.String(bs),
		BuildFingerprint: proto.String(m.BuildFingerprint),
	}
	pkgs, errs := packageutils.ExtractAppsFromBugReport(br)
	if len(errs) > 0 {
		log.Fatalf("Errors encountered when getting package list: %v", errs)
	}

	var ctr checkinutil.IntCounter
	stats, warns, errs := checkinparse.ParseBatteryStats(&ctr, checkinparse.CreateCheckinReport(s), pkgs)
	if len(warns) > 0 {
		log.Printf("Encountered unexpected warnings: %v\n", warns)
	}
	if len(errs) > 0 {
		log.Fatalf("Could not parse battery stats: %v\n", errs)
	}
	fmt.Println("\n################\n")
	fmt.Println("Partial Wakelocks")
	fmt.Println("################\n")
	var pwl []*checkinparse.WakelockInfo
	for _, app := range stats.App {
		for _, pw := range app.Wakelock {
			if pw.GetPartialTimeMsec() > 0 {
				pwl = append(pwl,
					&checkinparse.WakelockInfo{
						Name:     fmt.Sprintf("%s : %s", app.GetName(), pw.GetName()),
						UID:      app.GetUid(),
						Duration: time.Duration(pw.GetPartialTimeMsec()) * time.Millisecond,
					})
			}
		}

	}
	checkinparse.SortByTime(pwl)
	for _, pw := range pwl[:min(5, len(pwl))] {
		fmt.Printf("%s (uid=%d) %s\n", pw.Duration, pw.UID, pw.Name)
	}

	fmt.Println("\n################")
	fmt.Println("Kernel Wakelocks")
	fmt.Println("################\n")
	var kwl []*checkinparse.WakelockInfo
	for _, kw := range stats.System.KernelWakelock {
		if kw.GetName() != "PowerManagerService.WakeLocks" && kw.GetTimeMsec() > 0 {
			kwl = append(kwl, &checkinparse.WakelockInfo{
				Name:     kw.GetName(),
				Duration: time.Duration(kw.GetTimeMsec()) * time.Millisecond,
			})
		}
	}
	checkinparse.SortByTime(kwl)
	for _, kw := range kwl[:min(5, len(kwl))] {
		fmt.Printf("%s %s\n", kw.Duration, kw.Name)
	}

	data, err := proto.Marshal(stats)
	if err != nil {
		log.Fatalf("Error from proto.Marshal: %v", err)
	}
	ioutil.WriteFile("checkin.proto", data, 0600)
}
// ParseCheckinData creates a Checkin struct from the given aggregated battery stats.
func ParseCheckinData(c *bspb.BatteryStats) Checkin {
	if c == nil {
		return Checkin{}
	}

	realtime := time.Duration(c.System.Battery.GetBatteryRealtimeMsec()) * time.Millisecond

	out := Checkin{
		Device:           c.Build.GetDevice(),
		Build:            c.Build.GetBuildId(),
		BuildFingerprint: c.Build.GetFingerprint(),
		ReportVersion:    c.GetReportVersion(),

		Realtime:          realtime,
		ScreenOffRealtime: time.Duration(c.System.Battery.GetScreenOffRealtimeMsec()) * time.Millisecond,

		ScreenOffDischargePoints: c.System.BatteryDischarge.GetScreenOff(),
		ScreenOnDischargePoints:  c.System.BatteryDischarge.GetScreenOn(),

		EstimatedDischarge: c.System.PowerUseSummary.GetComputedPowerMah(),
		ActualDischarge:    (c.System.PowerUseSummary.GetMinDrainedPowerMah() + c.System.PowerUseSummary.GetMaxDrainedPowerMah()) / 2,

		// Uptime is the same as screen-off uptime + screen on time
		Uptime: MDuration{
			V: (time.Duration(c.System.Battery.GetBatteryUptimeMsec()) * time.Millisecond),
		},

		ScreenOffUptime: MDuration{
			V: (time.Duration(c.System.Battery.GetScreenOffUptimeMsec()) * time.Millisecond),
		},

		ScreenOnTime: MDuration{
			V: (time.Duration(c.System.Misc.GetScreenOnTimeMsec()) * time.Millisecond),
		},

		PartialWakelockTime: MDuration{
			V: (time.Duration(c.System.Misc.GetPartialWakelockTimeMsec()) * time.Millisecond),
		},

		KernelOverheadTime: MDuration{
			V: (time.Duration(c.System.Battery.GetScreenOffUptimeMsec()-c.System.Misc.GetPartialWakelockTimeMsec()) * time.Millisecond),
		},

		SignalScanningTime: MDuration{
			V: (time.Duration(c.System.SignalScanningTime.GetTimeMsec()) * time.Millisecond),
		},

		MobileActiveTime: MDuration{
			V: (time.Duration(c.System.Misc.GetMobileActiveTimeMsec()) * time.Millisecond),
		},

		PhoneCallTime: MDuration{
			V: (time.Duration(c.System.Misc.GetPhoneOnTimeMsec()) * time.Millisecond),
		},

		WifiOnTime: MDuration{
			V: (time.Duration(c.System.Misc.GetWifiOnTimeMsec()) * time.Millisecond),
		},

		DeviceIdleModeEnabledTime: MDuration{
			V: (time.Duration(c.System.Misc.GetDeviceIdleModeEnabledTimeMsec()) * time.Millisecond),
		},

		DeviceIdlingTime: MDuration{
			V: (time.Duration(c.System.Misc.GetDeviceIdlingTimeMsec()) * time.Millisecond),
		},

		FullWakelockTime: MDuration{
			V: (time.Duration(c.System.Misc.GetFullWakelockTimeMsec()) * time.Millisecond),
		},

		InteractiveTime: MDuration{
			V: (time.Duration(c.System.Misc.GetInteractiveTimeMsec()) * time.Millisecond),
		},

		BluetoothOnTime: MDuration{
			V: (time.Duration(c.System.Misc.GetBluetoothOnTimeMsec()) * time.Millisecond),
		},

		LowPowerModeEnabledTime: MDuration{
			V: (time.Duration(c.System.Misc.GetLowPowerModeEnabledTimeMsec()) * time.Millisecond),
		},
		ConnectivityChanges: c.System.Misc.GetConnectivityChanges(),
	}
	out.ScreenOffUptimePercentage = (float32(out.ScreenOffUptime.V) / float32(realtime)) * 100
	out.ScreenOnTimePercentage = (float32(out.ScreenOnTime.V) / float32(realtime)) * 100
	out.PartialWakelockTimePercentage = (float32(out.PartialWakelockTime.V) / float32(realtime)) * 100
	out.KernelOverheadTimePercentage = (float32(out.KernelOverheadTime.V) / float32(realtime)) * 100
	out.SignalScanningTimePercentage = (float32(out.SignalScanningTime.V) / float32(realtime)) * 100
	out.MobileActiveTimePercentage = (float32(out.MobileActiveTime.V) / float32(realtime)) * 100
	out.FullWakelockTimePercentage = (float32(out.FullWakelockTime.V) / float32(realtime)) * 100
	out.PhoneCallTimePercentage = (float32(out.PhoneCallTime.V) / float32(realtime)) * 100
	out.DeviceIdleModeEnabledTimePercentage = (float32(out.DeviceIdleModeEnabledTime.V) / float32(realtime)) * 100
	out.DeviceIdlingTimePercentage = (float32(out.DeviceIdlingTime.V) / float32(realtime)) * 100
	out.InteractiveTimePercentage = (float32(out.InteractiveTime.V) / float32(realtime)) * 100

	out.BluetoothOnTimePercentage = (float32(out.BluetoothOnTime.V) / float32(realtime)) * 100
	out.LowPowerModeEnabledTimePercentage = (float32(out.LowPowerModeEnabledTime.V) / float32(realtime)) * 100

	out.MobileKiloBytesPerHr = MFloat32{V: (c.System.GlobalNetwork.GetMobileBytesRx() + c.System.GlobalNetwork.GetMobileBytesTx()) / (1024 * float32(realtime.Hours()))}
	out.WifiKiloBytesPerHr = MFloat32{V: (c.System.GlobalNetwork.GetWifiBytesRx() + c.System.GlobalNetwork.GetWifiBytesTx()) / (1024 * float32(realtime.Hours()))}

	if c.GetReportVersion() >= 14 {
		out.WifiOnTime = MDuration{V: time.Duration(c.System.GlobalWifi.GetWifiOnTimeMsec()) * time.Millisecond}
		out.WifiOnTimePercentage = (float32(out.WifiOnTime.V) / float32(realtime)) * 100
		out.WifiIdleTime = MDuration{V: time.Duration(c.System.GlobalWifi.GetWifiIdleTimeMsec()) * time.Millisecond}
		out.WifiTransmitTime = MDuration{V: time.Duration(c.System.GlobalWifi.GetWifiRxTimeMsec()+c.System.GlobalWifi.GetWifiTxTimeMsec()) * time.Millisecond}
		out.WifiTransmitTimePercentage = (float32(out.WifiTransmitTime.V) / float32(realtime)) * 100
		out.WifiDischargePoints = 100 * c.System.GlobalWifi.GetWifiPowerMah() / c.GetSystem().GetPowerUseSummary().GetBatteryCapacityMah()
		out.WifiDischargeRatePerHr = MFloat32{
			V: 100 * c.System.GlobalWifi.GetWifiPowerMah() / c.GetSystem().GetPowerUseSummary().GetBatteryCapacityMah() / float32(realtime.Hours()),
		}

		out.BluetoothIdleTime = MDuration{V: time.Duration(c.System.GlobalBluetooth.GetBluetoothIdleTimeMsec()) * time.Millisecond}
		out.BluetoothTransmitTime = MDuration{V: time.Duration(c.System.GlobalBluetooth.GetBluetoothRxTimeMsec()+c.System.GlobalBluetooth.GetBluetoothTxTimeMsec()) * time.Millisecond}
		out.BluetoothTransmitTimePercentage = (float32(out.BluetoothTransmitTime.V) / float32(realtime)) * 100
		out.BluetoothDischargePoints = 100 * c.System.GlobalBluetooth.GetBluetoothPowerMah() / c.GetSystem().GetPowerUseSummary().GetBatteryCapacityMah()
		out.BluetoothDischargeRatePerHr = MFloat32{
			V: 100 * c.System.GlobalBluetooth.GetBluetoothPowerMah() / c.GetSystem().GetPowerUseSummary().GetBatteryCapacityMah() / float32(realtime.Hours()),
		}
	}

	if s := c.System.Battery.GetScreenOffRealtimeMsec(); s > 0 {
		out.ScreenOffDischargeRatePerHr = MFloat32{V: 60 * 60 * 1000 * c.System.BatteryDischarge.GetScreenOff() / s}
	}
	if s := c.System.Misc.GetScreenOnTimeMsec(); s > 0 {
		out.ScreenOnDischargeRatePerHr = MFloat32{V: 60 * 60 * 1000 * c.System.BatteryDischarge.GetScreenOn() / s}
	}
	// Screen Brightness.
	out.ScreenBrightness = make(map[string]float32)
	for _, sb := range c.System.ScreenBrightness {
		out.ScreenBrightness[sb.GetName().String()] += (sb.GetTimeMsec() / msecsInMinute) / float32(realtime.Hours())
	}
	// Signal Strength.
	out.SignalStrength = make(map[string]float32)
	for _, ss := range c.System.SignalStrength {
		out.SignalStrength[ss.GetName().String()] += (ss.GetTimeMsec() / msecsInMinute) / float32(realtime.Hours())
	}
	// Wifi Signal Strength.
	out.WifiSignalStrength = make(map[string]float32)
	for _, ws := range c.System.WifiSignalStrength {
		out.WifiSignalStrength[ws.GetName().String()] += (ws.GetTimeMsec() / msecsInMinute) / float32(realtime.Hours())
	}
	// Bluetooth States.
	out.BluetoothState = make(map[string]float32)
	for _, bs := range c.System.BluetoothState {
		out.BluetoothState[bs.GetName().String()] += (bs.GetTimeMsec() / msecsInMinute) / float32(realtime.Hours())

	}
	// DataConnection
	out.DataConnection = make(map[string]float32)
	for _, dc := range c.System.DataConnection {
		out.DataConnection[dc.GetName().String()] += (dc.GetTimeMsec() / msecsInMinute) / float32(realtime.Hours())
	}
	// Kernel Wakelocks.
	var kwl []*checkinparse.WakelockInfo
	for _, kw := range c.System.KernelWakelock {
		if kw.GetName() != "PowerManagerService.WakeLocks" && kw.GetTimeMsec() >= 0.01 {
			kwl = append(kwl, &checkinparse.WakelockInfo{
				Name:     kw.GetName(),
				Duration: time.Duration(kw.GetTimeMsec()) * time.Millisecond,
				Count:    kw.GetCount(),
			})
		}
	}
	// Sorting Kernel Wakelocks by time.
	checkinparse.SortByTime(kwl)
	for _, kw := range kwl {
		out.KernelWakelocks = append(out.KernelWakelocks, ActivityData{
			Name:         kw.Name,
			Count:        kw.Count,
			CountPerHour: kw.Count / float32(realtime.Hours()),
			Duration:     kw.Duration,
			SecondsPerHr: float32(kw.Duration.Seconds()) / float32(realtime.Hours()),
		})
	}

	// Wakeup Reasons.
	var wrl []*checkinparse.WakelockInfo
	for _, wr := range c.System.WakeupReason {
		if wr.GetTimeMsec() >= 0.01 {
			wrl = append(wrl, &checkinparse.WakelockInfo{
				Name:     wr.GetName(),
				Duration: time.Duration(wr.GetTimeMsec()) * time.Millisecond,
				Count:    wr.GetCount(),
			})
		}
	}

	// Sorting Wakeup Reasons by count.
	checkinparse.SortByCount(wrl)
	for _, wr := range wrl {
		out.WakeupReasons = append(out.WakeupReasons, ActivityData{
			Name:         wr.Name,
			Count:        wr.Count,
			CountPerHour: wr.Count / float32(realtime.Hours()),
			Duration:     wr.Duration,
			SecondsPerHr: float32(wr.Duration.Seconds()) / float32(realtime.Hours()),
		})
	}

	// Power usage per app.
	var e []*PowerUseData
	// Network usage per app.
	var m []*checkinparse.WakelockInfo
	var n []*NetworkTrafficData
	// App wakeup count.
	var wu []*RateData
	// App ANR and crash count.
	var ac []*ANRCrashData
	// CPU use per app.
	var cpu []*CPUData
	// Wifi activity per app.
	var wfScan []*checkinparse.WakelockInfo
	var wfFull []*checkinparse.WakelockInfo
	// SyncManager Tasks.
	var stl []*checkinparse.WakelockInfo
	// Userspace Partial Wakelocks and GPS use.
	var pwl []*checkinparse.WakelockInfo
	var gps []*checkinparse.WakelockInfo
	// Camera use per app.
	var ca []*checkinparse.WakelockInfo
	// Flashlight use per app.
	var fla []*checkinparse.WakelockInfo
	au := make(map[string]int32)
	bCapMah := c.GetSystem().GetPowerUseSummary().GetBatteryCapacityMah()
	for _, app := range c.App {
		if app.GetName() == "" {
			app.Name = proto.String(fmt.Sprintf("UNKNOWN_%d", app.GetUid()))
		}
		au[app.GetName()] = app.GetUid()
		this := AppData{
			Name: app.GetName(),
			UID:  app.GetUid(),
		}

		if pct := 100 * app.PowerUseItem.GetComputedPowerMah() / bCapMah; pct >= 0.01 {
			e = append(e, &PowerUseData{
				Name:    app.GetName(),
				UID:     app.GetUid(),
				Percent: pct,
			})
		}
		if mat, mac := app.Network.GetMobileActiveTimeMsec(), app.Network.GetMobileActiveCount(); mat >= 0.01 || mac > 0 {
			m = append(m, &checkinparse.WakelockInfo{
				Name:     app.GetName(),
				UID:      app.GetUid(),
				Duration: time.Duration(mat) * time.Millisecond,
				Count:    mac,
			})
		}
		wr := app.Network.GetWifiBytesRx()
		wt := app.Network.GetWifiBytesTx()
		mt := app.Network.GetMobileBytesTx()
		mr := app.Network.GetMobileBytesRx()
		if wr+wt+mt+mr >= 0.01 {
			ntd := NetworkTrafficData{
				Name:                   app.GetName(),
				UID:                    app.GetUid(),
				WifiMegaBytes:          (wr + wt) / (1024 * 1024),
				MobileMegaBytes:        (mr + mt) / (1024 * 1024),
				WifiMegaBytesPerHour:   (wr + wt) / (1024 * 1024) / float32(realtime.Hours()),
				MobileMegaBytesPerHour: (mr + mt) / (1024 * 1024) / float32(realtime.Hours()),
			}
			n = append(n, &ntd)
			this.Network = ntd
		}
		if w := app.Apk.GetWakeups(); w > 0 {
			rd := RateData{
				Name:       app.GetName(),
				UID:        app.GetUid(),
				Count:      w,
				CountPerHr: w / float32(realtime.Hours()),
			}
			wu = append(wu, &rd)
			this.Alarms = rd
		}
		for _, p := range app.Process {
			if an, cr := p.GetAnrs(), p.GetCrashes(); an > 0 || cr > 0 {
				ac = append(ac, &ANRCrashData{
					Name:       fmt.Sprintf("%s : %s", app.GetName(), p.GetName()),
					UID:        app.GetUid(),
					ANRCount:   int32(an),
					CrashCount: int32(cr),
				})
				out.TotalAppANRCount += int32(an)
				out.TotalAppCrashCount += int32(cr)
			}
		}
		out.TotalAppANRRate = float32(out.TotalAppANRCount) / float32(realtime.Hours())
		out.TotalAppCrashRate = float32(out.TotalAppCrashCount) / float32(realtime.Hours())

		if ut, st := app.Cpu.GetUserTimeMs(), app.Cpu.GetSystemTimeMs(); ut > 0 || st > 0 {
			cpud := CPUData{
				Name:       app.GetName(),
				UID:        app.GetUid(),
				UserTime:   time.Duration(ut) * time.Millisecond,
				SystemTime: time.Duration(st) * time.Millisecond,
				PowerPct:   100 * (app.Cpu.GetPowerMaMs() / (1000 * 60 * 60)) / bCapMah,
			}
			cpu = append(cpu, &cpud)
			this.CPU = cpud
		}
		if wfl := app.Wifi.GetFullWifiLockTimeMsec(); wfl > 0 {
			wfFull = append(wfFull, &checkinparse.WakelockInfo{
				Name:     app.GetName(),
				UID:      app.GetUid(),
				Duration: time.Duration(wfl) * time.Millisecond,
			})
		}
		wst := app.Wifi.GetScanTimeMsec()
		wsc := app.Wifi.GetScanCount()
		if wst > 0 || wsc > 0 {
			wfScan = append(wfScan, &checkinparse.WakelockInfo{
				Name:     app.GetName(),
				UID:      app.GetUid(),
				Duration: time.Duration(wst) * time.Millisecond,
				Count:    wsc,
			})
			this.WifiScan = ActivityData{
				Name:         app.GetName(),
				UID:          app.GetUid(),
				Count:        wsc,
				CountPerHour: wsc / float32(realtime.Hours()),
				Duration:     time.Duration(wst) * time.Millisecond,
				SecondsPerHr: float32(time.Duration(wst).Seconds()) / float32(realtime.Hours()),
			}
		}

		var stlt []*checkinparse.WakelockInfo
		for _, st := range app.Sync {
			stlt = append(stlt, &checkinparse.WakelockInfo{
				Name:     fmt.Sprintf("%s : %s", app.GetName(), st.GetName()),
				UID:      app.GetUid(),
				Duration: time.Duration(st.GetTotalTimeMsec()) * time.Millisecond,
				Count:    st.GetCount(),
			})
		}
		stl = append(stl, stlt...)
		this.Syncs = sumWakelockInfo(stlt, realtime)
		this.Syncs.Name = app.GetName()
		this.Syncs.UID = app.GetUid()

		var pwlt []*checkinparse.WakelockInfo
		for _, pw := range app.Wakelock {
			pwlt = append(pwlt, &checkinparse.WakelockInfo{
				Name:     fmt.Sprintf("%s : %s", app.GetName(), pw.GetName()),
				UID:      app.GetUid(),
				Duration: time.Duration(pw.GetPartialTimeMsec()) * time.Millisecond,
				Count:    pw.GetPartialCount(),
			})
		}
		pwl = append(pwl, pwlt...)
		this.PartialWakelocks = sumWakelockInfo(pwlt, realtime)
		this.PartialWakelocks.Name = app.GetName()
		this.PartialWakelocks.UID = app.GetUid()

		var gpst []*checkinparse.WakelockInfo
		for _, s := range app.Sensor {
			if s.GetNumber() == bugreportutils.GPSSensorNumber {
				gpst = append(gpst, &checkinparse.WakelockInfo{
					Name:     app.GetName(),
					UID:      app.GetUid(),
					Duration: time.Duration(s.GetTotalTimeMsec()) * time.Millisecond,
					Count:    s.GetCount(),
				})
				continue
			}
		}
		gps = append(gps, gpst...)
		this.GPSUse = sumWakelockInfo(gpst, realtime)
		this.GPSUse.Name = app.GetName()
		this.GPSUse.UID = app.GetUid()

		if cat, cac := app.Camera.GetTotalTimeMsec(), app.Camera.GetCount(); cat > 0 || cac > 0 {
			ca = append(ca, &checkinparse.WakelockInfo{
				Name:     app.GetName(),
				UID:      app.GetUid(),
				Duration: time.Duration(cat) * time.Millisecond,
				Count:    cac,
			})
		}
		if flt, flc := app.Flashlight.GetTotalTimeMsec(), app.Flashlight.GetCount(); flt > 0 || flc > 0 {
			fla = append(fla, &checkinparse.WakelockInfo{
				Name:     app.GetName(),
				UID:      app.GetUid(),
				Duration: time.Duration(flt) * time.Millisecond,
				Count:    flc,
			})
		}

		out.AggregatedApps = append(out.AggregatedApps, this)
	}
	for _, pwi := range c.System.PowerUseItem {
		if pwi.GetName() == bspb.BatteryStats_System_PowerUseItem_APP {
			// We have the apps split up in the preceding for loop, and the APP entry is just the sum of all of them, so we skip it here.
			continue
		}
		if pct := 100 * pwi.GetComputedPowerMah() / bCapMah; pct >= 0.01 {
			e = append(e, &PowerUseData{
				Name:    pwi.GetName().String(),
				Percent: pct,
			})
		}
	}

	sort.Sort(byPercent(e))
	for _, ent := range e {
		out.DevicePowerEstimates = append(out.DevicePowerEstimates, *ent)
	}

	checkinparse.SortByTime(m)
	for _, mad := range m {
		out.TopMobileActiveApps = append(out.TopMobileActiveApps, ActivityData{
			Name:         mad.Name,
			UID:          mad.UID,
			Count:        mad.Count,
			CountPerHour: mad.Count / float32(realtime.Hours()),
			Duration:     mad.Duration,
			SecondsPerHr: float32(mad.Duration.Seconds()) / float32(realtime.Hours()),
		})
	}

	sort.Sort(byMobileBytes(n))
	for _, ntd := range n {
		if ntd.MobileMegaBytes >= 0.01 {
			out.TopMobileTrafficApps = append(out.TopMobileTrafficApps, *ntd)
		}
	}

	sort.Sort(byWifiBytes(n))
	for _, ntd := range n {
		if ntd.WifiMegaBytes >= 0.01 {
			out.TopWifiTrafficApps = append(out.TopWifiTrafficApps, *ntd)
		}
	}

	sort.Sort(byCount(wu))
	for _, w := range wu {
		out.AppWakeups = append(out.AppWakeups, *w)
		out.TotalAppWakeupsPerHr += w.CountPerHr
	}

	sort.Sort(byCrashThenANR(ac))
	for _, x := range ac {
		out.ANRAndCrash = append(out.ANRAndCrash, *x)
	}

	sort.Sort(byPower(cpu))
	for _, cp := range cpu {
		out.CPUUsage = append(out.CPUUsage, *cp)
		out.TotalAppCPUPowerPct += cp.PowerPct
	}

	checkinparse.SortByTime(wfScan)
	for _, w := range wfScan {
		out.WifiScanActivity = append(out.WifiScanActivity, ActivityData{
			Name:         w.Name,
			UID:          w.UID,
			Count:        w.Count,
			CountPerHour: w.Count / float32(realtime.Hours()),
			Duration:     w.Duration,
			SecondsPerHr: float32(w.Duration.Seconds()) / float32(realtime.Hours()),
		})
	}

	checkinparse.SortByTime(wfFull)
	for _, w := range wfFull {
		out.WifiFullLockActivity = append(out.WifiFullLockActivity, ActivityData{
			Name:         w.Name,
			UID:          w.UID,
			Duration:     w.Duration,
			SecondsPerHr: float32(w.Duration.Seconds()) / float32(realtime.Hours()),
		})
	}

	// Sorting SyncManager Tasks by time.
	checkinparse.SortByTime(stl)
	for _, st := range stl {
		out.SyncTasks = append(out.SyncTasks, ActivityData{
			Name:         st.Name,
			UID:          st.UID,
			Count:        st.Count,
			CountPerHour: st.Count / float32(realtime.Hours()),
			Duration:     st.Duration,
			SecondsPerHr: float32(st.Duration.Seconds()) / float32(realtime.Hours()),
		})
		out.TotalAppSyncsPerHr += float32(st.Duration.Seconds()) / float32(realtime.Hours())
	}

	// Sorting Partial Wakelocks by time.
	checkinparse.SortByTime(pwl)
	for _, pw := range pwl {
		out.UserspaceWakelocks = append(out.UserspaceWakelocks, ActivityData{
			Name:         pw.Name,
			UID:          pw.UID,
			Count:        pw.Count,
			CountPerHour: pw.Count / float32(realtime.Hours()),
			Duration:     pw.Duration,
			SecondsPerHr: float32(pw.Duration.Seconds()) / float32(realtime.Hours()),
		})
	}

	// Sort GPS use by time.
	checkinparse.SortByTime(gps)
	for _, g := range gps {
		out.GPSUse = append(out.GPSUse, ActivityData{
			Name:         g.Name,
			UID:          g.UID,
			Count:        g.Count,
			CountPerHour: g.Count / float32(realtime.Hours()),
			Duration:     g.Duration,
			SecondsPerHr: float32(g.Duration.Seconds()) / float32(realtime.Hours()),
		})
		out.TotalAppGPSUseTimePerHour += float32(g.Duration.Seconds()) / float32(realtime.Hours())
	}

	// Sort camera use by time.
	checkinparse.SortByTime(ca)
	for _, c := range ca {
		out.CameraUse = append(out.CameraUse, ActivityData{
			Name:         c.Name,
			UID:          c.UID,
			Count:        c.Count,
			CountPerHour: c.Count / float32(realtime.Hours()),
			Duration:     c.Duration,
			SecondsPerHr: float32(c.Duration.Seconds()) / float32(realtime.Hours()),
		})
		out.TotalAppCameraUsePerHr += float32(c.Duration.Seconds()) / float32(realtime.Hours())
	}

	// Sort flashlight use by time.
	checkinparse.SortByTime(fla)
	for _, f := range fla {
		out.FlashlightUse = append(out.FlashlightUse, ActivityData{
			Name:         f.Name,
			UID:          f.UID,
			Count:        f.Count,
			CountPerHour: f.Count / float32(realtime.Hours()),
			Duration:     f.Duration,
			SecondsPerHr: float32(f.Duration.Seconds()) / float32(realtime.Hours()),
		})
		out.TotalAppFlashlightUsePerHr += float32(f.Duration.Seconds()) / float32(realtime.Hours())
	}
	return out
}
func parseCheckinData(c *bspb.BatteryStats) checkin {
	if c == nil {
		return checkin{}
	}

	realtime := time.Duration(c.System.Battery.GetBatteryRealtimeMsec()) * time.Millisecond

	out := checkin{
		Device:           c.Build.GetDevice(),
		Build:            c.Build.GetBuildId(),
		BuildFingerprint: c.Build.GetFingerprint(),
		ReportVersion:    c.GetReportVersion(),

		Realtime:          realtime,
		ScreenOffRealtime: time.Duration(c.System.Battery.GetScreenOffRealtimeMsec()) * time.Millisecond,

		ScreenOffDischargePoints: c.System.BatteryDischarge.GetScreenOff(),
		ScreenOnDischargePoints:  c.System.BatteryDischarge.GetScreenOn(),

		BatteryCapacity:    c.System.PowerUseSummary.GetBatteryCapacityMah(),
		EstimatedDischarge: c.System.PowerUseSummary.GetComputedPowerMah(),
		ActualDischarge:    (c.System.PowerUseSummary.GetMinDrainedPowerMah() + c.System.PowerUseSummary.GetMaxDrainedPowerMah()) / 2,

		// Uptime is the same as screen-off uptime + screen on time
		Uptime: mDuration{
			V: (time.Duration(c.System.Battery.GetBatteryUptimeMsec()) * time.Millisecond),
		},

		ScreenOffUptime: mDuration{
			V: (time.Duration(c.System.Battery.GetScreenOffUptimeMsec()) * time.Millisecond),
		},

		ScreenOnTime: mDuration{
			V: (time.Duration(c.System.Misc.GetScreenOnTimeMsec()) * time.Millisecond),
		},

		PartialWakelockTime: mDuration{
			V: (time.Duration(c.System.Misc.GetPartialWakelockTimeMsec()) * time.Millisecond),
		},

		KernelOverheadTime: mDuration{
			V: (time.Duration(c.System.Battery.GetScreenOffUptimeMsec()-c.System.Misc.GetPartialWakelockTimeMsec()) * time.Millisecond),
		},

		SignalScanningTime: mDuration{
			V: (time.Duration(c.System.SignalScanningTime.GetTimeMsec()) * time.Millisecond),
		},

		MobileActiveTime: mDuration{
			V: (time.Duration(c.System.Misc.GetMobileActiveTimeMsec()) * time.Millisecond),
		},
	}

	out.MiscPercentage = 100 * (out.ActualDischarge - out.EstimatedDischarge) / out.BatteryCapacity

	out.MobileKiloBytesPerHr = mFloat32{V: (c.System.GlobalNetwork.GetMobileBytesRx() + c.System.GlobalNetwork.GetMobileBytesTx()) / (1024 * float32(realtime.Hours()))}
	out.WifiKiloBytesPerHr = mFloat32{V: (c.System.GlobalNetwork.GetWifiBytesRx() + c.System.GlobalNetwork.GetWifiBytesTx()) / (1024 * float32(realtime.Hours()))}

	if c.GetReportVersion() >= 14 {
		out.WifiOnTime = mDuration{V: time.Duration(c.System.GlobalWifi.GetWifiOnTimeMsec()) * time.Millisecond}
		out.WifiIdleTime = mDuration{V: time.Duration(c.System.GlobalWifi.GetWifiIdleTimeMsec()) * time.Millisecond}
		out.WifiTransmitTime = mDuration{V: time.Duration(c.System.GlobalWifi.GetWifiRxTimeMsec()+c.System.GlobalWifi.GetWifiTxTimeMsec()) * time.Millisecond}
		out.WifiDischargePoints = 100 * c.System.GlobalWifi.GetWifiPowerMah() / c.GetSystem().GetPowerUseSummary().GetBatteryCapacityMah()
		out.WifiDischargeRatePerHr = mFloat32{
			V: 100 * c.System.GlobalWifi.GetWifiPowerMah() / c.GetSystem().GetPowerUseSummary().GetBatteryCapacityMah() / float32(realtime.Hours()),
		}

		out.BluetoothIdleTime = mDuration{V: time.Duration(c.System.GlobalBluetooth.GetBluetoothIdleTimeMsec()) * time.Millisecond}
		out.BluetoothTransmitTime = mDuration{V: time.Duration(c.System.GlobalBluetooth.GetBluetoothRxTimeMsec()+c.System.GlobalBluetooth.GetBluetoothTxTimeMsec()) * time.Millisecond}
		out.BluetoothDischargePoints = 100 * c.System.GlobalBluetooth.GetBluetoothPowerMah() / c.GetSystem().GetPowerUseSummary().GetBatteryCapacityMah()
		out.BluetoothDischargeRatePerHr = mFloat32{
			V: 100 * c.System.GlobalBluetooth.GetBluetoothPowerMah() / c.GetSystem().GetPowerUseSummary().GetBatteryCapacityMah() / float32(realtime.Hours()),
		}
	}

	if s := c.System.Battery.GetScreenOffRealtimeMsec(); s > 0 {
		out.ScreenOffDichargeRatePerHr = mFloat32{V: 60 * 60 * 1000 * c.System.BatteryDischarge.GetScreenOff() / s}
	}
	if s := c.System.Misc.GetScreenOnTimeMsec(); s > 0 {
		out.ScreenOnDichargeRatePerHr = mFloat32{V: 60 * 60 * 1000 * c.System.BatteryDischarge.GetScreenOn() / s}
	}

	// Top Partial Wakelocks by time and count
	var pwl []*checkinparse.WakelockInfo
	for _, app := range c.App {
		for _, pw := range app.Wakelock {
			if pw.GetPartialTimeMsec() >= 0.01 {
				pwl = append(pwl, &checkinparse.WakelockInfo{
					Name:     fmt.Sprintf("%s : %s", app.GetName(), pw.GetName()),
					UID:      app.GetUid(),
					Duration: time.Duration(pw.GetPartialTimeMsec()) * time.Millisecond,
					Count:    pw.GetPartialCount(),
				})
			}
		}
	}

	// Top Partial Wakelocks by time
	checkinparse.SortByTime(pwl)
	for _, pw := range pwl {
		out.UserspaceWakelocks = append(out.UserspaceWakelocks, WakelockData{
			Name:     pw.Name,
			UID:      pw.UID,
			Count:    pw.Count,
			Duration: pw.Duration,
		})
	}

	// Top 5 Kernel Wakelocks
	var kwl []*checkinparse.WakelockInfo
	for _, kw := range c.System.KernelWakelock {
		if kw.GetName() != "PowerManagerService.WakeLocks" && kw.GetTimeMsec() >= 0.01 {
			kwl = append(kwl, &checkinparse.WakelockInfo{
				Name:     kw.GetName(),
				Duration: time.Duration(kw.GetTimeMsec()) * time.Millisecond,
				Count:    kw.GetCount(),
			})
		}
	}
	// Top Kernel Wakelocks by time
	checkinparse.SortByTime(kwl)
	for _, kw := range kwl {
		out.KernelWakelocks = append(out.KernelWakelocks, WakelockData{
			Name:     kw.Name,
			Count:    kw.Count,
			Duration: kw.Duration,
		})
	}

	// Top SyncTasks by time and count
	var stl []*checkinparse.WakelockInfo
	for _, app := range c.App {
		for _, st := range app.Sync {
			if st.GetTotalTimeMsec() >= 0.01 {
				stl = append(stl, &checkinparse.WakelockInfo{
					Name:     fmt.Sprintf("%s : %s", app.GetName(), st.GetName()),
					UID:      app.GetUid(),
					Duration: time.Duration(st.GetTotalTimeMsec()) * time.Millisecond,
					Count:    st.GetCount(),
				})
			}
		}
	}

	// Top SyncTasks by time
	checkinparse.SortByTime(stl)
	for _, st := range stl {
		out.SyncTasks = append(out.SyncTasks, WakelockData{
			Name:     st.Name,
			UID:      st.UID,
			Count:    st.Count,
			Duration: st.Duration,
		})
	}

	// Top power consumers and network users
	var e []*PowerUseData
	var m []*MobileActiveData
	var n []*NetworkTrafficData
	for _, app := range c.App {
		if mah := app.PowerUseItem.GetComputedPowerMah(); mah >= 0.01 {
			e = append(e, &PowerUseData{
				Name:    app.GetName(),
				UID:     app.GetUid(),
				Percent: 100 * mah / c.GetSystem().GetPowerUseSummary().GetBatteryCapacityMah(),
			})
		}
		if mat := app.Network.GetMobileActiveTimeMsec(); mat >= 0.01 {
			m = append(m, &MobileActiveData{
				Name:     app.GetName(),
				UID:      app.GetUid(),
				Duration: time.Duration(mat) * time.Millisecond,
			})
		}
		wr := app.Network.GetWifiBytesRx()
		wt := app.Network.GetWifiBytesTx()
		mt := app.Network.GetMobileBytesTx()
		mr := app.Network.GetMobileBytesRx()
		if wr+wt+mt+mr >= 0.01 {
			n = append(n, &NetworkTrafficData{
				Name:            app.GetName(),
				UID:             app.GetUid(),
				WifiMegaBytes:   (wr + wt) / (1024 * 1024),
				MobileMegaBytes: (mr + mt) / (1024 * 1024),
			})
		}
	}
	for _, pwi := range c.System.PowerUseItem {
		if pwi.GetName() == bspb.BatteryStats_System_PowerUseItem_APP {
			// We have the apps split up in the preceding for loop, and the APP entry is just the sum of all of them, so we skip it here.
			continue
		}
		if mah := pwi.GetComputedPowerMah(); mah >= 0.01 {
			e = append(e, &PowerUseData{
				Name:    pwi.GetName().String(),
				Percent: 100 * mah / c.GetSystem().GetPowerUseSummary().GetBatteryCapacityMah(),
			})
		}
	}

	sort.Sort(byPercent(e))
	for _, ent := range e {
		out.TopBatteryConsumingEntities = append(out.TopBatteryConsumingEntities, *ent)
	}

	sort.Sort(byTime(m))
	for _, mad := range m {
		out.TopMobileActiveApps = append(out.TopMobileActiveApps, *mad)
	}

	sort.Sort(byMobileBytes(n))
	for _, ntd := range n {
		if ntd.MobileMegaBytes >= 0.01 {
			out.TopMobileTrafficApps = append(out.TopMobileTrafficApps, *ntd)
		}
	}

	sort.Sort(byWifiBytes(n))
	for _, ntd := range n {
		if ntd.WifiMegaBytes >= 0.01 {
			out.TopWifiTrafficApps = append(out.TopWifiTrafficApps, *ntd)
		}
	}

	return out
}