Loading config/hiddenapi-light-greylist.txt +0 −6 Original line number Diff line number Diff line Loading @@ -7604,7 +7604,6 @@ Lcom/android/internal/os/BatteryStatsImpl$Uid;->getWakelockStats()Landroid/util/ Lcom/android/internal/os/BatteryStatsImpl$Uid;->getWifiRunningTime(JI)J Lcom/android/internal/os/BatteryStatsImpl$Uid;->getWifiScanTime(JI)J Lcom/android/internal/os/BatteryStatsImpl;-><init>(Landroid/os/Parcel;)V Lcom/android/internal/os/BatteryStatsImpl;->commitPendingDataToDisk()V Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryRealtime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryTimeRemaining(J)J Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryUptime(JI)J Loading Loading @@ -8357,11 +8356,6 @@ Lcom/android/internal/util/AsyncChannel;->sendMessageSynchronously(III)Landroid/ Lcom/android/internal/util/AsyncChannel;->sendMessageSynchronously(Landroid/os/Message;)Landroid/os/Message; Lcom/android/internal/util/AsyncChannel;->STATUS_SUCCESSFUL:I Lcom/android/internal/util/FastPrintWriter;-><init>(Ljava/io/OutputStream;)V Lcom/android/internal/util/JournaledFile;-><init>(Ljava/io/File;Ljava/io/File;)V Lcom/android/internal/util/JournaledFile;->chooseForRead()Ljava/io/File; Lcom/android/internal/util/JournaledFile;->chooseForWrite()Ljava/io/File; Lcom/android/internal/util/JournaledFile;->commit()V Lcom/android/internal/util/JournaledFile;->rollback()V Lcom/android/internal/util/XmlUtils;->convertValueToBoolean(Ljava/lang/CharSequence;Z)Z Lcom/android/internal/util/XmlUtils;->convertValueToInt(Ljava/lang/CharSequence;I)I Lcom/android/internal/util/XmlUtils;->readMapXml(Ljava/io/InputStream;)Ljava/util/HashMap; core/java/android/provider/Settings.java +2 −0 Original line number Diff line number Diff line Loading @@ -10963,6 +10963,8 @@ public final class Settings { * proc_state_cpu_times_read_delay_ms (long) * external_stats_collection_rate_limit_ms (long) * battery_level_collection_delay_ms (long) * max_history_files (int) * max_history_buffer_kb (int) * </pre> * * <p> Loading core/java/com/android/internal/os/BatteryStatsHistory.java 0 → 100644 +453 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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.internal.os; import android.os.BatteryStats; import android.os.Parcel; import android.os.StatFs; import android.os.SystemClock; import android.util.ArraySet; import android.util.Slog; import com.android.internal.util.ParseUtils; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; /** * BatteryStatsHistory encapsulates battery history files. * Battery history record is appended into buffer {@link #mHistoryBuffer} and backed up into * {@link #mActiveFile}. * When {@link #mHistoryBuffer} size reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER}, * current mActiveFile is closed and a new mActiveFile is open. * History files are under directory /data/system/battery-history/. * History files have name battery-history-<num>.bin. The file number <num> starts from zero and * grows sequentially. * The mActiveFile is always the highest numbered history file. * The lowest number file is always the oldest file. * The highest number file is always the newest file. * The file number grows sequentially and we never skip number. * When count of history files exceeds {@link BatteryStatsImpl.Constants#MAX_HISTORY_FILES}, * the lowest numbered file is deleted and a new file is open. * * All interfaces in BatteryStatsHistory should only be called by BatteryStatsImpl and protected by * locks on BatteryStatsImpl object. */ class BatteryStatsHistory { private static final boolean DEBUG = false; private static final String TAG = "BatteryStatsHistory"; public static final String HISTORY_DIR = "battery-history"; public static final String FILE_SUFFIX = ".bin"; private static final int MIN_FREE_SPACE = 100 * 1024 * 1024; private final BatteryStatsImpl mStats; private final Parcel mHistoryBuffer; private final File mHistoryDir; /** * The active history file that the history buffer is backed up into. */ private AtomicFile mActiveFile; /** * A list of history files with incremental indexes. */ private final List<Integer> mFileNumbers = new ArrayList<>(); /** * A list of small history parcels, used when BatteryStatsImpl object is created from * deserialization of a parcel, such as Settings app or checkin file. */ private List<Parcel> mHistoryParcels = null; /** * When iterating history files, the current file index. */ private int mCurrentFileIndex; /** * When iterating history files, the current file parcel. */ private Parcel mCurrentParcel; /** * When iterating history file, the current parcel's Parcel.dataSize(). */ private int mCurrentParcelEnd; /** * When iterating history files, the current record count. */ private int mRecordCount = 0; /** * Used when BatteryStatsImpl object is created from deserialization of a parcel, * such as Settings app or checkin file, to iterate over history parcels. */ private int mParcelIndex = 0; /** * Constructor * @param stats BatteryStatsImpl object. * @param systemDir typically /data/system * @param historyBuffer The in-memory history buffer. */ public BatteryStatsHistory(BatteryStatsImpl stats, File systemDir, Parcel historyBuffer) { mStats = stats; mHistoryBuffer = historyBuffer; mHistoryDir = new File(systemDir, HISTORY_DIR); mHistoryDir.mkdirs(); if (!mHistoryDir.exists()) { Slog.wtf(TAG, "HistoryDir does not exist:" + mHistoryDir.getPath()); } final Set<Integer> dedup = new ArraySet<>(); // scan directory, fill mFileNumbers and mActiveFile. mHistoryDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { final int b = name.lastIndexOf(FILE_SUFFIX); if (b <= 0) { return false; } final Integer c = ParseUtils.parseInt(name.substring(0, b), -1); if (c != -1) { dedup.add(c); return true; } else { return false; } } }); if (!dedup.isEmpty()) { mFileNumbers.addAll(dedup); Collections.sort(mFileNumbers); } else { // No file found, default to have file 0. mFileNumbers.add(0); } createActiveFile(); } /** * Used when BatteryStatsImpl object is created from deserialization of a parcel, * such as Settings app or checkin file. * @param stats BatteryStatsImpl object. * @param historyBuffer the history buffer inside BatteryStatsImpl */ public BatteryStatsHistory(BatteryStatsImpl stats, Parcel historyBuffer) { mStats = stats; mHistoryDir = null; mHistoryBuffer = historyBuffer; } /** * The highest numbered history file is active file that mHistoryBuffer is backed up into. * If file does not exists, truncate() creates a empty file. */ private void createActiveFile() { final AtomicFile file = getFile(mFileNumbers.get(mFileNumbers.size() - 1)); if (DEBUG) { Slog.d(TAG, "activeHistoryFile:" + file.getBaseFile().getPath()); } if (!file.exists()) { try { file.truncate(); } catch (IOException e) { Slog.e(TAG, "Error creating history file "+ file.getBaseFile().getPath(), e); } } mActiveFile = file; } /** * Create history AtomicFile from file number. * @param num file number. * @return AtomicFile object. */ private AtomicFile getFile(int num) { return new AtomicFile( new File(mHistoryDir, num + FILE_SUFFIX)); } /** * When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER}, * create next history file. */ public void createNextFile() { if (mFileNumbers.isEmpty()) { Slog.wtf(TAG, "mFileNumbers should never be empty"); return; } // The last number in mFileNumbers is the highest number. The next file number is highest // number plus one. final int next = mFileNumbers.get(mFileNumbers.size() - 1) + 1; mFileNumbers.add(next); createActiveFile(); // if free disk space is less than 100MB, delete oldest history file. if (!hasFreeDiskSpace()) { int oldest = mFileNumbers.remove(0); getFile(oldest).delete(); } // if there are more history files than allowed, delete oldest history files. // MAX_HISTORY_FILES can be updated by GService config at run time. while (mFileNumbers.size() > mStats.mConstants.MAX_HISTORY_FILES) { int oldest = mFileNumbers.get(0); getFile(oldest).delete(); mFileNumbers.remove(0); } } /** * Delete all existing history files. Active history file start from number 0 again. */ public void resetAllFiles() { for (Integer i : mFileNumbers) { getFile(i).delete(); } mFileNumbers.clear(); mFileNumbers.add(0); createActiveFile(); } /** * Start iterating history files and history buffer. * @return always return true. */ public boolean startIteratingHistory() { mRecordCount = 0; mCurrentFileIndex = 0; mCurrentParcel = null; mCurrentParcelEnd = 0; mParcelIndex = 0; return true; } /** * Finish iterating history files and history buffer. */ public void finishIteratingHistory() { // setDataPosition so mHistoryBuffer Parcel can be written. mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize()); if (DEBUG) { Slog.d(TAG, "Battery history records iterated: " + mRecordCount); } } /** * When iterating history files and history buffer, always start from the lowest numbered * history file, when reached the mActiveFile (highest numbered history file), do not read from * mActiveFile, read from history buffer instead because the buffer has more updated data. * @param out a history item. * @return The parcel that has next record. null if finished all history files and history * buffer */ public Parcel getNextParcel(BatteryStats.HistoryItem out) { if (mRecordCount == 0) { // reset out if it is the first record. out.clear(); } ++mRecordCount; // First iterate through all records in current parcel. if (mCurrentParcel != null) { if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) { // There are more records in current parcel. return mCurrentParcel; } else if (mHistoryBuffer == mCurrentParcel) { // finished iterate through all history files and history buffer. return null; } else if (mHistoryParcels == null || !mHistoryParcels.contains(mCurrentParcel)) { // current parcel is from history file. mCurrentParcel.recycle(); } } // Try next available history file. // skip the last file because its data is in history buffer. while (mCurrentFileIndex < mFileNumbers.size() - 1) { mCurrentParcel = null; mCurrentParcelEnd = 0; final Parcel p = Parcel.obtain(); AtomicFile file = getFile(mFileNumbers.get(mCurrentFileIndex++)); if (readFileToParcel(p, file)) { int bufSize = p.readInt(); int curPos = p.dataPosition(); mCurrentParcelEnd = curPos + bufSize; mCurrentParcel = p; if (curPos < mCurrentParcelEnd) { return mCurrentParcel; } } else { p.recycle(); } } // mHistoryParcels is created when BatteryStatsImpl object is created from deserialization // of a parcel, such as Settings app or checkin file. if (mHistoryParcels != null) { while (mParcelIndex < mHistoryParcels.size()) { final Parcel p = mHistoryParcels.get(mParcelIndex++); if (!skipHead(p)) { continue; } final int bufSize = p.readInt(); final int curPos = p.dataPosition(); mCurrentParcelEnd = curPos + bufSize; mCurrentParcel = p; if (curPos < mCurrentParcelEnd) { return mCurrentParcel; } } } // finished iterator through history files (except the last one), now history buffer. if (mHistoryBuffer.dataSize() <= 0) { // buffer is empty. return null; } mHistoryBuffer.setDataPosition(0); mCurrentParcel = mHistoryBuffer; mCurrentParcelEnd = mCurrentParcel.dataSize(); return mCurrentParcel; } /** * Read history file into a parcel. * @param out the Parcel read into. * @param file the File to read from. * @return true if success, false otherwise. */ public boolean readFileToParcel(Parcel out, AtomicFile file) { byte[] raw = null; try { final long start = SystemClock.uptimeMillis(); raw = file.readFully(); if (DEBUG) { Slog.d(TAG, "readFileToParcel:" + file.getBaseFile().getPath() + " duration ms:" + (SystemClock.uptimeMillis() - start)); } } catch(Exception e) { Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e); return false; } out.unmarshall(raw, 0, raw.length); out.setDataPosition(0); return skipHead(out); } /** * Skip the header part of history parcel. * @param p history parcel to skip head. * @return true if version match, false if not. */ private boolean skipHead(Parcel p) { p.setDataPosition(0); final int version = p.readInt(); if (version != mStats.VERSION) { return false; } // skip historyBaseTime field. p.readLong(); return true; } /** * Read all history files and serialize into a big Parcel. This is to send history files to * Settings app since Settings app can not access /data/system directory. * Checkin file also call this method. * @param out the output parcel */ public void writeToParcel(Parcel out) { final long start = SystemClock.uptimeMillis(); out.writeInt(mFileNumbers.size() - 1); for(int i = 0; i < mFileNumbers.size() - 1; i++) { AtomicFile file = getFile(mFileNumbers.get(i)); byte[] raw = new byte[0]; try { raw = file.readFully(); } catch(Exception e) { Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e); } out.writeByteArray(raw); } if (DEBUG) { Slog.d(TAG, "writeToParcel duration ms:" + (SystemClock.uptimeMillis() - start)); } } /** * This is for Settings app, when Settings app receives big history parcel, it call * this method to parse it into list of parcels. * Checkin file also call this method. * @param in the input parcel. */ public void readFromParcel(Parcel in) { final long start = SystemClock.uptimeMillis(); mHistoryParcels = new ArrayList<>(); final int count = in.readInt(); for(int i = 0; i < count; i++) { byte[] temp = in.createByteArray(); if (temp.length == 0) { continue; } Parcel p = Parcel.obtain(); p.unmarshall(temp, 0, temp.length); p.setDataPosition(0); mHistoryParcels.add(p); } if (DEBUG) { Slog.d(TAG, "readFromParcel duration ms:" + (SystemClock.uptimeMillis() - start)); } } /** * @return true if there is more than 100MB free disk space left. */ private boolean hasFreeDiskSpace() { final StatFs stats = new StatFs(mHistoryDir.getAbsolutePath()); return stats.getAvailableBytes() > MIN_FREE_SPACE; } public List<Integer> getFilesNumbers() { return mFileNumbers; } public AtomicFile getActiveFile() { return mActiveFile; } /** * @return the total size of all history files and history buffer. */ public int getHistoryUsedSize() { int ret = 0; for(int i = 0; i < mFileNumbers.size() - 1; i++) { ret += getFile(mFileNumbers.get(i)).getBaseFile().length(); } ret += mHistoryBuffer.dataSize(); if (mHistoryParcels != null) { for(int i = 0; i < mHistoryParcels.size(); i++) { ret += mHistoryParcels.get(i).dataSize(); } } return ret; } } core/java/com/android/internal/os/BatteryStatsImpl.java +248 −224 File changed.Preview size limit exceeded, changes collapsed. Show changes core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryTest.java 0 → 100644 +145 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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.internal.os; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertEquals; import android.content.Context; import android.os.Parcel; import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; import android.support.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Test BatteryStatsHistory. */ @RunWith(AndroidJUnit4.class) public class BatteryStatsHistoryTest { private static final int MAX_HISTORY_FILES = 32; private final BatteryStatsImpl mBatteryStatsImpl = new MockBatteryStatsImpl(); private final Parcel mHistoryBuffer = Parcel.obtain(); private File mSystemDir; private File mHistoryDir; @Before public void setUp() { MockitoAnnotations.initMocks(this); Context context = InstrumentationRegistry.getContext(); mSystemDir = context.getDataDir(); mHistoryDir = new File(mSystemDir, BatteryStatsHistory.HISTORY_DIR); mHistoryDir.delete(); } @Test public void testConstruct() { BatteryStatsHistory history = new BatteryStatsHistory(mBatteryStatsImpl, mSystemDir, mHistoryBuffer); verifyFileNumbers(history, Arrays.asList(0)); verifyActiveFile(history, "0.bin"); } @Test public void testCreateNextFile() { BatteryStatsHistory history = new BatteryStatsHistory(mBatteryStatsImpl, mSystemDir, mHistoryBuffer); List<Integer> fileList = new ArrayList<>(); fileList.add(0); // create file 1 to 31. for (int i = 1; i < MAX_HISTORY_FILES; i++) { fileList.add(i); history.createNextFile(); verifyFileNumbers(history, fileList); verifyActiveFile(history, i + ".bin"); } // create file 32 history.createNextFile(); fileList.add(32); fileList.remove(0); // verify file 0 is deleted. verifyFileDeleted("0.bin"); verifyFileNumbers(history, fileList); verifyActiveFile(history, "32.bin"); // create file 33 history.createNextFile(); // verify file 1 is deleted fileList.add(33); fileList.remove(0); verifyFileDeleted("1.bin"); verifyFileNumbers(history, fileList); verifyActiveFile(history, "33.bin"); assertEquals(0, history.getHistoryUsedSize()); // create a new BatteryStatsHistory object, it will pick up existing history files. BatteryStatsHistory history2 = new BatteryStatsHistory(mBatteryStatsImpl, mSystemDir, mHistoryBuffer); // verify construct can pick up all files from file system. verifyFileNumbers(history2, fileList); verifyActiveFile(history2, "33.bin"); history2.resetAllFiles(); // verify all existing files are deleted. for (int i = 2; i < 33; ++i) { verifyFileDeleted(i + ".bin"); } // verify file 0 is created verifyFileNumbers(history2, Arrays.asList(0)); verifyActiveFile(history2, "0.bin"); // create file 1. history2.createNextFile(); verifyFileNumbers(history2, Arrays.asList(0, 1)); verifyActiveFile(history2, "1.bin"); } private void verifyActiveFile(BatteryStatsHistory history, String file) { final File expectedFile = new File(mHistoryDir, file); assertEquals(expectedFile.getPath(), history.getActiveFile().getBaseFile().getPath()); assertTrue(expectedFile.exists()); } private void verifyFileNumbers(BatteryStatsHistory history, List<Integer> fileList) { assertEquals(fileList.size(), history.getFilesNumbers().size()); for (int i = 0; i < fileList.size(); i++) { assertEquals(fileList.get(i), history.getFilesNumbers().get(i)); final File expectedFile = new File(mHistoryDir, fileList.get(i) + ".bin"); assertTrue(expectedFile.exists()); } } private void verifyFileDeleted(String file) { assertFalse(new File(mHistoryDir, file).exists()); } } No newline at end of file Loading
config/hiddenapi-light-greylist.txt +0 −6 Original line number Diff line number Diff line Loading @@ -7604,7 +7604,6 @@ Lcom/android/internal/os/BatteryStatsImpl$Uid;->getWakelockStats()Landroid/util/ Lcom/android/internal/os/BatteryStatsImpl$Uid;->getWifiRunningTime(JI)J Lcom/android/internal/os/BatteryStatsImpl$Uid;->getWifiScanTime(JI)J Lcom/android/internal/os/BatteryStatsImpl;-><init>(Landroid/os/Parcel;)V Lcom/android/internal/os/BatteryStatsImpl;->commitPendingDataToDisk()V Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryRealtime(JI)J Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryTimeRemaining(J)J Lcom/android/internal/os/BatteryStatsImpl;->computeBatteryUptime(JI)J Loading Loading @@ -8357,11 +8356,6 @@ Lcom/android/internal/util/AsyncChannel;->sendMessageSynchronously(III)Landroid/ Lcom/android/internal/util/AsyncChannel;->sendMessageSynchronously(Landroid/os/Message;)Landroid/os/Message; Lcom/android/internal/util/AsyncChannel;->STATUS_SUCCESSFUL:I Lcom/android/internal/util/FastPrintWriter;-><init>(Ljava/io/OutputStream;)V Lcom/android/internal/util/JournaledFile;-><init>(Ljava/io/File;Ljava/io/File;)V Lcom/android/internal/util/JournaledFile;->chooseForRead()Ljava/io/File; Lcom/android/internal/util/JournaledFile;->chooseForWrite()Ljava/io/File; Lcom/android/internal/util/JournaledFile;->commit()V Lcom/android/internal/util/JournaledFile;->rollback()V Lcom/android/internal/util/XmlUtils;->convertValueToBoolean(Ljava/lang/CharSequence;Z)Z Lcom/android/internal/util/XmlUtils;->convertValueToInt(Ljava/lang/CharSequence;I)I Lcom/android/internal/util/XmlUtils;->readMapXml(Ljava/io/InputStream;)Ljava/util/HashMap;
core/java/android/provider/Settings.java +2 −0 Original line number Diff line number Diff line Loading @@ -10963,6 +10963,8 @@ public final class Settings { * proc_state_cpu_times_read_delay_ms (long) * external_stats_collection_rate_limit_ms (long) * battery_level_collection_delay_ms (long) * max_history_files (int) * max_history_buffer_kb (int) * </pre> * * <p> Loading
core/java/com/android/internal/os/BatteryStatsHistory.java 0 → 100644 +453 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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.internal.os; import android.os.BatteryStats; import android.os.Parcel; import android.os.StatFs; import android.os.SystemClock; import android.util.ArraySet; import android.util.Slog; import com.android.internal.util.ParseUtils; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; /** * BatteryStatsHistory encapsulates battery history files. * Battery history record is appended into buffer {@link #mHistoryBuffer} and backed up into * {@link #mActiveFile}. * When {@link #mHistoryBuffer} size reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER}, * current mActiveFile is closed and a new mActiveFile is open. * History files are under directory /data/system/battery-history/. * History files have name battery-history-<num>.bin. The file number <num> starts from zero and * grows sequentially. * The mActiveFile is always the highest numbered history file. * The lowest number file is always the oldest file. * The highest number file is always the newest file. * The file number grows sequentially and we never skip number. * When count of history files exceeds {@link BatteryStatsImpl.Constants#MAX_HISTORY_FILES}, * the lowest numbered file is deleted and a new file is open. * * All interfaces in BatteryStatsHistory should only be called by BatteryStatsImpl and protected by * locks on BatteryStatsImpl object. */ class BatteryStatsHistory { private static final boolean DEBUG = false; private static final String TAG = "BatteryStatsHistory"; public static final String HISTORY_DIR = "battery-history"; public static final String FILE_SUFFIX = ".bin"; private static final int MIN_FREE_SPACE = 100 * 1024 * 1024; private final BatteryStatsImpl mStats; private final Parcel mHistoryBuffer; private final File mHistoryDir; /** * The active history file that the history buffer is backed up into. */ private AtomicFile mActiveFile; /** * A list of history files with incremental indexes. */ private final List<Integer> mFileNumbers = new ArrayList<>(); /** * A list of small history parcels, used when BatteryStatsImpl object is created from * deserialization of a parcel, such as Settings app or checkin file. */ private List<Parcel> mHistoryParcels = null; /** * When iterating history files, the current file index. */ private int mCurrentFileIndex; /** * When iterating history files, the current file parcel. */ private Parcel mCurrentParcel; /** * When iterating history file, the current parcel's Parcel.dataSize(). */ private int mCurrentParcelEnd; /** * When iterating history files, the current record count. */ private int mRecordCount = 0; /** * Used when BatteryStatsImpl object is created from deserialization of a parcel, * such as Settings app or checkin file, to iterate over history parcels. */ private int mParcelIndex = 0; /** * Constructor * @param stats BatteryStatsImpl object. * @param systemDir typically /data/system * @param historyBuffer The in-memory history buffer. */ public BatteryStatsHistory(BatteryStatsImpl stats, File systemDir, Parcel historyBuffer) { mStats = stats; mHistoryBuffer = historyBuffer; mHistoryDir = new File(systemDir, HISTORY_DIR); mHistoryDir.mkdirs(); if (!mHistoryDir.exists()) { Slog.wtf(TAG, "HistoryDir does not exist:" + mHistoryDir.getPath()); } final Set<Integer> dedup = new ArraySet<>(); // scan directory, fill mFileNumbers and mActiveFile. mHistoryDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { final int b = name.lastIndexOf(FILE_SUFFIX); if (b <= 0) { return false; } final Integer c = ParseUtils.parseInt(name.substring(0, b), -1); if (c != -1) { dedup.add(c); return true; } else { return false; } } }); if (!dedup.isEmpty()) { mFileNumbers.addAll(dedup); Collections.sort(mFileNumbers); } else { // No file found, default to have file 0. mFileNumbers.add(0); } createActiveFile(); } /** * Used when BatteryStatsImpl object is created from deserialization of a parcel, * such as Settings app or checkin file. * @param stats BatteryStatsImpl object. * @param historyBuffer the history buffer inside BatteryStatsImpl */ public BatteryStatsHistory(BatteryStatsImpl stats, Parcel historyBuffer) { mStats = stats; mHistoryDir = null; mHistoryBuffer = historyBuffer; } /** * The highest numbered history file is active file that mHistoryBuffer is backed up into. * If file does not exists, truncate() creates a empty file. */ private void createActiveFile() { final AtomicFile file = getFile(mFileNumbers.get(mFileNumbers.size() - 1)); if (DEBUG) { Slog.d(TAG, "activeHistoryFile:" + file.getBaseFile().getPath()); } if (!file.exists()) { try { file.truncate(); } catch (IOException e) { Slog.e(TAG, "Error creating history file "+ file.getBaseFile().getPath(), e); } } mActiveFile = file; } /** * Create history AtomicFile from file number. * @param num file number. * @return AtomicFile object. */ private AtomicFile getFile(int num) { return new AtomicFile( new File(mHistoryDir, num + FILE_SUFFIX)); } /** * When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER}, * create next history file. */ public void createNextFile() { if (mFileNumbers.isEmpty()) { Slog.wtf(TAG, "mFileNumbers should never be empty"); return; } // The last number in mFileNumbers is the highest number. The next file number is highest // number plus one. final int next = mFileNumbers.get(mFileNumbers.size() - 1) + 1; mFileNumbers.add(next); createActiveFile(); // if free disk space is less than 100MB, delete oldest history file. if (!hasFreeDiskSpace()) { int oldest = mFileNumbers.remove(0); getFile(oldest).delete(); } // if there are more history files than allowed, delete oldest history files. // MAX_HISTORY_FILES can be updated by GService config at run time. while (mFileNumbers.size() > mStats.mConstants.MAX_HISTORY_FILES) { int oldest = mFileNumbers.get(0); getFile(oldest).delete(); mFileNumbers.remove(0); } } /** * Delete all existing history files. Active history file start from number 0 again. */ public void resetAllFiles() { for (Integer i : mFileNumbers) { getFile(i).delete(); } mFileNumbers.clear(); mFileNumbers.add(0); createActiveFile(); } /** * Start iterating history files and history buffer. * @return always return true. */ public boolean startIteratingHistory() { mRecordCount = 0; mCurrentFileIndex = 0; mCurrentParcel = null; mCurrentParcelEnd = 0; mParcelIndex = 0; return true; } /** * Finish iterating history files and history buffer. */ public void finishIteratingHistory() { // setDataPosition so mHistoryBuffer Parcel can be written. mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize()); if (DEBUG) { Slog.d(TAG, "Battery history records iterated: " + mRecordCount); } } /** * When iterating history files and history buffer, always start from the lowest numbered * history file, when reached the mActiveFile (highest numbered history file), do not read from * mActiveFile, read from history buffer instead because the buffer has more updated data. * @param out a history item. * @return The parcel that has next record. null if finished all history files and history * buffer */ public Parcel getNextParcel(BatteryStats.HistoryItem out) { if (mRecordCount == 0) { // reset out if it is the first record. out.clear(); } ++mRecordCount; // First iterate through all records in current parcel. if (mCurrentParcel != null) { if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) { // There are more records in current parcel. return mCurrentParcel; } else if (mHistoryBuffer == mCurrentParcel) { // finished iterate through all history files and history buffer. return null; } else if (mHistoryParcels == null || !mHistoryParcels.contains(mCurrentParcel)) { // current parcel is from history file. mCurrentParcel.recycle(); } } // Try next available history file. // skip the last file because its data is in history buffer. while (mCurrentFileIndex < mFileNumbers.size() - 1) { mCurrentParcel = null; mCurrentParcelEnd = 0; final Parcel p = Parcel.obtain(); AtomicFile file = getFile(mFileNumbers.get(mCurrentFileIndex++)); if (readFileToParcel(p, file)) { int bufSize = p.readInt(); int curPos = p.dataPosition(); mCurrentParcelEnd = curPos + bufSize; mCurrentParcel = p; if (curPos < mCurrentParcelEnd) { return mCurrentParcel; } } else { p.recycle(); } } // mHistoryParcels is created when BatteryStatsImpl object is created from deserialization // of a parcel, such as Settings app or checkin file. if (mHistoryParcels != null) { while (mParcelIndex < mHistoryParcels.size()) { final Parcel p = mHistoryParcels.get(mParcelIndex++); if (!skipHead(p)) { continue; } final int bufSize = p.readInt(); final int curPos = p.dataPosition(); mCurrentParcelEnd = curPos + bufSize; mCurrentParcel = p; if (curPos < mCurrentParcelEnd) { return mCurrentParcel; } } } // finished iterator through history files (except the last one), now history buffer. if (mHistoryBuffer.dataSize() <= 0) { // buffer is empty. return null; } mHistoryBuffer.setDataPosition(0); mCurrentParcel = mHistoryBuffer; mCurrentParcelEnd = mCurrentParcel.dataSize(); return mCurrentParcel; } /** * Read history file into a parcel. * @param out the Parcel read into. * @param file the File to read from. * @return true if success, false otherwise. */ public boolean readFileToParcel(Parcel out, AtomicFile file) { byte[] raw = null; try { final long start = SystemClock.uptimeMillis(); raw = file.readFully(); if (DEBUG) { Slog.d(TAG, "readFileToParcel:" + file.getBaseFile().getPath() + " duration ms:" + (SystemClock.uptimeMillis() - start)); } } catch(Exception e) { Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e); return false; } out.unmarshall(raw, 0, raw.length); out.setDataPosition(0); return skipHead(out); } /** * Skip the header part of history parcel. * @param p history parcel to skip head. * @return true if version match, false if not. */ private boolean skipHead(Parcel p) { p.setDataPosition(0); final int version = p.readInt(); if (version != mStats.VERSION) { return false; } // skip historyBaseTime field. p.readLong(); return true; } /** * Read all history files and serialize into a big Parcel. This is to send history files to * Settings app since Settings app can not access /data/system directory. * Checkin file also call this method. * @param out the output parcel */ public void writeToParcel(Parcel out) { final long start = SystemClock.uptimeMillis(); out.writeInt(mFileNumbers.size() - 1); for(int i = 0; i < mFileNumbers.size() - 1; i++) { AtomicFile file = getFile(mFileNumbers.get(i)); byte[] raw = new byte[0]; try { raw = file.readFully(); } catch(Exception e) { Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e); } out.writeByteArray(raw); } if (DEBUG) { Slog.d(TAG, "writeToParcel duration ms:" + (SystemClock.uptimeMillis() - start)); } } /** * This is for Settings app, when Settings app receives big history parcel, it call * this method to parse it into list of parcels. * Checkin file also call this method. * @param in the input parcel. */ public void readFromParcel(Parcel in) { final long start = SystemClock.uptimeMillis(); mHistoryParcels = new ArrayList<>(); final int count = in.readInt(); for(int i = 0; i < count; i++) { byte[] temp = in.createByteArray(); if (temp.length == 0) { continue; } Parcel p = Parcel.obtain(); p.unmarshall(temp, 0, temp.length); p.setDataPosition(0); mHistoryParcels.add(p); } if (DEBUG) { Slog.d(TAG, "readFromParcel duration ms:" + (SystemClock.uptimeMillis() - start)); } } /** * @return true if there is more than 100MB free disk space left. */ private boolean hasFreeDiskSpace() { final StatFs stats = new StatFs(mHistoryDir.getAbsolutePath()); return stats.getAvailableBytes() > MIN_FREE_SPACE; } public List<Integer> getFilesNumbers() { return mFileNumbers; } public AtomicFile getActiveFile() { return mActiveFile; } /** * @return the total size of all history files and history buffer. */ public int getHistoryUsedSize() { int ret = 0; for(int i = 0; i < mFileNumbers.size() - 1; i++) { ret += getFile(mFileNumbers.get(i)).getBaseFile().length(); } ret += mHistoryBuffer.dataSize(); if (mHistoryParcels != null) { for(int i = 0; i < mHistoryParcels.size(); i++) { ret += mHistoryParcels.get(i).dataSize(); } } return ret; } }
core/java/com/android/internal/os/BatteryStatsImpl.java +248 −224 File changed.Preview size limit exceeded, changes collapsed. Show changes
core/tests/coretests/src/com/android/internal/os/BatteryStatsHistoryTest.java 0 → 100644 +145 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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.internal.os; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertEquals; import android.content.Context; import android.os.Parcel; import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; import android.support.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Test BatteryStatsHistory. */ @RunWith(AndroidJUnit4.class) public class BatteryStatsHistoryTest { private static final int MAX_HISTORY_FILES = 32; private final BatteryStatsImpl mBatteryStatsImpl = new MockBatteryStatsImpl(); private final Parcel mHistoryBuffer = Parcel.obtain(); private File mSystemDir; private File mHistoryDir; @Before public void setUp() { MockitoAnnotations.initMocks(this); Context context = InstrumentationRegistry.getContext(); mSystemDir = context.getDataDir(); mHistoryDir = new File(mSystemDir, BatteryStatsHistory.HISTORY_DIR); mHistoryDir.delete(); } @Test public void testConstruct() { BatteryStatsHistory history = new BatteryStatsHistory(mBatteryStatsImpl, mSystemDir, mHistoryBuffer); verifyFileNumbers(history, Arrays.asList(0)); verifyActiveFile(history, "0.bin"); } @Test public void testCreateNextFile() { BatteryStatsHistory history = new BatteryStatsHistory(mBatteryStatsImpl, mSystemDir, mHistoryBuffer); List<Integer> fileList = new ArrayList<>(); fileList.add(0); // create file 1 to 31. for (int i = 1; i < MAX_HISTORY_FILES; i++) { fileList.add(i); history.createNextFile(); verifyFileNumbers(history, fileList); verifyActiveFile(history, i + ".bin"); } // create file 32 history.createNextFile(); fileList.add(32); fileList.remove(0); // verify file 0 is deleted. verifyFileDeleted("0.bin"); verifyFileNumbers(history, fileList); verifyActiveFile(history, "32.bin"); // create file 33 history.createNextFile(); // verify file 1 is deleted fileList.add(33); fileList.remove(0); verifyFileDeleted("1.bin"); verifyFileNumbers(history, fileList); verifyActiveFile(history, "33.bin"); assertEquals(0, history.getHistoryUsedSize()); // create a new BatteryStatsHistory object, it will pick up existing history files. BatteryStatsHistory history2 = new BatteryStatsHistory(mBatteryStatsImpl, mSystemDir, mHistoryBuffer); // verify construct can pick up all files from file system. verifyFileNumbers(history2, fileList); verifyActiveFile(history2, "33.bin"); history2.resetAllFiles(); // verify all existing files are deleted. for (int i = 2; i < 33; ++i) { verifyFileDeleted(i + ".bin"); } // verify file 0 is created verifyFileNumbers(history2, Arrays.asList(0)); verifyActiveFile(history2, "0.bin"); // create file 1. history2.createNextFile(); verifyFileNumbers(history2, Arrays.asList(0, 1)); verifyActiveFile(history2, "1.bin"); } private void verifyActiveFile(BatteryStatsHistory history, String file) { final File expectedFile = new File(mHistoryDir, file); assertEquals(expectedFile.getPath(), history.getActiveFile().getBaseFile().getPath()); assertTrue(expectedFile.exists()); } private void verifyFileNumbers(BatteryStatsHistory history, List<Integer> fileList) { assertEquals(fileList.size(), history.getFilesNumbers().size()); for (int i = 0; i < fileList.size(); i++) { assertEquals(fileList.get(i), history.getFilesNumbers().get(i)); final File expectedFile = new File(mHistoryDir, fileList.get(i) + ".bin"); assertTrue(expectedFile.exists()); } } private void verifyFileDeleted(String file) { assertFalse(new File(mHistoryDir, file).exists()); } } No newline at end of file