Merge "Android.mk to Android.bp for frameworks/base/"
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index d401373..aeb6abc 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -306,6 +306,10 @@
return eventId & MASK_TYPE;
}
+ static boolean isReward(int eventId) {
+ return getEventType(eventId) == TYPE_REWARD;
+ }
+
@NonNull
static String eventToString(int eventId) {
switch (eventId & MASK_TYPE) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index 2e2a9b5..3b5e444 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -22,10 +22,14 @@
import static com.android.server.tare.TareUtils.dumpTime;
import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
+import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.IndentingPrintWriter;
import android.util.SparseLongArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
@@ -34,6 +38,21 @@
* Ledger to track the last recorded balance and recent activities of an app.
*/
class Ledger {
+ /** The window size within which rewards will be counted and used towards reward limiting. */
+ private static final long TOTAL_REWARD_WINDOW_MS = 24 * HOUR_IN_MILLIS;
+ /** The number of buckets to split {@link #TOTAL_REWARD_WINDOW_MS} into. */
+ @VisibleForTesting
+ static final int NUM_REWARD_BUCKET_WINDOWS = 4;
+ /**
+ * The duration size of each bucket resulting from splitting {@link #TOTAL_REWARD_WINDOW_MS}
+ * into smaller buckets.
+ */
+ private static final long REWARD_BUCKET_WINDOW_SIZE_MS =
+ TOTAL_REWARD_WINDOW_MS / NUM_REWARD_BUCKET_WINDOWS;
+ /** The maximum number of transactions to retain in memory at any one time. */
+ @VisibleForTesting
+ static final int MAX_TRANSACTION_COUNT = 50;
+
static class Transaction {
public final long startTimeMs;
public final long endTimeMs;
@@ -54,18 +73,47 @@
}
}
+ static class RewardBucket {
+ @CurrentTimeMillisLong
+ public long startTimeMs;
+ public final SparseLongArray cumulativeDelta = new SparseLongArray();
+
+ private void reset() {
+ startTimeMs = 0;
+ cumulativeDelta.clear();
+ }
+ }
+
/** Last saved balance. This doesn't take currently ongoing events into account. */
private long mCurrentBalance = 0;
- private final List<Transaction> mTransactions = new ArrayList<>();
- private final SparseLongArray mCumulativeDeltaPerReason = new SparseLongArray();
- private long mEarliestSumTime;
+ private final Transaction[] mTransactions = new Transaction[MAX_TRANSACTION_COUNT];
+ /** Index within {@link #mTransactions} where the next transaction should be placed. */
+ private int mTransactionIndex = 0;
+ private final RewardBucket[] mRewardBuckets = new RewardBucket[NUM_REWARD_BUCKET_WINDOWS];
+ /** Index within {@link #mRewardBuckets} of the current active bucket. */
+ private int mRewardBucketIndex = 0;
Ledger() {
}
- Ledger(long currentBalance, @NonNull List<Transaction> transactions) {
+ Ledger(long currentBalance, @NonNull List<Transaction> transactions,
+ @NonNull List<RewardBucket> rewardBuckets) {
mCurrentBalance = currentBalance;
- mTransactions.addAll(transactions);
+
+ final int numTxs = transactions.size();
+ for (int i = Math.max(0, numTxs - MAX_TRANSACTION_COUNT); i < numTxs; ++i) {
+ mTransactions[mTransactionIndex++] = transactions.get(i);
+ }
+ mTransactionIndex %= MAX_TRANSACTION_COUNT;
+
+ final int numBuckets = rewardBuckets.size();
+ if (numBuckets > 0) {
+ // Set the index to -1 so that we put the first bucket in index 0.
+ mRewardBucketIndex = -1;
+ for (int i = Math.max(0, numBuckets - NUM_REWARD_BUCKET_WINDOWS); i < numBuckets; ++i) {
+ mRewardBuckets[++mRewardBucketIndex] = rewardBuckets.get(i);
+ }
+ }
}
long getCurrentBalance() {
@@ -74,66 +122,142 @@
@Nullable
Transaction getEarliestTransaction() {
- if (mTransactions.size() > 0) {
- return mTransactions.get(0);
+ for (int t = 0; t < mTransactions.length; ++t) {
+ final Transaction transaction =
+ mTransactions[(mTransactionIndex + t) % mTransactions.length];
+ if (transaction != null) {
+ return transaction;
+ }
}
return null;
}
@NonNull
+ List<RewardBucket> getRewardBuckets() {
+ final long cutoffMs = getCurrentTimeMillis() - TOTAL_REWARD_WINDOW_MS;
+ final List<RewardBucket> list = new ArrayList<>(NUM_REWARD_BUCKET_WINDOWS);
+ for (int i = 1; i <= NUM_REWARD_BUCKET_WINDOWS; ++i) {
+ final int idx = (mRewardBucketIndex + i) % NUM_REWARD_BUCKET_WINDOWS;
+ final RewardBucket rewardBucket = mRewardBuckets[idx];
+ if (rewardBucket != null) {
+ if (cutoffMs <= rewardBucket.startTimeMs) {
+ list.add(rewardBucket);
+ } else {
+ rewardBucket.reset();
+ }
+ }
+ }
+ return list;
+ }
+
+ @NonNull
List<Transaction> getTransactions() {
- return mTransactions;
+ final List<Transaction> list = new ArrayList<>(MAX_TRANSACTION_COUNT);
+ for (int i = 0; i < MAX_TRANSACTION_COUNT; ++i) {
+ final int idx = (mTransactionIndex + i) % MAX_TRANSACTION_COUNT;
+ final Transaction transaction = mTransactions[idx];
+ if (transaction != null) {
+ list.add(transaction);
+ }
+ }
+ return list;
}
void recordTransaction(@NonNull Transaction transaction) {
- mTransactions.add(transaction);
+ mTransactions[mTransactionIndex] = transaction;
mCurrentBalance += transaction.delta;
+ mTransactionIndex = (mTransactionIndex + 1) % MAX_TRANSACTION_COUNT;
- final long sum = mCumulativeDeltaPerReason.get(transaction.eventId);
- mCumulativeDeltaPerReason.put(transaction.eventId, sum + transaction.delta);
- mEarliestSumTime = Math.min(mEarliestSumTime, transaction.startTimeMs);
+ if (EconomicPolicy.isReward(transaction.eventId)) {
+ final RewardBucket bucket = getCurrentRewardBucket();
+ bucket.cumulativeDelta.put(transaction.eventId,
+ bucket.cumulativeDelta.get(transaction.eventId, 0) + transaction.delta);
+ }
+ }
+
+ @NonNull
+ private RewardBucket getCurrentRewardBucket() {
+ RewardBucket bucket = mRewardBuckets[mRewardBucketIndex];
+ final long now = getCurrentTimeMillis();
+ if (bucket == null) {
+ bucket = new RewardBucket();
+ bucket.startTimeMs = now;
+ mRewardBuckets[mRewardBucketIndex] = bucket;
+ return bucket;
+ }
+
+ if (now - bucket.startTimeMs < REWARD_BUCKET_WINDOW_SIZE_MS) {
+ return bucket;
+ }
+
+ mRewardBucketIndex = (mRewardBucketIndex + 1) % NUM_REWARD_BUCKET_WINDOWS;
+ bucket = mRewardBuckets[mRewardBucketIndex];
+ if (bucket == null) {
+ bucket = new RewardBucket();
+ mRewardBuckets[mRewardBucketIndex] = bucket;
+ }
+ bucket.reset();
+ // Using now as the start time means there will be some gaps between sequential buckets,
+ // but makes processing of large gaps between events easier.
+ bucket.startTimeMs = now;
+ return bucket;
}
long get24HourSum(int eventId, final long now) {
final long windowStartTime = now - 24 * HOUR_IN_MILLIS;
- if (mEarliestSumTime < windowStartTime) {
- // Need to redo sums
- mCumulativeDeltaPerReason.clear();
- for (int i = mTransactions.size() - 1; i >= 0; --i) {
- final Transaction transaction = mTransactions.get(i);
- if (transaction.endTimeMs <= windowStartTime) {
- break;
- }
- long sum = mCumulativeDeltaPerReason.get(transaction.eventId);
- if (transaction.startTimeMs >= windowStartTime) {
- sum += transaction.delta;
- } else {
- // Pro-rate durationed deltas. Intentionally floor the result.
- sum += (long) (1.0 * (transaction.endTimeMs - windowStartTime)
- * transaction.delta)
- / (transaction.endTimeMs - transaction.startTimeMs);
- }
- mCumulativeDeltaPerReason.put(transaction.eventId, sum);
+ long sum = 0;
+ for (int i = 0; i < mRewardBuckets.length; ++i) {
+ final RewardBucket bucket = mRewardBuckets[i];
+ if (bucket != null
+ && bucket.startTimeMs >= windowStartTime && bucket.startTimeMs < now) {
+ sum += bucket.cumulativeDelta.get(eventId, 0);
}
- mEarliestSumTime = windowStartTime;
}
- return mCumulativeDeltaPerReason.get(eventId);
+ return sum;
}
- /** Deletes transactions that are older than {@code minAgeMs}. */
- void removeOldTransactions(long minAgeMs) {
+ /**
+ * Deletes transactions that are older than {@code minAgeMs}.
+ * @return The earliest transaction in the ledger, or {@code null} if there are no more
+ * transactions.
+ */
+ @Nullable
+ Transaction removeOldTransactions(long minAgeMs) {
final long cutoff = getCurrentTimeMillis() - minAgeMs;
- while (mTransactions.size() > 0 && mTransactions.get(0).endTimeMs <= cutoff) {
- mTransactions.remove(0);
+ for (int t = 0; t < mTransactions.length; ++t) {
+ final int idx = (mTransactionIndex + t) % mTransactions.length;
+ final Transaction transaction = mTransactions[idx];
+ if (transaction == null) {
+ continue;
+ }
+ if (transaction.endTimeMs <= cutoff) {
+ mTransactions[idx] = null;
+ } else {
+ // Everything we look at after this transaction will also be within the window,
+ // so no need to go further.
+ return transaction;
+ }
}
+ return null;
}
void dump(IndentingPrintWriter pw, int numRecentTransactions) {
pw.print("Current balance", cakeToString(getCurrentBalance())).println();
+ pw.println();
- final int size = mTransactions.size();
- for (int i = Math.max(0, size - numRecentTransactions); i < size; ++i) {
- final Transaction transaction = mTransactions.get(i);
+ boolean printedTransactionTitle = false;
+ for (int t = 0; t < Math.min(MAX_TRANSACTION_COUNT, numRecentTransactions); ++t) {
+ final int idx = (mTransactionIndex - t + MAX_TRANSACTION_COUNT) % MAX_TRANSACTION_COUNT;
+ final Transaction transaction = mTransactions[idx];
+ if (transaction == null) {
+ continue;
+ }
+
+ if (!printedTransactionTitle) {
+ pw.println("Transactions:");
+ pw.increaseIndent();
+ printedTransactionTitle = true;
+ }
dumpTime(pw, transaction.startTimeMs);
pw.print("--");
@@ -151,5 +275,42 @@
pw.print(cakeToString(transaction.ctp));
pw.println(")");
}
+ if (printedTransactionTitle) {
+ pw.decreaseIndent();
+ pw.println();
+ }
+
+ final long now = getCurrentTimeMillis();
+ boolean printedBucketTitle = false;
+ for (int b = 0; b < NUM_REWARD_BUCKET_WINDOWS; ++b) {
+ final int idx = (mRewardBucketIndex - b + NUM_REWARD_BUCKET_WINDOWS)
+ % NUM_REWARD_BUCKET_WINDOWS;
+ final RewardBucket rewardBucket = mRewardBuckets[idx];
+ if (rewardBucket == null) {
+ continue;
+ }
+
+ if (!printedBucketTitle) {
+ pw.println("Reward buckets:");
+ pw.increaseIndent();
+ printedBucketTitle = true;
+ }
+
+ dumpTime(pw, rewardBucket.startTimeMs);
+ pw.print(" (");
+ TimeUtils.formatDuration(now - rewardBucket.startTimeMs, pw);
+ pw.println(" ago):");
+ pw.increaseIndent();
+ for (int r = 0; r < rewardBucket.cumulativeDelta.size(); ++r) {
+ pw.print(EconomicPolicy.eventToString(rewardBucket.cumulativeDelta.keyAt(r)));
+ pw.print(": ");
+ pw.println(cakeToString(rewardBucket.cumulativeDelta.valueAt(r)));
+ }
+ pw.decreaseIndent();
+ }
+ if (printedBucketTitle) {
+ pw.decreaseIndent();
+ pw.println();
+ }
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index 941cc39..8f7657e 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -62,15 +62,14 @@
private static final int MAX_NUM_TRANSACTION_DUMP = 25;
/**
* The maximum amount of time we'll keep a transaction around for.
- * For now, only keep transactions we actually have a use for. We can increase it if we want
- * to use older transactions or provide older transactions to apps.
*/
- private static final long MAX_TRANSACTION_AGE_MS = 24 * HOUR_IN_MILLIS;
+ private static final long MAX_TRANSACTION_AGE_MS = 8 * 24 * HOUR_IN_MILLIS;
private static final String XML_TAG_HIGH_LEVEL_STATE = "irs-state";
private static final String XML_TAG_LEDGER = "ledger";
private static final String XML_TAG_TARE = "tare";
private static final String XML_TAG_TRANSACTION = "transaction";
+ private static final String XML_TAG_REWARD_BUCKET = "rewardBucket";
private static final String XML_TAG_USER = "user";
private static final String XML_TAG_PERIOD_REPORT = "report";
@@ -346,8 +345,8 @@
for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) {
final String pkgName = mLedgers.keyAt(uIdx, pIdx);
final Ledger ledger = mLedgers.get(userId, pkgName);
- ledger.removeOldTransactions(MAX_TRANSACTION_AGE_MS);
- Ledger.Transaction transaction = ledger.getEarliestTransaction();
+ final Ledger.Transaction transaction =
+ ledger.removeOldTransactions(MAX_TRANSACTION_AGE_MS);
if (transaction != null) {
earliestEndTime = Math.min(earliestEndTime, transaction.endTimeMs);
}
@@ -370,6 +369,7 @@
final String pkgName;
final long curBalance;
final List<Ledger.Transaction> transactions = new ArrayList<>();
+ final List<Ledger.RewardBucket> rewardBuckets = new ArrayList<>();
pkgName = parser.getAttributeValue(null, XML_ATTR_PACKAGE_NAME);
curBalance = parser.getAttributeLong(null, XML_ATTR_CURRENT_BALANCE);
@@ -391,8 +391,7 @@
}
continue;
}
- if (eventType != XmlPullParser.START_TAG || !XML_TAG_TRANSACTION.equals(tagName)) {
- // Expecting only "transaction" tags.
+ if (eventType != XmlPullParser.START_TAG || tagName == null) {
Slog.e(TAG, "Unexpected event: (" + eventType + ") " + tagName);
return null;
}
@@ -402,25 +401,37 @@
if (DEBUG) {
Slog.d(TAG, "Starting ledger tag: " + tagName);
}
- final String tag = parser.getAttributeValue(null, XML_ATTR_TAG);
- final long startTime = parser.getAttributeLong(null, XML_ATTR_START_TIME);
- final long endTime = parser.getAttributeLong(null, XML_ATTR_END_TIME);
- final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID);
- final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA);
- final long ctp = parser.getAttributeLong(null, XML_ATTR_CTP);
- if (endTime <= endTimeCutoff) {
- if (DEBUG) {
- Slog.d(TAG, "Skipping event because it's too old.");
- }
- continue;
+ switch (tagName) {
+ case XML_TAG_TRANSACTION:
+ final long endTime = parser.getAttributeLong(null, XML_ATTR_END_TIME);
+ if (endTime <= endTimeCutoff) {
+ if (DEBUG) {
+ Slog.d(TAG, "Skipping event because it's too old.");
+ }
+ continue;
+ }
+ final String tag = parser.getAttributeValue(null, XML_ATTR_TAG);
+ final long startTime = parser.getAttributeLong(null, XML_ATTR_START_TIME);
+ final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID);
+ final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA);
+ final long ctp = parser.getAttributeLong(null, XML_ATTR_CTP);
+ transactions.add(
+ new Ledger.Transaction(startTime, endTime, eventId, tag, delta, ctp));
+ break;
+ case XML_TAG_REWARD_BUCKET:
+ rewardBuckets.add(readRewardBucketFromXml(parser));
+ break;
+ default:
+ // Expecting only "transaction" and "rewardBucket" tags.
+ Slog.e(TAG, "Unexpected event: (" + eventType + ") " + tagName);
+ return null;
}
- transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta, ctp));
}
if (!isInstalled) {
return null;
}
- return Pair.create(pkgName, new Ledger(curBalance, transactions));
+ return Pair.create(pkgName, new Ledger(curBalance, transactions, rewardBuckets));
}
/**
@@ -508,6 +519,44 @@
return report;
}
+ /**
+ * @param parser Xml parser at the beginning of a {@value #XML_TAG_REWARD_BUCKET} tag. The next
+ * "parser.next()" call will take the parser into the body of the tag.
+ * @return Newly instantiated {@link Ledger.RewardBucket} holding all the information we just
+ * read out of the xml tag.
+ */
+ @Nullable
+ private static Ledger.RewardBucket readRewardBucketFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+
+ final Ledger.RewardBucket rewardBucket = new Ledger.RewardBucket();
+
+ rewardBucket.startTimeMs = parser.getAttributeLong(null, XML_ATTR_START_TIME);
+
+ for (int eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT;
+ eventType = parser.next()) {
+ final String tagName = parser.getName();
+ if (eventType == XmlPullParser.END_TAG) {
+ if (XML_TAG_REWARD_BUCKET.equals(tagName)) {
+ // We've reached the end of the rewardBucket tag.
+ break;
+ }
+ continue;
+ }
+ if (eventType != XmlPullParser.START_TAG || !XML_ATTR_DELTA.equals(tagName)) {
+ // Expecting only delta tags.
+ Slog.e(TAG, "Unexpected event: (" + eventType + ") " + tagName);
+ return null;
+ }
+
+ final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID);
+ final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA);
+ rewardBucket.cumulativeDelta.put(eventId, delta);
+ }
+
+ return rewardBucket;
+ }
+
private void scheduleCleanup(long earliestEndTime) {
if (earliestEndTime == Long.MAX_VALUE) {
return;
@@ -595,6 +644,11 @@
}
writeTransaction(out, transaction);
}
+
+ final List<Ledger.RewardBucket> rewardBuckets = ledger.getRewardBuckets();
+ for (int r = 0; r < rewardBuckets.size(); ++r) {
+ writeRewardBucket(out, rewardBuckets.get(r));
+ }
out.endTag(null, XML_TAG_LEDGER);
}
out.endTag(null, XML_TAG_USER);
@@ -616,6 +670,23 @@
out.endTag(null, XML_TAG_TRANSACTION);
}
+ private static void writeRewardBucket(@NonNull TypedXmlSerializer out,
+ @NonNull Ledger.RewardBucket rewardBucket) throws IOException {
+ final int numEvents = rewardBucket.cumulativeDelta.size();
+ if (numEvents == 0) {
+ return;
+ }
+ out.startTag(null, XML_TAG_REWARD_BUCKET);
+ out.attributeLong(null, XML_ATTR_START_TIME, rewardBucket.startTimeMs);
+ for (int i = 0; i < numEvents; ++i) {
+ out.startTag(null, XML_ATTR_DELTA);
+ out.attributeInt(null, XML_ATTR_EVENT_ID, rewardBucket.cumulativeDelta.keyAt(i));
+ out.attributeLong(null, XML_ATTR_DELTA, rewardBucket.cumulativeDelta.valueAt(i));
+ out.endTag(null, XML_ATTR_DELTA);
+ }
+ out.endTag(null, XML_TAG_REWARD_BUCKET);
+ }
+
private static void writeReport(@NonNull TypedXmlSerializer out,
@NonNull Analyst.Report report) throws IOException {
out.startTag(null, XML_TAG_PERIOD_REPORT);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 8db298f..861a850 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1325,8 +1325,11 @@
* flashlight brightness level via
* {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.
* If this value is equal to 1, flashlight brightness control is not supported.
- * The value for this key will be null for devices with no flash unit.
- * This level must be set to a safe value to prevent any burn out issues.</p>
+ * The value for this key will be null for devices with no flash unit.</p>
+ * <p>The maximum value is guaranteed to be safe to use for an indefinite duration in
+ * terms of device flashlight lifespan, but may be too bright for comfort for many
+ * use cases. Use the default torch brightness value to avoid problems with an
+ * over-bright flashlight.</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*/
@PublicKey
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index c14c3c5..5ab21bc 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -237,6 +237,7 @@
private final boolean mUnlockedDeviceRequired;
private final boolean mIsStrongBoxBacked;
private final int mMaxUsageCount;
+ private final boolean mRollbackResistant;
private KeyProtection(
Date keyValidityStart,
@@ -259,7 +260,8 @@
boolean userConfirmationRequired,
boolean unlockedDeviceRequired,
boolean isStrongBoxBacked,
- int maxUsageCount) {
+ int maxUsageCount,
+ boolean rollbackResistant) {
mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
@@ -283,6 +285,7 @@
mUnlockedDeviceRequired = unlockedDeviceRequired;
mIsStrongBoxBacked = isStrongBoxBacked;
mMaxUsageCount = maxUsageCount;
+ mRollbackResistant = rollbackResistant;
}
/**
@@ -563,6 +566,17 @@
}
/**
+ * Returns {@code true} if the key is rollback-resistant, meaning that when deleted it is
+ * guaranteed to be permanently deleted and unusable.
+ *
+ * @see Builder#setRollbackResistant(boolean)
+ * @hide
+ */
+ public boolean isRollbackResistant() {
+ return mRollbackResistant;
+ }
+
+ /**
* Builder of {@link KeyProtection} instances.
*/
public final static class Builder {
@@ -591,6 +605,7 @@
private boolean mIsStrongBoxBacked = false;
private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
private String mAttestKeyAlias = null;
+ private boolean mRollbackResistant = false;
/**
* Creates a new instance of the {@code Builder}.
@@ -1097,6 +1112,21 @@
}
/**
+ * Sets whether the key should be rollback-resistant, meaning that when deleted it is
+ * guaranteed to be permanently deleted and unusable. Not all implementations support
+ * rollback-resistant keys. This method is hidden because implementations only support a
+ * limited number of rollback-resistant keys; currently the available space is reserved for
+ * critical system keys.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setRollbackResistant(boolean rollbackResistant) {
+ mRollbackResistant = rollbackResistant;
+ return this;
+ }
+
+ /**
* Builds an instance of {@link KeyProtection}.
*
* @throws IllegalArgumentException if a required field is missing
@@ -1124,7 +1154,8 @@
mUserConfirmationRequired,
mUnlockedDeviceRequired,
mIsStrongBoxBacked,
- mMaxUsageCount);
+ mMaxUsageCount,
+ mRollbackResistant);
}
}
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 33411e1..dfda356 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -783,6 +783,12 @@
params.getMaxUsageCount()
));
}
+
+ if (params.isRollbackResistant()) {
+ importArgs.add(KeyStore2ParameterUtils.makeBool(
+ KeymasterDefs.KM_TAG_ROLLBACK_RESISTANT
+ ));
+ }
} catch (IllegalArgumentException | IllegalStateException e) {
throw new KeyStoreException(e);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 4332bd2..45c0d78 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -57,6 +57,7 @@
private final Handler mHandler;
private final Handler mMainThreadHandler;
private final Set<Integer> mNetworks = new HashSet<>();
+ private int mPrimaryNetworkId;
// Save the previous HISTORY_SIZE states for logging.
private final String[] mHistory = new String[HISTORY_SIZE];
// Where to copy the next state into.
@@ -106,6 +107,7 @@
if (!mNetworks.contains(network.getNetId())) {
mNetworks.add(network.getNetId());
}
+ mPrimaryNetworkId = network.getNetId();
updateWifiInfo(wifiInfo);
updateStatusLabel();
mMainThreadHandler.post(() -> postResults());
@@ -121,10 +123,13 @@
recordLastWifiNetwork(log);
if (mNetworks.contains(network.getNetId())) {
mNetworks.remove(network.getNetId());
- updateWifiInfo(null);
- updateStatusLabel();
- mMainThreadHandler.post(() -> postResults());
}
+ if (network.getNetId() != mPrimaryNetworkId) {
+ return;
+ }
+ updateWifiInfo(null);
+ updateStatusLabel();
+ mMainThreadHandler.post(() -> postResults());
}
};
private final NetworkCallback mDefaultNetworkCallback =
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
new file mode 100644
index 0000000..dc7e313d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.settingslib.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class WifiStatusTrackerTest {
+ @Mock Context mContext;
+ @Mock WifiManager mWifiManager;
+ @Mock NetworkScoreManager mNetworkScoreManager;
+ @Mock ConnectivityManager mConnectivityManager;
+ @Mock Runnable mCallback;
+
+ private final ArgumentCaptor<ConnectivityManager.NetworkCallback>
+ mNetworkCallbackCaptor =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Verify that we only clear the WifiInfo if the primary network was lost.
+ */
+ @Test
+ public void testWifiInfoClearedOnPrimaryNetworkLost() {
+ WifiStatusTracker wifiStatusTracker = new WifiStatusTracker(mContext, mWifiManager,
+ mNetworkScoreManager, mConnectivityManager, mCallback);
+ wifiStatusTracker.setListening(true);
+
+ verify(mConnectivityManager)
+ .registerNetworkCallback(any(), mNetworkCallbackCaptor.capture(), any());
+
+ // Trigger a validation callback for the primary Wifi network.
+ WifiInfo primaryWifiInfo = Mockito.mock(WifiInfo.class);
+ when(primaryWifiInfo.makeCopy(anyLong())).thenReturn(primaryWifiInfo);
+ when(primaryWifiInfo.isPrimary()).thenReturn(true);
+ int primaryRssi = -55;
+ when(primaryWifiInfo.getRssi()).thenReturn(primaryRssi);
+ NetworkCapabilities primaryCap = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .setTransportInfo(primaryWifiInfo)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build();
+ Network primaryNetwork = Mockito.mock(Network.class);
+ int primaryNetworkId = 1;
+ when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+ mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(primaryNetwork, primaryCap);
+
+ // Verify primary wifi info is the one being used.
+ assertThat(wifiStatusTracker.connected).isTrue();
+ assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+
+ // Trigger a validation callback for the non-primary Wifi network.
+ WifiInfo nonPrimaryWifiInfo = Mockito.mock(WifiInfo.class);
+ when(nonPrimaryWifiInfo.makeCopy(anyLong())).thenReturn(nonPrimaryWifiInfo);
+ when(nonPrimaryWifiInfo.isPrimary()).thenReturn(false);
+ int nonPrimaryRssi = -75;
+ when(primaryWifiInfo.getRssi()).thenReturn(nonPrimaryRssi);
+ NetworkCapabilities nonPrimaryCap = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .setTransportInfo(nonPrimaryWifiInfo)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build();
+ Network nonPrimaryNetwork = Mockito.mock(Network.class);
+ int nonPrimaryNetworkId = 2;
+ when(nonPrimaryNetwork.getNetId()).thenReturn(nonPrimaryNetworkId);
+ mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(nonPrimaryNetwork, nonPrimaryCap);
+
+ // Verify primary wifi info is still the one being used.
+ assertThat(wifiStatusTracker.connected).isTrue();
+ assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+
+ // Lose the non-primary network.
+ mNetworkCallbackCaptor.getValue().onLost(nonPrimaryNetwork);
+
+ // Verify primary wifi info is still the one being used.
+ assertThat(wifiStatusTracker.connected).isTrue();
+ assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+
+ // Lose the primary network.
+ mNetworkCallbackCaptor.getValue().onLost(primaryNetwork);
+
+ // Verify we aren't connected anymore.
+ assertThat(wifiStatusTracker.connected).isFalse();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 2e66648..dfcdaef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -62,7 +62,6 @@
import com.android.internal.util.LatencyTracker;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.users.UserCreatingDialog;
-import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.GuestResetOrExitSessionReceiver;
import com.android.systemui.GuestResumeSessionReceiver;
@@ -167,6 +166,7 @@
private final AtomicBoolean mGuestIsResetting;
private final AtomicBoolean mGuestCreationScheduled;
private FalsingManager mFalsingManager;
+ @Nullable
private View mView;
private String mCreateSupervisedUserPackage;
private GlobalSettings mGlobalSettings;
@@ -572,9 +572,11 @@
protected void switchToUserId(int id) {
try {
- mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
- .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
- .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
+ if (mView != null) {
+ mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
+ .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
+ .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
+ }
mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
pauseRefreshUsers();
mActivityManager.switchUser(id);
@@ -936,15 +938,17 @@
guestCreationProgressDialog.show();
// userManager.createGuest will block the thread so post is needed for the dialog to show
- ThreadUtils.postOnMainThread(() -> {
+ mBgExecutor.execute(() -> {
final int guestId = createGuest();
- guestCreationProgressDialog.dismiss();
- if (guestId == UserHandle.USER_NULL) {
- Toast.makeText(mContext,
- com.android.settingslib.R.string.add_guest_failed,
- Toast.LENGTH_SHORT).show();
- }
- callback.accept(guestId);
+ mUiExecutor.execute(() -> {
+ guestCreationProgressDialog.dismiss();
+ if (guestId == UserHandle.USER_NULL) {
+ Toast.makeText(mContext,
+ com.android.settingslib.R.string.add_guest_failed,
+ Toast.LENGTH_SHORT).show();
+ }
+ callback.accept(guestId);
+ });
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
deleted file mode 100644
index 0686071c..0000000
--- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2020 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.systemui.user;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.UserManager;
-
-import com.android.internal.util.UserIcons;
-import com.android.settingslib.users.UserCreatingDialog;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-/**
- * A class to do the user creation process. It shows a progress dialog, and manages the user
- * creation
- */
-public class UserCreator {
-
- private final Context mContext;
- private final UserManager mUserManager;
-
- @Inject
- public UserCreator(Context context, UserManager userManager) {
- mContext = context;
- mUserManager = userManager;
- }
-
- /**
- * Shows a progress dialog then starts the user creation process on the main thread.
- *
- * @param successCallback is called when the user creation is successful.
- * @param errorCallback is called when userManager.createUser returns null.
- * (Exceptions are not handled by this class)
- */
- public void createUser(String userName, Drawable userIcon, Consumer<UserInfo> successCallback,
- Runnable errorCallback) {
-
- Dialog userCreationProgressDialog = new UserCreatingDialog(mContext);
- userCreationProgressDialog.show();
-
- // userManager.createUser will block the thread so post is needed for the dialog to show
- ThreadUtils.postOnMainThread(() -> {
- UserInfo user =
- mUserManager.createUser(userName, UserManager.USER_TYPE_FULL_SECONDARY, 0);
- if (user == null) {
- // Couldn't create user for some reason
- userCreationProgressDialog.dismiss();
- errorCallback.run();
- return;
- }
-
- Drawable newUserIcon = userIcon;
- Resources res = mContext.getResources();
- if (newUserIcon == null) {
- newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false);
- }
- mUserManager.setUserIcon(
- user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon));
-
- userCreationProgressDialog.dismiss();
- successCallback.accept(user);
- });
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
new file mode 100644
index 0000000..dcbbe74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 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.systemui.user
+
+import android.app.Dialog
+import android.content.Context
+import android.content.pm.UserInfo
+import android.graphics.drawable.Drawable
+import android.os.UserManager
+import com.android.internal.util.UserIcons
+import com.android.settingslib.users.UserCreatingDialog
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/**
+ * A class to do the user creation process. It shows a progress dialog, and manages the user
+ * creation
+ */
+class UserCreator @Inject constructor(
+ private val context: Context,
+ private val userManager: UserManager,
+ @Main private val mainExecutor: Executor,
+ @Background private val bgExecutor: Executor
+) {
+ /**
+ * Shows a progress dialog then starts the user creation process on the main thread.
+ *
+ * @param successCallback is called when the user creation is successful.
+ * @param errorCallback is called when userManager.createUser returns null.
+ * (Exceptions are not handled by this class)
+ */
+ fun createUser(
+ userName: String?,
+ userIcon: Drawable?,
+ successCallback: Consumer<UserInfo?>,
+ errorCallback: Runnable
+ ) {
+ val userCreationProgressDialog: Dialog = UserCreatingDialog(context)
+ userCreationProgressDialog.show()
+
+ // userManager.createUser will block the thread so post is needed for the dialog to show
+ bgExecutor.execute {
+ val user = userManager.createUser(userName, UserManager.USER_TYPE_FULL_SECONDARY, 0)
+ mainExecutor.execute main@{
+ if (user == null) {
+ // Couldn't create user for some reason
+ userCreationProgressDialog.dismiss()
+ errorCallback.run()
+ return@main
+ }
+ bgExecutor.execute {
+ var newUserIcon = userIcon
+ val res = context.resources
+ if (newUserIcon == null) {
+ newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false)
+ }
+ userManager.setUserIcon(
+ user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon))
+ }
+ userCreationProgressDialog.dismiss()
+ successCallback.accept(user)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index 09d7c03..359a780 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -77,6 +77,7 @@
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -269,6 +270,8 @@
`when`(userManager.createGuest(any())).thenReturn(guestInfo)
userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
+ bgExecutor.runAllReady()
+ uiExecutor.runAllReady()
testableLooper.processAllMessages()
verify(interactionJankMonitor).begin(any())
verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
@@ -294,6 +297,8 @@
`when`(userManager.createGuest(any())).thenReturn(guestInfo)
userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
+ bgExecutor.runAllReady()
+ uiExecutor.runAllReady()
testableLooper.processAllMessages()
verify(dialogShower).dismiss()
}
@@ -584,4 +589,24 @@
broadcastReceiverCaptor.value.onReceive(context, intent)
verify(cb).onUserSwitched()
}
+
+ @Test
+ fun onUserItemClicked_guest_runsOnBgThread() {
+ val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java)
+ val guestUserRecord = UserSwitcherController.UserRecord(
+ null,
+ picture,
+ true /* guest */,
+ false /* current */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
+
+ userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower)
+ assertTrue(bgExecutor.numPending() > 0)
+ verify(userManager, never()).createGuest(context)
+ bgExecutor.runAllReady()
+ verify(userManager).createGuest(context)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
new file mode 100644
index 0000000..a85ae7df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
@@ -0,0 +1,73 @@
+package com.android.systemui.user
+
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.os.UserManager
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class UserCreatorTest : SysuiTestCase() {
+ companion object {
+ const val USER_NAME = "abc"
+ }
+
+ @Mock
+ private lateinit var userCreator: UserCreator
+ @Mock
+ private lateinit var userManager: UserManager
+ private lateinit var mainExecutor: FakeExecutor
+ private lateinit var bgExecutor: FakeExecutor
+ private lateinit var user: UserInfo
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mainExecutor = FakeExecutor(FakeSystemClock())
+ bgExecutor = FakeExecutor(FakeSystemClock())
+ userCreator = UserCreator(context, userManager, mainExecutor, bgExecutor)
+ user = Mockito.mock(UserInfo::class.java)
+ Mockito.`when`(userManager.createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0))
+ .thenReturn(user)
+ }
+
+ @Test
+ fun testCreateUser_threadingOrder() {
+ val successCallback = Mockito.mock(Consumer::class.java)
+ val errorCallback = Mockito.mock(Runnable::class.java)
+
+ userCreator.createUser(
+ USER_NAME,
+ null,
+ successCallback as Consumer<UserInfo?>,
+ errorCallback)
+
+ verify(userManager, never()).createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0)
+ bgExecutor.runAllReady()
+ verify(successCallback, never()).accept(user)
+ mainExecutor.runAllReady()
+ verify(userManager, never()).setUserIcon(anyInt(), any(Bitmap::class.java))
+ bgExecutor.runAllReady()
+
+ verify(userManager).createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0)
+ verify(userManager).setUserIcon(anyInt(), any(Bitmap::class.java))
+ verify(successCallback).accept(user)
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index 371ef76..b06af8e 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -17,6 +17,7 @@
package com.android.server.locksettings;
import android.security.AndroidKeyStoreMaintenance;
+import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
@@ -210,10 +211,26 @@
.setBoundToSpecificSecureUserId(sid)
.setUserAuthenticationValidityDurationSeconds(USER_AUTHENTICATION_VALIDITY);
}
+ final KeyProtection protNonRollbackResistant = builder.build();
+ builder.setRollbackResistant(true);
+ final KeyProtection protRollbackResistant = builder.build();
+ final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(keyStoreKey);
+ try {
+ keyStore.setEntry(keyAlias, entry, protRollbackResistant);
+ Slog.i(TAG, "Using rollback-resistant key");
+ } catch (KeyStoreException e) {
+ if (!(e.getCause() instanceof android.security.KeyStoreException)) {
+ throw e;
+ }
+ int errorCode = ((android.security.KeyStoreException) e.getCause()).getErrorCode();
+ if (errorCode != KeymasterDefs.KM_ERROR_ROLLBACK_RESISTANCE_UNAVAILABLE) {
+ throw e;
+ }
+ Slog.w(TAG, "Rollback-resistant keys unavailable. Falling back to "
+ + "non-rollback-resistant key");
+ keyStore.setEntry(keyAlias, entry, protNonRollbackResistant);
+ }
- keyStore.setEntry(keyAlias,
- new KeyStore.SecretKeyEntry(keyStoreKey),
- builder.build());
byte[] intermediate = encrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, data);
return encrypt(keyStoreKey, intermediate);
} catch (CertificateException | IOException | BadPaddingException
diff --git a/services/core/java/com/android/server/pm/ApexPackageInfo.java b/services/core/java/com/android/server/pm/ApexPackageInfo.java
index 1fd9f8e..f959a52 100644
--- a/services/core/java/com/android/server/pm/ApexPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ApexPackageInfo.java
@@ -22,9 +22,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.ApexInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.ArrayMap;
-import android.util.Pair;
import android.util.PrintWriterPrinter;
import com.android.internal.annotations.GuardedBy;
@@ -32,9 +33,8 @@
import com.android.internal.util.Preconditions;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import java.io.File;
@@ -59,7 +59,17 @@
private final Object mLock = new Object();
@GuardedBy("mLock")
- private List<Pair<ApexInfo, AndroidPackage>> mAllPackagesCache;
+ private List<PackageInfo> mAllPackagesCache;
+
+ /**
+ * Whether an APEX package is active or not.
+ *
+ * @param packageInfo the package to check
+ * @return {@code true} if this package is active, {@code false} otherwise.
+ */
+ private static boolean isActive(PackageInfo packageInfo) {
+ return packageInfo.isActiveApex;
+ }
/**
* Called by package manager service to scan apex package files when device boots up.
@@ -95,23 +105,20 @@
* is not found.
*/
@Nullable
- Pair<ApexInfo, AndroidPackage> getPackageInfo(String packageName,
- @ApexManager.PackageInfoFlags int flags) {
+ PackageInfo getPackageInfo(String packageName, @ApexManager.PackageInfoFlags int flags) {
synchronized (mLock) {
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
- final Pair<ApexInfo, AndroidPackage> pair = mAllPackagesCache.get(i);
- var apexInfo = pair.first;
- var pkg = pair.second;
- if (!pkg.getPackageName().equals(packageName)) {
+ final PackageInfo packageInfo = mAllPackagesCache.get(i);
+ if (!packageInfo.packageName.equals(packageName)) {
continue;
}
- if ((matchActive && apexInfo.isActive)
- || (matchFactory && apexInfo.isFactory)) {
- return pair;
+ if ((matchActive && isActive(packageInfo))
+ || (matchFactory && isFactory(packageInfo))) {
+ return packageInfo;
}
}
return null;
@@ -121,18 +128,18 @@
/**
* Retrieves information about all active APEX packages.
*
- * @return list containing information about different active packages.
+ * @return a List of PackageInfo object, each one containing information about a different
+ * active package.
*/
- @NonNull
- List<Pair<ApexInfo, AndroidPackage>> getActivePackages() {
+ List<PackageInfo> getActivePackages() {
synchronized (mLock) {
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
- final List<Pair<ApexInfo, AndroidPackage>> activePackages = new ArrayList<>();
+ final List<PackageInfo> activePackages = new ArrayList<>();
for (int i = 0; i < mAllPackagesCache.size(); i++) {
- final var pair = mAllPackagesCache.get(i);
- if (pair.first.isActive) {
- activePackages.add(pair);
+ final PackageInfo packageInfo = mAllPackagesCache.get(i);
+ if (isActive(packageInfo)) {
+ activePackages.add(packageInfo);
}
}
return activePackages;
@@ -140,20 +147,20 @@
}
/**
- * Retrieves information about all pre-installed APEX packages.
+ * Retrieves information about all active pre-installed APEX packages.
*
- * @return list containing information about different pre-installed packages.
+ * @return a List of PackageInfo object, each one containing information about a different
+ * active pre-installed package.
*/
- @NonNull
- List<Pair<ApexInfo, AndroidPackage>> getFactoryPackages() {
+ List<PackageInfo> getFactoryPackages() {
synchronized (mLock) {
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
- final List<Pair<ApexInfo, AndroidPackage>> factoryPackages = new ArrayList<>();
+ final List<PackageInfo> factoryPackages = new ArrayList<>();
for (int i = 0; i < mAllPackagesCache.size(); i++) {
- final var pair = mAllPackagesCache.get(i);
- if (pair.first.isFactory) {
- factoryPackages.add(pair);
+ final PackageInfo packageInfo = mAllPackagesCache.get(i);
+ if (isFactory(packageInfo)) {
+ factoryPackages.add(packageInfo);
}
}
return factoryPackages;
@@ -163,18 +170,18 @@
/**
* Retrieves information about all inactive APEX packages.
*
- * @return list containing information about different inactive packages.
+ * @return a List of PackageInfo object, each one containing information about a different
+ * inactive package.
*/
- @NonNull
- List<Pair<ApexInfo, AndroidPackage>> getInactivePackages() {
+ List<PackageInfo> getInactivePackages() {
synchronized (mLock) {
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
- final List<Pair<ApexInfo, AndroidPackage>> inactivePackages = new ArrayList<>();
+ final List<PackageInfo> inactivePackages = new ArrayList<>();
for (int i = 0; i < mAllPackagesCache.size(); i++) {
- final var pair = mAllPackagesCache.get(i);
- if (!pair.first.isActive) {
- inactivePackages.add(pair);
+ final PackageInfo packageInfo = mAllPackagesCache.get(i);
+ if (!isActive(packageInfo)) {
+ inactivePackages.add(packageInfo);
}
}
return inactivePackages;
@@ -192,8 +199,8 @@
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
- final var pair = mAllPackagesCache.get(i);
- if (pair.second.getPackageName().equals(packageName)) {
+ final PackageInfo packageInfo = mAllPackagesCache.get(i);
+ if (packageInfo.packageName.equals(packageName)) {
return true;
}
}
@@ -215,18 +222,21 @@
}
void notifyPackageInstalled(ApexInfo apexInfo, AndroidPackage pkg) {
- final String packageName = pkg.getPackageName();
+ final int flags = PackageManager.GET_META_DATA
+ | PackageManager.GET_SIGNING_CERTIFICATES
+ | PackageManager.GET_SIGNATURES;
+ final PackageInfo newApexPkg = PackageInfoWithoutStateUtils.generate(
+ pkg, apexInfo, flags);
+ final String packageName = newApexPkg.packageName;
synchronized (mLock) {
for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
- var pair = mAllPackagesCache.get(i);
- var oldApexInfo = pair.first;
- var oldApexPkg = pair.second;
- if (oldApexInfo.isActive && oldApexPkg.getPackageName().equals(packageName)) {
- if (oldApexInfo.isFactory) {
- oldApexInfo.isActive = false;
- mAllPackagesCache.add(Pair.create(apexInfo, pkg));
+ PackageInfo oldApexPkg = mAllPackagesCache.get(i);
+ if (oldApexPkg.isActiveApex && oldApexPkg.packageName.equals(packageName)) {
+ if (isFactory(oldApexPkg)) {
+ oldApexPkg.isActiveApex = false;
+ mAllPackagesCache.add(newApexPkg);
} else {
- mAllPackagesCache.set(i, Pair.create(apexInfo, pkg));
+ mAllPackagesCache.set(i, newApexPkg);
}
break;
}
@@ -235,6 +245,16 @@
}
/**
+ * Whether the APEX package is pre-installed or not.
+ *
+ * @param packageInfo the package to check
+ * @return {@code true} if this package is pre-installed, {@code false} otherwise.
+ */
+ private static boolean isFactory(@NonNull PackageInfo packageInfo) {
+ return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0;
+ }
+
+ /**
* Dumps various state information to the provided {@link PrintWriter} object.
*
* @param pw the {@link PrintWriter} object to send information to.
@@ -268,25 +288,33 @@
HashSet<String> factoryPackagesSet = new HashSet<>();
for (ApexManager.ScanResult result : scanResults) {
ApexInfo ai = result.apexInfo;
- String packageName = result.pkg.getPackageName();
- if (!packageName.equals(result.packageName)) {
+
+ final PackageInfo packageInfo = PackageInfoWithoutStateUtils.generate(
+ result.pkg, ai, flags);
+ if (packageInfo == null) {
+ throw new IllegalStateException("Unable to generate package info: "
+ + ai.modulePath);
+ }
+ if (!packageInfo.packageName.equals(result.packageName)) {
throw new IllegalStateException("Unmatched package name: "
- + result.packageName + " != " + packageName
+ + result.packageName + " != " + packageInfo.packageName
+ ", path=" + ai.modulePath);
}
- mAllPackagesCache.add(Pair.create(ai, result.pkg));
+ mAllPackagesCache.add(packageInfo);
if (ai.isActive) {
- if (!activePackagesSet.add(packageName)) {
+ if (!activePackagesSet.add(packageInfo.packageName)) {
throw new IllegalStateException(
- "Two active packages have the same name: " + packageName);
+ "Two active packages have the same name: "
+ + packageInfo.packageName);
}
}
if (ai.isFactory) {
// Don't throw when the duplicating APEX is VNDK APEX
- if (!factoryPackagesSet.add(packageName)
+ if (!factoryPackagesSet.add(packageInfo.packageName)
&& !ai.moduleName.startsWith(VNDK_APEX_MODULE_NAME_PREFIX)) {
throw new IllegalStateException(
- "Two factory packages have the same name: " + packageName);
+ "Two factory packages have the same name: "
+ + packageInfo.packageName);
}
}
}
@@ -335,65 +363,30 @@
}
/**
- * @see #dumpPackages(List, String, IndentingPrintWriter)
- */
- static void dumpPackageStates(List<PackageStateInternal> packageStates, boolean isActive,
- @Nullable String packageName, IndentingPrintWriter ipw) {
- ipw.println();
- ipw.increaseIndent();
- for (int i = 0, size = packageStates.size(); i < size; i++) {
- final var packageState = packageStates.get(i);
- var pkg = packageState.getPkg();
- if (packageName != null && !packageName.equals(pkg.getPackageName())) {
- continue;
- }
- ipw.println(pkg.getPackageName());
- ipw.increaseIndent();
- ipw.println("Version: " + pkg.getLongVersionCode());
- ipw.println("Path: " + pkg.getBaseApkPath());
- ipw.println("IsActive: " + isActive);
- ipw.println("IsFactory: " + !packageState.isUpdatedSystemApp());
- ipw.println("ApplicationInfo: ");
- ipw.increaseIndent();
- // TODO: Dump the package manually
- AndroidPackageUtils.generateAppInfoWithoutState(pkg)
- .dump(new PrintWriterPrinter(ipw), "");
- ipw.decreaseIndent();
- ipw.decreaseIndent();
- }
- ipw.decreaseIndent();
- ipw.println();
- }
-
- /**
* Dump information about the packages contained in a particular cache
* @param packagesCache the cache to print information about.
* @param packageName a {@link String} containing a package name, or {@code null}. If set,
* only information about that specific package will be dumped.
* @param ipw the {@link IndentingPrintWriter} object to send information to.
*/
- static void dumpPackages(List<Pair<ApexInfo, AndroidPackage>> packagesCache,
+ static void dumpPackages(List<PackageInfo> packagesCache,
@Nullable String packageName, IndentingPrintWriter ipw) {
ipw.println();
ipw.increaseIndent();
for (int i = 0, size = packagesCache.size(); i < size; i++) {
- final var pair = packagesCache.get(i);
- var apexInfo = pair.first;
- var pkg = pair.second;
- if (packageName != null && !packageName.equals(pkg.getPackageName())) {
+ final PackageInfo pi = packagesCache.get(i);
+ if (packageName != null && !packageName.equals(pi.packageName)) {
continue;
}
- ipw.println(pkg.getPackageName());
+ ipw.println(pi.packageName);
ipw.increaseIndent();
- ipw.println("Version: " + pkg.getLongVersionCode());
- ipw.println("Path: " + pkg.getBaseApkPath());
- ipw.println("IsActive: " + apexInfo.isActive);
- ipw.println("IsFactory: " + apexInfo.isFactory);
+ ipw.println("Version: " + pi.versionCode);
+ ipw.println("Path: " + pi.applicationInfo.sourceDir);
+ ipw.println("IsActive: " + isActive(pi));
+ ipw.println("IsFactory: " + isFactory(pi));
ipw.println("ApplicationInfo: ");
ipw.increaseIndent();
- // TODO: Dump the package manually
- AndroidPackageUtils.generateAppInfoWithoutState(pkg)
- .dump(new PrintWriterPrinter(ipw), "");
+ pi.applicationInfo.dump(new PrintWriterPrinter(ipw), "");
ipw.decreaseIndent();
ipw.decreaseIndent();
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 8ec3d2b..63c25ea 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -65,7 +65,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.apex.ApexInfo;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -1019,12 +1018,11 @@
if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
apexFlags = ApexManager.MATCH_FACTORY_PACKAGE;
}
- final var pair = mApexPackageInfo.getPackageInfo(packageName, apexFlags);
- if (pair == null) {
+ final PackageInfo pi = mApexPackageInfo.getPackageInfo(packageName, apexFlags);
+ if (pi == null) {
return null;
}
- return PackageInfoUtils.generateApplicationInfo(pair.second, flags,
- PackageUserStateInternal.DEFAULT, userId, null);
+ return pi.applicationInfo;
}
}
if ("android".equals(packageName) || "system".equals(packageName)) {
@@ -1720,12 +1718,8 @@
// Instant app filtering for APEX modules is ignored
if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
if (matchApex) {
- final var pair = mApexPackageInfo.getPackageInfo(packageName,
+ return mApexPackageInfo.getPackageInfo(packageName,
ApexManager.MATCH_FACTORY_PACKAGE);
- if (pair == null) {
- return null;
- }
- return PackageInfoUtils.generate(pair.second, pair.first, flags, null, userId);
}
}
final PackageStateInternal ps = mSettings.getDisabledSystemPkg(packageName);
@@ -1781,12 +1775,8 @@
}
if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
if (matchApex) {
- final var pair = mApexPackageInfo.getPackageInfo(packageName,
+ return mApexPackageInfo.getPackageInfo(packageName,
ApexManager.MATCH_ACTIVE_PACKAGE);
- if (pair == null) {
- return null;
- }
- return PackageInfoUtils.generate(pair.second, pair.first, flags, null, userId);
}
}
return null;
@@ -1893,17 +1883,10 @@
}
if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
if (listApex) {
- List<Pair<ApexInfo, AndroidPackage>> pairs;
if (listFactory) {
- pairs = mApexPackageInfo.getFactoryPackages();
+ list.addAll(mApexPackageInfo.getFactoryPackages());
} else {
- pairs = mApexPackageInfo.getActivePackages();
- }
-
- for (int index = 0; index < pairs.size(); index++) {
- var pair = pairs.get(index);
- list.add(PackageInfoUtils.generate(pair.second, pair.first, flags, null,
- userId));
+ list.addAll(mApexPackageInfo.getActivePackages());
}
}
}
@@ -3421,23 +3404,29 @@
} // switch
}
- private void generateApexPackageInfo(@NonNull List<PackageStateInternal> activePackages,
- @NonNull List<PackageStateInternal> inactivePackages,
- @NonNull List<PackageStateInternal> factoryActivePackages,
- @NonNull List<PackageStateInternal> factoryInactivePackages) {
+ private void generateApexPackageInfo(List<PackageInfo> activePackages,
+ List<PackageInfo> inactivePackages, List<PackageInfo> factoryPackages) {
for (AndroidPackage p : mPackages.values()) {
final String packageName = p.getPackageName();
PackageStateInternal ps = mSettings.getPackage(packageName);
if (!p.isApex() || ps == null) {
continue;
}
- activePackages.add(ps);
+ PackageInfo pi = generatePackageInfo(ps, 0, 0);
+ if (pi == null) {
+ continue;
+ }
+ pi.isActiveApex = true;
+ activePackages.add(pi);
if (!ps.isUpdatedSystemApp()) {
- factoryActivePackages.add(ps);
+ factoryPackages.add(pi);
} else {
PackageStateInternal psDisabled = mSettings.getDisabledSystemPkg(packageName);
- factoryInactivePackages.add(psDisabled);
- inactivePackages.add(psDisabled);
+ pi = generatePackageInfo(psDisabled, 0, 0);
+ if (pi != null) {
+ factoryPackages.add(pi);
+ inactivePackages.add(pi);
+ }
}
}
}
@@ -3445,19 +3434,16 @@
private void dumpApex(PrintWriter pw, String packageName) {
if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
- List<PackageStateInternal> activePackages = new ArrayList<>();
- List<PackageStateInternal> inactivePackages = new ArrayList<>();
- List<PackageStateInternal> factoryActivePackages = new ArrayList<>();
- List<PackageStateInternal> factoryInactivePackages = new ArrayList<>();
- generateApexPackageInfo(activePackages, inactivePackages, factoryActivePackages,
- factoryInactivePackages);
+ List<PackageInfo> activePackages = new ArrayList<>();
+ List<PackageInfo> inactivePackages = new ArrayList<>();
+ List<PackageInfo> factoryPackages = new ArrayList<>();
+ generateApexPackageInfo(activePackages, inactivePackages, factoryPackages);
ipw.println("Active APEX packages:");
- ApexPackageInfo.dumpPackageStates(activePackages, true, packageName, ipw);
+ ApexPackageInfo.dumpPackages(activePackages, packageName, ipw);
ipw.println("Inactive APEX packages:");
- ApexPackageInfo.dumpPackageStates(inactivePackages, false, packageName, ipw);
+ ApexPackageInfo.dumpPackages(inactivePackages, packageName, ipw);
ipw.println("Factory APEX packages:");
- ApexPackageInfo.dumpPackageStates(factoryActivePackages, true, packageName, ipw);
- ApexPackageInfo.dumpPackageStates(factoryInactivePackages, false, packageName, ipw);
+ ApexPackageInfo.dumpPackages(factoryPackages, packageName, ipw);
} else {
mApexPackageInfo.dump(pw, packageName);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 32b3e6a..2bdf62bd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1611,7 +1611,7 @@
mSharedLibraries = injector.getSharedLibrariesImpl();
mApexManager = testParams.apexManager;
- mApexPackageInfo = new ApexPackageInfo(this);
+ mApexPackageInfo = new ApexPackageInfo();
mArtManagerService = testParams.artManagerService;
mAvailableFeatures = testParams.availableFeatures;
mBackgroundDexOptService = testParams.backgroundDexOptService;
@@ -1811,7 +1811,7 @@
mProtectedPackages = new ProtectedPackages(mContext);
mApexManager = injector.getApexManager();
- mApexPackageInfo = new ApexPackageInfo(this);
+ mApexPackageInfo = new ApexPackageInfo();
mAppsFilter = mInjector.getAppsFilter();
mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 86affdd..0e57c91 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -823,8 +823,8 @@
* ideally be static, but, it requires locks to read system state.
*/
public static void applyPolicy(ParsedPackage parsedPackage,
- final @PackageManagerService.ScanFlags int scanFlags,
- @Nullable AndroidPackage platformPkg, boolean isUpdatedSystemApp) {
+ final @PackageManagerService.ScanFlags int scanFlags, AndroidPackage platformPkg,
+ boolean isUpdatedSystemApp) {
if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
parsedPackage.setSystem(true);
// TODO(b/135203078): Can this be done in PackageParser? Or just inferred when the flag
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 937b2cf..b57d4d5 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -219,38 +219,21 @@
// Install/uninstall system packages per user.
for (int userId : mUm.getUserIds()) {
- final Set<String> userWhitelist = getInstallablePackagesForUserId(userId);
+ final Set<String> userAllowlist = getInstallablePackagesForUserId(userId);
- // If null, run for all packages
- if (userWhitelist == null) {
- pmInt.forEachPackageState(packageState -> {
- if (packageState.getPkg() == null) {
- return;
- }
- final boolean install = !packageState.getTransientState()
- .isHiddenUntilInstalled();
- if (packageState.getUserStateOrDefault(userId).isInstalled() != install
- && shouldChangeInstallationState(packageState, install, userId,
- isFirstBoot, isConsideredUpgrade, preExistingPackages)) {
- changesToCommit.add(userId, packageState.getPackageName(), install);
- }
- });
- } else {
- for (String packageName : userWhitelist) {
- PackageStateInternal packageState = pmInt.getPackageStateInternal(packageName);
- if (packageState.getPkg() == null) {
- continue;
- }
-
- final boolean install = !packageState.getTransientState()
- .isHiddenUntilInstalled();
- if (packageState.getUserStateOrDefault(userId).isInstalled() != install
- && shouldChangeInstallationState(packageState, install, userId,
- isFirstBoot, isConsideredUpgrade, preExistingPackages)) {
- changesToCommit.add(userId, packageState.getPackageName(), install);
- }
+ pmInt.forEachPackageState(packageState -> {
+ if (packageState.getPkg() == null) {
+ return;
}
- }
+ boolean install = (userAllowlist == null
+ || userAllowlist.contains(packageState.getPackageName()))
+ && !packageState.getTransientState().isHiddenUntilInstalled();
+ if (packageState.getUserStateOrDefault(userId).isInstalled() != install
+ && shouldChangeInstallationState(packageState, install, userId,
+ isFirstBoot, isConsideredUpgrade, preExistingPackages)) {
+ changesToCommit.add(userId, packageState.getPackageName(), install);
+ }
+ });
}
pmInt.commitPackageStateMutation(null, packageStateMutator -> {
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index a44def8..9c620c4 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -93,14 +93,12 @@
/**
* @param pkgSetting See {@link PackageInfoUtils} for description of pkgSetting usage.
- * @deprecated Once ENABLE_FEATURE_SCAN_APEX is removed, this should also be removed.
*/
- @Deprecated
@Nullable
- public static PackageInfo generate(AndroidPackage pkg, ApexInfo apexInfo, long flags,
- @Nullable PackageStateInternal pkgSetting, @UserIdInt int userId) {
+ public static PackageInfo generate(AndroidPackage pkg, ApexInfo apexInfo, int flags,
+ @Nullable PackageStateInternal pkgSetting) {
return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
- PackageUserStateInternal.DEFAULT, userId, apexInfo, pkgSetting);
+ PackageUserStateInternal.DEFAULT, UserHandle.getCallingUserId(), apexInfo, pkgSetting);
}
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index 721777c..13510adb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -30,6 +30,7 @@
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArrayMap;
+import android.util.SparseLongArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -174,10 +175,13 @@
when(mIrs.getConsumptionLimitLocked()).thenReturn(consumptionLimit);
Ledger ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
- ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, 2000, 0));
+ ledger.recordTransaction(
+ new Ledger.Transaction(0, 1000L, EconomicPolicy.TYPE_REWARD | 1, null, 2000, 0));
// Negative ledger balance shouldn't affect the total circulation value.
ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID + 1, TEST_PACKAGE);
- ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, -5000, 3000));
+ ledger.recordTransaction(
+ new Ledger.Transaction(0, 1000L,
+ EconomicPolicy.TYPE_ACTION | 1, null, -5000, 3000));
mScribeUnderTest.setLastReclamationTimeLocked(lastReclamationTime);
mScribeUnderTest.setConsumptionLimitLocked(consumptionLimit);
mScribeUnderTest.adjustRemainingConsumableCakesLocked(
@@ -209,9 +213,13 @@
@Test
public void testWritingPopulatedLedgerToDisk() {
final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
- ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 0));
- ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, -1));
- ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 12));
+ ogLedger.recordTransaction(
+ new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD | 1, null, 51, 0));
+ ogLedger.recordTransaction(
+ new Ledger.Transaction(1500, 2000,
+ EconomicPolicy.TYPE_REWARD | 2, "green", 52, -1));
+ ogLedger.recordTransaction(
+ new Ledger.Transaction(2500, 3000, EconomicPolicy.TYPE_REWARD | 3, "blue", 3, 12));
mScribeUnderTest.writeImmediatelyForTesting();
mScribeUnderTest.loadFromDiskLocked();
@@ -230,11 +238,13 @@
addInstalledPackage(userId, pkgName);
final Ledger ledger = mScribeUnderTest.getLedgerLocked(userId, pkgName);
ledger.recordTransaction(new Ledger.Transaction(
- 0, 1000L * u + l, 1, null, -51L * u + l, 50));
+ 0, 1000L * u + l, EconomicPolicy.TYPE_ACTION | 1, null, -51L * u + l, 50));
ledger.recordTransaction(new Ledger.Transaction(
- 1500L * u + l, 2000L * u + l, 2 * u + l, "green" + u + l, 52L * u + l, 0));
+ 1500L * u + l, 2000L * u + l,
+ EconomicPolicy.TYPE_REWARD | 2 * u + l, "green" + u + l, 52L * u + l, 0));
ledger.recordTransaction(new Ledger.Transaction(
- 2500L * u + l, 3000L * u + l, 3 * u + l, "blue" + u + l, 3L * u + l, 0));
+ 2500L * u + l, 3000L * u + l,
+ EconomicPolicy.TYPE_REWARD | 3 * u + l, "blue" + u + l, 3L * u + l, 0));
ledgers.add(userId, pkgName, ledger);
}
}
@@ -248,9 +258,12 @@
@Test
public void testDiscardLedgerFromDisk() {
final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE);
- ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 1));
- ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 0));
- ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 1));
+ ogLedger.recordTransaction(
+ new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD | 1, null, 51, 1));
+ ogLedger.recordTransaction(
+ new Ledger.Transaction(1500, 2000, EconomicPolicy.TYPE_REWARD | 2, "green", 52, 0));
+ ogLedger.recordTransaction(
+ new Ledger.Transaction(2500, 3000, EconomicPolicy.TYPE_REWARD | 3, "blue", 3, 1));
mScribeUnderTest.writeImmediatelyForTesting();
mScribeUnderTest.loadFromDiskLocked();
@@ -269,9 +282,12 @@
public void testLoadingMissingPackageFromDisk() {
final String pkgName = TEST_PACKAGE + ".uninstalled";
final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, pkgName);
- ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 1));
- ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 2));
- ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 3));
+ ogLedger.recordTransaction(
+ new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REGULATION | 1, null, 51, 1));
+ ogLedger.recordTransaction(
+ new Ledger.Transaction(1500, 2000, EconomicPolicy.TYPE_REWARD | 2, "green", 52, 2));
+ ogLedger.recordTransaction(
+ new Ledger.Transaction(2500, 3000, EconomicPolicy.TYPE_ACTION | 3, "blue", -3, 3));
mScribeUnderTest.writeImmediatelyForTesting();
// Package isn't installed, so make sure it's not saved to memory after loading.
@@ -283,9 +299,13 @@
public void testLoadingMissingUserFromDisk() {
final int userId = TEST_USER_ID + 1;
final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE);
- ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 0));
- ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 1));
- ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 3));
+ ogLedger.recordTransaction(
+ new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD | 1, null, 51, 0));
+ ogLedger.recordTransaction(
+ new Ledger.Transaction(1500, 2000, EconomicPolicy.TYPE_REWARD | 2, "green", 52, 1));
+ ogLedger.recordTransaction(
+ new Ledger.Transaction(2500, 3000,
+ EconomicPolicy.TYPE_REGULATION | 3, "blue", 3, 3));
mScribeUnderTest.writeImmediatelyForTesting();
// User doesn't show up with any packages, so make sure nothing is saved after loading.
@@ -331,12 +351,34 @@
}
assertNotNull(actual);
assertEquals(expected.getCurrentBalance(), actual.getCurrentBalance());
+
List<Ledger.Transaction> expectedTransactions = expected.getTransactions();
List<Ledger.Transaction> actualTransactions = actual.getTransactions();
assertEquals(expectedTransactions.size(), actualTransactions.size());
for (int i = 0; i < expectedTransactions.size(); ++i) {
assertTransactionsEqual(expectedTransactions.get(i), actualTransactions.get(i));
}
+
+ List<Ledger.RewardBucket> expectedRewardBuckets = expected.getRewardBuckets();
+ List<Ledger.RewardBucket> actualRewardBuckets = actual.getRewardBuckets();
+ assertEquals(expectedRewardBuckets.size(), actualRewardBuckets.size());
+ for (int i = 0; i < expectedRewardBuckets.size(); ++i) {
+ assertRewardBucketsEqual(expectedRewardBuckets.get(i), actualRewardBuckets.get(i));
+ }
+ }
+
+ private void assertSparseLongArraysEqual(SparseLongArray expected, SparseLongArray actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ final int size = expected.size();
+ assertEquals(size, actual.size());
+ for (int i = 0; i < size; ++i) {
+ assertEquals(expected.keyAt(i), actual.keyAt(i));
+ assertEquals(expected.valueAt(i), actual.valueAt(i));
+ }
}
private void assertReportListsEqual(List<Analyst.Report> expected,
@@ -382,6 +424,17 @@
}
}
+ private void assertRewardBucketsEqual(Ledger.RewardBucket expected,
+ Ledger.RewardBucket actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.startTimeMs, actual.startTimeMs);
+ assertSparseLongArraysEqual(expected.cumulativeDelta, actual.cumulativeDelta);
+ }
+
private void assertTransactionsEqual(Ledger.Transaction expected, Ledger.Transaction actual) {
if (expected == null) {
assertNull(actual);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index 503ca69..20482af 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -36,6 +36,8 @@
import android.apex.ApexSessionParams;
import android.apex.IApexService;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.os.Environment;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -91,16 +93,16 @@
ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
apexPackageInfo.scanApexPackages(
apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
- final var activePair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
+ final PackageInfo activePkgPi = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
ApexManager.MATCH_ACTIVE_PACKAGE);
- assertThat(activePair).isNotNull();
- assertThat(activePair.second.getPackageName()).contains(TEST_APEX_PKG);
+ assertThat(activePkgPi).isNotNull();
+ assertThat(activePkgPi.packageName).contains(TEST_APEX_PKG);
- final var factoryPair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
+ final PackageInfo factoryPkgPi = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
ApexManager.MATCH_FACTORY_PACKAGE);
- assertThat(factoryPair).isNull();
+ assertThat(factoryPkgPi).isNull();
}
@Test
@@ -109,16 +111,16 @@
ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
apexPackageInfo.scanApexPackages(
apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
- var factoryPair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
+ PackageInfo factoryPkgPi = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
ApexManager.MATCH_FACTORY_PACKAGE);
- assertThat(factoryPair).isNotNull();
- assertThat(factoryPair.second.getPackageName()).contains(TEST_APEX_PKG);
+ assertThat(factoryPkgPi).isNotNull();
+ assertThat(factoryPkgPi.packageName).contains(TEST_APEX_PKG);
- final var activePair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
+ final PackageInfo activePkgPi = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
ApexManager.MATCH_ACTIVE_PACKAGE);
- assertThat(activePair).isNull();
+ assertThat(activePkgPi).isNull();
}
@Test
@@ -386,16 +388,23 @@
newApexInfo = mApexManager.installPackage(installedApex);
apexPackageInfo.notifyPackageInstalled(newApexInfo, mPackageParser2);
- var newInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
+ PackageInfo newInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
ApexManager.MATCH_ACTIVE_PACKAGE);
- assertThat(newInfo.second.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
- assertThat(newInfo.second.getLongVersionCode()).isEqualTo(2);
+ assertThat(newInfo.applicationInfo.sourceDir).isEqualTo(finalApex.getAbsolutePath());
+ assertThat(newInfo.applicationInfo.longVersionCode).isEqualTo(2);
+ assertThat(newInfo.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
+ .isEqualTo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
+ assertThat(newInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
+ .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
- var factoryInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
+ PackageInfo factoryInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
ApexManager.MATCH_FACTORY_PACKAGE);
- assertThat(factoryInfo.second.getBaseApkPath()).isEqualTo(activeApexInfo.modulePath);
- assertThat(factoryInfo.second.getLongVersionCode()).isEqualTo(1);
- assertThat(factoryInfo.second.isSystem()).isTrue();
+ assertThat(factoryInfo.applicationInfo.sourceDir).isEqualTo(activeApexInfo.modulePath);
+ assertThat(factoryInfo.applicationInfo.longVersionCode).isEqualTo(1);
+ assertThat(factoryInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+ .isEqualTo(ApplicationInfo.FLAG_SYSTEM);
+ assertThat(factoryInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
+ .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
}
@Test
@@ -420,16 +429,23 @@
newApexInfo = mApexManager.installPackage(installedApex);
apexPackageInfo.notifyPackageInstalled(newApexInfo, mPackageParser2);
- var newInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
+ PackageInfo newInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
ApexManager.MATCH_ACTIVE_PACKAGE);
- assertThat(newInfo.second.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
- assertThat(newInfo.second.getLongVersionCode()).isEqualTo(2);
+ assertThat(newInfo.applicationInfo.sourceDir).isEqualTo(finalApex.getAbsolutePath());
+ assertThat(newInfo.applicationInfo.longVersionCode).isEqualTo(2);
+ assertThat(newInfo.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
+ .isEqualTo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
+ assertThat(newInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
+ .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
- var factoryInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
+ PackageInfo factoryInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
ApexManager.MATCH_FACTORY_PACKAGE);
- assertThat(factoryInfo.second.getBaseApkPath()).isEqualTo(factoryApexInfo.modulePath);
- assertThat(factoryInfo.second.getLongVersionCode()).isEqualTo(1);
- assertThat(factoryInfo.second.isSystem()).isTrue();
+ assertThat(factoryInfo.applicationInfo.sourceDir).isEqualTo(factoryApexInfo.modulePath);
+ assertThat(factoryInfo.applicationInfo.longVersionCode).isEqualTo(1);
+ assertThat(factoryInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+ .isEqualTo(ApplicationInfo.FLAG_SYSTEM);
+ assertThat(factoryInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
+ .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
index 22dcf84..54566c3 100644
--- a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
+++ b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java
@@ -22,7 +22,12 @@
import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.util.SparseLongArray;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -32,7 +37,10 @@
import org.junit.runner.RunWith;
import java.time.Clock;
+import java.time.Duration;
import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.List;
/** Test that the ledger records transactions correctly. */
@RunWith(AndroidJUnit4.class)
@@ -44,6 +52,11 @@
TareUtils.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
}
+ private void shiftSystemTime(long incrementMs) {
+ TareUtils.sSystemClock =
+ Clock.offset(TareUtils.sSystemClock, Duration.ofMillis(incrementMs));
+ }
+
@Test
public void testInitialState() {
final Ledger ledger = new Ledger();
@@ -52,37 +65,149 @@
}
@Test
+ public void testInitialization_FullLists() {
+ final long balance = 1234567890L;
+ List<Ledger.Transaction> transactions = new ArrayList<>();
+ List<Ledger.RewardBucket> rewardBuckets = new ArrayList<>();
+
+ final long now = getCurrentTimeMillis();
+ Ledger.Transaction secondTxn = null;
+ Ledger.RewardBucket remainingBucket = null;
+ for (int i = 0; i < Ledger.MAX_TRANSACTION_COUNT; ++i) {
+ final long start = now - 10 * HOUR_IN_MILLIS + i * MINUTE_IN_MILLIS;
+ Ledger.Transaction transaction = new Ledger.Transaction(
+ start, start + MINUTE_IN_MILLIS, 1, null, 400, 0);
+ if (i == 1) {
+ secondTxn = transaction;
+ }
+ transactions.add(transaction);
+ }
+ for (int b = 0; b < Ledger.NUM_REWARD_BUCKET_WINDOWS; ++b) {
+ final long start = now - (Ledger.NUM_REWARD_BUCKET_WINDOWS - b) * 24 * HOUR_IN_MILLIS;
+ Ledger.RewardBucket rewardBucket = new Ledger.RewardBucket();
+ rewardBucket.startTimeMs = start;
+ for (int r = 0; r < 5; ++r) {
+ rewardBucket.cumulativeDelta.put(EconomicPolicy.TYPE_REWARD | r, b * start + r);
+ }
+ if (b == Ledger.NUM_REWARD_BUCKET_WINDOWS - 1) {
+ remainingBucket = rewardBucket;
+ }
+ rewardBuckets.add(rewardBucket);
+ }
+ final Ledger ledger = new Ledger(balance, transactions, rewardBuckets);
+ assertEquals(balance, ledger.getCurrentBalance());
+ assertEquals(transactions, ledger.getTransactions());
+ // Everything but the last bucket is old, so the returned list should only contain that
+ // bucket.
+ rewardBuckets.clear();
+ rewardBuckets.add(remainingBucket);
+ assertEquals(rewardBuckets, ledger.getRewardBuckets());
+
+ // Make sure the ledger can properly record new transactions.
+ final long start = now - MINUTE_IN_MILLIS;
+ final long delta = 400;
+ final Ledger.Transaction transaction = new Ledger.Transaction(
+ start, start + MINUTE_IN_MILLIS, EconomicPolicy.TYPE_REWARD | 1, null, delta, 0);
+ ledger.recordTransaction(transaction);
+ assertEquals(balance + delta, ledger.getCurrentBalance());
+ transactions = ledger.getTransactions();
+ assertEquals(secondTxn, transactions.get(0));
+ assertEquals(transaction, transactions.get(Ledger.MAX_TRANSACTION_COUNT - 1));
+ final Ledger.RewardBucket rewardBucket = new Ledger.RewardBucket();
+ rewardBucket.startTimeMs = now;
+ rewardBucket.cumulativeDelta.put(EconomicPolicy.TYPE_REWARD | 1, delta);
+ rewardBuckets = ledger.getRewardBuckets();
+ assertRewardBucketsEqual(remainingBucket, rewardBuckets.get(0));
+ assertRewardBucketsEqual(rewardBucket, rewardBuckets.get(1));
+ }
+
+ @Test
+ public void testInitialization_OverflowingLists() {
+ final long balance = 1234567890L;
+ final List<Ledger.Transaction> transactions = new ArrayList<>();
+ final List<Ledger.RewardBucket> rewardBuckets = new ArrayList<>();
+
+ final long now = getCurrentTimeMillis();
+ for (int i = 0; i < 2 * Ledger.MAX_TRANSACTION_COUNT; ++i) {
+ final long start = now - 20 * HOUR_IN_MILLIS + i * MINUTE_IN_MILLIS;
+ Ledger.Transaction transaction = new Ledger.Transaction(
+ start, start + MINUTE_IN_MILLIS, 1, null, 400, 0);
+ transactions.add(transaction);
+ }
+ for (int b = 0; b < 2 * Ledger.NUM_REWARD_BUCKET_WINDOWS; ++b) {
+ final long start = now
+ - (2 * Ledger.NUM_REWARD_BUCKET_WINDOWS - b) * 6 * HOUR_IN_MILLIS;
+ Ledger.RewardBucket rewardBucket = new Ledger.RewardBucket();
+ rewardBucket.startTimeMs = start;
+ for (int r = 0; r < 5; ++r) {
+ rewardBucket.cumulativeDelta.put(EconomicPolicy.TYPE_REWARD | r, b * start + r);
+ }
+ rewardBuckets.add(rewardBucket);
+ }
+ final Ledger ledger = new Ledger(balance, transactions, rewardBuckets);
+ assertEquals(balance, ledger.getCurrentBalance());
+ assertEquals(transactions.subList(Ledger.MAX_TRANSACTION_COUNT,
+ 2 * Ledger.MAX_TRANSACTION_COUNT),
+ ledger.getTransactions());
+ assertEquals(rewardBuckets.subList(Ledger.NUM_REWARD_BUCKET_WINDOWS,
+ 2 * Ledger.NUM_REWARD_BUCKET_WINDOWS),
+ ledger.getRewardBuckets());
+ }
+
+ @Test
public void testMultipleTransactions() {
final Ledger ledger = new Ledger();
ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 5, 0));
assertEquals(5, ledger.getCurrentBalance());
- assertEquals(5, ledger.get24HourSum(1, 60_000));
ledger.recordTransaction(new Ledger.Transaction(2000, 2000, 1, null, 25, 0));
assertEquals(30, ledger.getCurrentBalance());
- assertEquals(30, ledger.get24HourSum(1, 60_000));
ledger.recordTransaction(new Ledger.Transaction(5000, 5500, 1, null, -10, 5));
assertEquals(20, ledger.getCurrentBalance());
- assertEquals(20, ledger.get24HourSum(1, 60_000));
}
@Test
public void test24HourSum() {
+ final long now = getCurrentTimeMillis();
+ final long end = now + 24 * HOUR_IN_MILLIS;
+ final int reward1 = EconomicPolicy.TYPE_REWARD | 1;
+ final int reward2 = EconomicPolicy.TYPE_REWARD | 2;
final Ledger ledger = new Ledger();
- ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 500, 0));
- assertEquals(500, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
+
+ // First bucket
+ assertEquals(0, ledger.get24HourSum(reward1, end));
+ ledger.recordTransaction(new Ledger.Transaction(now, now + 1000, reward1, null, 500, 0));
+ assertEquals(500, ledger.get24HourSum(reward1, end));
+ assertEquals(0, ledger.get24HourSum(reward2, end));
ledger.recordTransaction(
- new Ledger.Transaction(2 * HOUR_IN_MILLIS, 3 * HOUR_IN_MILLIS, 1, null, 2500, 0));
- assertEquals(3000, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
+ new Ledger.Transaction(now + 2 * HOUR_IN_MILLIS, now + 3 * HOUR_IN_MILLIS,
+ reward1, null, 2500, 0));
+ assertEquals(3000, ledger.get24HourSum(reward1, end));
+ // Second bucket
+ shiftSystemTime(7 * HOUR_IN_MILLIS); // now + 7
ledger.recordTransaction(
- new Ledger.Transaction(4 * HOUR_IN_MILLIS, 4 * HOUR_IN_MILLIS, 1, null, 1, 0));
- assertEquals(3001, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS));
- assertEquals(2501, ledger.get24HourSum(1, 25 * HOUR_IN_MILLIS));
- assertEquals(2501, ledger.get24HourSum(1, 26 * HOUR_IN_MILLIS));
- // Pro-rated as the second transaction phases out
- assertEquals(1251,
- ledger.get24HourSum(1, 26 * HOUR_IN_MILLIS + 30 * MINUTE_IN_MILLIS));
- assertEquals(1, ledger.get24HourSum(1, 27 * HOUR_IN_MILLIS));
- assertEquals(0, ledger.get24HourSum(1, 28 * HOUR_IN_MILLIS));
+ new Ledger.Transaction(now + 7 * HOUR_IN_MILLIS, now + 7 * HOUR_IN_MILLIS,
+ reward1, null, 1, 0));
+ ledger.recordTransaction(
+ new Ledger.Transaction(now + 7 * HOUR_IN_MILLIS, now + 7 * HOUR_IN_MILLIS,
+ reward2, null, 42, 0));
+ assertEquals(3001, ledger.get24HourSum(reward1, end));
+ assertEquals(42, ledger.get24HourSum(reward2, end));
+ // Third bucket
+ shiftSystemTime(12 * HOUR_IN_MILLIS); // now + 19
+ ledger.recordTransaction(
+ new Ledger.Transaction(now + 12 * HOUR_IN_MILLIS, now + 13 * HOUR_IN_MILLIS,
+ reward1, null, 300, 0));
+ assertEquals(3301, ledger.get24HourSum(reward1, end));
+ assertRewardBucketsInOrder(ledger.getRewardBuckets());
+ // Older buckets should be excluded
+ assertEquals(301, ledger.get24HourSum(reward1, end + HOUR_IN_MILLIS));
+ assertEquals(301, ledger.get24HourSum(reward1, end + 2 * HOUR_IN_MILLIS));
+ // 2nd bucket should still be included since it started at the 7 hour mark
+ assertEquals(301, ledger.get24HourSum(reward1, end + 6 * HOUR_IN_MILLIS));
+ assertEquals(42, ledger.get24HourSum(reward2, end + 6 * HOUR_IN_MILLIS));
+ assertEquals(300, ledger.get24HourSum(reward1, end + 7 * HOUR_IN_MILLIS + 1));
+ assertEquals(0, ledger.get24HourSum(reward2, end + 8 * HOUR_IN_MILLIS));
+ assertEquals(0, ledger.get24HourSum(reward1, end + 19 * HOUR_IN_MILLIS + 1));
}
@Test
@@ -125,4 +250,127 @@
ledger.removeOldTransactions(0);
assertNull(ledger.getEarliestTransaction());
}
+
+ @Test
+ public void testTransactionsAlwaysInOrder() {
+ final Ledger ledger = new Ledger();
+ List<Ledger.Transaction> transactions = ledger.getTransactions();
+ assertTrue(transactions.isEmpty());
+
+ final long now = getCurrentTimeMillis();
+ Ledger.Transaction transaction1 = new Ledger.Transaction(
+ now - 48 * HOUR_IN_MILLIS, now - 40 * HOUR_IN_MILLIS, 1, null, 4800, 0);
+ Ledger.Transaction transaction2 = new Ledger.Transaction(
+ now - 24 * HOUR_IN_MILLIS, now - 23 * HOUR_IN_MILLIS, 1, null, 600, 0);
+ Ledger.Transaction transaction3 = new Ledger.Transaction(
+ now - 22 * HOUR_IN_MILLIS, now - 21 * HOUR_IN_MILLIS, 1, null, 600, 0);
+ // Instant event
+ Ledger.Transaction transaction4 = new Ledger.Transaction(
+ now - 20 * HOUR_IN_MILLIS, now - 20 * HOUR_IN_MILLIS, 1, null, 500, 0);
+
+ Ledger.Transaction transaction5 = new Ledger.Transaction(
+ now - 15 * HOUR_IN_MILLIS, now - 15 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS,
+ 1, null, 400, 0);
+ ledger.recordTransaction(transaction1);
+ ledger.recordTransaction(transaction2);
+ ledger.recordTransaction(transaction3);
+ ledger.recordTransaction(transaction4);
+ ledger.recordTransaction(transaction5);
+
+ transactions = ledger.getTransactions();
+ assertEquals(5, transactions.size());
+ assertTransactionsInOrder(transactions);
+
+ for (int i = 0; i < Ledger.MAX_TRANSACTION_COUNT - 5; ++i) {
+ final long start = now - 10 * HOUR_IN_MILLIS + i * MINUTE_IN_MILLIS;
+ Ledger.Transaction transaction = new Ledger.Transaction(
+ start, start + MINUTE_IN_MILLIS, 1, null, 400, 0);
+ ledger.recordTransaction(transaction);
+ }
+ transactions = ledger.getTransactions();
+ assertEquals(Ledger.MAX_TRANSACTION_COUNT, transactions.size());
+ assertTransactionsInOrder(transactions);
+
+ long start = now - 5 * HOUR_IN_MILLIS;
+ Ledger.Transaction transactionLast5 = new Ledger.Transaction(
+ start, start + MINUTE_IN_MILLIS, 1, null, 4800, 0);
+ start = now - 4 * HOUR_IN_MILLIS;
+ Ledger.Transaction transactionLast4 = new Ledger.Transaction(
+ start, start + MINUTE_IN_MILLIS, 1, null, 600, 0);
+ start = now - 3 * HOUR_IN_MILLIS;
+ Ledger.Transaction transactionLast3 = new Ledger.Transaction(
+ start, start + MINUTE_IN_MILLIS, 1, null, 600, 0);
+ // Instant event
+ start = now - 2 * HOUR_IN_MILLIS;
+ Ledger.Transaction transactionLast2 = new Ledger.Transaction(
+ start, start, 1, null, 500, 0);
+ Ledger.Transaction transactionLast1 = new Ledger.Transaction(
+ start, start + MINUTE_IN_MILLIS, 1, null, 400, 0);
+ ledger.recordTransaction(transactionLast5);
+ ledger.recordTransaction(transactionLast4);
+ ledger.recordTransaction(transactionLast3);
+ ledger.recordTransaction(transactionLast2);
+ ledger.recordTransaction(transactionLast1);
+
+ transactions = ledger.getTransactions();
+ assertEquals(Ledger.MAX_TRANSACTION_COUNT, transactions.size());
+ assertTransactionsInOrder(transactions);
+ assertEquals(transactionLast1, transactions.get(Ledger.MAX_TRANSACTION_COUNT - 1));
+ assertEquals(transactionLast2, transactions.get(Ledger.MAX_TRANSACTION_COUNT - 2));
+ assertEquals(transactionLast3, transactions.get(Ledger.MAX_TRANSACTION_COUNT - 3));
+ assertEquals(transactionLast4, transactions.get(Ledger.MAX_TRANSACTION_COUNT - 4));
+ assertEquals(transactionLast5, transactions.get(Ledger.MAX_TRANSACTION_COUNT - 5));
+ assertFalse(transactions.contains(transaction1));
+ assertFalse(transactions.contains(transaction2));
+ assertFalse(transactions.contains(transaction3));
+ assertFalse(transactions.contains(transaction4));
+ assertFalse(transactions.contains(transaction5));
+ }
+
+ private void assertSparseLongArraysEqual(SparseLongArray expected, SparseLongArray actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ final int size = expected.size();
+ assertEquals(size, actual.size());
+ for (int i = 0; i < size; ++i) {
+ assertEquals(expected.keyAt(i), actual.keyAt(i));
+ assertEquals(expected.valueAt(i), actual.valueAt(i));
+ }
+ }
+
+ private void assertRewardBucketsEqual(Ledger.RewardBucket expected,
+ Ledger.RewardBucket actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.startTimeMs, actual.startTimeMs);
+ assertSparseLongArraysEqual(expected.cumulativeDelta, actual.cumulativeDelta);
+ }
+
+ private void assertRewardBucketsInOrder(List<Ledger.RewardBucket> rewardBuckets) {
+ assertNotNull(rewardBuckets);
+ for (int i = 1; i < rewardBuckets.size(); ++i) {
+ final Ledger.RewardBucket prev = rewardBuckets.get(i - 1);
+ final Ledger.RewardBucket cur = rewardBuckets.get(i);
+ assertTrue("Newer bucket stored before older bucket @ index " + i
+ + ": " + prev.startTimeMs + " vs " + cur.startTimeMs,
+ prev.startTimeMs <= cur.startTimeMs);
+ }
+ }
+
+ private void assertTransactionsInOrder(List<Ledger.Transaction> transactions) {
+ assertNotNull(transactions);
+ for (int i = 1; i < transactions.size(); ++i) {
+ final Ledger.Transaction prev = transactions.get(i - 1);
+ final Ledger.Transaction cur = transactions.get(i);
+ assertTrue("Newer transaction stored before older transaction @ index " + i
+ + ": " + prev.endTimeMs + " vs " + cur.endTimeMs,
+ prev.endTimeMs <= cur.endTimeMs);
+ }
+ }
}