Interface init
ActivityManagerService
public void setUsageStatsManager(UsageStatsManagerInternal usageStatsManager) {
mUsageStatsService = usageStatsManager;
}
SystemServer
It is invoked in SystemServer
:
private void startCoreServices() {
// Tracks the battery level. Requires LightService.
mSystemServiceManager.startService(BatteryService.class);
// Tracks application usage stats.
mSystemServiceManager.startService(UsageStatsService.class);
mActivityManagerService.setUsageStatsManager(
LocalServices.getService(UsageStatsManagerInternal.class));
// Tracks whether the updatable WebView is in a ready state and watches for update installs.
mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
}
ActivityManagerService
will invoke updateUsageStats
indirectly in UsageStatsService
class to update usage data.
UsageStatsService updateUsageStats
void updateUsageStats(ActivityRecord component, boolean resumed) {
if (DEBUG_SWITCH) Slog.d(TAG_SWITCH,
"updateUsageStats: comp=" + component + "res=" + resumed);
final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
if (resumed) {
if (mUsageStatsService != null) {
mUsageStatsService.reportEvent(component.realActivity, component.userId,
UsageEvents.Event.MOVE_TO_FOREGROUND);
}
synchronized (stats) {
stats.noteActivityResumedLocked(component.app.uid);
}
} else {
if (mUsageStatsService != null) {
mUsageStatsService.reportEvent(component.realActivity, component.userId,
UsageEvents.Event.MOVE_TO_BACKGROUND);
}
synchronized (stats) {
stats.noteActivityPausedLocked(component.app.uid);
}
}
}
it is invoked in ActivityStack startPausingLocked
with three points:
ActivityStack startPausingLocked
when a activity is launched,it will be added to stack,from ActivityManager
to ActivityManagerService
,it invokes ActivityStackSupervisor createStackOnDisplay
method,in this method, it initializes ActivityContainer
object,and allocations a stack id for this activity,in ActivityContainer
constructor,it initializes Stack object.
ActivityStack createStackOnDisplay(int stackId, int displayId, boolean onTop) {
ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
if (activityDisplay == null) {
return null;
}
ActivityContainer activityContainer = new ActivityContainer(stackId);
mActivityContainers.put(stackId, activityContainer);
activityContainer.attachToDisplayLocked(activityDisplay, onTop);
return activityContainer.mStack;
}
in ActivityContainer:
ActivityContainer(int stackId) {
synchronized (mService) {
mStackId = stackId;
mStack = new ActivityStack(this, mRecentTasks);
mIdString = "ActivtyContainer{" + mStackId + "}";
if (DEBUG_STACK) Slog.d(TAG_STACK, "Creating " + this);
}
}
When a activity from resume-state to pause-state,it may invoke startPausingLocked
in ActivityStack class,this method is invoked in several conditions:
- boot is finishing but not unlock
- device is locked and lock view is visible
- device is locked and lock view is hidden while a activity at the top of stack meanwhile device is in dreaming or doze or awake mode which indicate device is sleeping
- activity is popping out and finished
- app’s process died but exists visible activity
- activity at top while device unlocked
- activity configuration is changed
- activity from or to Translucent
- new activity starts while others to pause state
the sleep which we can find in AMS:
private boolean shouldSleepLocked() {
// Resume applications while running a voice interactor.
if (mRunningVoice != null) {
return false;
}
// TODO: Transform the lock screen state into a sleep token instead.
switch (mWakefulness) {
case PowerManagerInternal.WAKEFULNESS_AWAKE:
case PowerManagerInternal.WAKEFULNESS_DREAMING:
case PowerManagerInternal.WAKEFULNESS_DOZING:
// Pause applications whenever the lock screen is shown or any sleep
// tokens have been acquired.
return (mLockScreenShown != LOCK_SCREEN_HIDDEN || !mSleepTokens.isEmpty());
case PowerManagerInternal.WAKEFULNESS_ASLEEP:
default:
// If we're asleep then pause applications unconditionally.
return true;
}
}
- shutdown
@Override
public boolean shutdown(int timeout) {
if (checkCallingPermission(android.Manifest.permission.SHUTDOWN)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires permission "
+ android.Manifest.permission.SHUTDOWN);
}
boolean timedout = false;
synchronized(this) {
mShuttingDown = true;
updateEventDispatchingLocked();
timedout = mStackSupervisor.shutdownLocked(timeout);
}
mAppOpsService.shutdown();
if (mUsageStatsService != null) {
mUsageStatsService.prepareShutdown();
}
mBatteryStatsService.shutdown();
synchronized (this) {
mProcessStats.shutdownLocked();
notifyTaskPersisterLocked(null, true);
}
return timedout;
}
After SHUT_DOWN broadcast is sent,IActivityManager
will invoke shutdown
method,this referred to shutdown
in AMS.
- maybeUpdateUsageStatsLocked
private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) {
if (DEBUG_USAGE_STATS) {
Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
+ "] state changes: old = " + app.setProcState + ", new = "
+ app.curProcState);
}
if (mUsageStatsService == null) {
return;
}
boolean isInteraction;
// To avoid some abuse patterns, we are going to be careful about what we consider
// to be an app interaction. Being the top activity doesn't count while the display
// is sleeping, nor do short foreground services.
if (app.curProcState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
isInteraction = true;
app.fgInteractionTime = 0;
} else if (app.curProcState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
if (app.fgInteractionTime == 0) {
app.fgInteractionTime = nowElapsed;
isInteraction = false;
} else {
isInteraction = nowElapsed > app.fgInteractionTime + SERVICE_USAGE_INTERACTION_TIME;
}
} else {
// If the app was being forced to the foreground, by say a Toast, then
// no need to treat it as an interaction
isInteraction = app.forcingToForeground == null
&& app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
app.fgInteractionTime = 0;
}
if (isInteraction && (!app.reportedInteraction
|| (nowElapsed-app.interactionEventTime) > USAGE_STATS_INTERACTION_INTERVAL)) {
app.interactionEventTime = nowElapsed;
String[] packages = app.getPackageList();
if (packages != null) {
for (int i = 0; i < packages.length; i++) {
mUsageStatsService.reportEvent(packages[i], app.userId,
UsageEvents.Event.SYSTEM_INTERACTION);
}
}
}
app.reportedInteraction = isInteraction;
if (!isInteraction) {
app.interactionEventTime = 0;
}
}
The method is invoked in applyOomAdjLocked
method.adj is a strategy by goals for app,it relates to each app’s memory,and can be triggered with activity starting or finishing,service starting or binding or destroying and so on.It also shows that maximum time for updating usage stats is 24 hours.
However,the applyOomAdjLocked
invokes UsageStatsService.reportEvent
with UsageEvents.Event.SYSTEM_INTERACTION
argument,we will analysis later.
Backing to updateUsageStats
method,we can find three core codes update usage stats:
mUsageStatsService.reportEvent(component.realActivity, component.userId,UsageEvents.Event.MOVE_TO_FOREGROUND);
mUsageStatsService.reportEvent(component.realActivity, component.userId,UsageEvents.Event.MOVE_TO_BACKGROUND);
mUsageStatsService.reportEvent(packages[i], app.userId,UsageEvents.Event.SYSTEM_INTERACTION);
UsageStatsManagerInternal
UsageStatsService LocalService
In UsageStatsService
class,we can find that LocalService
extends UsageStatsManagerInternal
,reportEvent
method defines here:
private final class LocalService extends UsageStatsManagerInternal {
@Override
public void reportEvent(ComponentName component, int userId, int eventType) {
if (component == null) {
Slog.w(TAG, "Event reported without a component name");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = component.getPackageName();
event.mClass = component.getClassName();
// This will later be converted to system time.
event.mTimeStamp = SystemClock.elapsedRealtime();
event.mEventType = eventType;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
@Override
public void reportEvent(String packageName, int userId, int eventType) {
if (packageName == null) {
Slog.w(TAG, "Event reported without a package name");
return;
}
UsageEvents.Event event = new UsageEvents.Event();
event.mPackage = packageName;
// This will later be converted to system time.
event.mTimeStamp = SystemClock.elapsedRealtime();
event.mEventType = eventType;
mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
}
}
UserUsageStatsService reportEvent
the actual work is in a handler which finally invoke reportEvent
in UserUsageStatsService
class:
void reportEvent(UsageEvents.Event event) {
if (DEBUG) {
Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
+ "[" + event.mTimeStamp + "]: "
+ eventToString(event.mEventType));
}
if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
// Need to rollover
rolloverStats(event.mTimeStamp);
}
final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];
final Configuration newFullConfig = event.mConfiguration;
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE &&
currentDailyStats.activeConfiguration != null) {
// Make the event configuration a delta.
event.mConfiguration = Configuration.generateDelta(
currentDailyStats.activeConfiguration, newFullConfig);
}
// Add the event to the daily list.
if (currentDailyStats.events == null) {
currentDailyStats.events = new TimeSparseArray<>();
}
if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) {
currentDailyStats.events.put(event.mTimeStamp, event);
}
for (IntervalStats stats : mCurrentStats) {
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
} else {
stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
}
}
notifyStatsChanged();
}
we can figure out that the latest usage event is put into mCurrentStats
events object with INTERVAL_DAILY
type.
currentDailyStats.events.put(event.mTimeStamp, event);
And core code is:
stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
IntervalStats update
In IntervalStats update
method:
void update(String packageName, long timeStamp, int eventType) {
UsageStats usageStats = getOrCreateUsageStats(packageName);
// TODO(adamlesinski): Ensure that we recover from incorrect event sequences
// like double MOVE_TO_BACKGROUND, etc.
if (eventType == UsageEvents.Event.MOVE_TO_BACKGROUND ||
eventType == UsageEvents.Event.END_OF_DAY) {
if (usageStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
usageStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
usageStats.mTotalTimeInForeground += timeStamp - usageStats.mLastTimeUsed;
}
}
if (isStatefulEvent(eventType)) {
usageStats.mLastEvent = eventType;
}
if (eventType != UsageEvents.Event.SYSTEM_INTERACTION) {
usageStats.mLastTimeUsed = timeStamp;
}
usageStats.mEndTimeStamp = timeStamp;
endTime = timeStamp;
}
get the last usage stats with same package name or create a new one:
UsageStats usageStats = getOrCreateUsageStats(packageName);
getOrCreateUsageStats
method:
UsageStats getOrCreateUsageStats(String packageName) {
UsageStats usageStats = packageStats.get(packageName);
if (usageStats == null) {
usageStats = new UsageStats();
usageStats.mPackageName = getCachedStringRef(packageName);
usageStats.mBeginTimeStamp = beginTime;
usageStats.mEndTimeStamp = endTime;
packageStats.put(usageStats.mPackageName, usageStats);
}
return usageStats;
}
beginTime
is initialized in init()
method in UserUsageStatsService
class which contains loadActiveStats
method and begin-Time is the initial-time stamp when UserUsageStatsService
is first initialized for user.
and current usage stats object is saved in:
public final ArrayMap<String, UsageStats> packageStats = new ArrayMap<>();
notifyStatsChanged()
updating usage stats is doing well and notifyStatsChanged();
is emerging at the last of reportEvent
method:
private void notifyStatsChanged() {
if (!mStatsChanged) {
mStatsChanged = true;
mListener.onStatsUpdated();
}
}
UsageStatsService onStatsUpdated
mListener
is from UsageStatsService
,so onStatsUpdated
will be invoked in UsageStatsService
class:
public void onStatsUpdated() {
mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL);
}
UsageStatsService flushToDiskLocked
Returned to handler again,this message invoke service.persistActiveStats
:
private void flushToDiskLocked() {
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
UserUsageStatsService service = mUserState.valueAt(i);
service.persistActiveStats();
mAppIdleHistory.writeAppIdleTimesLocked(mUserState.keyAt(i));
}
// Persist elapsed and screen on time. If this fails for whatever reason, the apps will be
// considered not-idle, which is the safest outcome in such an event.
mAppIdleHistory.writeAppIdleDurationsLocked();
mHandler.removeMessages(MSG_FLUSH_TO_DISK);
}
UserUsageStatsService persistActiveStats
service
is UserUsageStatsService
,method persistActiveStats
is:
void persistActiveStats() {
if (mStatsChanged) {
Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
try {
for (int i = 0; i < mCurrentStats.length; i++) {
mDatabase.putUsageStats(i, mCurrentStats[i]);
}
mStatsChanged = false;
} catch (IOException e) {
Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e);
}
}
}
UsageStatsDatabase putUsageStats
well,invoked in putUsageStats
in UsageStatsDatabase
class,and data will be written in each interval type file unless data is exist:
public void putUsageStats(int intervalType, IntervalStats stats) throws IOException {
if (stats == null) return;
synchronized (mLock) {
if (intervalType < 0 || intervalType >= mIntervalDirs.length) {
throw new IllegalArgumentException("Bad interval type " + intervalType);
}
AtomicFile f = mSortedStatFiles[intervalType].get(stats.beginTime);
if (f == null) {
f = new AtomicFile(new File(mIntervalDirs[intervalType],
Long.toString(stats.beginTime)));
mSortedStatFiles[intervalType].put(stats.beginTime, f);
}
UsageStatsXml.write(f, stats);
stats.lastTimeSaved = f.getLastModifiedTime();
}
}
UsageStatsXMLV1 write
finally,the UsageStatsXMLV1
class write
method is invoked,the core codes:
for (int i = 0; i < statsCount; i++) {
writeUsageStats(xml, stats, stats.packageStats.valueAt(i));
}
UsageStatsXMLV1 writeUsageStats
writeUsageStats
method:
private static void writeUsageStats(XmlSerializer xml, final IntervalStats stats,
final UsageStats usageStats) throws IOException {
xml.startTag(null, PACKAGE_TAG);
// Write the time offset.
XmlUtils.writeLongAttribute(xml, LAST_TIME_ACTIVE_ATTR,
usageStats.mLastTimeUsed - stats.beginTime);
XmlUtils.writeStringAttribute(xml, PACKAGE_ATTR, usageStats.mPackageName);
XmlUtils.writeLongAttribute(xml, TOTAL_TIME_ACTIVE_ATTR, usageStats.mTotalTimeInForeground);
XmlUtils.writeIntAttribute(xml, LAST_EVENT_ATTR, usageStats.mLastEvent);
xml.endTag(null, PACKAGE_TAG);
}
it will write package name,last usage time,usage duration and last event in file.
usage duration
usageStats.mTotalTimeInForeground += timeStamp - usageStats.mLastTimeUsed;
timeStamp is current time,mLastTimeUsed is last update time.
end time equals to current update-time
endTime = timeStamp;
and
usageStats.mEndTimeStamp = timeStamp;
LAST_TIME_ACTIVE_ATTR = “lastTimeActive”;
it equals to usageStats.mLastTimeUsed - stats.beginTime
.
it is not the package or component start time or last usage time,but is the gap which is between last update time and UsageUsageStatsService
initial time.