Android UsageStatsManager get data

1、Register service(binder client)

ContextImpl

context.getSystemService(“usagestats”)

SystemServiceRegistry

public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

if it is not initialized:

registerService(Context.USAGE_STATS_SERVICE, UsageStatsManager.class,
        new CachedServiceFetcher<UsageStatsManager>() {
    @Override
    public UsageStatsManager createService(ContextImpl ctx) {
        IBinder iBinder = ServiceManager.getService(Context.USAGE_STATS_SERVICE);
        IUsageStatsManager service = IUsageStatsManager.Stub.asInterface(iBinder);
        return new UsageStatsManager(ctx.getOuterContext(), service);
    }});

2、SystemServer(binder server)

Device is booting with SystemServer.main() being invoked:

  • register UsageStatsService to ServiceManager:
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);
}

mSystemServiceManager.startService(UsageStatsService.class);is the core code.
it will invoke SystemServiceManager class’s startService method,

//Having deleted some extra codes for easy reading
public <T extends SystemService> T startService(Class<T> serviceClass) {
    try {
        final String name = serviceClass.getName();

        final T service;
        Constructor<T> constructor = serviceClass.getConstructor(Context.class);
        service = constructor.newInstance(mContext);
      
        // Register it.
        mServices.add(service);

        // Start it.
        try {
            service.onStart();
        } catch (RuntimeException ex) {
          
        }
        return service;
    } finally {
    
    }
}

service.onStart();relates to a abstract class,SystemService,and it is extended by other system services,
such as:

public class UsageStatsService extends SystemService implements
        UserUsageStatsService.StatsUpdatedListener {}

in UsageStatsService onStart method,it does some initial works:

  • init the save-director for data :
File systemDataDir = new File(Environment.getDataDirectory(), "system");
mUsageStatsDir = new File(systemDataDir, "usagestats");
mUsageStatsDir.mkdirs();
  • register service
publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());

the codes relate to SystemService:

protected final void publishBinderService(String name, IBinder service,
        boolean allowIsolated) {
    ServiceManager.addService(name, service, allowIsolated);
}

3、Get data example

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private ArrayList<AppLaunchInfoBean> getAppLaunchInfoBean(long start, long end) {
    final UsageStatsManager usageStatsManager = (UsageStatsManager) mContext.getSystemService("usagestats");
    UsageEvents usageEvents = usageStatsManager.queryEvents(start, end);
    return getAppLaunchInfoBeanList(usageEvents, end);
}

4、Tracking source code

  • In UsageStatsManager class track queryEvents method:
public UsageEvents queryEvents(long beginTime, long endTime) {
  try {
      UsageEvents iter = mService.queryEvents(beginTime, endTime,        mContext.getOpPackageName());
      if (iter != null) {
          return iter;
      }
  } catch (RemoteException e) {
      // fallthrough and return null
  }
  return sEmptyResults;
}

UsageStatsService

mService is referred to UsageStatsService,this service is initialized in queryEvents method in UsageStatsService class,:

UsageEvents queryEvents(int userId, long beginTime, long endTime) {
    synchronized (mLock) {
        final long timeNow = checkAndGetTimeLocked();
        if (!validRange(timeNow, beginTime, endTime)) {
            return null;
        }

        final UserUsageStatsService service =
                getUserDataAndInitializeIfNeededLocked(userId, timeNow);
        return service.queryEvents(beginTime, endTime);
    }
}

getUserDataAndInitializeIfNeededLocked method is here:

private UserUsageStatsService getUserDataAndInitializeIfNeededLocked(int userId,
        long currentTimeMillis) {
    UserUsageStatsService service = mUserState.get(userId);
    if (service == null) {
        service = new UserUsageStatsService(getContext(), userId,
                new File(mUsageStatsDir, Integer.toString(userId)), this);
        service.init(currentTimeMillis);
        mUserState.put(userId, service);
    }
    return service;
}

the constructor and init method accomplish some important work:

UserUsageStatsService constructor

