1/*
2 * Copyright (C) 2015 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
17package com.android.server.am;
18
19import android.app.ActivityOptions;
20import android.app.PendingIntent;
21import android.content.Context;
22import android.content.Intent;
23import android.os.Bundle;
24import android.os.SystemClock;
25import android.util.ArrayMap;
26import android.util.MutableLong;
27import android.util.TimeUtils;
28import android.util.proto.ProtoOutputStream;
29import android.util.proto.ProtoUtils;
30
31import java.io.PrintWriter;
32
33/**
34 * Tracks the time a user spent in an app.
35 */
36public class AppTimeTracker {
37    private final PendingIntent mReceiver;
38
39    private long mTotalTime;
40    private final ArrayMap<String, MutableLong> mPackageTimes = new ArrayMap<>();
41
42    private long mStartedTime;
43    private String mStartedPackage;
44    private MutableLong mStartedPackageTime;
45
46    public AppTimeTracker(PendingIntent receiver) {
47        mReceiver = receiver;
48    }
49
50    public void start(String packageName) {
51        long now = SystemClock.elapsedRealtime();
52        if (mStartedTime == 0) {
53            mStartedTime = now;
54        }
55        if (!packageName.equals(mStartedPackage)) {
56            if (mStartedPackageTime != null) {
57                long elapsedTime = now - mStartedTime;
58                mStartedPackageTime.value += elapsedTime;
59                mTotalTime += elapsedTime;
60            }
61            mStartedPackage = packageName;
62            mStartedPackageTime = mPackageTimes.get(packageName);
63            if (mStartedPackageTime == null) {
64                mStartedPackageTime = new MutableLong(0);
65                mPackageTimes.put(packageName, mStartedPackageTime);
66            }
67        }
68    }
69
70    public void stop() {
71        if (mStartedTime != 0) {
72            long elapsedTime = SystemClock.elapsedRealtime() - mStartedTime;
73            mTotalTime += elapsedTime;
74            if (mStartedPackageTime != null) {
75                mStartedPackageTime.value += elapsedTime;
76            }
77            mStartedPackage = null;
78            mStartedPackageTime = null;
79        }
80    }
81
82    public void deliverResult(Context context) {
83        stop();
84        Bundle extras = new Bundle();
85        extras.putLong(ActivityOptions.EXTRA_USAGE_TIME_REPORT, mTotalTime);
86        Bundle pkgs = new Bundle();
87        for (int i=mPackageTimes.size()-1; i>=0; i--) {
88            pkgs.putLong(mPackageTimes.keyAt(i), mPackageTimes.valueAt(i).value);
89        }
90        extras.putBundle(ActivityOptions.EXTRA_USAGE_TIME_REPORT_PACKAGES, pkgs);
91        Intent fillinIntent = new Intent();
92        fillinIntent.putExtras(extras);
93        try {
94            mReceiver.send(context, 0, fillinIntent);
95        } catch (PendingIntent.CanceledException e) {
96        }
97    }
98
99    public void dumpWithHeader(PrintWriter pw, String prefix, boolean details) {
100        pw.print(prefix); pw.print("AppTimeTracker #");
101        pw.print(Integer.toHexString(System.identityHashCode(this)));
102        pw.println(":");
103        dump(pw, prefix + "  ", details);
104    }
105
106    public void dump(PrintWriter pw, String prefix, boolean details) {
107        pw.print(prefix); pw.print("mReceiver="); pw.println(mReceiver);
108        pw.print(prefix); pw.print("mTotalTime=");
109        TimeUtils.formatDuration(mTotalTime, pw);
110        pw.println();
111        for (int i = 0; i < mPackageTimes.size(); i++) {
112            pw.print(prefix); pw.print("mPackageTime:"); pw.print(mPackageTimes.keyAt(i));
113            pw.print("=");
114            TimeUtils.formatDuration(mPackageTimes.valueAt(i).value, pw);
115            pw.println();
116        }
117        if (details && mStartedTime != 0) {
118            pw.print(prefix); pw.print("mStartedTime=");
119            TimeUtils.formatDuration(SystemClock.elapsedRealtime(), mStartedTime, pw);
120            pw.println();
121            pw.print(prefix); pw.print("mStartedPackage="); pw.println(mStartedPackage);
122        }
123    }
124
125    void writeToProto(ProtoOutputStream proto, long fieldId, boolean details) {
126        final long token = proto.start(fieldId);
127        proto.write(AppTimeTrackerProto.RECEIVER, mReceiver.toString());
128        proto.write(AppTimeTrackerProto.TOTAL_DURATION_MS, mTotalTime);
129        for (int i=0; i<mPackageTimes.size(); i++) {
130            final long ptoken = proto.start(AppTimeTrackerProto.PACKAGE_TIMES);
131            proto.write(AppTimeTrackerProto.PackageTime.PACKAGE, mPackageTimes.keyAt(i));
132            proto.write(AppTimeTrackerProto.PackageTime.DURATION_MS, mPackageTimes.valueAt(i).value);
133            proto.end(ptoken);
134        }
135        if (details && mStartedTime != 0) {
136            ProtoUtils.toDuration(proto, AppTimeTrackerProto.STARTED_TIME,
137                    mStartedTime, SystemClock.elapsedRealtime());
138            proto.write(AppTimeTrackerProto.STARTED_PACKAGE, mStartedPackage);
139        }
140        proto.end(token);
141    }
142}
143