/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.fuelgauge.batteryusage; import android.app.usage.UsageEvents; import android.content.Context; import android.os.AsyncTask; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.Utils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Manages the async tasks to process battery and app usage data. * * For now, there exist 3 async tasks in this manager: * * * The 3 async tasks will be started at the same time. * */ public class DataProcessManager { private static final String TAG = "DataProcessManager"; private final Handler mHandler; private final DataProcessor.UsageMapAsyncResponse mCallbackFunction; private Context mContext; private List mHourlyBatteryLevelsPerDay; private Map> mBatteryHistoryMap; private boolean mIsCurrentBatteryHistoryLoaded = false; private boolean mIsCurrentAppUsageLoaded = false; private boolean mIsDatabaseAppUsageLoaded = false; // Used to identify whether screen-on time data should be shown in the UI. private boolean mShowScreenOnTime = true; private List mAppUsageEventList = new ArrayList<>(); /** * Constructor when this exists battery level data. */ DataProcessManager( Context context, Handler handler, final DataProcessor.UsageMapAsyncResponse callbackFunction, final List hourlyBatteryLevelsPerDay, final Map> batteryHistoryMap) { mContext = context.getApplicationContext(); mHandler = handler; mCallbackFunction = callbackFunction; mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay; mBatteryHistoryMap = batteryHistoryMap; } /** * Starts the async tasks to load battery history data and app usage data. */ public void start() { // Load the latest battery history data from the service. loadCurrentBatteryHistoryMap(); // Load app usage list from database. loadDatabaseAppUsageList(); // Load the latest app usage list from the service. loadCurrentAppUsageList(); } @VisibleForTesting List getAppUsageEventList() { return mAppUsageEventList; } @VisibleForTesting boolean getIsCurrentAppUsageLoaded() { return mIsCurrentAppUsageLoaded; } @VisibleForTesting boolean getIsDatabaseAppUsageLoaded() { return mIsDatabaseAppUsageLoaded; } @VisibleForTesting boolean getIsCurrentBatteryHistoryLoaded() { return mIsCurrentBatteryHistoryLoaded; } @VisibleForTesting boolean getShowScreenOnTime() { return mShowScreenOnTime; } private void loadCurrentBatteryHistoryMap() { new AsyncTask>() { @Override protected Map doInBackground(Void... voids) { final long startTime = System.currentTimeMillis(); // Loads the current battery usage data from the battery stats service. final Map currentBatteryHistoryMap = DataProcessor.getCurrentBatteryHistoryMapFromStatsService( mContext); Log.d(TAG, String.format("execute loadCurrentBatteryHistoryMap size=%d in %d/ms", currentBatteryHistoryMap.size(), (System.currentTimeMillis() - startTime))); return currentBatteryHistoryMap; } @Override protected void onPostExecute( final Map currentBatteryHistoryMap) { if (mBatteryHistoryMap != null) { // Replaces the placeholder in mBatteryHistoryMap. for (Map.Entry> mapEntry : mBatteryHistoryMap.entrySet()) { if (mapEntry.getValue().containsKey( DataProcessor.CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)) { mapEntry.setValue(currentBatteryHistoryMap); } } } mIsCurrentBatteryHistoryLoaded = true; tryToGenerateFinalDataAndApplyCallback(); } }.execute(); } private void loadCurrentAppUsageList() { new AsyncTask>() { @Override protected List doInBackground(Void... voids) { final long startTime = System.currentTimeMillis(); // Loads the current battery usage data from the battery stats service. final int currentUserId = getCurrentUserId(); final int workProfileUserId = getWorkProfileUserId(); final UsageEvents usageEventsForCurrentUser = DataProcessor.getAppUsageEventsForUser(mContext, currentUserId); // If fail to load usage events for current user, return null directly and screen-on // time will not be shown in the UI. if (usageEventsForCurrentUser == null) { Log.w(TAG, "usageEventsForCurrentUser is null"); return null; } UsageEvents usageEventsForWorkProfile = null; if (workProfileUserId != Integer.MIN_VALUE) { usageEventsForWorkProfile = DataProcessor.getAppUsageEventsForUser( mContext, workProfileUserId); } else { Log.d(TAG, "there is no work profile"); } final Map usageEventsMap = new HashMap<>(); usageEventsMap.put(Long.valueOf(currentUserId), usageEventsForCurrentUser); if (usageEventsForWorkProfile != null) { Log.d(TAG, "usageEventsForWorkProfile is null"); usageEventsMap.put(Long.valueOf(workProfileUserId), usageEventsForWorkProfile); } final List appUsageEventList = DataProcessor.generateAppUsageEventListFromUsageEvents( mContext, usageEventsMap); Log.d(TAG, String.format("execute loadCurrentAppUsageList size=%d in %d/ms", appUsageEventList.size(), (System.currentTimeMillis() - startTime))); return appUsageEventList; } @Override protected void onPostExecute( final List currentAppUsageList) { final int currentUserId = getCurrentUserId(); final UserManager userManager = mContext.getSystemService(UserManager.class); // If current user is locked, don't show screen-on time data in the UI. // Even if we have data in the database, we won't show screen-on time because we // don't have the latest data. if (userManager == null || !userManager.isUserUnlocked(currentUserId)) { Log.d(TAG, "current user is locked"); mShowScreenOnTime = false; } else if (currentAppUsageList == null || currentAppUsageList.isEmpty()) { Log.d(TAG, "usageEventsForWorkProfile is null or empty"); } else { mAppUsageEventList.addAll(currentAppUsageList); } mIsCurrentAppUsageLoaded = true; tryToProcessAppUsageData(); } }.execute(); } private void loadDatabaseAppUsageList() { // TODO: load app usage data from database. mIsDatabaseAppUsageLoaded = true; tryToProcessAppUsageData(); } private void tryToProcessAppUsageData() { // Only when all app usage events has been loaded, start processing app usage data to an // intermediate result for further use. if (!mIsCurrentAppUsageLoaded || !mIsDatabaseAppUsageLoaded) { return; } processAppUsageData(); tryToGenerateFinalDataAndApplyCallback(); } private void processAppUsageData() { // If there is no screen-on time data, no need to process. if (!mShowScreenOnTime) { return; } // TODO: process app usage data to an intermediate result for further use. } private void tryToGenerateFinalDataAndApplyCallback() { // Only when both battery history data and app usage events data has been loaded, start the // final data processing. if (!mIsCurrentBatteryHistoryLoaded || !mIsCurrentAppUsageLoaded || !mIsDatabaseAppUsageLoaded) { return; } generateFinalDataAndApplyCallback(); } private void generateFinalDataAndApplyCallback() { // TODO: generate the final data including battery usage map and device screen-on time and // then apply the callback function. } private int getCurrentUserId() { return mContext.getUserId(); } private int getWorkProfileUserId() { final UserHandle userHandle = Utils.getManagedProfile( mContext.getSystemService(UserManager.class)); return userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE; } }