UserUsageStatsService(Context context, int userId, File usageStatsDir,
        StatsUpdatedListener listener) {
    mContext = context;
    mDailyExpiryDate = new UnixCalendar(0);
    mDatabase = new UsageStatsDatabase(usageStatsDir);
    mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
    mListener = listener;
    mLogPrefix = "User[" + Integer.toString(userId) + "] ";
    mUserId = userId;
}

mDatabase = new UsageStatsDatabase(usageStatsDir);it initializes file operator which can query data from disk file.

UsageStatsDatabase

public UsageStatsDatabase(File dir) {
    mIntervalDirs = new File[] {
            new File(dir, "daily"),
            new File(dir, "weekly"),
            new File(dir, "monthly"),
            new File(dir, "yearly"),
    };
    mVersionFile = new File(dir, "version");
    mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length];
    mCal = new UnixCalendar(0);
}

it initialized all directors in mIntervalDirs,and mSortedStatFiles is a map which keys are the file’s name which exclude end of .bak and values are file,what’s more ,the file’s name is time.

UserUsageStatsService init() method

init()method contains mDatabase.init(currentTimeMillis);method which updates current stats time and deletes some dead data which beyond today and it contains indexFilesLocked method.

  • UsageStatsDatabase init()
private void indexFilesLocked() {
    final FilenameFilter backupFileFilter = new FilenameFilter() {
        @Override
        public boolean accept(File dir, String name) {
            return !name.endsWith(BAK_SUFFIX);
        }
    };

    // Index the available usage stat files on disk.
    for (int i = 0; i < mSortedStatFiles.length; i++) {
        if (mSortedStatFiles[i] == null) {
            mSortedStatFiles[i] = new TimeSparseArray<>();
        } else {
            mSortedStatFiles[i].clear();
        }
        File[] files = mIntervalDirs[i].listFiles(backupFileFilter);
        if (files != null) {
            if (DEBUG) {
                Slog.d(TAG, "Found " + files.length + " stat files for interval " + i);
            }

            for (File f : files) {
                final AtomicFile af = new AtomicFile(f);
                try {
                    mSortedStatFiles[i].put(UsageStatsXml.parseBeginTime(af), af);
                } catch (IOException e) {
                    Slog.e(TAG, "failed to index file: " + f, e);
                }
            }
        }
    }
}

mSortedStatFiles put all interval type of files in and its key are file’s name which is the begin-time and detail is in UsageStatsXml.parseBeginTime:

public static long parseBeginTime(File file) throws IOException {
    String name = file.getName();
    while (name.endsWith(CHECKED_IN_SUFFIX)) {
        name = name.substring(0, name.length() - CHECKED_IN_SUFFIX.length());
    }

    try {
        return Long.parseLong(name);
    } catch (NumberFormatException e) {
        throw new IOException(e);
    }
}

Back to UserUsageStatsService init() method,it invokes loadActiveStats(currentTimeMillis);which load all type interval data,updateRolloverDeadline(); build daily data while all of interval type are not exist.
loadActiveStats method:

for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
    final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType);
    if (stats != null && currentTimeMillis - 500 >= stats.endTime &&
            currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) {
        if (DEBUG) {
            Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
                    sDateFormat.format(stats.beginTime) + "(" + stats.beginTime +
                    ") for interval " + intervalType);
        }
        mCurrentStats[intervalType] = stats;
    } else {
        // No good fit remains.
        if (DEBUG) {
            Slog.d(TAG, "Creating new stats @ " +
                    sDateFormat.format(currentTimeMillis) + "(" +
                    currentTimeMillis + ") for interval " + intervalType);
        }

        mCurrentStats[intervalType] = new IntervalStats();
        mCurrentStats[intervalType].beginTime = currentTimeMillis;
        mCurrentStats[intervalType].endTime = currentTimeMillis + 1;
    }
}

mCurrentStats is initialized in UserUsageStatsService class constructor:

mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];

and is filled with UsageStatsDatabase getLatestUsageStats returned:

mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
  • UsageStatsDatabase getLatestUsageStats:
try {
    final AtomicFile f = mSortedStatFiles[intervalType].valueAt(fileCount - 1);
    IntervalStats stats = new IntervalStats();
    UsageStatsXml.read(f, stats);
    return stats;
} catch (IOException e) {
    Slog.e(TAG, "Failed to read usage stats file", e);
}

the returned data is the latest file’s content.

Back to mService.queryEvents:

UsageEvents queryEvents(int userId, long beginTime, long endTime) {
    synchronized (mLock) {
        final long timeNow = checkAndGetTimeLocked();
        if (!validRange(timeNow, beginTime, endTime)) {
            return null;
        }

        final UserUsageStatsService service =
            getUserDataAndInitializeIfNeededLocked(userId, timeNow);
        return service.queryEvents(beginTime, endTime);
    }
}
  • And in UserUsageStatsService,method queryEvents finally invokes queryStats:
UsageEvents queryEvents(final long beginTime, final long endTime) {
    final ArraySet<String> names = new ArraySet<>();
    List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
            beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
                @Override
                public void combine(IntervalStats stats, boolean mutable,
                        List<UsageEvents.Event> accumulatedResult) {
                    if (stats.events == null) {
                        return;
                    }
    
                    final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
                    if (startIndex < 0) {
                        return;
                    }
    
                    final int size = stats.events.size();
                    for (int i = startIndex; i < size; i++) {
                        if (stats.events.keyAt(i) >= endTime) {
                            return;
                        }
    
                        final UsageEvents.Event event = stats.events.valueAt(i);
                        names.add(event.mPackage);
                        if (event.mClass != null) {
                            names.add(event.mClass);
                        }
                        accumulatedResult.add(event);
                    }
                }
            });
    
    if (results == null || results.isEmpty()) {
        return null;
    }
    
    String[] table = names.toArray(new String[names.size()]);
    Arrays.sort(table);
    return new UsageEvents(results, table);
}

Generally,in queryStats method,we find some vital codes:

// Truncate the endTime to just before the in-memory //stats. Then, we'll append the
// in-memory stats to the results (if necessary) so as to //avoid writing to disk too often.
final long truncatedEndTime = Math.min(currentStats.beginTime, endTime);

the code means some stats in memory that will not write to file immediately,and the truncatedEndTime make sure that the end time which will query in disk is less than the start time in current stats with interval type.
Firstly,it will search result in disk file:

List<T> results = mDatabase.queryUsageStats(intervalType, beginTime,truncatedEndTime, combiner);
  • UsageStatsDatabase queryUsageStats
final IntervalStats stats = new IntervalStats();
final ArrayList<T> results = new ArrayList<>();
for (int i = startIndex; i <= endIndex; i++) {
    final AtomicFile f = intervalStats.valueAt(i);

    if (DEBUG) {
        Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath());
    }

    try {
        UsageStatsXml.read(f, stats);
        if (beginTime < stats.endTime) {
            combiner.combine(stats, false, results);
        }
    } catch (IOException e) {
        Slog.e(TAG, "Failed to read usage stats file", e);
        // We continue so that we return results that are not
        // corrupt.
    }
}
return results;
  • UsageStatsXmlV1 read

