Merge "SpatializerHelper: report head tracking disabled" into tm-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index f4faec8..8b8a57d 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -73,6 +73,7 @@
private final Object mLock;
private final Handler mHandler;
+ private final Analyst mAnalyst;
private final InternalResourceService mIrs;
private final Scribe mScribe;
@@ -110,10 +111,11 @@
*/
private static final int MSG_CHECK_INDIVIDUAL_AFFORDABILITY = 1;
- Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe) {
+ Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe, @NonNull Analyst analyst) {
mLock = irs.getLock();
mIrs = irs;
mScribe = scribe;
+ mAnalyst = analyst;
mHandler = new AgentHandler(TareHandlerThread.get().getLooper());
mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
mBalanceThresholdAlarmQueue = new BalanceThresholdAlarmQueue(
@@ -443,7 +445,7 @@
void recordTransactionLocked(final int userId, @NonNull final String pkgName,
@NonNull Ledger ledger, @NonNull Ledger.Transaction transaction,
final boolean notifyOnAffordabilityChange) {
- if (transaction.delta == 0) {
+ if (!DEBUG && transaction.delta == 0) {
// Skip recording transactions with a delta of 0 to save on space.
return;
}
@@ -471,6 +473,7 @@
}
ledger.recordTransaction(transaction);
mScribe.adjustRemainingConsumableCakesLocked(-transaction.ctp);
+ mAnalyst.noteTransaction(transaction);
if (transaction.delta != 0 && notifyOnAffordabilityChange) {
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
mActionAffordabilityNotes.get(userId, pkgName);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java b/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
new file mode 100644
index 0000000..bc6fe7e5
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
@@ -0,0 +1,290 @@
+/*
+ * 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.server.tare;
+
+import static com.android.server.tare.EconomicPolicy.TYPE_ACTION;
+import static com.android.server.tare.EconomicPolicy.TYPE_REGULATION;
+import static com.android.server.tare.EconomicPolicy.TYPE_REWARD;
+import static com.android.server.tare.EconomicPolicy.getEventType;
+import static com.android.server.tare.TareUtils.cakeToString;
+
+import android.annotation.NonNull;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Responsible for maintaining statistics and analysis of TARE's performance.
+ */
+public class Analyst {
+ private static final String TAG = "TARE-" + Analyst.class.getSimpleName();
+ private static final boolean DEBUG = InternalResourceService.DEBUG
+ || Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final int NUM_PERIODS_TO_RETAIN = 8;
+
+ static final class Report {
+ /** How much the battery was discharged over the tracked period. */
+ public int cumulativeBatteryDischarge = 0;
+ public int currentBatteryLevel = 0;
+ /**
+ * Profit from performing actions. This excludes special circumstances where we charge the
+ * app
+ * less than the action's CTP.
+ */
+ public long cumulativeProfit = 0;
+ public int numProfitableActions = 0;
+ /**
+ * Losses from performing actions for special circumstances (eg. for a TOP app) where we
+ * charge
+ * the app less than the action's CTP.
+ */
+ public long cumulativeLoss = 0;
+ public int numUnprofitableActions = 0;
+ /**
+ * The total number of rewards given to apps over this period.
+ */
+ public long cumulativeRewards = 0;
+ public int numRewards = 0;
+ /**
+ * Regulations that increased an app's balance.
+ */
+ public long cumulativePositiveRegulations = 0;
+ public int numPositiveRegulations = 0;
+ /**
+ * Regulations that decreased an app's balance.
+ */
+ public long cumulativeNegativeRegulations = 0;
+ public int numNegativeRegulations = 0;
+
+ private void clear() {
+ cumulativeBatteryDischarge = 0;
+ currentBatteryLevel = 0;
+ cumulativeProfit = 0;
+ numProfitableActions = 0;
+ cumulativeLoss = 0;
+ numUnprofitableActions = 0;
+ cumulativeRewards = 0;
+ numRewards = 0;
+ cumulativePositiveRegulations = 0;
+ numPositiveRegulations = 0;
+ cumulativeNegativeRegulations = 0;
+ numNegativeRegulations = 0;
+ }
+ }
+
+ private int mPeriodIndex = 0;
+ /** How much the battery was discharged over the tracked period. */
+ private final Report[] mReports = new Report[NUM_PERIODS_TO_RETAIN];
+
+ /** Returns the list of most recent reports, with the oldest report first. */
+ @NonNull
+ List<Report> getReports() {
+ final List<Report> list = new ArrayList<>(NUM_PERIODS_TO_RETAIN);
+ for (int i = 1; i <= NUM_PERIODS_TO_RETAIN; ++i) {
+ final int idx = (mPeriodIndex + i) % NUM_PERIODS_TO_RETAIN;
+ final Report report = mReports[idx];
+ if (report != null) {
+ list.add(report);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Tracks the given reports instead of whatever is currently saved. Reports should be ordered
+ * oldest to most recent.
+ */
+ void loadReports(@NonNull List<Report> reports) {
+ final int numReports = reports.size();
+ mPeriodIndex = Math.max(0, numReports - 1);
+ for (int i = 0; i < NUM_PERIODS_TO_RETAIN; ++i) {
+ if (i < numReports) {
+ mReports[i] = reports.get(i);
+ } else {
+ mReports[i] = null;
+ }
+ }
+ }
+
+ void noteBatteryLevelChange(int newBatteryLevel) {
+ if (newBatteryLevel == 100 && mReports[mPeriodIndex] != null
+ && mReports[mPeriodIndex].currentBatteryLevel < newBatteryLevel) {
+ mPeriodIndex = (mPeriodIndex + 1) % NUM_PERIODS_TO_RETAIN;
+ if (mReports[mPeriodIndex] != null) {
+ final Report report = mReports[mPeriodIndex];
+ report.clear();
+ report.currentBatteryLevel = newBatteryLevel;
+ return;
+ }
+ }
+
+ if (mReports[mPeriodIndex] == null) {
+ Report report = new Report();
+ mReports[mPeriodIndex] = report;
+ report.currentBatteryLevel = newBatteryLevel;
+ return;
+ }
+
+ final Report report = mReports[mPeriodIndex];
+ if (newBatteryLevel < report.currentBatteryLevel) {
+ report.cumulativeBatteryDischarge += (report.currentBatteryLevel - newBatteryLevel);
+ }
+ report.currentBatteryLevel = newBatteryLevel;
+ }
+
+ void noteTransaction(@NonNull Ledger.Transaction transaction) {
+ if (mReports[mPeriodIndex] == null) {
+ mReports[mPeriodIndex] = new Report();
+ }
+ final Report report = mReports[mPeriodIndex];
+ switch (getEventType(transaction.eventId)) {
+ case TYPE_ACTION:
+ // For now, assume all instances where price < CTP is a special instance.
+ // TODO: add an explicit signal for special circumstances
+ if (-transaction.delta > transaction.ctp) {
+ report.cumulativeProfit += (-transaction.delta - transaction.ctp);
+ report.numProfitableActions++;
+ } else if (-transaction.delta < transaction.ctp) {
+ report.cumulativeLoss += (transaction.ctp + transaction.delta);
+ report.numUnprofitableActions++;
+ }
+ break;
+ case TYPE_REGULATION:
+ if (transaction.delta > 0) {
+ report.cumulativePositiveRegulations += transaction.delta;
+ report.numPositiveRegulations++;
+ } else if (transaction.delta < 0) {
+ report.cumulativeNegativeRegulations -= transaction.delta;
+ report.numNegativeRegulations++;
+ }
+ break;
+ case TYPE_REWARD:
+ if (transaction.delta != 0) {
+ report.cumulativeRewards += transaction.delta;
+ report.numRewards++;
+ }
+ break;
+ }
+ }
+
+ void tearDown() {
+ for (int i = 0; i < mReports.length; ++i) {
+ mReports[i] = null;
+ }
+ mPeriodIndex = 0;
+ }
+
+ @NonNull
+ private String padStringWithSpaces(@NonNull String text, int targetLength) {
+ // Make sure to have at least one space on either side.
+ final int padding = Math.max(2, targetLength - text.length()) >>> 1;
+ return " ".repeat(padding) + text + " ".repeat(padding);
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Reports:");
+ pw.increaseIndent();
+ pw.print(" Total Discharge");
+ final int statColsLength = 47;
+ pw.print(padStringWithSpaces("Profit (avg/action : avg/discharge)", statColsLength));
+ pw.print(padStringWithSpaces("Loss (avg/action : avg/discharge)", statColsLength));
+ pw.print(padStringWithSpaces("Rewards (avg/reward : avg/discharge)", statColsLength));
+ pw.print(padStringWithSpaces("+Regs (avg/reg : avg/discharge)", statColsLength));
+ pw.print(padStringWithSpaces("-Regs (avg/reg : avg/discharge)", statColsLength));
+ pw.println();
+ for (int r = 0; r < NUM_PERIODS_TO_RETAIN; ++r) {
+ final int idx = (mPeriodIndex - r + NUM_PERIODS_TO_RETAIN) % NUM_PERIODS_TO_RETAIN;
+ final Report report = mReports[idx];
+ if (report == null) {
+ continue;
+ }
+ pw.print("t-");
+ pw.print(r);
+ pw.print(": ");
+ pw.print(padStringWithSpaces(Integer.toString(report.cumulativeBatteryDischarge), 15));
+ if (report.numProfitableActions > 0) {
+ final String perDischarge = report.cumulativeBatteryDischarge > 0
+ ? cakeToString(report.cumulativeProfit / report.cumulativeBatteryDischarge)
+ : "N/A";
+ pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
+ cakeToString(report.cumulativeProfit),
+ cakeToString(report.cumulativeProfit / report.numProfitableActions),
+ perDischarge),
+ statColsLength));
+ } else {
+ pw.print(padStringWithSpaces("N/A", statColsLength));
+ }
+ if (report.numUnprofitableActions > 0) {
+ final String perDischarge = report.cumulativeBatteryDischarge > 0
+ ? cakeToString(report.cumulativeLoss / report.cumulativeBatteryDischarge)
+ : "N/A";
+ pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
+ cakeToString(report.cumulativeLoss),
+ cakeToString(report.cumulativeLoss / report.numUnprofitableActions),
+ perDischarge),
+ statColsLength));
+ } else {
+ pw.print(padStringWithSpaces("N/A", statColsLength));
+ }
+ if (report.numRewards > 0) {
+ final String perDischarge = report.cumulativeBatteryDischarge > 0
+ ? cakeToString(report.cumulativeRewards / report.cumulativeBatteryDischarge)
+ : "N/A";
+ pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
+ cakeToString(report.cumulativeRewards),
+ cakeToString(report.cumulativeRewards / report.numRewards),
+ perDischarge),
+ statColsLength));
+ } else {
+ pw.print(padStringWithSpaces("N/A", statColsLength));
+ }
+ if (report.numPositiveRegulations > 0) {
+ final String perDischarge = report.cumulativeBatteryDischarge > 0
+ ? cakeToString(
+ report.cumulativePositiveRegulations / report.cumulativeBatteryDischarge)
+ : "N/A";
+ pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
+ cakeToString(report.cumulativePositiveRegulations),
+ cakeToString(report.cumulativePositiveRegulations
+ / report.numPositiveRegulations),
+ perDischarge),
+ statColsLength));
+ } else {
+ pw.print(padStringWithSpaces("N/A", statColsLength));
+ }
+ if (report.numNegativeRegulations > 0) {
+ final String perDischarge = report.cumulativeBatteryDischarge > 0
+ ? cakeToString(
+ report.cumulativeNegativeRegulations / report.cumulativeBatteryDischarge)
+ : "N/A";
+ pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
+ cakeToString(report.cumulativeNegativeRegulations),
+ cakeToString(report.cumulativeNegativeRegulations
+ / report.numNegativeRegulations),
+ perDischarge),
+ statColsLength));
+ } else {
+ pw.print(padStringWithSpaces("N/A", statColsLength));
+ }
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 2118eeb..9c7d702 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -121,6 +121,7 @@
private IDeviceIdleController mDeviceIdleController;
private final Agent mAgent;
+ private final Analyst mAnalyst;
private final ConfigObserver mConfigObserver;
private final EconomyManagerStub mEconomyManagerStub;
private final Scribe mScribe;
@@ -254,9 +255,10 @@
mPackageManager = context.getPackageManager();
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mEconomyManagerStub = new EconomyManagerStub();
- mScribe = new Scribe(this);
+ mAnalyst = new Analyst();
+ mScribe = new Scribe(this, mAnalyst);
mCompleteEconomicPolicy = new CompleteEconomicPolicy(this);
- mAgent = new Agent(this, mScribe);
+ mAgent = new Agent(this, mScribe, mAnalyst);
mConfigObserver = new ConfigObserver(mHandler, context);
@@ -375,6 +377,7 @@
void onBatteryLevelChanged() {
synchronized (mLock) {
final int newBatteryLevel = getCurrentBatteryLevel();
+ mAnalyst.noteBatteryLevelChange(newBatteryLevel);
final boolean increased = newBatteryLevel > mCurrentBatteryLevel;
if (increased) {
mAgent.distributeBasicIncomeLocked(newBatteryLevel);
@@ -741,6 +744,7 @@
}
synchronized (mLock) {
mAgent.tearDownLocked();
+ mAnalyst.tearDown();
mCompleteEconomicPolicy.tearDown();
mExemptedApps.clear();
mExemptListLoaded = false;
@@ -1145,6 +1149,9 @@
pw.println();
mAgent.dumpLocked(pw);
+
+ pw.println();
+ mAnalyst.dump(pw);
}
}
}
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 7442877..941cc39 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -72,6 +72,7 @@
private static final String XML_TAG_TARE = "tare";
private static final String XML_TAG_TRANSACTION = "transaction";
private static final String XML_TAG_USER = "user";
+ private static final String XML_TAG_PERIOD_REPORT = "report";
private static final String XML_ATTR_CTP = "ctp";
private static final String XML_ATTR_DELTA = "delta";
@@ -86,6 +87,18 @@
private static final String XML_ATTR_LAST_RECLAMATION_TIME = "lastReclamationTime";
private static final String XML_ATTR_REMAINING_CONSUMABLE_CAKES = "remainingConsumableCakes";
private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit";
+ private static final String XML_ATTR_PR_DISCHARGE = "discharge";
+ private static final String XML_ATTR_PR_BATTERY_LEVEL = "batteryLevel";
+ private static final String XML_ATTR_PR_PROFIT = "profit";
+ private static final String XML_ATTR_PR_NUM_PROFIT = "numProfits";
+ private static final String XML_ATTR_PR_LOSS = "loss";
+ private static final String XML_ATTR_PR_NUM_LOSS = "numLoss";
+ private static final String XML_ATTR_PR_REWARDS = "rewards";
+ private static final String XML_ATTR_PR_NUM_REWARDS = "numRewards";
+ private static final String XML_ATTR_PR_POS_REGULATIONS = "posRegulations";
+ private static final String XML_ATTR_PR_NUM_POS_REGULATIONS = "numPosRegulations";
+ private static final String XML_ATTR_PR_NEG_REGULATIONS = "negRegulations";
+ private static final String XML_ATTR_PR_NUM_NEG_REGULATIONS = "numNegRegulations";
/** Version of the file schema. */
private static final int STATE_FILE_VERSION = 0;
@@ -94,6 +107,7 @@
private final AtomicFile mStateFile;
private final InternalResourceService mIrs;
+ private final Analyst mAnalyst;
@GuardedBy("mIrs.getLock()")
private long mLastReclamationTime;
@@ -107,13 +121,14 @@
private final Runnable mCleanRunnable = this::cleanupLedgers;
private final Runnable mWriteRunnable = this::writeState;
- Scribe(InternalResourceService irs) {
- this(irs, Environment.getDataSystemDirectory());
+ Scribe(InternalResourceService irs, Analyst analyst) {
+ this(irs, analyst, Environment.getDataSystemDirectory());
}
@VisibleForTesting
- Scribe(InternalResourceService irs, File dataDir) {
+ Scribe(InternalResourceService irs, Analyst analyst, File dataDir) {
mIrs = irs;
+ mAnalyst = analyst;
final File tareDir = new File(dataDir, "tare");
//noinspection ResultOfMethodCallIgnored
@@ -210,6 +225,7 @@
}
}
+ final List<Analyst.Report> reports = new ArrayList<>();
try (FileInputStream fis = mStateFile.openRead()) {
TypedXmlPullParser parser = Xml.resolvePullParser(fis);
@@ -263,11 +279,15 @@
readUserFromXmlLocked(
parser, installedPackagesPerUser, endTimeCutoff));
break;
+ case XML_TAG_PERIOD_REPORT:
+ reports.add(readReportFromXml(parser));
+ break;
default:
Slog.e(TAG, "Unexpected tag: " + tagName);
break;
}
}
+ mAnalyst.loadReports(reports);
scheduleCleanup(earliestEndTime);
} catch (IOException | XmlPullParserException e) {
Slog.wtf(TAG, "Error reading state from disk", e);
@@ -457,6 +477,37 @@
return earliestEndTime;
}
+
+ /**
+ * @param parser Xml parser at the beginning of a {@link #XML_TAG_PERIOD_REPORT} tag. The next
+ * "parser.next()" call will take the parser into the body of the report tag.
+ * @return Newly instantiated Report holding all the information we just read out of the xml tag
+ */
+ @NonNull
+ private static Analyst.Report readReportFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ final Analyst.Report report = new Analyst.Report();
+
+ report.cumulativeBatteryDischarge = parser.getAttributeInt(null, XML_ATTR_PR_DISCHARGE);
+ report.currentBatteryLevel = parser.getAttributeInt(null, XML_ATTR_PR_BATTERY_LEVEL);
+ report.cumulativeProfit = parser.getAttributeLong(null, XML_ATTR_PR_PROFIT);
+ report.numProfitableActions = parser.getAttributeInt(null, XML_ATTR_PR_NUM_PROFIT);
+ report.cumulativeLoss = parser.getAttributeLong(null, XML_ATTR_PR_LOSS);
+ report.numUnprofitableActions = parser.getAttributeInt(null, XML_ATTR_PR_NUM_LOSS);
+ report.cumulativeRewards = parser.getAttributeLong(null, XML_ATTR_PR_REWARDS);
+ report.numRewards = parser.getAttributeInt(null, XML_ATTR_PR_NUM_REWARDS);
+ report.cumulativePositiveRegulations =
+ parser.getAttributeLong(null, XML_ATTR_PR_POS_REGULATIONS);
+ report.numPositiveRegulations =
+ parser.getAttributeInt(null, XML_ATTR_PR_NUM_POS_REGULATIONS);
+ report.cumulativeNegativeRegulations =
+ parser.getAttributeLong(null, XML_ATTR_PR_NEG_REGULATIONS);
+ report.numNegativeRegulations =
+ parser.getAttributeInt(null, XML_ATTR_PR_NUM_NEG_REGULATIONS);
+
+ return report;
+ }
+
private void scheduleCleanup(long earliestEndTime) {
if (earliestEndTime == Long.MAX_VALUE) {
return;
@@ -501,6 +552,11 @@
writeUserLocked(out, userId));
}
+ List<Analyst.Report> reports = mAnalyst.getReports();
+ for (int i = 0, size = reports.size(); i < size; ++i) {
+ writeReport(out, reports.get(i));
+ }
+
out.endTag(null, XML_TAG_TARE);
out.endDocument();
@@ -560,6 +616,24 @@
out.endTag(null, XML_TAG_TRANSACTION);
}
+ private static void writeReport(@NonNull TypedXmlSerializer out,
+ @NonNull Analyst.Report report) throws IOException {
+ out.startTag(null, XML_TAG_PERIOD_REPORT);
+ out.attributeInt(null, XML_ATTR_PR_DISCHARGE, report.cumulativeBatteryDischarge);
+ out.attributeInt(null, XML_ATTR_PR_BATTERY_LEVEL, report.currentBatteryLevel);
+ out.attributeLong(null, XML_ATTR_PR_PROFIT, report.cumulativeProfit);
+ out.attributeInt(null, XML_ATTR_PR_NUM_PROFIT, report.numProfitableActions);
+ out.attributeLong(null, XML_ATTR_PR_LOSS, report.cumulativeLoss);
+ out.attributeInt(null, XML_ATTR_PR_NUM_LOSS, report.numUnprofitableActions);
+ out.attributeLong(null, XML_ATTR_PR_REWARDS, report.cumulativeRewards);
+ out.attributeInt(null, XML_ATTR_PR_NUM_REWARDS, report.numRewards);
+ out.attributeLong(null, XML_ATTR_PR_POS_REGULATIONS, report.cumulativePositiveRegulations);
+ out.attributeInt(null, XML_ATTR_PR_NUM_POS_REGULATIONS, report.numPositiveRegulations);
+ out.attributeLong(null, XML_ATTR_PR_NEG_REGULATIONS, report.cumulativeNegativeRegulations);
+ out.attributeInt(null, XML_ATTR_PR_NUM_NEG_REGULATIONS, report.numNegativeRegulations);
+ out.endTag(null, XML_TAG_PERIOD_REPORT);
+ }
+
@GuardedBy("mIrs.getLock()")
void dumpLocked(IndentingPrintWriter pw, boolean dumpAll) {
pw.println("Ledgers:");
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3158bd7..c6f5920 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -873,6 +873,7 @@
String processName;
@UnsupportedAppUsage
ApplicationInfo appInfo;
+ String sdkSandboxClientAppVolumeUuid;
String sdkSandboxClientAppPackage;
@UnsupportedAppUsage
List<ProviderInfo> providers;
@@ -1119,9 +1120,10 @@
@Override
public final void bindApplication(String processName, ApplicationInfo appInfo,
- String sdkSandboxClientAppPackage, ProviderInfoList providerList,
- ComponentName instrumentationName, ProfilerInfo profilerInfo,
- Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
+ String sdkSandboxClientAppVolumeUuid, String sdkSandboxClientAppPackage,
+ ProviderInfoList providerList, ComponentName instrumentationName,
+ ProfilerInfo profilerInfo, Bundle instrumentationArgs,
+ IInstrumentationWatcher instrumentationWatcher,
IUiAutomationConnection instrumentationUiConnection, int debugMode,
boolean enableBinderTracking, boolean trackAllocation,
boolean isRestrictedBackupMode, boolean persistent, Configuration config,
@@ -1161,6 +1163,7 @@
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
+ data.sdkSandboxClientAppVolumeUuid = sdkSandboxClientAppVolumeUuid;
data.sdkSandboxClientAppPackage = sdkSandboxClientAppPackage;
data.providers = providerList.getList();
data.instrumentationName = instrumentationName;
@@ -2561,6 +2564,11 @@
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
+ private LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo,
+ boolean isSdkSandbox) {
+ return getPackageInfo(ai, compatInfo, null, false, true, false, isSdkSandbox);
+ }
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) {
synchronized (mResourcesManager) {
@@ -2577,11 +2585,18 @@
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
+ return getPackageInfo(aInfo, compatInfo, baseLoader, securityViolation, includeCode,
+ registerPackage, /*isSdkSandbox=*/false);
+ }
+
+ private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
+ ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
+ boolean registerPackage, boolean isSdkSandbox) {
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
WeakReference<LoadedApk> ref;
- if (differentUser) {
- // Caching not supported across users
+ if (differentUser || isSdkSandbox) {
+ // Caching not supported across users and for sdk sandboxes
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
@@ -2628,8 +2643,8 @@
getSystemContext().mPackageInfo.getClassLoader());
}
- if (differentUser) {
- // Caching not supported across users
+ if (differentUser || isSdkSandbox) {
+ // Caching not supported across users and for sdk sandboxes
} else if (includeCode) {
mPackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
@@ -6575,9 +6590,11 @@
mConfigurationController.applyCompatConfiguration();
}
- data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
- if (data.sdkSandboxClientAppPackage != null) {
- data.info.setSdkSandboxStorage(data.sdkSandboxClientAppPackage);
+ final boolean isSdkSandbox = data.sdkSandboxClientAppPackage != null;
+ data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo, isSdkSandbox);
+ if (isSdkSandbox) {
+ data.info.setSdkSandboxStorage(data.sdkSandboxClientAppVolumeUuid,
+ data.sdkSandboxClientAppPackage);
}
if (agent != null) {
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index f4fbcce..75ad363 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -72,7 +72,7 @@
@UnsupportedAppUsage
void scheduleStopService(IBinder token);
void bindApplication(in String packageName, in ApplicationInfo info,
- in String sdkSandboxClientAppPackage,
+ in String sdkSandboxClientAppVolumeUuid, in String sdkSandboxClientAppPackage,
in ProviderInfoList providerList, in ComponentName testName,
in ProfilerInfo profilerInfo, in Bundle testArguments,
IInstrumentationWatcher testWatcher, IUiAutomationConnection uiAutomationConnection,
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 7bb5d9a..36e5762 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -137,7 +137,7 @@
* activities inside it belong to a managed profile user, and that user has just
* been locked.
*/
- void onTaskProfileLocked(int taskId, int userId);
+ void onTaskProfileLocked(in ActivityManager.RunningTaskInfo taskInfo);
/**
* Called when a task snapshot got updated.
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index deefea8..0849a6f 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -400,7 +400,8 @@
mLibDir = aInfo.nativeLibraryDir;
mDataDirFile = FileUtils.newFileOrNull(aInfo.dataDir);
mDeviceProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.deviceProtectedDataDir);
- mCredentialProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.credentialProtectedDataDir);
+ mCredentialProtectedDataDirFile = FileUtils.newFileOrNull(
+ aInfo.credentialProtectedDataDir);
mSplitNames = aInfo.splitNames;
mSplitAppDirs = aInfo.splitSourceDirs;
@@ -412,14 +413,16 @@
}
}
- /** @hide */
- void setSdkSandboxStorage(String sdkSandboxClientAppPackage) {
+ void setSdkSandboxStorage(@Nullable String sdkSandboxClientAppVolumeUuid,
+ String sdkSandboxClientAppPackage) {
int userId = UserHandle.myUserId();
mDeviceProtectedDataDirFile = Environment
- .getDataMiscDeSharedSdkSandboxDirectory(userId, sdkSandboxClientAppPackage)
+ .getDataMiscDeSharedSdkSandboxDirectory(sdkSandboxClientAppVolumeUuid, userId,
+ sdkSandboxClientAppPackage)
.getAbsoluteFile();
mCredentialProtectedDataDirFile = Environment
- .getDataMiscCeSharedSdkSandboxDirectory(userId, sdkSandboxClientAppPackage)
+ .getDataMiscCeSharedSdkSandboxDirectory(sdkSandboxClientAppVolumeUuid, userId,
+ sdkSandboxClientAppPackage)
.getAbsoluteFile();
if ((mApplicationInfo.privateFlags
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 83fe29f..774bc06 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -155,7 +155,7 @@
@Override
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public void onTaskProfileLocked(int taskId, int userId) throws RemoteException {
+ public void onTaskProfileLocked(RunningTaskInfo taskInfo) throws RemoteException {
}
@Override
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 022d213..5177cb4 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -478,8 +478,15 @@
}
/** {@hide} */
- public static File getDataMiscCeSharedSdkSandboxDirectory(int userId, String packageName) {
- return buildPath(getDataMiscCeDirectory(userId), "sdksandbox", packageName, "shared");
+ private static File getDataMiscCeDirectory(String volumeUuid, int userId) {
+ return buildPath(getDataDirectory(volumeUuid), "misc_ce", String.valueOf(userId));
+ }
+
+ /** {@hide} */
+ public static File getDataMiscCeSharedSdkSandboxDirectory(String volumeUuid, int userId,
+ String packageName) {
+ return buildPath(getDataMiscCeDirectory(volumeUuid, userId), "sdksandbox",
+ packageName, "shared");
}
/** {@hide} */
@@ -488,8 +495,15 @@
}
/** {@hide} */
- public static File getDataMiscDeSharedSdkSandboxDirectory(int userId, String packageName) {
- return buildPath(getDataMiscDeDirectory(userId), "sdksandbox", packageName, "shared");
+ private static File getDataMiscDeDirectory(String volumeUuid, int userId) {
+ return buildPath(getDataDirectory(volumeUuid), "misc_de", String.valueOf(userId));
+ }
+
+ /** {@hide} */
+ public static File getDataMiscDeSharedSdkSandboxDirectory(String volumeUuid, int userId,
+ String packageName) {
+ return buildPath(getDataMiscDeDirectory(volumeUuid, userId), "sdksandbox",
+ packageName, "shared");
}
private static File getDataProfilesDeDirectory(int userId) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index e2e9a85..a0a3b4f 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -782,6 +782,7 @@
@Override
public void setCurrentRootView(ViewRootImpl rootView) {
synchronized (mH) {
+ mImeDispatcher.switchRootView(mCurRootView, rootView);
mCurRootView = rootView;
}
}
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 8bdf233..5924844 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -25,8 +25,9 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
+import android.view.ViewRootImpl;
-import java.util.HashMap;
+import java.util.ArrayList;
/**
* A {@link OnBackInvokedDispatcher} for IME that forwards {@link OnBackInvokedCallback}
@@ -117,7 +118,7 @@
}
};
- private final HashMap<Integer, OnBackInvokedCallback> mImeCallbackMap = new HashMap<>();
+ private final ArrayList<ImeOnBackInvokedCallback> mImeCallbacks = new ArrayList<>();
private void receive(
int resultCode, Bundle resultData,
@@ -140,39 +141,55 @@
int callbackId,
@NonNull OnBackInvokedDispatcher receivingDispatcher) {
final ImeOnBackInvokedCallback imeCallback =
- new ImeOnBackInvokedCallback(iCallback);
- mImeCallbackMap.put(callbackId, imeCallback);
+ new ImeOnBackInvokedCallback(iCallback, callbackId, priority);
+ mImeCallbacks.add(imeCallback);
receivingDispatcher.registerOnBackInvokedCallback(priority, imeCallback);
}
private void unregisterReceivedCallback(
int callbackId, OnBackInvokedDispatcher receivingDispatcher) {
- final OnBackInvokedCallback callback = mImeCallbackMap.get(callbackId);
+ ImeOnBackInvokedCallback callback = null;
+ for (ImeOnBackInvokedCallback imeCallback : mImeCallbacks) {
+ if (imeCallback.getId() == callbackId) {
+ callback = imeCallback;
+ break;
+ }
+ }
if (callback == null) {
Log.e(TAG, "Ime callback not found. Ignoring unregisterReceivedCallback. "
+ "callbackId: " + callbackId);
return;
}
receivingDispatcher.unregisterOnBackInvokedCallback(callback);
+ mImeCallbacks.remove(callback);
}
/** Clears all registered callbacks on the instance. */
public void clear() {
// Unregister previously registered callbacks if there's any.
if (getReceivingDispatcher() != null) {
- for (OnBackInvokedCallback callback : mImeCallbackMap.values()) {
+ for (ImeOnBackInvokedCallback callback : mImeCallbacks) {
getReceivingDispatcher().unregisterOnBackInvokedCallback(callback);
}
}
- mImeCallbackMap.clear();
+ mImeCallbacks.clear();
}
static class ImeOnBackInvokedCallback implements OnBackInvokedCallback {
@NonNull
private final IOnBackInvokedCallback mIOnBackInvokedCallback;
+ /**
+ * The hashcode of the callback instance in the IME process, used as a unique id to
+ * identify the callback when it's passed between processes.
+ */
+ private final int mId;
+ private final int mPriority;
- ImeOnBackInvokedCallback(@NonNull IOnBackInvokedCallback iCallback) {
+ ImeOnBackInvokedCallback(@NonNull IOnBackInvokedCallback iCallback, int id,
+ @Priority int priority) {
mIOnBackInvokedCallback = iCallback;
+ mId = id;
+ mPriority = priority;
}
@Override
@@ -186,8 +203,33 @@
}
}
+ private int getId() {
+ return mId;
+ }
+
IOnBackInvokedCallback getIOnBackInvokedCallback() {
return mIOnBackInvokedCallback;
}
}
+
+ /**
+ * Transfers {@link ImeOnBackInvokedCallback}s registered on one {@link ViewRootImpl} to
+ * another {@link ViewRootImpl} on focus change.
+ *
+ * @param previous the previously focused {@link ViewRootImpl}.
+ * @param current the currently focused {@link ViewRootImpl}.
+ */
+ // TODO(b/232845902): Add CTS to test IME back behavior when there's root view change while
+ // IME is up.
+ public void switchRootView(ViewRootImpl previous, ViewRootImpl current) {
+ for (ImeOnBackInvokedCallback imeCallback : mImeCallbacks) {
+ if (previous != null) {
+ previous.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(imeCallback);
+ }
+ if (current != null) {
+ current.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ imeCallback.mPriority, imeCallback);
+ }
+ }
+ }
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index edfdbcc..985e2b9 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -157,15 +157,16 @@
/** Clears all registered callbacks on the instance. */
public void clear() {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.clear();
+ mImeDispatcher = null;
+ }
if (!mAllCallbacks.isEmpty()) {
// Clear binder references in WM.
setTopOnBackInvokedCallback(null);
}
mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear();
- if (mImeDispatcher != null) {
- mImeDispatcher = null;
- }
}
private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 8276d10..0eca0a8 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -425,7 +425,7 @@
@Override
public void bindApplication(String s, ApplicationInfo applicationInfo,
- String sdkSandboxClientAppPackage,
+ String sdkSandboxClientAppVolumeUuid, String sdkSandboxClientAppPackage,
ProviderInfoList list, ComponentName componentName, ProfilerInfo profilerInfo,
Bundle bundle, IInstrumentationWatcher iInstrumentationWatcher,
IUiAutomationConnection iUiAutomationConnection, int i, boolean b, boolean b1,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt
index 0d3ba9a..1eee029 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt
@@ -126,7 +126,11 @@
@UserIdInt userId: Int,
@UserIdInt parentUserId: Int
): Boolean {
- return entitiesByUser.get(parentUserId).removeIf { b: BubbleEntity -> b.userId == userId }
+ if (entitiesByUser.get(parentUserId) != null) {
+ return entitiesByUser.get(parentUserId).removeIf {
+ b: BubbleEntity -> b.userId == userId }
+ }
+ return false
}
/**
@@ -141,8 +145,9 @@
// First check if the user is a parent / top-level user
val parentUserId = entitiesByUser.keyAt(i)
if (!activeUsers.contains(parentUserId)) {
- return removeBubblesForUser(parentUserId, -1)
- } else {
+ entitiesByUser.remove(parentUserId)
+ return true
+ } else if (entitiesByUser.get(parentUserId) != null) {
// Then check if each of the bubbles in the top-level user, still has a valid user
// as it could belong to a profile and have a different id from the parent.
return entitiesByUser.get(parentUserId).removeIf { b: BubbleEntity ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
index 59374a6..b9ddd36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
@@ -38,7 +38,7 @@
default void onTaskStackChanged() { }
- default void onTaskProfileLocked(int taskId, int userId) { }
+ default void onTaskProfileLocked(RunningTaskInfo taskInfo) { }
default void onTaskDisplayChanged(int taskId, int newDisplayId) { }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
index 3b67005..85e2654 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
@@ -150,8 +150,8 @@
}
@Override
- public void onTaskProfileLocked(int taskId, int userId) {
- mMainHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
+ public void onTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo) {
+ mMainHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskInfo).sendToTarget();
}
@Override
@@ -341,8 +341,10 @@
break;
}
case ON_TASK_PROFILE_LOCKED: {
+ final ActivityManager.RunningTaskInfo
+ info = (ActivityManager.RunningTaskInfo) msg.obj;
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
+ mTaskStackListeners.get(i).onTaskProfileLocked(info);
}
break;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
index 77c7055..9f0d89b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
@@ -269,6 +269,12 @@
assertThat(repository.getEntities(user11.identifier).toList())
.isEqualTo(listOf(bubble11, bubble12))
}
+
+ @Test
+ fun testRemoveBubbleForUser_invalidInputDoesntCrash() {
+ repository.removeBubblesForUser(-1, 0)
+ repository.removeBubblesForUser(-1, -1)
+ }
}
private const val PKG_MESSENGER = "com.example.messenger"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
index d8aebc2..96938eb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
@@ -109,9 +109,10 @@
@Test
public void testOnTaskProfileLocked() {
- mImpl.onTaskProfileLocked(1, 2);
- verify(mCallback).onTaskProfileLocked(eq(1), eq(2));
- verify(mOtherCallback).onTaskProfileLocked(eq(1), eq(2));
+ ActivityManager.RunningTaskInfo info = mock(ActivityManager.RunningTaskInfo.class);
+ mImpl.onTaskProfileLocked(info);
+ verify(mCallback).onTaskProfileLocked(eq(info));
+ verify(mOtherCallback).onTaskProfileLocked(eq(info));
}
@Test
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 4106ac5..4757513 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -47,3 +47,39 @@
"androidx.leanback_leanback",
],
}
+
+android_app {
+ name: "PackageInstaller_tablet",
+ defaults: ["platform_app_defaults"],
+
+ srcs: ["src/**/*.java"],
+
+ certificate: "platform",
+ privileged: true,
+ platform_apis: true,
+ rename_resources_package: false,
+
+ static_libs: [
+ "xz-java",
+ "androidx.leanback_leanback",
+ ],
+ aaptflags: ["--product tablet"],
+}
+
+android_app {
+ name: "PackageInstaller_tv",
+ defaults: ["platform_app_defaults"],
+
+ srcs: ["src/**/*.java"],
+
+ certificate: "platform",
+ privileged: true,
+ platform_apis: true,
+ rename_resources_package: false,
+
+ static_libs: [
+ "xz-java",
+ "androidx.leanback_leanback",
+ ],
+ aaptflags: ["--product tv"],
+}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4d0888a..5dd1b09 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -723,7 +723,7 @@
android:excludeFromRecents="true"
android:stateNotNeeded="true"
android:resumeWhilePausing="true"
- android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
+ android:theme="@style/Theme.AppCompat.DayNight.NoActionBar">
<intent-filter>
<action android:name="android.app.action.CONFIRM_DEVICE_CREDENTIAL_WITH_USER" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/packages/SystemUI/res/layout/auth_biometric_background.xml b/packages/SystemUI/res/layout/auth_biometric_background.xml
new file mode 100644
index 0000000..7ce81ad
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_biometric_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:layout_marginBottom="160dp"/>
+
+</LinearLayout>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index f65d82a..acd4222 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -61,7 +61,7 @@
onActivityLaunchOnSecondaryDisplayRerouted();
}
- default void onTaskProfileLocked(int taskId, int userId) { }
+ default void onTaskProfileLocked(RunningTaskInfo taskInfo) { }
default void onTaskCreated(int taskId, ComponentName componentName) { }
default void onTaskRemoved(int taskId) { }
default void onTaskMovedToFront(int taskId) { }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index b5019b4..2fd5aae 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -211,8 +211,8 @@
}
@Override
- public void onTaskProfileLocked(int taskId, int userId) {
- mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
+ public void onTaskProfileLocked(RunningTaskInfo taskInfo) {
+ mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskInfo).sendToTarget();
}
@Override
@@ -357,8 +357,9 @@
break;
}
case ON_TASK_PROFILE_LOCKED: {
+ final RunningTaskInfo info = (RunningTaskInfo) msg.obj;
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
+ mTaskStackListeners.get(i).onTaskProfileLocked(info);
}
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index e6b650b..546a409 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -17,23 +17,22 @@
package com.android.systemui.keyguard;
import static android.app.ActivityManager.TaskDescription;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.WORK_LOCK_ACCESSIBILITY;
-import android.annotation.ColorInt;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.Color;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
-import android.view.View;
+import android.os.UserManager;
+import android.widget.ImageView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
@@ -52,12 +51,6 @@
public class WorkLockActivity extends Activity {
private static final String TAG = "WorkLockActivity";
- /**
- * Contains a {@link TaskDescription} for the activity being covered.
- */
- static final String EXTRA_TASK_DESCRIPTION =
- "com.android.systemui.keyguard.extra.TASK_DESCRIPTION";
-
private static final int REQUEST_CODE_CONFIRM_CREDENTIALS = 1;
/**
@@ -65,12 +58,17 @@
* @see KeyguardManager
*/
private KeyguardManager mKgm;
+ private UserManager mUserManager;
+ private PackageManager mPackageManager;
private final BroadcastDispatcher mBroadcastDispatcher;
@Inject
- public WorkLockActivity(BroadcastDispatcher broadcastDispatcher) {
+ public WorkLockActivity(BroadcastDispatcher broadcastDispatcher, UserManager userManager,
+ PackageManager packageManager) {
super();
mBroadcastDispatcher = broadcastDispatcher;
+ mUserManager = userManager;
+ mPackageManager = packageManager;
}
@Override
@@ -91,15 +89,28 @@
// Draw captions overlaid on the content view, so the whole window is one solid color.
setOverlayWithDecorCaptionEnabled(true);
- // Blank out the activity. When it is on-screen it will look like a Recents thumbnail with
- // redaction switched on.
- final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
- String contentDescription = dpm.getResources().getString(
- WORK_LOCK_ACCESSIBILITY, () -> getString(R.string.accessibility_desc_work_lock));
- final View blankView = new View(this);
- blankView.setContentDescription(contentDescription);
- blankView.setBackgroundColor(getPrimaryColor());
- setContentView(blankView);
+ // Add background protection that contains a badged icon of the app being opened.
+ setContentView(R.layout.auth_biometric_background);
+ Drawable badgedIcon = getBadgedIcon();
+ if (badgedIcon != null) {
+ ((ImageView) findViewById(R.id.icon)).setImageDrawable(badgedIcon);
+ }
+ }
+
+ @VisibleForTesting
+ protected Drawable getBadgedIcon() {
+ String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ if (!packageName.isEmpty()) {
+ try {
+ return mUserManager.getBadgedIconForUser(mPackageManager.getApplicationIcon(
+ mPackageManager.getApplicationInfoAsUser(packageName,
+ PackageManager.ApplicationInfoFlags.of(0), getTargetUserId())),
+ UserHandle.of(getTargetUserId()));
+ } catch (PackageManager.NameNotFoundException e) {
+ // Unable to set the badged icon, show the background protection without an icon.
+ }
+ }
+ return null;
}
/**
@@ -208,19 +219,4 @@
final int getTargetUserId() {
return getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId());
}
-
- @VisibleForTesting
- @ColorInt
- final int getPrimaryColor() {
- final TaskDescription taskDescription = (TaskDescription)
- getIntent().getExtra(EXTRA_TASK_DESCRIPTION);
- if (taskDescription != null && Color.alpha(taskDescription.getPrimaryColor()) == 255) {
- return taskDescription.getPrimaryColor();
- } else {
- // No task description. Use an organization color set by the policy controller.
- final DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
- getSystemService(Context.DEVICE_POLICY_SERVICE);
- return devicePolicyManager.getOrganizationColorForUser(getTargetUserId());
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index 7585110..16817ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -52,22 +52,17 @@
tscl.registerTaskStackListener(mLockListener);
}
- private void startWorkChallengeInTask(int taskId, int userId) {
- ActivityManager.TaskDescription taskDescription = null;
- try {
- taskDescription = mIatm.getTaskDescription(taskId);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to get description for task=" + taskId);
- }
+ private void startWorkChallengeInTask(ActivityManager.RunningTaskInfo info) {
+ String packageName = info.baseActivity != null ? info.baseActivity.getPackageName() : "";
Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
.setComponent(new ComponentName(mContext, WorkLockActivity.class))
- .putExtra(Intent.EXTRA_USER_ID, userId)
- .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, taskDescription)
+ .putExtra(Intent.EXTRA_USER_ID, info.userId)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchTaskId(taskId);
+ options.setLaunchTaskId(info.taskId);
options.setTaskOverlay(true, false /* canResume */);
final int result = startActivityAsUser(intent, options.toBundle(), UserHandle.USER_CURRENT);
@@ -77,9 +72,9 @@
// Starting the activity inside the task failed. We can't be sure why, so to be
// safe just remove the whole task if it still exists.
try {
- mIatm.removeTask(taskId);
+ mIatm.removeTask(info.taskId);
} catch (RemoteException e) {
- Log.w(TAG, "Failed to get description for task=" + taskId);
+ Log.w(TAG, "Failed to get description for task=" + info.taskId);
}
}
}
@@ -112,8 +107,8 @@
private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() {
@Override
- public void onTaskProfileLocked(int taskId, int userId) {
- startWorkChallengeInTask(taskId, userId);
+ public void onTaskProfileLocked(ActivityManager.RunningTaskInfo info) {
+ startWorkChallengeInTask(info);
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index 7800b4c..8405aea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -22,6 +22,7 @@
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Rect
+import android.view.ContextThemeWrapper
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
@@ -46,6 +47,7 @@
) : SystemStatusAnimationCallback {
private lateinit var animationWindowView: FrameLayout
+ private lateinit var themedContext: ContextThemeWrapper
private var currentAnimatedView: BackgroundAnimatableView? = null
@@ -76,7 +78,7 @@
// Initialize the animated view
val insets = contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()
- currentAnimatedView = viewCreator(context).also {
+ currentAnimatedView = viewCreator(themedContext).also {
animationWindowView.addView(
it.view,
layoutParamsDefault(
@@ -221,7 +223,8 @@
private fun init() {
initialized = true
- animationWindowView = LayoutInflater.from(context)
+ themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+ animationWindowView = LayoutInflater.from(themedContext)
.inflate(R.layout.system_event_animation_window, null) as FrameLayout
val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 075df1a..366cd07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -2315,8 +2315,9 @@
void setQsExpansion(float height) {
height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
+ boolean qsAnimatingAway = !mQsAnimatorExpand && mAnimatingQS;
if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
- && !mDozing) {
+ && !mDozing && !qsAnimatingAway) {
setQsExpanded(true);
} else if (height <= mQsMinExpansionHeight && mQsExpanded) {
setQsExpanded(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 4c43734..576962d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -103,6 +103,10 @@
private boolean mShowSeconds;
private Handler mSecondsHandler;
+ // Fields to cache the width so the clock remains at an approximately constant width
+ private int mCharsAtCurrentWidth = -1;
+ private int mCachedWidth = -1;
+
/**
* Color to be set on this {@link TextView}, when wallpaperTextColor is <b>not</b> utilized.
*/
@@ -302,6 +306,32 @@
setContentDescription(mContentDescriptionFormat.format(mCalendar.getTime()));
}
+ /**
+ * In order to avoid the clock growing and shrinking due to proportional fonts, we want to
+ * cache the drawn width at a given number of characters (removing the cache when it changes),
+ * and only use the biggest value. This means that the clock width with grow to the maximum
+ * size over time, but reset whenever the number of characters changes (or the configuration
+ * changes)
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int chars = getText().length();
+ if (chars != mCharsAtCurrentWidth) {
+ mCharsAtCurrentWidth = chars;
+ mCachedWidth = getMeasuredWidth();
+ return;
+ }
+
+ int measuredWidth = getMeasuredWidth();
+ if (mCachedWidth > measuredWidth) {
+ setMeasuredDimension(mCachedWidth, getMeasuredHeight());
+ } else {
+ mCachedWidth = measuredWidth;
+ }
+ }
+
@Override
public void onTuningChanged(String key, String newValue) {
if (CLOCK_SECONDS.equals(key)) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
index cd5740d..e9db8cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -60,6 +60,13 @@
public class WorkLockActivityControllerTest extends SysuiTestCase {
private static final int USER_ID = 333;
private static final int TASK_ID = 444;
+ private static final ActivityManager.RunningTaskInfo TASK_INFO =
+ new ActivityManager.RunningTaskInfo();
+
+ static {
+ TASK_INFO.userId = USER_ID;
+ TASK_INFO.taskId = TASK_ID;
+ }
private @Mock Context mContext;
private @Mock TaskStackChangeListeners mTaskStackChangeListeners;
@@ -91,7 +98,7 @@
setActivityStartCode(TASK_ID, true /*taskOverlay*/, ActivityManager.START_SUCCESS);
// And the controller receives a message saying the profile is locked,
- mTaskStackListener.onTaskProfileLocked(TASK_ID, USER_ID);
+ mTaskStackListener.onTaskProfileLocked(TASK_INFO);
// The overlay should start and the task the activity started in should not be removed.
verifyStartActivity(TASK_ID, true /*taskOverlay*/);
@@ -104,7 +111,7 @@
setActivityStartCode(TASK_ID, true /*taskOverlay*/, ActivityManager.START_CLASS_NOT_FOUND);
// And the controller receives a message saying the profile is locked,
- mTaskStackListener.onTaskProfileLocked(TASK_ID, USER_ID);
+ mTaskStackListener.onTaskProfileLocked(TASK_INFO);
// The task the activity started in should be removed to prevent the locked task from
// being shown.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
index e4c387a..640e6dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.keyguard;
-import static android.app.ActivityManager.TaskDescription;
-
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.eq;
@@ -25,14 +23,15 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.ColorInt;
import android.annotation.UserIdInt;
-import android.app.KeyguardManager;
-import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Color;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -53,18 +52,21 @@
@RunWith(AndroidJUnit4.class)
public class WorkLockActivityTest extends SysuiTestCase {
private static final @UserIdInt int USER_ID = 270;
- private static final String TASK_LABEL = "task label";
+ private static final String CALLING_PACKAGE_NAME = "com.android.test";
- private @Mock DevicePolicyManager mDevicePolicyManager;
- private @Mock KeyguardManager mKeyguardManager;
+ private @Mock UserManager mUserManager;
+ private @Mock PackageManager mPackageManager;
private @Mock Context mContext;
private @Mock BroadcastDispatcher mBroadcastDispatcher;
+ private @Mock Drawable mDrawable;
+ private @Mock Drawable mBadgedDrawable;
private WorkLockActivity mActivity;
private static class WorkLockActivityTestable extends WorkLockActivity {
- WorkLockActivityTestable(Context baseContext, BroadcastDispatcher broadcastDispatcher) {
- super(broadcastDispatcher);
+ WorkLockActivityTestable(Context baseContext, BroadcastDispatcher broadcastDispatcher,
+ UserManager userManager, PackageManager packageManager) {
+ super(broadcastDispatcher, userManager, packageManager);
attachBaseContext(baseContext);
}
}
@@ -73,46 +75,26 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mContext.getSystemService(eq(Context.DEVICE_POLICY_SERVICE)))
- .thenReturn(mDevicePolicyManager);
- when(mContext.getSystemService(eq(Context.KEYGUARD_SERVICE)))
- .thenReturn(mKeyguardManager);
-
if (Looper.myLooper() == null) {
Looper.prepare();
}
- mActivity = new WorkLockActivityTestable(mContext, mBroadcastDispatcher);
+ mActivity = new WorkLockActivityTestable(mContext, mBroadcastDispatcher, mUserManager,
+ mPackageManager);
}
@Test
- public void testBackgroundAlwaysOpaque() throws Exception {
- final @ColorInt int orgColor = Color.rgb(250, 199, 67);
- when(mDevicePolicyManager.getOrganizationColorForUser(eq(USER_ID))).thenReturn(orgColor);
-
- final @ColorInt int opaqueColor= Color.rgb(164, 198, 57);
- final @ColorInt int transparentColor = Color.argb(0, 0, 0, 0);
- TaskDescription opaque = new TaskDescription(null, null, opaqueColor);
- TaskDescription transparent = new TaskDescription(null, null, transparentColor);
-
- // When a task description is provided with a suitable (opaque) primaryColor, it should be
- // used as the scrim's background color.
+ public void testGetBadgedIcon() throws Exception {
+ ApplicationInfo info = new ApplicationInfo();
+ when(mPackageManager.getApplicationInfoAsUser(eq(CALLING_PACKAGE_NAME), any(),
+ eq(USER_ID))).thenReturn(info);
+ when(mPackageManager.getApplicationIcon(eq(info))).thenReturn(mDrawable);
+ when(mUserManager.getBadgedIconForUser(any(), eq(UserHandle.of(USER_ID)))).thenReturn(
+ mBadgedDrawable);
mActivity.setIntent(new Intent()
.putExtra(Intent.EXTRA_USER_ID, USER_ID)
- .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, opaque));
- assertEquals(opaqueColor, mActivity.getPrimaryColor());
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, CALLING_PACKAGE_NAME));
- // When a task description is provided but has no primaryColor / the primaryColor is
- // transparent, the organization color should be used instead.
- mActivity.setIntent(new Intent()
- .putExtra(Intent.EXTRA_USER_ID, USER_ID)
- .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, transparent));
- assertEquals(orgColor, mActivity.getPrimaryColor());
-
- // When no task description is provided at all, it should be treated like a transparent
- // description and the organization color shown instead.
- mActivity.setIntent(new Intent()
- .putExtra(Intent.EXTRA_USER_ID, USER_ID));
- assertEquals(orgColor, mActivity.getPrimaryColor());
+ assertEquals(mBadgedDrawable, mActivity.getBadgedIcon());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt
new file mode 100644
index 0000000..22c72cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.systemui.statusbar.policy
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View.MeasureSpec.UNSPECIFIED
+import android.view.View.MeasureSpec.makeMeasureSpec
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.LinearLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.runner.RunWith
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ClockTest : SysuiTestCase() {
+ private lateinit var clockView: Clock
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ TestableLooper.get(this).runWithLooper {
+ val container = LinearLayout(context)
+ val lp = LinearLayout.LayoutParams(1000, WRAP_CONTENT)
+ container.layoutParams = lp
+ clockView = Clock(context, null)
+ container.addView(clockView)
+ measureClock()
+ }
+ }
+
+ @Test
+ fun testWidthDoesNotDecrease_sameCharLength() {
+ // GIVEN time is narrow
+ clockView.text = ONE_3
+ measureClock()
+ val width1 = clockView.measuredWidth
+
+ // WHEN the text changes to be wider characters
+ clockView.text = ZERO_3
+ measureClock()
+ val width2 = clockView.measuredWidth
+
+ // THEN the width should be wider (or equals when using monospace font)
+ assertThat(width2).isAtLeast(width1)
+ }
+
+ @Test
+ fun testWidthDoesNotDecrease_narrowerFont_sameNumberOfChars() {
+ // GIVEN time is wide
+ clockView.text = ZERO_3
+ measureClock()
+ val width1 = clockView.measuredWidth
+
+ // WHEN the text changes to a narrower font
+ clockView.text = ONE_3
+ measureClock()
+ val width2 = clockView.measuredWidth
+
+ // THEN the width should not have decreased, and they should in fact be the same
+ assertThat(width2).isEqualTo(width1)
+ }
+
+ @Test
+ fun testWidthIncreases_whenCharsChanges() {
+ // GIVEN wide 3-char text
+ clockView.text = ZERO_3
+ measureClock()
+ val width1 = clockView.measuredWidth
+
+ // WHEN text changes to 4-char wide text
+ clockView.text = ZERO_4
+ measureClock()
+ val width2 = clockView.measuredWidth
+
+ // THEN the text field is wider
+ assertThat(width2).isGreaterThan(width1)
+ }
+
+ @Test
+ fun testWidthDecreases_whenCharsChange_longToShort() {
+ // GIVEN wide 4-char text
+ clockView.text = ZERO_4
+ measureClock()
+ val width1 = clockView.measuredWidth
+
+ // WHEN number of characters changes to a narrow 3-char text
+ clockView.text = ONE_3
+ measureClock()
+ val width2 = clockView.measuredWidth
+
+ // THEN the width can shrink, because number of chars changed
+ assertThat(width2).isLessThan(width1)
+ }
+
+ private fun measureClock() {
+ clockView.measure(
+ makeMeasureSpec(0, UNSPECIFIED),
+ makeMeasureSpec(0, UNSPECIFIED)
+ )
+ }
+}
+
+/**
+ * In a non-monospace font, it is expected that "0:00" is wider than "1:11"
+ */
+private const val ZERO_3 = "0:00"
+private const val ZERO_4 = "00:00"
+private const val ONE_3 = "1:11"
+private const val ONE_4 = "11:11"
diff --git a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java
index 2eae6af..222d779 100644
--- a/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java
+++ b/services/cloudsearch/java/com/android/server/cloudsearch/CloudSearchPerUserService.java
@@ -147,7 +147,10 @@
}
});
if (sessionInfo.linkToDeath()) {
- mCallbackQueue.put(requestId, sessionInfo);
+ CloudSearchCallbackInfo removedInfo = mCallbackQueue.put(requestId, sessionInfo);
+ if (removedInfo != null) {
+ removedInfo.destroy();
+ }
} else {
// destroy the session if calling process is already dead
onDestroyLocked(requestId);
diff --git a/services/core/java/com/android/server/CircularQueue.java b/services/core/java/com/android/server/CircularQueue.java
index aac6752..4538078 100644
--- a/services/core/java/com/android/server/CircularQueue.java
+++ b/services/core/java/com/android/server/CircularQueue.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.annotation.Nullable;
import android.util.ArrayMap;
import java.util.Collection;
@@ -43,16 +44,19 @@
/**
* Put a (key|value) pair in the CircularQueue. Only the key will be added to the queue. Value
* will be added to the ArrayMap.
- * @return {@code true} (as specified by {@link Collection#add})
+ * @return the most recently removed value if keys were removed, or {@code null} if no keys were
+ * removed.
*/
- public boolean put(K key, V value) {
+ @Nullable
+ public V put(K key, V value) {
super.add(key);
mArrayMap.put(key, value);
+ V removedValue = null;
while (size() > mLimit) {
K removedKey = super.remove();
- mArrayMap.remove(removedKey);
+ removedValue = mArrayMap.remove(removedKey);
}
- return true;
+ return removedValue;
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 163df10..3a5a866 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4859,7 +4859,8 @@
thread.runIsolatedEntryPoint(
app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs());
} else if (instr2 != null) {
- thread.bindApplication(processName, appInfo, app.sdkSandboxClientAppPackage,
+ thread.bindApplication(processName, appInfo,
+ app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
providerList,
instr2.mClass,
profilerInfo, instr2.mArguments,
@@ -4874,7 +4875,8 @@
app.getDisabledCompatChanges(), serializedSystemFontMap,
app.getStartElapsedTime(), app.getStartUptime());
} else {
- thread.bindApplication(processName, appInfo, app.sdkSandboxClientAppPackage,
+ thread.bindApplication(processName, appInfo,
+ app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
providerList, null, profilerInfo, null, null, null, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.isPersistent(),
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b4ff870..ac5cb2d 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -87,6 +87,7 @@
final String processName; // name of the process
final String sdkSandboxClientAppPackage; // if this is an sdk sandbox process, name of the
// app package for which it is running
+ final String sdkSandboxClientAppVolumeUuid; // uuid of the app for which the sandbox is running
/**
* Overall state of process's uid.
@@ -535,6 +536,11 @@
userId = UserHandle.getUserId(_uid);
processName = _processName;
sdkSandboxClientAppPackage = _sdkSandboxClientAppPackage;
+ if (isSdkSandbox) {
+ sdkSandboxClientAppVolumeUuid = getClientInfoForSdkSandbox().volumeUuid;
+ } else {
+ sdkSandboxClientAppVolumeUuid = null;
+ }
mPersistent = false;
mRemoved = false;
mProfile = new ProcessProfileRecord(this);
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 3f00244..61350bb 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -380,12 +380,12 @@
}
checkDataOwner(clip, intendingUid);
synchronized (mLock) {
- scheduleAutoClear(userId);
+ scheduleAutoClear(userId, intendingUid);
setPrimaryClipInternalLocked(clip, intendingUid, sourcePackage);
}
}
- private void scheduleAutoClear(@UserIdInt int userId) {
+ private void scheduleAutoClear(@UserIdInt int userId, int intendingUid) {
final long oldIdentity = Binder.clearCallingIdentity();
try {
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
@@ -393,7 +393,7 @@
mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
userId);
Message clearMessage = Message.obtain(mClipboardClearHandler,
- ClipboardClearHandler.MSG_CLEAR, userId, 0, userId);
+ ClipboardClearHandler.MSG_CLEAR, userId, intendingUid, userId);
mClipboardClearHandler.sendMessageDelayed(clearMessage,
getTimeoutForAutoClear());
}
@@ -446,7 +446,7 @@
showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
notifyTextClassifierLocked(clipboard, pkg, intendingUid);
if (clipboard.primaryClip != null) {
- scheduleAutoClear(userId);
+ scheduleAutoClear(userId, intendingUid);
}
return clipboard.primaryClip;
}
@@ -554,11 +554,12 @@
switch (msg.what) {
case MSG_CLEAR:
final int userId = msg.arg1;
+ final int intendingUid = msg.arg2;
synchronized (mLock) {
if (getClipboardLocked(userId).primaryClip != null) {
FrameworkStatsLog.write(FrameworkStatsLog.CLIPBOARD_CLEARED,
FrameworkStatsLog.CLIPBOARD_CLEARED__SOURCE__AUTO_CLEAR);
- setPrimaryClipInternalLocked(null, Binder.getCallingUid(), null);
+ setPrimaryClipInternalLocked(null, intendingUid, null);
}
}
break;
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 954b930..f93e06d 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -78,6 +78,7 @@
import com.android.internal.os.BinderDeathDispatcher;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -1542,11 +1543,62 @@
return ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET_WITH_TEMP;
}
if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND || isUidActive) {
+ FrameworkStatsLog.write(FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED,
+ callingUid, getProcStateForStatsd(procState), isUidActive);
return ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET;
}
return ContentResolver.SYNC_EXEMPTION_NONE;
}
+ private int getProcStateForStatsd(int procState) {
+ switch (procState) {
+ case ActivityManager.PROCESS_STATE_UNKNOWN:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__UNKNOWN;
+ case ActivityManager.PROCESS_STATE_PERSISTENT:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__PERSISTENT;
+ case ActivityManager.PROCESS_STATE_PERSISTENT_UI:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__PERSISTENT_UI;
+ case ActivityManager.PROCESS_STATE_TOP:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__TOP;
+ case ActivityManager.PROCESS_STATE_BOUND_TOP:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__BOUND_TOP;
+ case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__FOREGROUND_SERVICE;
+ case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
+ return FrameworkStatsLog
+ .SYNC_EXEMPTION_OCCURRED__PROC_STATE__BOUND_FOREGROUND_SERVICE;
+ case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__IMPORTANT_FOREGROUND;
+ case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__IMPORTANT_BACKGROUND;
+ case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__TRANSIENT_BACKGROUND;
+ case ActivityManager.PROCESS_STATE_BACKUP:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__BACKUP;
+ case ActivityManager.PROCESS_STATE_SERVICE:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__SERVICE;
+ case ActivityManager.PROCESS_STATE_RECEIVER:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__RECEIVER;
+ case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__TOP_SLEEPING;
+ case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__HEAVY_WEIGHT;
+ case ActivityManager.PROCESS_STATE_LAST_ACTIVITY:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__LAST_ACTIVITY;
+ case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__CACHED_ACTIVITY;
+ case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+ return FrameworkStatsLog
+ .SYNC_EXEMPTION_OCCURRED__PROC_STATE__CACHED_ACTIVITY_CLIENT;
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__CACHED_RECENT;
+ case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__CACHED_EMPTY;
+ default:
+ return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__UNKNOWN;
+ }
+ }
+
/** {@hide} */
@VisibleForTesting
public static final class ObserverNode {
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
index 91d2010..b7fde43 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
@@ -546,7 +546,9 @@
ai.windowLayout = a.getWindowLayout();
ai.attributionTags = a.getAttributionTags();
if ((flags & PackageManager.GET_META_DATA) != 0) {
- ai.metaData = a.getMetaData();
+ var metaData = a.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ ai.metaData = metaData.isEmpty() ? null : metaData;
}
ai.applicationInfo = applicationInfo;
ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
@@ -599,7 +601,9 @@
si.mForegroundServiceType = s.getForegroundServiceType();
si.applicationInfo = applicationInfo;
if ((flags & PackageManager.GET_META_DATA) != 0) {
- si.metaData = s.getMetaData();
+ var metaData = s.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ si.metaData = metaData.isEmpty() ? null : metaData;
}
return si;
}
@@ -660,7 +664,9 @@
pi.uriPermissionPatterns = null;
}
if ((flags & PackageManager.GET_META_DATA) != 0) {
- pi.metaData = p.getMetaData();
+ var metaData = p.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ pi.metaData = metaData.isEmpty() ? null : metaData;
}
pi.applicationInfo = applicationInfo;
return pi;
@@ -706,7 +712,9 @@
if ((flags & PackageManager.GET_META_DATA) == 0) {
return ii;
}
- ii.metaData = i.getMetaData();
+ var metaData = i.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ ii.metaData = metaData.isEmpty() ? null : metaData;
return ii;
}
@@ -729,7 +737,9 @@
if ((flags & PackageManager.GET_META_DATA) == 0) {
return pi;
}
- pi.metaData = p.getMetaData();
+ var metaData = p.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ pi.metaData = metaData.isEmpty() ? null : metaData;
return pi;
}
@@ -753,7 +763,9 @@
if ((flags & PackageManager.GET_META_DATA) == 0) {
return pgi;
}
- pgi.metaData = pg.getMetaData();
+ var metaData = pg.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ pgi.metaData = metaData.isEmpty() ? null : metaData;
return pgi;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 0aab186..7240fd5 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3231,7 +3231,7 @@
if (task.getActivity(activity -> !activity.finishing && activity.mUserId == userId)
!= null) {
mService.getTaskChangeNotificationController().notifyTaskProfileLocked(
- task.mTaskId, userId);
+ task.getTaskInfo());
}
}, true /* traverseTopToBottom */);
}
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 61963c4..49d064f 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -144,7 +144,7 @@
};
private final TaskStackConsumer mNotifyTaskProfileLocked = (l, m) -> {
- l.onTaskProfileLocked(m.arg1, m.arg2);
+ l.onTaskProfileLocked((RunningTaskInfo) m.obj);
};
private final TaskStackConsumer mNotifyTaskSnapshotChanged = (l, m) -> {
@@ -467,9 +467,9 @@
* Notify listeners that the task has been put in a locked state because one or more of the
* activities inside it belong to a managed profile user that has been locked.
*/
- void notifyTaskProfileLocked(int taskId, int userId) {
- final Message msg = mHandler.obtainMessage(NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG, taskId,
- userId);
+ void notifyTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo) {
+ final Message msg = mHandler.obtainMessage(NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG,
+ taskInfo);
forAllLocalListeners(mNotifyTaskProfileLocked, msg);
msg.sendToTarget();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/CircularQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/CircularQueueTest.java
index fac37fd..01bafc1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/CircularQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/CircularQueueTest.java
@@ -43,11 +43,12 @@
mQueue = new CircularQueue<>(LIMIT);
mQueue.put(1, "A");
mQueue.put(2, "B");
- mQueue.put(3, "C");
+ String removedElement = mQueue.put(3, "C");
assertNull(mQueue.getElement(1));
assertEquals(mQueue.getElement(2), "B");
assertEquals(mQueue.getElement(3), "C");
-
+ // Confirming that put is returning the deleted element
+ assertEquals(removedElement, "A");
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
index 534d0a1..f9f6fe9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
@@ -46,6 +46,8 @@
@Mock
private CompleteEconomicPolicy mEconomicPolicy;
@Mock
+ private Analyst mAnalyst;
+ @Mock
private Context mContext;
@Mock
private InternalResourceService mIrs;
@@ -53,8 +55,8 @@
private Scribe mScribe;
private static class MockScribe extends Scribe {
- MockScribe(InternalResourceService irs) {
- super(irs);
+ MockScribe(InternalResourceService irs, Analyst analyst) {
+ super(irs, analyst);
}
@Override
@@ -74,7 +76,7 @@
doReturn(mEconomicPolicy).when(mIrs).getCompleteEconomicPolicyLocked();
doReturn(mIrs).when(mIrs).getLock();
doReturn(mock(AlarmManager.class)).when(mContext).getSystemService(Context.ALARM_SERVICE);
- mScribe = new MockScribe(mIrs);
+ mScribe = new MockScribe(mIrs, mAnalyst);
}
@After
@@ -86,7 +88,7 @@
@Test
public void testRecordTransaction_UnderMax() {
- Agent agent = new Agent(mIrs, mScribe);
+ Agent agent = new Agent(mIrs, mScribe, mAnalyst);
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
@@ -115,7 +117,7 @@
@Test
public void testRecordTransaction_MaxConsumptionLimit() {
- Agent agent = new Agent(mIrs, mScribe);
+ Agent agent = new Agent(mIrs, mScribe, mAnalyst);
Ledger ledger = new Ledger();
doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
@@ -162,7 +164,7 @@
@Test
public void testRecordTransaction_MaxSatiatedBalance() {
- Agent agent = new Agent(mIrs, mScribe);
+ Agent agent = new Agent(mIrs, mScribe, mAnalyst);
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
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 14f95e9..721777c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -16,7 +16,7 @@
package com.android.server.tare;
-
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.junit.Assert.assertEquals;
@@ -41,6 +41,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -66,8 +68,11 @@
private Scribe mScribeUnderTest;
private File mTestFileDir;
private final List<PackageInfo> mInstalledPackages = new ArrayList<>();
+ private final List<Analyst.Report> mReports = new ArrayList<>();
@Mock
+ private Analyst mAnalyst;
+ @Mock
private InternalResourceService mIrs;
private Context getContext() {
@@ -84,11 +89,12 @@
when(mIrs.getLock()).thenReturn(new Object());
when(mIrs.isEnabled()).thenReturn(true);
when(mIrs.getInstalledPackages()).thenReturn(mInstalledPackages);
+ when(mAnalyst.getReports()).thenReturn(mReports);
mTestFileDir = new File(getContext().getFilesDir(), "scribe_test");
//noinspection ResultOfMethodCallIgnored
mTestFileDir.mkdirs();
Log.d(TAG, "Saving data to '" + mTestFileDir + "'");
- mScribeUnderTest = new Scribe(mIrs, mTestFileDir);
+ mScribeUnderTest = new Scribe(mIrs, mAnalyst, mTestFileDir);
addInstalledPackage(TEST_USER_ID, TEST_PACKAGE);
}
@@ -105,6 +111,62 @@
}
@Test
+ public void testWritingAnalystReportsToDisk() {
+ ArgumentCaptor<List<Analyst.Report>> reportCaptor =
+ ArgumentCaptor.forClass(List.class);
+
+ InOrder inOrder = inOrder(mAnalyst);
+
+ // Empty set
+ mReports.clear();
+ mScribeUnderTest.writeImmediatelyForTesting();
+ mScribeUnderTest.loadFromDiskLocked();
+ inOrder.verify(mAnalyst).loadReports(reportCaptor.capture());
+ List<Analyst.Report> result = reportCaptor.getValue();
+ assertReportListsEqual(mReports, result);
+
+ Analyst.Report report1 = new Analyst.Report();
+ report1.cumulativeBatteryDischarge = 1;
+ report1.currentBatteryLevel = 2;
+ report1.cumulativeProfit = 3;
+ report1.numProfitableActions = 4;
+ report1.cumulativeLoss = 5;
+ report1.numUnprofitableActions = 6;
+ report1.cumulativeRewards = 7;
+ report1.numRewards = 8;
+ report1.cumulativePositiveRegulations = 9;
+ report1.numPositiveRegulations = 10;
+ report1.cumulativeNegativeRegulations = 11;
+ report1.numNegativeRegulations = 12;
+ mReports.add(report1);
+ mScribeUnderTest.writeImmediatelyForTesting();
+ mScribeUnderTest.loadFromDiskLocked();
+ inOrder.verify(mAnalyst).loadReports(reportCaptor.capture());
+ result = reportCaptor.getValue();
+ assertReportListsEqual(mReports, result);
+
+ Analyst.Report report2 = new Analyst.Report();
+ report2.cumulativeBatteryDischarge = 10;
+ report2.currentBatteryLevel = 20;
+ report2.cumulativeProfit = 30;
+ report2.numProfitableActions = 40;
+ report2.cumulativeLoss = 50;
+ report2.numUnprofitableActions = 60;
+ report2.cumulativeRewards = 70;
+ report2.numRewards = 80;
+ report2.cumulativePositiveRegulations = 90;
+ report2.numPositiveRegulations = 100;
+ report2.cumulativeNegativeRegulations = 110;
+ report2.numNegativeRegulations = 120;
+ mReports.add(report2);
+ mScribeUnderTest.writeImmediatelyForTesting();
+ mScribeUnderTest.loadFromDiskLocked();
+ inOrder.verify(mAnalyst).loadReports(reportCaptor.capture());
+ result = reportCaptor.getValue();
+ assertReportListsEqual(mReports, result);
+ }
+
+ @Test
public void testWriteHighLevelStateToDisk() {
long lastReclamationTime = System.currentTimeMillis();
long remainingConsumableCakes = 2000L;
@@ -277,6 +339,49 @@
}
}
+ private void assertReportListsEqual(List<Analyst.Report> expected,
+ List<Analyst.Report> actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.size(), actual.size());
+ for (int i = 0; i < expected.size(); ++i) {
+ Analyst.Report eReport = expected.get(i);
+ Analyst.Report aReport = actual.get(i);
+ if (eReport == null) {
+ assertNull(aReport);
+ continue;
+ }
+ assertNotNull(aReport);
+ assertEquals("Reports #" + i + " cumulativeBatteryDischarge are not equal",
+ eReport.cumulativeBatteryDischarge, aReport.cumulativeBatteryDischarge);
+ assertEquals("Reports #" + i + " currentBatteryLevel are not equal",
+ eReport.currentBatteryLevel, aReport.currentBatteryLevel);
+ assertEquals("Reports #" + i + " cumulativeProfit are not equal",
+ eReport.cumulativeProfit, aReport.cumulativeProfit);
+ assertEquals("Reports #" + i + " numProfitableActions are not equal",
+ eReport.numProfitableActions, aReport.numProfitableActions);
+ assertEquals("Reports #" + i + " cumulativeLoss are not equal",
+ eReport.cumulativeLoss, aReport.cumulativeLoss);
+ assertEquals("Reports #" + i + " numUnprofitableActions are not equal",
+ eReport.numUnprofitableActions, aReport.numUnprofitableActions);
+ assertEquals("Reports #" + i + " cumulativeRewards are not equal",
+ eReport.cumulativeRewards, aReport.cumulativeRewards);
+ assertEquals("Reports #" + i + " numRewards are not equal",
+ eReport.numRewards, aReport.numRewards);
+ assertEquals("Reports #" + i + " cumulativePositiveRegulations are not equal",
+ eReport.cumulativePositiveRegulations, aReport.cumulativePositiveRegulations);
+ assertEquals("Reports #" + i + " numPositiveRegulations are not equal",
+ eReport.numPositiveRegulations, aReport.numPositiveRegulations);
+ assertEquals("Reports #" + i + " cumulativeNegativeRegulations are not equal",
+ eReport.cumulativeNegativeRegulations, aReport.cumulativeNegativeRegulations);
+ assertEquals("Reports #" + i + " numNegativeRegulations are not equal",
+ eReport.numNegativeRegulations, aReport.numNegativeRegulations);
+ }
+ }
+
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/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 952200d..2fd7853 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -23,6 +23,7 @@
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.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -65,6 +66,7 @@
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.permission.CompatibilityPermissionInfo;
+import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.component.ParsedActivity;
import com.android.server.pm.pkg.component.ParsedActivityImpl;
@@ -85,6 +87,7 @@
import com.android.server.pm.pkg.component.ParsedServiceImpl;
import com.android.server.pm.pkg.component.ParsedUsesPermission;
import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
import com.android.server.pm.pkg.parsing.ParsingPackage;
import org.junit.Before;
@@ -596,6 +599,38 @@
}
}
+ @Test
+ public void testNoComponentMetadataIsCoercedToNullForInfoObject() throws Exception {
+ final File testFile = extractFile(TEST_APP4_APK);
+ try {
+ final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+ ApplicationInfo appInfo = PackageInfoWithoutStateUtils.generateApplicationInfo(pkg, 0,
+ PackageUserState.DEFAULT, 0);
+ for (ParsedActivity activity : pkg.getActivities()) {
+ assertNotNull(activity.getMetaData());
+ assertNull(PackageInfoWithoutStateUtils.generateActivityInfoUnchecked(activity, 0,
+ appInfo).metaData);
+ }
+ for (ParsedProvider provider : pkg.getProviders()) {
+ assertNotNull(provider.getMetaData());
+ assertNull(PackageInfoWithoutStateUtils.generateProviderInfoUnchecked(provider, 0,
+ appInfo).metaData);
+ }
+ for (ParsedActivity receiver : pkg.getReceivers()) {
+ assertNotNull(receiver.getMetaData());
+ assertNull(PackageInfoWithoutStateUtils.generateActivityInfoUnchecked(receiver, 0,
+ appInfo).metaData);
+ }
+ for (ParsedService service : pkg.getServices()) {
+ assertNotNull(service.getMetaData());
+ assertNull(PackageInfoWithoutStateUtils.generateServiceInfoUnchecked(service, 0,
+ appInfo).metaData);
+ }
+ } finally {
+ testFile.delete();
+ }
+ }
+
/**
* A trivial subclass of package parser that only caches the package name, and throws away
* all other information.
diff --git a/services/tests/servicestests/src/com/android/server/tare/AnalystTest.java b/services/tests/servicestests/src/com/android/server/tare/AnalystTest.java
new file mode 100644
index 0000000..2b527a2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/tare/AnalystTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.server.tare;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Test that the Analyst processes transactions correctly. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AnalystTest {
+
+ @Test
+ public void testInitialState() {
+ final Analyst analyst = new Analyst();
+ assertEquals(0, analyst.getReports().size());
+ }
+
+ @Test
+ public void testBatteryLevelChange() {
+ final Analyst analyst = new Analyst();
+
+ Analyst.Report expected = new Analyst.Report();
+ expected.currentBatteryLevel = 55;
+ analyst.noteBatteryLevelChange(55);
+ assertEquals(1, analyst.getReports().size());
+ assertReportsEqual(expected, analyst.getReports().get(0));
+
+ // Discharging
+ analyst.noteBatteryLevelChange(54);
+ expected.currentBatteryLevel = 54;
+ expected.cumulativeBatteryDischarge = 1;
+ assertEquals(1, analyst.getReports().size());
+ assertReportsEqual(expected, analyst.getReports().get(0));
+ analyst.noteBatteryLevelChange(50);
+ expected.currentBatteryLevel = 50;
+ expected.cumulativeBatteryDischarge = 5;
+ assertEquals(1, analyst.getReports().size());
+ assertReportsEqual(expected, analyst.getReports().get(0));
+
+ // Charging
+ analyst.noteBatteryLevelChange(51);
+ expected.currentBatteryLevel = 51;
+ assertEquals(1, analyst.getReports().size());
+ assertReportsEqual(expected, analyst.getReports().get(0));
+ analyst.noteBatteryLevelChange(55);
+ expected.currentBatteryLevel = 55;
+ assertEquals(1, analyst.getReports().size());
+ assertReportsEqual(expected, analyst.getReports().get(0));
+
+ // Reset
+ analyst.noteBatteryLevelChange(100);
+ assertEquals(2, analyst.getReports().size());
+ assertReportsEqual(expected, analyst.getReports().get(0));
+ expected.currentBatteryLevel = 100;
+ expected.cumulativeBatteryDischarge = 0;
+ assertReportsEqual(expected, analyst.getReports().get(1));
+ }
+
+ @Test
+ public void testTransaction() {
+ runTestTransactions(new Analyst(), new Analyst.Report(), 1);
+ }
+
+ @Test
+ public void testTransaction_PeriodChange() {
+ final Analyst analyst = new Analyst();
+
+ Analyst.Report expected = new Analyst.Report();
+ expected.currentBatteryLevel = 55;
+ analyst.noteBatteryLevelChange(55);
+
+ runTestTransactions(analyst, expected, 1);
+
+ expected.currentBatteryLevel = 49;
+ expected.cumulativeBatteryDischarge = 6;
+ analyst.noteBatteryLevelChange(49);
+
+ runTestTransactions(analyst, expected, 1);
+
+ expected = new Analyst.Report();
+ expected.currentBatteryLevel = 100;
+ analyst.noteBatteryLevelChange(100);
+ expected.cumulativeBatteryDischarge = 0;
+
+ runTestTransactions(analyst, expected, 2);
+ }
+
+ private void runTestTransactions(Analyst analyst, Analyst.Report lastExpectedReport,
+ int numExpectedReports) {
+ Analyst.Report expected = lastExpectedReport;
+
+ // Profit
+ analyst.noteTransaction(
+ new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_ACTION, null, -51, 1));
+ expected.cumulativeProfit += 50;
+ expected.numProfitableActions += 1;
+ assertEquals(numExpectedReports, analyst.getReports().size());
+ assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
+
+ // Loss
+ analyst.noteTransaction(
+ new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_ACTION, null, -51, 100));
+ expected.cumulativeLoss += 49;
+ expected.numUnprofitableActions += 1;
+ assertEquals(numExpectedReports, analyst.getReports().size());
+ assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
+
+ // Reward
+ analyst.noteTransaction(
+ new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD, null, 51, 0));
+ expected.cumulativeRewards += 51;
+ expected.numRewards += 1;
+ assertEquals(numExpectedReports, analyst.getReports().size());
+ assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
+
+ // Regulations
+ analyst.noteTransaction(
+ new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REGULATION, null, 25, 0));
+ expected.cumulativePositiveRegulations += 25;
+ expected.numPositiveRegulations += 1;
+ assertEquals(numExpectedReports, analyst.getReports().size());
+ assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
+ analyst.noteTransaction(
+ new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REGULATION, null, -25, 0));
+ expected.cumulativeNegativeRegulations += 25;
+ expected.numNegativeRegulations += 1;
+ assertEquals(numExpectedReports, analyst.getReports().size());
+ assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
+
+ // No-ops
+ analyst.noteTransaction(
+ new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_ACTION, null, -100, 100));
+ analyst.noteTransaction(
+ new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REGULATION, null, 0, 0));
+ analyst.noteTransaction(
+ new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD, null, 0, 0));
+ assertEquals(numExpectedReports, analyst.getReports().size());
+ }
+
+ @Test
+ public void testLoadReports() {
+ final Analyst analyst = new Analyst();
+
+ List<Analyst.Report> expected = new ArrayList<>();
+ analyst.loadReports(expected);
+ assertReportListsEqual(expected, analyst.getReports());
+
+ Analyst.Report report1 = new Analyst.Report();
+ report1.cumulativeBatteryDischarge = 1;
+ report1.currentBatteryLevel = 2;
+ report1.cumulativeProfit = 3;
+ report1.numProfitableActions = 4;
+ report1.cumulativeLoss = 5;
+ report1.numUnprofitableActions = 6;
+ report1.cumulativeRewards = 7;
+ report1.numRewards = 8;
+ report1.cumulativePositiveRegulations = 9;
+ report1.numPositiveRegulations = 10;
+ report1.cumulativeNegativeRegulations = 11;
+ report1.numNegativeRegulations = 12;
+ expected.add(report1);
+ analyst.loadReports(expected);
+ assertReportListsEqual(expected, analyst.getReports());
+
+ Analyst.Report report2 = new Analyst.Report();
+ report2.cumulativeBatteryDischarge = 10;
+ report2.currentBatteryLevel = 20;
+ report2.cumulativeProfit = 30;
+ report2.numProfitableActions = 40;
+ report2.cumulativeLoss = 50;
+ report2.numUnprofitableActions = 60;
+ report2.cumulativeRewards = 70;
+ report2.numRewards = 80;
+ report2.cumulativePositiveRegulations = 90;
+ report2.numPositiveRegulations = 100;
+ report2.cumulativeNegativeRegulations = 110;
+ report2.numNegativeRegulations = 120;
+ expected.add(report2);
+ analyst.loadReports(expected);
+ assertReportListsEqual(expected, analyst.getReports());
+ }
+
+ private void assertReportsEqual(Analyst.Report expected, Analyst.Report actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.cumulativeBatteryDischarge, actual.cumulativeBatteryDischarge);
+ assertEquals(expected.currentBatteryLevel, actual.currentBatteryLevel);
+ assertEquals(expected.cumulativeProfit, actual.cumulativeProfit);
+ assertEquals(expected.numProfitableActions, actual.numProfitableActions);
+ assertEquals(expected.cumulativeLoss, actual.cumulativeLoss);
+ assertEquals(expected.numUnprofitableActions, actual.numUnprofitableActions);
+ assertEquals(expected.cumulativeRewards, actual.cumulativeRewards);
+ assertEquals(expected.numRewards, actual.numRewards);
+ assertEquals(expected.cumulativePositiveRegulations, actual.cumulativePositiveRegulations);
+ assertEquals(expected.numPositiveRegulations, actual.numPositiveRegulations);
+ assertEquals(expected.cumulativeNegativeRegulations, actual.cumulativeNegativeRegulations);
+ assertEquals(expected.numNegativeRegulations, actual.numNegativeRegulations);
+ }
+
+ private void assertReportListsEqual(List<Analyst.Report> expected,
+ List<Analyst.Report> actual) {
+ if (expected == null) {
+ assertNull(actual);
+ return;
+ }
+ assertNotNull(actual);
+ assertEquals(expected.size(), actual.size());
+ for (int i = 0; i < expected.size(); ++i) {
+ assertReportsEqual(expected.get(i), actual.get(i));
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 8ef9ada..700fadd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -1097,7 +1097,7 @@
TaskChangeNotificationController controller = mAtm.getTaskChangeNotificationController();
spyOn(controller);
mWm.mRoot.lockAllProfileTasks(profileUserId);
- verify(controller).notifyTaskProfileLocked(eq(task.mTaskId), eq(profileUserId));
+ verify(controller).notifyTaskProfileLocked(any());
// Create the work lock activity on top of the task
final ActivityRecord workLockActivity = new ActivityBuilder(mAtm).setTask(task).build();
@@ -1107,7 +1107,7 @@
// Make sure the listener won't be notified again.
clearInvocations(controller);
mWm.mRoot.lockAllProfileTasks(profileUserId);
- verify(controller, never()).notifyTaskProfileLocked(anyInt(), anyInt());
+ verify(controller, never()).notifyTaskProfileLocked(any());
}
/**