UsageStatsXml.read(f, stats);will finally invoke UsageStatsXmlV1 read method,the file locates:/data/system/usagestats/0/,and with four directors,daily,monthly,weekly,yearly.File’s content is like this:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<usagestats version="1" endTime="86399999">
    <packages>
        <package lastTimeActive="-1552966614241" package="com.qiku.smart.components" timeActive="0" lastEvent="0" />
        <package lastTimeActive="-1552966614241" package="com.android.providers.telephony" timeActive="0" lastEvent="0" />
        <package lastTimeActive="-1552966614241" package="com.qiku.logsystem" timeActive="0" lastEvent="0" />
        <package lastTimeActive="-1552966614241" package="com.android.providers.calendar" timeActive="0" lastEvent="0" />
    </packages>
    <configurations>
        <config lastTimeActive="83403363" timeActive="48" count="2" fs="1065353216" locales="zh-CN" touch="3" key="1" keyHid="1" hardKeyHid="2" nav="1" navHid="2" ori="1" scrLay="268435810" clrMod="5" width="360" height="648" sw="360" density="480" app_bounds="0 0 1080 2016" />
        <config lastTimeActive="83403339" timeActive="51" count="2" fs="1065353216" locales="zh-CN" touch="1" key="1" keyHid="1" hardKeyHid="2" nav="1" navHid="2" ori="1" scrLay="268435810" clrMod="5" width="360" height="720" sw="360" density="480" app_bounds="0 0 1080 2160" />
        <config lastTimeActive="86399998" timeActive="74959721" count="2" active="true" fs="1065353216" locales="zh-CN" touch="3" key="1" keyHid="1" hardKeyHid="2" nav="1" navHid="2" ori="1" scrLay="268435810" clrMod="5" ui="17" width="360" height="648" sw="360" density="480" app_bounds="0 0 1080 2016" />
    </configurations>
    <event-log>
        <event time="459" package="com.qiku.android.launcher3" class="com.qiku.android.launcher3.Launcher" flags="0" type="1" />
        <event time="2181" package="com.qiku.android.launcher3" class="com.qiku.android.launcher3.Launcher" flags="0" type="2" />
    </event-log>
</usagestats>

we can get end-time which is the latest stats’s begin-time adding time-attribute in xml file.

statsOut.endTime = statsOut.beginTime + XmlUtils.readLongAttribute(parser, END_TIME_ATTR);

And another data are filled below:

while ((eventCode = parser.next()) != XmlPullParser.END_DOCUMENT
        && (eventCode != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    if (eventCode != XmlPullParser.START_TAG) {
        continue;
    }

    final String tag = parser.getName();
    switch (tag) {
        case PACKAGE_TAG:
            loadUsageStats(parser, statsOut);
            break;

        case CONFIG_TAG:
            loadConfigStats(parser, statsOut);
            break;

        case EVENT_TAG:
            loadEvent(parser, statsOut);
            break;
    }
}

the data is wrapped into statsOut object which is partial data,because entire data include that in memory,in UserUsageStatsService queryStats:

if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) {
    if (DEBUG) {
        Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
    }

    if (results == null) {
        results = new ArrayList<>();
    }
    combiner.combine(currentStats, true, results);
}

All data are wrapped in UsageEvents object to caller.

ContextImpl(ge SystemServ SystemServer SystemServ UsageStatsService ServiceManager invoke if not initialized will invoke registerService method <<----binder client binder server---->> invoke main method startCoreServices invoked startService(UsageStatsService.class) invoked onStart invoke UsageStatsService extends from SystemService publishBinderService invoke addService UsageStatsService rigistered ContextImpl(ge SystemServ SystemServer SystemServ UsageStatsService ServiceManager UsageStatsService Init
UsageStatsManager UsageStatsService UserUsageS UsageStat UsageStatsXmlV1 StatCombiner queryEvents invoke to get data,request from client to sever by binder queryEvents invoke getUserDataAndInitializeIfNeededLocked invoke getUserDataAndInitializeIfNeededLocked invoke UserUsageStatsService constructor UsageStatsDatabase constructor invoked getUserDataAndInitializeIfNeededLocked invoke UserUsageStatsService init in constructor init invoke in UserUsageStatsService init method indexFilesLocked invoked to initialized mSortedStatFiles in init loadActiveStats invoked load all type interval data getLatestUsageStats invoked in loadActiveStats method data returned to initialize mCurrentStats in loadActiveStats method queryEvents finally invoke queryStats queryUsageStats invoked read invoke combine invoke callback to queryEvents method with stats data all wrapped in UsageEvents object data returned with binder UsageStatsManager UsageStatsService UserUsageS UsageStat UsageStatsXmlV1 StatCombiner Get Data series
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值